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