diff --git a/lrwn/src/applayer/mod.rs b/lrwn/src/applayer/mod.rs
index ee002523..83872ec1 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 multicastsetup;
 
 pub trait PayloadCodec<Struct = Self> {
     fn decode(b: &[u8]) -> Result<Struct>;
diff --git a/lrwn/src/applayer/multicastsetup/mod.rs b/lrwn/src/applayer/multicastsetup/mod.rs
new file mode 100644
index 00000000..a3a6d96c
--- /dev/null
+++ b/lrwn/src/applayer/multicastsetup/mod.rs
@@ -0,0 +1 @@
+pub mod v1;
diff --git a/lrwn/src/applayer/multicastsetup/v1.rs b/lrwn/src/applayer/multicastsetup/v1.rs
new file mode 100644
index 00000000..a3742230
--- /dev/null
+++ b/lrwn/src/applayer/multicastsetup/v1.rs
@@ -0,0 +1,1283 @@
+use anyhow::Result;
+
+use crate::applayer::PayloadCodec;
+use crate::DevAddr;
+
+pub enum Cid {
+    PackageVersionReq,
+    PackageVersionAns,
+    McGroupStatusReq,
+    McGroupStatusAns,
+    McGroupSetupReq,
+    McGroupSetupAns,
+    McGroupDeleteReq,
+    McGroupDeleteAns,
+    McClassCSessionReq,
+    McClassCSessionAns,
+    McClassBSessionReq,
+    McClassBSessionAns,
+}
+
+impl Cid {
+    pub fn from_u8(uplink: bool, value: u8) -> Result<Self> {
+        Ok(match uplink {
+            true => match value {
+                0x00 => Cid::PackageVersionAns,
+                0x01 => Cid::McGroupStatusAns,
+                0x02 => Cid::McGroupSetupAns,
+                0x03 => Cid::McGroupDeleteAns,
+                0x04 => Cid::McClassCSessionAns,
+                0x05 => Cid::McClassBSessionAns,
+                _ => return Err(anyhow!("Invalid CID: {}", value)),
+            },
+            false => match value {
+                0x00 => Cid::PackageVersionReq,
+                0x01 => Cid::McGroupStatusReq,
+                0x02 => Cid::McGroupSetupReq,
+                0x03 => Cid::McGroupDeleteReq,
+                0x04 => Cid::McClassCSessionReq,
+                0x05 => Cid::McClassBSessionReq,
+                _ => return Err(anyhow!("Invalid CID: {}", value)),
+            },
+        })
+    }
+
+    pub fn to_u8(&self) -> u8 {
+        match self {
+            Cid::PackageVersionReq | Cid::PackageVersionAns => 0x00,
+            Cid::McGroupStatusReq | Cid::McGroupStatusAns => 0x01,
+            Cid::McGroupSetupReq | Cid::McGroupSetupAns => 0x02,
+            Cid::McGroupDeleteReq | Cid::McGroupDeleteAns => 0x03,
+            Cid::McClassCSessionReq | Cid::McClassCSessionAns => 0x04,
+            Cid::McClassBSessionReq | Cid::McClassBSessionAns => 0x05,
+        }
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub enum Payload {
+    PackageVersionReq,
+    PackageVersionAns(PackageVersionAnsPayload),
+    McGroupStatusReq(McGroupStatusReqPayload),
+    McGroupStatusAns(McGroupStatusAnsPayload),
+    McGroupSetupReq(McGroupSetupReqPayload),
+    McGroupSetupAns(McGroupSetupAnsPayload),
+    McGroupDeleteReq(McGroupDeleteReqPayload),
+    McGroupDeleteAns(McGroupDeleteAnsPayload),
+    McClassCSessionReq(McClassCSessionReqPayload),
+    McClassCSessionAns(McClassCSessionAnsPayload),
+    McClassBSessionReq(McClassBSessionReqPayload),
+    McClassBSessionAns(McClassBSessionAnsPayload),
+}
+
+impl Payload {
+    pub fn cid(&self) -> Cid {
+        match self {
+            Self::PackageVersionReq => Cid::PackageVersionReq,
+            Self::PackageVersionAns(_) => Cid::PackageVersionAns,
+            Self::McGroupStatusReq(_) => Cid::McGroupStatusReq,
+            Self::McGroupStatusAns(_) => Cid::McGroupStatusAns,
+            Self::McGroupSetupReq(_) => Cid::McGroupSetupReq,
+            Self::McGroupSetupAns(_) => Cid::McGroupSetupAns,
+            Self::McGroupDeleteReq(_) => Cid::McGroupDeleteReq,
+            Self::McGroupDeleteAns(_) => Cid::McGroupDeleteAns,
+            Self::McClassCSessionReq(_) => Cid::McClassCSessionReq,
+            Self::McClassCSessionAns(_) => Cid::McClassCSessionAns,
+            Self::McClassBSessionReq(_) => Cid::McClassBSessionReq,
+            Self::McClassBSessionAns(_) => Cid::McClassBSessionAns,
+        }
+    }
+
+    pub fn from_slice(uplink: bool, b: &[u8]) -> Result<Self> {
+        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::McGroupStatusReq => {
+                Payload::McGroupStatusReq(McGroupStatusReqPayload::decode(&b[1..])?)
+            }
+            Cid::McGroupStatusAns => {
+                Payload::McGroupStatusAns(McGroupStatusAnsPayload::decode(&b[1..])?)
+            }
+            Cid::McGroupSetupReq => {
+                Payload::McGroupSetupReq(McGroupSetupReqPayload::decode(&b[1..])?)
+            }
+            Cid::McGroupSetupAns => {
+                Payload::McGroupSetupAns(McGroupSetupAnsPayload::decode(&b[1..])?)
+            }
+            Cid::McGroupDeleteReq => {
+                Payload::McGroupDeleteReq(McGroupDeleteReqPayload::decode(&b[1..])?)
+            }
+            Cid::McGroupDeleteAns => {
+                Payload::McGroupDeleteAns(McGroupDeleteAnsPayload::decode(&b[1..])?)
+            }
+            Cid::McClassCSessionReq => {
+                Payload::McClassCSessionReq(McClassCSessionReqPayload::decode(&b[1..])?)
+            }
+            Cid::McClassCSessionAns => {
+                Payload::McClassCSessionAns(McClassCSessionAnsPayload::decode(&b[1..])?)
+            }
+            Cid::McClassBSessionReq => {
+                Payload::McClassBSessionReq(McClassBSessionReqPayload::decode(&b[1..])?)
+            }
+            Cid::McClassBSessionAns => {
+                Payload::McClassBSessionAns(McClassBSessionAnsPayload::decode(&b[1..])?)
+            }
+        })
+    }
+
+    pub fn to_vec(&self) -> Result<Vec<u8>> {
+        let mut out = vec![self.cid().to_u8()];
+
+        match self {
+            Self::PackageVersionReq => {}
+            Self::PackageVersionAns(pl) => out.extend_from_slice(&pl.encode()?),
+            Self::McGroupStatusReq(pl) => out.extend_from_slice(&pl.encode()?),
+            Self::McGroupStatusAns(pl) => out.extend_from_slice(&pl.encode()?),
+            Self::McGroupSetupReq(pl) => out.extend_from_slice(&pl.encode()?),
+            Self::McGroupSetupAns(pl) => out.extend_from_slice(&pl.encode()?),
+            Self::McGroupDeleteReq(pl) => out.extend_from_slice(&pl.encode()?),
+            Self::McGroupDeleteAns(pl) => out.extend_from_slice(&pl.encode()?),
+            Self::McClassCSessionReq(pl) => out.extend_from_slice(&pl.encode()?),
+            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)
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub struct PackageVersionAnsPayload {
+    pub package_identifier: u8,
+    pub package_version: u8,
+}
+
+impl PayloadCodec for PackageVersionAnsPayload {
+    fn decode(b: &[u8]) -> Result<Self> {
+        if b.len() != 2 {
+            return Err(anyhow!("Expected 2 bytes"));
+        }
+
+        Ok(PackageVersionAnsPayload {
+            package_identifier: b[0],
+            package_version: b[1],
+        })
+    }
+    fn encode(&self) -> Result<Vec<u8>> {
+        Ok(vec![self.package_identifier, self.package_version])
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub struct McGroupStatusReqPayload {
+    pub cmd_mask: McGroupStatusReqPayloadCmdMask,
+}
+
+impl PayloadCodec for McGroupStatusReqPayload {
+    fn decode(b: &[u8]) -> Result<Self> {
+        if b.len() != 1 {
+            return Err(anyhow!("Expected 1 byte"));
+        }
+
+        let mut out = McGroupStatusReqPayload {
+            cmd_mask: McGroupStatusReqPayloadCmdMask {
+                req_group_mask: [false; 4],
+            },
+        };
+
+        for (i, v) in out.cmd_mask.req_group_mask.iter_mut().enumerate() {
+            *v = b[0] & (1 << i) != 0;
+        }
+
+        Ok(out)
+    }
+
+    fn encode(&self) -> Result<Vec<u8>> {
+        let mut out = vec![0; 1];
+        for (i, v) in self.cmd_mask.req_group_mask.iter().enumerate() {
+            if *v {
+                out[0] |= 1 << i;
+            }
+        }
+        Ok(out)
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub struct McGroupStatusReqPayloadCmdMask {
+    pub req_group_mask: [bool; 4],
+}
+
+#[derive(Debug, PartialEq)]
+pub struct McGroupStatusAnsPayload {
+    pub status: McGroupStatusAnsPayloadStatus,
+    pub items: Vec<McGroupStatusAnsPayloadItem>,
+}
+
+impl PayloadCodec for McGroupStatusAnsPayload {
+    fn decode(b: &[u8]) -> Result<Self> {
+        if b.is_empty() {
+            return Err(anyhow!("At least 1 byte is expected"));
+        }
+
+        if (b.len() - 1) % 5 != 0 {
+            return Err(anyhow!("Expected a multiple of 5 bytes + 1"));
+        }
+
+        Ok(McGroupStatusAnsPayload {
+            status: McGroupStatusAnsPayloadStatus {
+                ans_group_mask: {
+                    let mut mask = [false; 4];
+                    for (i, v) in mask.iter_mut().enumerate() {
+                        *v = b[0] & (1 << i) != 0;
+                    }
+                    mask
+                },
+                nb_total_groups: (b[0] >> 4) & 0x07,
+            },
+            items: b[1..]
+                .chunks(5)
+                .map(|b| {
+                    let mut mc_addr = [0; 4];
+                    mc_addr.copy_from_slice(&b[1..5]);
+                    McGroupStatusAnsPayloadItem {
+                        mc_group_id: b[0],
+                        mc_addr: DevAddr::from_le_bytes(mc_addr),
+                    }
+                })
+                .collect(),
+        })
+    }
+
+    fn encode(&self) -> Result<Vec<u8>> {
+        let mut b = Vec::with_capacity(1 + (self.items.len() * 5));
+
+        b.push(self.status.nb_total_groups << 4);
+        for (i, v) in self.status.ans_group_mask.iter().enumerate() {
+            if *v {
+                b[0] |= 1 << i;
+            }
+        }
+
+        for v in &self.items {
+            b.push(v.mc_group_id);
+            b.extend_from_slice(&v.mc_addr.to_le_bytes());
+        }
+
+        Ok(b)
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub struct McGroupStatusAnsPayloadStatus {
+    pub ans_group_mask: [bool; 4],
+    pub nb_total_groups: u8,
+}
+
+#[derive(Debug, PartialEq)]
+pub struct McGroupStatusAnsPayloadItem {
+    pub mc_group_id: u8,
+    pub mc_addr: DevAddr,
+}
+
+#[derive(Debug, PartialEq)]
+pub struct McGroupSetupReqPayload {
+    pub mc_group_id_header: McGroupSetupReqPayloadMcGroupIdHeader,
+    pub mc_addr: DevAddr,
+    pub mc_key_encrypted: [u8; 16],
+    pub min_mc_f_count: u32,
+    pub max_mc_f_count: u32,
+}
+
+impl PayloadCodec for McGroupSetupReqPayload {
+    fn decode(b: &[u8]) -> Result<Self> {
+        if b.len() != 29 {
+            return Err(anyhow!("Expected 29 bytes"));
+        }
+
+        Ok(McGroupSetupReqPayload {
+            mc_group_id_header: McGroupSetupReqPayloadMcGroupIdHeader {
+                mc_group_id: b[0] & 0x03,
+            },
+            mc_addr: {
+                let mut mc_addr = [0; 4];
+                mc_addr.copy_from_slice(&b[1..5]);
+                DevAddr::from_le_bytes(mc_addr)
+            },
+            mc_key_encrypted: {
+                let mut mc_key_encrypted = [0; 16];
+                mc_key_encrypted.copy_from_slice(&b[5..21]);
+                mc_key_encrypted
+            },
+            min_mc_f_count: {
+                let mut f_count = [0; 4];
+                f_count.copy_from_slice(&b[21..25]);
+                u32::from_le_bytes(f_count)
+            },
+            max_mc_f_count: {
+                let mut f_count = [0; 4];
+                f_count.copy_from_slice(&b[25..29]);
+                u32::from_le_bytes(f_count)
+            },
+        })
+    }
+
+    fn encode(&self) -> Result<Vec<u8>> {
+        if self.mc_group_id_header.mc_group_id > 3 {
+            return Err(anyhow!("Max mc_group_id value is 3"));
+        }
+
+        let mut b = Vec::with_capacity(29);
+        b.push(self.mc_group_id_header.mc_group_id);
+        b.extend_from_slice(&self.mc_addr.to_le_bytes());
+        b.extend_from_slice(&self.mc_key_encrypted);
+        b.extend_from_slice(&self.min_mc_f_count.to_le_bytes());
+        b.extend_from_slice(&self.max_mc_f_count.to_le_bytes());
+
+        Ok(b)
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub struct McGroupSetupReqPayloadMcGroupIdHeader {
+    pub mc_group_id: u8,
+}
+
+#[derive(Debug, PartialEq)]
+pub struct McGroupSetupAnsPayload {
+    pub mc_group_id_header: McGroupSetupAnsPayloadMcGroupIdHeader,
+}
+
+impl PayloadCodec for McGroupSetupAnsPayload {
+    fn decode(b: &[u8]) -> Result<Self> {
+        if b.len() != 1 {
+            return Err(anyhow!("Expected 1 byte"));
+        }
+
+        Ok(McGroupSetupAnsPayload {
+            mc_group_id_header: McGroupSetupAnsPayloadMcGroupIdHeader {
+                mc_group_id: b[0] & 0x03,
+                id_error: b[0] & 0x04 != 0,
+            },
+        })
+    }
+
+    fn encode(&self) -> Result<Vec<u8>> {
+        if self.mc_group_id_header.mc_group_id > 3 {
+            return Err(anyhow!("Max mc_group_id value is 3"));
+        }
+
+        let mut b = vec![self.mc_group_id_header.mc_group_id];
+        if self.mc_group_id_header.id_error {
+            b[0] |= 0x04;
+        }
+
+        Ok(b)
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub struct McGroupSetupAnsPayloadMcGroupIdHeader {
+    pub mc_group_id: u8,
+    pub id_error: bool,
+}
+
+#[derive(Debug, PartialEq)]
+pub struct McGroupDeleteReqPayload {
+    pub mc_group_id_header: McGroupDeleteReqPayloadMcGroupIdHeader,
+}
+
+impl PayloadCodec for McGroupDeleteReqPayload {
+    fn decode(b: &[u8]) -> Result<Self> {
+        if b.len() != 1 {
+            return Err(anyhow!("Expected 1 byte"));
+        }
+
+        Ok(McGroupDeleteReqPayload {
+            mc_group_id_header: McGroupDeleteReqPayloadMcGroupIdHeader {
+                mc_group_id: b[0] & 0x03,
+            },
+        })
+    }
+
+    fn encode(&self) -> Result<Vec<u8>> {
+        if self.mc_group_id_header.mc_group_id > 3 {
+            return Err(anyhow!("Max mc_group_id value is 3"));
+        }
+
+        Ok(vec![self.mc_group_id_header.mc_group_id])
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub struct McGroupDeleteReqPayloadMcGroupIdHeader {
+    pub mc_group_id: u8,
+}
+
+#[derive(Debug, PartialEq)]
+pub struct McGroupDeleteAnsPayload {
+    pub mc_group_id_header: McGroupDeleteAnsPayloadMcGroupIdHeader,
+}
+
+impl PayloadCodec for McGroupDeleteAnsPayload {
+    fn decode(b: &[u8]) -> Result<Self> {
+        if b.len() != 1 {
+            return Err(anyhow!("Expected 1 byte"));
+        }
+
+        Ok(McGroupDeleteAnsPayload {
+            mc_group_id_header: McGroupDeleteAnsPayloadMcGroupIdHeader {
+                mc_group_id: b[0] & 0x03,
+                mc_group_undefined: b[0] & 0x04 != 0,
+            },
+        })
+    }
+
+    fn encode(&self) -> Result<Vec<u8>> {
+        if self.mc_group_id_header.mc_group_id > 3 {
+            return Err(anyhow!("Max mc_group_id value is 3"));
+        }
+
+        let mut b = vec![self.mc_group_id_header.mc_group_id];
+        if self.mc_group_id_header.mc_group_undefined {
+            b[0] |= 0x04;
+        }
+
+        Ok(b)
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub struct McGroupDeleteAnsPayloadMcGroupIdHeader {
+    pub mc_group_id: u8,
+    pub mc_group_undefined: bool,
+}
+
+#[derive(Debug, PartialEq)]
+pub struct McClassCSessionReqPayload {
+    pub mc_group_id_header: McClassCSessionReqPayloadMcGroupIdHeader,
+    pub session_time: u32,
+    pub session_time_out: McClassCSessionReqPayloadSessionTimeOut,
+    pub dl_frequ: u32,
+    pub dr: u8,
+}
+
+impl PayloadCodec for McClassCSessionReqPayload {
+    fn decode(b: &[u8]) -> Result<Self> {
+        if b.len() != 10 {
+            return Err(anyhow!("Expected 10 bytes"));
+        }
+
+        Ok(McClassCSessionReqPayload {
+            mc_group_id_header: McClassCSessionReqPayloadMcGroupIdHeader {
+                mc_group_id: b[0] & 0x03,
+            },
+            session_time: {
+                let mut bytes = [0; 4];
+                bytes.copy_from_slice(&b[1..5]);
+                u32::from_le_bytes(bytes)
+            },
+            session_time_out: McClassCSessionReqPayloadSessionTimeOut {
+                time_out: b[5] & 0x0f,
+            },
+            dl_frequ: {
+                let mut bytes = [0; 4];
+                bytes[0..3].copy_from_slice(&b[6..9]);
+                u32::from_le_bytes(bytes) * 100
+            },
+            dr: b[9],
+        })
+    }
+
+    fn encode(&self) -> Result<Vec<u8>> {
+        if self.mc_group_id_header.mc_group_id > 3 {
+            return Err(anyhow!("Max mc_group_id value is 3"));
+        }
+
+        if self.session_time_out.time_out > 15 {
+            return Err(anyhow!("Max time_out value is 15"));
+        }
+
+        if self.dl_frequ % 100 != 0 {
+            return Err(anyhow!("dl_frequ must be a multiple of 100"));
+        }
+
+        let mut b = Vec::with_capacity(10);
+        b.push(self.mc_group_id_header.mc_group_id);
+        b.extend_from_slice(&self.session_time.to_le_bytes());
+        b.push(self.session_time_out.time_out);
+        b.extend_from_slice(&(self.dl_frequ / 100).to_le_bytes()[0..3]);
+        b.push(self.dr);
+
+        Ok(b)
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub struct McClassCSessionReqPayloadMcGroupIdHeader {
+    pub mc_group_id: u8,
+}
+
+#[derive(Debug, PartialEq)]
+pub struct McClassCSessionReqPayloadSessionTimeOut {
+    pub time_out: u8,
+}
+
+#[derive(Debug, PartialEq)]
+pub struct McClassCSessionAnsPayload {
+    pub status_and_mc_group_id: McClassCSessionAnsPayloadStatusAnsMcGroupId,
+    pub time_to_start: Option<u32>,
+}
+
+impl PayloadCodec for McClassCSessionAnsPayload {
+    fn decode(b: &[u8]) -> Result<Self> {
+        if b.is_empty() {
+            return Err(anyhow!("At least 1 byte expected"));
+        }
+
+        Ok(McClassCSessionAnsPayload {
+            status_and_mc_group_id: McClassCSessionAnsPayloadStatusAnsMcGroupId {
+                mc_group_id: b[0] & 0x03,
+                dr_error: b[0] & 0x04 != 0,
+                freq_error: b[0] & 0x08 != 0,
+                mc_group_undefined: b[0] & 0x10 != 0,
+            },
+            time_to_start: if b[0] & 0x1c != 0 {
+                None
+            } else {
+                if b.len() != 4 {
+                    return Err(anyhow!("Expected 4 bytes"));
+                }
+
+                let mut bytes = [0; 4];
+                bytes[0..3].copy_from_slice(&b[1..4]);
+                Some(u32::from_le_bytes(bytes))
+            },
+        })
+    }
+
+    fn encode(&self) -> Result<Vec<u8>> {
+        if self.status_and_mc_group_id.mc_group_id > 3 {
+            return Err(anyhow!("Max mc_group_id value is 3"));
+        }
+
+        if let Some(v) = self.time_to_start {
+            if v >= (1 << 24) {
+                return Err(anyhow!("Max time_to_start is 1^24 - 1"));
+            }
+        }
+
+        let mut b = Vec::with_capacity(4);
+        b.push(self.status_and_mc_group_id.mc_group_id);
+        if self.status_and_mc_group_id.dr_error {
+            b[0] |= 0x04;
+        }
+        if self.status_and_mc_group_id.freq_error {
+            b[0] |= 0x08;
+        }
+        if self.status_and_mc_group_id.mc_group_undefined {
+            b[0] |= 0x10;
+        }
+
+        if let Some(v) = self.time_to_start {
+            b.extend_from_slice(&v.to_le_bytes()[0..3]);
+        }
+
+        Ok(b)
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub struct McClassCSessionAnsPayloadStatusAnsMcGroupId {
+    pub mc_group_id: u8,
+    pub dr_error: bool,
+    pub freq_error: bool,
+    pub mc_group_undefined: bool,
+}
+
+#[derive(Debug, PartialEq)]
+pub struct McClassBSessionReqPayload {
+    pub mc_group_id_header: McClassBSessionReqPayloadMcGroupIdHeader,
+    pub session_time: u32,
+    pub time_out_periodicity: McClassBSessionReqPayloadTimeOutPeriodicity,
+    pub dl_frequ: u32,
+    pub dr: u8,
+}
+
+impl PayloadCodec for McClassBSessionReqPayload {
+    fn decode(b: &[u8]) -> Result<Self> {
+        if b.len() != 10 {
+            return Err(anyhow!("Expected 10 bytes"));
+        }
+
+        Ok(McClassBSessionReqPayload {
+            mc_group_id_header: McClassBSessionReqPayloadMcGroupIdHeader {
+                mc_group_id: b[0] & 0x03,
+            },
+            session_time: {
+                let mut bytes = [0; 4];
+                bytes.copy_from_slice(&b[1..5]);
+                u32::from_le_bytes(bytes)
+            },
+            time_out_periodicity: McClassBSessionReqPayloadTimeOutPeriodicity {
+                time_out: b[5] & 0x0f,
+                periodicity: (b[5] >> 4) & 0x07,
+            },
+            dl_frequ: {
+                let mut bytes = [0; 4];
+                bytes[0..3].copy_from_slice(&b[6..9]);
+                u32::from_le_bytes(bytes) * 100
+            },
+            dr: b[9],
+        })
+    }
+
+    fn encode(&self) -> Result<Vec<u8>> {
+        if self.mc_group_id_header.mc_group_id > 3 {
+            return Err(anyhow!("Max mc_group_id value is 3"));
+        }
+
+        if self.time_out_periodicity.time_out > 15 {
+            return Err(anyhow!("Max time_out value is 15"));
+        }
+
+        if self.time_out_periodicity.periodicity > 7 {
+            return Err(anyhow!("Max periodicity value is 7"));
+        }
+
+        let mut b = Vec::with_capacity(10);
+        b.push(self.mc_group_id_header.mc_group_id);
+        b.extend_from_slice(&self.session_time.to_le_bytes());
+        b.push((self.time_out_periodicity.periodicity << 4) | self.time_out_periodicity.time_out);
+        b.extend_from_slice(&(self.dl_frequ / 100).to_le_bytes()[0..3]);
+        b.push(self.dr);
+
+        Ok(b)
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub struct McClassBSessionReqPayloadMcGroupIdHeader {
+    pub mc_group_id: u8,
+}
+
+#[derive(Debug, PartialEq)]
+pub struct McClassBSessionReqPayloadTimeOutPeriodicity {
+    pub time_out: u8,
+    pub periodicity: u8,
+}
+
+#[derive(Debug, PartialEq)]
+pub struct McClassBSessionAnsPayload {
+    pub status_and_mc_group_id: McClassBSessionAnsPayloadStatusAndMcGroupId,
+    pub time_to_start: Option<u32>,
+}
+
+#[derive(Debug, PartialEq)]
+pub struct McClassBSessionAnsPayloadStatusAndMcGroupId {
+    pub mc_group_id: u8,
+    pub dr_error: bool,
+    pub freq_error: bool,
+    pub mc_group_undefined: bool,
+}
+
+impl PayloadCodec for McClassBSessionAnsPayload {
+    fn decode(b: &[u8]) -> Result<Self> {
+        if b.is_empty() {
+            return Err(anyhow!("At least 1 byte expected"));
+        }
+
+        Ok(McClassBSessionAnsPayload {
+            status_and_mc_group_id: McClassBSessionAnsPayloadStatusAndMcGroupId {
+                mc_group_id: b[0] & 0x03,
+                dr_error: b[0] & 0x04 != 0,
+                freq_error: b[0] & 0x08 != 0,
+                mc_group_undefined: b[0] & 0x10 != 0,
+            },
+            time_to_start: if b[0] & 0x1c != 0 {
+                None
+            } else {
+                if b.len() != 4 {
+                    return Err(anyhow!("Expected 4 bytes"));
+                }
+
+                let mut bytes = [0; 4];
+                bytes[0..3].copy_from_slice(&b[1..4]);
+                Some(u32::from_le_bytes(bytes))
+            },
+        })
+    }
+
+    fn encode(&self) -> Result<Vec<u8>> {
+        if self.status_and_mc_group_id.mc_group_id > 3 {
+            return Err(anyhow!("Max mc_group_id value is 3"));
+        }
+
+        if let Some(v) = self.time_to_start {
+            if v >= (1 << 24) {
+                return Err(anyhow!("Max time_to_start is 1^24 - 1"));
+            }
+        }
+
+        let mut b = Vec::with_capacity(4);
+        b.push(self.status_and_mc_group_id.mc_group_id);
+
+        if self.status_and_mc_group_id.dr_error {
+            b[0] |= 0x04;
+        }
+        if self.status_and_mc_group_id.freq_error {
+            b[0] |= 0x08;
+        }
+        if self.status_and_mc_group_id.mc_group_undefined {
+            b[0] |= 0x10;
+        }
+
+        if let Some(v) = self.time_to_start {
+            b.extend_from_slice(&v.to_le_bytes()[0..3]);
+        }
+
+        Ok(b)
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    struct CommandTest {
+        name: String,
+        uplink: bool,
+        command: Payload,
+        bytes: Vec<u8>,
+        expected_error: Option<String>,
+    }
+
+    #[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_package_version_ans() {
+        let encode_tests = [CommandTest {
+            name: "encode PackageVersionAns".into(),
+            uplink: true,
+            command: Payload::PackageVersionAns(PackageVersionAnsPayload {
+                package_identifier: 1,
+                package_version: 1,
+            }),
+            bytes: vec![0x00, 0x01, 0x01],
+            expected_error: None,
+        }];
+
+        let decode_tests = [CommandTest {
+            name: "decode PackageVersionAns".into(),
+            uplink: true,
+            command: Payload::PackageVersionAns(PackageVersionAnsPayload {
+                package_identifier: 1,
+                package_version: 1,
+            }),
+            bytes: vec![0x00, 0x01, 0x01],
+            expected_error: None,
+        }];
+
+        run_tests_encode(&encode_tests);
+        run_tests_decode(&decode_tests);
+    }
+
+    #[test]
+    fn test_mc_group_status_req() {
+        let encode_tests = [CommandTest {
+            name: "encode McGroupStatusReq".into(),
+            uplink: false,
+            command: Payload::McGroupStatusReq(McGroupStatusReqPayload {
+                cmd_mask: McGroupStatusReqPayloadCmdMask {
+                    req_group_mask: [true, true, false, false],
+                },
+            }),
+            bytes: vec![0x01, 0x03],
+            expected_error: None,
+        }];
+
+        let decode_tests = [CommandTest {
+            name: "decode McGroupStatusReq".into(),
+            uplink: false,
+            command: Payload::McGroupStatusReq(McGroupStatusReqPayload {
+                cmd_mask: McGroupStatusReqPayloadCmdMask {
+                    req_group_mask: [true, true, false, false],
+                },
+            }),
+            bytes: vec![0x01, 0x03],
+            expected_error: None,
+        }];
+
+        run_tests_encode(&encode_tests);
+        run_tests_decode(&decode_tests);
+    }
+
+    #[test]
+    fn test_mc_group_status_ans() {
+        let encode_tests = [CommandTest {
+            name: "encode McGroupStatusAns".into(),
+            uplink: true,
+            command: Payload::McGroupStatusAns(McGroupStatusAnsPayload {
+                status: McGroupStatusAnsPayloadStatus {
+                    ans_group_mask: [true, true, false, false],
+                    nb_total_groups: 2,
+                },
+                items: vec![
+                    McGroupStatusAnsPayloadItem {
+                        mc_group_id: 0,
+                        mc_addr: DevAddr::from_be_bytes([1, 2, 3, 4]),
+                    },
+                    McGroupStatusAnsPayloadItem {
+                        mc_group_id: 1,
+                        mc_addr: DevAddr::from_be_bytes([2, 2, 3, 4]),
+                    },
+                ],
+            }),
+            bytes: vec![
+                0x01, 0x23, 0x00, 0x04, 0x03, 0x02, 0x01, 0x01, 0x04, 0x03, 0x02, 0x02,
+            ],
+            expected_error: None,
+        }];
+
+        let decode_tests = [CommandTest {
+            name: "decode McGroupStatusAns".into(),
+            uplink: true,
+            command: Payload::McGroupStatusAns(McGroupStatusAnsPayload {
+                status: McGroupStatusAnsPayloadStatus {
+                    ans_group_mask: [true, true, false, false],
+                    nb_total_groups: 2,
+                },
+                items: vec![
+                    McGroupStatusAnsPayloadItem {
+                        mc_group_id: 0,
+                        mc_addr: DevAddr::from_be_bytes([1, 2, 3, 4]),
+                    },
+                    McGroupStatusAnsPayloadItem {
+                        mc_group_id: 1,
+                        mc_addr: DevAddr::from_be_bytes([2, 2, 3, 4]),
+                    },
+                ],
+            }),
+            bytes: vec![
+                0x01, 0x23, 0x00, 0x04, 0x03, 0x02, 0x01, 0x01, 0x04, 0x03, 0x02, 0x02,
+            ],
+            expected_error: None,
+        }];
+
+        run_tests_encode(&encode_tests);
+        run_tests_decode(&decode_tests);
+    }
+
+    #[test]
+    fn test_mc_group_setup_req() {
+        let encode_tests = [CommandTest {
+            name: "encode McGroupSetupReq".into(),
+            uplink: false,
+            command: Payload::McGroupSetupReq(McGroupSetupReqPayload {
+                mc_group_id_header: McGroupSetupReqPayloadMcGroupIdHeader { mc_group_id: 2 },
+                mc_addr: DevAddr::from_be_bytes([1, 2, 3, 4]),
+                mc_key_encrypted: [1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8],
+                min_mc_f_count: 1024,
+                max_mc_f_count: 2048,
+            }),
+            bytes: vec![
+                0x02, 0x02, 0x04, 0x03, 0x02, 0x01, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+                0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08,
+                0x00, 0x00,
+            ],
+            expected_error: None,
+        }];
+
+        let decode_tests = [CommandTest {
+            name: "decode McGroupSetupReq".into(),
+            uplink: false,
+            command: Payload::McGroupSetupReq(McGroupSetupReqPayload {
+                mc_group_id_header: McGroupSetupReqPayloadMcGroupIdHeader { mc_group_id: 2 },
+                mc_addr: DevAddr::from_be_bytes([1, 2, 3, 4]),
+                mc_key_encrypted: [1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8],
+                min_mc_f_count: 1024,
+                max_mc_f_count: 2048,
+            }),
+            bytes: vec![
+                0x02, 0x02, 0x04, 0x03, 0x02, 0x01, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+                0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08,
+                0x00, 0x00,
+            ],
+            expected_error: None,
+        }];
+
+        run_tests_encode(&encode_tests);
+        run_tests_decode(&decode_tests);
+    }
+
+    #[test]
+    fn test_mc_group_setup_ans() {
+        let encode_tests = [CommandTest {
+            name: "encode McGroupSetupAns".into(),
+            uplink: true,
+            command: Payload::McGroupSetupAns(McGroupSetupAnsPayload {
+                mc_group_id_header: McGroupSetupAnsPayloadMcGroupIdHeader {
+                    mc_group_id: 2,
+                    id_error: true,
+                },
+            }),
+            bytes: vec![0x02, 0x06],
+            expected_error: None,
+        }];
+
+        let decode_tests = [CommandTest {
+            name: "decode McGroupSetupAns".into(),
+            uplink: true,
+            command: Payload::McGroupSetupAns(McGroupSetupAnsPayload {
+                mc_group_id_header: McGroupSetupAnsPayloadMcGroupIdHeader {
+                    mc_group_id: 2,
+                    id_error: true,
+                },
+            }),
+            bytes: vec![0x02, 0x06],
+            expected_error: None,
+        }];
+
+        run_tests_encode(&encode_tests);
+        run_tests_decode(&decode_tests);
+    }
+
+    #[test]
+    fn test_mc_group_delete_req() {
+        let encode_tests = [CommandTest {
+            name: "encode McGroupDeleteReq".into(),
+            uplink: false,
+            command: Payload::McGroupDeleteReq(McGroupDeleteReqPayload {
+                mc_group_id_header: McGroupDeleteReqPayloadMcGroupIdHeader { mc_group_id: 3 },
+            }),
+            bytes: vec![0x03, 0x03],
+            expected_error: None,
+        }];
+
+        let decode_tests = [CommandTest {
+            name: "decode McGroupDeleteReq".into(),
+            uplink: false,
+            command: Payload::McGroupDeleteReq(McGroupDeleteReqPayload {
+                mc_group_id_header: McGroupDeleteReqPayloadMcGroupIdHeader { mc_group_id: 3 },
+            }),
+            bytes: vec![0x03, 0x03],
+            expected_error: None,
+        }];
+
+        run_tests_encode(&encode_tests);
+        run_tests_decode(&decode_tests);
+    }
+
+    #[test]
+    fn test_mc_group_delete_ans() {
+        let encode_tests = [CommandTest {
+            name: "encode McGroupDeleteAns".into(),
+            uplink: true,
+            command: Payload::McGroupDeleteAns(McGroupDeleteAnsPayload {
+                mc_group_id_header: McGroupDeleteAnsPayloadMcGroupIdHeader {
+                    mc_group_id: 3,
+                    mc_group_undefined: true,
+                },
+            }),
+            bytes: vec![0x03, 0x07],
+            expected_error: None,
+        }];
+
+        let decode_tests = [CommandTest {
+            name: "decode McGroupDeleteAns".into(),
+            uplink: true,
+            command: Payload::McGroupDeleteAns(McGroupDeleteAnsPayload {
+                mc_group_id_header: McGroupDeleteAnsPayloadMcGroupIdHeader {
+                    mc_group_id: 3,
+                    mc_group_undefined: true,
+                },
+            }),
+            bytes: vec![0x03, 0x07],
+            expected_error: None,
+        }];
+
+        run_tests_encode(&encode_tests);
+        run_tests_decode(&decode_tests);
+    }
+
+    #[test]
+    fn test_mc_class_c_session_req() {
+        let encode_tests = [CommandTest {
+            name: "encode McClassCSessionReq".into(),
+            uplink: false,
+            command: Payload::McClassCSessionReq(McClassCSessionReqPayload {
+                mc_group_id_header: McClassCSessionReqPayloadMcGroupIdHeader { mc_group_id: 2 },
+                session_time: 1024,
+                session_time_out: McClassCSessionReqPayloadSessionTimeOut { time_out: 15 },
+                dl_frequ: 868100000,
+                dr: 5,
+            }),
+            bytes: vec![
+                0x04, 0x02, 0x00, 0x04, 0x00, 0x00, 0x0f, 0x28, 0x76, 0x84, 0x05,
+            ],
+            expected_error: None,
+        }];
+
+        let decode_tests = [CommandTest {
+            name: "decode McClassCSessionReq".into(),
+            uplink: false,
+            command: Payload::McClassCSessionReq(McClassCSessionReqPayload {
+                mc_group_id_header: McClassCSessionReqPayloadMcGroupIdHeader { mc_group_id: 2 },
+                session_time: 1024,
+                session_time_out: McClassCSessionReqPayloadSessionTimeOut { time_out: 15 },
+                dl_frequ: 868100000,
+                dr: 5,
+            }),
+            bytes: vec![
+                0x04, 0x02, 0x00, 0x04, 0x00, 0x00, 0x0f, 0x28, 0x76, 0x84, 0x05,
+            ],
+            expected_error: None,
+        }];
+
+        run_tests_encode(&encode_tests);
+        run_tests_decode(&decode_tests);
+    }
+
+    #[test]
+    fn test_mc_class_c_session_ans() {
+        let encode_tests = [
+            CommandTest {
+                name: "encode McClassCSessionAns no error".into(),
+                uplink: true,
+                command: Payload::McClassCSessionAns(McClassCSessionAnsPayload {
+                    status_and_mc_group_id: McClassCSessionAnsPayloadStatusAnsMcGroupId {
+                        mc_group_id: 2,
+                        dr_error: false,
+                        freq_error: false,
+                        mc_group_undefined: false,
+                    },
+                    time_to_start: Some(1024),
+                }),
+                bytes: vec![0x04, 0x02, 0x00, 0x04, 0x00],
+                expected_error: None,
+            },
+            CommandTest {
+                name: "encode McClassCSessionAns with error".into(),
+                uplink: true,
+                command: Payload::McClassCSessionAns(McClassCSessionAnsPayload {
+                    status_and_mc_group_id: McClassCSessionAnsPayloadStatusAnsMcGroupId {
+                        mc_group_id: 2,
+                        dr_error: true,
+                        freq_error: true,
+                        mc_group_undefined: true,
+                    },
+                    time_to_start: None,
+                }),
+                bytes: vec![0x04, 0x1e],
+                expected_error: None,
+            },
+        ];
+
+        let decode_tests = [
+            CommandTest {
+                name: "decode McClassCSessionAns no error".into(),
+                uplink: true,
+                command: Payload::McClassCSessionAns(McClassCSessionAnsPayload {
+                    status_and_mc_group_id: McClassCSessionAnsPayloadStatusAnsMcGroupId {
+                        mc_group_id: 2,
+                        dr_error: false,
+                        freq_error: false,
+                        mc_group_undefined: false,
+                    },
+                    time_to_start: Some(1024),
+                }),
+                bytes: vec![0x04, 0x02, 0x00, 0x04, 0x00],
+                expected_error: None,
+            },
+            CommandTest {
+                name: "decode McClassCSessionAns with error".into(),
+                uplink: true,
+                command: Payload::McClassCSessionAns(McClassCSessionAnsPayload {
+                    status_and_mc_group_id: McClassCSessionAnsPayloadStatusAnsMcGroupId {
+                        mc_group_id: 2,
+                        dr_error: true,
+                        freq_error: true,
+                        mc_group_undefined: true,
+                    },
+                    time_to_start: None,
+                }),
+                bytes: vec![0x04, 0x1e],
+                expected_error: None,
+            },
+        ];
+
+        run_tests_encode(&encode_tests);
+        run_tests_decode(&decode_tests);
+    }
+
+    #[test]
+    fn test_mc_class_b_session_req() {
+        let encode_tests = [CommandTest {
+            name: "encode McClassBSessionReq".into(),
+            uplink: false,
+            command: Payload::McClassBSessionReq(McClassBSessionReqPayload {
+                mc_group_id_header: McClassBSessionReqPayloadMcGroupIdHeader { mc_group_id: 3 },
+                session_time: 1024,
+                time_out_periodicity: McClassBSessionReqPayloadTimeOutPeriodicity {
+                    time_out: 15,
+                    periodicity: 4,
+                },
+                dl_frequ: 868100000,
+                dr: 5,
+            }),
+            bytes: vec![
+                0x05, 0x03, 0x00, 0x04, 0x00, 0x00, 0x4f, 0x28, 0x76, 0x84, 0x05,
+            ],
+            expected_error: None,
+        }];
+
+        let decode_tests = [CommandTest {
+            name: "decode McClassBSessionReq".into(),
+            uplink: false,
+            command: Payload::McClassBSessionReq(McClassBSessionReqPayload {
+                mc_group_id_header: McClassBSessionReqPayloadMcGroupIdHeader { mc_group_id: 3 },
+                session_time: 1024,
+                time_out_periodicity: McClassBSessionReqPayloadTimeOutPeriodicity {
+                    time_out: 15,
+                    periodicity: 4,
+                },
+                dl_frequ: 868100000,
+                dr: 5,
+            }),
+            bytes: vec![
+                0x05, 0x03, 0x00, 0x04, 0x00, 0x00, 0x4f, 0x28, 0x76, 0x84, 0x05,
+            ],
+            expected_error: None,
+        }];
+
+        run_tests_encode(&encode_tests);
+        run_tests_decode(&decode_tests);
+    }
+
+    #[test]
+    fn test_mc_class_b_session_ans() {
+        let encode_tests = [
+            CommandTest {
+                name: "encode McClassBSessionAns no errors".into(),
+                uplink: true,
+                command: Payload::McClassBSessionAns(McClassBSessionAnsPayload {
+                    status_and_mc_group_id: McClassBSessionAnsPayloadStatusAndMcGroupId {
+                        mc_group_id: 3,
+                        dr_error: false,
+                        freq_error: false,
+                        mc_group_undefined: false,
+                    },
+                    time_to_start: Some(1024),
+                }),
+                bytes: vec![0x05, 0x03, 0x00, 0x04, 0x00],
+                expected_error: None,
+            },
+            CommandTest {
+                name: "encode McClassBSessionAns with errors".into(),
+                uplink: true,
+                command: Payload::McClassBSessionAns(McClassBSessionAnsPayload {
+                    status_and_mc_group_id: McClassBSessionAnsPayloadStatusAndMcGroupId {
+                        mc_group_id: 3,
+                        dr_error: true,
+                        freq_error: true,
+                        mc_group_undefined: true,
+                    },
+                    time_to_start: None,
+                }),
+                bytes: vec![0x05, 0x1f],
+                expected_error: None,
+            },
+        ];
+
+        let decode_tests = [
+            CommandTest {
+                name: "decode McClassBSessionAns no errors".into(),
+                uplink: true,
+                command: Payload::McClassBSessionAns(McClassBSessionAnsPayload {
+                    status_and_mc_group_id: McClassBSessionAnsPayloadStatusAndMcGroupId {
+                        mc_group_id: 3,
+                        dr_error: false,
+                        freq_error: false,
+                        mc_group_undefined: false,
+                    },
+                    time_to_start: Some(1024),
+                }),
+                bytes: vec![0x05, 0x03, 0x00, 0x04, 0x00],
+                expected_error: None,
+            },
+            CommandTest {
+                name: "decode McClassBSessionAns with errors".into(),
+                uplink: true,
+                command: Payload::McClassBSessionAns(McClassBSessionAnsPayload {
+                    status_and_mc_group_id: McClassBSessionAnsPayloadStatusAndMcGroupId {
+                        mc_group_id: 3,
+                        dr_error: true,
+                        freq_error: true,
+                        mc_group_undefined: true,
+                    },
+                    time_to_start: None,
+                }),
+                bytes: vec![0x05, 0x1f],
+                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());
+            }
+        }
+    }
+}