lnmp_codec/binary/
nested_decoder.rs

1#![allow(clippy::approx_constant)]
2
3//! Nested structure decoding for LNMP v0.5 binary format.
4//!
5//! This module provides decoding support for nested records and arrays in the binary format.
6//! It implements recursive decoding with depth validation to prevent stack overflow attacks.
7
8use super::error::BinaryError;
9use super::types::TypeTag;
10use super::varint;
11use lnmp_core::{LnmpField, LnmpRecord, LnmpValue};
12
13/// Configuration for nested structure decoding (v0.5)
14#[derive(Debug, Clone)]
15pub struct NestedDecoderConfig {
16    /// Whether to allow nested structures (default: true)
17    pub allow_nested: bool,
18    /// Whether to validate nesting rules (default: false)
19    pub validate_nesting: bool,
20    /// Maximum nesting depth allowed (default: 32)
21    pub max_depth: usize,
22}
23
24impl Default for NestedDecoderConfig {
25    fn default() -> Self {
26        Self {
27            allow_nested: true,
28            validate_nesting: false,
29            max_depth: 32,
30        }
31    }
32}
33
34impl NestedDecoderConfig {
35    /// Creates a new nested decoder configuration with default settings
36    pub fn new() -> Self {
37        Self::default()
38    }
39
40    /// Sets whether to allow nested structures
41    pub fn with_allow_nested(mut self, allow: bool) -> Self {
42        self.allow_nested = allow;
43        self
44    }
45
46    /// Sets whether to validate nesting rules
47    pub fn with_validate_nesting(mut self, validate: bool) -> Self {
48        self.validate_nesting = validate;
49        self
50    }
51
52    /// Sets the maximum nesting depth
53    pub fn with_max_depth(mut self, max_depth: usize) -> Self {
54        self.max_depth = max_depth;
55        self
56    }
57}
58
59/// Binary nested structure decoder for LNMP v0.5
60///
61/// Decodes nested records and arrays with depth validation.
62#[derive(Debug)]
63pub struct BinaryNestedDecoder {
64    config: NestedDecoderConfig,
65}
66
67impl BinaryNestedDecoder {
68    /// Creates a new nested decoder with default configuration
69    pub fn new() -> Self {
70        Self {
71            config: NestedDecoderConfig::default(),
72        }
73    }
74
75    /// Creates a nested decoder with custom configuration
76    pub fn with_config(config: NestedDecoderConfig) -> Self {
77        Self { config }
78    }
79
80    /// Decodes a nested record from binary format
81    ///
82    /// Binary layout:
83    /// ```text
84    /// ┌──────────┬──────────────┬─────────────────────────────────┐
85    /// │   TAG    │ FIELD_COUNT  │      FIELD ENTRIES...           │
86    /// │ (1 byte) │  (VarInt)    │  { FID | VALUE }*               │
87    /// └──────────┴──────────────┴─────────────────────────────────┘
88    /// ```
89    ///
90    /// # Arguments
91    ///
92    /// * `bytes` - The binary data to decode
93    ///
94    /// # Returns
95    ///
96    /// A tuple of (decoded_record, bytes_consumed)
97    ///
98    /// # Errors
99    ///
100    /// Returns `BinaryError` if:
101    /// - Nesting depth exceeds configured maximum
102    /// - Binary data is malformed
103    /// - TAG byte is not 0x06
104    pub fn decode_nested_record(&self, bytes: &[u8]) -> Result<(LnmpRecord, usize), BinaryError> {
105        self.decode_nested_record_with_depth(bytes, 0)
106    }
107
108    /// Decodes a nested record with depth tracking
109    fn decode_nested_record_with_depth(
110        &self,
111        bytes: &[u8],
112        current_depth: usize,
113    ) -> Result<(LnmpRecord, usize), BinaryError> {
114        // Validate depth
115        if current_depth >= self.config.max_depth {
116            return Err(BinaryError::NestingDepthExceeded {
117                depth: current_depth,
118                max: self.config.max_depth,
119            });
120        }
121
122        let mut offset = 0;
123
124        // Read and validate TAG byte (0x06)
125        if bytes.is_empty() {
126            return Err(BinaryError::UnexpectedEof {
127                expected: 1,
128                found: bytes.len(),
129            });
130        }
131
132        let tag = bytes[offset];
133        offset += 1;
134
135        if tag != TypeTag::NestedRecord.to_u8() {
136            return Err(BinaryError::InvalidTypeTag { tag });
137        }
138
139        // Decode FIELD_COUNT from VarInt
140        let (field_count, consumed) = varint::decode(&bytes[offset..])?;
141        offset += consumed;
142
143        if field_count < 0 {
144            return Err(BinaryError::InvalidNestedStructure {
145                reason: format!("Negative field count: {}", field_count),
146            });
147        }
148
149        let field_count = field_count as usize;
150
151        // Recursively decode each field entry
152        let mut record = LnmpRecord::new();
153
154        for _ in 0..field_count {
155            // Decode FID as VarInt
156            let (fid_i64, consumed) = varint::decode(&bytes[offset..])?;
157            offset += consumed;
158
159            if fid_i64 < 0 || fid_i64 > u16::MAX as i64 {
160                return Err(BinaryError::InvalidFID {
161                    fid: fid_i64 as u16,
162                    reason: format!("FID out of range: {}", fid_i64),
163                });
164            }
165
166            let fid = fid_i64 as u16;
167
168            // Decode VALUE recursively (depth stays the same, will increment inside nested record/array)
169            let (value, consumed) = self.decode_value_recursive(&bytes[offset..], current_depth)?;
170            offset += consumed;
171
172            // Add field to record
173            record.add_field(LnmpField { fid, value });
174        }
175
176        Ok((record, offset))
177    }
178
179    /// Decodes a value recursively, handling nested structures
180    fn decode_value_recursive(
181        &self,
182        bytes: &[u8],
183        current_depth: usize,
184    ) -> Result<(LnmpValue, usize), BinaryError> {
185        // Validate depth for nested structures
186        if current_depth >= self.config.max_depth {
187            return Err(BinaryError::NestingDepthExceeded {
188                depth: current_depth,
189                max: self.config.max_depth,
190            });
191        }
192
193        let mut offset = 0;
194
195        // Read type tag
196        if bytes.is_empty() {
197            return Err(BinaryError::UnexpectedEof {
198                expected: 1,
199                found: bytes.len(),
200            });
201        }
202
203        let tag_byte = bytes[offset];
204        offset += 1;
205
206        let type_tag = TypeTag::from_u8(tag_byte)?;
207
208        match type_tag {
209            TypeTag::Int => {
210                let (value, consumed) = varint::decode(&bytes[offset..])?;
211                offset += consumed;
212                Ok((LnmpValue::Int(value), offset))
213            }
214            TypeTag::Float => {
215                if bytes.len() < offset + 8 {
216                    return Err(BinaryError::UnexpectedEof {
217                        expected: offset + 8,
218                        found: bytes.len(),
219                    });
220                }
221                let float_bytes: [u8; 8] = bytes[offset..offset + 8].try_into().unwrap();
222                let value = f64::from_le_bytes(float_bytes);
223                offset += 8;
224                Ok((LnmpValue::Float(value), offset))
225            }
226            TypeTag::Bool => {
227                if bytes.len() < offset + 1 {
228                    return Err(BinaryError::UnexpectedEof {
229                        expected: offset + 1,
230                        found: bytes.len(),
231                    });
232                }
233                let value = bytes[offset] != 0x00;
234                offset += 1;
235                Ok((LnmpValue::Bool(value), offset))
236            }
237            TypeTag::String => {
238                let (length, consumed) = varint::decode(&bytes[offset..])?;
239                offset += consumed;
240
241                if length < 0 {
242                    return Err(BinaryError::InvalidValue {
243                        field_id: 0,
244                        type_tag: tag_byte,
245                        reason: format!("Negative string length: {}", length),
246                    });
247                }
248
249                let length = length as usize;
250
251                if bytes.len() < offset + length {
252                    return Err(BinaryError::UnexpectedEof {
253                        expected: offset + length,
254                        found: bytes.len(),
255                    });
256                }
257
258                let string_bytes = &bytes[offset..offset + length];
259                let value = String::from_utf8(string_bytes.to_vec())
260                    .map_err(|_| BinaryError::InvalidUtf8 { field_id: 0 })?;
261                offset += length;
262                Ok((LnmpValue::String(value), offset))
263            }
264            TypeTag::StringArray => {
265                let (count, consumed) = varint::decode(&bytes[offset..])?;
266                offset += consumed;
267
268                if count < 0 {
269                    return Err(BinaryError::InvalidValue {
270                        field_id: 0,
271                        type_tag: tag_byte,
272                        reason: format!("Negative array count: {}", count),
273                    });
274                }
275
276                let count = count as usize;
277                let mut array = Vec::with_capacity(count);
278
279                for _ in 0..count {
280                    let (length, consumed) = varint::decode(&bytes[offset..])?;
281                    offset += consumed;
282
283                    if length < 0 {
284                        return Err(BinaryError::InvalidValue {
285                            field_id: 0,
286                            type_tag: tag_byte,
287                            reason: format!("Negative string length in array: {}", length),
288                        });
289                    }
290
291                    let length = length as usize;
292
293                    if bytes.len() < offset + length {
294                        return Err(BinaryError::UnexpectedEof {
295                            expected: offset + length,
296                            found: bytes.len(),
297                        });
298                    }
299
300                    let string_bytes = &bytes[offset..offset + length];
301                    let string = String::from_utf8(string_bytes.to_vec())
302                        .map_err(|_| BinaryError::InvalidUtf8 { field_id: 0 })?;
303                    offset += length;
304                    array.push(string);
305                }
306
307                Ok((LnmpValue::StringArray(array), offset))
308            }
309            TypeTag::NestedRecord => {
310                // Check if nested structures are allowed
311                if !self.config.allow_nested {
312                    return Err(BinaryError::NestedStructureNotSupported);
313                }
314
315                // Recursively decode nested record (offset already advanced past tag)
316                // Increment depth when entering a nested record
317                let (record, consumed) =
318                    self.decode_nested_record_with_depth(&bytes[offset - 1..], current_depth + 1)?;
319                // Subtract 1 because we already consumed the tag byte
320                offset += consumed - 1;
321                Ok((LnmpValue::NestedRecord(Box::new(record)), offset))
322            }
323            TypeTag::NestedArray => {
324                // Check if nested structures are allowed
325                if !self.config.allow_nested {
326                    return Err(BinaryError::NestedStructureNotSupported);
327                }
328
329                // Recursively decode nested array (offset already advanced past tag)
330                // Increment depth when entering a nested array
331                let (records, consumed) =
332                    self.decode_nested_array_with_depth(&bytes[offset - 1..], current_depth + 1)?;
333                // Subtract 1 because we already consumed the tag byte
334                offset += consumed - 1;
335                Ok((LnmpValue::NestedArray(records), offset))
336            }
337            _ => Err(BinaryError::InvalidTypeTag { tag: tag_byte }),
338        }
339    }
340
341    /// Decodes a nested array from binary format
342    ///
343    /// Binary layout:
344    /// ```text
345    /// ┌──────────┬──────────────┬─────────────────────────────────┐
346    /// │   TAG    │ ELEM_COUNT   │      RECORD ENTRIES...          │
347    /// │ (1 byte) │  (VarInt)    │  [ RECORD(...) ]*               │
348    /// └──────────┴──────────────┴─────────────────────────────────┘
349    /// ```
350    ///
351    /// # Arguments
352    ///
353    /// * `bytes` - The binary data to decode
354    ///
355    /// # Returns
356    ///
357    /// A tuple of (decoded_records, bytes_consumed)
358    ///
359    /// # Errors
360    ///
361    /// Returns `BinaryError` if:
362    /// - Nesting depth exceeds configured maximum
363    /// - Binary data is malformed
364    /// - TAG byte is not 0x07
365    pub fn decode_nested_array(
366        &self,
367        bytes: &[u8],
368    ) -> Result<(Vec<LnmpRecord>, usize), BinaryError> {
369        self.decode_nested_array_with_depth(bytes, 0)
370    }
371
372    /// Decodes a nested array with depth tracking
373    fn decode_nested_array_with_depth(
374        &self,
375        bytes: &[u8],
376        current_depth: usize,
377    ) -> Result<(Vec<LnmpRecord>, usize), BinaryError> {
378        // Validate depth
379        if current_depth >= self.config.max_depth {
380            return Err(BinaryError::NestingDepthExceeded {
381                depth: current_depth,
382                max: self.config.max_depth,
383            });
384        }
385
386        let mut offset = 0;
387
388        // Read and validate TAG byte (0x07)
389        if bytes.is_empty() {
390            return Err(BinaryError::UnexpectedEof {
391                expected: 1,
392                found: bytes.len(),
393            });
394        }
395
396        let tag = bytes[offset];
397        offset += 1;
398
399        if tag != TypeTag::NestedArray.to_u8() {
400            return Err(BinaryError::InvalidTypeTag { tag });
401        }
402
403        // Decode ELEMENT_COUNT from VarInt
404        let (element_count, consumed) = varint::decode(&bytes[offset..])?;
405        offset += consumed;
406
407        if element_count < 0 {
408            return Err(BinaryError::InvalidNestedStructure {
409                reason: format!("Negative element count: {}", element_count),
410            });
411        }
412
413        let element_count = element_count as usize;
414
415        // Recursively decode each record
416        let mut records = Vec::with_capacity(element_count);
417
418        for _ in 0..element_count {
419            // Each record in the array is encoded as a nested record
420            // Depth stays the same since we're already inside the array
421            let (record, consumed) =
422                self.decode_nested_record_with_depth(&bytes[offset..], current_depth)?;
423            offset += consumed;
424            records.push(record);
425        }
426
427        Ok((records, offset))
428    }
429}
430
431impl Default for BinaryNestedDecoder {
432    fn default() -> Self {
433        Self::new()
434    }
435}
436
437#[cfg(test)]
438mod tests {
439    #![allow(clippy::approx_constant)]
440
441    use super::*;
442
443    #[test]
444    fn test_nested_decoder_config_default() {
445        let config = NestedDecoderConfig::default();
446        assert!(config.allow_nested);
447        assert!(!config.validate_nesting);
448        assert_eq!(config.max_depth, 32);
449    }
450
451    #[test]
452    fn test_nested_decoder_config_builder() {
453        let config = NestedDecoderConfig::new()
454            .with_allow_nested(false)
455            .with_validate_nesting(true)
456            .with_max_depth(16);
457
458        assert!(!config.allow_nested);
459        assert!(config.validate_nesting);
460        assert_eq!(config.max_depth, 16);
461    }
462
463    #[test]
464    fn test_nested_decoder_new() {
465        let decoder = BinaryNestedDecoder::new();
466        assert!(decoder.config.allow_nested);
467        assert!(!decoder.config.validate_nesting);
468        assert_eq!(decoder.config.max_depth, 32);
469    }
470
471    #[test]
472    fn test_nested_decoder_with_config() {
473        let config = NestedDecoderConfig::new()
474            .with_max_depth(8)
475            .with_validate_nesting(true);
476
477        let decoder = BinaryNestedDecoder::with_config(config);
478        assert_eq!(decoder.config.max_depth, 8);
479        assert!(decoder.config.validate_nesting);
480    }
481
482    #[test]
483    fn test_nested_decoder_default() {
484        let decoder = BinaryNestedDecoder::default();
485        assert!(decoder.config.allow_nested);
486        assert_eq!(decoder.config.max_depth, 32);
487    }
488}
489
490#[test]
491fn test_decode_empty_nested_record() {
492    use crate::binary::nested_encoder::BinaryNestedEncoder;
493
494    let encoder = BinaryNestedEncoder::new();
495    let record = LnmpRecord::new();
496    let binary = encoder.encode_nested_record(&record).unwrap();
497
498    let decoder = BinaryNestedDecoder::new();
499    let (decoded, consumed) = decoder.decode_nested_record(&binary).unwrap();
500
501    assert_eq!(decoded.fields().len(), 0);
502    assert_eq!(consumed, binary.len());
503}
504
505#[test]
506fn test_decode_single_level_nested_record() {
507    use crate::binary::nested_encoder::BinaryNestedEncoder;
508
509    let encoder = BinaryNestedEncoder::new();
510    let mut record = LnmpRecord::new();
511    record.add_field(LnmpField {
512        fid: 1,
513        value: LnmpValue::Int(42),
514    });
515    record.add_field(LnmpField {
516        fid: 2,
517        value: LnmpValue::String("test".to_string()),
518    });
519
520    let binary = encoder.encode_nested_record(&record).unwrap();
521
522    let decoder = BinaryNestedDecoder::new();
523    let (decoded, consumed) = decoder.decode_nested_record(&binary).unwrap();
524
525    assert_eq!(decoded.fields().len(), 2);
526    assert_eq!(decoded.get_field(1).unwrap().value, LnmpValue::Int(42));
527    assert_eq!(
528        decoded.get_field(2).unwrap().value,
529        LnmpValue::String("test".to_string())
530    );
531    assert_eq!(consumed, binary.len());
532}
533
534#[test]
535fn test_decode_nested_record_invalid_tag() {
536    let decoder = BinaryNestedDecoder::new();
537    let bytes = vec![0x01, 0x00]; // Wrong tag (Int instead of NestedRecord)
538
539    let result = decoder.decode_nested_record(&bytes);
540    assert!(matches!(
541        result,
542        Err(BinaryError::InvalidTypeTag { tag: 0x01 })
543    ));
544}
545
546#[test]
547fn test_decode_nested_record_insufficient_data() {
548    let decoder = BinaryNestedDecoder::new();
549    let bytes = vec![]; // Empty
550
551    let result = decoder.decode_nested_record(&bytes);
552    assert!(matches!(result, Err(BinaryError::UnexpectedEof { .. })));
553}
554
555#[test]
556fn test_decode_nested_record_negative_field_count() {
557    let decoder = BinaryNestedDecoder::new();
558    // TAG (0x06) + negative field count
559    let bytes = vec![0x06, 0x7F]; // -1 in VarInt
560
561    let result = decoder.decode_nested_record(&bytes);
562    assert!(matches!(
563        result,
564        Err(BinaryError::InvalidNestedStructure { .. })
565    ));
566}
567
568#[test]
569fn test_decode_multi_level_nested_record() {
570    use crate::binary::nested_encoder::BinaryNestedEncoder;
571
572    let encoder = BinaryNestedEncoder::new();
573
574    // Create a 2-level nested structure
575    let mut inner_record = LnmpRecord::new();
576    inner_record.add_field(LnmpField {
577        fid: 1,
578        value: LnmpValue::Int(42),
579    });
580
581    let mut outer_record = LnmpRecord::new();
582    outer_record.add_field(LnmpField {
583        fid: 2,
584        value: LnmpValue::NestedRecord(Box::new(inner_record)),
585    });
586
587    let binary = encoder.encode_nested_record(&outer_record).unwrap();
588
589    let decoder = BinaryNestedDecoder::new();
590    let (decoded, consumed) = decoder.decode_nested_record(&binary).unwrap();
591
592    assert_eq!(decoded.fields().len(), 1);
593    assert_eq!(consumed, binary.len());
594
595    // Verify nested structure
596    match &decoded.get_field(2).unwrap().value {
597        LnmpValue::NestedRecord(inner) => {
598            assert_eq!(inner.fields().len(), 1);
599            assert_eq!(inner.get_field(1).unwrap().value, LnmpValue::Int(42));
600        }
601        _ => panic!("Expected NestedRecord"),
602    }
603}
604
605#[test]
606fn test_decode_nested_record_depth_limit() {
607    use crate::binary::nested_encoder::BinaryNestedEncoder;
608
609    let config = NestedDecoderConfig::new().with_max_depth(2);
610    let decoder = BinaryNestedDecoder::with_config(config);
611
612    let encoder = BinaryNestedEncoder::new();
613
614    // Create a deeply nested structure (depth 3)
615    let mut level3 = LnmpRecord::new();
616    level3.add_field(LnmpField {
617        fid: 1,
618        value: LnmpValue::Int(42),
619    });
620
621    let mut level2 = LnmpRecord::new();
622    level2.add_field(LnmpField {
623        fid: 2,
624        value: LnmpValue::NestedRecord(Box::new(level3)),
625    });
626
627    let mut level1 = LnmpRecord::new();
628    level1.add_field(LnmpField {
629        fid: 3,
630        value: LnmpValue::NestedRecord(Box::new(level2)),
631    });
632
633    let binary = encoder.encode_nested_record(&level1).unwrap();
634
635    let result = decoder.decode_nested_record(&binary);
636    assert!(matches!(
637        result,
638        Err(BinaryError::NestingDepthExceeded { .. })
639    ));
640}
641
642#[test]
643fn test_decode_nested_record_all_primitive_types() {
644    use crate::binary::nested_encoder::BinaryNestedEncoder;
645
646    let encoder = BinaryNestedEncoder::new();
647
648    let mut record = LnmpRecord::new();
649    record.add_field(LnmpField {
650        fid: 1,
651        value: LnmpValue::Int(-42),
652    });
653    record.add_field(LnmpField {
654        fid: 2,
655        value: LnmpValue::Float(3.14),
656    });
657    record.add_field(LnmpField {
658        fid: 3,
659        value: LnmpValue::Bool(false),
660    });
661    record.add_field(LnmpField {
662        fid: 4,
663        value: LnmpValue::String("test".to_string()),
664    });
665    record.add_field(LnmpField {
666        fid: 5,
667        value: LnmpValue::StringArray(vec!["a".to_string(), "b".to_string()]),
668    });
669
670    let binary = encoder.encode_nested_record(&record).unwrap();
671
672    let decoder = BinaryNestedDecoder::new();
673    let (decoded, _) = decoder.decode_nested_record(&binary).unwrap();
674
675    assert_eq!(decoded.get_field(1).unwrap().value, LnmpValue::Int(-42));
676    assert_eq!(decoded.get_field(2).unwrap().value, LnmpValue::Float(3.14));
677    assert_eq!(decoded.get_field(3).unwrap().value, LnmpValue::Bool(false));
678    assert_eq!(
679        decoded.get_field(4).unwrap().value,
680        LnmpValue::String("test".to_string())
681    );
682    assert_eq!(
683        decoded.get_field(5).unwrap().value,
684        LnmpValue::StringArray(vec!["a".to_string(), "b".to_string()])
685    );
686}
687
688#[test]
689fn test_decode_nested_record_roundtrip() {
690    use crate::binary::nested_encoder::BinaryNestedEncoder;
691
692    let encoder = BinaryNestedEncoder::new();
693    let decoder = BinaryNestedDecoder::new();
694
695    let mut original = LnmpRecord::new();
696    original.add_field(LnmpField {
697        fid: 1,
698        value: LnmpValue::Int(100),
699    });
700    original.add_field(LnmpField {
701        fid: 2,
702        value: LnmpValue::String("hello".to_string()),
703    });
704
705    let binary = encoder.encode_nested_record(&original).unwrap();
706    let (decoded, _) = decoder.decode_nested_record(&binary).unwrap();
707
708    // Compare sorted fields for equality
709    assert_eq!(original.sorted_fields(), decoded.sorted_fields());
710}
711
712#[test]
713fn test_decode_empty_nested_array() {
714    use crate::binary::nested_encoder::BinaryNestedEncoder;
715
716    let encoder = BinaryNestedEncoder::new();
717    let records: Vec<LnmpRecord> = vec![];
718    let binary = encoder.encode_nested_array(&records).unwrap();
719
720    let decoder = BinaryNestedDecoder::new();
721    let (decoded, consumed) = decoder.decode_nested_array(&binary).unwrap();
722
723    assert_eq!(decoded.len(), 0);
724    assert_eq!(consumed, binary.len());
725}
726
727#[test]
728fn test_decode_nested_array_single_record() {
729    use crate::binary::nested_encoder::BinaryNestedEncoder;
730
731    let encoder = BinaryNestedEncoder::new();
732    let mut record = LnmpRecord::new();
733    record.add_field(LnmpField {
734        fid: 1,
735        value: LnmpValue::Int(42),
736    });
737
738    let binary = encoder.encode_nested_array(&[record.clone()]).unwrap();
739
740    let decoder = BinaryNestedDecoder::new();
741    let (decoded, consumed) = decoder.decode_nested_array(&binary).unwrap();
742
743    assert_eq!(decoded.len(), 1);
744    assert_eq!(decoded[0].sorted_fields(), record.sorted_fields());
745    assert_eq!(consumed, binary.len());
746}
747
748#[test]
749fn test_decode_nested_array_multiple_records() {
750    use crate::binary::nested_encoder::BinaryNestedEncoder;
751
752    let encoder = BinaryNestedEncoder::new();
753
754    let mut record1 = LnmpRecord::new();
755    record1.add_field(LnmpField {
756        fid: 1,
757        value: LnmpValue::Int(1),
758    });
759
760    let mut record2 = LnmpRecord::new();
761    record2.add_field(LnmpField {
762        fid: 1,
763        value: LnmpValue::Int(2),
764    });
765
766    let binary = encoder
767        .encode_nested_array(&[record1.clone(), record2.clone()])
768        .unwrap();
769
770    let decoder = BinaryNestedDecoder::new();
771    let (decoded, consumed) = decoder.decode_nested_array(&binary).unwrap();
772
773    assert_eq!(decoded.len(), 2);
774    assert_eq!(decoded[0].sorted_fields(), record1.sorted_fields());
775    assert_eq!(decoded[1].sorted_fields(), record2.sorted_fields());
776    assert_eq!(consumed, binary.len());
777}
778
779#[test]
780fn test_decode_nested_array_invalid_tag() {
781    let decoder = BinaryNestedDecoder::new();
782    let bytes = vec![0x06, 0x00]; // Wrong tag (NestedRecord instead of NestedArray)
783
784    let result = decoder.decode_nested_array(&bytes);
785    assert!(matches!(
786        result,
787        Err(BinaryError::InvalidTypeTag { tag: 0x06 })
788    ));
789}
790
791#[test]
792fn test_decode_nested_array_negative_element_count() {
793    let decoder = BinaryNestedDecoder::new();
794    // TAG (0x07) + negative element count
795    let bytes = vec![0x07, 0x7F]; // -1 in VarInt
796
797    let result = decoder.decode_nested_array(&bytes);
798    assert!(matches!(
799        result,
800        Err(BinaryError::InvalidNestedStructure { .. })
801    ));
802}
803
804#[test]
805fn test_decode_nested_array_depth_limit() {
806    use crate::binary::nested_encoder::BinaryNestedEncoder;
807
808    let config = NestedDecoderConfig::new().with_max_depth(2);
809    let decoder = BinaryNestedDecoder::with_config(config);
810
811    let encoder = BinaryNestedEncoder::new();
812
813    // Create a deeply nested structure (depth 3)
814    let mut level3 = LnmpRecord::new();
815    level3.add_field(LnmpField {
816        fid: 1,
817        value: LnmpValue::Int(42),
818    });
819
820    let mut level2 = LnmpRecord::new();
821    level2.add_field(LnmpField {
822        fid: 2,
823        value: LnmpValue::NestedRecord(Box::new(level3)),
824    });
825
826    let mut level1 = LnmpRecord::new();
827    level1.add_field(LnmpField {
828        fid: 3,
829        value: LnmpValue::NestedArray(vec![level2]),
830    });
831
832    let binary = encoder.encode_nested_record(&level1).unwrap();
833
834    let result = decoder.decode_nested_record(&binary);
835    assert!(matches!(
836        result,
837        Err(BinaryError::NestingDepthExceeded { .. })
838    ));
839}
840
841#[test]
842fn test_decode_nested_array_roundtrip() {
843    use crate::binary::nested_encoder::BinaryNestedEncoder;
844
845    let encoder = BinaryNestedEncoder::new();
846    let decoder = BinaryNestedDecoder::new();
847
848    let mut record1 = LnmpRecord::new();
849    record1.add_field(LnmpField {
850        fid: 1,
851        value: LnmpValue::String("first".to_string()),
852    });
853
854    let mut record2 = LnmpRecord::new();
855    record2.add_field(LnmpField {
856        fid: 2,
857        value: LnmpValue::String("second".to_string()),
858    });
859
860    let original = vec![record1.clone(), record2.clone()];
861
862    let binary = encoder.encode_nested_array(&original).unwrap();
863    let (decoded, _) = decoder.decode_nested_array(&binary).unwrap();
864
865    assert_eq!(decoded.len(), original.len());
866    for (i, record) in decoded.iter().enumerate() {
867        assert_eq!(record.sorted_fields(), original[i].sorted_fields());
868    }
869}
870
871#[test]
872fn test_decode_deeply_nested_record_depth_2() {
873    use crate::binary::nested_encoder::BinaryNestedEncoder;
874
875    let encoder = BinaryNestedEncoder::new();
876    let decoder = BinaryNestedDecoder::new();
877
878    // Create depth-2 nested structure
879    let mut level2 = LnmpRecord::new();
880    level2.add_field(LnmpField {
881        fid: 1,
882        value: LnmpValue::Int(100),
883    });
884
885    let mut level1 = LnmpRecord::new();
886    level1.add_field(LnmpField {
887        fid: 2,
888        value: LnmpValue::NestedRecord(Box::new(level2)),
889    });
890
891    let binary = encoder.encode_nested_record(&level1).unwrap();
892    let (decoded, _) = decoder.decode_nested_record(&binary).unwrap();
893
894    // Verify structure
895    match &decoded.get_field(2).unwrap().value {
896        LnmpValue::NestedRecord(inner) => {
897            assert_eq!(inner.get_field(1).unwrap().value, LnmpValue::Int(100));
898        }
899        _ => panic!("Expected NestedRecord"),
900    }
901}
902
903#[test]
904fn test_decode_deeply_nested_record_depth_3() {
905    use crate::binary::nested_encoder::BinaryNestedEncoder;
906
907    let encoder = BinaryNestedEncoder::new();
908    let decoder = BinaryNestedDecoder::new();
909
910    // Create depth-3 nested structure
911    let mut level3 = LnmpRecord::new();
912    level3.add_field(LnmpField {
913        fid: 1,
914        value: LnmpValue::Int(100),
915    });
916
917    let mut level2 = LnmpRecord::new();
918    level2.add_field(LnmpField {
919        fid: 2,
920        value: LnmpValue::NestedRecord(Box::new(level3)),
921    });
922
923    let mut level1 = LnmpRecord::new();
924    level1.add_field(LnmpField {
925        fid: 3,
926        value: LnmpValue::NestedRecord(Box::new(level2)),
927    });
928
929    let binary = encoder.encode_nested_record(&level1).unwrap();
930    let result = decoder.decode_nested_record(&binary);
931    assert!(result.is_ok());
932}
933
934#[test]
935fn test_decode_malformed_nested_structure_incomplete_field() {
936    let decoder = BinaryNestedDecoder::new();
937    // TAG (0x06) + FIELD_COUNT (1) + FID (1) but no VALUE
938    let bytes = vec![0x06, 0x01, 0x01];
939
940    let result = decoder.decode_nested_record(&bytes);
941    assert!(matches!(result, Err(BinaryError::UnexpectedEof { .. })));
942}
943
944#[test]
945fn test_decode_malformed_nested_structure_invalid_fid() {
946    let decoder = BinaryNestedDecoder::new();
947    // TAG (0x06) + FIELD_COUNT (1) + negative FID
948    let bytes = vec![0x06, 0x01, 0x7F]; // -1 as FID
949
950    let result = decoder.decode_nested_record(&bytes);
951    assert!(matches!(result, Err(BinaryError::InvalidFID { .. })));
952}
953
954#[test]
955fn test_decode_nested_structure_not_allowed() {
956    let config = NestedDecoderConfig::new().with_allow_nested(false);
957    let decoder = BinaryNestedDecoder::with_config(config);
958
959    // Try to decode a nested record value
960    // TAG (0x06) + FIELD_COUNT (0)
961    let bytes = vec![0x06, 0x00];
962
963    let result = decoder.decode_value_recursive(&bytes, 0);
964    assert!(matches!(
965        result,
966        Err(BinaryError::NestedStructureNotSupported)
967    ));
968}
969
970#[test]
971fn test_decode_nested_array_not_allowed() {
972    let config = NestedDecoderConfig::new().with_allow_nested(false);
973    let decoder = BinaryNestedDecoder::with_config(config);
974
975    // Try to decode a nested array value
976    // TAG (0x07) + ELEMENT_COUNT (0)
977    let bytes = vec![0x07, 0x00];
978
979    let result = decoder.decode_value_recursive(&bytes, 0);
980    assert!(matches!(
981        result,
982        Err(BinaryError::NestedStructureNotSupported)
983    ));
984}
985
986#[test]
987fn test_decode_roundtrip_complex_nested_structure() {
988    use crate::binary::nested_encoder::BinaryNestedEncoder;
989
990    let encoder = BinaryNestedEncoder::new();
991    let decoder = BinaryNestedDecoder::new();
992
993    // Create a complex nested structure with mixed types
994    let mut inner1 = LnmpRecord::new();
995    inner1.add_field(LnmpField {
996        fid: 1,
997        value: LnmpValue::String("inner1".to_string()),
998    });
999
1000    let mut inner2 = LnmpRecord::new();
1001    inner2.add_field(LnmpField {
1002        fid: 2,
1003        value: LnmpValue::Int(42),
1004    });
1005
1006    let mut outer = LnmpRecord::new();
1007    outer.add_field(LnmpField {
1008        fid: 1,
1009        value: LnmpValue::NestedRecord(Box::new(inner1)),
1010    });
1011    outer.add_field(LnmpField {
1012        fid: 2,
1013        value: LnmpValue::NestedArray(vec![inner2]),
1014    });
1015    outer.add_field(LnmpField {
1016        fid: 3,
1017        value: LnmpValue::Bool(true),
1018    });
1019
1020    let binary = encoder.encode_nested_record(&outer).unwrap();
1021    let (decoded, _) = decoder.decode_nested_record(&binary).unwrap();
1022
1023    assert_eq!(decoded.sorted_fields().len(), outer.sorted_fields().len());
1024}
1025
1026#[test]
1027fn test_decode_empty_nested_records_at_multiple_levels() {
1028    use crate::binary::nested_encoder::BinaryNestedEncoder;
1029
1030    let encoder = BinaryNestedEncoder::new();
1031    let decoder = BinaryNestedDecoder::new();
1032
1033    let inner = LnmpRecord::new(); // Empty
1034    let mut outer = LnmpRecord::new();
1035    outer.add_field(LnmpField {
1036        fid: 1,
1037        value: LnmpValue::NestedRecord(Box::new(inner)),
1038    });
1039
1040    let binary = encoder.encode_nested_record(&outer).unwrap();
1041    let (decoded, _) = decoder.decode_nested_record(&binary).unwrap();
1042
1043    match &decoded.get_field(1).unwrap().value {
1044        LnmpValue::NestedRecord(inner) => {
1045            assert_eq!(inner.fields().len(), 0);
1046        }
1047        _ => panic!("Expected NestedRecord"),
1048    }
1049}
1050
1051#[test]
1052fn test_decode_empty_nested_arrays() {
1053    use crate::binary::nested_encoder::BinaryNestedEncoder;
1054
1055    let encoder = BinaryNestedEncoder::new();
1056    let decoder = BinaryNestedDecoder::new();
1057
1058    let mut record = LnmpRecord::new();
1059    record.add_field(LnmpField {
1060        fid: 1,
1061        value: LnmpValue::NestedArray(vec![]),
1062    });
1063
1064    let binary = encoder.encode_nested_record(&record).unwrap();
1065    let (decoded, _) = decoder.decode_nested_record(&binary).unwrap();
1066
1067    match &decoded.get_field(1).unwrap().value {
1068        LnmpValue::NestedArray(arr) => {
1069            assert_eq!(arr.len(), 0);
1070        }
1071        _ => panic!("Expected NestedArray"),
1072    }
1073}
1074
1075#[test]
1076fn test_decode_mixed_primitive_and_nested_fields() {
1077    use crate::binary::nested_encoder::BinaryNestedEncoder;
1078
1079    let encoder = BinaryNestedEncoder::new();
1080    let decoder = BinaryNestedDecoder::new();
1081
1082    let mut inner = LnmpRecord::new();
1083    inner.add_field(LnmpField {
1084        fid: 1,
1085        value: LnmpValue::String("inner".to_string()),
1086    });
1087
1088    let mut outer = LnmpRecord::new();
1089    outer.add_field(LnmpField {
1090        fid: 1,
1091        value: LnmpValue::Int(42),
1092    });
1093    outer.add_field(LnmpField {
1094        fid: 2,
1095        value: LnmpValue::NestedRecord(Box::new(inner)),
1096    });
1097    outer.add_field(LnmpField {
1098        fid: 3,
1099        value: LnmpValue::Bool(true),
1100    });
1101
1102    let binary = encoder.encode_nested_record(&outer).unwrap();
1103    let (decoded, _) = decoder.decode_nested_record(&binary).unwrap();
1104
1105    assert_eq!(decoded.fields().len(), 3);
1106    assert_eq!(decoded.get_field(1).unwrap().value, LnmpValue::Int(42));
1107    assert_eq!(decoded.get_field(3).unwrap().value, LnmpValue::Bool(true));
1108}
1109
1110#[test]
1111fn test_decode_depth_limit_enforced_at_exact_limit() {
1112    use crate::binary::nested_encoder::BinaryNestedEncoder;
1113
1114    // With max_depth=2, we can decode structures where the deepest call to
1115    // decode_nested_record_with_depth has current_depth < 2
1116    let config = NestedDecoderConfig::new().with_max_depth(2);
1117    let decoder = BinaryNestedDecoder::with_config(config);
1118    let encoder = BinaryNestedEncoder::new();
1119
1120    // Flat record (no nesting) - should succeed
1121    let mut flat = LnmpRecord::new();
1122    flat.add_field(LnmpField {
1123        fid: 1,
1124        value: LnmpValue::Int(42),
1125    });
1126
1127    let binary = encoder.encode_nested_record(&flat).unwrap();
1128    let result = decoder.decode_nested_record(&binary);
1129    assert!(result.is_ok(), "Flat record should succeed");
1130
1131    // One level of nesting - should succeed
1132    let mut inner = LnmpRecord::new();
1133    inner.add_field(LnmpField {
1134        fid: 1,
1135        value: LnmpValue::Int(42),
1136    });
1137
1138    let mut outer = LnmpRecord::new();
1139    outer.add_field(LnmpField {
1140        fid: 2,
1141        value: LnmpValue::NestedRecord(Box::new(inner)),
1142    });
1143
1144    let binary = encoder.encode_nested_record(&outer).unwrap();
1145    let result = decoder.decode_nested_record(&binary);
1146    assert!(result.is_ok(), "One level of nesting should succeed");
1147
1148    // Two levels of nesting - should fail with max_depth=2
1149    let mut level3 = LnmpRecord::new();
1150    level3.add_field(LnmpField {
1151        fid: 1,
1152        value: LnmpValue::Int(42),
1153    });
1154
1155    let mut level2 = LnmpRecord::new();
1156    level2.add_field(LnmpField {
1157        fid: 2,
1158        value: LnmpValue::NestedRecord(Box::new(level3)),
1159    });
1160
1161    let mut level1 = LnmpRecord::new();
1162    level1.add_field(LnmpField {
1163        fid: 3,
1164        value: LnmpValue::NestedRecord(Box::new(level2)),
1165    });
1166
1167    let binary = encoder.encode_nested_record(&level1).unwrap();
1168    let result = decoder.decode_nested_record(&binary);
1169    assert!(
1170        matches!(result, Err(BinaryError::NestingDepthExceeded { .. })),
1171        "Two levels of nesting should fail with max_depth=2"
1172    );
1173}
1174
1175#[test]
1176fn test_decode_invalid_utf8_in_string() {
1177    let decoder = BinaryNestedDecoder::new();
1178    // TAG (0x06) + FIELD_COUNT (1) + FID (1) + String TAG (0x04) + length (3) + invalid UTF-8
1179    let bytes = vec![0x06, 0x01, 0x01, 0x04, 0x03, 0xFF, 0xFE, 0xFD];
1180
1181    let result = decoder.decode_nested_record(&bytes);
1182    assert!(matches!(result, Err(BinaryError::InvalidUtf8 { .. })));
1183}
1184
1185#[test]
1186fn test_decode_string_array_with_empty_strings() {
1187    use crate::binary::nested_encoder::BinaryNestedEncoder;
1188
1189    let encoder = BinaryNestedEncoder::new();
1190    let decoder = BinaryNestedDecoder::new();
1191
1192    let mut record = LnmpRecord::new();
1193    record.add_field(LnmpField {
1194        fid: 1,
1195        value: LnmpValue::StringArray(vec!["".to_string(), "test".to_string(), "".to_string()]),
1196    });
1197
1198    let binary = encoder.encode_nested_record(&record).unwrap();
1199    let (decoded, _) = decoder.decode_nested_record(&binary).unwrap();
1200
1201    assert_eq!(
1202        decoded.get_field(1).unwrap().value,
1203        LnmpValue::StringArray(vec!["".to_string(), "test".to_string(), "".to_string()])
1204    );
1205}