Implement generated packet wire codecs (#47)
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user