diff --git a/lrwn/src/applayer/fragmentation/mod.rs b/lrwn/src/applayer/fragmentation/mod.rs new file mode 100644 index 00000000..a3a6d96c --- /dev/null +++ b/lrwn/src/applayer/fragmentation/mod.rs @@ -0,0 +1 @@ +pub mod v1; diff --git a/lrwn/src/applayer/fragmentation/v1.rs b/lrwn/src/applayer/fragmentation/v1.rs new file mode 100644 index 00000000..e6036c26 --- /dev/null +++ b/lrwn/src/applayer/fragmentation/v1.rs @@ -0,0 +1,787 @@ +use anyhow::Result; + +use crate::applayer::PayloadCodec; + +pub enum Cid { + PackageVersionReq, + PackageVersionAns, + FragSessionStatusReq, + FragSessionStatusAns, + FragSessionSetupReq, + FragSessionSetupAns, + FragSessionDeleteReq, + FragSessionDeleteAns, + DataFragment, +} + +impl Cid { + pub fn from_u8(uplink: bool, value: u8) -> Result { + Ok(match uplink { + true => match value { + 0x00 => Cid::PackageVersionAns, + 0x01 => Cid::FragSessionStatusAns, + 0x02 => Cid::FragSessionSetupAns, + 0x03 => Cid::FragSessionDeleteAns, + _ => return Err(anyhow!("Invalid CID: {}", value)), + }, + false => match value { + 0x00 => Cid::PackageVersionReq, + 0x01 => Cid::FragSessionStatusReq, + 0x02 => Cid::FragSessionSetupReq, + 0x03 => Cid::FragSessionDeleteReq, + 0x08 => Cid::DataFragment, + _ => return Err(anyhow!("Invalid CID: {}", value)), + }, + }) + } + + pub fn to_u8(&self) -> u8 { + match self { + Cid::PackageVersionReq | Cid::PackageVersionAns => 0x00, + Cid::FragSessionStatusReq | Cid::FragSessionStatusAns => 0x01, + Cid::FragSessionSetupReq | Cid::FragSessionSetupAns => 0x02, + Cid::FragSessionDeleteReq | Cid::FragSessionDeleteAns => 0x03, + Cid::DataFragment => 0x08, + } + } +} + +#[derive(Debug, PartialEq)] +pub enum Payload { + PackageVersionReq, + PackageVersionAns(PackageVersionAnsPayload), + FragSessionStatusReq(FragSessionStatusReqPayload), + FragSessionStatusAns(FragSessionStatusAnsPayload), + FragSessionSetupReq(FragSessionSetupReqPayload), + FragSessionSetupAns(FragSessionSetupAnsPayload), + FragSessionDeleteReq(FragSessionDeleteReqPayload), + FragSessionDeleteAns(FragSessionDeleteAnsPayload), + DataFragment(DataFragmentPayload), +} + +impl Payload { + pub fn cid(&self) -> Cid { + match self { + Self::PackageVersionReq => Cid::PackageVersionReq, + Self::PackageVersionAns(_) => Cid::PackageVersionAns, + Self::FragSessionStatusReq(_) => Cid::FragSessionStatusReq, + Self::FragSessionStatusAns(_) => Cid::FragSessionStatusAns, + Self::FragSessionSetupReq(_) => Cid::FragSessionSetupReq, + Self::FragSessionSetupAns(_) => Cid::FragSessionSetupAns, + Self::FragSessionDeleteReq(_) => Cid::FragSessionDeleteReq, + Self::FragSessionDeleteAns(_) => Cid::FragSessionDeleteAns, + Self::DataFragment(_) => Cid::DataFragment, + } + } + + pub fn from_slice(uplink: bool, b: &[u8]) -> Result { + if b.is_empty() { + return Err(anyhow!("At least one byte is expected")); + } + + let cid = Cid::from_u8(uplink, b[0])?; + + Ok(match cid { + Cid::PackageVersionReq => Payload::PackageVersionReq, + Cid::PackageVersionAns => { + Payload::PackageVersionAns(PackageVersionAnsPayload::decode(&b[1..])?) + } + Cid::FragSessionStatusReq => { + Payload::FragSessionStatusReq(FragSessionStatusReqPayload::decode(&b[1..])?) + } + Cid::FragSessionStatusAns => { + Payload::FragSessionStatusAns(FragSessionStatusAnsPayload::decode(&b[1..])?) + } + Cid::FragSessionSetupReq => { + Payload::FragSessionSetupReq(FragSessionSetupReqPayload::decode(&b[1..])?) + } + Cid::FragSessionSetupAns => { + Payload::FragSessionSetupAns(FragSessionSetupAnsPayload::decode(&b[1..])?) + } + Cid::FragSessionDeleteReq => { + Payload::FragSessionDeleteReq(FragSessionDeleteReqPayload::decode(&b[1..])?) + } + Cid::FragSessionDeleteAns => { + Payload::FragSessionDeleteAns(FragSessionDeleteAnsPayload::decode(&b[1..])?) + } + Cid::DataFragment => Payload::DataFragment(DataFragmentPayload::decode(&b[1..])?), + }) + } + + pub fn to_vec(&self) -> Result> { + let mut out = vec![self.cid().to_u8()]; + + match self { + Self::PackageVersionReq => {} + Self::PackageVersionAns(pl) => out.extend_from_slice(&pl.encode()?), + Self::FragSessionStatusReq(pl) => out.extend_from_slice(&pl.encode()?), + Self::FragSessionStatusAns(pl) => out.extend_from_slice(&pl.encode()?), + Self::FragSessionSetupReq(pl) => out.extend_from_slice(&pl.encode()?), + Self::FragSessionSetupAns(pl) => out.extend_from_slice(&pl.encode()?), + Self::FragSessionDeleteReq(pl) => out.extend_from_slice(&pl.encode()?), + Self::FragSessionDeleteAns(pl) => out.extend_from_slice(&pl.encode()?), + Self::DataFragment(pl) => out.extend_from_slice(&pl.encode()?), + } + + Ok(out) + } +} + +#[derive(Debug, PartialEq)] +pub struct PackageVersionAnsPayload { + pub package_identifier: u8, + pub package_version: u8, +} + +impl PayloadCodec for PackageVersionAnsPayload { + fn decode(b: &[u8]) -> Result { + if b.len() != 2 { + return Err(anyhow!("Expected 2 bytes")); + } + + Ok(PackageVersionAnsPayload { + package_identifier: b[0], + package_version: b[1], + }) + } + fn encode(&self) -> Result> { + Ok(vec![self.package_identifier, self.package_version]) + } +} + +#[derive(Debug, PartialEq)] +pub struct FragSessionStatusReqPayload { + pub participants: bool, + pub frag_index: u8, +} + +impl PayloadCodec for FragSessionStatusReqPayload { + fn decode(b: &[u8]) -> Result { + if b.len() != 1 { + return Err(anyhow!("Expected 1 byte")); + } + + Ok(FragSessionStatusReqPayload { + participants: b[0] & 0x01 != 0, + frag_index: (b[0] >> 1) & 0x03, + }) + } + + fn encode(&self) -> Result> { + if self.frag_index > 3 { + return Err(anyhow!("Max frag_index value is 3")); + } + + let mut b = vec![self.frag_index << 1]; + if self.participants { + b[0] |= 0x01; + } + + Ok(b) + } +} + +#[derive(Debug, PartialEq)] +pub struct FragSessionStatusAnsPayload { + pub received_and_index: FragSessionStatusAnsPayloadReceivedAndIndex, + pub missing_frag: u8, + pub status: FragSessionStatusAnsPayloadStatus, +} + +impl PayloadCodec for FragSessionStatusAnsPayload { + fn decode(b: &[u8]) -> Result { + if b.len() != 4 { + return Err(anyhow!("Expected 4 bytes")); + } + + Ok(FragSessionStatusAnsPayload { + received_and_index: FragSessionStatusAnsPayloadReceivedAndIndex { + nb_frag_received: { + let mut bytes = [0; 2]; + bytes.copy_from_slice(&b[0..2]); + u16::from_le_bytes(bytes) & 0x3fff + }, + frag_index: b[1] >> 6, + }, + missing_frag: b[2], + status: FragSessionStatusAnsPayloadStatus { + not_enough_matrix_memory: b[3] & 0x01 != 0, + }, + }) + } + + fn encode(&self) -> Result> { + if self.received_and_index.nb_frag_received > 16383 { + return Err(anyhow!("Max nb_frag_received value us 16383")); + } + + if self.received_and_index.frag_index > 3 { + return Err(anyhow!("Max frag_index value is 3")); + } + + let mut b = Vec::with_capacity(4); + b.extend_from_slice(&self.received_and_index.nb_frag_received.to_le_bytes()); + b[1] |= self.received_and_index.frag_index << 6; + + b.push(self.missing_frag); + + b.push(0x00); + if self.status.not_enough_matrix_memory { + b[3] |= 0x01; + } + + Ok(b) + } +} + +#[derive(Debug, PartialEq)] +pub struct FragSessionStatusAnsPayloadStatus { + pub not_enough_matrix_memory: bool, +} + +#[derive(Debug, PartialEq)] +pub struct FragSessionStatusAnsPayloadReceivedAndIndex { + pub nb_frag_received: u16, + pub frag_index: u8, +} + +#[derive(Debug, PartialEq)] +pub struct FragSessionSetupReqPayload { + pub frag_session: FragSessionSetuReqPayloadFragSession, + pub nb_frag: u16, + pub frag_size: u8, + pub control: FragSessionSetuReqPayloadControl, + pub padding: u8, + pub descriptor: [u8; 4], +} + +impl PayloadCodec for FragSessionSetupReqPayload { + fn decode(b: &[u8]) -> Result { + if b.len() != 10 { + return Err(anyhow!("Expected 10 bytes")); + } + + Ok(FragSessionSetupReqPayload { + frag_session: FragSessionSetuReqPayloadFragSession { + mc_group_bit_mask: { + let mut mask = [false; 4]; + for (i, v) in mask.iter_mut().enumerate() { + *v = b[0] & 1 << i != 0; + } + mask + }, + frag_index: (b[0] >> 4) & 0x03, + }, + nb_frag: { + let mut bytes = [0; 2]; + bytes.copy_from_slice(&b[1..3]); + u16::from_le_bytes(bytes) + }, + frag_size: b[3], + control: FragSessionSetuReqPayloadControl { + block_ack_delay: b[4] & 0x07, + fragmentation_matrix: (b[4] >> 3) & 0x07, + }, + padding: b[5], + descriptor: { + let mut bytes = [0; 4]; + bytes.copy_from_slice(&b[6..10]); + bytes + }, + }) + } + + fn encode(&self) -> Result> { + if self.frag_session.frag_index > 3 { + return Err(anyhow!("Max frag_index value is 3")); + } + + if self.control.block_ack_delay > 7 { + return Err(anyhow!("Max block_ack_delay value is 7")); + } + + if self.control.fragmentation_matrix > 7 { + return Err(anyhow!("Max fragmentation_matrix value is 7")); + } + + let mut b = Vec::with_capacity(10); + + b.push(self.frag_session.frag_index << 4); + for (i, v) in self.frag_session.mc_group_bit_mask.iter().enumerate() { + if *v { + b[0] |= 1 << i; + } + } + b.extend_from_slice(&self.nb_frag.to_le_bytes()); + b.push(self.frag_size); + b.push(self.control.block_ack_delay | (self.control.fragmentation_matrix << 3)); + b.push(self.padding); + b.extend_from_slice(&self.descriptor); + + Ok(b) + } +} + +#[derive(Debug, PartialEq)] +pub struct FragSessionSetuReqPayloadFragSession { + pub mc_group_bit_mask: [bool; 4], + pub frag_index: u8, +} + +#[derive(Debug, PartialEq)] +pub struct FragSessionSetuReqPayloadControl { + pub block_ack_delay: u8, + pub fragmentation_matrix: u8, +} + +#[derive(Debug, PartialEq)] +pub struct FragSessionSetupAnsPayload { + pub encoding_unsupported: bool, + pub not_enough_memory: bool, + pub frag_session_index_not_supported: bool, + pub wrong_descriptor: bool, + pub frag_index: u8, +} + +impl PayloadCodec for FragSessionSetupAnsPayload { + fn decode(b: &[u8]) -> Result { + if b.len() != 1 { + return Err(anyhow!("Expected 1 byte")); + } + + Ok(FragSessionSetupAnsPayload { + encoding_unsupported: b[0] & 0x01 != 0, + not_enough_memory: b[0] & 0x02 != 0, + frag_session_index_not_supported: b[0] & 0x04 != 0, + wrong_descriptor: b[0] & 0x08 != 0, + frag_index: (b[0] >> 6), + }) + } + + fn encode(&self) -> Result> { + if self.frag_index > 3 { + return Err(anyhow!("Max frag_index value is 3")); + } + + let mut b = vec![self.frag_index << 6]; + if self.encoding_unsupported { + b[0] |= 0x01; + } + if self.not_enough_memory { + b[0] |= 0x02; + } + if self.frag_session_index_not_supported { + b[0] |= 0x04; + } + if self.wrong_descriptor { + b[0] |= 0x08; + } + + Ok(b) + } +} + +#[derive(Debug, PartialEq)] +pub struct FragSessionDeleteReqPayload { + pub frag_index: u8, +} + +impl PayloadCodec for FragSessionDeleteReqPayload { + fn decode(b: &[u8]) -> Result { + if b.len() != 1 { + return Err(anyhow!("Expected 1 byte")); + } + + Ok(FragSessionDeleteReqPayload { + frag_index: b[0] & 0x03, + }) + } + + fn encode(&self) -> Result> { + if self.frag_index > 3 { + return Err(anyhow!("Max frag_index value is 3")); + } + + Ok(vec![self.frag_index]) + } +} + +#[derive(Debug, PartialEq)] +pub struct FragSessionDeleteAnsPayload { + pub frag_index: u8, + pub session_does_not_exist: bool, +} + +impl PayloadCodec for FragSessionDeleteAnsPayload { + fn decode(b: &[u8]) -> Result { + if b.len() != 1 { + return Err(anyhow!("Expected 1 byte")); + } + + Ok(FragSessionDeleteAnsPayload { + frag_index: b[0] & 0x03, + session_does_not_exist: b[0] & 0x04 != 0, + }) + } + + fn encode(&self) -> Result> { + if self.frag_index > 3 { + return Err(anyhow!("Max frag_index value is 3")); + } + + let mut b = vec![self.frag_index]; + if self.session_does_not_exist { + b[0] |= 0x04; + } + + Ok(b) + } +} + +#[derive(Debug, PartialEq)] +pub struct DataFragmentPayload { + pub index_and_n: DataFragmentPayloadIndexAndN, + pub data: Vec, +} + +impl PayloadCodec for DataFragmentPayload { + fn decode(b: &[u8]) -> Result { + if b.len() < 2 { + return Err(anyhow!("At least 2 bytes expected")); + } + + Ok(DataFragmentPayload { + index_and_n: DataFragmentPayloadIndexAndN { + n: { + let mut bytes = [0; 2]; + bytes.copy_from_slice(&b[0..2]); + u16::from_le_bytes(bytes) & 0x3fff + }, + frag_index: b[1] >> 6, + }, + data: b[2..].to_vec(), + }) + } + + fn encode(&self) -> Result> { + if self.index_and_n.n > 16383 { + return Err(anyhow!("Max n value us 16383")); + } + + if self.index_and_n.frag_index > 3 { + return Err(anyhow!("Max frag_index value is 3")); + } + + let mut b = Vec::with_capacity(2 + self.data.len()); + b.extend_from_slice(&self.index_and_n.n.to_le_bytes()); + b[1] |= self.index_and_n.frag_index << 6; + b.extend_from_slice(&self.data); + + Ok(b) + } +} + +#[derive(Debug, PartialEq)] +pub struct DataFragmentPayloadIndexAndN { + pub n: u16, + pub frag_index: u8, +} + +#[cfg(test)] +mod test { + use super::*; + + struct CommandTest { + name: String, + uplink: bool, + command: Payload, + bytes: Vec, + expected_error: Option, + } + + #[test] + fn test_package_version_req() { + let encode_tests = [CommandTest { + name: "encode PackageVersionReq".into(), + uplink: false, + command: Payload::PackageVersionReq, + bytes: vec![0x00], + expected_error: None, + }]; + + let decode_tests = [CommandTest { + name: "decode PackageVersionReq".into(), + uplink: false, + command: Payload::PackageVersionReq, + bytes: vec![0x00], + expected_error: None, + }]; + + run_tests_encode(&encode_tests); + run_tests_decode(&decode_tests); + } + + #[test] + fn test_frag_session_status_req() { + let encode_tests = [CommandTest { + name: "encode FragSessionStatusReq".into(), + uplink: false, + command: Payload::FragSessionStatusReq(FragSessionStatusReqPayload { + participants: true, + frag_index: 2, + }), + bytes: vec![0x01, 0x05], + expected_error: None, + }]; + + let decode_tests = [CommandTest { + name: "decode FragSessionStatusReq".into(), + uplink: false, + command: Payload::FragSessionStatusReq(FragSessionStatusReqPayload { + participants: true, + frag_index: 2, + }), + bytes: vec![0x01, 0x05], + expected_error: None, + }]; + + run_tests_encode(&encode_tests); + run_tests_decode(&decode_tests); + } + + #[test] + fn test_frag_session_status_ans() { + let encode_tests = [CommandTest { + name: "encode FragSessionStatusAns".into(), + uplink: true, + command: Payload::FragSessionStatusAns(FragSessionStatusAnsPayload { + received_and_index: FragSessionStatusAnsPayloadReceivedAndIndex { + nb_frag_received: 1024, + frag_index: 3, + }, + missing_frag: 128, + status: FragSessionStatusAnsPayloadStatus { + not_enough_matrix_memory: true, + }, + }), + bytes: vec![0x01, 0x00, 0xc4, 0x80, 0x01], + expected_error: None, + }]; + + let decode_tests = [CommandTest { + name: "decode FragSessionStatusAns".into(), + uplink: true, + command: Payload::FragSessionStatusAns(FragSessionStatusAnsPayload { + received_and_index: FragSessionStatusAnsPayloadReceivedAndIndex { + nb_frag_received: 1024, + frag_index: 3, + }, + missing_frag: 128, + status: FragSessionStatusAnsPayloadStatus { + not_enough_matrix_memory: true, + }, + }), + bytes: vec![0x01, 0x00, 0xc4, 0x80, 0x01], + expected_error: None, + }]; + + run_tests_encode(&encode_tests); + run_tests_decode(&decode_tests); + } + + #[test] + fn test_frag_session_setup_req() { + let encode_tests = [CommandTest { + name: "encode FragSessionSetupReq".into(), + uplink: false, + command: Payload::FragSessionSetupReq(FragSessionSetupReqPayload { + frag_session: FragSessionSetuReqPayloadFragSession { + mc_group_bit_mask: [true, false, false, false], + frag_index: 3, + }, + nb_frag: 1024, + frag_size: 128, + control: FragSessionSetuReqPayloadControl { + block_ack_delay: 5, + fragmentation_matrix: 1, + }, + padding: 64, + descriptor: [0x01, 0x02, 0x03, 0x04], + }), + bytes: vec![ + 0x02, 0x31, 0x00, 0x04, 0x80, 0x0d, 0x40, 0x01, 0x02, 0x03, 0x04, + ], + expected_error: None, + }]; + + let decode_tests = [CommandTest { + name: "encode FragSessionSetupReq".into(), + uplink: false, + command: Payload::FragSessionSetupReq(FragSessionSetupReqPayload { + frag_session: FragSessionSetuReqPayloadFragSession { + mc_group_bit_mask: [true, false, false, false], + frag_index: 3, + }, + nb_frag: 1024, + frag_size: 128, + control: FragSessionSetuReqPayloadControl { + block_ack_delay: 5, + fragmentation_matrix: 1, + }, + padding: 64, + descriptor: [0x01, 0x02, 0x03, 0x04], + }), + bytes: vec![ + 0x02, 0x31, 0x00, 0x04, 0x80, 0x0d, 0x40, 0x01, 0x02, 0x03, 0x04, + ], + expected_error: None, + }]; + + run_tests_encode(&encode_tests); + run_tests_decode(&decode_tests); + } + + #[test] + fn test_frag_session_setup_ans() { + let encode_tests = [CommandTest { + name: "encode FragSessionSetupAns".into(), + uplink: true, + command: Payload::FragSessionSetupAns(FragSessionSetupAnsPayload { + encoding_unsupported: true, + not_enough_memory: true, + frag_session_index_not_supported: false, + wrong_descriptor: true, + frag_index: 2, + }), + bytes: vec![0x02, 0x8B], + expected_error: None, + }]; + + let decode_tests = [CommandTest { + name: "decode FragSessionSetupAns".into(), + uplink: true, + command: Payload::FragSessionSetupAns(FragSessionSetupAnsPayload { + encoding_unsupported: true, + not_enough_memory: true, + frag_session_index_not_supported: false, + wrong_descriptor: true, + frag_index: 2, + }), + bytes: vec![0x02, 0x8B], + expected_error: None, + }]; + + run_tests_encode(&encode_tests); + run_tests_decode(&decode_tests); + } + + #[test] + fn test_frag_session_delete_req() { + let encode_tests = [CommandTest { + name: "encode FragSessionDelete".into(), + uplink: false, + command: Payload::FragSessionDeleteReq(FragSessionDeleteReqPayload { frag_index: 3 }), + bytes: vec![0x03, 0x03], + expected_error: None, + }]; + + let decode_tests = [CommandTest { + name: "decode FragSessionDelete".into(), + uplink: false, + command: Payload::FragSessionDeleteReq(FragSessionDeleteReqPayload { frag_index: 3 }), + bytes: vec![0x03, 0x03], + expected_error: None, + }]; + + run_tests_encode(&encode_tests); + run_tests_decode(&decode_tests); + } + + #[test] + fn test_frag_session_delete_ans() { + let encode_tests = [CommandTest { + name: "encode FragSessionDeleteAns".into(), + uplink: true, + command: Payload::FragSessionDeleteAns(FragSessionDeleteAnsPayload { + frag_index: 3, + session_does_not_exist: true, + }), + bytes: vec![0x03, 0x07], + expected_error: None, + }]; + + let decode_tests = [CommandTest { + name: "decode FragSessionDeleteAns".into(), + uplink: true, + command: Payload::FragSessionDeleteAns(FragSessionDeleteAnsPayload { + frag_index: 3, + session_does_not_exist: true, + }), + bytes: vec![0x03, 0x07], + expected_error: None, + }]; + + run_tests_encode(&encode_tests); + run_tests_decode(&decode_tests); + } + + #[test] + fn test_data_fragment() { + let encode_tests = [CommandTest { + name: "encode DataFragment".into(), + uplink: false, + command: Payload::DataFragment(DataFragmentPayload { + index_and_n: DataFragmentPayloadIndexAndN { + n: 1024, + frag_index: 2, + }, + data: vec![0x01, 0x02, 0x03, 0x04], + }), + bytes: vec![0x08, 0x00, 0x84, 0x01, 0x02, 0x03, 0x04], + expected_error: None, + }]; + + let decode_tests = [CommandTest { + name: "decode DataFragment".into(), + uplink: false, + command: Payload::DataFragment(DataFragmentPayload { + index_and_n: DataFragmentPayloadIndexAndN { + n: 1024, + frag_index: 2, + }, + data: vec![0x01, 0x02, 0x03, 0x04], + }), + bytes: vec![0x08, 0x00, 0x84, 0x01, 0x02, 0x03, 0x04], + expected_error: None, + }]; + + run_tests_encode(&encode_tests); + run_tests_decode(&decode_tests); + } + + fn run_tests_encode(tests: &[CommandTest]) { + for tst in tests { + println!("> {}", tst.name); + let resp = tst.command.to_vec(); + if let Some(e) = &tst.expected_error { + assert!(resp.is_err()); + assert_eq!(e, &resp.err().unwrap().to_string()); + } else { + assert_eq!(tst.bytes, resp.unwrap()); + } + } + } + + fn run_tests_decode(tests: &[CommandTest]) { + for tst in tests { + println!("> {}", tst.name); + let resp = Payload::from_slice(tst.uplink, &tst.bytes); + if let Some(e) = &tst.expected_error { + assert!(resp.is_err()); + assert_eq!(e, &resp.err().unwrap().to_string()); + } else { + assert_eq!(tst.command, resp.unwrap()); + } + } + } +} diff --git a/lrwn/src/applayer/mod.rs b/lrwn/src/applayer/mod.rs index 83872ec1..af49044a 100644 --- a/lrwn/src/applayer/mod.rs +++ b/lrwn/src/applayer/mod.rs @@ -1,6 +1,7 @@ use anyhow::Result; pub mod clocksync; +pub mod fragmentation; pub mod multicastsetup; pub trait PayloadCodec { diff --git a/lrwn/src/applayer/multicastsetup/v1.rs b/lrwn/src/applayer/multicastsetup/v1.rs index a3742230..feae43c9 100644 --- a/lrwn/src/applayer/multicastsetup/v1.rs +++ b/lrwn/src/applayer/multicastsetup/v1.rs @@ -149,7 +149,7 @@ impl Payload { Self::McClassCSessionAns(pl) => out.extend_from_slice(&pl.encode()?), Self::McClassBSessionReq(pl) => out.extend_from_slice(&pl.encode()?), Self::McClassBSessionAns(pl) => out.extend_from_slice(&pl.encode()?), - }; + } Ok(out) }