use std::error::Error;
use std::fmt;
use crate::engine::types::{FieldInfo, FieldRef, FieldType, PgnInfo};
use crate::engine::db::PgnDatabase;
use crate::engine::frame::RawFrame;
#[derive(Debug, Clone, PartialEq)]
pub enum EncodeValue {
Number(f64),
Int(i64),
Lookup(String),
Text(String),
Bytes(Vec<u8>),
Pgn(u32),
NotAvailable,
}
impl From<f64> for EncodeValue {
fn from(v: f64) -> Self {
EncodeValue::Number(v)
}
}
impl From<i64> for EncodeValue {
fn from(v: i64) -> Self {
EncodeValue::Int(v)
}
}
impl From<i32> for EncodeValue {
fn from(v: i32) -> Self {
EncodeValue::Int(v as i64)
}
}
impl From<u32> for EncodeValue {
fn from(v: u32) -> Self {
EncodeValue::Int(v as i64)
}
}
impl From<&str> for EncodeValue {
fn from(v: &str) -> Self {
EncodeValue::Text(v.to_string())
}
}
impl From<String> for EncodeValue {
fn from(v: String) -> Self {
EncodeValue::Text(v)
}
}
impl From<u64> for EncodeValue {
fn from(v: u64) -> Self {
EncodeValue::Int(v as i64)
}
}
impl From<Vec<u8>> for EncodeValue {
fn from(v: Vec<u8>) -> Self {
EncodeValue::Bytes(v)
}
}
impl From<&[u8]> for EncodeValue {
fn from(v: &[u8]) -> Self {
EncodeValue::Bytes(v.to_vec())
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum EncodeError {
NoSuchPgnId(String),
NoSuchPgn(u32),
AmbiguousPgn { pgn: u32, variants: usize },
NoSuchField { pgn_id: &'static str, field: String },
UnknownLookupLabel { field: &'static str, label: String },
ValueOutOfRange { field: &'static str, value: f64 },
NotFixedLength(&'static str),
TypeMismatch {
field: &'static str,
expected: &'static str,
},
LengthMismatch { pgn: u32, expected: u32, got: usize },
VariableUnresolved {
field: &'static str,
reason: &'static str,
},
NoRepeatingSet { pgn_id: &'static str, set: usize },
NoSuchInstance {
pgn_id: &'static str,
set: usize,
instance: usize,
},
}
impl fmt::Display for EncodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
EncodeError::NoSuchPgnId(id) => write!(f, "no PGN with id '{id}'"),
EncodeError::NoSuchPgn(p) => write!(f, "no PGN {p} in the schema"),
EncodeError::AmbiguousPgn { pgn, variants } => {
write!(f, "PGN {pgn} has {variants} variants; select one by id")
}
EncodeError::NoSuchField { pgn_id, field } => {
write!(f, "PGN '{pgn_id}' has no field '{field}'")
}
EncodeError::UnknownLookupLabel { field, label } => {
write!(f, "field '{field}': '{label}' is not a valid value")
}
EncodeError::ValueOutOfRange { field, value } => {
write!(f, "field '{field}': value {value} out of range")
}
EncodeError::NotFixedLength(field) => {
write!(
f,
"field '{field}': dynamic group-function field must be set explicitly"
)
}
EncodeError::VariableUnresolved { field, reason } => {
write!(f, "field '{field}': {reason}")
}
EncodeError::NoRepeatingSet { pgn_id, set } => {
write!(f, "PGN '{pgn_id}' has no repeating field set {set}")
}
EncodeError::NoSuchInstance {
pgn_id,
set,
instance,
} => {
write!(
f,
"PGN '{pgn_id}' set {set}: instance {instance} was never added"
)
}
EncodeError::TypeMismatch { field, expected } => {
write!(f, "field '{field}': expected {expected}")
}
EncodeError::LengthMismatch { pgn, expected, got } => {
write!(f, "PGN {pgn}: packed {got} bytes, schema says {expected}")
}
}
}
}
impl Error for EncodeError {}
pub struct PgnBuilder {
db: &'static PgnDatabase,
pgn: &'static PgnInfo,
prio: u8,
src: u8,
dst: u8,
timestamp: Option<String>,
staged: Vec<Option<Staged>>,
sets: [Vec<Vec<Option<Staged>>>; 2],
}
#[derive(Clone)]
enum Staged {
Scalar(u64),
Bytes(Vec<u8>),
Deferred(EncodeValue),
}
impl PgnBuilder {
pub(crate) fn for_pgn(db: &'static PgnDatabase, pgn: &'static PgnInfo) -> Self {
Self {
db,
pgn,
prio: pgn.priority.unwrap_or(6),
src: 0,
dst: 255,
timestamp: None,
staged: vec![None; pgn.fields.len()],
sets: [Vec::new(), Vec::new()],
}
}
pub fn priority(mut self, prio: u8) -> Self {
self.prio = prio;
self
}
pub fn source(mut self, src: u8) -> Self {
self.src = src;
self
}
pub fn destination(mut self, dst: u8) -> Self {
self.dst = dst;
self
}
pub fn timestamp(mut self, ts: impl Into<String>) -> Self {
self.timestamp = Some(ts.into());
self
}
pub fn push(
&mut self,
field: FieldRef,
value: impl Into<EncodeValue>,
) -> Result<&mut Self, EncodeError> {
debug_assert_eq!(
field.pgn.id, self.pgn.id,
"FieldRef/PGN mismatch: a `{}` field ref was used on a `{}` builder",
field.pgn.id, self.pgn.id,
);
let idx = (field.field.order as usize)
.checked_sub(1)
.filter(|&i| i < self.pgn.fields.len())
.ok_or(EncodeError::NoSuchField {
pgn_id: self.pgn.id,
field: field.field.id.to_string(),
})?;
let staged = self.stage_value(&self.pgn.fields[idx], value.into())?;
self.staged[idx] = Some(staged);
Ok(self)
}
pub fn push_by_name(
&mut self,
field: &str,
value: impl Into<EncodeValue>,
) -> Result<&mut Self, EncodeError> {
let idx = self.find_by_name(field)?;
let staged = self.stage_value(&self.pgn.fields[idx], value.into())?;
self.staged[idx] = Some(staged);
Ok(self)
}
pub fn push_arg(&mut self, field: &str, value: &str) -> Result<&mut Self, EncodeError> {
let idx = self.find_by_name_or_id(field)?;
let f = &self.pgn.fields[idx];
let ev = coerce_arg(f, value)?;
let staged = self.stage_value(f, ev)?;
self.staged[idx] = Some(staged);
Ok(self)
}
fn set_layout(&self, set: usize) -> Option<(usize, usize, Option<usize>)> {
let (start, size, count) = match set {
1 => (
self.pgn.repeating_field_set1_start_field,
self.pgn.repeating_field_set1_size,
self.pgn.repeating_field_set1_count_field,
),
2 => (
self.pgn.repeating_field_set2_start_field,
self.pgn.repeating_field_set2_size,
self.pgn.repeating_field_set2_count_field,
),
_ => (None, None, None),
};
Some((
start? as usize - 1,
size? as usize,
count.map(|c| c as usize - 1),
))
}
pub fn add_set_instance(&mut self, set: usize) -> Result<usize, EncodeError> {
let (_, size, _) = self.set_layout(set).ok_or(EncodeError::NoRepeatingSet {
pgn_id: self.pgn.id,
set,
})?;
let instances = &mut self.sets[set - 1];
instances.push(vec![None; size]);
Ok(instances.len() - 1)
}
pub fn push_in_set(
&mut self,
set: usize,
instance: usize,
field: &str,
value: impl Into<EncodeValue>,
) -> Result<&mut Self, EncodeError> {
let (start, size, _) = self.set_layout(set).ok_or(EncodeError::NoRepeatingSet {
pgn_id: self.pgn.id,
set,
})?;
let rel = self.pgn.fields[start..start + size]
.iter()
.position(|f| f.name == field || f.id == field)
.ok_or_else(|| EncodeError::NoSuchField {
pgn_id: self.pgn.id,
field: field.to_string(),
})?;
let f = &self.pgn.fields[start + rel];
let staged = if f.field_type == Some(FieldType::Variable) {
Staged::Deferred(value.into())
} else {
self.stage_value(f, value.into())?
};
let inst = self.sets[set - 1]
.get_mut(instance)
.ok_or(EncodeError::NoSuchInstance {
pgn_id: self.pgn.id,
set,
instance,
})?;
inst[rel] = Some(staged);
Ok(self)
}
pub fn build(&self) -> Result<RawFrame, EncodeError> {
let mut buf: Vec<u8> = Vec::new();
let mut next_bit = 0usize;
let set1 = self.set_layout(1);
let set2 = self.set_layout(2);
let mut idx = 0usize;
while idx < self.pgn.fields.len() {
let mut in_set = false;
for (layout, instances) in [(&set1, &self.sets[0]), (&set2, &self.sets[1])] {
if let Some((start, size, _)) = *layout
&& idx == start
{
for inst in instances {
for (j, f) in self.pgn.fields[start..start + size].iter().enumerate() {
match &inst[j] {
Some(Staged::Deferred(v)) => self.emit_variable(
&mut buf,
&mut next_bit,
f,
inst,
start,
size,
v,
)?,
other => emit_field(&mut buf, &mut next_bit, f, other.as_ref())?,
}
}
}
idx += size;
in_set = true;
break;
}
}
if in_set {
continue;
}
let f = &self.pgn.fields[idx];
let auto_count = [(&set1, &self.sets[0]), (&set2, &self.sets[1])]
.into_iter()
.find_map(|(layout, instances)| match *layout {
Some((_, _, Some(count_idx)))
if count_idx == idx && self.staged[idx].is_none() =>
{
Some(Staged::Scalar(instances.len() as u64))
}
_ => None,
});
emit_field(
&mut buf,
&mut next_bit,
f,
auto_count.as_ref().or(self.staged[idx].as_ref()),
)?;
idx += 1;
}
if !next_bit.is_multiple_of(8) {
let pad = 8 - (next_bit % 8);
write_bits(&mut buf, &mut next_bit, pad, u64::MAX);
}
if let Some(expected) = self.pgn.length
&& buf.len() != expected as usize
{
return Err(EncodeError::LengthMismatch {
pgn: self.pgn.pgn,
expected,
got: buf.len(),
});
}
Ok(RawFrame {
timestamp: self.timestamp.clone(),
prio: self.prio,
pgn: self.pgn.pgn,
src: self.src,
dst: self.dst,
data: buf.into_iter().collect(),
})
}
pub fn pgn_info(&self) -> &'static PgnInfo {
self.pgn
}
fn find_by_name(&self, name: &str) -> Result<usize, EncodeError> {
self.pgn
.fields
.iter()
.position(|f| f.name == name)
.ok_or_else(|| EncodeError::NoSuchField {
pgn_id: self.pgn.id,
field: name.to_string(),
})
}
fn find_by_name_or_id(&self, key: &str) -> Result<usize, EncodeError> {
self.pgn
.fields
.iter()
.position(|f| f.name == key || f.id == key)
.ok_or_else(|| EncodeError::NoSuchField {
pgn_id: self.pgn.id,
field: key.to_string(),
})
}
fn staged_parameter_pairs(&self) -> Vec<(u32, &EncodeValue)> {
let mut pairs = Vec::new();
for set in 1..=2 {
let Some((start, size, _)) = self.set_layout(set) else {
continue;
};
for inst in &self.sets[set - 1] {
let mut index = None;
let mut value = None;
for (j, tf) in self.pgn.fields[start..start + size].iter().enumerate() {
match &inst[j] {
Some(Staged::Scalar(n)) if tf.field_type == Some(FieldType::FieldIndex) => {
index = Some(*n as u32)
}
Some(Staged::Deferred(v)) => value = Some(v),
_ => {}
}
}
if let (Some(i), Some(v)) = (index, value) {
pairs.push((i, v));
}
}
}
pairs
}
fn match_target_variant(&self, target_pgn: u32) -> Option<&'static PgnInfo> {
let first = self.db.first_pgn(target_pgn)?;
if !first.fields.iter().any(|fi| fi.match_value.is_some()) {
return Some(first);
}
let pairs = self.staged_parameter_pairs();
if crate::engine::decode::is_pgn_proprietary(target_pgn)
&& (pairs.len() < 2 || pairs[0].0 != 1 || pairs[1].0 != 3)
{
return None;
}
'variant: for cand in self.db.pgn_variants(target_pgn) {
for &(idx, value) in &pairs {
if idx == 0 {
continue 'variant;
}
let Some(field) = cand.fields.get(idx as usize - 1) else {
continue 'variant;
};
let Some(want) = field.match_value else {
continue;
};
let staged_value = match value {
EncodeValue::Text(s) if field.lookup_enumeration.is_some() => {
EncodeValue::Lookup(s.clone())
}
other => other.clone(),
};
match self.stage_value(field, staged_value) {
Ok(Staged::Scalar(raw)) if raw as i64 == want => {}
_ => continue 'variant,
}
}
return Some(cand);
}
None
}
#[allow(clippy::too_many_arguments)]
fn emit_variable(
&self,
buf: &mut Vec<u8>,
next_bit: &mut usize,
f: &FieldInfo,
inst: &[Option<Staged>],
start: usize,
size: usize,
v: &EncodeValue,
) -> Result<(), EncodeError> {
let unresolved = |reason| EncodeError::VariableUnresolved {
field: f.name,
reason,
};
let target_pgn = self
.pgn
.fields
.iter()
.position(|tf| tf.field_type == Some(FieldType::Pgn))
.and_then(|i| match self.staged[i] {
Some(Staged::Scalar(p)) => Some(p as u32),
_ => None,
})
.ok_or(unresolved("no staged PGN field names the referenced PGN"))?;
let target_info = self
.match_target_variant(target_pgn)
.or_else(|| self.db.first_pgn(target_pgn))
.or_else(|| self.db.fallback_pgn(target_pgn))
.ok_or(unresolved("referenced PGN is not in the schema"))?;
let param_idx = self.pgn.fields[start..start + size]
.iter()
.enumerate()
.find(|(_, tf)| tf.field_type == Some(FieldType::FieldIndex))
.and_then(|(j, _)| match inst[j] {
Some(Staged::Scalar(n)) => Some(n as u32),
_ => None,
})
.ok_or(unresolved("no staged FIELD_INDEX selects the target field"))?;
let target_field = target_info
.fields
.iter()
.find(|tf| (tf.order as u32) == param_idx)
.ok_or(unresolved(
"FIELD_INDEX is beyond the referenced PGN's fields",
))?;
let value = match v {
EncodeValue::Text(s) if target_field.lookup_enumeration.is_some() => {
EncodeValue::Lookup(s.clone())
}
other => other.clone(),
};
match self.stage_value(target_field, value)? {
Staged::Scalar(raw) => {
let bl = target_field
.bit_length
.ok_or(EncodeError::NotFixedLength(target_field.name))?
as usize;
write_bits(buf, next_bit, bl, raw);
if !next_bit.is_multiple_of(8) {
let pad = 8 - (*next_bit % 8);
write_bits(buf, next_bit, pad, 0);
}
}
Staged::Bytes(bytes) => {
for b in bytes {
write_bits(buf, next_bit, 8, u64::from(b));
}
}
Staged::Deferred(_) => unreachable!("stage_value never defers"),
}
Ok(())
}
fn stage_value(&self, f: &FieldInfo, v: EncodeValue) -> Result<Staged, EncodeError> {
match f.field_type {
Some(FieldType::StringFix) => stage_string_fix(f, v),
Some(FieldType::StringLz) => stage_string_lz(f, v),
Some(FieldType::StringLau) => stage_string_lau(f, v),
Some(FieldType::Binary) => stage_binary(f, v),
Some(FieldType::DynamicFieldValue) => stage_dynamic_value(f, v),
_ => Ok(Staged::Scalar(self.value_to_raw(f, v)?)),
}
}
fn value_to_raw(&self, f: &FieldInfo, v: EncodeValue) -> Result<u64, EncodeError> {
let bl = f.bit_length.ok_or(EncodeError::NotFixedLength(f.name))?;
let raw_i64: i64 = match (f.field_type, v) {
(_, EncodeValue::NotAvailable) => return Ok(default_raw(f)),
(
Some(FieldType::Lookup) | Some(FieldType::IndirectLookup),
EncodeValue::Lookup(label),
) => self.resolve_lookup(f, &label)? as i64,
(Some(FieldType::Lookup) | Some(FieldType::IndirectLookup), EncodeValue::Int(n)) => n,
(Some(FieldType::BitLookup), EncodeValue::Int(n)) => n,
(Some(FieldType::Pgn), EncodeValue::Pgn(p)) => p as i64,
(Some(FieldType::Pgn), EncodeValue::Int(n)) => n,
(Some(FieldType::Float), EncodeValue::Number(x)) => {
return Ok(u64::from((x as f32).to_bits()));
}
(Some(FieldType::Float), EncodeValue::Int(n)) => {
return Ok(u64::from((n as f32).to_bits()));
}
(_, EncodeValue::Text(_)) | (_, EncodeValue::Bytes(_)) => {
return Err(EncodeError::TypeMismatch {
field: f.name,
expected: "a numeric value",
});
}
(_, EncodeValue::Pgn(p)) => p as i64,
(_, EncodeValue::Int(n)) => n,
(_, EncodeValue::Number(x)) => scaled_to_raw(f, x)?,
(_, EncodeValue::Lookup(_)) => {
return Err(EncodeError::TypeMismatch {
field: f.name,
expected: "a numeric value",
});
}
};
Ok(mask_to_width(raw_i64, bl))
}
fn resolve_lookup(&self, f: &FieldInfo, label: &str) -> Result<u64, EncodeError> {
let table_name = f.lookup_enumeration.ok_or(EncodeError::TypeMismatch {
field: f.name,
expected: "a value for a lookup field",
})?;
if let Some(table) = self.db.lookup(table_name) {
for lv in table.values {
if lv.name == label || lv.id == Some(label) {
return Ok(lv.value);
}
}
}
Err(EncodeError::UnknownLookupLabel {
field: f.name,
label: label.to_string(),
})
}
}
fn scaled_to_raw(f: &FieldInfo, scaled: f64) -> Result<i64, EncodeError> {
let resolution = f.resolution.unwrap_or(1.0);
let display_offset = f.offset.map(|o| o as f64).unwrap_or(0.0);
let raw = (scaled - f.unit_offset - display_offset) / resolution;
let rounded = raw.round();
if !rounded.is_finite() {
return Err(EncodeError::ValueOutOfRange {
field: f.name,
value: scaled,
});
}
Ok(rounded as i64)
}
fn stage_string_fix(f: &FieldInfo, v: EncodeValue) -> Result<Staged, EncodeError> {
let byte_len = f.bit_length.ok_or(EncodeError::NotFixedLength(f.name))? as usize / 8;
let text = match v {
EncodeValue::Text(s) => s,
EncodeValue::NotAvailable => String::new(),
EncodeValue::Bytes(b) => {
if b.len() != byte_len {
return Err(EncodeError::ValueOutOfRange {
field: f.name,
value: b.len() as f64,
});
}
return Ok(Staged::Bytes(b));
}
_ => {
return Err(EncodeError::TypeMismatch {
field: f.name,
expected: "text",
});
}
};
let bytes = text.as_bytes();
if bytes.len() > byte_len {
return Err(EncodeError::ValueOutOfRange {
field: f.name,
value: bytes.len() as f64,
});
}
let mut buf = vec![0xffu8; byte_len];
buf[..bytes.len()].copy_from_slice(bytes);
Ok(Staged::Bytes(buf))
}
fn text_of(f: &FieldInfo, v: EncodeValue) -> Result<String, EncodeError> {
match v {
EncodeValue::Text(s) => Ok(s),
EncodeValue::NotAvailable => Ok(String::new()),
_ => Err(EncodeError::TypeMismatch {
field: f.name,
expected: "text",
}),
}
}
fn stage_string_lz(f: &FieldInfo, v: EncodeValue) -> Result<Staged, EncodeError> {
let text = text_of(f, v)?;
let content = text.as_bytes();
if content.len() > u8::MAX as usize {
return Err(EncodeError::ValueOutOfRange {
field: f.name,
value: content.len() as f64,
});
}
let mut buf = Vec::with_capacity(content.len() + 1);
buf.push(content.len() as u8);
buf.extend_from_slice(content);
if let Some(bl) = f.bit_length {
let want = bl as usize / 8;
if buf.len() > want {
return Err(EncodeError::ValueOutOfRange {
field: f.name,
value: content.len() as f64,
});
}
buf.resize(want, 0);
}
Ok(Staged::Bytes(buf))
}
fn stage_string_lau(f: &FieldInfo, v: EncodeValue) -> Result<Staged, EncodeError> {
let text = text_of(f, v)?;
let content = text.as_bytes();
if content.len() + 2 > u8::MAX as usize {
return Err(EncodeError::ValueOutOfRange {
field: f.name,
value: content.len() as f64,
});
}
let mut buf = Vec::with_capacity(content.len() + 2);
buf.push((content.len() + 2) as u8);
buf.push(1); buf.extend_from_slice(content);
Ok(Staged::Bytes(buf))
}
fn stage_binary(f: &FieldInfo, v: EncodeValue) -> Result<Staged, EncodeError> {
let mut bytes = match v {
EncodeValue::Bytes(b) => b,
EncodeValue::NotAvailable => Vec::new(),
_ => {
return Err(EncodeError::TypeMismatch {
field: f.name,
expected: "bytes",
});
}
};
if let Some(bl) = f.bit_length {
if bl % 8 != 0 && bl <= 64 {
let want = bl.div_ceil(8) as usize;
if bytes.len() > want {
return Err(EncodeError::ValueOutOfRange {
field: f.name,
value: bytes.len() as f64,
});
}
let mut raw: u64 = 0;
for (i, b) in bytes.iter().enumerate() {
raw |= u64::from(*b) << (8 * i);
}
raw &= u64::MAX >> (64 - bl);
return Ok(Staged::Scalar(raw));
}
let want = bl as usize / 8;
if bytes.len() > want {
return Err(EncodeError::ValueOutOfRange {
field: f.name,
value: bytes.len() as f64,
});
}
bytes.resize(want, 0);
}
Ok(Staged::Bytes(bytes))
}
fn stage_dynamic_value(f: &FieldInfo, v: EncodeValue) -> Result<Staged, EncodeError> {
match v {
EncodeValue::Bytes(b) => Ok(Staged::Bytes(b)),
EncodeValue::NotAvailable => Ok(Staged::Bytes(Vec::new())),
_ => Err(EncodeError::TypeMismatch {
field: f.name,
expected: "value bytes for a dynamic (key/value) field",
}),
}
}
fn default_raw(f: &FieldInfo) -> u64 {
let bl = f.bit_length.unwrap_or(0);
if let Some(mv) = f.match_value {
return mask_to_width(mv, bl);
}
match f.field_type {
Some(FieldType::Spare) => 0,
_ => f.unknown_value.unwrap_or_else(|| all_ones(bl)),
}
}
fn emit_field(
buf: &mut Vec<u8>,
next_bit: &mut usize,
f: &FieldInfo,
staged: Option<&Staged>,
) -> Result<(), EncodeError> {
match staged {
Some(Staged::Bytes(bytes)) => {
for &b in bytes {
write_bits(buf, next_bit, 8, u64::from(b));
}
Ok(())
}
Some(Staged::Scalar(v)) => {
let bl = f.bit_length.ok_or(EncodeError::NotFixedLength(f.name))? as usize;
write_bits(buf, next_bit, bl, *v);
Ok(())
}
Some(Staged::Deferred(_)) => Err(EncodeError::NotFixedLength(f.name)),
None => write_unset(buf, next_bit, f),
}
}
fn write_unset(buf: &mut Vec<u8>, next_bit: &mut usize, f: &FieldInfo) -> Result<(), EncodeError> {
match f.field_type {
Some(FieldType::StringFix) | Some(FieldType::Binary) if f.bit_length.is_some() => {
for _ in 0..f.bit_length.unwrap() as usize / 8 {
write_bits(buf, next_bit, 8, 0xff);
}
}
Some(FieldType::Binary) => {}
Some(FieldType::StringLz) => write_bits(buf, next_bit, 8, 0), Some(FieldType::StringLau) => {
write_bits(buf, next_bit, 8, 2); write_bits(buf, next_bit, 8, 1); }
Some(FieldType::Variable)
| Some(FieldType::DynamicFieldKey)
| Some(FieldType::DynamicFieldLength)
| Some(FieldType::DynamicFieldValue) => {
return Err(EncodeError::NotFixedLength(f.name));
}
_ => {
let bl = f.bit_length.ok_or(EncodeError::NotFixedLength(f.name))? as usize;
write_bits(buf, next_bit, bl, default_raw(f));
}
}
Ok(())
}
fn mask_to_width(value: i64, bits: u32) -> u64 {
(value as u64) & all_ones(bits)
}
fn all_ones(bits: u32) -> u64 {
if bits >= 64 {
u64::MAX
} else {
(1u64 << bits) - 1
}
}
fn write_bits(buf: &mut Vec<u8>, next_bit: &mut usize, bits: usize, mut value: u64) {
for _ in 0..bits {
let idx = *next_bit >> 3;
let bitpos = *next_bit & 7;
if bitpos == 0 {
buf.push(0);
}
if value & 1 != 0 {
buf[idx] |= 1 << bitpos;
}
value >>= 1;
*next_bit += 1;
}
}
fn coerce_arg(f: &FieldInfo, s: &str) -> Result<EncodeValue, EncodeError> {
let s = s.trim();
if s.eq_ignore_ascii_case("n/a") || s.is_empty() {
return Ok(EncodeValue::NotAvailable);
}
if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X"))
&& let Ok(n) = i64::from_str_radix(hex, 16)
{
return Ok(EncodeValue::Int(n));
}
match f.field_type {
Some(FieldType::Lookup) | Some(FieldType::IndirectLookup) => {
match s.parse::<i64>() {
Ok(n) => Ok(EncodeValue::Int(n)),
Err(_) => Ok(EncodeValue::Lookup(s.to_string())),
}
}
Some(FieldType::Pgn) => {
s.parse::<u32>()
.map(EncodeValue::Pgn)
.map_err(|_| EncodeError::ValueOutOfRange {
field: f.name,
value: f64::NAN,
})
}
Some(FieldType::StringFix)
| Some(FieldType::StringLz)
| Some(FieldType::StringLau)
| Some(FieldType::Variable) => Ok(EncodeValue::Text(s.to_string())),
_ => {
if let Ok(n) = s.parse::<i64>() {
Ok(EncodeValue::Int(n))
} else if let Ok(x) = s.parse::<f64>() {
Ok(EncodeValue::Number(x))
} else {
Err(EncodeError::ValueOutOfRange {
field: f.name,
value: f64::NAN,
})
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn db() -> &'static PgnDatabase {
PgnDatabase::embedded(crate::engine::Units::Metric)
}
#[test]
fn iso_request_matches_c_format_message() {
let frame = db()
.encode_for(crate::engine::pgn::ISO_REQUEST)
.priority(6)
.destination(255)
.push(crate::engine::field::iso_request::PGN, 126996u32)
.unwrap()
.build()
.unwrap();
assert_eq!(frame.pgn, 59904);
assert_eq!(frame.prio, 6);
assert_eq!(frame.dst, 255);
assert_eq!(frame.data.as_slice(), &[0x14, 0xf0, 0x01]);
}
#[test]
fn ambiguous_pgn_number_is_rejected() {
assert!(db().encode_by_pgn(59904).is_ok());
assert!(matches!(
db().encode_by_pgn(126208),
Err(EncodeError::AmbiguousPgn { pgn: 126208, .. })
));
}
#[test]
fn unset_fields_default_and_length_checks() {
let frame = db().encode_for(crate::engine::pgn::ISO_REQUEST).build();
let f = frame.unwrap();
assert_eq!(f.data.len(), 3);
assert_eq!(f.data.as_slice(), &[0xff, 0xff, 0xff]);
}
#[test]
fn wind_data_round_trips_through_decode() {
use crate::engine::field::wind_data as wd;
let db = db();
let frame = db
.encode_for(crate::engine::pgn::WIND_DATA)
.push(wd::SID, 7)
.unwrap()
.push(wd::WIND_SPEED, 5.23)
.unwrap()
.push(wd::WIND_ANGLE, 1.5)
.unwrap()
.push(wd::REFERENCE, EncodeValue::Lookup("Apparent".into()))
.unwrap()
.build()
.unwrap();
assert_eq!(frame.pgn, 130306);
assert_eq!(frame.data.len(), 8);
let decoded = db.decode(&frame).unwrap();
assert_eq!(decoded.id, "windData");
let num = |name: &str| match &decoded.field_by_name(name).unwrap().value {
crate::engine::FieldValue::Number(x) => *x,
crate::engine::FieldValue::Integer(n) => *n as f64,
other => panic!("field {name}: expected a number, got {other:?}"),
};
assert!((num("Wind Speed") - 5.23).abs() < 0.01);
assert!((num("Wind Angle") - 1.5).abs() < 0.01);
assert_eq!(num("SID"), 7.0);
match &decoded.field_by_name("Reference").unwrap().value {
crate::engine::FieldValue::Lookup { value, name } => {
assert_eq!(*value, 2);
assert_eq!(*name, Some("Apparent"));
}
other => panic!("Reference: expected lookup, got {other:?}"),
}
}
#[test]
fn repeating_set_round_trips_through_decode() {
let db = db();
let mut b = db.encode("gnssSatsInView").unwrap();
b.push_by_name("SID", 9).unwrap();
for (prn, snr) in [(7i64, 30.0f64), (11, 42.5)] {
let i = b.add_set_instance(1).unwrap();
b.push_in_set(1, i, "PRN", prn).unwrap();
b.push_in_set(1, i, "SNR", snr).unwrap();
}
let frame = b.build().unwrap();
assert_eq!(frame.pgn, 129540);
let decoded = db.decode(&frame).unwrap();
assert!(decoded.has_repeating_set[0]);
let count = match &decoded.field_by_name("Sats in View").unwrap().value {
crate::engine::FieldValue::Integer(n) => *n,
crate::engine::FieldValue::Number(x) => *x as i64,
other => panic!("Sats in View: expected a number, got {other:?}"),
};
assert_eq!(count, 2, "count field must auto-fill from instances");
let per_iter = |id: &str| -> Vec<f64> {
decoded
.fields
.iter()
.filter(|f| f.info.id == id && f.repeat_set == 1)
.map(|f| match &f.value {
crate::engine::FieldValue::Integer(n) => *n as f64,
crate::engine::FieldValue::Number(x) => *x,
other => panic!("{id}: expected a number, got {other:?}"),
})
.collect()
};
assert_eq!(per_iter("prn"), vec![7.0, 11.0]);
let snrs = per_iter("snr");
assert_eq!(snrs.len(), 2);
assert!((snrs[0] - 30.0).abs() < 0.01);
assert!((snrs[1] - 42.5).abs() < 0.01);
}
#[test]
fn repeating_set_errors() {
let db = db();
assert!(matches!(
db.encode("windData").unwrap().add_set_instance(1),
Err(EncodeError::NoRepeatingSet { set: 1, .. })
));
assert!(matches!(
db.encode("gnssSatsInView")
.unwrap()
.push_in_set(1, 0, "PRN", 1),
Err(EncodeError::NoSuchInstance { instance: 0, .. })
));
}
#[test]
fn group_function_variable_values_encode_via_the_referenced_pgn() {
let db = db();
let mut b = db.encode("nmeaCommandGroupFunction").unwrap();
b.push_by_name("PGN", EncodeValue::Pgn(127250)).unwrap();
let heading_order = db
.first_pgn(127250)
.unwrap()
.fields
.iter()
.find(|f| f.id == "heading")
.unwrap()
.order as i64;
let i = b.add_set_instance(1).unwrap();
b.push_in_set(1, i, "Parameter", heading_order).unwrap();
b.push_in_set(1, i, "Value", 1.5f64).unwrap();
let frame = b.build().map_err(|e| format!("{e}")).unwrap();
assert_eq!(frame.pgn, 126208);
let decoded = db.decode(&frame).unwrap();
let val = decoded
.fields
.iter()
.find(|f| f.repeat_set == 1 && f.info.field_type == Some(FieldType::Variable))
.expect("decoded VARIABLE value");
match &val.value {
crate::engine::FieldValue::Number(x) => assert!((x - 1.5).abs() < 0.01, "got {x}"),
crate::engine::FieldValue::Integer(n) => {
assert!((*n as f64 - 1.5).abs() < 1.0, "got {n}")
}
other => panic!("value: {other:?}"),
}
}
#[test]
fn variable_target_variant_follows_the_parameter_pairs() {
let db = db();
let mut b = db.encode("nmeaCommandGroupFunction").unwrap();
b.push_by_name("PGN", EncodeValue::Pgn(130833)).unwrap();
for (param, value) in [(1i64, 1855f64), (3, 4.0), (5, 3.21)] {
let i = b.add_set_instance(1).unwrap();
b.push_in_set(1, i, "Parameter", param).unwrap();
b.push_in_set(1, i, "Value", value).unwrap();
}
let frame = b.build().map_err(|e| format!("{e}")).unwrap();
assert_eq!(frame.data.len(), 14, "data: {:02x?}", frame.data);
assert_eq!(
&frame.data[12..],
&[0x41, 0x01],
"data: {:02x?}",
frame.data
);
let decoded = db.decode(&frame).unwrap();
let val = decoded
.fields
.iter()
.rfind(|f| f.repeat_set == 1 && f.info.field_type == Some(FieldType::Variable))
.expect("decoded VARIABLE value");
match &val.value {
crate::engine::FieldValue::Number(x) => assert!((x - 3.21).abs() < 0.005, "got {x}"),
other => panic!("value: {other:?}"),
}
}
#[test]
fn variable_target_without_narrowing_pairs_keeps_the_leading_definition() {
let db = db();
let mut b = db.encode("nmeaCommandGroupFunction").unwrap();
b.push_by_name("PGN", EncodeValue::Pgn(130833)).unwrap();
let i = b.add_set_instance(1).unwrap();
b.push_in_set(1, i, "Parameter", 5i64).unwrap();
b.push_in_set(1, i, "Value", 3f64).unwrap();
let frame = b.build().map_err(|e| format!("{e}")).unwrap();
assert_eq!(frame.data.len(), 15, "data: {:02x?}", frame.data);
assert_eq!(frame.data[7], 3, "data: {:02x?}", frame.data);
}
#[test]
fn variable_without_target_is_a_clear_error() {
let db = db();
let mut b = db.encode("nmeaCommandGroupFunction").unwrap();
let i = b.add_set_instance(1).unwrap();
b.push_in_set(1, i, "Value", 1i64).unwrap();
assert!(matches!(
b.build(),
Err(EncodeError::VariableUnresolved { .. })
));
}
#[test]
fn unknown_lookup_label_is_rejected() {
assert!(matches!(
db().encode("windData")
.unwrap()
.push_by_name("Reference", EncodeValue::Lookup("Nonsense".into())),
Err(EncodeError::UnknownLookupLabel { .. })
));
}
#[test]
fn no_such_field_and_pgn() {
assert!(matches!(
db().encode("isoRequest").unwrap().push_by_name("Nope", 1),
Err(EncodeError::NoSuchField { .. })
));
assert!(matches!(
db().encode("notAPgnId"),
Err(EncodeError::NoSuchPgnId(_))
));
}
#[test]
fn string_fix_round_trips() {
use crate::engine::field::product_information::MODEL_ID;
let frame = db()
.encode("productInformation")
.unwrap()
.push(MODEL_ID, "SCX-20")
.unwrap()
.build()
.unwrap();
let d = db().decode(&frame).unwrap();
assert_eq!(
d.field(MODEL_ID).and_then(|f| f.value.as_str()),
Some("SCX-20")
);
}
#[test]
fn string_lau_round_trips() {
use crate::engine::field::configuration_information::INSTALLATION_DESCRIPTION1 as DESC1;
let frame = db()
.encode("configurationInformation")
.unwrap()
.push(DESC1, "Helm Station")
.unwrap()
.build()
.unwrap();
let d = db().decode(&frame).unwrap();
assert_eq!(
d.field(DESC1).and_then(|f| f.value.as_str()),
Some("Helm Station")
);
}
#[test]
fn string_lz_round_trips() {
use crate::engine::field::navico_ascii_identifier::IDENTIFIER;
let frame = db()
.encode("navicoAsciiIdentifier")
.unwrap()
.push(IDENTIFIER, "BOW")
.unwrap()
.build()
.unwrap();
let d = db().decode(&frame).unwrap();
assert_eq!(
d.field(IDENTIFIER).and_then(|f| f.value.as_str()),
Some("BOW")
);
}
#[test]
fn binary_round_trips() {
use crate::engine::field::iso_transport_protocol_data_transfer::DATA;
let payload = vec![0xde_u8, 0xad, 0xbe, 0xef, 0x01, 0x02, 0x03];
let frame = db()
.encode("isoTransportProtocolDataTransfer")
.unwrap()
.push(DATA, payload.clone())
.unwrap()
.build()
.unwrap();
let d = db().decode(&frame).unwrap();
match d.field(DATA).map(|f| &f.value) {
Some(crate::engine::FieldValue::Binary(v)) => assert_eq!(v, &payload),
other => panic!("expected binary, got {other:?}"),
}
}
#[test]
fn float_round_trips() {
use crate::engine::field::garmin_autopilot_heading_to_steer::HEADING_TO_STEER as HTS;
let frame = db()
.encode("garminAutopilotHeadingToSteer")
.unwrap()
.push(HTS, 1.5)
.unwrap()
.build()
.unwrap();
let d = db().decode(&frame).unwrap();
let got = d.field(HTS).and_then(|f| f.value.as_f64()).unwrap();
assert!((got - 1.5).abs() < 1e-6, "got {got}");
}
#[test]
fn iso_name_round_trips() {
use crate::engine::field::simnet_data_source_selection::SOURCE;
let name: u64 = 0xC078_8C00_E7E0_4364;
let frame = db()
.encode("simnetDataSourceSelection")
.unwrap()
.push(SOURCE, name)
.unwrap()
.build()
.unwrap();
let d = db().decode(&frame).unwrap();
match d.field(SOURCE).map(|f| &f.value) {
Some(crate::engine::FieldValue::IsoName { value, .. }) => assert_eq!(*value, name),
other => panic!("expected iso name, got {other:?}"),
}
}
#[test]
fn simnet_key_value_encodes_key_and_value() {
let frame = db()
.encode("simnetKeyValue")
.unwrap()
.destination(255)
.push_by_name("Address", EncodeValue::Int(255))
.unwrap()
.push_by_name("Instance", EncodeValue::Int(255))
.unwrap()
.push_by_name("Network Group", EncodeValue::Int(1))
.unwrap()
.push_by_name("Source", EncodeValue::Int(255))
.unwrap()
.push_by_name("Key", EncodeValue::Int(9983))
.unwrap()
.push_by_name("Operation", EncodeValue::Int(1))
.unwrap()
.push_by_name("Value", EncodeValue::Bytes(vec![0x04]))
.unwrap()
.build()
.unwrap();
assert_eq!(frame.pgn, 130845);
assert_eq!(frame.data.len(), 11);
assert_eq!(&frame.data[6..9], &[0xff, 0x26, 0x00]);
assert_eq!(frame.data[9], 0x01); assert_eq!(frame.data[10], 0x04); }
#[test]
fn dynamic_field_value_rejects_non_bytes() {
assert!(matches!(
db().encode("simnetKeyValue")
.unwrap()
.push_by_name("Value", EncodeValue::Int(4)),
Err(EncodeError::TypeMismatch { .. })
));
}
#[test]
fn dynamic_group_function_field_is_a_clear_error() {
let mut b = db().encode("nmeaRequestGroupFunction").unwrap();
b.add_set_instance(1).unwrap();
let err = b.build();
assert!(
matches!(err, Err(EncodeError::NotFixedLength(_))),
"got {err:?}"
);
}
#[test]
fn zero_instance_repeating_pgn_builds_with_zero_count() {
let frame = db()
.encode("nmeaRequestGroupFunction")
.unwrap()
.build()
.unwrap();
assert_eq!(frame.pgn, 126208);
let decoded = db().decode(&frame).unwrap();
match &decoded
.field_by_name("Number of Parameters")
.expect("count field decoded")
.value
{
crate::engine::FieldValue::Integer(n) => assert_eq!(*n, 0),
crate::engine::FieldValue::Number(x) => assert_eq!(*x, 0.0),
other => panic!("count: expected a number, got {other:?}"),
}
}
}