lnmp_codec/binary/
entry.rs

1//! Binary entry structure for LNMP v0.4 protocol.
2//!
3//! A BinaryEntry represents a single field in binary format, consisting of:
4//! - FID (Field Identifier): 2 bytes, little-endian
5//! - TAG (Type Tag): 1 byte
6//! - VALUE: Variable length, encoding depends on type
7
8use super::error::BinaryError;
9use super::types::{BinaryValue, TypeTag};
10use super::varint;
11use lnmp_core::{FieldId, LnmpField};
12use lnmp_embedding::{Decoder as EmbeddingDecoder, Encoder as EmbeddingEncoder};
13
14/// A single field encoded in binary format
15#[derive(Debug, Clone, PartialEq)]
16pub struct BinaryEntry {
17    /// Field identifier (16-bit unsigned integer)
18    pub fid: FieldId,
19    /// Type tag indicating value type
20    pub tag: TypeTag,
21    /// The encoded value
22    pub value: BinaryValue,
23}
24
25impl BinaryEntry {
26    /// Creates a new binary entry
27    pub fn new(fid: FieldId, value: BinaryValue) -> Self {
28        Self {
29            fid,
30            tag: value.type_tag(),
31            value,
32        }
33    }
34
35    /// Creates a new binary entry from an LnmpField
36    ///
37    /// # Errors
38    ///
39    /// Returns `BinaryError::InvalidValue` if the field contains nested structures
40    /// (not supported in v0.4 binary format)
41    pub fn from_field(field: &LnmpField) -> Result<Self, BinaryError> {
42        let value = BinaryValue::from_lnmp_value(&field.value).map_err(|e| match e {
43            BinaryError::InvalidValue {
44                type_tag, reason, ..
45            } => BinaryError::InvalidValue {
46                field_id: field.fid,
47                type_tag,
48                reason,
49            },
50            other => other,
51        })?;
52
53        Ok(Self {
54            fid: field.fid,
55            tag: value.type_tag(),
56            value,
57        })
58    }
59
60    /// Converts to an LnmpField
61    pub fn to_field(&self) -> LnmpField {
62        LnmpField {
63            fid: self.fid,
64            value: self.value.to_lnmp_value(),
65        }
66    }
67
68    /// Returns the type tag of this entry
69    pub fn type_tag(&self) -> TypeTag {
70        self.tag
71    }
72
73    /// Encodes the entry to bytes
74    ///
75    /// Binary layout:
76    /// ```text
77    /// ┌──────────┬──────────┬──────────────────┐
78    /// │   FID    │  THTAG   │      VALUE       │
79    /// │ (2 bytes)│ (1 byte) │   (variable)     │
80    /// └──────────┴──────────┴──────────────────┘
81    /// ```
82    pub fn encode(&self) -> Vec<u8> {
83        let mut bytes = Vec::new();
84
85        // Write FID (2 bytes, little-endian)
86        bytes.extend_from_slice(&self.fid.to_le_bytes());
87
88        // Write TAG (1 byte)
89        bytes.push(self.tag.to_u8());
90
91        // Write VALUE (encoding depends on type)
92        match &self.value {
93            BinaryValue::Int(i) => {
94                // VarInt encoding
95                bytes.extend_from_slice(&varint::encode(*i));
96            }
97            BinaryValue::Float(f) => {
98                // 8 bytes IEEE754 little-endian
99                bytes.extend_from_slice(&f.to_le_bytes());
100            }
101            BinaryValue::Bool(b) => {
102                // 1 byte: 0x00 for false, 0x01 for true
103                bytes.push(if *b { 0x01 } else { 0x00 });
104            }
105            BinaryValue::String(s) => {
106                // Length (VarInt) + UTF-8 bytes
107                let utf8_bytes = s.as_bytes();
108                bytes.extend_from_slice(&varint::encode(utf8_bytes.len() as i64));
109                bytes.extend_from_slice(utf8_bytes);
110            }
111            BinaryValue::StringArray(arr) => {
112                // Count (VarInt) + repeated (length + UTF-8 bytes)
113                bytes.extend_from_slice(&varint::encode(arr.len() as i64));
114                for s in arr {
115                    let utf8_bytes = s.as_bytes();
116                    bytes.extend_from_slice(&varint::encode(utf8_bytes.len() as i64));
117                    bytes.extend_from_slice(utf8_bytes);
118                }
119            }
120            BinaryValue::NestedRecord(_) | BinaryValue::NestedArray(_) => {
121                // Nested structure encoding will be implemented in task 2
122                // For now, this is a placeholder that should not be reached
123                // as nested encoding requires special handling
124                panic!("Nested structure encoding not yet implemented - use BinaryNestedEncoder");
125            }
126            BinaryValue::Embedding(vec) => {
127                // Encode using lnmp-embedding crate
128                // We wrap the result in a Result, but encode() signature returns Vec<u8>
129                // For now, we'll panic on error as this is an in-memory operation that shouldn't fail
130                // unless OOM. Ideally signature should return Result.
131                let encoded = EmbeddingEncoder::encode(vec).expect("Failed to encode embedding");
132                // Write length (VarInt) + encoded data
133                bytes.extend_from_slice(&varint::encode(encoded.len() as i64));
134                bytes.extend_from_slice(&encoded);
135            }
136        }
137
138        bytes
139    }
140
141    /// Decodes an entry from bytes
142    ///
143    /// Returns a tuple of (BinaryEntry, bytes_consumed)
144    ///
145    /// # Errors
146    ///
147    /// Returns errors for:
148    /// - `UnexpectedEof`: Insufficient data
149    /// - `InvalidTypeTag`: Unknown type tag
150    /// - `InvalidVarInt`: Malformed VarInt
151    /// - `InvalidUtf8`: Invalid UTF-8 in string
152    /// - `InvalidValue`: Other value decoding errors
153    pub fn decode(bytes: &[u8]) -> Result<(Self, usize), BinaryError> {
154        let mut offset = 0;
155
156        // Read FID (2 bytes, little-endian)
157        if bytes.len() < 2 {
158            return Err(BinaryError::UnexpectedEof {
159                expected: 2,
160                found: bytes.len(),
161            });
162        }
163        let fid = u16::from_le_bytes([bytes[0], bytes[1]]);
164        offset += 2;
165
166        // Read TAG (1 byte)
167        if bytes.len() < offset + 1 {
168            return Err(BinaryError::UnexpectedEof {
169                expected: offset + 1,
170                found: bytes.len(),
171            });
172        }
173        let tag = TypeTag::from_u8(bytes[offset])?;
174        offset += 1;
175
176        // Read VALUE (depends on type)
177        let value = match tag {
178            TypeTag::NestedRecord | TypeTag::NestedArray => {
179                // Nested structure decoding will be implemented in task 3
180                return Err(BinaryError::InvalidValue {
181                    field_id: fid,
182                    type_tag: tag.to_u8(),
183                    reason:
184                        "Nested structure decoding not yet implemented - use BinaryNestedDecoder"
185                            .to_string(),
186                });
187            }
188            TypeTag::Reserved09
189            | TypeTag::Reserved0A
190            | TypeTag::Reserved0B
191            | TypeTag::Reserved0C
192            | TypeTag::Reserved0D
193            | TypeTag::Reserved0E
194            | TypeTag::Reserved0F => {
195                return Err(BinaryError::InvalidValue {
196                    field_id: fid,
197                    type_tag: tag.to_u8(),
198                    reason: format!("Reserved type tag 0x{:02X} cannot be used", tag.to_u8()),
199                });
200            }
201            TypeTag::Int => {
202                let (int_val, consumed) =
203                    varint::decode(&bytes[offset..]).map_err(|_| BinaryError::InvalidValue {
204                        field_id: fid,
205                        type_tag: tag.to_u8(),
206                        reason: "Invalid VarInt encoding".to_string(),
207                    })?;
208                offset += consumed;
209                BinaryValue::Int(int_val)
210            }
211            TypeTag::Float => {
212                if bytes.len() < offset + 8 {
213                    return Err(BinaryError::UnexpectedEof {
214                        expected: offset + 8,
215                        found: bytes.len(),
216                    });
217                }
218                let float_bytes: [u8; 8] = bytes[offset..offset + 8]
219                    .try_into()
220                    .expect("slice length checked");
221                let float_val = f64::from_le_bytes(float_bytes);
222                offset += 8;
223                BinaryValue::Float(float_val)
224            }
225            TypeTag::Bool => {
226                if bytes.len() < offset + 1 {
227                    return Err(BinaryError::UnexpectedEof {
228                        expected: offset + 1,
229                        found: bytes.len(),
230                    });
231                }
232                let bool_val = match bytes[offset] {
233                    0x00 => false,
234                    0x01 => true,
235                    other => {
236                        return Err(BinaryError::InvalidValue {
237                            field_id: fid,
238                            type_tag: tag.to_u8(),
239                            reason: format!(
240                                "Invalid boolean value: 0x{:02X} (expected 0x00 or 0x01)",
241                                other
242                            ),
243                        });
244                    }
245                };
246                offset += 1;
247                BinaryValue::Bool(bool_val)
248            }
249            TypeTag::String => {
250                let (length, consumed) =
251                    varint::decode(&bytes[offset..]).map_err(|_| BinaryError::InvalidValue {
252                        field_id: fid,
253                        type_tag: tag.to_u8(),
254                        reason: "Invalid string length VarInt".to_string(),
255                    })?;
256                offset += consumed;
257
258                if length < 0 {
259                    return Err(BinaryError::InvalidValue {
260                        field_id: fid,
261                        type_tag: tag.to_u8(),
262                        reason: format!("Negative string length: {}", length),
263                    });
264                }
265
266                let length = length as usize;
267                if bytes.len() < offset + length {
268                    return Err(BinaryError::UnexpectedEof {
269                        expected: offset + length,
270                        found: bytes.len(),
271                    });
272                }
273
274                let string_val = std::str::from_utf8(&bytes[offset..offset + length])
275                    .map_err(|_| BinaryError::InvalidUtf8 { field_id: fid })?
276                    .to_string();
277                offset += length;
278                BinaryValue::String(string_val)
279            }
280            TypeTag::StringArray => {
281                let (count, consumed) =
282                    varint::decode(&bytes[offset..]).map_err(|_| BinaryError::InvalidValue {
283                        field_id: fid,
284                        type_tag: tag.to_u8(),
285                        reason: "Invalid array count VarInt".to_string(),
286                    })?;
287                offset += consumed;
288
289                if count < 0 {
290                    return Err(BinaryError::InvalidValue {
291                        field_id: fid,
292                        type_tag: tag.to_u8(),
293                        reason: format!("Negative array count: {}", count),
294                    });
295                }
296
297                let count = count as usize;
298                let mut strings = Vec::with_capacity(count);
299
300                for _ in 0..count {
301                    let (length, consumed) = varint::decode(&bytes[offset..]).map_err(|_| {
302                        BinaryError::InvalidValue {
303                            field_id: fid,
304                            type_tag: tag.to_u8(),
305                            reason: "Invalid string length in array".to_string(),
306                        }
307                    })?;
308                    offset += consumed;
309
310                    if length < 0 {
311                        return Err(BinaryError::InvalidValue {
312                            field_id: fid,
313                            type_tag: tag.to_u8(),
314                            reason: format!("Negative string length in array: {}", length),
315                        });
316                    }
317
318                    let length = length as usize;
319                    if bytes.len() < offset + length {
320                        return Err(BinaryError::UnexpectedEof {
321                            expected: offset + length,
322                            found: bytes.len(),
323                        });
324                    }
325
326                    let string_val = std::str::from_utf8(&bytes[offset..offset + length])
327                        .map_err(|_| BinaryError::InvalidUtf8 { field_id: fid })?
328                        .to_string();
329                    offset += length;
330                    strings.push(string_val);
331                }
332
333                BinaryValue::StringArray(strings)
334            }
335            TypeTag::Embedding => {
336                let (length, consumed) =
337                    varint::decode(&bytes[offset..]).map_err(|_| BinaryError::InvalidValue {
338                        field_id: fid,
339                        type_tag: tag.to_u8(),
340                        reason: "Invalid embedding length VarInt".to_string(),
341                    })?;
342                offset += consumed;
343
344                if length < 0 {
345                    return Err(BinaryError::InvalidValue {
346                        field_id: fid,
347                        type_tag: tag.to_u8(),
348                        reason: format!("Negative embedding length: {}", length),
349                    });
350                }
351
352                let length = length as usize;
353                if bytes.len() < offset + length {
354                    return Err(BinaryError::UnexpectedEof {
355                        expected: offset + length,
356                        found: bytes.len(),
357                    });
358                }
359
360                let embedding_bytes = &bytes[offset..offset + length];
361                let vector = EmbeddingDecoder::decode(embedding_bytes).map_err(|e| {
362                    BinaryError::InvalidValue {
363                        field_id: fid,
364                        type_tag: tag.to_u8(),
365                        reason: format!("Failed to decode embedding: {}", e),
366                    }
367                })?;
368                offset += length;
369
370                BinaryValue::Embedding(vector)
371            }
372        };
373
374        Ok((Self { fid, tag, value }, offset))
375    }
376}
377
378#[cfg(test)]
379mod tests {
380    #![allow(clippy::approx_constant)]
381
382    use super::*;
383    use lnmp_core::LnmpValue;
384
385    #[test]
386    fn test_from_field_int() {
387        let field = LnmpField {
388            fid: 12,
389            value: LnmpValue::Int(14532),
390        };
391
392        let entry = BinaryEntry::from_field(&field).unwrap();
393        assert_eq!(entry.fid, 12);
394        assert_eq!(entry.tag, TypeTag::Int);
395        assert_eq!(entry.value, BinaryValue::Int(14532));
396    }
397
398    #[test]
399    fn test_from_field_float() {
400        let field = LnmpField {
401            fid: 5,
402            value: LnmpValue::Float(3.14),
403        };
404
405        let entry = BinaryEntry::from_field(&field).unwrap();
406        assert_eq!(entry.fid, 5);
407        assert_eq!(entry.tag, TypeTag::Float);
408        assert_eq!(entry.value, BinaryValue::Float(3.14));
409    }
410
411    #[test]
412    fn test_from_field_bool() {
413        let field = LnmpField {
414            fid: 7,
415            value: LnmpValue::Bool(true),
416        };
417
418        let entry = BinaryEntry::from_field(&field).unwrap();
419        assert_eq!(entry.fid, 7);
420        assert_eq!(entry.tag, TypeTag::Bool);
421        assert_eq!(entry.value, BinaryValue::Bool(true));
422    }
423
424    #[test]
425    fn test_from_field_string() {
426        let field = LnmpField {
427            fid: 1,
428            value: LnmpValue::String("hello".to_string()),
429        };
430
431        let entry = BinaryEntry::from_field(&field).unwrap();
432        assert_eq!(entry.fid, 1);
433        assert_eq!(entry.tag, TypeTag::String);
434        assert_eq!(entry.value, BinaryValue::String("hello".to_string()));
435    }
436
437    #[test]
438    fn test_from_field_string_array() {
439        let field = LnmpField {
440            fid: 23,
441            value: LnmpValue::StringArray(vec!["admin".to_string(), "dev".to_string()]),
442        };
443
444        let entry = BinaryEntry::from_field(&field).unwrap();
445        assert_eq!(entry.fid, 23);
446        assert_eq!(entry.tag, TypeTag::StringArray);
447        assert_eq!(
448            entry.value,
449            BinaryValue::StringArray(vec!["admin".to_string(), "dev".to_string()])
450        );
451    }
452
453    #[test]
454    fn test_to_field_int() {
455        let entry = BinaryEntry {
456            fid: 12,
457            tag: TypeTag::Int,
458            value: BinaryValue::Int(42),
459        };
460
461        let field = entry.to_field();
462        assert_eq!(field.fid, 12);
463        assert_eq!(field.value, LnmpValue::Int(42));
464    }
465
466    #[test]
467    fn test_to_field_float() {
468        let entry = BinaryEntry {
469            fid: 5,
470            tag: TypeTag::Float,
471            value: BinaryValue::Float(2.718),
472        };
473
474        let field = entry.to_field();
475        assert_eq!(field.fid, 5);
476        assert_eq!(field.value, LnmpValue::Float(2.718));
477    }
478
479    #[test]
480    fn test_to_field_bool() {
481        let entry = BinaryEntry {
482            fid: 7,
483            tag: TypeTag::Bool,
484            value: BinaryValue::Bool(false),
485        };
486
487        let field = entry.to_field();
488        assert_eq!(field.fid, 7);
489        assert_eq!(field.value, LnmpValue::Bool(false));
490    }
491
492    #[test]
493    fn test_to_field_string() {
494        let entry = BinaryEntry {
495            fid: 1,
496            tag: TypeTag::String,
497            value: BinaryValue::String("world".to_string()),
498        };
499
500        let field = entry.to_field();
501        assert_eq!(field.fid, 1);
502        assert_eq!(field.value, LnmpValue::String("world".to_string()));
503    }
504
505    #[test]
506    fn test_to_field_string_array() {
507        let entry = BinaryEntry {
508            fid: 23,
509            tag: TypeTag::StringArray,
510            value: BinaryValue::StringArray(vec!["x".to_string(), "y".to_string()]),
511        };
512
513        let field = entry.to_field();
514        assert_eq!(field.fid, 23);
515        assert_eq!(
516            field.value,
517            LnmpValue::StringArray(vec!["x".to_string(), "y".to_string()])
518        );
519    }
520
521    #[test]
522    fn test_encode_int() {
523        let entry = BinaryEntry {
524            fid: 12,
525            tag: TypeTag::Int,
526            value: BinaryValue::Int(14532),
527        };
528
529        let bytes = entry.encode();
530
531        // FID (12 in little-endian)
532        assert_eq!(bytes[0], 0x0C);
533        assert_eq!(bytes[1], 0x00);
534        // TAG (Int = 0x01)
535        assert_eq!(bytes[2], 0x01);
536        // VALUE (14532 as VarInt)
537        let varint_bytes = varint::encode(14532);
538        assert_eq!(&bytes[3..], &varint_bytes[..]);
539    }
540
541    #[test]
542    fn test_encode_float() {
543        let entry = BinaryEntry {
544            fid: 5,
545            tag: TypeTag::Float,
546            value: BinaryValue::Float(3.14),
547        };
548
549        let bytes = entry.encode();
550
551        // FID (5 in little-endian)
552        assert_eq!(bytes[0], 0x05);
553        assert_eq!(bytes[1], 0x00);
554        // TAG (Float = 0x02)
555        assert_eq!(bytes[2], 0x02);
556        // VALUE (3.14 as IEEE754 LE)
557        let float_bytes = 3.14f64.to_le_bytes();
558        assert_eq!(&bytes[3..11], &float_bytes[..]);
559    }
560
561    #[test]
562    fn test_encode_bool_true() {
563        let entry = BinaryEntry {
564            fid: 7,
565            tag: TypeTag::Bool,
566            value: BinaryValue::Bool(true),
567        };
568
569        let bytes = entry.encode();
570
571        // FID (7 in little-endian)
572        assert_eq!(bytes[0], 0x07);
573        assert_eq!(bytes[1], 0x00);
574        // TAG (Bool = 0x03)
575        assert_eq!(bytes[2], 0x03);
576        // VALUE (true = 0x01)
577        assert_eq!(bytes[3], 0x01);
578    }
579
580    #[test]
581    fn test_encode_bool_false() {
582        let entry = BinaryEntry {
583            fid: 7,
584            tag: TypeTag::Bool,
585            value: BinaryValue::Bool(false),
586        };
587
588        let bytes = entry.encode();
589
590        // FID (7 in little-endian)
591        assert_eq!(bytes[0], 0x07);
592        assert_eq!(bytes[1], 0x00);
593        // TAG (Bool = 0x03)
594        assert_eq!(bytes[2], 0x03);
595        // VALUE (false = 0x00)
596        assert_eq!(bytes[3], 0x00);
597    }
598
599    #[test]
600    fn test_encode_string() {
601        let entry = BinaryEntry {
602            fid: 1,
603            tag: TypeTag::String,
604            value: BinaryValue::String("hello".to_string()),
605        };
606
607        let bytes = entry.encode();
608
609        // FID (1 in little-endian)
610        assert_eq!(bytes[0], 0x01);
611        assert_eq!(bytes[1], 0x00);
612        // TAG (String = 0x04)
613        assert_eq!(bytes[2], 0x04);
614        // VALUE (length VarInt + UTF-8)
615        let length_varint = varint::encode(5);
616        assert_eq!(&bytes[3..3 + length_varint.len()], &length_varint[..]);
617        let offset = 3 + length_varint.len();
618        assert_eq!(&bytes[offset..], b"hello");
619    }
620
621    #[test]
622    fn test_encode_string_array() {
623        let entry = BinaryEntry {
624            fid: 23,
625            tag: TypeTag::StringArray,
626            value: BinaryValue::StringArray(vec!["admin".to_string(), "dev".to_string()]),
627        };
628
629        let bytes = entry.encode();
630
631        // FID (23 in little-endian)
632        assert_eq!(bytes[0], 0x17);
633        assert_eq!(bytes[1], 0x00);
634        // TAG (StringArray = 0x05)
635        assert_eq!(bytes[2], 0x05);
636
637        let mut offset = 3;
638        // Count VarInt (2)
639        let count_varint = varint::encode(2);
640        assert_eq!(
641            &bytes[offset..offset + count_varint.len()],
642            &count_varint[..]
643        );
644        offset += count_varint.len();
645
646        // First string "admin"
647        let len1_varint = varint::encode(5);
648        assert_eq!(&bytes[offset..offset + len1_varint.len()], &len1_varint[..]);
649        offset += len1_varint.len();
650        assert_eq!(&bytes[offset..offset + 5], b"admin");
651        offset += 5;
652
653        // Second string "dev"
654        let len2_varint = varint::encode(3);
655        assert_eq!(&bytes[offset..offset + len2_varint.len()], &len2_varint[..]);
656        offset += len2_varint.len();
657        assert_eq!(&bytes[offset..offset + 3], b"dev");
658    }
659
660    #[test]
661    fn test_decode_int() {
662        let entry = BinaryEntry {
663            fid: 12,
664            tag: TypeTag::Int,
665            value: BinaryValue::Int(14532),
666        };
667
668        let bytes = entry.encode();
669        let (decoded, consumed) = BinaryEntry::decode(&bytes).unwrap();
670
671        assert_eq!(decoded, entry);
672        assert_eq!(consumed, bytes.len());
673    }
674
675    #[test]
676    fn test_decode_float() {
677        let entry = BinaryEntry {
678            fid: 5,
679            tag: TypeTag::Float,
680            value: BinaryValue::Float(3.14),
681        };
682
683        let bytes = entry.encode();
684        let (decoded, consumed) = BinaryEntry::decode(&bytes).unwrap();
685
686        assert_eq!(decoded, entry);
687        assert_eq!(consumed, bytes.len());
688    }
689
690    #[test]
691    fn test_decode_bool() {
692        let entry = BinaryEntry {
693            fid: 7,
694            tag: TypeTag::Bool,
695            value: BinaryValue::Bool(true),
696        };
697
698        let bytes = entry.encode();
699        let (decoded, consumed) = BinaryEntry::decode(&bytes).unwrap();
700
701        assert_eq!(decoded, entry);
702        assert_eq!(consumed, bytes.len());
703    }
704
705    #[test]
706    fn test_decode_string() {
707        let entry = BinaryEntry {
708            fid: 1,
709            tag: TypeTag::String,
710            value: BinaryValue::String("hello".to_string()),
711        };
712
713        let bytes = entry.encode();
714        let (decoded, consumed) = BinaryEntry::decode(&bytes).unwrap();
715
716        assert_eq!(decoded, entry);
717        assert_eq!(consumed, bytes.len());
718    }
719
720    #[test]
721    fn test_decode_string_array() {
722        let entry = BinaryEntry {
723            fid: 23,
724            tag: TypeTag::StringArray,
725            value: BinaryValue::StringArray(vec!["admin".to_string(), "dev".to_string()]),
726        };
727
728        let bytes = entry.encode();
729        let (decoded, consumed) = BinaryEntry::decode(&bytes).unwrap();
730
731        assert_eq!(decoded, entry);
732        assert_eq!(consumed, bytes.len());
733    }
734
735    #[test]
736    fn test_decode_with_trailing_data() {
737        let entry = BinaryEntry {
738            fid: 1,
739            tag: TypeTag::Int,
740            value: BinaryValue::Int(42),
741        };
742
743        let mut bytes = entry.encode();
744        bytes.extend_from_slice(&[0xFF, 0xFF, 0xFF]); // Extra bytes
745
746        let (decoded, consumed) = BinaryEntry::decode(&bytes).unwrap();
747
748        assert_eq!(decoded, entry);
749        assert_eq!(consumed, bytes.len() - 3); // Should not consume trailing bytes
750    }
751
752    #[test]
753    fn test_decode_insufficient_data_fid() {
754        let bytes = vec![0x01]; // Only 1 byte, need 2 for FID
755        let result = BinaryEntry::decode(&bytes);
756        assert!(matches!(result, Err(BinaryError::UnexpectedEof { .. })));
757    }
758
759    #[test]
760    fn test_decode_insufficient_data_tag() {
761        let bytes = vec![0x01, 0x00]; // FID but no TAG
762        let result = BinaryEntry::decode(&bytes);
763        assert!(matches!(result, Err(BinaryError::UnexpectedEof { .. })));
764    }
765
766    #[test]
767    fn test_decode_invalid_type_tag() {
768        let bytes = vec![0x01, 0x00, 0xFF]; // Invalid TAG
769        let result = BinaryEntry::decode(&bytes);
770        assert!(matches!(result, Err(BinaryError::InvalidTypeTag { .. })));
771    }
772
773    #[test]
774    fn test_decode_invalid_bool_value() {
775        let bytes = vec![0x01, 0x00, 0x03, 0x02]; // Bool with value 0x02
776        let result = BinaryEntry::decode(&bytes);
777        assert!(matches!(result, Err(BinaryError::InvalidValue { .. })));
778    }
779
780    #[test]
781    fn test_decode_invalid_utf8() {
782        let mut bytes = vec![0x01, 0x00, 0x04]; // FID=1, TAG=String
783        bytes.extend_from_slice(&varint::encode(3)); // Length=3
784        bytes.extend_from_slice(&[0xFF, 0xFE, 0xFD]); // Invalid UTF-8
785
786        let result = BinaryEntry::decode(&bytes);
787        assert!(matches!(result, Err(BinaryError::InvalidUtf8 { .. })));
788    }
789
790    #[test]
791    fn test_roundtrip_all_types() {
792        let test_cases = vec![
793            BinaryEntry {
794                fid: 1,
795                tag: TypeTag::Int,
796                value: BinaryValue::Int(-42),
797            },
798            BinaryEntry {
799                fid: 2,
800                tag: TypeTag::Float,
801                value: BinaryValue::Float(3.14159),
802            },
803            BinaryEntry {
804                fid: 3,
805                tag: TypeTag::Bool,
806                value: BinaryValue::Bool(true),
807            },
808            BinaryEntry {
809                fid: 4,
810                tag: TypeTag::String,
811                value: BinaryValue::String("test".to_string()),
812            },
813            BinaryEntry {
814                fid: 5,
815                tag: TypeTag::StringArray,
816                value: BinaryValue::StringArray(vec!["a".to_string(), "b".to_string()]),
817            },
818        ];
819
820        for entry in test_cases {
821            let bytes = entry.encode();
822            let (decoded, _) = BinaryEntry::decode(&bytes).unwrap();
823            assert_eq!(decoded, entry);
824        }
825    }
826
827    #[test]
828    fn test_fid_boundary_values() {
829        // Test FID = 0
830        let entry0 = BinaryEntry {
831            fid: 0,
832            tag: TypeTag::Int,
833            value: BinaryValue::Int(1),
834        };
835        let bytes0 = entry0.encode();
836        let (decoded0, _) = BinaryEntry::decode(&bytes0).unwrap();
837        assert_eq!(decoded0.fid, 0);
838
839        // Test FID = 65535 (max u16)
840        let entry_max = BinaryEntry {
841            fid: 65535,
842            tag: TypeTag::Int,
843            value: BinaryValue::Int(1),
844        };
845        let bytes_max = entry_max.encode();
846        let (decoded_max, _) = BinaryEntry::decode(&bytes_max).unwrap();
847        assert_eq!(decoded_max.fid, 65535);
848    }
849
850    #[test]
851    fn test_empty_string() {
852        let entry = BinaryEntry {
853            fid: 1,
854            tag: TypeTag::String,
855            value: BinaryValue::String("".to_string()),
856        };
857
858        let bytes = entry.encode();
859        let (decoded, _) = BinaryEntry::decode(&bytes).unwrap();
860        assert_eq!(decoded, entry);
861    }
862
863    #[test]
864    fn test_empty_string_array() {
865        let entry = BinaryEntry {
866            fid: 1,
867            tag: TypeTag::StringArray,
868            value: BinaryValue::StringArray(vec![]),
869        };
870
871        let bytes = entry.encode();
872        let (decoded, _) = BinaryEntry::decode(&bytes).unwrap();
873        assert_eq!(decoded, entry);
874    }
875
876    #[test]
877    fn test_string_with_unicode() {
878        let entry = BinaryEntry {
879            fid: 1,
880            tag: TypeTag::String,
881            value: BinaryValue::String("Hello 🎯 World".to_string()),
882        };
883
884        let bytes = entry.encode();
885        let (decoded, _) = BinaryEntry::decode(&bytes).unwrap();
886        assert_eq!(decoded, entry);
887    }
888
889    #[test]
890    fn test_negative_int() {
891        let entry = BinaryEntry {
892            fid: 1,
893            tag: TypeTag::Int,
894            value: BinaryValue::Int(-9999),
895        };
896
897        let bytes = entry.encode();
898        let (decoded, _) = BinaryEntry::decode(&bytes).unwrap();
899        assert_eq!(decoded, entry);
900    }
901
902    #[test]
903    fn test_special_floats() {
904        // Test NaN
905        let entry_nan = BinaryEntry {
906            fid: 1,
907            tag: TypeTag::Float,
908            value: BinaryValue::Float(f64::NAN),
909        };
910        let bytes_nan = entry_nan.encode();
911        let (decoded_nan, _) = BinaryEntry::decode(&bytes_nan).unwrap();
912        match decoded_nan.value {
913            BinaryValue::Float(f) => assert!(f.is_nan()),
914            _ => panic!("Expected Float variant"),
915        }
916
917        // Test Infinity
918        let entry_inf = BinaryEntry {
919            fid: 2,
920            tag: TypeTag::Float,
921            value: BinaryValue::Float(f64::INFINITY),
922        };
923        let bytes_inf = entry_inf.encode();
924        let (decoded_inf, _) = BinaryEntry::decode(&bytes_inf).unwrap();
925        assert_eq!(decoded_inf, entry_inf);
926
927        // Test Negative Infinity
928        let entry_neg_inf = BinaryEntry {
929            fid: 3,
930            tag: TypeTag::Float,
931            value: BinaryValue::Float(f64::NEG_INFINITY),
932        };
933        let bytes_neg_inf = entry_neg_inf.encode();
934        let (decoded_neg_inf, _) = BinaryEntry::decode(&bytes_neg_inf).unwrap();
935        assert_eq!(decoded_neg_inf, entry_neg_inf);
936    }
937}