lnmp_codec/binary/
decoder.rs

1//! Binary decoder for converting LNMP binary format to text format.
2//!
3//! The BinaryDecoder converts LNMP records from binary format (v0.4) to text format (v0.3).
4//! It validates the binary structure and ensures canonical form compliance.
5
6use super::error::BinaryError;
7use super::frame::BinaryFrame;
8use crate::encoder::Encoder;
9use lnmp_core::LnmpRecord;
10
11/// Configuration for binary decoding
12#[derive(Debug, Clone)]
13pub struct DecoderConfig {
14    /// Whether to validate field ordering (canonical form)
15    pub validate_ordering: bool,
16    /// Whether to check for trailing data
17    pub strict_parsing: bool,
18
19    // v0.5 fields
20    /// Whether to allow streaming frame processing (v0.5)
21    pub allow_streaming: bool,
22    /// Whether to validate nesting depth and structure (v0.5)
23    pub validate_nesting: bool,
24    /// Whether to allow delta packet processing (v0.5)
25    pub allow_delta: bool,
26    /// Maximum nesting depth for nested structures (v0.5)
27    pub max_depth: usize,
28}
29
30impl Default for DecoderConfig {
31    fn default() -> Self {
32        Self {
33            validate_ordering: false,
34            strict_parsing: false,
35            // v0.5 defaults
36            allow_streaming: false,
37            validate_nesting: false,
38            allow_delta: false,
39            max_depth: 32,
40        }
41    }
42}
43
44impl DecoderConfig {
45    /// Creates a new decoder configuration with default settings
46    pub fn new() -> Self {
47        Self::default()
48    }
49
50    /// Sets whether to validate field ordering
51    pub fn with_validate_ordering(mut self, validate: bool) -> Self {
52        self.validate_ordering = validate;
53        self
54    }
55
56    /// Sets whether to check for trailing data
57    pub fn with_strict_parsing(mut self, strict: bool) -> Self {
58        self.strict_parsing = strict;
59        self
60    }
61
62    // v0.5 builder methods
63
64    /// Enables streaming frame processing (v0.5)
65    pub fn with_streaming(mut self, allow: bool) -> Self {
66        self.allow_streaming = allow;
67        self
68    }
69
70    /// Enables nesting validation (v0.5)
71    pub fn with_validate_nesting(mut self, validate: bool) -> Self {
72        self.validate_nesting = validate;
73        self
74    }
75
76    /// Enables delta packet processing (v0.5)
77    pub fn with_delta(mut self, allow: bool) -> Self {
78        self.allow_delta = allow;
79        self
80    }
81
82    /// Sets maximum nesting depth for nested structures (v0.5)
83    pub fn with_max_depth(mut self, depth: usize) -> Self {
84        self.max_depth = depth;
85        self
86    }
87}
88
89/// Binary decoder for LNMP v0.4
90///
91/// Converts LNMP records from binary format (v0.4) to text format (v0.3).
92/// The decoder validates the binary structure and can optionally enforce
93/// canonical form compliance.
94///
95/// # Examples
96///
97/// ```
98/// use lnmp_codec::binary::{BinaryDecoder, BinaryEncoder};
99/// use lnmp_core::{LnmpRecord, LnmpField, LnmpValue};
100///
101/// // Create and encode a record
102/// let mut record = LnmpRecord::new();
103/// record.add_field(LnmpField {
104///     fid: 7,
105///     value: LnmpValue::Bool(true),
106/// });
107///
108/// let encoder = BinaryEncoder::new();
109/// let binary = encoder.encode(&record).unwrap();
110///
111/// // Decode back to record
112/// let decoder = BinaryDecoder::new();
113/// let decoded_record = decoder.decode(&binary).unwrap();
114/// ```
115#[derive(Debug)]
116pub struct BinaryDecoder {
117    config: DecoderConfig,
118}
119
120impl BinaryDecoder {
121    /// Creates a new binary decoder with default configuration
122    ///
123    /// Default configuration:
124    /// - `validate_ordering`: false
125    /// - `strict_parsing`: false
126    pub fn new() -> Self {
127        Self {
128            config: DecoderConfig::default(),
129        }
130    }
131
132    /// Creates a binary decoder with custom configuration
133    pub fn with_config(config: DecoderConfig) -> Self {
134        Self { config }
135    }
136
137    /// Decodes binary format to LnmpRecord
138    ///
139    /// The decoder will:
140    /// 1. Validate the version byte (must be 0x04)
141    /// 2. Decode the BinaryFrame from bytes
142    /// 3. Validate field ordering (if validate_ordering is enabled)
143    /// 4. Check for trailing data (if strict_parsing is enabled)
144    /// 5. Convert the frame to an LnmpRecord
145    ///
146    /// # Arguments
147    ///
148    /// * `bytes` - Binary-encoded LNMP data
149    ///
150    /// # Returns
151    ///
152    /// An LnmpRecord representing the decoded data
153    ///
154    /// # Errors
155    ///
156    /// Returns `BinaryError` if:
157    /// - Version byte is not 0x04 (UnsupportedVersion)
158    /// - Binary data is malformed (UnexpectedEof, InvalidVarInt, etc.)
159    /// - Field ordering is invalid (CanonicalViolation, if validate_ordering is enabled)
160    /// - Trailing data is present (TrailingData, if strict_parsing is enabled)
161    pub fn decode(&self, bytes: &[u8]) -> Result<LnmpRecord, BinaryError> {
162        // Decode the binary frame
163        let frame = if self.config.validate_ordering {
164            BinaryFrame::decode(bytes)?
165        } else {
166            BinaryFrame::decode_allow_unsorted(bytes)?
167        };
168
169        // Convert frame to record
170        let record = frame.to_record();
171
172        // Validate field ordering if enabled
173        if self.config.validate_ordering {
174            self.validate_field_ordering(&record)?;
175        }
176
177        // Check for trailing data if strict parsing is enabled
178        if self.config.strict_parsing {
179            // Calculate how many bytes were consumed
180            let consumed = self.calculate_frame_size(bytes)?;
181            if consumed < bytes.len() {
182                return Err(BinaryError::TrailingData {
183                    bytes_remaining: bytes.len() - consumed,
184                });
185            }
186        }
187
188        Ok(record)
189    }
190
191    /// Decodes binary format to text format
192    ///
193    /// This method:
194    /// 1. Decodes the binary data to an LnmpRecord
195    /// 2. Encodes the record to canonical text format using the v0.3 encoder
196    ///
197    /// # Arguments
198    ///
199    /// * `bytes` - Binary-encoded LNMP data
200    ///
201    /// # Returns
202    ///
203    /// A string containing the canonical text representation
204    ///
205    /// # Errors
206    ///
207    /// Returns `BinaryError` if decoding fails
208    ///
209    /// # Examples
210    ///
211    /// ```
212    /// use lnmp_codec::binary::{BinaryDecoder, BinaryEncoder};
213    ///
214    /// let text = "F7=1;F12=14532";
215    /// let encoder = BinaryEncoder::new();
216    /// let binary = encoder.encode_text(text).unwrap();
217    ///
218    /// let decoder = BinaryDecoder::new();
219    /// let decoded_text = decoder.decode_to_text(&binary).unwrap();
220    /// ```
221    pub fn decode_to_text(&self, bytes: &[u8]) -> Result<String, BinaryError> {
222        // Decode to LnmpRecord
223        let record = self.decode(bytes)?;
224
225        // Encode to canonical text format using v0.3 encoder
226        let encoder = Encoder::new();
227        Ok(encoder.encode(&record))
228    }
229
230    /// Validates that fields are in ascending FID order (canonical form)
231    fn validate_field_ordering(&self, record: &LnmpRecord) -> Result<(), BinaryError> {
232        let fields = record.fields();
233
234        for i in 1..fields.len() {
235            if fields[i].fid < fields[i - 1].fid {
236                return Err(BinaryError::CanonicalViolation {
237                    reason: format!(
238                        "Fields not in ascending FID order: F{} appears after F{}",
239                        fields[i].fid,
240                        fields[i - 1].fid
241                    ),
242                });
243            }
244        }
245
246        Ok(())
247    }
248
249    /// Calculates the size of the frame in bytes
250    fn calculate_frame_size(&self, bytes: &[u8]) -> Result<usize, BinaryError> {
251        // Decode the frame to determine its size
252        // We need to re-decode to track the exact number of bytes consumed
253        let mut offset = 0;
254
255        // VERSION (1 byte)
256        if bytes.is_empty() {
257            return Err(BinaryError::UnexpectedEof {
258                expected: 1,
259                found: bytes.len(),
260            });
261        }
262        offset += 1;
263
264        // FLAGS (1 byte)
265        if bytes.len() < offset + 1 {
266            return Err(BinaryError::UnexpectedEof {
267                expected: offset + 1,
268                found: bytes.len(),
269            });
270        }
271        offset += 1;
272
273        // ENTRY_COUNT (VarInt)
274        let (entry_count, consumed) =
275            super::varint::decode(&bytes[offset..]).map_err(|_| BinaryError::InvalidVarInt {
276                reason: "Invalid entry count VarInt".to_string(),
277            })?;
278        offset += consumed;
279
280        if entry_count < 0 {
281            return Err(BinaryError::InvalidValue {
282                field_id: 0,
283                type_tag: 0,
284                reason: format!("Negative entry count: {}", entry_count),
285            });
286        }
287
288        let entry_count = entry_count as usize;
289
290        // Decode each entry to calculate size
291        for _ in 0..entry_count {
292            let (_, consumed) = super::entry::BinaryEntry::decode(&bytes[offset..])?;
293            offset += consumed;
294        }
295
296        Ok(offset)
297    }
298
299    /// Detects the binary format version from the first byte
300    ///
301    /// This method examines the version byte to determine which version of the
302    /// LNMP binary protocol is being used. This is useful for backward compatibility
303    /// and version-specific handling.
304    ///
305    /// # Arguments
306    ///
307    /// * `bytes` - Binary data to inspect
308    ///
309    /// # Returns
310    ///
311    /// The version byte (0x04 for v0.4, 0x05 for v0.5, etc.)
312    ///
313    /// # Errors
314    ///
315    /// Returns `BinaryError::UnexpectedEof` if the data is too short to contain a version byte
316    ///
317    /// # Examples
318    ///
319    /// ```
320    /// use lnmp_codec::binary::BinaryDecoder;
321    ///
322    /// let decoder = BinaryDecoder::new();
323    /// let v04_data = vec![0x04, 0x00, 0x00]; // v0.4 binary
324    /// let version = decoder.detect_version(&v04_data).unwrap();
325    /// assert_eq!(version, 0x04);
326    /// ```
327    pub fn detect_version(&self, bytes: &[u8]) -> Result<u8, BinaryError> {
328        if bytes.is_empty() {
329            return Err(BinaryError::UnexpectedEof {
330                expected: 1,
331                found: 0,
332            });
333        }
334        Ok(bytes[0])
335    }
336
337    /// Checks if the binary data contains nested structures (v0.5+ feature)
338    ///
339    /// This method scans the binary data to determine if it uses v0.5+ type tags
340    /// (NestedRecord 0x06, NestedArray 0x07, or reserved types 0x08-0x0F).
341    /// This is useful for determining compatibility with v0.4 decoders.
342    ///
343    /// # Arguments
344    ///
345    /// * `bytes` - Binary data to inspect
346    ///
347    /// # Returns
348    ///
349    /// `true` if the data contains v0.5+ type tags, `false` otherwise
350    ///
351    /// # Examples
352    ///
353    /// ```
354    /// use lnmp_codec::binary::BinaryDecoder;
355    ///
356    /// let decoder = BinaryDecoder::new();
357    /// let v04_data = vec![0x04, 0x00, 0x01, 0x07, 0x00, 0x03, 0x01]; // v0.4 with Bool
358    /// assert!(!decoder.supports_nested(&v04_data));
359    /// ```
360    pub fn supports_nested(&self, bytes: &[u8]) -> bool {
361        // Try to parse the frame and check for v0.5 type tags
362        // We need to scan through entries looking for type tags >= 0x06
363
364        if bytes.len() < 3 {
365            return false; // Too short to contain any entries
366        }
367
368        let mut offset = 0;
369
370        // Skip VERSION (1 byte)
371        offset += 1;
372
373        // Skip FLAGS (1 byte)
374        offset += 1;
375
376        // Read ENTRY_COUNT (VarInt)
377        let (entry_count, consumed) = match super::varint::decode(&bytes[offset..]) {
378            Ok(result) => result,
379            Err(_) => return false,
380        };
381        offset += consumed;
382
383        if entry_count < 0 {
384            return false;
385        }
386
387        let entry_count = entry_count as usize;
388
389        // Scan each entry for v0.5 type tags
390        for _ in 0..entry_count {
391            if offset >= bytes.len() {
392                return false;
393            }
394
395            // Try to decode the entry
396            match super::entry::BinaryEntry::decode(&bytes[offset..]) {
397                Ok((entry, consumed)) => {
398                    // Check if the type tag is v0.5+
399                    let type_tag = entry.type_tag();
400                    if type_tag.is_v0_5_type() {
401                        return true;
402                    }
403                    offset += consumed;
404                }
405                Err(_) => return false,
406            }
407        }
408
409        false
410    }
411}
412
413impl Default for BinaryDecoder {
414    fn default() -> Self {
415        Self::new()
416    }
417}
418
419#[cfg(test)]
420mod tests {
421    #![allow(clippy::approx_constant)]
422
423    use super::super::encoder::BinaryEncoder;
424    use super::*;
425    use lnmp_core::{LnmpField, LnmpValue};
426
427    #[test]
428    fn test_new_decoder() {
429        let decoder = BinaryDecoder::new();
430        assert!(!decoder.config.validate_ordering);
431        assert!(!decoder.config.strict_parsing);
432        // v0.5 defaults
433        assert!(!decoder.config.allow_streaming);
434        assert!(!decoder.config.validate_nesting);
435        assert!(!decoder.config.allow_delta);
436        assert_eq!(decoder.config.max_depth, 32);
437    }
438
439    #[test]
440    fn test_decoder_with_config() {
441        let config = DecoderConfig::new()
442            .with_validate_ordering(true)
443            .with_strict_parsing(true);
444
445        let decoder = BinaryDecoder::with_config(config);
446        assert!(decoder.config.validate_ordering);
447        assert!(decoder.config.strict_parsing);
448    }
449
450    #[test]
451    fn test_decode_empty_record() {
452        let encoder = BinaryEncoder::new();
453        let record = LnmpRecord::new();
454        let binary = encoder.encode(&record).unwrap();
455
456        let decoder = BinaryDecoder::new();
457        let decoded = decoder.decode(&binary).unwrap();
458
459        assert_eq!(decoded.fields().len(), 0);
460    }
461
462    #[test]
463    fn test_decode_single_field() {
464        let mut record = LnmpRecord::new();
465        record.add_field(LnmpField {
466            fid: 7,
467            value: LnmpValue::Bool(true),
468        });
469
470        let encoder = BinaryEncoder::new();
471        let binary = encoder.encode(&record).unwrap();
472
473        let decoder = BinaryDecoder::new();
474        let decoded = decoder.decode(&binary).unwrap();
475
476        assert_eq!(decoded.fields().len(), 1);
477        assert_eq!(decoded.get_field(7).unwrap().value, LnmpValue::Bool(true));
478    }
479
480    #[test]
481    fn test_decode_multiple_fields() {
482        let mut record = LnmpRecord::new();
483        record.add_field(LnmpField {
484            fid: 7,
485            value: LnmpValue::Bool(true),
486        });
487        record.add_field(LnmpField {
488            fid: 12,
489            value: LnmpValue::Int(14532),
490        });
491        record.add_field(LnmpField {
492            fid: 23,
493            value: LnmpValue::StringArray(vec!["admin".to_string(), "dev".to_string()]),
494        });
495
496        let encoder = BinaryEncoder::new();
497        let binary = encoder.encode(&record).unwrap();
498
499        let decoder = BinaryDecoder::new();
500        let decoded = decoder.decode(&binary).unwrap();
501
502        assert_eq!(decoded.fields().len(), 3);
503        assert_eq!(decoded.get_field(7).unwrap().value, LnmpValue::Bool(true));
504        assert_eq!(decoded.get_field(12).unwrap().value, LnmpValue::Int(14532));
505        assert_eq!(
506            decoded.get_field(23).unwrap().value,
507            LnmpValue::StringArray(vec!["admin".to_string(), "dev".to_string()])
508        );
509    }
510
511    #[test]
512    fn test_decode_all_types() {
513        let mut record = LnmpRecord::new();
514        record.add_field(LnmpField {
515            fid: 1,
516            value: LnmpValue::Int(-42),
517        });
518        record.add_field(LnmpField {
519            fid: 2,
520            value: LnmpValue::Float(3.14159),
521        });
522        record.add_field(LnmpField {
523            fid: 3,
524            value: LnmpValue::Bool(false),
525        });
526        record.add_field(LnmpField {
527            fid: 4,
528            value: LnmpValue::String("hello".to_string()),
529        });
530        record.add_field(LnmpField {
531            fid: 5,
532            value: LnmpValue::StringArray(vec!["a".to_string(), "b".to_string()]),
533        });
534
535        let encoder = BinaryEncoder::new();
536        let binary = encoder.encode(&record).unwrap();
537
538        let decoder = BinaryDecoder::new();
539        let decoded = decoder.decode(&binary).unwrap();
540
541        assert_eq!(decoded.get_field(1).unwrap().value, LnmpValue::Int(-42));
542        assert_eq!(
543            decoded.get_field(2).unwrap().value,
544            LnmpValue::Float(3.14159)
545        );
546        assert_eq!(decoded.get_field(3).unwrap().value, LnmpValue::Bool(false));
547        assert_eq!(
548            decoded.get_field(4).unwrap().value,
549            LnmpValue::String("hello".to_string())
550        );
551        assert_eq!(
552            decoded.get_field(5).unwrap().value,
553            LnmpValue::StringArray(vec!["a".to_string(), "b".to_string()])
554        );
555    }
556
557    #[test]
558    fn test_decode_unsupported_version() {
559        let bytes = vec![0x99, 0x00, 0x00]; // Invalid version
560        let decoder = BinaryDecoder::new();
561        let result = decoder.decode(&bytes);
562
563        assert!(matches!(
564            result,
565            Err(BinaryError::UnsupportedVersion { found: 0x99, .. })
566        ));
567    }
568
569    #[test]
570    fn test_decode_version_0x04_accepted() {
571        let bytes = vec![0x04, 0x00, 0x00]; // Valid version, empty record
572        let decoder = BinaryDecoder::new();
573        let result = decoder.decode(&bytes);
574
575        assert!(result.is_ok());
576    }
577
578    #[test]
579    fn test_decode_to_text_simple() {
580        let mut record = LnmpRecord::new();
581        record.add_field(LnmpField {
582            fid: 7,
583            value: LnmpValue::Bool(true),
584        });
585
586        let encoder = BinaryEncoder::new();
587        let binary = encoder.encode(&record).unwrap();
588
589        let decoder = BinaryDecoder::new();
590        let text = decoder.decode_to_text(&binary).unwrap();
591
592        assert_eq!(text, "F7=1");
593    }
594
595    #[test]
596    fn test_decode_to_text_multiple_fields() {
597        let mut record = LnmpRecord::new();
598        record.add_field(LnmpField {
599            fid: 7,
600            value: LnmpValue::Bool(true),
601        });
602        record.add_field(LnmpField {
603            fid: 12,
604            value: LnmpValue::Int(14532),
605        });
606        record.add_field(LnmpField {
607            fid: 23,
608            value: LnmpValue::StringArray(vec!["admin".to_string(), "dev".to_string()]),
609        });
610
611        let encoder = BinaryEncoder::new();
612        let binary = encoder.encode(&record).unwrap();
613
614        let decoder = BinaryDecoder::new();
615        let text = decoder.decode_to_text(&binary).unwrap();
616
617        // Fields should be in canonical order (sorted by FID)
618        assert_eq!(text, "F7=1\nF12=14532\nF23=[admin,dev]");
619    }
620
621    #[test]
622    fn test_decode_to_text_canonical_format() {
623        // Test that output is in canonical format (newline-separated, sorted)
624        let mut record = LnmpRecord::new();
625        record.add_field(LnmpField {
626            fid: 23,
627            value: LnmpValue::StringArray(vec!["admin".to_string()]),
628        });
629        record.add_field(LnmpField {
630            fid: 7,
631            value: LnmpValue::Bool(true),
632        });
633        record.add_field(LnmpField {
634            fid: 12,
635            value: LnmpValue::Int(14532),
636        });
637
638        let encoder = BinaryEncoder::new();
639        let binary = encoder.encode(&record).unwrap();
640
641        let decoder = BinaryDecoder::new();
642        let text = decoder.decode_to_text(&binary).unwrap();
643
644        // Should be sorted by FID
645        assert_eq!(text, "F7=1\nF12=14532\nF23=[admin]");
646    }
647
648    #[test]
649    fn test_validate_ordering_accepts_sorted() {
650        let mut record = LnmpRecord::new();
651        record.add_field(LnmpField {
652            fid: 7,
653            value: LnmpValue::Bool(true),
654        });
655        record.add_field(LnmpField {
656            fid: 12,
657            value: LnmpValue::Int(14532),
658        });
659
660        let encoder = BinaryEncoder::new();
661        let binary = encoder.encode(&record).unwrap();
662
663        let config = DecoderConfig::new().with_validate_ordering(true);
664        let decoder = BinaryDecoder::with_config(config);
665        let result = decoder.decode(&binary);
666
667        assert!(result.is_ok());
668    }
669
670    #[test]
671    fn test_trailing_data_detection() {
672        let mut record = LnmpRecord::new();
673        record.add_field(LnmpField {
674            fid: 7,
675            value: LnmpValue::Bool(true),
676        });
677
678        let encoder = BinaryEncoder::new();
679        let mut binary = encoder.encode(&record).unwrap();
680
681        // Add trailing data
682        binary.extend_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF]);
683
684        let config = DecoderConfig::new().with_strict_parsing(true);
685        let decoder = BinaryDecoder::with_config(config);
686        let result = decoder.decode(&binary);
687
688        assert!(matches!(
689            result,
690            Err(BinaryError::TrailingData { bytes_remaining: 4 })
691        ));
692    }
693
694    #[test]
695    fn test_trailing_data_ignored_without_strict() {
696        let mut record = LnmpRecord::new();
697        record.add_field(LnmpField {
698            fid: 7,
699            value: LnmpValue::Bool(true),
700        });
701
702        let encoder = BinaryEncoder::new();
703        let mut binary = encoder.encode(&record).unwrap();
704
705        // Add trailing data
706        binary.extend_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF]);
707
708        let decoder = BinaryDecoder::new(); // strict_parsing = false
709        let result = decoder.decode(&binary);
710
711        // Should succeed without strict parsing
712        assert!(result.is_ok());
713    }
714
715    #[test]
716    fn test_roundtrip_binary_to_text_to_binary() {
717        let original_text = "F7=1\nF12=14532\nF23=[admin,dev]";
718
719        // Text -> Binary
720        let encoder = BinaryEncoder::new();
721        let binary1 = encoder.encode_text(original_text).unwrap();
722
723        // Binary -> Text
724        let decoder = BinaryDecoder::new();
725        let text = decoder.decode_to_text(&binary1).unwrap();
726
727        // Text -> Binary again
728        let binary2 = encoder.encode_text(&text).unwrap();
729
730        // Binary representations should be identical
731        assert_eq!(binary1, binary2);
732    }
733
734    #[test]
735    fn test_roundtrip_text_to_binary_to_text() {
736        let original_text = "F7=1\nF12=14532\nF23=[admin,dev]";
737
738        // Text -> Binary
739        let encoder = BinaryEncoder::new();
740        let binary = encoder.encode_text(original_text).unwrap();
741
742        // Binary -> Text
743        let decoder = BinaryDecoder::new();
744        let decoded_text = decoder.decode_to_text(&binary).unwrap();
745
746        // Should produce identical canonical text
747        assert_eq!(decoded_text, original_text);
748    }
749
750    #[test]
751    fn test_roundtrip_unsorted_text() {
752        let unsorted_text = "F23=[admin]\nF7=1\nF12=14532";
753
754        // Text -> Binary
755        let encoder = BinaryEncoder::new();
756        let binary = encoder.encode_text(unsorted_text).unwrap();
757
758        // Binary -> Text
759        let decoder = BinaryDecoder::new();
760        let decoded_text = decoder.decode_to_text(&binary).unwrap();
761
762        // Should be sorted in canonical form
763        assert_eq!(decoded_text, "F7=1\nF12=14532\nF23=[admin]");
764    }
765
766    #[test]
767    fn test_roundtrip_all_types() {
768        let test_cases = vec![
769            ("F1=-42", "F1=-42"),
770            ("F2=3.14159", "F2=3.14159"),
771            ("F3=0", "F3=0"),
772            ("F4=1", "F4=1"),
773            ("F5=\"hello\\nworld\"", "F5=\"hello\\nworld\""),
774            ("F6=[\"a\",\"b\",\"c\"]", "F6=[a,b,c]"), // Simple strings don't need quotes
775            ("F7=[]", ""), // Empty arrays are omitted during canonicalization (Requirement 9.3)
776        ];
777
778        for (input, expected) in test_cases {
779            let encoder = BinaryEncoder::new();
780            let binary = encoder.encode_text(input).unwrap();
781
782            let decoder = BinaryDecoder::new();
783            let decoded = decoder.decode_to_text(&binary).unwrap();
784
785            assert_eq!(decoded, expected, "Failed for: {}", input);
786        }
787    }
788
789    #[test]
790    fn test_roundtrip_stability() {
791        let text = "F7=1\nF12=14532";
792
793        let encoder = BinaryEncoder::new();
794        let decoder = BinaryDecoder::new();
795
796        // Multiple round-trips should be stable
797        let mut current = text.to_string();
798        for _ in 0..5 {
799            let binary = encoder.encode_text(&current).unwrap();
800            current = decoder.decode_to_text(&binary).unwrap();
801        }
802
803        assert_eq!(current, text);
804    }
805
806    #[test]
807    fn test_decode_insufficient_data() {
808        let bytes = vec![0x04]; // Only version, no flags
809        let decoder = BinaryDecoder::new();
810        let result = decoder.decode(&bytes);
811
812        assert!(matches!(result, Err(BinaryError::UnexpectedEof { .. })));
813    }
814
815    #[test]
816    fn test_decode_invalid_entry_count() {
817        let bytes = vec![0x04, 0x00, 0x80]; // Incomplete VarInt
818        let decoder = BinaryDecoder::new();
819        let result = decoder.decode(&bytes);
820
821        assert!(matches!(result, Err(BinaryError::InvalidVarInt { .. })));
822    }
823
824    #[test]
825    fn test_decode_empty_string() {
826        let mut record = LnmpRecord::new();
827        record.add_field(LnmpField {
828            fid: 1,
829            value: LnmpValue::String("".to_string()),
830        });
831
832        let encoder = BinaryEncoder::new();
833        let binary = encoder.encode(&record).unwrap();
834
835        let decoder = BinaryDecoder::new();
836        let decoded = decoder.decode(&binary).unwrap();
837
838        assert_eq!(
839            decoded.get_field(1).unwrap().value,
840            LnmpValue::String("".to_string())
841        );
842    }
843
844    #[test]
845    fn test_decode_empty_array() {
846        let mut record = LnmpRecord::new();
847        record.add_field(LnmpField {
848            fid: 1,
849            value: LnmpValue::StringArray(vec![]),
850        });
851
852        let encoder = BinaryEncoder::new();
853        let binary = encoder.encode(&record).unwrap();
854
855        let decoder = BinaryDecoder::new();
856        let decoded = decoder.decode(&binary).unwrap();
857
858        assert_eq!(
859            decoded.get_field(1).unwrap().value,
860            LnmpValue::StringArray(vec![])
861        );
862    }
863
864    #[test]
865    fn test_decode_special_characters() {
866        let mut record = LnmpRecord::new();
867        record.add_field(LnmpField {
868            fid: 1,
869            value: LnmpValue::String("hello\nworld".to_string()),
870        });
871        record.add_field(LnmpField {
872            fid: 2,
873            value: LnmpValue::String("path\\to\\file".to_string()),
874        });
875        record.add_field(LnmpField {
876            fid: 3,
877            value: LnmpValue::String("say \"hello\"".to_string()),
878        });
879
880        let encoder = BinaryEncoder::new();
881        let binary = encoder.encode(&record).unwrap();
882
883        let decoder = BinaryDecoder::new();
884        let decoded = decoder.decode(&binary).unwrap();
885
886        assert_eq!(
887            decoded.get_field(1).unwrap().value,
888            LnmpValue::String("hello\nworld".to_string())
889        );
890        assert_eq!(
891            decoded.get_field(2).unwrap().value,
892            LnmpValue::String("path\\to\\file".to_string())
893        );
894        assert_eq!(
895            decoded.get_field(3).unwrap().value,
896            LnmpValue::String("say \"hello\"".to_string())
897        );
898    }
899
900    #[test]
901    fn test_decode_unicode() {
902        let mut record = LnmpRecord::new();
903        record.add_field(LnmpField {
904            fid: 1,
905            value: LnmpValue::String("emoji: 🎯".to_string()),
906        });
907
908        let encoder = BinaryEncoder::new();
909        let binary = encoder.encode(&record).unwrap();
910
911        let decoder = BinaryDecoder::new();
912        let decoded = decoder.decode(&binary).unwrap();
913
914        assert_eq!(
915            decoded.get_field(1).unwrap().value,
916            LnmpValue::String("emoji: 🎯".to_string())
917        );
918    }
919
920    #[test]
921    fn test_default_decoder() {
922        let decoder = BinaryDecoder::default();
923        assert!(!decoder.config.validate_ordering);
924        assert!(!decoder.config.strict_parsing);
925    }
926
927    #[test]
928    fn test_decoder_config_builder() {
929        let config = DecoderConfig::new()
930            .with_validate_ordering(true)
931            .with_strict_parsing(true);
932
933        assert!(config.validate_ordering);
934        assert!(config.strict_parsing);
935    }
936
937    #[test]
938    fn test_decoder_config_v05_fields() {
939        let config = DecoderConfig::new()
940            .with_streaming(true)
941            .with_validate_nesting(true)
942            .with_delta(true)
943            .with_max_depth(64);
944
945        assert!(config.allow_streaming);
946        assert!(config.validate_nesting);
947        assert!(config.allow_delta);
948        assert_eq!(config.max_depth, 64);
949    }
950
951    #[test]
952    fn test_decoder_config_v05_defaults() {
953        let config = DecoderConfig::default();
954
955        assert!(!config.allow_streaming);
956        assert!(!config.validate_nesting);
957        assert!(!config.allow_delta);
958        assert_eq!(config.max_depth, 32);
959    }
960
961    #[test]
962    fn test_decoder_config_backward_compatibility() {
963        // v0.4 configurations should work without any changes
964        let v04_config = DecoderConfig::new()
965            .with_validate_ordering(true)
966            .with_strict_parsing(true);
967
968        // v0.4 fields should work as before
969        assert!(v04_config.validate_ordering);
970        assert!(v04_config.strict_parsing);
971
972        // v0.5 fields should have safe defaults (disabled)
973        assert!(!v04_config.allow_streaming);
974        assert!(!v04_config.validate_nesting);
975        assert!(!v04_config.allow_delta);
976    }
977
978    #[test]
979    fn test_decoder_config_mixed_v04_v05() {
980        // Test that v0.4 and v0.5 configurations can be mixed
981        let config = DecoderConfig::new()
982            .with_validate_ordering(true) // v0.4
983            .with_streaming(true) // v0.5
984            .with_strict_parsing(true) // v0.4
985            .with_delta(true); // v0.5
986
987        assert!(config.validate_ordering);
988        assert!(config.strict_parsing);
989        assert!(config.allow_streaming);
990        assert!(config.allow_delta);
991    }
992
993    #[test]
994    fn test_decoder_v04_mode_decoding() {
995        // Test that decoder with v0.5 disabled behaves like v0.4
996        let config = DecoderConfig::new()
997            .with_streaming(false)
998            .with_validate_nesting(false)
999            .with_delta(false);
1000
1001        let decoder = BinaryDecoder::with_config(config);
1002
1003        // Should decode a v0.4 record just like before
1004        let mut record = LnmpRecord::new();
1005        record.add_field(LnmpField {
1006            fid: 7,
1007            value: LnmpValue::Bool(true),
1008        });
1009
1010        let encoder = BinaryEncoder::new();
1011        let binary = encoder.encode(&record).unwrap();
1012
1013        let decoded = decoder.decode(&binary).unwrap();
1014
1015        // Should decode correctly
1016        assert_eq!(decoded.fields().len(), 1);
1017        assert_eq!(decoded.get_field(7).unwrap().value, LnmpValue::Bool(true));
1018    }
1019
1020    #[test]
1021    fn test_decoder_v04_compatibility_with_existing_binary() {
1022        // Test that v0.5 decoder can decode v0.4 binary format
1023        let v05_decoder = BinaryDecoder::new(); // All v0.5 features disabled by default
1024
1025        // Create a v0.4 binary (no nested structures)
1026        let mut record = LnmpRecord::new();
1027        record.add_field(LnmpField {
1028            fid: 7,
1029            value: LnmpValue::Bool(true),
1030        });
1031        record.add_field(LnmpField {
1032            fid: 12,
1033            value: LnmpValue::Int(14532),
1034        });
1035
1036        let encoder = BinaryEncoder::new();
1037        let v04_binary = encoder.encode(&record).unwrap();
1038
1039        // v0.5 decoder should decode v0.4 binary without issues
1040        let decoded = v05_decoder.decode(&v04_binary).unwrap();
1041
1042        assert_eq!(decoded.fields().len(), 2);
1043        assert_eq!(decoded.get_field(7).unwrap().value, LnmpValue::Bool(true));
1044        assert_eq!(decoded.get_field(12).unwrap().value, LnmpValue::Int(14532));
1045    }
1046
1047    #[test]
1048    fn test_decode_large_fid() {
1049        let mut record = LnmpRecord::new();
1050        record.add_field(LnmpField {
1051            fid: 65535,
1052            value: LnmpValue::Int(42),
1053        });
1054
1055        let encoder = BinaryEncoder::new();
1056        let binary = encoder.encode(&record).unwrap();
1057
1058        let decoder = BinaryDecoder::new();
1059        let decoded = decoder.decode(&binary).unwrap();
1060
1061        assert_eq!(decoded.get_field(65535).unwrap().value, LnmpValue::Int(42));
1062    }
1063
1064    #[test]
1065    fn test_decode_large_integer() {
1066        let mut record = LnmpRecord::new();
1067        record.add_field(LnmpField {
1068            fid: 1,
1069            value: LnmpValue::Int(i64::MAX),
1070        });
1071
1072        let encoder = BinaryEncoder::new();
1073        let binary = encoder.encode(&record).unwrap();
1074
1075        let decoder = BinaryDecoder::new();
1076        let decoded = decoder.decode(&binary).unwrap();
1077
1078        assert_eq!(
1079            decoded.get_field(1).unwrap().value,
1080            LnmpValue::Int(i64::MAX)
1081        );
1082    }
1083
1084    #[test]
1085    fn test_decode_negative_integer() {
1086        let mut record = LnmpRecord::new();
1087        record.add_field(LnmpField {
1088            fid: 1,
1089            value: LnmpValue::Int(i64::MIN),
1090        });
1091
1092        let encoder = BinaryEncoder::new();
1093        let binary = encoder.encode(&record).unwrap();
1094
1095        let decoder = BinaryDecoder::new();
1096        let decoded = decoder.decode(&binary).unwrap();
1097
1098        assert_eq!(
1099            decoded.get_field(1).unwrap().value,
1100            LnmpValue::Int(i64::MIN)
1101        );
1102    }
1103}