1use super::entry::BinaryEntry;
12use super::error::BinaryError;
13use super::varint;
14use lnmp_core::{LnmpField, LnmpRecord};
15
16const VERSION_0_4: u8 = 0x04;
18
19#[derive(Debug, Clone, PartialEq)]
21pub struct BinaryFrame {
22 version: u8,
24 flags: u8,
26 entries: Vec<BinaryEntry>,
28}
29
30impl BinaryFrame {
31 pub fn new(entries: Vec<BinaryEntry>) -> Self {
37 Self {
38 version: VERSION_0_4,
39 flags: 0x00,
40 entries,
41 }
42 }
43
44 pub fn encode(&self) -> Vec<u8> {
52 let mut bytes = Vec::new();
53
54 bytes.push(self.version);
56
57 bytes.push(self.flags);
59
60 bytes.extend_from_slice(&varint::encode(self.entries.len() as i64));
62
63 for entry in &self.entries {
65 bytes.extend_from_slice(&entry.encode());
66 }
67
68 bytes
69 }
70
71 pub fn decode(bytes: &[u8]) -> Result<Self, BinaryError> {
81 Self::decode_with_options(bytes, true)
82 }
83
84 pub fn decode_allow_unsorted(bytes: &[u8]) -> Result<Self, BinaryError> {
86 Self::decode_with_options(bytes, false)
87 }
88
89 fn decode_with_options(bytes: &[u8], enforce_sorted: bool) -> Result<Self, BinaryError> {
90 let mut offset = 0;
91
92 if bytes.is_empty() {
94 return Err(BinaryError::UnexpectedEof {
95 expected: 1,
96 found: bytes.len(),
97 });
98 }
99 let version = bytes[offset];
100 offset += 1;
101
102 if version != VERSION_0_4 {
104 return Err(BinaryError::UnsupportedVersion {
105 found: version,
106 supported: vec![VERSION_0_4],
107 });
108 }
109
110 if bytes.len() < offset + 1 {
112 return Err(BinaryError::UnexpectedEof {
113 expected: offset + 1,
114 found: bytes.len(),
115 });
116 }
117 let flags = bytes[offset];
118 offset += 1;
119
120 let (entry_count, consumed) =
122 varint::decode(&bytes[offset..]).map_err(|_| BinaryError::InvalidVarInt {
123 reason: "Invalid entry count VarInt".to_string(),
124 })?;
125 offset += consumed;
126
127 if entry_count < 0 {
128 return Err(BinaryError::InvalidValue {
129 field_id: 0,
130 type_tag: 0,
131 reason: format!("Negative entry count: {}", entry_count),
132 });
133 }
134
135 let entry_count = entry_count as usize;
136 let mut entries = Vec::with_capacity(entry_count);
137
138 for _ in 0..entry_count {
140 let (entry, consumed) = BinaryEntry::decode(&bytes[offset..])?;
141 offset += consumed;
142 entries.push(entry);
143 }
144
145 if enforce_sorted {
146 let mut prev_fid: Option<u16> = None;
147 for entry in &entries {
148 if let Some(prev) = prev_fid {
149 if entry.fid < prev {
150 return Err(BinaryError::CanonicalViolation {
151 reason: format!(
152 "entries must be sorted by FID (saw {} after {})",
153 entry.fid, prev
154 ),
155 });
156 }
157 }
158 prev_fid = Some(entry.fid);
159 }
160 }
161
162 Ok(Self {
163 version,
164 flags,
165 entries,
166 })
167 }
168
169 pub fn to_record(&self) -> LnmpRecord {
171 let fields: Vec<LnmpField> = self.entries.iter().map(|entry| entry.to_field()).collect();
172
173 LnmpRecord::from_sorted_fields(fields)
174 }
175
176 pub fn from_record(record: &LnmpRecord) -> Result<Self, BinaryError> {
183 let sorted_fields = record.sorted_fields();
185
186 let mut entries = Vec::with_capacity(sorted_fields.len());
188 for field in sorted_fields {
189 entries.push(BinaryEntry::from_field(&field)?);
190 }
191
192 Ok(Self::new(entries))
193 }
194}
195
196#[cfg(test)]
197mod tests {
198 #![allow(clippy::approx_constant)]
199
200 use super::super::types::{BinaryValue, TypeTag};
201 use super::*;
202 use lnmp_core::LnmpValue;
203
204 #[test]
205 fn test_new_frame() {
206 let entries = vec![BinaryEntry {
207 fid: 1,
208 tag: TypeTag::Int,
209 value: BinaryValue::Int(42),
210 }];
211
212 let frame = BinaryFrame::new(entries.clone());
213 assert_eq!(frame.version, VERSION_0_4);
214 assert_eq!(frame.flags, 0x00);
215 assert_eq!(frame.entries, entries);
216 }
217
218 #[test]
219 fn test_encode_empty_frame() {
220 let frame = BinaryFrame::new(vec![]);
221 let bytes = frame.encode();
222
223 assert_eq!(bytes[0], 0x04);
225 assert_eq!(bytes[1], 0x00);
227 assert_eq!(bytes[2], 0x00);
229 assert_eq!(bytes.len(), 3);
230 }
231
232 #[test]
233 fn test_encode_single_entry() {
234 let entries = vec![BinaryEntry {
235 fid: 7,
236 tag: TypeTag::Bool,
237 value: BinaryValue::Bool(true),
238 }];
239
240 let frame = BinaryFrame::new(entries);
241 let bytes = frame.encode();
242
243 assert_eq!(bytes[0], 0x04);
245 assert_eq!(bytes[1], 0x00);
247 assert_eq!(bytes[2], 0x01);
249 assert!(bytes.len() > 3);
251 }
252
253 #[test]
254 fn test_encode_multiple_entries() {
255 let entries = vec![
256 BinaryEntry {
257 fid: 7,
258 tag: TypeTag::Bool,
259 value: BinaryValue::Bool(true),
260 },
261 BinaryEntry {
262 fid: 12,
263 tag: TypeTag::Int,
264 value: BinaryValue::Int(14532),
265 },
266 BinaryEntry {
267 fid: 23,
268 tag: TypeTag::StringArray,
269 value: BinaryValue::StringArray(vec!["admin".to_string(), "dev".to_string()]),
270 },
271 ];
272
273 let frame = BinaryFrame::new(entries);
274 let bytes = frame.encode();
275
276 assert_eq!(bytes[0], 0x04);
278 assert_eq!(bytes[1], 0x00);
280 assert_eq!(bytes[2], 0x03);
282 }
283
284 #[test]
285 fn test_decode_empty_frame() {
286 let frame = BinaryFrame::new(vec![]);
287 let bytes = frame.encode();
288 let decoded = BinaryFrame::decode(&bytes).unwrap();
289
290 assert_eq!(decoded, frame);
291 }
292
293 #[test]
294 fn test_decode_single_entry() {
295 let entries = vec![BinaryEntry {
296 fid: 1,
297 tag: TypeTag::String,
298 value: BinaryValue::String("hello".to_string()),
299 }];
300
301 let frame = BinaryFrame::new(entries);
302 let bytes = frame.encode();
303 let decoded = BinaryFrame::decode(&bytes).unwrap();
304
305 assert_eq!(decoded, frame);
306 }
307
308 #[test]
309 fn test_decode_multiple_entries() {
310 let entries = vec![
311 BinaryEntry {
312 fid: 1,
313 tag: TypeTag::Int,
314 value: BinaryValue::Int(-42),
315 },
316 BinaryEntry {
317 fid: 2,
318 tag: TypeTag::Float,
319 value: BinaryValue::Float(3.14),
320 },
321 BinaryEntry {
322 fid: 3,
323 tag: TypeTag::Bool,
324 value: BinaryValue::Bool(false),
325 },
326 ];
327
328 let frame = BinaryFrame::new(entries);
329 let bytes = frame.encode();
330 let decoded = BinaryFrame::decode(&bytes).unwrap();
331
332 assert_eq!(decoded, frame);
333 }
334
335 #[test]
336 fn test_decode_unsupported_version() {
337 let bytes = vec![0x99, 0x00, 0x00]; let result = BinaryFrame::decode(&bytes);
339
340 assert!(matches!(
341 result,
342 Err(BinaryError::UnsupportedVersion { found: 0x99, .. })
343 ));
344 }
345
346 #[test]
347 fn test_decode_insufficient_data_version() {
348 let bytes = vec![];
349 let result = BinaryFrame::decode(&bytes);
350
351 assert!(matches!(result, Err(BinaryError::UnexpectedEof { .. })));
352 }
353
354 #[test]
355 fn test_decode_insufficient_data_flags() {
356 let bytes = vec![0x04]; let result = BinaryFrame::decode(&bytes);
358
359 assert!(matches!(result, Err(BinaryError::UnexpectedEof { .. })));
360 }
361
362 #[test]
363 fn test_decode_invalid_entry_count_varint() {
364 let bytes = vec![0x04, 0x00, 0x80]; let result = BinaryFrame::decode(&bytes);
366
367 assert!(matches!(result, Err(BinaryError::InvalidVarInt { .. })));
368 }
369
370 #[test]
371 fn test_decode_insufficient_entry_data() {
372 let bytes = vec![0x04, 0x00, 0x01]; let result = BinaryFrame::decode(&bytes);
374
375 assert!(matches!(result, Err(BinaryError::UnexpectedEof { .. })));
376 }
377
378 #[test]
379 fn test_to_record() {
380 let entries = vec![
381 BinaryEntry {
382 fid: 7,
383 tag: TypeTag::Bool,
384 value: BinaryValue::Bool(true),
385 },
386 BinaryEntry {
387 fid: 12,
388 tag: TypeTag::Int,
389 value: BinaryValue::Int(14532),
390 },
391 ];
392
393 let frame = BinaryFrame::new(entries);
394 let record = frame.to_record();
395
396 assert_eq!(record.fields().len(), 2);
397 assert_eq!(record.get_field(7).unwrap().value, LnmpValue::Bool(true));
398 assert_eq!(record.get_field(12).unwrap().value, LnmpValue::Int(14532));
399 }
400
401 #[test]
402 fn test_from_record() {
403 let mut record = LnmpRecord::new();
404 record.add_field(LnmpField {
405 fid: 12,
406 value: LnmpValue::Int(14532),
407 });
408 record.add_field(LnmpField {
409 fid: 7,
410 value: LnmpValue::Bool(true),
411 });
412
413 let frame = BinaryFrame::from_record(&record).unwrap();
414
415 assert_eq!(frame.entries.len(), 2);
417 assert_eq!(frame.entries[0].fid, 7);
418 assert_eq!(frame.entries[1].fid, 12);
419 }
420
421 #[test]
422 fn test_from_record_sorts_fields() {
423 let mut record = LnmpRecord::new();
424 record.add_field(LnmpField {
425 fid: 23,
426 value: LnmpValue::StringArray(vec!["admin".to_string()]),
427 });
428 record.add_field(LnmpField {
429 fid: 7,
430 value: LnmpValue::Bool(true),
431 });
432 record.add_field(LnmpField {
433 fid: 12,
434 value: LnmpValue::Int(14532),
435 });
436
437 let frame = BinaryFrame::from_record(&record).unwrap();
438
439 assert_eq!(frame.entries.len(), 3);
441 assert_eq!(frame.entries[0].fid, 7);
442 assert_eq!(frame.entries[1].fid, 12);
443 assert_eq!(frame.entries[2].fid, 23);
444 }
445
446 #[test]
447 fn test_roundtrip_record_to_frame_to_record() {
448 let mut record = LnmpRecord::new();
449 record.add_field(LnmpField {
450 fid: 1,
451 value: LnmpValue::Int(-42),
452 });
453 record.add_field(LnmpField {
454 fid: 2,
455 value: LnmpValue::Float(3.14159),
456 });
457 record.add_field(LnmpField {
458 fid: 3,
459 value: LnmpValue::Bool(true),
460 });
461 record.add_field(LnmpField {
462 fid: 4,
463 value: LnmpValue::String("hello".to_string()),
464 });
465 record.add_field(LnmpField {
466 fid: 5,
467 value: LnmpValue::StringArray(vec!["a".to_string(), "b".to_string()]),
468 });
469
470 let frame = BinaryFrame::from_record(&record).unwrap();
471 let decoded_record = frame.to_record();
472
473 assert_eq!(decoded_record.fields().len(), 5);
475 assert_eq!(
476 decoded_record.get_field(1).unwrap().value,
477 LnmpValue::Int(-42)
478 );
479 assert_eq!(
480 decoded_record.get_field(2).unwrap().value,
481 LnmpValue::Float(3.14159)
482 );
483 assert_eq!(
484 decoded_record.get_field(3).unwrap().value,
485 LnmpValue::Bool(true)
486 );
487 assert_eq!(
488 decoded_record.get_field(4).unwrap().value,
489 LnmpValue::String("hello".to_string())
490 );
491 assert_eq!(
492 decoded_record.get_field(5).unwrap().value,
493 LnmpValue::StringArray(vec!["a".to_string(), "b".to_string()])
494 );
495 }
496
497 #[test]
498 fn test_roundtrip_frame_encode_decode() {
499 let entries = vec![
500 BinaryEntry {
501 fid: 1,
502 tag: TypeTag::Int,
503 value: BinaryValue::Int(100),
504 },
505 BinaryEntry {
506 fid: 2,
507 tag: TypeTag::String,
508 value: BinaryValue::String("test".to_string()),
509 },
510 ];
511
512 let frame = BinaryFrame::new(entries);
513 let bytes = frame.encode();
514 let decoded = BinaryFrame::decode(&bytes).unwrap();
515
516 assert_eq!(decoded, frame);
517 }
518
519 #[test]
520 fn test_flags_preserved() {
521 let frame = BinaryFrame::new(vec![]);
522 let bytes = frame.encode();
523 let decoded = BinaryFrame::decode(&bytes).unwrap();
524
525 assert_eq!(decoded.flags, 0x00);
526 }
527
528 #[test]
529 fn test_empty_record() {
530 let record = LnmpRecord::new();
531 let frame = BinaryFrame::from_record(&record).unwrap();
532
533 assert_eq!(frame.entries.len(), 0);
534
535 let bytes = frame.encode();
536 let decoded = BinaryFrame::decode(&bytes).unwrap();
537 let decoded_record = decoded.to_record();
538
539 assert_eq!(decoded_record.fields().len(), 0);
540 }
541
542 #[test]
543 fn test_from_record_with_all_types() {
544 let mut record = LnmpRecord::new();
545 record.add_field(LnmpField {
546 fid: 1,
547 value: LnmpValue::Int(42),
548 });
549 record.add_field(LnmpField {
550 fid: 2,
551 value: LnmpValue::Float(2.718),
552 });
553 record.add_field(LnmpField {
554 fid: 3,
555 value: LnmpValue::Bool(false),
556 });
557 record.add_field(LnmpField {
558 fid: 4,
559 value: LnmpValue::String("world".to_string()),
560 });
561 record.add_field(LnmpField {
562 fid: 5,
563 value: LnmpValue::StringArray(vec!["x".to_string(), "y".to_string()]),
564 });
565
566 let frame = BinaryFrame::from_record(&record).unwrap();
567 assert_eq!(frame.entries.len(), 5);
568
569 let bytes = frame.encode();
570 let decoded = BinaryFrame::decode(&bytes).unwrap();
571 let decoded_record = decoded.to_record();
572
573 assert_eq!(decoded_record.fields().len(), 5);
574 }
575
576 #[test]
577 fn test_version_validation() {
578 let valid_bytes = vec![0x04, 0x00, 0x00];
580 assert!(BinaryFrame::decode(&valid_bytes).is_ok());
581
582 let invalid_versions = vec![0x00, 0x01, 0x02, 0x03, 0x05, 0xFF];
583 for version in invalid_versions {
584 let bytes = vec![version, 0x00, 0x00];
585 let result = BinaryFrame::decode(&bytes);
586 assert!(matches!(
587 result,
588 Err(BinaryError::UnsupportedVersion { .. })
589 ));
590 }
591 }
592}