lnmp_codec/binary/
nested_encoder.rs

1//! Nested structure encoding for LNMP v0.5 binary format.
2//!
3//! This module provides encoding support for nested records and arrays in the binary format.
4//! It implements recursive encoding with depth validation and size limits to prevent
5//! stack overflow and memory exhaustion attacks.
6
7use super::error::BinaryError;
8use super::types::TypeTag;
9use super::varint;
10use lnmp_core::{LnmpRecord, LnmpValue};
11
12/// Configuration for nested structure encoding (v0.5)
13#[derive(Debug, Clone)]
14pub struct NestedEncoderConfig {
15    /// Maximum nesting depth allowed (default: 32)
16    pub max_depth: usize,
17    /// Maximum record size in bytes (None = unlimited)
18    pub max_record_size: Option<usize>,
19    /// Whether to validate canonical ordering
20    pub validate_canonical: bool,
21}
22
23impl Default for NestedEncoderConfig {
24    fn default() -> Self {
25        Self {
26            max_depth: 32,
27            max_record_size: None,
28            validate_canonical: false,
29        }
30    }
31}
32
33impl NestedEncoderConfig {
34    /// Creates a new nested encoder configuration with default settings
35    pub fn new() -> Self {
36        Self::default()
37    }
38
39    /// Sets the maximum nesting depth
40    pub fn with_max_depth(mut self, max_depth: usize) -> Self {
41        self.max_depth = max_depth;
42        self
43    }
44
45    /// Sets the maximum record size in bytes
46    pub fn with_max_record_size(mut self, max_size: Option<usize>) -> Self {
47        self.max_record_size = max_size;
48        self
49    }
50
51    /// Sets whether to validate canonical ordering
52    pub fn with_validate_canonical(mut self, validate: bool) -> Self {
53        self.validate_canonical = validate;
54        self
55    }
56}
57
58/// Binary nested structure encoder for LNMP v0.5
59///
60/// Encodes nested records and arrays with depth validation and size limits.
61#[derive(Debug)]
62pub struct BinaryNestedEncoder {
63    config: NestedEncoderConfig,
64}
65
66impl BinaryNestedEncoder {
67    /// Creates a new nested encoder with default configuration
68    pub fn new() -> Self {
69        Self {
70            config: NestedEncoderConfig::default(),
71        }
72    }
73
74    /// Creates a nested encoder with custom configuration
75    pub fn with_config(config: NestedEncoderConfig) -> Self {
76        Self { config }
77    }
78
79    /// Encodes a nested record to binary format
80    ///
81    /// Binary layout:
82    /// ```text
83    /// ┌──────────┬──────────────┬─────────────────────────────────┐
84    /// │   TAG    │ FIELD_COUNT  │      FIELD ENTRIES...           │
85    /// │ (1 byte) │  (VarInt)    │  { FID | VALUE }*               │
86    /// └──────────┴──────────────┴─────────────────────────────────┘
87    /// ```
88    ///
89    /// # Arguments
90    ///
91    /// * `record` - The nested record to encode
92    ///
93    /// # Returns
94    ///
95    /// A vector of bytes representing the binary-encoded nested record
96    ///
97    /// # Errors
98    ///
99    /// Returns `BinaryError` if:
100    /// - Nesting depth exceeds configured maximum
101    /// - Record size exceeds configured maximum
102    /// - Field encoding fails
103    pub fn encode_nested_record(&self, record: &LnmpRecord) -> Result<Vec<u8>, BinaryError> {
104        self.encode_nested_record_with_depth(record, 0)
105    }
106
107    /// Encodes a nested record with depth tracking
108    fn encode_nested_record_with_depth(
109        &self,
110        record: &LnmpRecord,
111        current_depth: usize,
112    ) -> Result<Vec<u8>, BinaryError> {
113        // Validate depth
114        if current_depth >= self.config.max_depth {
115            return Err(BinaryError::NestingDepthExceeded {
116                depth: current_depth,
117                max: self.config.max_depth,
118            });
119        }
120
121        let mut buffer = Vec::new();
122
123        // Write TAG byte (0x06 for NestedRecord)
124        buffer.push(TypeTag::NestedRecord.to_u8());
125
126        // Get fields (sorted for canonical ordering)
127        let fields = record.sorted_fields();
128
129        // Encode FIELD_COUNT as VarInt
130        let field_count = fields.len() as i64;
131        buffer.extend_from_slice(&varint::encode(field_count));
132
133        // Recursively encode each field
134        for field in fields {
135            // Encode FID as VarInt
136            buffer.extend_from_slice(&varint::encode(field.fid as i64));
137
138            // Encode VALUE recursively
139            let value_bytes = self.encode_value_recursive(&field.value, current_depth + 1)?;
140            buffer.extend_from_slice(&value_bytes);
141
142            // Check size limit if configured
143            if let Some(max_size) = self.config.max_record_size {
144                if buffer.len() > max_size {
145                    return Err(BinaryError::RecordSizeExceeded {
146                        size: buffer.len(),
147                        max: max_size,
148                    });
149                }
150            }
151        }
152
153        Ok(buffer)
154    }
155
156    /// Encodes a nested array to binary format
157    ///
158    /// Binary layout:
159    /// ```text
160    /// ┌──────────┬──────────────┬─────────────────────────────────┐
161    /// │   TAG    │ ELEM_COUNT   │      RECORD ENTRIES...          │
162    /// │ (1 byte) │  (VarInt)    │  [ RECORD(...) ]*               │
163    /// └──────────┴──────────────┴─────────────────────────────────┘
164    /// ```
165    ///
166    /// # Arguments
167    ///
168    /// * `records` - The array of records to encode
169    ///
170    /// # Returns
171    ///
172    /// A vector of bytes representing the binary-encoded nested array
173    ///
174    /// # Errors
175    ///
176    /// Returns `BinaryError` if:
177    /// - Nesting depth exceeds configured maximum
178    /// - Record size exceeds configured maximum
179    /// - Record encoding fails
180    pub fn encode_nested_array(&self, records: &[LnmpRecord]) -> Result<Vec<u8>, BinaryError> {
181        self.encode_nested_array_with_depth(records, 0)
182    }
183
184    /// Encodes a nested array with depth tracking
185    fn encode_nested_array_with_depth(
186        &self,
187        records: &[LnmpRecord],
188        current_depth: usize,
189    ) -> Result<Vec<u8>, BinaryError> {
190        // Validate depth
191        if current_depth >= self.config.max_depth {
192            return Err(BinaryError::NestingDepthExceeded {
193                depth: current_depth,
194                max: self.config.max_depth,
195            });
196        }
197
198        let mut buffer = Vec::new();
199
200        // Write TAG byte (0x07 for NestedArray)
201        buffer.push(TypeTag::NestedArray.to_u8());
202
203        // Encode ELEMENT_COUNT as VarInt
204        let element_count = records.len() as i64;
205        buffer.extend_from_slice(&varint::encode(element_count));
206
207        // Recursively encode each record
208        for record in records {
209            // For records within arrays, we need to encode them with canonical ordering
210            let record_bytes = self.encode_nested_record_with_depth(record, current_depth + 1)?;
211            buffer.extend_from_slice(&record_bytes);
212
213            // Check size limit if configured
214            if let Some(max_size) = self.config.max_record_size {
215                if buffer.len() > max_size {
216                    return Err(BinaryError::RecordSizeExceeded {
217                        size: buffer.len(),
218                        max: max_size,
219                    });
220                }
221            }
222        }
223
224        Ok(buffer)
225    }
226
227    /// Encodes a value recursively, handling nested structures
228    fn encode_value_recursive(
229        &self,
230        value: &LnmpValue,
231        current_depth: usize,
232    ) -> Result<Vec<u8>, BinaryError> {
233        // Validate depth for nested structures
234        if current_depth >= self.config.max_depth
235            && matches!(
236                value,
237                LnmpValue::NestedRecord(_) | LnmpValue::NestedArray(_)
238            )
239        {
240            return Err(BinaryError::NestingDepthExceeded {
241                depth: current_depth,
242                max: self.config.max_depth,
243            });
244        }
245
246        match value {
247            LnmpValue::Int(i) => {
248                let mut buffer = Vec::new();
249                buffer.push(TypeTag::Int.to_u8());
250                buffer.extend_from_slice(&varint::encode(*i));
251                Ok(buffer)
252            }
253            LnmpValue::Float(f) => {
254                let mut buffer = Vec::new();
255                buffer.push(TypeTag::Float.to_u8());
256                buffer.extend_from_slice(&f.to_le_bytes());
257                Ok(buffer)
258            }
259            LnmpValue::Bool(b) => {
260                let mut buffer = Vec::new();
261                buffer.push(TypeTag::Bool.to_u8());
262                buffer.push(if *b { 0x01 } else { 0x00 });
263                Ok(buffer)
264            }
265            LnmpValue::String(s) => {
266                let mut buffer = Vec::new();
267                buffer.push(TypeTag::String.to_u8());
268                let bytes = s.as_bytes();
269                buffer.extend_from_slice(&varint::encode(bytes.len() as i64));
270                buffer.extend_from_slice(bytes);
271                Ok(buffer)
272            }
273            LnmpValue::StringArray(arr) => {
274                let mut buffer = Vec::new();
275                buffer.push(TypeTag::StringArray.to_u8());
276                buffer.extend_from_slice(&varint::encode(arr.len() as i64));
277                for s in arr {
278                    let bytes = s.as_bytes();
279                    buffer.extend_from_slice(&varint::encode(bytes.len() as i64));
280                    buffer.extend_from_slice(bytes);
281                }
282                Ok(buffer)
283            }
284            LnmpValue::NestedRecord(record) => {
285                self.encode_nested_record_with_depth(record, current_depth)
286            }
287            LnmpValue::NestedArray(records) => {
288                self.encode_nested_array_with_depth(records, current_depth)
289            }
290        }
291    }
292}
293
294impl Default for BinaryNestedEncoder {
295    fn default() -> Self {
296        Self::new()
297    }
298}
299
300#[cfg(test)]
301mod tests {
302    #![allow(clippy::approx_constant)]
303
304    use super::*;
305    use lnmp_core::LnmpField;
306
307    #[test]
308    fn test_nested_encoder_config_default() {
309        let config = NestedEncoderConfig::default();
310        assert_eq!(config.max_depth, 32);
311        assert_eq!(config.max_record_size, None);
312        assert!(!config.validate_canonical);
313    }
314
315    #[test]
316    fn test_nested_encoder_config_builder() {
317        let config = NestedEncoderConfig::new()
318            .with_max_depth(16)
319            .with_max_record_size(Some(1024))
320            .with_validate_canonical(true);
321
322        assert_eq!(config.max_depth, 16);
323        assert_eq!(config.max_record_size, Some(1024));
324        assert!(config.validate_canonical);
325    }
326
327    #[test]
328    fn test_encode_empty_nested_record() {
329        let encoder = BinaryNestedEncoder::new();
330        let record = LnmpRecord::new();
331
332        let result = encoder.encode_nested_record(&record).unwrap();
333
334        // Should have TAG (0x06) + FIELD_COUNT (0)
335        assert_eq!(result[0], 0x06); // NestedRecord tag
336        assert_eq!(result[1], 0x00); // Field count = 0
337        assert_eq!(result.len(), 2);
338    }
339
340    #[test]
341    fn test_encode_single_level_nested_record() {
342        let encoder = BinaryNestedEncoder::new();
343        let mut record = LnmpRecord::new();
344        record.add_field(LnmpField {
345            fid: 1,
346            value: LnmpValue::Int(42),
347        });
348        record.add_field(LnmpField {
349            fid: 2,
350            value: LnmpValue::String("test".to_string()),
351        });
352
353        let result = encoder.encode_nested_record(&record).unwrap();
354
355        // Should start with TAG (0x06) + FIELD_COUNT (2)
356        assert_eq!(result[0], 0x06); // NestedRecord tag
357        assert_eq!(result[1], 0x02); // Field count = 2
358    }
359
360    #[test]
361    fn test_encode_nested_record_canonical_ordering() {
362        let encoder = BinaryNestedEncoder::new();
363        let mut record = LnmpRecord::new();
364        // Add fields in non-sorted order
365        record.add_field(LnmpField {
366            fid: 10,
367            value: LnmpValue::Int(3),
368        });
369        record.add_field(LnmpField {
370            fid: 2,
371            value: LnmpValue::Int(1),
372        });
373        record.add_field(LnmpField {
374            fid: 5,
375            value: LnmpValue::Int(2),
376        });
377
378        let result = encoder.encode_nested_record(&record).unwrap();
379
380        // Verify TAG and count
381        assert_eq!(result[0], 0x06); // NestedRecord tag
382        assert_eq!(result[1], 0x03); // Field count = 3
383
384        // Fields should be encoded in sorted order: 2, 5, 10
385        // After TAG and COUNT, we should see FID=2 first
386        assert_eq!(result[2], 0x02); // FID = 2 (VarInt)
387    }
388
389    #[test]
390    fn test_encode_empty_nested_array() {
391        let encoder = BinaryNestedEncoder::new();
392        let records: Vec<LnmpRecord> = vec![];
393
394        let result = encoder.encode_nested_array(&records).unwrap();
395
396        // Should have TAG (0x07) + ELEMENT_COUNT (0)
397        assert_eq!(result[0], 0x07); // NestedArray tag
398        assert_eq!(result[1], 0x00); // Element count = 0
399        assert_eq!(result.len(), 2);
400    }
401
402    #[test]
403    fn test_encode_nested_array_single_record() {
404        let encoder = BinaryNestedEncoder::new();
405        let mut record = LnmpRecord::new();
406        record.add_field(LnmpField {
407            fid: 1,
408            value: LnmpValue::Int(42),
409        });
410
411        let result = encoder.encode_nested_array(&[record]).unwrap();
412
413        // Should start with TAG (0x07) + ELEMENT_COUNT (1)
414        assert_eq!(result[0], 0x07); // NestedArray tag
415        assert_eq!(result[1], 0x01); // Element count = 1
416                                     // Next should be the nested record (TAG 0x06)
417        assert_eq!(result[2], 0x06); // NestedRecord tag
418    }
419
420    #[test]
421    fn test_encode_nested_array_multiple_records() {
422        let encoder = BinaryNestedEncoder::new();
423        let mut record1 = LnmpRecord::new();
424        record1.add_field(LnmpField {
425            fid: 1,
426            value: LnmpValue::Int(1),
427        });
428
429        let mut record2 = LnmpRecord::new();
430        record2.add_field(LnmpField {
431            fid: 1,
432            value: LnmpValue::Int(2),
433        });
434
435        let result = encoder.encode_nested_array(&[record1, record2]).unwrap();
436
437        // Should start with TAG (0x07) + ELEMENT_COUNT (2)
438        assert_eq!(result[0], 0x07); // NestedArray tag
439        assert_eq!(result[1], 0x02); // Element count = 2
440    }
441
442    #[test]
443    fn test_encode_depth_limit_exceeded() {
444        let config = NestedEncoderConfig::new().with_max_depth(2);
445        let encoder = BinaryNestedEncoder::with_config(config);
446
447        // Create a deeply nested structure (depth 3)
448        let mut level3 = LnmpRecord::new();
449        level3.add_field(LnmpField {
450            fid: 1,
451            value: LnmpValue::Int(42),
452        });
453
454        let mut level2 = LnmpRecord::new();
455        level2.add_field(LnmpField {
456            fid: 2,
457            value: LnmpValue::NestedRecord(Box::new(level3)),
458        });
459
460        let mut level1 = LnmpRecord::new();
461        level1.add_field(LnmpField {
462            fid: 3,
463            value: LnmpValue::NestedRecord(Box::new(level2)),
464        });
465
466        let result = encoder.encode_nested_record(&level1);
467
468        assert!(result.is_err());
469        match result {
470            Err(BinaryError::NestingDepthExceeded { depth, max }) => {
471                assert_eq!(max, 2);
472                assert!(depth >= 2);
473            }
474            _ => panic!("Expected NestingDepthExceeded error"),
475        }
476    }
477
478    #[test]
479    fn test_encode_size_limit_exceeded() {
480        let config = NestedEncoderConfig::new().with_max_record_size(Some(10));
481        let encoder = BinaryNestedEncoder::with_config(config);
482
483        let mut record = LnmpRecord::new();
484        // Add enough fields to exceed the size limit
485        for i in 0..100 {
486            record.add_field(LnmpField {
487                fid: i,
488                value: LnmpValue::String("test".to_string()),
489            });
490        }
491
492        let result = encoder.encode_nested_record(&record);
493
494        assert!(result.is_err());
495        match result {
496            Err(BinaryError::RecordSizeExceeded { size, max }) => {
497                assert_eq!(max, 10);
498                assert!(size > 10);
499            }
500            _ => panic!("Expected RecordSizeExceeded error"),
501        }
502    }
503
504    #[test]
505    fn test_encode_multi_level_nested_record() {
506        let encoder = BinaryNestedEncoder::new();
507
508        // Create a 2-level nested structure
509        let mut inner_record = LnmpRecord::new();
510        inner_record.add_field(LnmpField {
511            fid: 1,
512            value: LnmpValue::Int(42),
513        });
514
515        let mut outer_record = LnmpRecord::new();
516        outer_record.add_field(LnmpField {
517            fid: 2,
518            value: LnmpValue::NestedRecord(Box::new(inner_record)),
519        });
520
521        let result = encoder.encode_nested_record(&outer_record).unwrap();
522
523        // Should start with TAG (0x06) + FIELD_COUNT (1)
524        assert_eq!(result[0], 0x06); // NestedRecord tag
525        assert_eq!(result[1], 0x01); // Field count = 1
526                                     // Next should be FID=2
527        assert_eq!(result[2], 0x02); // FID = 2
528                                     // Next should be nested record TAG (0x06)
529        assert_eq!(result[3], 0x06); // Inner NestedRecord tag
530    }
531
532    #[test]
533    fn test_encode_nested_record_depth_2() {
534        let encoder = BinaryNestedEncoder::new();
535
536        // Create depth-2 nested structure
537        let mut level2 = LnmpRecord::new();
538        level2.add_field(LnmpField {
539            fid: 1,
540            value: LnmpValue::Int(100),
541        });
542
543        let mut level1 = LnmpRecord::new();
544        level1.add_field(LnmpField {
545            fid: 2,
546            value: LnmpValue::NestedRecord(Box::new(level2)),
547        });
548
549        let result = encoder.encode_nested_record(&level1).unwrap();
550        assert!(!result.is_empty());
551        assert_eq!(result[0], 0x06); // NestedRecord tag
552    }
553
554    #[test]
555    fn test_encode_nested_record_depth_3() {
556        let encoder = BinaryNestedEncoder::new();
557
558        // Create depth-3 nested structure
559        let mut level3 = LnmpRecord::new();
560        level3.add_field(LnmpField {
561            fid: 1,
562            value: LnmpValue::Int(100),
563        });
564
565        let mut level2 = LnmpRecord::new();
566        level2.add_field(LnmpField {
567            fid: 2,
568            value: LnmpValue::NestedRecord(Box::new(level3)),
569        });
570
571        let mut level1 = LnmpRecord::new();
572        level1.add_field(LnmpField {
573            fid: 3,
574            value: LnmpValue::NestedRecord(Box::new(level2)),
575        });
576
577        let result = encoder.encode_nested_record(&level1).unwrap();
578        assert!(!result.is_empty());
579        assert_eq!(result[0], 0x06); // NestedRecord tag
580    }
581
582    #[test]
583    fn test_encode_nested_record_depth_4() {
584        let encoder = BinaryNestedEncoder::new();
585
586        // Create depth-4 nested structure
587        let mut level4 = LnmpRecord::new();
588        level4.add_field(LnmpField {
589            fid: 1,
590            value: LnmpValue::Int(100),
591        });
592
593        let mut level3 = LnmpRecord::new();
594        level3.add_field(LnmpField {
595            fid: 2,
596            value: LnmpValue::NestedRecord(Box::new(level4)),
597        });
598
599        let mut level2 = LnmpRecord::new();
600        level2.add_field(LnmpField {
601            fid: 3,
602            value: LnmpValue::NestedRecord(Box::new(level3)),
603        });
604
605        let mut level1 = LnmpRecord::new();
606        level1.add_field(LnmpField {
607            fid: 4,
608            value: LnmpValue::NestedRecord(Box::new(level2)),
609        });
610
611        let result = encoder.encode_nested_record(&level1).unwrap();
612        assert!(!result.is_empty());
613        assert_eq!(result[0], 0x06); // NestedRecord tag
614    }
615
616    #[test]
617    fn test_encode_nested_record_depth_5() {
618        let encoder = BinaryNestedEncoder::new();
619
620        // Create depth-5 nested structure
621        let mut level5 = LnmpRecord::new();
622        level5.add_field(LnmpField {
623            fid: 1,
624            value: LnmpValue::Int(100),
625        });
626
627        let mut level4 = LnmpRecord::new();
628        level4.add_field(LnmpField {
629            fid: 2,
630            value: LnmpValue::NestedRecord(Box::new(level5)),
631        });
632
633        let mut level3 = LnmpRecord::new();
634        level3.add_field(LnmpField {
635            fid: 3,
636            value: LnmpValue::NestedRecord(Box::new(level4)),
637        });
638
639        let mut level2 = LnmpRecord::new();
640        level2.add_field(LnmpField {
641            fid: 4,
642            value: LnmpValue::NestedRecord(Box::new(level3)),
643        });
644
645        let mut level1 = LnmpRecord::new();
646        level1.add_field(LnmpField {
647            fid: 5,
648            value: LnmpValue::NestedRecord(Box::new(level2)),
649        });
650
651        let result = encoder.encode_nested_record(&level1).unwrap();
652        assert!(!result.is_empty());
653        assert_eq!(result[0], 0x06); // NestedRecord tag
654    }
655
656    #[test]
657    fn test_encode_depth_limit_enforced_at_exact_limit() {
658        let config = NestedEncoderConfig::new().with_max_depth(2);
659        let encoder = BinaryNestedEncoder::with_config(config);
660
661        // Create a structure at depth 1 (should succeed)
662        let mut level1 = LnmpRecord::new();
663        level1.add_field(LnmpField {
664            fid: 1,
665            value: LnmpValue::Int(42),
666        });
667
668        // This should succeed (depth 0 when encoding starts)
669        let result = encoder.encode_nested_record(&level1);
670        assert!(result.is_ok());
671
672        // Create a structure at depth 2 (should succeed)
673        let mut inner = LnmpRecord::new();
674        inner.add_field(LnmpField {
675            fid: 1,
676            value: LnmpValue::Int(42),
677        });
678
679        let mut outer = LnmpRecord::new();
680        outer.add_field(LnmpField {
681            fid: 2,
682            value: LnmpValue::NestedRecord(Box::new(inner)),
683        });
684
685        // This should succeed (depth 1 when encoding the nested record)
686        let result = encoder.encode_nested_record(&outer);
687        assert!(result.is_ok());
688
689        // Create a structure at depth 3 (should fail)
690        let mut level3 = LnmpRecord::new();
691        level3.add_field(LnmpField {
692            fid: 1,
693            value: LnmpValue::Int(42),
694        });
695
696        let mut level2 = LnmpRecord::new();
697        level2.add_field(LnmpField {
698            fid: 2,
699            value: LnmpValue::NestedRecord(Box::new(level3)),
700        });
701
702        let mut level1_deep = LnmpRecord::new();
703        level1_deep.add_field(LnmpField {
704            fid: 3,
705            value: LnmpValue::NestedRecord(Box::new(level2)),
706        });
707
708        // This should fail (depth 2 when encoding the deepest nested record)
709        let result = encoder.encode_nested_record(&level1_deep);
710        assert!(result.is_err());
711    }
712
713    #[test]
714    fn test_encode_size_limit_enforced_incrementally() {
715        let config = NestedEncoderConfig::new().with_max_record_size(Some(50));
716        let encoder = BinaryNestedEncoder::with_config(config);
717
718        let mut record = LnmpRecord::new();
719        // Add fields until we exceed the limit
720        for i in 0..20 {
721            record.add_field(LnmpField {
722                fid: i,
723                value: LnmpValue::String("test".to_string()),
724            });
725        }
726
727        let result = encoder.encode_nested_record(&record);
728        assert!(result.is_err());
729        match result {
730            Err(BinaryError::RecordSizeExceeded { .. }) => {}
731            _ => panic!("Expected RecordSizeExceeded error"),
732        }
733    }
734
735    #[test]
736    fn test_encode_canonical_ordering_at_all_levels() {
737        let encoder = BinaryNestedEncoder::new();
738
739        // Create nested structure with unsorted fields at each level
740        let mut inner = LnmpRecord::new();
741        inner.add_field(LnmpField {
742            fid: 10,
743            value: LnmpValue::Int(3),
744        });
745        inner.add_field(LnmpField {
746            fid: 2,
747            value: LnmpValue::Int(1),
748        });
749        inner.add_field(LnmpField {
750            fid: 5,
751            value: LnmpValue::Int(2),
752        });
753
754        let mut outer = LnmpRecord::new();
755        outer.add_field(LnmpField {
756            fid: 20,
757            value: LnmpValue::String("last".to_string()),
758        });
759        outer.add_field(LnmpField {
760            fid: 1,
761            value: LnmpValue::NestedRecord(Box::new(inner)),
762        });
763        outer.add_field(LnmpField {
764            fid: 15,
765            value: LnmpValue::String("middle".to_string()),
766        });
767
768        let result = encoder.encode_nested_record(&outer).unwrap();
769
770        // Verify outer level is sorted (FID 1 should come first)
771        assert_eq!(result[0], 0x06); // NestedRecord tag
772        assert_eq!(result[1], 0x03); // Field count = 3
773        assert_eq!(result[2], 0x01); // First FID = 1 (sorted)
774    }
775
776    #[test]
777    fn test_encode_empty_nested_records_at_multiple_levels() {
778        let encoder = BinaryNestedEncoder::new();
779
780        let inner = LnmpRecord::new(); // Empty
781        let mut outer = LnmpRecord::new();
782        outer.add_field(LnmpField {
783            fid: 1,
784            value: LnmpValue::NestedRecord(Box::new(inner)),
785        });
786
787        let result = encoder.encode_nested_record(&outer).unwrap();
788
789        assert_eq!(result[0], 0x06); // NestedRecord tag
790        assert_eq!(result[1], 0x01); // Field count = 1
791        assert_eq!(result[2], 0x01); // FID = 1
792        assert_eq!(result[3], 0x06); // Inner NestedRecord tag
793        assert_eq!(result[4], 0x00); // Inner field count = 0
794    }
795
796    #[test]
797    fn test_encode_empty_nested_arrays() {
798        let encoder = BinaryNestedEncoder::new();
799
800        let mut record = LnmpRecord::new();
801        record.add_field(LnmpField {
802            fid: 1,
803            value: LnmpValue::NestedArray(vec![]),
804        });
805
806        let result = encoder.encode_nested_record(&record).unwrap();
807
808        assert_eq!(result[0], 0x06); // NestedRecord tag
809        assert_eq!(result[1], 0x01); // Field count = 1
810        assert_eq!(result[2], 0x01); // FID = 1
811        assert_eq!(result[3], 0x07); // NestedArray tag
812        assert_eq!(result[4], 0x00); // Element count = 0
813    }
814
815    #[test]
816    fn test_encode_nested_array_with_canonical_ordering() {
817        let encoder = BinaryNestedEncoder::new();
818
819        // Create records with unsorted fields
820        let mut record1 = LnmpRecord::new();
821        record1.add_field(LnmpField {
822            fid: 10,
823            value: LnmpValue::Int(2),
824        });
825        record1.add_field(LnmpField {
826            fid: 5,
827            value: LnmpValue::Int(1),
828        });
829
830        let mut record2 = LnmpRecord::new();
831        record2.add_field(LnmpField {
832            fid: 20,
833            value: LnmpValue::Int(4),
834        });
835        record2.add_field(LnmpField {
836            fid: 15,
837            value: LnmpValue::Int(3),
838        });
839
840        let result = encoder.encode_nested_array(&[record1, record2]).unwrap();
841
842        // Verify array structure
843        assert_eq!(result[0], 0x07); // NestedArray tag
844        assert_eq!(result[1], 0x02); // Element count = 2
845
846        // First record should have sorted fields
847        assert_eq!(result[2], 0x06); // First record tag
848        assert_eq!(result[3], 0x02); // First record field count = 2
849        assert_eq!(result[4], 0x05); // First FID = 5 (sorted)
850    }
851
852    #[test]
853    fn test_encode_mixed_primitive_and_nested_fields() {
854        let encoder = BinaryNestedEncoder::new();
855
856        let mut inner = LnmpRecord::new();
857        inner.add_field(LnmpField {
858            fid: 1,
859            value: LnmpValue::String("inner".to_string()),
860        });
861
862        let mut outer = LnmpRecord::new();
863        outer.add_field(LnmpField {
864            fid: 1,
865            value: LnmpValue::Int(42),
866        });
867        outer.add_field(LnmpField {
868            fid: 2,
869            value: LnmpValue::NestedRecord(Box::new(inner)),
870        });
871        outer.add_field(LnmpField {
872            fid: 3,
873            value: LnmpValue::Bool(true),
874        });
875
876        let result = encoder.encode_nested_record(&outer).unwrap();
877
878        assert_eq!(result[0], 0x06); // NestedRecord tag
879        assert_eq!(result[1], 0x03); // Field count = 3
880    }
881
882    #[test]
883    fn test_encode_all_primitive_types_in_nested_record() {
884        let encoder = BinaryNestedEncoder::new();
885
886        let mut record = LnmpRecord::new();
887        record.add_field(LnmpField {
888            fid: 1,
889            value: LnmpValue::Int(-42),
890        });
891        record.add_field(LnmpField {
892            fid: 2,
893            value: LnmpValue::Float(3.14),
894        });
895        record.add_field(LnmpField {
896            fid: 3,
897            value: LnmpValue::Bool(false),
898        });
899        record.add_field(LnmpField {
900            fid: 4,
901            value: LnmpValue::String("test".to_string()),
902        });
903        record.add_field(LnmpField {
904            fid: 5,
905            value: LnmpValue::StringArray(vec!["a".to_string(), "b".to_string()]),
906        });
907
908        let result = encoder.encode_nested_record(&record).unwrap();
909
910        assert_eq!(result[0], 0x06); // NestedRecord tag
911        assert_eq!(result[1], 0x05); // Field count = 5
912    }
913}