1use std::collections::HashMap;
23
24use crate::base_type::BaseType;
25use crate::crc;
26use crate::dev_fields::{base_type_to_type_name, DevFieldInfo, DevFieldRegistry};
27use crate::error::{FieldTooLargeKind, FitError};
28use crate::output_stream::OutputStream;
29use crate::profile;
30use crate::value::{FieldKind, Message, Value};
31
32#[derive(Debug, Clone)]
38pub struct Encoder {
39 protocol_version: u8,
40 profile_version: u16,
41}
42
43impl Encoder {
44 pub fn new() -> Self {
46 Self {
47 protocol_version: 0x20,
48 profile_version: 21200,
49 }
50 }
51
52 pub fn builder() -> EncoderBuilder {
54 EncoderBuilder::default()
55 }
56
57 pub fn encode(&self, messages: &[Message]) -> Result<Vec<u8>, FitError> {
59 let mut out = OutputStream::with_capacity(14 + messages.len() * 32);
60 self.write_segment(&mut out, messages)?;
61 Ok(out.into_bytes())
62 }
63
64 pub fn encode_chain(&self, segments: &[&[Message]]) -> Result<Vec<u8>, FitError> {
68 if segments.is_empty() {
69 return Ok(self.encode_empty());
70 }
71 let mut out = OutputStream::with_capacity(segments.iter().map(|s| s.len() * 32).sum());
72 for seg in segments {
73 self.write_segment(&mut out, seg)?;
74 }
75 Ok(out.into_bytes())
76 }
77
78 fn write_segment(&self, out: &mut OutputStream, messages: &[Message]) -> Result<(), FitError> {
81 if messages.is_empty() {
82 self.write_empty_segment(out);
83 return Ok(());
84 }
85
86 let dev_registry = collect_dev_registry(messages);
90
91 let segment_start = out.position();
92
93 out.write_u8(14);
95 out.write_u8(self.protocol_version);
96 out.write_u16(self.profile_version);
97 out.write_u32(0); out.write_bytes(b".FIT");
99 out.write_u16(0); let mut registry = LocalDefRegistry::new();
102 let mut clock: u64 = 0;
103
104 for msg in messages {
105 clock += 1;
106 let new_def = build_wire_def(msg, &dev_registry)?;
107 let local = registry.acquire(msg.global_mesg_num, &new_def, clock)?;
108
109 if registry.needs_redefinition(msg.global_mesg_num, &new_def) {
110 write_definition_record(out, local, &new_def)?;
111 registry.commit(msg.global_mesg_num, local, new_def.clone(), clock);
112 }
113
114 write_data_record(out, local, msg, &new_def, &dev_registry)?;
115 }
116
117 let header_off = segment_start;
119 let data_size = u32::try_from(out.position() - header_off - 14)
120 .expect("FIT segment data exceeds u32::MAX bytes");
121 out.patch(header_off + 4, &data_size.to_le_bytes());
122
123 let header_crc = crc::calculate(&out.as_slice()[header_off..header_off + 12]);
124 out.patch(header_off + 12, &header_crc.to_le_bytes());
125
126 let file_crc = crc::calculate(&out.as_slice()[header_off..]);
127 out.write_u16(file_crc);
128 Ok(())
129 }
130
131 fn write_empty_segment(&self, out: &mut OutputStream) {
132 let segment_start = out.position();
133 out.write_u8(14);
134 out.write_u8(self.protocol_version);
135 out.write_u16(self.profile_version);
136 out.write_u32(0);
137 out.write_bytes(b".FIT");
138 let header_crc = crc::calculate(&out.as_slice()[segment_start..segment_start + 12]);
139 out.write_u16(header_crc);
140 let file_crc = crc::calculate(&out.as_slice()[segment_start..]);
141 out.write_u16(file_crc);
142 }
143
144 fn encode_empty(&self) -> Vec<u8> {
145 let mut out = OutputStream::with_capacity(16);
146 self.write_empty_segment(&mut out);
147 out.into_bytes()
148 }
149}
150
151impl Default for Encoder {
152 fn default() -> Self {
153 Self::new()
154 }
155}
156
157#[derive(Debug, Clone)]
159pub struct EncoderBuilder {
160 protocol_version: u8,
161 profile_version: u16,
162}
163
164impl Default for EncoderBuilder {
165 fn default() -> Self {
166 Self {
167 protocol_version: 0x20,
168 profile_version: 21200,
169 }
170 }
171}
172
173impl EncoderBuilder {
174 pub fn protocol_version(mut self, v: u8) -> Self {
176 self.protocol_version = v;
177 self
178 }
179
180 pub fn profile_version(mut self, v: u16) -> Self {
182 self.profile_version = v;
183 self
184 }
185
186 pub fn build(self) -> Encoder {
188 Encoder {
189 protocol_version: self.protocol_version,
190 profile_version: self.profile_version,
191 }
192 }
193}
194
195#[derive(Debug, Clone, PartialEq)]
201struct WireField {
202 field_def_num: u8,
203 size: u8,
205 base_type: BaseType,
206 base_type_byte: u8,
207 type_name: &'static str,
211 scale: Option<f64>,
213 offset: Option<f64>,
215}
216
217#[derive(Debug, Clone, PartialEq)]
219struct WireDevField {
220 field_def_num: u8,
221 size: u8,
222 developer_data_index: u8,
223 base_type: BaseType,
224 scale: Option<f64>,
225 offset: Option<f64>,
226}
227
228#[derive(Debug, Clone, PartialEq)]
230struct WireDef {
231 global_mesg_num: u16,
232 fields: Vec<WireField>,
233 dev_fields: Vec<WireDevField>,
234}
235
236fn is_real_wire_field(
245 field_name: &str,
246 field_def_num: u8,
247 mesg_info: Option<&profile::MesgInfo>,
248) -> bool {
249 let Some(info) = mesg_info else {
250 return true;
251 };
252 let Some(real) = info.field(field_def_num) else {
253 return false;
254 };
255 real.name == field_name || real.sub_fields.iter().any(|sf| sf.name == field_name)
256}
257
258fn build_wire_def(msg: &Message, dev_registry: &DevFieldRegistry) -> Result<WireDef, FitError> {
262 let mesg_info = profile::mesg_info_by_num(msg.global_mesg_num);
263
264 let mut fields = Vec::with_capacity(msg.fields.len());
265 let mut dev_fields = Vec::new();
266 let mut seen_fdns = std::collections::HashSet::<u8>::new();
267
268 for field in &msg.fields {
269 match field.kind {
270 FieldKind::Standard { field_def_num } => {
271 let fi = mesg_info.and_then(|m| m.field(field_def_num));
272
273 if !is_real_wire_field(&field.name, field_def_num, mesg_info) {
274 continue;
275 }
276 if !seen_fdns.insert(field_def_num) {
277 continue;
278 }
279
280 let base_type = resolve_base_type(fi, &field.value);
281 let base_type_byte = base_type.type_code();
282 let size = compute_wire_size(fi, &field.value, base_type)?;
283 let (type_name, scale, offset) = match fi {
284 Some(f) => (f.type_name, f.scale, f.offset),
285 None => ("", None, None),
286 };
287
288 fields.push(WireField {
289 field_def_num,
290 size,
291 base_type,
292 base_type_byte,
293 type_name,
294 scale,
295 offset,
296 });
297 }
298 FieldKind::Developer {
299 field_def_num,
300 developer_data_index,
301 } => {
302 let Some(info) = dev_registry.get(developer_data_index, field_def_num) else {
305 continue;
306 };
307 let size = compute_dev_wire_size(info, &field.value)?;
308 dev_fields.push(WireDevField {
309 field_def_num,
310 size,
311 developer_data_index,
312 base_type: info.base_type,
313 scale: info.scale,
314 offset: info.offset,
315 });
316 }
317 }
318 }
319
320 Ok(WireDef {
321 global_mesg_num: msg.global_mesg_num,
322 fields,
323 dev_fields,
324 })
325}
326
327fn resolve_base_type(fi: Option<&profile::FieldInfo>, value: &Value) -> BaseType {
332 if let Some(fi) = fi {
333 if let Some(bt) = crate::transforms::enum_strings::base_type_for_type_name(fi.type_name) {
334 return bt;
335 }
336 if let Some(bt) = base_type_from_name(fi.type_name) {
337 return bt;
338 }
339 }
340 infer_base_type(value)
341}
342
343fn base_type_from_name(type_name: &str) -> Option<BaseType> {
344 match type_name {
345 "enum" => Some(BaseType::Enum),
346 "sint8" => Some(BaseType::SInt8),
347 "uint8" => Some(BaseType::UInt8),
348 "sint16" => Some(BaseType::SInt16),
349 "uint16" => Some(BaseType::UInt16),
350 "sint32" => Some(BaseType::SInt32),
351 "uint32" => Some(BaseType::UInt32),
352 "sint64" => Some(BaseType::SInt64),
353 "uint64" => Some(BaseType::UInt64),
354 "float32" => Some(BaseType::Float32),
355 "float64" => Some(BaseType::Float64),
356 "uint8z" => Some(BaseType::UInt8z),
357 "uint16z" => Some(BaseType::UInt16z),
358 "uint32z" => Some(BaseType::UInt32z),
359 "uint64z" => Some(BaseType::UInt64z),
360 "string" => Some(BaseType::String),
361 "byte" => Some(BaseType::Byte),
362 "bool" => Some(BaseType::UInt8),
363 "date_time" | "local_date_time" => Some(BaseType::UInt32),
364 _ => None,
365 }
366}
367
368fn infer_base_type(value: &Value) -> BaseType {
369 match value {
370 Value::UInt(_) => BaseType::UInt32,
371 Value::SInt(_) => BaseType::SInt32,
372 Value::Float(_) => BaseType::Float64,
373 Value::String(_) => BaseType::String,
374 Value::Bytes(_) => BaseType::Byte,
375 Value::Bool(_) => BaseType::UInt8,
376 Value::Enum(_) => BaseType::Enum,
377 Value::DateTime(_) => BaseType::UInt32,
378 Value::Invalid => BaseType::UInt32,
379 Value::Array(items) if !items.is_empty() => infer_base_type(&items[0]),
380 Value::Array(_) => BaseType::UInt32,
381 }
382}
383
384fn compute_wire_size(
385 fi: Option<&profile::FieldInfo>,
386 value: &Value,
387 base_type: BaseType,
388) -> Result<u8, FitError> {
389 if base_type == BaseType::String {
390 return match value {
391 Value::String(s) => u8::try_from(s.len() + 1).map_err(|_| FitError::FieldTooLarge {
392 kind: FieldTooLargeKind::String,
393 size: s.len(),
394 }),
395 _ => Ok(1),
396 };
397 }
398 if base_type == BaseType::Byte {
399 return match value {
400 Value::Bytes(b) => u8::try_from(b.len().max(1)).map_err(|_| FitError::FieldTooLarge {
401 kind: FieldTooLargeKind::ByteArray,
402 size: b.len(),
403 }),
404 _ => Ok(1),
405 };
406 }
407 let element_size = base_type.element_size() as u8;
408 let count = element_count(fi, value)?;
409 Ok(element_size * count.max(1))
410}
411
412fn compute_dev_wire_size(info: &DevFieldInfo, value: &Value) -> Result<u8, FitError> {
413 if info.base_type == BaseType::String {
414 return match value {
415 Value::String(s) => u8::try_from(s.len() + 1).map_err(|_| FitError::FieldTooLarge {
416 kind: FieldTooLargeKind::String,
417 size: s.len(),
418 }),
419 _ => Ok(1),
420 };
421 }
422 if info.base_type == BaseType::Byte {
423 return match value {
424 Value::Bytes(b) => u8::try_from(b.len().max(1)).map_err(|_| FitError::FieldTooLarge {
425 kind: FieldTooLargeKind::ByteArray,
426 size: b.len(),
427 }),
428 _ => Ok(1),
429 };
430 }
431 let element_size = info.base_type.element_size() as u8;
432 if let Value::Array(a) = value {
433 let len = u8::try_from(a.len()).map_err(|_| FitError::FieldTooLarge {
434 kind: FieldTooLargeKind::Array,
435 size: a.len(),
436 })?;
437 return Ok(element_size * len.max(1));
438 }
439 Ok(element_size)
440}
441
442fn element_count(fi: Option<&profile::FieldInfo>, value: &Value) -> Result<u8, FitError> {
443 if let Value::Array(a) = value {
444 return u8::try_from(a.len()).map_err(|_| FitError::FieldTooLarge {
445 kind: FieldTooLargeKind::Array,
446 size: a.len(),
447 });
448 }
449 if let Some(fi) = fi {
450 if let Some(spec) = fi.array {
451 let inner = spec.trim_matches(|c| c == '[' || c == ']');
452 if let Some((first, _rest)) = inner.split_once('x') {
453 if let Ok(n) = first.parse::<u8>() {
454 return Ok(n);
455 }
456 } else if inner != "N" {
457 if let Ok(n) = inner.parse::<u8>() {
458 return Ok(n);
459 }
460 }
461 }
462 }
463 Ok(1)
464}
465
466#[derive(Debug, Clone)]
471struct RegisteredDef {
472 mesg_num: u16,
473 wire_def: WireDef,
474 last_used: u64,
475}
476
477#[derive(Debug, Default)]
481struct LocalDefRegistry {
482 slots: [Option<RegisteredDef>; 16],
484 by_mesg_num: HashMap<u16, u8>,
486 last_acquire_was_match: bool,
489}
490
491impl LocalDefRegistry {
492 fn new() -> Self {
493 Self::default()
494 }
495
496 fn acquire(&mut self, mesg_num: u16, wire_def: &WireDef, clock: u64) -> Result<u8, FitError> {
500 if let Some(&local) = self.by_mesg_num.get(&mesg_num) {
501 let slot = self.slots[local as usize]
502 .as_mut()
503 .expect("by_mesg_num invariant");
504 self.last_acquire_was_match = slot.wire_def == *wire_def;
505 slot.last_used = clock;
506 return Ok(local);
507 }
508 if let Some(free) = self.slots.iter().position(Option::is_none) {
510 self.last_acquire_was_match = false;
511 return Ok(free as u8);
512 }
513 let (lru_local, lru_mesg) = self
515 .slots
516 .iter()
517 .enumerate()
518 .min_by_key(|(_, s)| s.as_ref().map(|d| d.last_used).unwrap_or(u64::MAX))
519 .map(|(i, s)| (i as u8, s.as_ref().expect("all slots occupied").mesg_num))
520 .ok_or(FitError::TooManyLocalDefinitions(17))?;
521 self.by_mesg_num.remove(&lru_mesg);
522 self.slots[lru_local as usize] = None;
523 self.last_acquire_was_match = false;
524 Ok(lru_local)
525 }
526
527 fn needs_redefinition(&self, mesg_num: u16, wire_def: &WireDef) -> bool {
530 match self.by_mesg_num.get(&mesg_num) {
531 Some(&local) => self.slots[local as usize]
532 .as_ref()
533 .map(|d| d.wire_def != *wire_def)
534 .unwrap_or(true),
535 None => true,
536 }
537 }
538
539 fn commit(&mut self, mesg_num: u16, local: u8, wire_def: WireDef, clock: u64) {
543 self.slots[local as usize] = Some(RegisteredDef {
544 mesg_num,
545 wire_def,
546 last_used: clock,
547 });
548 self.by_mesg_num.insert(mesg_num, local);
549 }
550}
551
552fn collect_dev_registry(messages: &[Message]) -> DevFieldRegistry {
561 let mut reg = DevFieldRegistry::new();
562 for m in messages {
563 if m.global_mesg_num != 206 {
564 continue;
565 }
566 let dev_idx = m.field("developer_data_index").and_then(value_as_u8);
567 let fdn = m.field("field_definition_number").and_then(value_as_u8);
568 let bt_id = m.field("fit_base_type_id").and_then(|f| match &f.value {
572 Value::UInt(v) => Some(*v as u8),
573 Value::SInt(v) => Some(*v as u8),
574 Value::Enum(name) => {
575 crate::transforms::enum_strings::enum_value_by_str("fit_base_type", name)
576 .map(|v| v as u8)
577 }
578 _ => None,
579 });
580 let name = m
581 .field("field_name")
582 .and_then(|f| match &f.value {
583 Value::String(s) => Some(s.clone()),
584 _ => None,
585 })
586 .unwrap_or_default();
587 let scale = m.field("scale").and_then(|f| match &f.value {
588 Value::Float(v) => Some(*v),
589 Value::UInt(v) => Some(*v as f64),
590 _ => None,
591 });
592 let offset = m.field("offset").and_then(|f| match &f.value {
593 Value::Float(v) => Some(*v),
594 Value::SInt(v) => Some(*v as f64),
595 _ => None,
596 });
597 let units = m.field("units").and_then(|f| match &f.value {
598 Value::String(s) => Some(s.clone()),
599 _ => None,
600 });
601 if let (Some(dev_idx), Some(fdn), Some(bt_id)) = (dev_idx, fdn, bt_id) {
602 reg.register_field(dev_idx, fdn, name, bt_id, scale, offset, units);
603 }
604 }
605 reg
606}
607
608fn value_as_u8(f: &crate::value::Field) -> Option<u8> {
609 match &f.value {
610 Value::UInt(v) => Some(*v as u8),
611 Value::SInt(v) => Some(*v as u8),
612 _ => None,
613 }
614}
615
616fn write_definition_record(
621 out: &mut OutputStream,
622 local_mesg_num: u8,
623 def: &WireDef,
624) -> Result<(), FitError> {
625 let header = if def.dev_fields.is_empty() {
626 0x40 | (local_mesg_num & 0x0F)
627 } else {
628 0x40 | 0x20 | (local_mesg_num & 0x0F)
630 };
631 out.write_u8(header);
632 out.write_u8(0x00); out.write_u8(0x00); out.write_u16(def.global_mesg_num);
635 let field_count = u8::try_from(def.fields.len()).map_err(|_| FitError::FieldTooLarge {
636 kind: FieldTooLargeKind::FieldList,
637 size: def.fields.len(),
638 })?;
639 out.write_u8(field_count);
640 for f in &def.fields {
641 out.write_u8(f.field_def_num);
642 out.write_u8(f.size);
643 out.write_u8(f.base_type_byte);
644 }
645 if !def.dev_fields.is_empty() {
646 let dev_count =
647 u8::try_from(def.dev_fields.len()).map_err(|_| FitError::FieldTooLarge {
648 kind: FieldTooLargeKind::DevFieldList,
649 size: def.dev_fields.len(),
650 })?;
651 out.write_u8(dev_count);
652 for d in &def.dev_fields {
653 out.write_u8(d.field_def_num);
654 out.write_u8(d.size);
655 out.write_u8(d.developer_data_index);
656 }
657 }
658 Ok(())
659}
660
661fn write_data_record(
662 out: &mut OutputStream,
663 local_mesg_num: u8,
664 msg: &Message,
665 def: &WireDef,
666 dev_registry: &DevFieldRegistry,
667) -> Result<(), FitError> {
668 out.write_u8(local_mesg_num & 0x0F); let mesg_info = profile::mesg_info_by_num(msg.global_mesg_num);
670
671 for wf in &def.fields {
673 let value = msg
674 .fields
675 .iter()
676 .find(|f| {
677 let FieldKind::Standard { field_def_num } = f.kind else {
678 return false;
679 };
680 field_def_num == wf.field_def_num
681 && is_real_wire_field(&f.name, field_def_num, mesg_info)
682 })
683 .map(|f| &f.value);
684 encode_field_value(out, value, wf)?;
685 }
686
687 for wd in &def.dev_fields {
689 let value = msg
690 .fields
691 .iter()
692 .find(|f| {
693 matches!(f.kind, FieldKind::Developer { field_def_num, developer_data_index }
694 if field_def_num == wd.field_def_num
695 && developer_data_index == wd.developer_data_index)
696 })
697 .map(|f| &f.value);
698 encode_dev_field_value(out, value, wd, dev_registry)?;
699 }
700 Ok(())
701}
702
703fn encode_field_value(
704 out: &mut OutputStream,
705 value: Option<&Value>,
706 wf: &WireField,
707) -> Result<(), FitError> {
708 let Some(value) = value else {
709 write_invalid(out, wf.base_type, wf.size);
710 return Ok(());
711 };
712 encode_value_inner(
713 out,
714 value,
715 wf.base_type,
716 wf.size,
717 wf.type_name,
718 wf.scale,
719 wf.offset,
720 )
721}
722
723fn encode_dev_field_value(
724 out: &mut OutputStream,
725 value: Option<&Value>,
726 wd: &WireDevField,
727 _registry: &DevFieldRegistry,
728) -> Result<(), FitError> {
729 let Some(value) = value else {
730 write_invalid(out, wd.base_type, wd.size);
731 return Ok(());
732 };
733 encode_value_inner(
734 out,
735 value,
736 wd.base_type,
737 wd.size,
738 base_type_to_type_name(wd.base_type),
739 wd.scale,
740 wd.offset,
741 )
742}
743
744fn encode_value_inner(
748 out: &mut OutputStream,
749 value: &Value,
750 base_type: BaseType,
751 size: u8,
752 type_name: &str,
753 scale: Option<f64>,
754 offset: Option<f64>,
755) -> Result<(), FitError> {
756 match value {
757 Value::Invalid => write_invalid(out, base_type, size),
758 Value::Bool(b) => out.write_u8(if *b { 1 } else { 0 }),
759 Value::UInt(v) => encode_int(out, *v as i128, base_type),
760 Value::SInt(v) => encode_int(out, *v as i128, base_type),
761 Value::Float(v) => encode_float(out, *v, base_type, scale, offset),
762 Value::String(s) => {
763 out.write_bytes(s.as_bytes());
764 out.write_u8(0x00);
765 for _ in s.len() + 1..size as usize {
766 out.write_u8(0x00);
767 }
768 }
769 Value::Bytes(b) => {
770 out.write_bytes(b);
771 for _ in b.len()..size as usize {
772 out.write_u8(0xFF);
773 }
774 }
775 Value::Enum(name) => {
776 let lookup_name = if type_name.is_empty() {
777 "enum"
778 } else {
779 type_name
780 };
781 if let Some(v) = crate::transforms::enum_strings::enum_value_by_str(lookup_name, name) {
782 encode_int(out, v as i128, base_type);
783 } else {
784 write_invalid(out, base_type, size);
785 }
786 }
787 Value::DateTime(dt) => {
788 #[cfg(feature = "chrono")]
789 let secs = crate::datetime::datetime_to_fit(*dt);
790 #[cfg(not(feature = "chrono"))]
791 let secs = Some(*dt);
792 if let Some(secs) = secs {
793 encode_int(out, secs as i128, base_type);
794 } else {
795 write_invalid(out, base_type, size);
796 }
797 }
798 Value::Array(items) => {
799 let element_size = base_type.element_size() as u8;
800 for item in items {
801 encode_value_inner(out, item, base_type, element_size, type_name, scale, offset)?;
802 }
803 let written = items.len() * base_type.element_size();
804 for _ in written..size as usize {
805 out.write_u8(invalid_byte_for_base(base_type));
806 }
807 }
808 }
809 Ok(())
810}
811
812fn encode_float(
813 out: &mut OutputStream,
814 v: f64,
815 base_type: BaseType,
816 scale: Option<f64>,
817 offset: Option<f64>,
818) {
819 match base_type {
820 BaseType::Float32 => out.write_u32((v as f32).to_bits()),
821 BaseType::Float64 => out.write_u64(v.to_bits()),
822 _ => {
823 let offset = offset.unwrap_or(0.0);
824 let scale = scale.unwrap_or(1.0);
825 let scaled = (v + offset) * scale;
826 let size = base_type.element_size() as u8;
827 if !scaled.is_finite() {
830 write_invalid(out, base_type, size);
831 return;
832 }
833 let as_i128 = scaled.round() as i128;
834 let (lo, hi) = base_type_int_range(base_type);
835 if as_i128 < lo || as_i128 > hi {
836 write_invalid(out, base_type, size);
837 return;
838 }
839 encode_int(out, as_i128, base_type);
840 }
841 }
842}
843
844fn base_type_int_range(base_type: BaseType) -> (i128, i128) {
849 match base_type {
850 BaseType::Enum | BaseType::UInt8 | BaseType::UInt8z | BaseType::Byte => {
851 (0, u8::MAX as i128)
852 }
853 BaseType::UInt16 | BaseType::UInt16z => (0, u16::MAX as i128),
854 BaseType::UInt32 | BaseType::UInt32z => (0, u32::MAX as i128),
855 BaseType::UInt64 | BaseType::UInt64z => (0, u64::MAX as i128),
856 BaseType::SInt8 => (i8::MIN as i128, i8::MAX as i128),
857 BaseType::SInt16 => (i16::MIN as i128, i16::MAX as i128),
858 BaseType::SInt32 => (i32::MIN as i128, i32::MAX as i128),
859 BaseType::SInt64 => (i64::MIN as i128, i64::MAX as i128),
860 BaseType::Float32 | BaseType::Float64 | BaseType::String => (0, 0),
861 }
862}
863
864fn encode_int(out: &mut OutputStream, v: i128, base_type: BaseType) {
865 match base_type {
866 BaseType::Enum | BaseType::UInt8 | BaseType::UInt8z | BaseType::Byte => {
867 out.write_u8(v as u8);
868 }
869 BaseType::UInt16 | BaseType::UInt16z => out.write_u16(v as u16),
870 BaseType::UInt32 | BaseType::UInt32z => out.write_u32(v as u32),
871 BaseType::UInt64 | BaseType::UInt64z => out.write_u64(v as u64),
872 BaseType::SInt8 => out.write_u8(v as i8 as u8),
873 BaseType::SInt16 => out.write_i16(v as i16),
874 BaseType::SInt32 => out.write_i32(v as i32),
875 BaseType::SInt64 => out.write_i64(v as i64),
876 BaseType::Float32 => out.write_u32((v as f32).to_bits()),
877 BaseType::Float64 => out.write_u64((v as f64).to_bits()),
878 BaseType::String => {} }
880}
881
882fn write_invalid(out: &mut OutputStream, base_type: BaseType, size: u8) {
883 let element_size = base_type.element_size().max(1);
884 let count = (size as usize / element_size).max(1);
885 match base_type {
886 BaseType::Enum | BaseType::UInt8 | BaseType::Byte => {
887 for _ in 0..count {
888 out.write_u8(0xFF);
889 }
890 }
891 BaseType::UInt8z => {
892 for _ in 0..count {
893 out.write_u8(0x00);
894 }
895 }
896 BaseType::SInt8 => {
897 for _ in 0..count {
898 out.write_u8(0x7F);
899 }
900 }
901 BaseType::UInt16 => {
902 for _ in 0..count {
903 out.write_u16(0xFFFF);
904 }
905 }
906 BaseType::UInt16z => {
907 for _ in 0..count {
908 out.write_u16(0x0000);
909 }
910 }
911 BaseType::SInt16 => {
912 for _ in 0..count {
913 out.write_i16(i16::MAX);
914 }
915 }
916 BaseType::UInt32 | BaseType::Float32 => {
917 for _ in 0..count {
918 out.write_u32(0xFFFF_FFFF);
919 }
920 }
921 BaseType::UInt32z => {
922 for _ in 0..count {
923 out.write_u32(0);
924 }
925 }
926 BaseType::SInt32 => {
927 for _ in 0..count {
928 out.write_i32(i32::MAX);
929 }
930 }
931 BaseType::UInt64 | BaseType::Float64 => {
932 for _ in 0..count {
933 out.write_u64(0xFFFF_FFFF_FFFF_FFFF);
934 }
935 }
936 BaseType::UInt64z => {
937 for _ in 0..count {
938 out.write_u64(0);
939 }
940 }
941 BaseType::SInt64 => {
942 for _ in 0..count {
943 out.write_i64(i64::MAX);
944 }
945 }
946 BaseType::String => out.write_u8(0x00),
947 }
948}
949
950fn invalid_byte_for_base(base_type: BaseType) -> u8 {
951 match base_type {
952 BaseType::UInt8z | BaseType::UInt16z | BaseType::UInt32z | BaseType::UInt64z => 0x00,
953 BaseType::String => 0x00,
954 _ => 0xFF,
955 }
956}
957
958#[cfg(test)]
959mod tests {
960 use super::*;
961
962 #[test]
963 fn encode_empty_produces_valid_header() {
964 let bytes = Encoder::new().encode(&[]).unwrap();
965 assert_eq!(bytes.len(), 16);
966 assert_eq!(&bytes[8..12], b".FIT");
967 assert_eq!(bytes[0], 14);
968 crate::check_integrity(&bytes).unwrap();
969 }
970
971 #[test]
972 fn encoder_builder_overrides_versions() {
973 let enc = Encoder::builder()
974 .protocol_version(0x21)
975 .profile_version(21300)
976 .build();
977 let bytes = enc.encode(&[]).unwrap();
978 assert_eq!(bytes[1], 0x21);
979 assert_eq!(u16::from_le_bytes([bytes[2], bytes[3]]), 21300);
980 crate::check_integrity(&bytes).unwrap();
981 }
982
983 #[test]
984 fn lru_registry_evicts_least_recently_used() {
985 let mut reg = LocalDefRegistry::new();
986 for i in 0..16u16 {
988 let def = WireDef {
989 global_mesg_num: i,
990 fields: vec![],
991 dev_fields: vec![],
992 };
993 let local = reg.acquire(i, &def, i as u64 + 1).unwrap();
994 reg.commit(i, local, def, i as u64 + 1);
995 }
996 for i in 1..16u16 {
999 let def = WireDef {
1000 global_mesg_num: i,
1001 fields: vec![],
1002 dev_fields: vec![],
1003 };
1004 reg.acquire(i, &def, 100 + i as u64).unwrap();
1005 }
1006 let new_def = WireDef {
1008 global_mesg_num: 999,
1009 fields: vec![],
1010 dev_fields: vec![],
1011 };
1012 let local = reg.acquire(999, &new_def, 200).unwrap();
1013 assert_eq!(local, 0);
1014 reg.commit(999, local, new_def, 200);
1015 assert!(reg.by_mesg_num.contains_key(&999));
1016 assert!(!reg.by_mesg_num.contains_key(&0));
1017 }
1018}