summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorzirkonya <zirkonya@iridium.lan>2026-07-26 22:22:32 +0200
committerzirkonya <zirkonya@iridium.lan>2026-07-26 22:22:32 +0200
commitda20363883b00aaf56b6ba068465d3e9bdf9fedf (patch)
tree792c6d0692392baf5ac380bafd7ea4a66b3346b6 /tests
parent08c8e9d37f3e0f9a82c3792c1927967631de73cd (diff)
Add some prefixe
Diffstat (limited to 'tests')
-rw-r--r--tests/count_prefix.rs305
-rw-r--r--tests/encode_decode.rs589
-rw-r--r--tests/len_prefix.rs310
-rw-r--r--tests/tmp/todo.md356
4 files changed, 1560 insertions, 0 deletions
diff --git a/tests/count_prefix.rs b/tests/count_prefix.rs
new file mode 100644
index 0000000..ab25e30
--- /dev/null
+++ b/tests/count_prefix.rs
@@ -0,0 +1,305 @@
+use zr_protocol::{
+ codec::{decode::Decode, encode::Encode},
+ types::prefix::count::CountPrefix,
+};
+
+fn encode_to_bytes<T: Encode>(value: T) -> Vec<u8> {
+ let mut buf = Vec::new();
+ value.encode(&mut buf).unwrap();
+ buf
+}
+
+// ────────────────────── Vecteur vide ────────────
+
+#[test]
+fn empty_vec_u8_with_u8_count() {
+ let cp = CountPrefix::<u8, u8, Vec<u8>>::new(vec![]);
+ let buf = encode_to_bytes(cp);
+ assert_eq!(buf, vec![0x00]);
+}
+
+#[test]
+fn empty_vec_u32_with_u16_count() {
+ let cp = CountPrefix::<u32, u16, Vec<u32>>::new(vec![]);
+ let buf = encode_to_bytes(cp);
+ assert_eq!(buf, vec![0x00, 0x00]);
+}
+
+#[test]
+fn empty_vec_u8_with_u32_count() {
+ let cp = CountPrefix::<u8, u32, Vec<u8>>::new(vec![]);
+ let buf = encode_to_bytes(cp);
+ assert_eq!(buf, vec![0x00, 0x00, 0x00, 0x00]);
+}
+
+// ────────────────────── Un seul élément ─────────
+
+#[test]
+fn single_u8_u8_count() {
+ let cp = CountPrefix::<u8, u8, Vec<u8>>::new(vec![42]);
+ let buf = encode_to_bytes(cp);
+ assert_eq!(buf, vec![0x01, 0x2A]);
+}
+
+#[test]
+fn single_u32_u8_count() {
+ let cp = CountPrefix::<u32, u8, Vec<u32>>::new(vec![0x01020304]);
+ let buf = encode_to_bytes(cp);
+ assert_eq!(buf, vec![0x01, 0x01, 0x02, 0x03, 0x04]);
+}
+
+// ────────────────────── Plusieurs éléments ──────
+
+#[test]
+fn multiple_u8_u8_count() {
+ let cp = CountPrefix::<u8, u8, Vec<u8>>::new(vec![1, 2, 3]);
+ let buf = encode_to_bytes(cp);
+ assert_eq!(buf, vec![0x03, 0x01, 0x02, 0x03]);
+}
+
+#[test]
+fn multiple_u16_u8_count() {
+ let cp = CountPrefix::<u16, u8, Vec<u16>>::new(vec![256, 512, 1024]);
+ let buf = encode_to_bytes(cp);
+ assert_eq!(
+ buf,
+ vec![
+ 0x03, // count = 3
+ 0x01, 0x00, // 256
+ 0x02, 0x00, // 512
+ 0x04, 0x00, // 1024
+ ]
+ );
+}
+
+#[test]
+fn multiple_u32_u16_count() {
+ let cp = CountPrefix::<u32, u16, Vec<u32>>::new(vec![100, 200, 300]);
+ let buf = encode_to_bytes(cp);
+ assert_eq!(
+ buf,
+ vec![
+ 0x00, 0x03, // count = 3 (u16)
+ 0x00, 0x00, 0x00, 0x64, // 100
+ 0x00, 0x00, 0x00, 0xC8, // 200
+ 0x00, 0x00, 0x01, 0x2C, // 300
+ ]
+ );
+}
+
+// ────────────────────── Valeurs extrêmes ────────
+
+#[test]
+fn u8_count_max_255() {
+ let data: Vec<u8> = (0..255).collect();
+ let cp = CountPrefix::<u8, u8, Vec<u8>>::new(data.clone());
+ let buf = encode_to_bytes(cp);
+ assert_eq!(buf[0], 0xFF);
+ assert_eq!(buf.len(), 1 + 255);
+}
+
+#[test]
+fn large_u32_items_u16_count() {
+ let data: Vec<u32> = vec![u32::MAX, 0, u32::MIN + 1];
+ let cp = CountPrefix::<u32, u16, Vec<u32>>::new(data);
+ let buf = encode_to_bytes(cp);
+ assert_eq!(buf.len(), 2 + 3 * 4);
+ assert_eq!(&buf[0..2], &[0x00, 0x03]); // count = 3
+}
+
+// ────────────────────── Roundtrip ───────────────
+
+#[test]
+fn roundtrip_u8_u8() {
+ let data = vec![10, 20, 30, 40, 50];
+ let cp = CountPrefix::<u8, u8, Vec<u8>>::new(data.clone());
+ let buf = encode_to_bytes(cp);
+
+ let mut reader = &buf[..];
+ let decoded = CountPrefix::<u8, u8, Vec<u8>>::decode(&mut reader).unwrap();
+ assert_eq!(*decoded.as_ref(), data);
+}
+
+#[test]
+fn roundtrip_u32_u16() {
+ let data = vec![1, 2, 3, 4, 5];
+ let cp = CountPrefix::<u32, u16, Vec<u32>>::new(data.clone());
+ let buf = encode_to_bytes(cp);
+
+ let mut reader = &buf[..];
+ let decoded = CountPrefix::<u32, u16, Vec<u32>>::decode(&mut reader).unwrap();
+ assert_eq!(*decoded.as_ref(), data);
+}
+
+#[test]
+fn roundtrip_empty() {
+ let cp = CountPrefix::<u32, u8, Vec<u32>>::new(vec![]);
+ let buf = encode_to_bytes(cp);
+
+ let mut reader = &buf[..];
+ let decoded = CountPrefix::<u32, u8, Vec<u32>>::decode(&mut reader).unwrap();
+ assert!(decoded.as_ref().is_empty());
+}
+
+#[test]
+fn roundtrip_single_element() {
+ let cp = CountPrefix::<u64, u8, Vec<u64>>::new(vec![u64::MAX]);
+ let buf = encode_to_bytes(cp);
+
+ let mut reader = &buf[..];
+ let decoded = CountPrefix::<u64, u8, Vec<u64>>::decode(&mut reader).unwrap();
+ assert_eq!(*decoded.as_ref(), vec![u64::MAX]);
+}
+
+#[test]
+fn roundtrip_negative_i32() {
+ let data = vec![-1, -100, 0, 100, 1];
+ let cp = CountPrefix::<i32, u16, Vec<i32>>::new(data.clone());
+ let buf = encode_to_bytes(cp);
+
+ let mut reader = &buf[..];
+ let decoded = CountPrefix::<i32, u16, Vec<i32>>::decode(&mut reader).unwrap();
+ assert_eq!(*decoded.as_ref(), data);
+}
+
+#[test]
+fn roundtrip_f64() {
+ let data = vec![0.0, 3.14, -2.71, f64::INFINITY, f64::NEG_INFINITY];
+ let cp = CountPrefix::<f64, u8, Vec<f64>>::new(data.clone());
+ let buf = encode_to_bytes(cp);
+
+ let mut reader = &buf[..];
+ let decoded = CountPrefix::<f64, u8, Vec<f64>>::decode(&mut reader).unwrap();
+ let decoded_data = decoded.as_ref().clone();
+ assert_eq!(decoded_data.len(), data.len());
+ for (a, b) in decoded_data.iter().zip(data.iter()) {
+ if a.is_nan() {
+ assert!(b.is_nan());
+ } else {
+ assert_eq!(a, b);
+ }
+ }
+}
+
+#[test]
+fn roundtrip_bool() {
+ let data = vec![true, false, true, true, false];
+ let cp = CountPrefix::<bool, u8, Vec<bool>>::new(data.clone());
+ let buf = encode_to_bytes(cp);
+
+ let mut reader = &buf[..];
+ let decoded = CountPrefix::<bool, u8, Vec<bool>>::decode(&mut reader).unwrap();
+ assert_eq!(*decoded.as_ref(), data);
+}
+
+#[test]
+fn roundtrip_large_dataset() {
+ let data: Vec<u32> = (0..1000).collect();
+ let cp = CountPrefix::<u32, u16, Vec<u32>>::new(data.clone());
+ let buf = encode_to_bytes(cp);
+
+ let mut reader = &buf[..];
+ let decoded = CountPrefix::<u32, u16, Vec<u32>>::decode(&mut reader).unwrap();
+ assert_eq!(*decoded.as_ref(), data);
+}
+
+// ────────────────────── Imbrication ─────────────
+
+#[test]
+fn nested_count_prefix() {
+ let inner1 = CountPrefix::<u8, u8, Vec<u8>>::new(vec![1, 2, 3]);
+ let inner2 = CountPrefix::<u8, u8, Vec<u8>>::new(vec![4, 5]);
+ let outer =
+ CountPrefix::<CountPrefix<u8, u8, Vec<u8>>, u8, Vec<CountPrefix<u8, u8, Vec<u8>>>>::new(
+ vec![inner1, inner2],
+ );
+
+ let buf = encode_to_bytes(outer);
+
+ let mut reader = &buf[..];
+ let decoded_outer =
+ CountPrefix::<CountPrefix<u8, u8, Vec<u8>>, u8, Vec<CountPrefix<u8, u8, Vec<u8>>>>::decode(
+ &mut reader,
+ )
+ .unwrap();
+
+ let outer_data: Vec<_> = decoded_outer
+ .as_ref()
+ .iter()
+ .map(|c| c.as_ref().clone())
+ .collect();
+ assert_eq!(outer_data.len(), 2);
+ assert_eq!(outer_data[0], vec![1, 2, 3]);
+ assert_eq!(outer_data[1], vec![4, 5]);
+}
+
+// ────────────────────── Erreurs de décodage ─────
+
+#[test]
+fn decode_empty_buffer_fails() {
+ let result = CountPrefix::<u8, u8, Vec<u8>>::decode(&mut &[][..]);
+ assert!(result.is_err());
+}
+
+#[test]
+fn decode_truncated_data_fails() {
+ let cp = CountPrefix::<u32, u8, Vec<u32>>::new(vec![1, 2, 3]);
+ let buf = encode_to_bytes(cp);
+ let truncated = &buf[..buf.len() - 1];
+ let result = CountPrefix::<u32, u8, Vec<u32>>::decode(&mut &truncated[..]);
+ assert!(result.is_err());
+}
+
+#[test]
+fn decode_wrong_count_fails() {
+ let cp = CountPrefix::<u32, u8, Vec<u32>>::new(vec![1, 2]);
+ let mut buf = encode_to_bytes(cp);
+ buf[0] = 0x05;
+ let result = CountPrefix::<u32, u8, Vec<u32>>::decode(&mut &buf[..]);
+ assert!(result.is_err());
+}
+
+// ────────────────────── AsRef / AsMut ───────────
+
+#[test]
+fn as_ref_returns_inner_data() {
+ let data = vec![10u32, 20, 30];
+ let cp = CountPrefix::<u32, u8, Vec<u32>>::new(data.clone());
+ assert_eq!(*cp.as_ref(), data);
+}
+
+#[test]
+fn as_mut_allows_mutation() {
+ let mut cp = CountPrefix::<u32, u8, Vec<u32>>::new(vec![1, 2, 3]);
+ cp.as_mut().push(4);
+ assert_eq!(*cp.as_ref(), vec![1, 2, 3, 4]);
+}
+
+// ────────────────────── Counts byte exactitude ──
+
+#[test]
+fn u8_count_header() {
+ let cp = CountPrefix::<u8, u8, Vec<u8>>::new(vec![1; 10]);
+ let buf = encode_to_bytes(cp);
+ assert_eq!(buf[0], 10);
+ assert_eq!(buf.len(), 1 + 10);
+}
+
+#[test]
+fn u16_count_header() {
+ let cp = CountPrefix::<u8, u16, Vec<u8>>::new(vec![1; 300]);
+ let buf = encode_to_bytes(cp);
+ assert_eq!(buf[0], 0x01);
+ assert_eq!(buf[1], 0x2C);
+ assert_eq!(buf.len(), 2 + 300);
+}
+
+#[test]
+fn u32_count_header() {
+ let data: Vec<u8> = vec![0xAA; 300];
+ let len = data.len();
+ let cp = CountPrefix::<u8, u32, Vec<u8>>::new(data);
+ let buf = encode_to_bytes(cp);
+ assert_eq!(buf.len(), 4 + len);
+ assert_eq!(&buf[0..4], &[0x00, 0x00, 0x01, 0x2C]);
+}
diff --git a/tests/encode_decode.rs b/tests/encode_decode.rs
new file mode 100644
index 0000000..92a1e71
--- /dev/null
+++ b/tests/encode_decode.rs
@@ -0,0 +1,589 @@
+use std::sync::Arc;
+use zr_protocol::codec::{decode::Decode, encode::Encode};
+
+fn roundtrip<T: Encode + Decode + PartialEq + Clone + std::fmt::Debug>(value: T) {
+ let mut buf = Vec::new();
+ let written = value.clone().encode(&mut buf).expect("encode failed");
+ let decoded = T::decode(&mut &buf[..]).expect("decode failed");
+ assert_eq!(written, buf.len(), "encode returned wrong byte count");
+ assert_eq!(decoded, value, "roundtrip value mismatch");
+}
+
+// ────────────────────── u8 ──────────────────────
+
+#[test]
+fn u8_zero() {
+ roundtrip(0u8);
+}
+
+#[test]
+fn u8_one() {
+ roundtrip(1u8);
+}
+
+#[test]
+fn u8_max() {
+ roundtrip(u8::MAX);
+}
+
+#[test]
+fn u8_arbitrary() {
+ roundtrip(42u8);
+}
+
+#[test]
+fn u8_bytes() {
+ let mut buf = Vec::new();
+ 255u8.encode(&mut buf).unwrap();
+ assert_eq!(buf, vec![255]);
+}
+
+// ────────────────────── u16 ─────────────────────
+
+#[test]
+fn u16_zero() {
+ roundtrip(0u16);
+}
+
+#[test]
+fn u16_one() {
+ roundtrip(1u16);
+}
+
+#[test]
+fn u16_max() {
+ roundtrip(u16::MAX);
+}
+
+#[test]
+fn u16_big_endian_bytes() {
+ let mut buf = Vec::new();
+ 256u16.encode(&mut buf).unwrap();
+ assert_eq!(buf, vec![0x01, 0x00]);
+}
+
+#[test]
+fn u16_one_big_endian_bytes() {
+ let mut buf = Vec::new();
+ 1u16.encode(&mut buf).unwrap();
+ assert_eq!(buf, vec![0x00, 0x01]);
+}
+
+#[test]
+fn u16_arbitrary() {
+ roundtrip(1337u16);
+}
+
+// ────────────────────── u32 ─────────────────────
+
+#[test]
+fn u32_zero() {
+ roundtrip(0u32);
+}
+
+#[test]
+fn u32_max() {
+ roundtrip(u32::MAX);
+}
+
+#[test]
+fn u32_big_endian_bytes() {
+ let mut buf = Vec::new();
+ 0x01020304u32.encode(&mut buf).unwrap();
+ assert_eq!(buf, vec![0x01, 0x02, 0x03, 0x04]);
+}
+
+#[test]
+fn u32_arbitrary() {
+ roundtrip(429496729u32);
+}
+
+// ────────────────────── u64 ─────────────────────
+
+#[test]
+fn u64_zero() {
+ roundtrip(0u64);
+}
+
+#[test]
+fn u64_max() {
+ roundtrip(u64::MAX);
+}
+
+#[test]
+fn u64_big_endian_bytes() {
+ let mut buf = Vec::new();
+ 0x0102030405060708u64.encode(&mut buf).unwrap();
+ assert_eq!(buf, vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]);
+}
+
+#[test]
+fn u64_arbitrary() {
+ roundtrip(999_999_999_999u64);
+}
+
+// ────────────────────── u128 ────────────────────
+
+#[test]
+fn u128_zero() {
+ roundtrip(0u128);
+}
+
+#[test]
+fn u128_max() {
+ roundtrip(u128::MAX);
+}
+
+#[test]
+fn u128_arbitrary() {
+ roundtrip(170141183460469231731687303715884105727u128);
+}
+
+// ────────────────────── usize ───────────────────
+
+#[test]
+fn usize_zero() {
+ roundtrip(0usize);
+}
+
+#[test]
+fn usize_max() {
+ roundtrip(usize::MAX);
+}
+
+#[test]
+fn usize_arbitrary() {
+ roundtrip(42usize);
+}
+
+// ────────────────────── i8 ──────────────────────
+
+#[test]
+fn i8_zero() {
+ roundtrip(0i8);
+}
+
+#[test]
+fn i8_positive() {
+ roundtrip(42i8);
+}
+
+#[test]
+fn i8_negative() {
+ roundtrip(-42i8);
+}
+
+#[test]
+fn i8_min() {
+ roundtrip(i8::MIN);
+}
+
+#[test]
+fn i8_max() {
+ roundtrip(i8::MAX);
+}
+
+#[test]
+fn i8_negative_bytes() {
+ let mut buf = Vec::new();
+ (-1i8).encode(&mut buf).unwrap();
+ assert_eq!(buf, vec![0xFF]);
+}
+
+// ────────────────────── i16 ─────────────────────
+
+#[test]
+fn i16_zero() {
+ roundtrip(0i16);
+}
+
+#[test]
+fn i16_positive() {
+ roundtrip(1234i16);
+}
+
+#[test]
+fn i16_negative() {
+ roundtrip(-1234i16);
+}
+
+#[test]
+fn i16_min() {
+ roundtrip(i16::MIN);
+}
+
+#[test]
+fn i16_max() {
+ roundtrip(i16::MAX);
+}
+
+#[test]
+fn i16_big_endian_negative() {
+ let mut buf = Vec::new();
+ (-1i16).encode(&mut buf).unwrap();
+ assert_eq!(buf, vec![0xFF, 0xFF]);
+}
+
+#[test]
+fn i16_negative_bytes() {
+ let mut buf = Vec::new();
+ (-256i16).encode(&mut buf).unwrap();
+ assert_eq!(buf, vec![0xFF, 0x00]);
+}
+
+// ────────────────────── i32 ─────────────────────
+
+#[test]
+fn i32_zero() {
+ roundtrip(0i32);
+}
+
+#[test]
+fn i32_positive() {
+ roundtrip(100000i32);
+}
+
+#[test]
+fn i32_negative() {
+ roundtrip(-100000i32);
+}
+
+#[test]
+fn i32_min() {
+ roundtrip(i32::MIN);
+}
+
+#[test]
+fn i32_max() {
+ roundtrip(i32::MAX);
+}
+
+// ────────────────────── i64 ─────────────────────
+
+#[test]
+fn i64_zero() {
+ roundtrip(0i64);
+}
+
+#[test]
+fn i64_positive() {
+ roundtrip(9_999_999_999i64);
+}
+
+#[test]
+fn i64_negative() {
+ roundtrip(-9_999_999_999i64);
+}
+
+#[test]
+fn i64_min() {
+ roundtrip(i64::MIN);
+}
+
+#[test]
+fn i64_max() {
+ roundtrip(i64::MAX);
+}
+
+// ────────────────────── i128 ────────────────────
+
+#[test]
+fn i128_zero() {
+ roundtrip(0i128);
+}
+
+#[test]
+fn i128_min() {
+ roundtrip(i128::MIN);
+}
+
+#[test]
+fn i128_max() {
+ roundtrip(i128::MAX);
+}
+
+#[test]
+fn i128_negative() {
+ roundtrip(-42i128);
+}
+
+// ────────────────────── isize ───────────────────
+
+#[test]
+fn isize_zero() {
+ roundtrip(0isize);
+}
+
+#[test]
+fn isize_positive() {
+ roundtrip(42isize);
+}
+
+#[test]
+fn isize_negative() {
+ roundtrip(-42isize);
+}
+
+// ────────────────────── f32 ─────────────────────
+
+#[test]
+fn f32_zero() {
+ roundtrip(0.0f32);
+}
+
+#[test]
+fn f32_positive() {
+ roundtrip(3.14f32);
+}
+
+#[test]
+fn f32_negative() {
+ roundtrip(-2.71f32);
+}
+
+#[test]
+fn f32_infinity() {
+ roundtrip(f32::INFINITY);
+}
+
+#[test]
+fn f32_neg_infinity() {
+ roundtrip(f32::NEG_INFINITY);
+}
+
+#[test]
+fn f32_nan() {
+ let mut buf = Vec::new();
+ f32::NAN.encode(&mut buf).unwrap();
+ let decoded = f32::decode(&mut &buf[..]).unwrap();
+ assert!(decoded.is_nan(), "NaN roundtrip failed");
+}
+
+#[test]
+fn f32_big_endian_bytes() {
+ let mut buf = Vec::new();
+ 1.0f32.encode(&mut buf).unwrap();
+ assert_eq!(buf, vec![0x3F, 0x80, 0x00, 0x00]);
+}
+
+// ────────────────────── f64 ─────────────────────
+
+#[test]
+fn f64_zero() {
+ roundtrip(0.0f64);
+}
+
+#[test]
+fn f64_positive() {
+ roundtrip(3.141592653589793f64);
+}
+
+#[test]
+fn f64_negative() {
+ roundtrip(-2.718281828459045f64);
+}
+
+#[test]
+fn f64_infinity() {
+ roundtrip(f64::INFINITY);
+}
+
+#[test]
+fn f64_neg_infinity() {
+ roundtrip(f64::NEG_INFINITY);
+}
+
+#[test]
+fn f64_nan() {
+ let mut buf = Vec::new();
+ f64::NAN.encode(&mut buf).unwrap();
+ let decoded = f64::decode(&mut &buf[..]).unwrap();
+ assert!(decoded.is_nan(), "NaN roundtrip failed");
+}
+
+#[test]
+fn f64_big_endian_bytes() {
+ let mut buf = Vec::new();
+ 1.0f64.encode(&mut buf).unwrap();
+ assert_eq!(buf, vec![0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
+}
+
+// ────────────────────── bool ────────────────────
+
+#[test]
+fn bool_true() {
+ roundtrip(true);
+}
+
+#[test]
+fn bool_false() {
+ roundtrip(false);
+}
+
+#[test]
+fn bool_true_byte() {
+ let mut buf = Vec::new();
+ true.encode(&mut buf).unwrap();
+ assert_eq!(buf, vec![1]);
+}
+
+#[test]
+fn bool_false_byte() {
+ let mut buf = Vec::new();
+ false.encode(&mut buf).unwrap();
+ assert_eq!(buf, vec![0]);
+}
+
+#[test]
+fn bool_non_zero_is_true() {
+ let decoded = bool::decode(&mut &[0x42u8][..]).unwrap();
+ assert!(decoded, "any non-zero byte should decode as true");
+}
+
+// ────────────────────── [u8; S] ─────────────────
+
+#[test]
+fn array_1_byte() {
+ roundtrip([0xABu8; 1]);
+}
+
+#[test]
+fn array_4_bytes() {
+ roundtrip([0x01u8, 0x02, 0x03, 0x04]);
+}
+
+#[test]
+fn array_32_bytes() {
+ let data: [u8; 32] = std::array::from_fn(|i| i as u8);
+ roundtrip(data);
+}
+
+#[test]
+fn array_zeroes() {
+ roundtrip([0u8; 16]);
+}
+
+#[test]
+fn array_256_bytes() {
+ let data: [u8; 256] = std::array::from_fn(|i| (i % 256) as u8);
+ roundtrip(data);
+}
+
+// ────────────────────── Arc<[u8]> ───────────────
+
+#[test]
+fn arc_slice_simple() {
+ roundtrip(Arc::<[u8]>::from(vec![1, 2, 3]));
+}
+
+#[test]
+fn arc_slice_empty() {
+ roundtrip(Arc::<[u8]>::from(Vec::<u8>::new()));
+}
+
+#[test]
+fn arc_slice_large() {
+ let data: Vec<u8> = (0..1024).map(|i| (i % 256) as u8).collect();
+ roundtrip(Arc::<[u8]>::from(data));
+}
+
+// ────────────────────── Retour de bytes written ─
+
+#[test]
+fn encode_returns_correct_byte_count_u32() {
+ let mut buf = Vec::new();
+ let written = 42u32.encode(&mut buf).unwrap();
+ assert_eq!(written, 4);
+}
+
+#[test]
+fn encode_returns_correct_byte_count_bool() {
+ let mut buf = Vec::new();
+ let written = true.encode(&mut buf).unwrap();
+ assert_eq!(written, 1);
+}
+
+#[test]
+fn encode_returns_correct_byte_count_u128() {
+ let mut buf = Vec::new();
+ let written = 123u128.encode(&mut buf).unwrap();
+ assert_eq!(written, 16);
+}
+
+#[test]
+fn encode_returns_correct_byte_count_array() {
+ let mut buf = Vec::new();
+ let written = [1u8, 2, 3, 4, 5].encode(&mut buf).unwrap();
+ assert_eq!(written, 5);
+}
+
+// ────────────────────── Buffer trop court ───────
+
+#[test]
+fn decode_u16_from_empty_buffer_fails() {
+ let result = u16::decode(&mut &[][..]);
+ assert!(result.is_err(), "decoding u16 from empty buffer should fail");
+}
+
+#[test]
+fn decode_u32_from_too_short_buffer_fails() {
+ let result = u32::decode(&mut &[0x01, 0x02][..]);
+ assert!(result.is_err(), "decoding u32 from 2-byte buffer should fail");
+}
+
+#[test]
+fn decode_bool_from_empty_buffer_fails() {
+ let result = bool::decode(&mut &[][..]);
+ assert!(result.is_err(), "decoding bool from empty buffer should fail");
+}
+
+#[test]
+fn decode_array_from_short_buffer_fails() {
+ let result = <[u8; 4]>::decode(&mut &[0x01, 0x02][..]);
+ assert!(
+ result.is_err(),
+ "decoding [u8;4] from 2-byte buffer should fail"
+ );
+}
+
+// ────────────────────── Sérialisation exacte ────
+
+#[test]
+fn u16_258_bytes() {
+ let mut buf = Vec::new();
+ 258u16.encode(&mut buf).unwrap();
+ assert_eq!(buf, vec![0x01, 0x02]);
+}
+
+#[test]
+fn u32_16909060_bytes() {
+ let mut buf = Vec::new();
+ 0x01020304u32.encode(&mut buf).unwrap();
+ assert_eq!(buf, vec![1, 2, 3, 4]);
+}
+
+#[test]
+fn i16_minus_256_bytes() {
+ let mut buf = Vec::new();
+ (-256i16).encode(&mut buf).unwrap();
+ assert_eq!(buf, vec![0xFF, 0x00]);
+}
+
+// ────────────────────── Sérialisation multiple ──
+
+#[test]
+fn multiple_values_sequential_encode() {
+ let mut buf = Vec::new();
+ 1u8.encode(&mut buf).unwrap();
+ 2u16.encode(&mut buf).unwrap();
+ 3u32.encode(&mut buf).unwrap();
+ true.encode(&mut buf).unwrap();
+
+ assert_eq!(buf.len(), 1 + 2 + 4 + 1);
+
+ let mut reader = &buf[..];
+ assert_eq!(u8::decode(&mut reader).unwrap(), 1);
+ assert_eq!(u16::decode(&mut reader).unwrap(), 2);
+ assert_eq!(u32::decode(&mut reader).unwrap(), 3);
+ assert!(bool::decode(&mut reader).unwrap());
+}
diff --git a/tests/len_prefix.rs b/tests/len_prefix.rs
new file mode 100644
index 0000000..d9597eb
--- /dev/null
+++ b/tests/len_prefix.rs
@@ -0,0 +1,310 @@
+use zr_protocol::{
+ codec::{decode::Decode, encode::Encode},
+ types::prefix::length::LenPrefixed,
+};
+
+fn encode_to_bytes<T: Encode>(value: T) -> Vec<u8> {
+ let mut buf = Vec::new();
+ value.encode(&mut buf).unwrap();
+ buf
+}
+
+// ────────────────────── Données simples ─────────
+
+#[test]
+fn simple_u32_with_u8_length() {
+ let lp = LenPrefixed::<u8, u32>::new(42u32);
+ let buf = encode_to_bytes(lp);
+ assert_eq!(buf, vec![0x04, 0x00, 0x00, 0x00, 0x2A]);
+}
+
+#[test]
+fn simple_u16_with_u16_length() {
+ let lp = LenPrefixed::<u16, u16>::new(256u16);
+ let buf = encode_to_bytes(lp);
+ assert_eq!(buf, vec![0x00, 0x02, 0x01, 0x00]);
+}
+
+#[test]
+fn simple_u8_with_u8_length() {
+ let lp = LenPrefixed::<u8, u8>::new(0xABu8);
+ let buf = encode_to_bytes(lp);
+ assert_eq!(buf, vec![0x01, 0xAB]);
+}
+
+#[test]
+fn simple_u64_with_u32_length() {
+ let lp = LenPrefixed::<u32, u64>::new(0x0102030405060708u64);
+ let buf = encode_to_bytes(lp);
+ assert_eq!(
+ buf,
+ vec![0x00, 0x00, 0x00, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]
+ );
+}
+
+// ────────────────────── Booléen ─────────────────
+
+#[test]
+fn bool_true_with_u8_length() {
+ let lp = LenPrefixed::<u8, bool>::new(true);
+ let buf = encode_to_bytes(lp);
+ assert_eq!(buf, vec![0x01, 0x01]);
+}
+
+#[test]
+fn bool_false_with_u8_length() {
+ let lp = LenPrefixed::<u8, bool>::new(false);
+ let buf = encode_to_bytes(lp);
+ assert_eq!(buf, vec![0x01, 0x00]);
+}
+
+// ────────────────────── Tableaux ────────────────
+
+#[test]
+fn array_4_bytes_with_u8_length() {
+ let lp = LenPrefixed::<u8, [u8; 4]>::new([0x01, 0x02, 0x03, 0x04]);
+ let buf = encode_to_bytes(lp);
+ assert_eq!(buf, vec![0x04, 0x01, 0x02, 0x03, 0x04]);
+}
+
+#[test]
+fn array_16_bytes_with_u16_length() {
+ let data: [u8; 16] = std::array::from_fn(|i| i as u8);
+ let lp = LenPrefixed::<u16, [u8; 16]>::new(data);
+ let buf = encode_to_bytes(lp);
+ assert_eq!(buf[0..2], [0x00, 0x10]);
+ assert_eq!(&buf[2..], &data[..]);
+}
+
+#[test]
+fn array_256_bytes_with_u16_length() {
+ let data: [u8; 256] = std::array::from_fn(|i| (i % 256) as u8);
+ let lp = LenPrefixed::<u16, [u8; 256]>::new(data);
+ let buf = encode_to_bytes(lp);
+ assert_eq!(buf[0..2], [0x01, 0x00]);
+ assert_eq!(&buf[2..], &data[..]);
+}
+
+// ────────────────────── Roundtrip ───────────────
+
+#[test]
+fn roundtrip_u32_u8() {
+ let lp = LenPrefixed::<u8, u32>::new(42u32);
+ let buf = encode_to_bytes(lp);
+
+ let mut reader = &buf[..];
+ let decoded = LenPrefixed::<u8, u32>::decode(&mut reader).unwrap();
+ assert_eq!(*decoded.as_ref(), 42u32);
+}
+
+#[test]
+fn roundtrip_u64_u16() {
+ let lp = LenPrefixed::<u16, u64>::new(u64::MAX);
+ let buf = encode_to_bytes(lp);
+
+ let mut reader = &buf[..];
+ let decoded = LenPrefixed::<u16, u64>::decode(&mut reader).unwrap();
+ assert_eq!(*decoded.as_ref(), u64::MAX);
+}
+
+#[test]
+fn roundtrip_bool_u8() {
+ let lp = LenPrefixed::<u8, bool>::new(true);
+ let buf = encode_to_bytes(lp);
+
+ let mut reader = &buf[..];
+ let decoded = LenPrefixed::<u8, bool>::decode(&mut reader).unwrap();
+ assert!(*decoded.as_ref());
+}
+
+#[test]
+fn roundtrip_array_u8() {
+ let data = [0xDE, 0xAD, 0xBE, 0xEF];
+ let lp = LenPrefixed::<u8, [u8; 4]>::new(data);
+ let buf = encode_to_bytes(lp);
+
+ let mut reader = &buf[..];
+ let decoded = LenPrefixed::<u8, [u8; 4]>::decode(&mut reader).unwrap();
+ assert_eq!(*decoded.as_ref(), data);
+}
+
+#[test]
+fn roundtrip_negative_i32() {
+ let lp = LenPrefixed::<u8, i32>::new(-12345);
+ let buf = encode_to_bytes(lp);
+
+ let mut reader = &buf[..];
+ let decoded = LenPrefixed::<u8, i32>::decode(&mut reader).unwrap();
+ assert_eq!(*decoded.as_ref(), -12345);
+}
+
+#[test]
+fn roundtrip_f64() {
+ let lp = LenPrefixed::<u8, f64>::new(3.141592653589793);
+ let buf = encode_to_bytes(lp);
+
+ let mut reader = &buf[..];
+ let decoded = LenPrefixed::<u8, f64>::decode(&mut reader).unwrap();
+ assert_eq!(*decoded.as_ref(), 3.141592653589793);
+}
+
+#[test]
+fn roundtrip_u128() {
+ let lp = LenPrefixed::<u16, u128>::new(u128::MAX);
+ let buf = encode_to_bytes(lp);
+
+ let mut reader = &buf[..];
+ let decoded = LenPrefixed::<u16, u128>::decode(&mut reader).unwrap();
+ assert_eq!(*decoded.as_ref(), u128::MAX);
+}
+
+// ────────────────────── Imbrication ─────────────
+
+#[test]
+fn nested_len_prefixed_u16_u8_u32() {
+ let inner = LenPrefixed::<u8, u32>::new(42u32);
+ let outer = LenPrefixed::<u16, LenPrefixed<u8, u32>>::new(inner);
+ let buf = encode_to_bytes(outer);
+
+ // outer length = 1 (u8 prefix of inner) + 4 (u32) = 5
+ assert_eq!(buf[0..2], [0x00, 0x05]);
+ // inner length = 4
+ assert_eq!(buf[2], 0x04);
+ // inner data = 42
+ assert_eq!(&buf[3..7], &[0x00, 0x00, 0x00, 0x2A]);
+}
+
+#[test]
+fn roundtrip_nested() {
+ let inner = LenPrefixed::<u8, u16>::new(1337u16);
+ let outer = LenPrefixed::<u16, LenPrefixed<u8, u16>>::new(inner);
+ let buf = encode_to_bytes(outer);
+
+ let mut reader = &buf[..];
+ let decoded_outer = LenPrefixed::<u16, LenPrefixed<u8, u16>>::decode(&mut reader).unwrap();
+ let decoded_inner = decoded_outer.as_ref();
+ assert_eq!(decoded_inner.as_ref(), &1337u16);
+}
+
+#[test]
+fn triple_nested() {
+ let l1 = LenPrefixed::<u8, u8>::new(0xAA);
+ let l2 = LenPrefixed::<u8, LenPrefixed<u8, u8>>::new(l1);
+ let l3 = LenPrefixed::<u16, LenPrefixed<u8, LenPrefixed<u8, u8>>>::new(l2);
+ let buf = encode_to_bytes(l3);
+
+ let mut reader = &buf[..];
+ let d3 = LenPrefixed::<u16, LenPrefixed<u8, LenPrefixed<u8, u8>>>::decode(&mut reader).unwrap();
+ let d2 = d3.as_ref();
+ let d1 = d2.as_ref();
+ assert_eq!(*d1.as_ref(), 0xAAu8);
+}
+
+// ────────────────────── Erreurs de décodage ─────
+
+#[test]
+fn decode_empty_buffer_fails() {
+ let result = LenPrefixed::<u8, u32>::decode(&mut &[][..]);
+ assert!(result.is_err());
+}
+
+#[test]
+fn decode_truncated_length_fails() {
+ let result = LenPrefixed::<u16, u32>::decode(&mut &[0x01][..]);
+ assert!(result.is_err());
+}
+
+#[test]
+fn decode_length_exceeds_data_fails() {
+ let mut buf = Vec::new();
+ 100u8.encode(&mut buf).unwrap();
+ buf.extend_from_slice(&[0x01, 0x02]);
+ let result = LenPrefixed::<u8, u32>::decode(&mut &buf[..]);
+ assert!(result.is_err());
+}
+
+#[test]
+fn decode_zero_length_with_zero_sized_type() {
+ let lp = LenPrefixed::<u8, [u8; 0]>::new([]);
+ let buf = encode_to_bytes(lp);
+ let mut reader = &buf[..];
+ let decoded = LenPrefixed::<u8, [u8; 0]>::decode(&mut reader).unwrap();
+ assert_eq!(*decoded.as_ref(), []);
+}
+
+// ────────────────────── AsRef / AsMut ───────────
+
+#[test]
+fn as_ref_returns_inner() {
+ let lp = LenPrefixed::<u8, u32>::new(99u32);
+ assert_eq!(*lp.as_ref(), 99u32);
+}
+
+#[test]
+fn as_mut_allows_mutation() {
+ let mut lp = LenPrefixed::<u8, u32>::new(1u32);
+ *lp.as_mut() = 42;
+ assert_eq!(*lp.as_ref(), 42);
+}
+
+// ────────────────────── Taille du buffer ────────
+
+#[test]
+fn u8_length_header_size() {
+ let lp = LenPrefixed::<u8, u8>::new(0);
+ let buf = encode_to_bytes(lp);
+ assert_eq!(buf.len(), 2); // 1 byte length + 1 byte data
+}
+
+#[test]
+fn u16_length_header_size() {
+ let lp = LenPrefixed::<u16, u8>::new(0);
+ let buf = encode_to_bytes(lp);
+ assert_eq!(buf.len(), 3); // 2 byte length + 1 byte data
+}
+
+#[test]
+fn u32_length_header_size() {
+ let lp = LenPrefixed::<u32, u8>::new(0);
+ let buf = encode_to_bytes(lp);
+ assert_eq!(buf.len(), 5); // 4 byte length + 1 byte data
+}
+
+#[test]
+fn u64_length_header_size() {
+ let lp = LenPrefixed::<u64, u8>::new(0);
+ let buf = encode_to_bytes(lp);
+ assert_eq!(buf.len(), 9); // 8 byte length + 1 byte data
+}
+
+#[test]
+fn large_data_length_correct() {
+ let data: [u8; 1000] = std::array::from_fn(|i| (i % 256) as u8);
+ let lp = LenPrefixed::<u16, [u8; 1000]>::new(data);
+ let buf = encode_to_bytes(lp);
+ assert_eq!(buf.len(), 2 + 1000);
+ assert_eq!(&buf[0..2], &[0x03, 0xE8]); // 1000 in u16 big-endian
+}
+
+// ────────────────────── Sérialisation exacte ────
+
+#[test]
+fn exact_bytes_u8_prefix() {
+ let lp = LenPrefixed::<u8, u16>::new(256u16);
+ let buf = encode_to_bytes(lp);
+ assert_eq!(buf, vec![0x02, 0x01, 0x00]);
+}
+
+#[test]
+fn exact_bytes_negative_i16() {
+ let lp = LenPrefixed::<u8, i16>::new(-1);
+ let buf = encode_to_bytes(lp);
+ assert_eq!(buf, vec![0x02, 0xFF, 0xFF]);
+}
+
+#[test]
+fn exact_bytes_zero_u32() {
+ let lp = LenPrefixed::<u8, u32>::new(0u32);
+ let buf = encode_to_bytes(lp);
+ assert_eq!(buf, vec![0x04, 0x00, 0x00, 0x00, 0x00]);
+}
diff --git a/tests/tmp/todo.md b/tests/tmp/todo.md
new file mode 100644
index 0000000..a0c6a83
--- /dev/null
+++ b/tests/tmp/todo.md
@@ -0,0 +1,356 @@
+# zr_protocol - Todo V1
+
+> Crate Rust de communication réseau : protocole custom, sérialisation binaire, proc-macro pour définir des packets via un format `.packet`.
+
+---
+
+## Phase 0 — Workspace & Scaffold
+
+- [ ] **0.1** Créer le workspace virtuel `Cargo.toml` à la racine (members: `zr-protocol`, `zr-protocol-macros`)
+- [ ] **0.2** Créer le crate `zr-protocol/` (library, edition 2024)
+ - [ ] `Cargo.toml` avec dépendances : `bytes = "1"`, `thiserror = "2"`, features optionnelles (`tokio`, `serde`, `derive`)
+ - [ ] `src/lib.rs` avec modules déclarés : `encode`, `decode`, `packet`, `codec`, `framing`, `builder`, `error`, `impls`
+- [ ] **0.3** Créer le crate `zr-protocol-macros/` (proc-macro = true, edition 2024)
+ - [ ] `Cargo.toml` avec dépendances : `syn = { version = "2", features = ["full"] }`, `quote = "1"`, `proc-macro2 = "1"`
+ - [ ] `src/lib.rs` minimal (entry point vide)
+- [ ] **0.4** Supprimer les anciens fichiers `src/bin/client.rs` et `src/bin/server.rs` ( remplacer par des exemples dans `examples/` )
+- [ ] **0.5** Vérifier que `cargo check --workspace` compile sans erreur
+- [ ] **0.6** Créer la structure de dossiers dans les deux crates :
+ - `zr-protocol/src/encode.rs`, `decode.rs`, `packet.rs`, `codec.rs`, `framing.rs`, `builder.rs`, `error.rs`
+ - `zr-protocol/src/impls/mod.rs`, `primitives.rs`, `arrays.rs`, `option.rs`
+ - `zr-protocol-macros/src/parser/ast.rs`, `lexer.rs`, `grammar.rs`, `mod.rs`
+ - `zr-protocol-macros/src/codegen/struct_gen.rs`, `encode_gen.rs`, `decode_gen.rs`, `registry_gen.rs`, `mod.rs`
+ - `examples/login/packets/`, `examples/login/server.rs`, `examples/login/client.rs`
+ - `tests/` (racine workspace)
+
+---
+
+## Phase 1 — Traits Core : `Encode` & `Decode`
+
+> Les traits fondamentaux de sérialisation/déserialization binaire.
+
+- [ ] **1.1** Définir `src/error.rs` — type d'erreur `ZrError`
+ ```rust
+ use thiserror::Error;
+ #[derive(Debug, Error)]
+ pub enum ZrError {
+ #[error("io error: {0}")]
+ Io(#[from] std::io::Error),
+ #[error("invalid packet id: 0x{0:02X}")]
+ InvalidPacketId(u32),
+ #[error("buffer underflow: expected {expected} bytes, got {got}")]
+ Underflow { expected: usize, got: usize },
+ #[error("packet too large: {0} bytes (max {1})")]
+ PacketTooLarge(usize, usize),
+ #[error("unknown field type: {0}")]
+ UnknownType(String),
+ #[error("decode error: {0}")]
+ Decode(String),
+ }
+ ```
+- [ ] **1.2** Définir `src/encode.rs` — trait `Encode`
+ ```rust
+ pub trait Encode {
+ fn encode(&self, buf: &mut BytesMut) -> io::Result<()>;
+ fn encoded_size(&self) -> usize; // optionnel, pour pré-réserver
+ }
+ ```
+- [ ] **1.3** Définir `src/decode.rs` — trait `Decode`
+ ```rust
+ pub trait Decode: Sized {
+ fn decode(buf: &mut BytesMut) -> io::Result<Self>;
+ }
+ ```
+- [ ] **1.4** Impl `Encode` pour les types primitifs dans `src/impls/primitives.rs`
+ - `u8`, `u16`, `u32`, `u64`, `u128`
+ - `i8`, `i16`, `i32`, `i64`, `i128`
+ - `bool` (1 octet : 0x00 = false, 0x01 = true)
+ - `f32`, `f64` (via `to_be_bytes()` / `to_le_bytes()`)
+ - Tous en big-endian par défaut (network byte order)
+- [ ] **1.5** Impl `Encode` pour `String` et `Vec<u8>`
+ - Format : `u32` (longueur en octets) + octets bruts
+- [ ] **1.6** Impl `Encode` pour `[u8; N]` dans `src/impls/arrays.rs` (écriture directe, pas de longueur)
+- [ ] **1.7** Impl `Encode` pour `Option<T: Encode>` dans `src/impls/option.rs`
+ - Format : `u8` tag (0x00 = None, 0x01 = Some) + données si Some
+- [ ] **1.8** Impl `Decode` pour tous les mêmes types (mirror de 1.4 à 1.7)
+ - Avec gestion propre des erreurs (underflow → `ZrError::Underflow`)
+- [ ] **1.9** Impl `Encode`/`Decode` pour `Vec<T: Encode/Decode>`
+ - Format : `u32` nombre d'éléments + sérialisation de chaque élément
+- [ ] **1.10** Tests unitaires pour chaque type implémenté
+ - Roundtrip : encode → decode → assert_eq
+ - Cas limites : chaîne vide, vec vide, None, MAX values
+
+---
+
+## Phase 2 — Trait `PacketMeta` & Types Runtime
+
+> Métadonnées des packets et typage dynamique.
+
+- [ ] **2.1** Définir `src/packet.rs` — trait `PacketMeta`
+ ```rust
+ pub trait PacketMeta {
+ const ID: u32;
+ const NAME: &'static str;
+ const SIZE_HINT: Option<usize>;
+ }
+ ```
+- [ ] **2.2** Définir un enum `PacketId` (ou type alias `u32`) pour les IDs réservés
+ - Réservé `0x00` = Reserved, `0xFF` = KeepAlive/Ping
+- [ ] **2.3** Définir `pub struct ZrPacket` (wrapper type-érasé pour le codec)
+ ```rust
+ pub struct ZrPacket {
+ pub id: u32,
+ pub payload: BytesMut,
+ }
+ ```
+- [ ] **2.4** Définir `pub trait IntoZrPacket: PacketMeta + Encode` pour convertir un typed packet → `ZrPacket`
+- [ ] **2.5** Tests pour `PacketMeta` et `ZrPacket`
+
+---
+
+## Phase 3 — Format `.packet` : Lexer & Parser
+
+> Parsing du format texte de définition de protocole.
+
+- [ ] **3.1** Définir l'AST dans `zr-protocol-macros/src/parser/ast.rs`
+ ```rust
+ pub struct ProtocolFile {
+ pub packets: Vec<PacketDef>,
+ }
+ pub struct PacketDef {
+ pub attributes: Vec<Attribute>,
+ pub name: Ident,
+ pub id: u32,
+ pub fields: Vec<FieldDef>,
+ }
+ pub struct FieldDef {
+ pub attributes: Vec<Attribute>,
+ pub name: Ident,
+ pub ty: TypeRef,
+ }
+ pub enum TypeRef {
+ Primitive(String), // u32, String, bool, etc.
+ Option(Box<TypeRef>), // Option<T>
+ Vec(Box<TypeRef>), // Vec<T>
+ Array(String, usize), // [u8; 64]
+ Custom(String), // un autre type packet
+ }
+ pub struct Attribute {
+ pub name: String,
+ pub value: Option<String>,
+ }
+ ```
+- [ ] **3.2** Implémenter le lexer dans `zr-protocol-macros/src/parser/lexer.rs`
+ - Tokens : `Packet`, `Ident`, `Number` (hex 0x.. et décimal), `Colon`, `BraceOpen`, `BraceClose`, `BracketOpen`, `BracketClose`, `Eq`, `String`, `Comma`, `EndAttribute`, `Hash`
+ - Whitespace et comments (`//`) ignorés
+- [ ] **3.3** Implémenter le parser dans `zr-protocol-macros/src/parser/grammar.rs`
+ - Parse un `ProtocolFile` à partir de tokens
+ - Validation : nom unique, ID unique, types connus
+ - Erreurs avec span (ligne/colonne) pour de bons messages d'erreur
+- [ ] **3.4** Tests du parser :
+ - [ ] Format valide basique
+ - [ ] Attributs optionnels (`#[endian = "big"]`)
+ - [ ] Types `Option<T>`, `Vec<T>`, `[u8; N]`
+ - [ ] Erreur : type inconnu
+ - [ ] Erreur : syntaxe invalide
+ - [ ] Erreur : ID manquant
+ - [ ] Fichier vide (pas de packet)
+
+---
+
+## Phase 4 — Proc-macro `packet!`
+
+> Génération de code Rust à partir du format `.packet`.
+
+- [ ] **4.1** Implémenter l'entry point `packet!` dans `zr-protocol-macros/src/lib.rs`
+ - Accepte `include!("path/to/file.packet")` OU du code inline
+ - Lit le fichier via `CARGO_MANIFEST_DIR` + chemin relatif
+- [ ] **4.2** Codegen struct dans `zr-protocol-macros/src/codegen/struct_gen.rs`
+ - Génère `#[derive(Debug, Clone)] pub struct NomPacket { pub field: Type, ... }`
+ - Gère `Option<T>` → `Option<T>`, `Vec<T>` → `Vec<T>`, `[u8; N]` → `[u8; N]`
+- [ ] **4.3** Codegen `impl PacketMeta` dans `zr-protocol-macros/src/codegen/registry_gen.rs`
+ - `const ID: u32 = ...; const NAME: &'static str = "..."; const SIZE_HINT: Option<usize> = None;`
+- [ ] **4.4** Codegen `impl Encode` dans `zr-protocol-macros/src/codegen/encode_gen.rs`
+ - Pour chaque champ : `Encode::encode(&self.champ, buf)?;`
+- [ ] **4.5** Codegen `impl Decode` dans `zr-protocol-macros/src/codegen/decode_gen.rs`
+ - Pour chaque champ : `champ: Decode::decode(buf)?`
+- [ ] **4.6** Codegen registry globale (un seul `match` ID → nom) dans `registry_gen.rs`
+ - Fonction `pub fn packet_name_by_id(id: u32) -> Option<&'static str>`
+- [ ] **4.7** Codegen `impl IntoZrPacket` pour chaque packet
+ - Sérialise → `ZrPacket { id, payload }`
+- [ ] **4.8** Gestion des attributs dans le codegen
+ - `#[endian = "big"]` → big-endian (défaut), `#[endian = "little"]` → little-endian
+ - `#[skip_if_none]` → ne sérialise pas le champ si `None` (sans le tag)
+- [ ] **4.9** Tests d'intégration :
+ - [ ] Un fichier `.packet` simple → compiles, encode/decode roundtrip
+ - [ ] Fichier avec `Option<T>` et `Vec<T>`
+ - [ ] Compilation échoue sur type inconnu (trybuild)
+ - [ ] Compilation échoue sur syntaxe invalide (trybuild)
+- [ ] **4.10** Macro `packet!` inline (pour les petits définitions sans fichier)
+ ```rust
+ packet! {
+ packet Ping 0xFF {
+ sequence: u32,
+ }
+ }
+ ```
+
+---
+
+## Phase 5 — Builder Pattern (secondaire)
+
+> API alternative pour construire des packets sans struct literals.
+
+- [ ] **5.1** Définir le trait `PacketBuilder` dans `src/builder.rs`
+ ```rust
+ pub trait PacketBuilder: Sized {
+ type Packet: Encode + PacketMeta;
+ fn new() -> Self;
+ fn field<T: IntoFieldValue>(mut self, name: &str, value: T) -> Self;
+ fn build(self) -> io::Result<Self::Packet>;
+ }
+ ```
+- [ ] **5.2** Codegen du builder dans `zr-protocol-macros/src/codegen/builder_gen.rs`
+ - Génère une struct `NomPacketBuilder { username: Option<String>, ... }` avec chaque champ en `Option`
+ - `field()` matche sur le nom (string) et set la valeur
+ - `build()` vérifie que tous les champs sont présents, sinon erreur
+- [ ] **5.3** Opt-out via attribute : `#[packet(no_builder)]` désactive la génération du builder
+- [ ] **5.4** Tests :
+ - [ ] Builder crée un packet valide
+ - [ ] Builder erreur si champ manquant
+ - [ ] `#[packet(no_builder)]` ne génère pas le builder
+
+---
+
+## Phase 6 — Feature Flags & Extensibilité
+
+> Support optionnel de crates externes.
+
+- [ ] **6.1** Feature `tokio` : active les dépendances `tokio` + `tokio-util`
+ - Active le module `codec.rs` et `framing.rs`
+- [ ] **6.2** Feature `serde` : active `serde` + `bincode`
+ - Génère `#[derive(serde::Serialize, serde::Deserialize)]` sur les structs via le proc-macro
+ - Attribut `#[packet(serde)]` pour forcer ou `#[packet(no_serde)]` pour désactiver par packet
+- [ ] **6.3** Feature `derive` : active `zr-protocol-macros`
+ - Les macros `packet!`, `include_packets!` ne sont disponibles que avec cette feature
+- [ ] **6.4** Feature `std` (défaut) : support `String`, `Vec`, etc.
+ - Feature `no_std` future (pas v1) : uniquement `[u8; N]`, `u8`, etc.
+- [ ] **6.5** Documentation des features dans le `Cargo.toml` et `lib.rs`
+
+---
+
+## Phase 7 — Codec & Framing (tokio-util)
+
+> Intégration avec tokio pour la communication async.
+
+- [ ] **7.1** Implémenter `src/framing.rs` — length-prefix framing
+ - Header : `u32` big-endian = longueur du payload
+ - Configurable : taille du header (2 ou 4 octets), endianness
+ - `max_packet_size` avec défaut (ex: 16 Mo)
+- [ ] **7.2** Implémenter `src/codec.rs` — `ZrCodec`
+ - `impl Encoder<Box<dyn Encode>> for ZrCodec` — écrit header + payload
+ - `impl Decoder for ZrCodec` — lit header, vérifie taille, lit payload, retourne `ZrPacket`
+ - Gestion propre de `BytesMut` (reserve, split_to, advance)
+- [ ] **7.3** Type `FramedPacket` pour le dispatch dynamique
+ - Le codec lit l'ID depuis le payload, lookup dans la registry
+ - Retourne un `ZrPacket` (type-érasé) que l'utilisateur cast avec un match sur l'ID
+- [ ] **7.4** Helper `fn framed_read(stream) -> impl Stream<Item = ZrPacket>` (optionnel, wrapper)
+- [ ] **7.5** Tests :
+ - [ ] Roundtrip TCP : encode → send → receive → decode → assert_eq
+ - [ ] Rejet des packets trop gros
+ - [ ] Gestion des reads partiels (TCP peut diviser les données)
+
+---
+
+## Phase 8 — Client & Server Helpers
+
+> Utilitaires pour simplifier l'usage réseau.
+
+- [ ] **8.1** Struct `ZrConnection` wrapper autour de `Framed<TcpStream, ZrCodec>`
+ ```rust
+ pub struct ZrConnection {
+ framed: Framed<TcpStream, ZrCodec>,
+ }
+ impl ZrConnection {
+ pub async fn connect(addr: &str) -> io::Result<Self>;
+ pub async fn send_packet<P: Encode + PacketMeta>(&mut self, packet: &P) -> io::Result<()>;
+ pub async fn next_packet(&mut self) -> io::Result<Option<ZrPacket>>;
+ }
+ ```
+- [ ] **8.2** Struct `ZrListener` wrapper autour de `TcpListener`
+ ```rust
+ pub struct ZrListener { listener: TcpListener }
+ impl ZrListener {
+ pub async fn bind(addr: &str) -> io::Result<Self>;
+ pub async fn accept(&self) -> io::Result<(ZrConnection, SocketAddr)>;
+ }
+ ```
+- [ ] **8.3** Macro `#[tokio::main]` compatible — les helpers utilisent tokio derrière
+- [ ] **8.4** Tests d'intégration : client/server qui s'échangent des packets
+
+---
+
+## Phase 9 — Exemples
+
+> Démonstrations complètes d'utilisation.
+
+- [ ] **9.1** Exemple `examples/simple.rs` — packet inline, encode/decode sans réseau
+- [ ] **9.2** Exemple `examples/login/` — client/server complet
+ - Fichiers `.packet` de définition
+ - Server qui écoute, reçoit `LoginRequest`, répond `LoginResponse`
+ - Client qui se connecte, envoie `LoginRequest`, lit `LoginResponse`
+- [ ] **9.3** Exemple avec `Option<T>` et `Vec<T>` pour montrer les types composés
+- [ ] **9.4** README avec badges, description, et example usage
+
+---
+
+## Phase 10 — Tests & Qualité
+
+> Fiabilité et robustesse.
+
+- [ ] **10.1** Tests unitaires : chaque type primitif encode/decode roundtrip
+- [ ] **10.2** Tests du parser lexer/grammar (bonne et mauvaise syntaxe)
+- [ ] **10.3** Tests trybuild : erreurs de compilation avec bons messages
+ - [ ] Type inconnu dans `.packet`
+ - [ ] Syntaxe invalide
+ - [ ] ID manquant
+ - [ ] Champ dupliqué
+- [ ] **10.4** Tests d'intégration TCP : client ↔ server roundtrip
+- [ ] **10.5** Benchmark (criterion) : throughput sérialisation vs bincode
+- [ ] **10.6** `cargo clippy --workspace` sans warning
+- [ ] **10.7** `cargo fmt --check` passe
+
+---
+
+## Phase 11 — Documentation
+
+- [ ] **11.1** `README.md` avec description, quick start, features
+- [ ] **11.2** Doc comments (`///`) sur tous les traits publics
+- [ ] **11.3** Doc comments sur le format `.packet` (guide de syntaxe)
+- [ ] **11.4** Exemples dans les doc comments (doc-tests)
+- [ ] **11.5** CHANGELOG.md
+
+---
+
+## Ordre d'exécution recommandé
+
+```
+Phase 0 → Phase 1 → Phase 2 → Phase 3 → Phase 4 → Phase 5
+ ↓
+ Phase 6 (features)
+ ↓
+ Phase 7 (codec)
+ ↓
+ Phase 8 → Phase 9 → Phase 10 → Phase 11
+```
+
+**Dépendances critiques :**
+- Phase 4 (proc-macro) dépend de Phase 1 (traits) et Phase 2 (AST)
+- Phase 7 (codec) dépend de Phase 1 (Encode/Decode) et Phase 4 (PacketMeta)
+- Phase 8 (helpers) dépend de Phase 7 (codec)
+- Phase 9-11 dépendent de tout le reste
+
+**Peut être fait en parallèle :**
+- Phase 5 (builder) peut démarrer après Phase 4
+- Phase 6 (features) peut démarrer après Phase 4
+- Les tests (Phase 10) peuvent être écrits au fur et à mesure de chaque phase