Implement generated packet wire codecs (#47)
Some checks failed
Native code generation / deterministic (push) Successful in 4m29s
Imaging and meshing gate / native (push) Failing after 16s
JPEG 2000 feature / linux (push) Failing after 59s
Skia feature / linux (push) Failing after 1m37s

This commit is contained in:
2026-08-09 08:16:32 +00:00
parent 5bc22bdd83
commit 74801c3488
16 changed files with 87505 additions and 25470 deletions

View File

@@ -843,6 +843,84 @@ fn field_default(kind: FieldKind) -> &'static str {
}
}
fn field_sample(field: &PacketField) -> String {
match field.kind {
FieldKind::Bool => "true".to_owned(),
FieldKind::U8 => "0xa5_u8".to_owned(),
FieldKind::U16 | FieldKind::IpPort => "0x1234_u16".to_owned(),
FieldKind::U32 | FieldKind::IpAddr => "0x1122_3344_u32".to_owned(),
FieldKind::U64 => "0x0102_0304_0506_0708_u64".to_owned(),
FieldKind::S8 => "-37_i8".to_owned(),
FieldKind::S16 => "-0x1234_i16".to_owned(),
FieldKind::S32 => "-0x1122_3344_i32".to_owned(),
FieldKind::F32 => "1.5_f32".to_owned(),
FieldKind::F64 => "-2.25_f64".to_owned(),
FieldKind::LlUuid => "libremetaverse_types::UUID::new_with_bytes_int32((0_u8..16).collect(), 0).expect(\"sample UUID\")".to_owned(),
FieldKind::LlVector3 => "libremetaverse_types::Vector3 { x: 1.25, y: -2.5, z: 3.75 }".to_owned(),
FieldKind::LlVector3d => "libremetaverse_types::Vector3d { x: 1.25, y: -2.5, z: 3.75 }".to_owned(),
FieldKind::LlVector4 => "libremetaverse_types::Vector4 { x: 1.25, y: -2.5, z: 3.75, w: -4.5 }".to_owned(),
FieldKind::LlQuaternion => "libremetaverse_types::Quaternion::identity()".to_owned(),
FieldKind::Variable => "vec![0_u8, 1, 2, 0, 0xff]".to_owned(),
FieldKind::Fixed => format!("vec![0xa5_u8; {}]", field.count),
}
}
fn field_decode_statement(field: &PacketField) -> String {
let name = rust_snake(&field.name);
let expression = match field.kind {
FieldKind::Bool => "reader.read_u8()? != 0".to_owned(),
FieldKind::U8 => "reader.read_u8()?".to_owned(),
FieldKind::U16 => "reader.read_u16_le()?".to_owned(),
FieldKind::U32 | FieldKind::IpAddr => "reader.read_u32_le()?".to_owned(),
FieldKind::U64 => "reader.read_u64_le()?".to_owned(),
FieldKind::S8 => "reader.read_i8()?".to_owned(),
FieldKind::S16 => "reader.read_i16_le()?".to_owned(),
FieldKind::S32 => "reader.read_i32_le()?".to_owned(),
FieldKind::F32 => "reader.read_f32_le()?".to_owned(),
FieldKind::F64 => "reader.read_f64_le()?".to_owned(),
FieldKind::LlUuid => "reader.read_uuid()?".to_owned(),
FieldKind::LlVector3 => "reader.read_vector3()?".to_owned(),
FieldKind::LlVector3d => "reader.read_vector3d()?".to_owned(),
FieldKind::LlVector4 => "reader.read_vector4()?".to_owned(),
FieldKind::LlQuaternion => "reader.read_quaternion()?".to_owned(),
FieldKind::IpPort => "reader.read_u16_be()?".to_owned(),
FieldKind::Variable => format!("reader.read_variable({})?", field.count),
FieldKind::Fixed => format!(
"reader.read_bytes({}, \"truncated fixed packet field\")?",
field.count
),
};
format!("self.{name} = {expression};")
}
fn field_encode_statement(field: &PacketField) -> String {
let name = rust_snake(&field.name);
match field.kind {
FieldKind::Bool => format!("writer.write_u8(u8::from(self.{name}))?;"),
FieldKind::U8 => format!("writer.write_u8(self.{name})?;"),
FieldKind::U16 => format!("writer.write_u16_le(self.{name})?;"),
FieldKind::U32 | FieldKind::IpAddr => {
format!("writer.write_u32_le(self.{name})?;")
}
FieldKind::U64 => format!("writer.write_u64_le(self.{name})?;"),
FieldKind::S8 => format!("writer.write_i8(self.{name})?;"),
FieldKind::S16 => format!("writer.write_i16_le(self.{name})?;"),
FieldKind::S32 => format!("writer.write_i32_le(self.{name})?;"),
FieldKind::F32 => format!("writer.write_f32_le(self.{name})?;"),
FieldKind::F64 => format!("writer.write_f64_le(self.{name})?;"),
FieldKind::LlUuid => format!("writer.write_uuid(&self.{name})?;"),
FieldKind::LlVector3 => format!("writer.write_vector3(&self.{name})?;"),
FieldKind::LlVector3d => format!("writer.write_vector3d(&self.{name})?;"),
FieldKind::LlVector4 => format!("writer.write_vector4(&self.{name})?;"),
FieldKind::LlQuaternion => format!("writer.write_quaternion(&self.{name})?;"),
FieldKind::IpPort => format!("writer.write_u16_be(self.{name})?;"),
FieldKind::Variable => {
format!("writer.write_variable(&self.{name}, {})?;", field.count)
}
FieldKind::Fixed => format!("writer.write_fixed(&self.{name}, {})?;", field.count),
}
}
fn ordered_packets(protocol: &PacketProtocol) -> Vec<&PacketDefinition> {
[
PacketFrequency::Low,
@@ -958,6 +1036,7 @@ fn append_descriptors(body: &mut String, packets: &[&PacketDefinition]) {
body.push_str("];\n\n");
}
#[allow(clippy::too_many_lines)]
fn append_dispatch(body: &mut String, packets: &[&PacketDefinition]) {
body.push_str("#[must_use]\n#[allow(clippy::too_many_lines)]\npub fn packet_type(id: u16, frequency: crate::PacketFrequency) -> PacketType {\n match (frequency, id) {\n");
for packet in packets {
@@ -1001,8 +1080,102 @@ fn append_dispatch(body: &mut String, packets: &[&PacketDefinition]) {
crate::packets::Header { ack_list: None, appended_acks: false, frequency, id, reliable: true, resent: false, sequence: 0, zerocoded }\n\
}\n\n\
fn usize_to_i32(value: usize) -> i32 { i32::try_from(value).unwrap_or(i32::MAX) }\n\n\
pub(crate) trait GeneratedBlock { fn new_generated() -> Self; fn generated_length(&self) -> i32; }\n\n\
pub(crate) trait GeneratedPacket { fn new_generated() -> Self; fn generated_length(&self) -> i32; const USES_BUFFER_POOLING: bool; }\n\n",
pub(crate) trait GeneratedBlock: Sized {\n\
fn new_generated() -> Self;\n\
fn generated_length(&self) -> i32;\n\
fn decode_payload(&mut self, reader: &mut crate::packet_wire::WireReader<'_>) -> Result<(), crate::Error>;\n\
fn encode_payload(&self, writer: &mut crate::packet_wire::WireWriter) -> Result<(), crate::Error>;\n\
fn new_from_bytes(bytes: &[u8], position: &mut i32) -> Result<Self, crate::Error> {\n\
let mut value = Self::new_generated();\n\
Self::decode_from_bytes(&mut value, bytes, position)?;\n\
Ok(value)\n\
}\n\
fn decode_from_bytes(&mut self, bytes: &[u8], position: &mut i32) -> Result<(), crate::Error> {\n\
if *position < 0 { return Err(crate::Error::Argument); }\n\
let start = usize::try_from(*position).map_err(|_| crate::Error::Argument)?;\n\
let mut reader = crate::packet_wire::WireReader::new(bytes, start, bytes.len())?;\n\
self.decode_payload(&mut reader)?;\n\
reader.commit_position(position)\n\
}\n\
fn encode_to_slice(&self, bytes: &mut [u8], position: &mut i32) -> Result<(), crate::Error> {\n\
if *position < 0 { return Err(crate::Error::Argument); }\n\
let start = usize::try_from(*position).map_err(|_| crate::Error::Argument)?;\n\
let capacity = usize::try_from(self.generated_length()).map_err(|_| crate::Error::Argument)?;\n\
let mut writer = crate::packet_wire::WireWriter::with_capacity(capacity)?;\n\
self.encode_payload(&mut writer)?;\n\
let encoded = writer.into_inner();\n\
let end = start.checked_add(encoded.len()).ok_or(crate::Error::Argument)?;\n\
if end > bytes.len() { return Err(crate::Error::IndexOutOfRange); }\n\
bytes[start..end].copy_from_slice(&encoded);\n\
*position = i32::try_from(end).map_err(|_| crate::Error::Argument)?;\n\
Ok(())\n\
}\n\
}\n\n\
pub(crate) trait GeneratedPacket: Sized {\n\
const USES_BUFFER_POOLING: bool;\n\
fn new_generated() -> Self;\n\
fn generated_length(&self) -> i32;\n\
fn generated_header(&self) -> crate::packets::Header;\n\
fn store_header(&mut self, header: crate::packets::Header);\n\
fn decode_payload(&mut self, reader: &mut crate::packet_wire::WireReader<'_>) -> Result<(), crate::Error>;\n\
fn encode_payload(&self, writer: &mut crate::packet_wire::WireWriter) -> Result<(), crate::Error>;\n\
fn encode_multiple(&self) -> Result<Vec<Vec<u8>>, crate::Error>;\n\
fn encode_packet(&self) -> Result<Vec<u8>, crate::Error> {\n\
let header = self.generated_header();\n\
let payload = usize::try_from(self.generated_length()).map_err(|_| crate::Error::Argument)?;\n\
let capacity = payload.checked_add(crate::packet_wire::ack_length(&header)?).ok_or(crate::Error::Argument)?;\n\
let mut writer = crate::packet_wire::WireWriter::with_capacity(capacity)?;\n\
crate::packet_wire::encode_header(&header, &mut writer)?;\n\
self.encode_payload(&mut writer)?;\n\
crate::packet_wire::encode_acks(&header, &mut writer)?;\n\
Ok(writer.into_inner())\n\
}\n\
fn new_from_bytes(bytes: &[u8], position: &mut i32) -> Result<Self, crate::Error> {\n\
let mut value = Self::new_generated();\n\
let mut packet_end = i32::try_from(bytes.len()).map_err(|_| crate::Error::Argument)?.checked_sub(1).ok_or(crate::Error::Argument)?;\n\
Self::decode_from_bytes(&mut value, bytes, position, &mut packet_end, None)?;\n\
Ok(value)\n\
}\n\
fn new_from_header(header: crate::packets::Header, bytes: &[u8], position: &mut i32) -> Result<Self, crate::Error> {\n\
let mut value = Self::new_generated();\n\
let mut packet_end = i32::try_from(bytes.len()).map_err(|_| crate::Error::Argument)?.checked_sub(1).ok_or(crate::Error::Argument)?;\n\
Self::decode_from_header(&mut value, header, bytes, position, &mut packet_end)?;\n\
Ok(value)\n\
}\n\
fn decode_from_header(&mut self, header: crate::packets::Header, bytes: &[u8], position: &mut i32, packet_end: &mut i32) -> Result<(), crate::Error> {\n\
if *position < 0 || *packet_end < 0 { return Err(crate::Error::Argument); }\n\
let start = usize::try_from(*position).map_err(|_| crate::Error::Argument)?;\n\
let end = usize::try_from(packet_end.checked_add(1).ok_or(crate::Error::Argument)?).map_err(|_| crate::Error::Argument)?;\n\
let mut reader = crate::packet_wire::WireReader::new(bytes, start, end)?;\n\
self.decode_payload(&mut reader)?;\n\
reader.finish(position)?;\n\
self.store_header(header);\n\
Ok(())\n\
}\n\
fn decode_from_bytes(&mut self, bytes: &[u8], position: &mut i32, packet_end: &mut i32, zero_buffer: Option<&mut [u8]>) -> Result<(), crate::Error> {\n\
let header = crate::packet_wire::decode_header(bytes, position, packet_end)?;\n\
if header.zerocoded && let Some(buffer) = zero_buffer {\n\
let decoded_length = crate::packet_wire::zero_decode(Some(bytes), packet_end.checked_add(1).ok_or(crate::Error::Argument)?, Some(buffer))?;\n\
*packet_end = decoded_length.checked_sub(1).ok_or(crate::Error::Argument)?;\n\
let end = usize::try_from(decoded_length).map_err(|_| crate::Error::Argument)?;\n\
return self.decode_from_header(header, &buffer[..end], position, packet_end);\n\
}\n\
self.decode_from_header(header, bytes, position, packet_end)\n\
}\n\
}\n\n",
);
body.push_str(
"#[allow(clippy::too_many_lines)]\npub(crate) fn validate_packet_payload(\n packet_type: PacketType,\n header: crate::packets::Header,\n bytes: &[u8],\n position: &mut i32,\n packet_end: &mut i32,\n) -> Result<(), crate::Error> {\n match packet_type {\n",
);
for packet in packets {
let _ = writeln!(
body,
" PacketType::{} => {{ let mut packet = <crate::packets::{}Packet as GeneratedPacket>::new_generated(); GeneratedPacket::decode_from_header(&mut packet, header, bytes, position, packet_end) }},",
packet.name, packet.name
);
}
body.push_str(
" PacketType::Default => Err(crate::Error::InvalidOperation),\n }\n}\n\n",
);
}
@@ -1043,7 +1216,17 @@ fn append_block_impls(body: &mut String, packets: &[&PacketDefinition]) {
}
body.push_str(" length\n");
}
body.push_str(" }\n}\n\n");
body.push_str(
" }\n\n fn decode_payload(&mut self, reader: &mut crate::packet_wire::WireReader<'_>) -> Result<(), crate::Error> {\n",
);
for field in &block.fields {
let _ = writeln!(body, " {}", field_decode_statement(field));
}
body.push_str(" Ok(())\n }\n\n fn encode_payload(&self, writer: &mut crate::packet_wire::WireWriter) -> Result<(), crate::Error> {\n");
for field in &block.fields {
let _ = writeln!(body, " {}", field_encode_statement(field));
}
body.push_str(" Ok(())\n }\n}\n\n");
}
}
}
@@ -1052,6 +1235,135 @@ fn packet_has_composed_base(name: &str) -> bool {
matches!(name, "DirPlacesReply" | "TestMessage")
}
fn packet_can_split(packet: &PacketDefinition) -> bool {
let mut saw_variable = false;
let mut has_variable = false;
for block in &packet.blocks {
if block.repetition == BlockRepetition::Variable {
saw_variable = true;
has_variable = true;
} else if saw_variable {
return false;
}
}
has_variable
}
#[allow(clippy::too_many_lines)]
fn append_packet_encode_multiple(body: &mut String, packet: &PacketDefinition) {
if !packet_can_split(packet) {
body.push_str(
" fn encode_multiple(&self) -> Result<Vec<Vec<u8>>, crate::Error> {\n Ok(vec![self.encode_packet()?])\n }\n",
);
return;
}
let variable_blocks = packet
.blocks
.iter()
.filter(|block| block.repetition == BlockRepetition::Variable)
.collect::<Vec<_>>();
body.push_str(
" #[allow(clippy::too_many_lines)]\n fn encode_multiple(&self) -> Result<Vec<Vec<u8>>, crate::Error> {\n let header = self.generated_header();\n let mut packets = Vec::new();\n",
);
for block in &variable_blocks {
let _ = writeln!(
body,
" let mut {}_start = 0_usize;",
rust_snake(&block.name)
);
}
body.push_str(" loop {\n let first_packet = packets.is_empty();\n let mut packet_header = crate::packet_wire::clone_header(&header);\n if !first_packet { packet_header.appended_acks = false; packet_header.ack_list = None; }\n let acks_length = crate::packet_wire::ack_length(&packet_header)?;\n let mut fixed_length = crate::packet_wire::header_length(packet_header.frequency);
");
for block in &packet.blocks {
let field_name = rust_snake(&block.name);
match block.repetition {
BlockRepetition::Single => {
let _ = writeln!(
body,
" fixed_length = fixed_length.checked_add(usize::try_from(GeneratedBlock::generated_length(&self.{field_name})).map_err(|_| crate::Error::Argument)?).ok_or(crate::Error::Argument)?;"
);
}
BlockRepetition::Multiple(_) => {
let _ = writeln!(
body,
" for block in &self.{field_name} {{ fixed_length = fixed_length.checked_add(usize::try_from(GeneratedBlock::generated_length(block)).map_err(|_| crate::Error::Argument)?).ok_or(crate::Error::Argument)?; }}"
);
}
BlockRepetition::Variable => {}
}
}
let _ = writeln!(
body,
" fixed_length = fixed_length.checked_add({}).ok_or(crate::Error::Argument)?;",
variable_blocks.len()
);
body.push_str(" let mut variable_length = 0_usize;\n");
for block in &variable_blocks {
let field_name = rust_snake(&block.name);
let _ = writeln!(body, " let mut {field_name}_count = 0_usize;");
let _ = writeln!(
body,
" while {field_name}_start + {field_name}_count < self.{field_name}.len() && {field_name}_count < usize::from(u8::MAX) {{"
);
let _ = writeln!(
body,
" let block_length = usize::try_from(GeneratedBlock::generated_length(&self.{field_name}[{field_name}_start + {field_name}_count])).map_err(|_| crate::Error::Argument)?;"
);
let _ = writeln!(
body,
" if fixed_length.saturating_add(variable_length).saturating_add(block_length).saturating_add(acks_length) <= usize::try_from(crate::packets::Packet::MTU).map_err(|_| crate::Error::Argument)? || {field_name}_count == 0 {{ variable_length = variable_length.checked_add(block_length).ok_or(crate::Error::Argument)?; {field_name}_count += 1; }} else {{ break; }}"
);
body.push_str(" }\n");
}
body.push_str(" let capacity = fixed_length.checked_add(variable_length).and_then(|length| length.checked_add(acks_length)).ok_or(crate::Error::Argument)?;\n let mut writer = crate::packet_wire::WireWriter::with_capacity(capacity)?;\n crate::packet_wire::encode_header(&packet_header, &mut writer)?;\n");
for block in &packet.blocks {
let field_name = rust_snake(&block.name);
match block.repetition {
BlockRepetition::Single => {
let _ = writeln!(
body,
" GeneratedBlock::encode_payload(&self.{field_name}, &mut writer)?;"
);
}
BlockRepetition::Multiple(count) => {
let _ = writeln!(
body,
" if self.{field_name}.len() != {count} {{ return Err(crate::Error::Argument); }}"
);
let _ = writeln!(
body,
" for block in &self.{field_name} {{ GeneratedBlock::encode_payload(block, &mut writer)?; }}"
);
}
BlockRepetition::Variable => {
let _ = writeln!(
body,
" writer.write_u8(u8::try_from({field_name}_count).map_err(|_| crate::Error::Argument)?)?;"
);
let _ = writeln!(
body,
" for block in &self.{field_name}[{field_name}_start..{field_name}_start + {field_name}_count] {{ GeneratedBlock::encode_payload(block, &mut writer)?; }}"
);
let _ = writeln!(
body,
" {field_name}_start += {field_name}_count;"
);
}
}
}
body.push_str(" crate::packet_wire::encode_acks(&packet_header, &mut writer)?;\n packets.push(writer.into_inner());\n if ");
for (index, block) in variable_blocks.iter().enumerate() {
if index != 0 {
body.push_str(" && ");
}
let field_name = rust_snake(&block.name);
let _ = write!(body, "{field_name}_start >= self.{field_name}.len()");
}
body.push_str(" { break; }\n }\n Ok(packets)\n }\n");
}
#[allow(clippy::too_many_lines)]
fn append_packet_impls(body: &mut String, packets: &[&PacketDefinition]) {
for packet in packets {
let type_name = format!("{}Packet", packet.name);
@@ -1105,42 +1417,198 @@ fn append_packet_impls(body: &mut String, packets: &[&PacketDefinition]) {
body.push_str(" }\n }\n\n fn generated_length(&self) -> i32 {\n");
if packet.blocks.is_empty() {
let _ = writeln!(body, " {base_length}_i32");
body.push_str(" }\n}\n\n");
continue;
} else {
let _ = writeln!(body, " let mut length = {base_length}_i32;");
for block in &packet.blocks {
let field_name = rust_snake(&block.name);
match block.repetition {
BlockRepetition::Single => {
let _ = writeln!(
body,
" length = length.saturating_add(GeneratedBlock::generated_length(&self.{field_name}));"
);
}
BlockRepetition::Variable => {
let _ = writeln!(body, " length = length.saturating_add(1);");
let _ = writeln!(
body,
" for block in &self.{field_name} {{ length = length.saturating_add(GeneratedBlock::generated_length(block)); }}"
);
}
BlockRepetition::Multiple(_) => {
let _ = writeln!(
body,
" for block in &self.{field_name} {{ length = length.saturating_add(GeneratedBlock::generated_length(block)); }}"
);
}
}
}
body.push_str(" length\n");
}
let _ = writeln!(
body,
" let mut length = {}_i32;",
base_length + variable_count
body.push_str(" }\n\n fn generated_header(&self) -> crate::packets::Header {\n");
if packet_has_composed_base(&packet.name) {
body.push_str(" crate::packet_wire::clone_header(&self.base.header)\n");
} else {
let _ = writeln!(
body,
" new_header(crate::PacketFrequency::{}, {}, {})",
frequency_name(packet.frequency),
packet.id,
packet.zerocoded
);
}
body.push_str(
" }\n\n fn store_header(&mut self, header: crate::packets::Header) {\n",
);
if packet_has_composed_base(&packet.name) {
body.push_str(" self.base.header = header;\n");
} else {
body.push_str(" let _ = header;\n");
}
body.push_str(" }\n\n fn decode_payload(&mut self, reader: &mut crate::packet_wire::WireReader<'_>) -> Result<(), crate::Error> {\n");
for block in &packet.blocks {
let field_name = rust_snake(&block.name);
let block_type = format!("crate::packets::{}Packet{}Block", packet.name, block.name);
match block.repetition {
BlockRepetition::Single => {
let _ = writeln!(
body,
" GeneratedBlock::decode_payload(&mut self.{field_name}, reader)?;"
);
}
BlockRepetition::Variable => {
let _ = writeln!(
body,
" let {field_name}_count = usize::from(reader.read_u8()?);"
);
let _ = writeln!(body, " let mut {field_name} = Vec::new();");
let _ = writeln!(
body,
" {field_name}.try_reserve_exact({field_name}_count).map_err(|_| crate::Error::InvalidOperation)?;"
);
let _ = writeln!(
body,
" for _ in 0..{field_name}_count {{ let mut block = <{block_type} as GeneratedBlock>::new_generated(); GeneratedBlock::decode_payload(&mut block, reader)?; {field_name}.push(block); }}"
);
let _ = writeln!(body, " self.{field_name} = {field_name};");
}
BlockRepetition::Multiple(count) => {
let _ = writeln!(body, " let mut {field_name} = Vec::new();");
let _ = writeln!(
body,
" {field_name}.try_reserve_exact({count}).map_err(|_| crate::Error::InvalidOperation)?;"
);
let _ = writeln!(
body,
" for _ in 0..{count} {{ let mut block = <{block_type} as GeneratedBlock>::new_generated(); GeneratedBlock::decode_payload(&mut block, reader)?; {field_name}.push(block); }}"
);
let _ = writeln!(body, " self.{field_name} = {field_name};");
}
}
}
if packet.blocks.is_empty() {
body.push_str(" let _ = reader;\n");
}
body.push_str(" Ok(())\n }\n\n fn encode_payload(&self, writer: &mut crate::packet_wire::WireWriter) -> Result<(), crate::Error> {\n");
for block in &packet.blocks {
let field_name = rust_snake(&block.name);
match block.repetition {
BlockRepetition::Single => {
let _ = writeln!(
body,
" length = length.saturating_add(GeneratedBlock::generated_length(&self.{field_name}));"
" GeneratedBlock::encode_payload(&self.{field_name}, writer)?;"
);
}
BlockRepetition::Variable => {
let _ = writeln!(body, " length = length.saturating_add(1);");
let _ = writeln!(
body,
" for block in &self.{field_name} {{ length = length.saturating_add(GeneratedBlock::generated_length(block)); }}"
" writer.write_u8(u8::try_from(self.{field_name}.len()).map_err(|_| crate::Error::Argument)?)?;"
);
let _ = writeln!(
body,
" for block in &self.{field_name} {{ GeneratedBlock::encode_payload(block, writer)?; }}"
);
}
BlockRepetition::Multiple(_) => {
BlockRepetition::Multiple(count) => {
let _ = writeln!(
body,
" for block in &self.{field_name} {{ length = length.saturating_add(GeneratedBlock::generated_length(block)); }}"
" if self.{field_name}.len() != {count} {{ return Err(crate::Error::Argument); }}"
);
let _ = writeln!(
body,
" for block in &self.{field_name} {{ GeneratedBlock::encode_payload(block, writer)?; }}"
);
}
}
}
body.push_str(" length\n }\n}\n\n");
if packet.blocks.is_empty() {
body.push_str(" let _ = writer;\n");
}
body.push_str(" Ok(())\n }\n\n");
append_packet_encode_multiple(body, packet);
body.push_str("}\n\n");
}
}
fn append_packet_codec_tests(body: &mut String, packets: &[&PacketDefinition]) {
body.push_str(
"#[cfg(test)]\nmod generated_codec_tests {\n use super::{GeneratedBlock, GeneratedPacket};\n\n fn assert_round_trip<T: GeneratedPacket>(packet: &T, name: &str) {\n let expected = packet.encode_packet().unwrap_or_else(|error| panic!(\"{name}: encode failed: {error}\"));\n let (wire, mut zero_buffer) = if expected[0] & crate::Helpers::MSG_ZEROCODED != 0 {\n let mut encoded = vec![0_u8; expected.len().saturating_mul(2).saturating_add(2)];\n let encoded_length = crate::packet_wire::zero_encode(\n Some(&expected),\n i32::try_from(expected.len()).expect(\"packet length fits i32\"),\n Some(&mut encoded),\n )\n .unwrap_or_else(|error| panic!(\"{name}: zero encode failed: {error}\"));\n encoded.truncate(usize::try_from(encoded_length).expect(\"encoded length fits usize\"));\n (encoded, vec![0_u8; expected.len()])\n } else {\n (expected.clone(), Vec::new())\n };\n let mut decoded = T::new_generated();\n let mut position = 0_i32;\n let mut packet_end = i32::try_from(wire.len()).expect(\"wire length fits i32\") - 1;\n let buffer = (!zero_buffer.is_empty()).then_some(zero_buffer.as_mut_slice());\n decoded\n .decode_from_bytes(&wire, &mut position, &mut packet_end, buffer)\n .unwrap_or_else(|error| panic!(\"{name}: decode failed: {error}\"));\n let actual = decoded.encode_packet().unwrap_or_else(|error| panic!(\"{name}: re-encode failed: {error}\"));\n assert_eq!(actual, expected, \"{name}: round-trip bytes differ\");\n assert_eq!(position, i32::try_from(expected.len()).expect(\"packet length fits i32\"), \"{name}: final position\");\n\n for truncated_length in 0..wire.len() {\n let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {\n let mut candidate = T::new_generated();\n let mut truncated_position = 0_i32;\n let mut truncated_end = i32::try_from(truncated_length).expect(\"truncated length fits i32\") - 1;\n let mut scratch = vec![0_u8; expected.len()];\n let truncated_buffer = (!zero_buffer.is_empty()).then_some(scratch.as_mut_slice());\n candidate.decode_from_bytes(\n &wire[..truncated_length],\n &mut truncated_position,\n &mut truncated_end,\n truncated_buffer,\n )\n }));\n assert!(outcome.is_ok(), \"{name}: truncation at {truncated_length} panicked\");\n }\n }\n\n #[test]\n #[allow(clippy::too_many_lines)]\n fn every_generated_packet_round_trips_all_fields() {\n",
);
for packet in packets {
let mutates_packet = packet
.blocks
.iter()
.any(|block| block.repetition == BlockRepetition::Variable || !block.fields.is_empty());
let _ = writeln!(
body,
" let {}packet = <crate::packets::{}Packet as GeneratedPacket>::new_generated();",
if mutates_packet { "mut " } else { "" },
packet.name,
);
for block in &packet.blocks {
let block_name = rust_snake(&block.name);
match block.repetition {
BlockRepetition::Single => {
for field in &block.fields {
let field_name = rust_snake(&field.name);
let sample = field_sample(field);
let _ =
writeln!(body, " packet.{block_name}.{field_name} = {sample};");
}
}
BlockRepetition::Multiple(_) => {
let _ = writeln!(body, " for block in &mut packet.{block_name} {{");
for field in &block.fields {
let field_name = rust_snake(&field.name);
let sample = field_sample(field);
let _ = writeln!(body, " block.{field_name} = {sample};");
}
body.push_str(" }\n");
}
BlockRepetition::Variable => {
let block_type = format!("{}Packet{}Block", packet.name, block.name);
let _ = writeln!(
body,
" let mut block = <crate::packets::{block_type} as GeneratedBlock>::new_generated();"
);
for field in &block.fields {
let field_name = rust_snake(&field.name);
let sample = field_sample(field);
let _ = writeln!(body, " block.{field_name} = {sample};");
}
let _ = writeln!(body, " packet.{block_name}.push(block);");
}
}
}
let _ = writeln!(
body,
" assert_round_trip(&packet, {:?});",
packet.name
);
}
body.push_str(" }\n}\n");
}
fn csharp_field_type(kind: FieldKind) -> &'static str {
match kind {
FieldKind::U8 => "System.Byte",
@@ -1401,6 +1869,7 @@ pub fn packet_catalog_bytes(root: &Path) -> Result<Vec<u8>, String> {
append_dispatch(&mut body, &packets);
append_block_impls(&mut body, &packets);
append_packet_impls(&mut body, &packets);
append_packet_codec_tests(&mut body, &packets);
let syntax = syn::parse_file(&body)
.map_err(|error| format!("generated packet Rust is invalid: {error}"))?;
let formatted = prettyplease::unparse(&syntax);