//! Rustler boundary for the `sidereon-core` RTCM 3.x stream decoder.
//!
//! Pure glue over `sidereon_core::rtcm`: it forwards a byte buffer to the crate's
//! forgiving frame scanner / message decoder and re-shapes the canonical message
//! IR into Elixir-friendly maps. No bit-field layout, CRC, or framing math lives
//! here. Each decoded message crosses as a `{type_atom, fields_map}` pair; the raw
//! transmitted integer fields are widened to signed 64-bit for a uniform numeric
//! boundary (decode is one-way, so no precision is lost relative to the
//! scaling-helper conversions the crate exposes). An unrecognized message number
//! is preserved as `{:unsupported, %{message_number, body}}`.
use rustler::{Encoder, Env, Error, NifResult, OwnedBinary, Term};
use sidereon_core::rtcm::{
self, AntennaDescriptor, GlonassEphemeris, GpsEphemeris, Message, MsmHeader, MsmKind,
MsmMessage, MsmSatellite, MsmSignal, StationCoordinates, UnsupportedMessage,
};
use sidereon_core::GnssSystem;
mod atoms {
rustler::atoms! {
ok,
error,
station_coordinates,
antenna_descriptor,
gps_ephemeris,
glonass_ephemeris,
msm,
unsupported
}
}
#[derive(Debug, Clone, rustler::NifMap)]
struct StationCoordinatesFields {
message_number: i64,
reference_station_id: i64,
itrf_realization_year: i64,
gps_indicator: bool,
glonass_indicator: bool,
galileo_indicator: bool,
reference_station_indicator: bool,
ecef_x: i64,
single_receiver_oscillator: bool,
reserved: bool,
ecef_y: i64,
quarter_cycle_indicator: i64,
ecef_z: i64,
antenna_height: Option<i64>,
x_m: f64,
y_m: f64,
z_m: f64,
antenna_height_m: Option<f64>,
}
impl From<StationCoordinates> for StationCoordinatesFields {
fn from(s: StationCoordinates) -> Self {
Self {
message_number: s.message_number as i64,
reference_station_id: s.reference_station_id as i64,
itrf_realization_year: s.itrf_realization_year as i64,
gps_indicator: s.gps_indicator,
glonass_indicator: s.glonass_indicator,
galileo_indicator: s.galileo_indicator,
reference_station_indicator: s.reference_station_indicator,
ecef_x: s.ecef_x,
single_receiver_oscillator: s.single_receiver_oscillator,
reserved: s.reserved,
ecef_y: s.ecef_y,
quarter_cycle_indicator: s.quarter_cycle_indicator as i64,
ecef_z: s.ecef_z,
antenna_height: s.antenna_height.map(|h| h as i64),
x_m: s.x_m(),
y_m: s.y_m(),
z_m: s.z_m(),
antenna_height_m: s.antenna_height_m(),
}
}
}
#[derive(Debug, Clone, rustler::NifMap)]
struct AntennaDescriptorFields {
message_number: i64,
reference_station_id: i64,
antenna_descriptor: String,
antenna_setup_id: i64,
antenna_serial_number: Option<String>,
receiver_type: Option<String>,
receiver_firmware_version: Option<String>,
receiver_serial_number: Option<String>,
}
impl From<AntennaDescriptor> for AntennaDescriptorFields {
fn from(a: AntennaDescriptor) -> Self {
Self {
message_number: a.message_number as i64,
reference_station_id: a.reference_station_id as i64,
antenna_descriptor: a.antenna_descriptor,
antenna_setup_id: a.antenna_setup_id as i64,
antenna_serial_number: a.antenna_serial_number,
receiver_type: a.receiver_type,
receiver_firmware_version: a.receiver_firmware_version,
receiver_serial_number: a.receiver_serial_number,
}
}
}
#[derive(Debug, Clone, rustler::NifMap)]
struct GpsEphemerisFields {
satellite_id: i64,
week_number: i64,
sv_accuracy: i64,
code_on_l2: i64,
idot: i64,
iode: i64,
t_oc: i64,
a_f2: i64,
a_f1: i64,
a_f0: i64,
iodc: i64,
c_rs: i64,
delta_n: i64,
m0: i64,
c_uc: i64,
eccentricity: i64,
c_us: i64,
sqrt_a: i64,
t_oe: i64,
c_ic: i64,
omega0: i64,
c_is: i64,
i0: i64,
c_rc: i64,
omega: i64,
omega_dot: i64,
t_gd: i64,
sv_health: i64,
l2_p_data_flag: bool,
fit_interval: bool,
}
impl From<GpsEphemeris> for GpsEphemerisFields {
fn from(e: GpsEphemeris) -> Self {
Self {
satellite_id: e.satellite_id as i64,
week_number: e.week_number as i64,
sv_accuracy: e.sv_accuracy as i64,
code_on_l2: e.code_on_l2 as i64,
idot: e.idot as i64,
iode: e.iode as i64,
t_oc: e.t_oc as i64,
a_f2: e.a_f2 as i64,
a_f1: e.a_f1 as i64,
a_f0: e.a_f0 as i64,
iodc: e.iodc as i64,
c_rs: e.c_rs as i64,
delta_n: e.delta_n as i64,
m0: e.m0,
c_uc: e.c_uc as i64,
eccentricity: e.eccentricity as i64,
c_us: e.c_us as i64,
sqrt_a: e.sqrt_a as i64,
t_oe: e.t_oe as i64,
c_ic: e.c_ic as i64,
omega0: e.omega0,
c_is: e.c_is as i64,
i0: e.i0,
c_rc: e.c_rc as i64,
omega: e.omega,
omega_dot: e.omega_dot as i64,
t_gd: e.t_gd as i64,
sv_health: e.sv_health as i64,
l2_p_data_flag: e.l2_p_data_flag,
fit_interval: e.fit_interval,
}
}
}
#[derive(Debug, Clone, rustler::NifMap)]
struct GlonassEphemerisFields {
satellite_id: i64,
frequency_channel: i64,
almanac_health: bool,
almanac_health_availability: bool,
p1: i64,
t_k: i64,
b_n_msb: bool,
p2: bool,
t_b: i64,
xn_dot: i64,
xn: i64,
xn_dot_dot: i64,
yn_dot: i64,
yn: i64,
yn_dot_dot: i64,
zn_dot: i64,
zn: i64,
zn_dot_dot: i64,
p3: bool,
gamma_n: i64,
m_p: i64,
m_l_n_third: bool,
tau_n: i64,
delta_tau_n: i64,
e_n: i64,
m_p4: bool,
m_f_t: i64,
m_n_t: i64,
m_m: i64,
additional_data_available: bool,
n_a: i64,
tau_c: i64,
m_n4: i64,
m_tau_gps: i64,
m_l_n_fifth: bool,
reserved: i64,
}
impl From<GlonassEphemeris> for GlonassEphemerisFields {
fn from(e: GlonassEphemeris) -> Self {
Self {
satellite_id: e.satellite_id as i64,
frequency_channel: e.frequency_channel as i64,
almanac_health: e.almanac_health,
almanac_health_availability: e.almanac_health_availability,
p1: e.p1 as i64,
t_k: e.t_k as i64,
b_n_msb: e.b_n_msb,
p2: e.p2,
t_b: e.t_b as i64,
xn_dot: e.xn_dot as i64,
xn: e.xn as i64,
xn_dot_dot: e.xn_dot_dot as i64,
yn_dot: e.yn_dot as i64,
yn: e.yn as i64,
yn_dot_dot: e.yn_dot_dot as i64,
zn_dot: e.zn_dot as i64,
zn: e.zn as i64,
zn_dot_dot: e.zn_dot_dot as i64,
p3: e.p3,
gamma_n: e.gamma_n as i64,
m_p: e.m_p as i64,
m_l_n_third: e.m_l_n_third,
tau_n: e.tau_n as i64,
delta_tau_n: e.delta_tau_n as i64,
e_n: e.e_n as i64,
m_p4: e.m_p4,
m_f_t: e.m_f_t as i64,
m_n_t: e.m_n_t as i64,
m_m: e.m_m as i64,
additional_data_available: e.additional_data_available,
n_a: e.n_a as i64,
tau_c: e.tau_c,
m_n4: e.m_n4 as i64,
m_tau_gps: e.m_tau_gps as i64,
m_l_n_fifth: e.m_l_n_fifth,
reserved: e.reserved as i64,
}
}
}
#[derive(Debug, Clone, rustler::NifMap)]
struct MsmHeaderFields {
reference_station_id: i64,
epoch_time: i64,
multiple_message: bool,
iods: i64,
reserved: i64,
clock_steering: i64,
external_clock: i64,
divergence_free_smoothing: bool,
smoothing_interval: i64,
}
impl From<MsmHeader> for MsmHeaderFields {
fn from(h: MsmHeader) -> Self {
Self {
reference_station_id: h.reference_station_id as i64,
epoch_time: h.epoch_time as i64,
multiple_message: h.multiple_message,
iods: h.iods as i64,
reserved: h.reserved as i64,
clock_steering: h.clock_steering as i64,
external_clock: h.external_clock as i64,
divergence_free_smoothing: h.divergence_free_smoothing,
smoothing_interval: h.smoothing_interval as i64,
}
}
}
#[derive(Debug, Clone, rustler::NifMap)]
struct MsmSatelliteFields {
id: i64,
rough_range_ms: i64,
rough_range_mod1: i64,
extended_info: Option<i64>,
rough_phase_range_rate_m_s: Option<i64>,
}
impl From<MsmSatellite> for MsmSatelliteFields {
fn from(s: MsmSatellite) -> Self {
Self {
id: s.id as i64,
rough_range_ms: s.rough_range_ms as i64,
rough_range_mod1: s.rough_range_mod1 as i64,
extended_info: s.extended_info.map(|v| v as i64),
rough_phase_range_rate_m_s: s.rough_phase_range_rate_m_s.map(|v| v as i64),
}
}
}
#[derive(Debug, Clone, rustler::NifMap)]
struct MsmSignalFields {
satellite_id: i64,
signal_id: i64,
fine_pseudorange: i64,
fine_phase_range: i64,
lock_time_indicator: i64,
half_cycle_ambiguity: bool,
cnr: i64,
fine_phase_range_rate: Option<i64>,
}
impl From<MsmSignal> for MsmSignalFields {
fn from(s: MsmSignal) -> Self {
Self {
satellite_id: s.satellite_id as i64,
signal_id: s.signal_id as i64,
fine_pseudorange: s.fine_pseudorange as i64,
fine_phase_range: s.fine_phase_range as i64,
lock_time_indicator: s.lock_time_indicator as i64,
half_cycle_ambiguity: s.half_cycle_ambiguity,
cnr: s.cnr as i64,
fine_phase_range_rate: s.fine_phase_range_rate.map(|v| v as i64),
}
}
}
#[derive(Debug, Clone, rustler::NifMap)]
struct MsmMessageFields {
message_number: i64,
system: String,
kind: String,
header: MsmHeaderFields,
satellites: Vec<MsmSatelliteFields>,
signals: Vec<MsmSignalFields>,
}
impl From<MsmMessage> for MsmMessageFields {
fn from(m: MsmMessage) -> Self {
let kind = match m.kind {
MsmKind::Msm4 => "msm4",
MsmKind::Msm7 => "msm7",
};
Self {
message_number: m.message_number as i64,
system: m.system.letter().to_string(),
kind: kind.to_string(),
header: m.header.into(),
satellites: m.satellites.into_iter().map(Into::into).collect(),
signals: m.signals.into_iter().map(Into::into).collect(),
}
}
}
#[derive(Debug, Clone, rustler::NifMap)]
struct UnsupportedFields {
message_number: i64,
body: Vec<u8>,
}
/// Construction input for a 1005 / 1006 station antenna reference point.
///
/// Carries only the raw transmitted fields, so a caller builds a message from
/// scratch without supplying the scaled `x_m`/`y_m`/`z_m` outputs the decoder
/// derives. A round-trip caller can also pass the full decoded map directly: the
/// extra derived keys are ignored at decode.
#[derive(Debug, Clone, rustler::NifMap)]
struct StationCoordinatesInput {
message_number: i64,
reference_station_id: i64,
itrf_realization_year: i64,
gps_indicator: bool,
glonass_indicator: bool,
galileo_indicator: bool,
reference_station_indicator: bool,
ecef_x: i64,
single_receiver_oscillator: bool,
reserved: bool,
ecef_y: i64,
quarter_cycle_indicator: i64,
ecef_z: i64,
antenna_height: Option<i64>,
}
impl From<StationCoordinatesInput> for StationCoordinates {
fn from(s: StationCoordinatesInput) -> Self {
Self {
message_number: s.message_number as u16,
reference_station_id: s.reference_station_id as u16,
itrf_realization_year: s.itrf_realization_year as u8,
gps_indicator: s.gps_indicator,
glonass_indicator: s.glonass_indicator,
galileo_indicator: s.galileo_indicator,
reference_station_indicator: s.reference_station_indicator,
ecef_x: s.ecef_x,
single_receiver_oscillator: s.single_receiver_oscillator,
reserved: s.reserved,
ecef_y: s.ecef_y,
quarter_cycle_indicator: s.quarter_cycle_indicator as u8,
ecef_z: s.ecef_z,
antenna_height: s.antenna_height.map(|h| h as u16),
}
}
}
impl From<AntennaDescriptorFields> for AntennaDescriptor {
fn from(a: AntennaDescriptorFields) -> Self {
Self {
message_number: a.message_number as u16,
reference_station_id: a.reference_station_id as u16,
antenna_descriptor: a.antenna_descriptor,
antenna_setup_id: a.antenna_setup_id as u8,
antenna_serial_number: a.antenna_serial_number,
receiver_type: a.receiver_type,
receiver_firmware_version: a.receiver_firmware_version,
receiver_serial_number: a.receiver_serial_number,
}
}
}
impl From<GpsEphemerisFields> for GpsEphemeris {
fn from(e: GpsEphemerisFields) -> Self {
Self {
satellite_id: e.satellite_id as u8,
week_number: e.week_number as u16,
sv_accuracy: e.sv_accuracy as u8,
code_on_l2: e.code_on_l2 as u8,
idot: e.idot as i32,
iode: e.iode as u8,
t_oc: e.t_oc as u16,
a_f2: e.a_f2 as i16,
a_f1: e.a_f1 as i32,
a_f0: e.a_f0 as i32,
iodc: e.iodc as u16,
c_rs: e.c_rs as i32,
delta_n: e.delta_n as i32,
m0: e.m0,
c_uc: e.c_uc as i32,
eccentricity: e.eccentricity as u64,
c_us: e.c_us as i32,
sqrt_a: e.sqrt_a as u64,
t_oe: e.t_oe as u16,
c_ic: e.c_ic as i32,
omega0: e.omega0,
c_is: e.c_is as i32,
i0: e.i0,
c_rc: e.c_rc as i32,
omega: e.omega,
omega_dot: e.omega_dot as i32,
t_gd: e.t_gd as i16,
sv_health: e.sv_health as u8,
l2_p_data_flag: e.l2_p_data_flag,
fit_interval: e.fit_interval,
}
}
}
impl From<GlonassEphemerisFields> for GlonassEphemeris {
fn from(e: GlonassEphemerisFields) -> Self {
Self {
satellite_id: e.satellite_id as u8,
frequency_channel: e.frequency_channel as u8,
almanac_health: e.almanac_health,
almanac_health_availability: e.almanac_health_availability,
p1: e.p1 as u8,
t_k: e.t_k as u16,
b_n_msb: e.b_n_msb,
p2: e.p2,
t_b: e.t_b as u8,
xn_dot: e.xn_dot as i32,
xn: e.xn as i32,
xn_dot_dot: e.xn_dot_dot as i8,
yn_dot: e.yn_dot as i32,
yn: e.yn as i32,
yn_dot_dot: e.yn_dot_dot as i8,
zn_dot: e.zn_dot as i32,
zn: e.zn as i32,
zn_dot_dot: e.zn_dot_dot as i8,
p3: e.p3,
gamma_n: e.gamma_n as i16,
m_p: e.m_p as u8,
m_l_n_third: e.m_l_n_third,
tau_n: e.tau_n as i32,
delta_tau_n: e.delta_tau_n as i8,
e_n: e.e_n as u8,
m_p4: e.m_p4,
m_f_t: e.m_f_t as u8,
m_n_t: e.m_n_t as u16,
m_m: e.m_m as u8,
additional_data_available: e.additional_data_available,
n_a: e.n_a as u16,
tau_c: e.tau_c,
m_n4: e.m_n4 as u8,
m_tau_gps: e.m_tau_gps as i32,
m_l_n_fifth: e.m_l_n_fifth,
reserved: e.reserved as u8,
}
}
}
impl From<MsmHeaderFields> for MsmHeader {
fn from(h: MsmHeaderFields) -> Self {
Self {
reference_station_id: h.reference_station_id as u16,
epoch_time: h.epoch_time as u32,
multiple_message: h.multiple_message,
iods: h.iods as u8,
reserved: h.reserved as u8,
clock_steering: h.clock_steering as u8,
external_clock: h.external_clock as u8,
divergence_free_smoothing: h.divergence_free_smoothing,
smoothing_interval: h.smoothing_interval as u8,
}
}
}
impl From<MsmSatelliteFields> for MsmSatellite {
fn from(s: MsmSatelliteFields) -> Self {
Self {
id: s.id as u8,
rough_range_ms: s.rough_range_ms as u8,
rough_range_mod1: s.rough_range_mod1 as u16,
extended_info: s.extended_info.map(|v| v as u8),
rough_phase_range_rate_m_s: s.rough_phase_range_rate_m_s.map(|v| v as i16),
}
}
}
impl From<MsmSignalFields> for MsmSignal {
fn from(s: MsmSignalFields) -> Self {
Self {
satellite_id: s.satellite_id as u8,
signal_id: s.signal_id as u8,
fine_pseudorange: s.fine_pseudorange as i32,
fine_phase_range: s.fine_phase_range as i32,
lock_time_indicator: s.lock_time_indicator as u16,
half_cycle_ambiguity: s.half_cycle_ambiguity,
cnr: s.cnr as u16,
fine_phase_range_rate: s.fine_phase_range_rate.map(|v| v as i16),
}
}
}
/// Build an [`MsmMessage`] from its decoded field map. The constellation letter
/// and MSM kind are validated here (the only fallible parts of construction).
fn build_msm(fields: MsmMessageFields) -> NifResult<MsmMessage> {
let system = fields
.system
.chars()
.next()
.and_then(GnssSystem::from_letter)
.ok_or_else(|| Error::Term(Box::new("unknown RTCM MSM constellation letter")))?;
let kind = match fields.kind.as_str() {
"msm4" => MsmKind::Msm4,
"msm7" => MsmKind::Msm7,
_ => return Err(Error::Term(Box::new("unknown RTCM MSM kind"))),
};
Ok(MsmMessage {
message_number: fields.message_number as u16,
system,
kind,
header: fields.header.into(),
satellites: fields.satellites.into_iter().map(Into::into).collect(),
signals: fields.signals.into_iter().map(Into::into).collect(),
})
}
/// Build the canonical [`Message`] IR for a `{type, fields}` construction pair.
fn build_message(kind: &str, fields: Term<'_>) -> NifResult<Message> {
let message = match kind {
"station_coordinates" => {
Message::StationCoordinates(fields.decode::<StationCoordinatesInput>()?.into())
}
"antenna_descriptor" => {
Message::AntennaDescriptor(fields.decode::<AntennaDescriptorFields>()?.into())
}
"gps_ephemeris" => Message::GpsEphemeris(fields.decode::<GpsEphemerisFields>()?.into()),
"glonass_ephemeris" => {
Message::GlonassEphemeris(fields.decode::<GlonassEphemerisFields>()?.into())
}
"msm" => Message::Msm(build_msm(fields.decode::<MsmMessageFields>()?)?),
"unsupported" => {
let unsupported = fields.decode::<UnsupportedFields>()?;
Message::Unsupported(UnsupportedMessage {
message_number: unsupported.message_number as u16,
body: unsupported.body,
})
}
_ => return Err(Error::Term(Box::new("unsupported RTCM message type"))),
};
Ok(message)
}
fn encode_message<'a>(env: Env<'a>, message: Message) -> Term<'a> {
match message {
Message::Msm(m) => (atoms::msm(), MsmMessageFields::from(m)).encode(env),
Message::StationCoordinates(s) => (
atoms::station_coordinates(),
StationCoordinatesFields::from(s),
)
.encode(env),
Message::AntennaDescriptor(a) => {
(atoms::antenna_descriptor(), AntennaDescriptorFields::from(a)).encode(env)
}
Message::GpsEphemeris(e) => {
(atoms::gps_ephemeris(), GpsEphemerisFields::from(e)).encode(env)
}
Message::GlonassEphemeris(e) => {
(atoms::glonass_ephemeris(), GlonassEphemerisFields::from(e)).encode(env)
}
Message::Unsupported(u) => (
atoms::unsupported(),
UnsupportedFields {
message_number: u.message_number as i64,
body: u.body,
},
)
.encode(env),
}
}
/// Decode every CRC-valid RTCM 3 frame in a byte buffer into the message IR.
///
/// Mirrors the forgiving `rtcm::decode_messages`: frames whose CRC fails or whose
/// body cannot be decoded are skipped, and the scan resynchronizes on the next
/// preamble. Returns a list of `{type_atom, fields_map}` pairs.
#[rustler::nif(schedule = "DirtyCpu")]
fn rtcm_decode_messages<'a>(env: Env<'a>, bytes: rustler::Binary) -> Vec<Term<'a>> {
rtcm::decode_messages(bytes.as_slice())
.into_iter()
.map(|message| encode_message(env, message))
.collect()
}
/// Decode a single RTCM message body into the message IR.
#[rustler::nif]
fn rtcm_decode_message<'a>(env: Env<'a>, body: rustler::Binary) -> NifResult<Term<'a>> {
match Message::decode(body.as_slice()) {
Ok(message) => Ok((atoms::ok(), encode_message(env, message)).encode(env)),
Err(error) => Ok((atoms::error(), error.to_string()).encode(env)),
}
}
/// Read the RTCM message number from a message body.
#[rustler::nif]
fn rtcm_message_number<'a>(env: Env<'a>, body: rustler::Binary) -> NifResult<Term<'a>> {
match rtcm::message_number(body.as_slice()) {
Ok(number) => Ok((atoms::ok(), number as i64).encode(env)),
Err(error) => Ok((atoms::error(), error.to_string()).encode(env)),
}
}
/// Decode the single RTCM 3 frame that begins at the start of `bytes`.
///
/// Verifies the preamble and the CRC-24Q. Returns
/// `{:ok, %{message_number, frame_len, body}}` (body as a binary) or
/// `{:error, reason}` for a missing preamble, a truncated buffer, or a CRC
/// mismatch.
#[rustler::nif]
fn rtcm_decode_frame<'a>(env: Env<'a>, bytes: rustler::Binary) -> NifResult<Term<'a>> {
match rtcm::decode_frame(bytes.as_slice()) {
Ok(frame) => {
let message_number = rtcm::message_number(frame.body)
.map(|n| n as i64)
.unwrap_or(-1);
let body = frame.body.to_vec();
Ok((
atoms::ok(),
FrameFields {
message_number,
frame_len: frame.frame_len as i64,
body,
},
)
.encode(env))
}
Err(e) => Ok((atoms::error(), e.to_string()).encode(env)),
}
}
/// Wrap an RTCM message body in a fresh RTCM frame.
#[rustler::nif]
fn rtcm_encode_frame_body<'a>(env: Env<'a>, body: rustler::Binary) -> NifResult<Term<'a>> {
match rtcm::encode_frame(body.as_slice()) {
Ok(frame) => Ok((atoms::ok(), bytes_to_binary(env, &frame)).encode(env)),
Err(error) => Ok((atoms::error(), error.to_string()).encode(env)),
}
}
#[derive(Debug, Clone, rustler::NifMap)]
struct FrameFields {
message_number: i64,
frame_len: i64,
body: Vec<u8>,
}
/// Construct a supported RTCM 3 message from a `{type, fields}` pair and encode
/// it into a complete transport frame (preamble, length, body, CRC-24Q).
///
/// Pure glue over the per-type constructors and `Message::to_frame`: it builds
/// the canonical message IR from the field map and emits the framed bytes a
/// stream consumer (or `decode_messages/1`) reads back. Returns
/// `{:ok, binary}` or `{:error, reason}` for an unsupported type, a malformed
/// field map, or a body that overflows the frame length limit.
#[rustler::nif(schedule = "DirtyCpu")]
fn rtcm_encode_message<'a>(env: Env<'a>, kind: String, fields: Term<'a>) -> NifResult<Term<'a>> {
let message = build_message(&kind, fields)?;
match message.to_frame() {
Ok(frame) => Ok((atoms::ok(), bytes_to_binary(env, &frame)).encode(env)),
Err(error) => Ok((atoms::error(), error.to_string()).encode(env)),
}
}
/// Construct a supported RTCM 3 message and return its message body.
#[rustler::nif(schedule = "DirtyCpu")]
fn rtcm_encode<'a>(env: Env<'a>, kind: String, fields: Term<'a>) -> NifResult<Term<'a>> {
let message = build_message(&kind, fields)?;
Ok((atoms::ok(), bytes_to_binary(env, &message.encode())).encode(env))
}
/// Construct a supported RTCM 3 message and return its complete frame.
#[rustler::nif(schedule = "DirtyCpu")]
fn rtcm_encode_frame<'a>(env: Env<'a>, kind: String, fields: Term<'a>) -> NifResult<Term<'a>> {
let message = build_message(&kind, fields)?;
match message.to_frame() {
Ok(frame) => Ok((atoms::ok(), bytes_to_binary(env, &frame)).encode(env)),
Err(error) => Ok((atoms::error(), error.to_string()).encode(env)),
}
}
/// Copy a byte slice into an Elixir binary term.
fn bytes_to_binary<'a>(env: Env<'a>, bytes: &[u8]) -> Term<'a> {
let mut binary = OwnedBinary::new(bytes.len()).expect("allocate RTCM frame binary");
binary.as_mut_slice().copy_from_slice(bytes);
binary.release(env).encode(env)
}