use std::fmt;
use std::io::{Cursor, Read};
use std::ops::{Deref, DerefMut};
use std::time::Duration;

use anyhow::Result;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

use crate::cflist::ChMask;
use crate::dl_settings::DLSettings;
use crate::helpers::{decode_freq, encode_freq};

pub trait PayloadCodec<Struct = Self> {
    fn decode(cur: &mut Cursor<Vec<u8>>) -> Result<Struct>;
    fn encode(&self) -> Result<Vec<u8>>;
}

#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub enum CID {
    // LoRaWAN
    ResetInd,
    ResetConf,
    LinkCheckReq,
    LinkCheckAns,
    LinkADRReq,
    LinkADRAns,
    DutyCycleReq,
    DutyCycleAns,
    RxParamSetupReq,
    RxParamSetupAns,
    DevStatusReq,
    DevStatusAns,
    NewChannelReq,
    NewChannelAns,
    RxTimingSetupReq,
    RxTimingSetupAns,
    TxParamSetupReq,
    TxParamSetupAns,
    DlChannelReq,
    DlChannelAns,
    RekeyConf,
    RekeyInd,
    ADRParamSetupReq,
    ADRParamSetupAns,
    DeviceTimeReq,
    DeviceTimeAns,
    ForceRejoinReq,
    RejoinParamSetupReq,
    RejoinParamSetupAns,
    PingSlotInfoReq,
    PingSlotInfoAns,
    PingSlotChannelReq,
    PingSlotChannelAns,
    BeaconFreqReq,
    BeaconFreqAns,
    DeviceModeInd,
    DeviceModeConf,
    // Relay
    RelayConfReq,
    RelayConfAns,
    EndDeviceConfReq,
    EndDeviceConfAns,
    FilterListReq,
    FilterListAns,
    UpdateUplinkListReq,
    UpdateUplinkListAns,
    CtrlUplinkListReq,
    CtrlUplinkListAns,
    ConfigureFwdLimitReq,
    ConfigureFwdLimitAns,
    NotifyNewEndDeviceReq,
    // Raw
    Raw,
}

impl fmt::Display for CID {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:?}", self)
    }
}

impl CID {
    pub fn to_u8(&self) -> u8 {
        match self {
            // LoRaWAN
            CID::ResetInd | CID::ResetConf => 0x01,
            CID::LinkCheckReq | CID::LinkCheckAns => 0x02,
            CID::LinkADRReq | CID::LinkADRAns => 0x03,
            CID::DutyCycleReq | CID::DutyCycleAns => 0x04,
            CID::RxParamSetupReq | CID::RxParamSetupAns => 0x05,
            CID::DevStatusReq | CID::DevStatusAns => 0x06,
            CID::NewChannelReq | CID::NewChannelAns => 0x07,
            CID::RxTimingSetupReq | CID::RxTimingSetupAns => 0x08,
            CID::TxParamSetupReq | CID::TxParamSetupAns => 0x09,
            CID::DlChannelReq | CID::DlChannelAns => 0x0a,
            CID::RekeyConf | CID::RekeyInd => 0x0b,
            CID::ADRParamSetupReq | CID::ADRParamSetupAns => 0x0c,
            CID::DeviceTimeReq | CID::DeviceTimeAns => 0x0d,
            CID::ForceRejoinReq => 0x0e,
            CID::RejoinParamSetupReq | CID::RejoinParamSetupAns => 0x0f,
            CID::PingSlotInfoReq | CID::PingSlotInfoAns => 0x10,
            CID::PingSlotChannelReq | CID::PingSlotChannelAns => 0x11,
            CID::BeaconFreqReq | CID::BeaconFreqAns => 0x13, // 0x12 is deprecated
            CID::DeviceModeInd | CID::DeviceModeConf => 0x20,
            // Relay
            CID::RelayConfReq | CID::RelayConfAns => 0x40,
            CID::EndDeviceConfReq | CID::EndDeviceConfAns => 0x41,
            CID::FilterListReq | CID::FilterListAns => 0x42,
            CID::UpdateUplinkListReq | CID::UpdateUplinkListAns => 0x43,
            CID::CtrlUplinkListReq | CID::CtrlUplinkListAns => 0x44,
            CID::ConfigureFwdLimitReq | CID::ConfigureFwdLimitAns => 0x45,
            CID::NotifyNewEndDeviceReq => 0x46,
            // Raw
            CID::Raw => 0xff,
        }
    }

    pub fn from_u8(uplink: bool, v: u8) -> Result<Self> {
        Ok(if uplink {
            match v {
                0x01 => CID::ResetInd,
                0x02 => CID::LinkCheckReq,
                0x03 => CID::LinkADRAns,
                0x04 => CID::DutyCycleAns,
                0x05 => CID::RxParamSetupAns,
                0x06 => CID::DevStatusAns,
                0x07 => CID::NewChannelAns,
                0x08 => CID::RxTimingSetupAns,
                0x09 => CID::TxParamSetupAns,
                0x0a => CID::DlChannelAns,
                0x0b => CID::RekeyInd,
                0x0c => CID::ADRParamSetupAns,
                0x0d => CID::DeviceTimeReq,
                0x0f => CID::RejoinParamSetupAns,
                0x10 => CID::PingSlotInfoReq,
                0x11 => CID::PingSlotChannelAns,
                0x13 => CID::BeaconFreqAns,
                0x20 => CID::DeviceModeInd,
                0x40 => CID::RelayConfAns,
                0x41 => CID::EndDeviceConfAns,
                0x42 => CID::FilterListAns,
                0x43 => CID::UpdateUplinkListAns,
                0x44 => CID::CtrlUplinkListAns,
                0x45 => CID::ConfigureFwdLimitAns,
                0x46 => CID::NotifyNewEndDeviceReq,
                _ => {
                    return Err(anyhow!("Invalid CID: {}", v));
                }
            }
        } else {
            match v {
                0x01 => CID::ResetConf,
                0x02 => CID::LinkCheckAns,
                0x03 => CID::LinkADRReq,
                0x04 => CID::DutyCycleReq,
                0x05 => CID::RxParamSetupReq,
                0x06 => CID::DevStatusReq,
                0x07 => CID::NewChannelReq,
                0x08 => CID::RxTimingSetupReq,
                0x09 => CID::TxParamSetupReq,
                0x0a => CID::DlChannelReq,
                0x0b => CID::RekeyConf,
                0x0c => CID::ADRParamSetupReq,
                0x0d => CID::DeviceTimeAns,
                0x0e => CID::ForceRejoinReq,
                0x0f => CID::RejoinParamSetupReq,
                0x10 => CID::PingSlotInfoAns,
                0x11 => CID::PingSlotChannelReq,
                0x13 => CID::BeaconFreqReq,
                0x20 => CID::DeviceModeConf,
                0x40 => CID::RelayConfReq,
                0x41 => CID::EndDeviceConfReq,
                0x42 => CID::FilterListReq,
                0x43 => CID::UpdateUplinkListReq,
                0x44 => CID::CtrlUplinkListReq,
                0x45 => CID::ConfigureFwdLimitReq,
                _ => {
                    return Err(anyhow!("Invalid CID: {}", v));
                }
            }
        })
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub enum MACCommand {
    // LoRaWAN
    ResetInd(ResetIndPayload),
    ResetConf(ResetConfPayload),
    LinkCheckReq,
    LinkCheckAns(LinkCheckAnsPayload),
    LinkADRReq(LinkADRReqPayload),
    LinkADRAns(LinkADRAnsPayload),
    DutyCycleReq(DutyCycleReqPayload),
    DutyCycleAns,
    RxParamSetupReq(RxParamSetupReqPayload),
    RxParamSetupAns(RxParamSetupAnsPayload),
    DevStatusReq,
    DevStatusAns(DevStatusAnsPayload),
    NewChannelReq(NewChannelReqPayload),
    NewChannelAns(NewChannelAnsPayload),
    RxTimingSetupReq(RxTimingSetupReqPayload),
    RxTimingSetupAns,
    TxParamSetupReq(TxParamSetupReqPayload),
    TxParamSetupAns,
    DlChannelReq(DlChannelReqPayload),
    DlChannelAns(DlChannelAnsPayload),
    RekeyConf(RekeyConfPayload),
    RekeyInd(RekeyIndPayload),
    ADRParamSetupReq(ADRParamSetupReqPayload),
    ADRParamSetupAns,
    DeviceTimeReq,
    DeviceTimeAns(DeviceTimeAnsPayload),
    ForceRejoinReq(ForceRejoinReqPayload),
    RejoinParamSetupReq(RejoinParamSetupReqPayload),
    RejoinParamSetupAns(RejoinParamSetupAnsPayload),
    PingSlotInfoReq(PingSlotInfoReqPayload),
    PingSlotInfoAns,
    PingSlotChannelReq(PingSlotChannelReqPayload),
    PingSlotChannelAns(PingSlotChannelAnsPayload),
    BeaconFreqReq(BeaconFreqReqPayload),
    BeaconFreqAns(BeaconFreqAnsPayload),
    DeviceModeInd(DeviceModeIndPayload),
    DeviceModeConf(DeviceModeConfPayload),
    // Relay
    RelayConfReq(RelayConfReqPayload),
    RelayConfAns(RelayConfAnsPayload),
    EndDeviceConfReq(EndDeviceConfReqPayload),
    EndDeviceConfAns(EndDeviceConfAnsPayload),
    FilterListReq(FilterListReqPayload),
    FilterListAns(FilterListAnsPayload),
    UpdateUplinkListReq(UpdateUplinkListReqPayload),
    UpdateUplinkListAns,
    CtrlUplinkListReq(CtrlUplinkListReqPayload),
    CtrlUplinkListAns(CtrlUplinkListAnsPayload),
    ConfigureFwdLimitReq(ConfigureFwdLimitReqPayload),
    ConfigureFwdLimitAns,
    NotifyNewEndDeviceReq(NotifyNewEndDeviceReqPayload),
    // Raw
    Raw(Vec<u8>),
}

impl MACCommand {
    pub fn cid(&self) -> CID {
        match self {
            // LoRaWAN
            MACCommand::ResetInd(_) => CID::ResetInd,
            MACCommand::ResetConf(_) => CID::ResetConf,
            MACCommand::LinkCheckReq => CID::LinkCheckReq,
            MACCommand::LinkCheckAns(_) => CID::LinkCheckAns,
            MACCommand::LinkADRReq(_) => CID::LinkADRReq,
            MACCommand::LinkADRAns(_) => CID::LinkADRAns,
            MACCommand::DutyCycleReq(_) => CID::DutyCycleReq,
            MACCommand::DutyCycleAns => CID::DutyCycleAns,
            MACCommand::RxParamSetupReq(_) => CID::RxParamSetupReq,
            MACCommand::RxParamSetupAns(_) => CID::RxParamSetupAns,
            MACCommand::DevStatusReq => CID::DevStatusReq,
            MACCommand::DevStatusAns(_) => CID::DevStatusAns,
            MACCommand::NewChannelReq(_) => CID::NewChannelReq,
            MACCommand::NewChannelAns(_) => CID::NewChannelAns,
            MACCommand::RxTimingSetupReq(_) => CID::RxTimingSetupReq,
            MACCommand::RxTimingSetupAns => CID::RxTimingSetupAns,
            MACCommand::TxParamSetupReq(_) => CID::TxParamSetupReq,
            MACCommand::TxParamSetupAns => CID::TxParamSetupAns,
            MACCommand::DlChannelReq(_) => CID::DlChannelReq,
            MACCommand::DlChannelAns(_) => CID::DlChannelAns,
            MACCommand::RekeyConf(_) => CID::RekeyConf,
            MACCommand::RekeyInd(_) => CID::RekeyInd,
            MACCommand::ADRParamSetupReq(_) => CID::ADRParamSetupReq,
            MACCommand::ADRParamSetupAns => CID::ADRParamSetupAns,
            MACCommand::DeviceTimeReq => CID::DeviceTimeReq,
            MACCommand::DeviceTimeAns(_) => CID::DeviceTimeAns,
            MACCommand::ForceRejoinReq(_) => CID::ForceRejoinReq,
            MACCommand::RejoinParamSetupReq(_) => CID::RejoinParamSetupReq,
            MACCommand::RejoinParamSetupAns(_) => CID::RejoinParamSetupAns,
            MACCommand::PingSlotInfoReq(_) => CID::PingSlotInfoReq,
            MACCommand::PingSlotInfoAns => CID::PingSlotInfoAns,
            MACCommand::PingSlotChannelReq(_) => CID::PingSlotChannelReq,
            MACCommand::PingSlotChannelAns(_) => CID::PingSlotChannelAns,
            MACCommand::BeaconFreqReq(_) => CID::BeaconFreqReq,
            MACCommand::BeaconFreqAns(_) => CID::BeaconFreqAns, // 0x12 is deprecated
            MACCommand::DeviceModeInd(_) => CID::DeviceModeInd,
            MACCommand::DeviceModeConf(_) => CID::DeviceModeConf,
            // Relay
            MACCommand::RelayConfReq(_) => CID::RelayConfReq,
            MACCommand::RelayConfAns(_) => CID::RelayConfAns,
            MACCommand::EndDeviceConfReq(_) => CID::EndDeviceConfReq,
            MACCommand::EndDeviceConfAns(_) => CID::EndDeviceConfAns,
            MACCommand::FilterListReq(_) => CID::FilterListReq,
            MACCommand::FilterListAns(_) => CID::FilterListAns,
            MACCommand::UpdateUplinkListReq(_) => CID::UpdateUplinkListReq,
            MACCommand::UpdateUplinkListAns => CID::UpdateUplinkListAns,
            MACCommand::CtrlUplinkListReq(_) => CID::CtrlUplinkListReq,
            MACCommand::CtrlUplinkListAns(_) => CID::CtrlUplinkListAns,
            MACCommand::ConfigureFwdLimitReq(_) => CID::ConfigureFwdLimitReq,
            MACCommand::ConfigureFwdLimitAns => CID::ConfigureFwdLimitAns,
            MACCommand::NotifyNewEndDeviceReq(_) => CID::NotifyNewEndDeviceReq,
            // Raw
            MACCommand::Raw(_) => CID::Raw,
        }
    }
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub enum Version {
    LoRaWAN1_1,
}

impl Version {
    pub fn to_u8(&self) -> u8 {
        match self {
            Version::LoRaWAN1_1 => 0x01,
        }
    }

    pub fn from_u8(v: u8) -> Result<Self> {
        Ok(match v {
            0x01 => Version::LoRaWAN1_1,
            _ => {
                return Err(anyhow!("invalid version"));
            }
        })
    }
}

impl fmt::Display for Version {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:?}", self)
    }
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub enum DwellTime {
    NoLimit,
    Limit400ms,
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub enum DeviceModeClass {
    ClassA,
    ClassC,
}

impl DeviceModeClass {
    pub fn to_u8(&self) -> u8 {
        match self {
            DeviceModeClass::ClassA => 0x00,
            DeviceModeClass::ClassC => 0x02,
        }
    }

    pub fn from_u8(v: u8) -> Result<Self> {
        Ok(match v {
            0x00 => DeviceModeClass::ClassA,
            0x02 => DeviceModeClass::ClassC,
            _ => {
                return Err(anyhow!("invalid device mode"));
            }
        })
    }
}

impl fmt::Display for DeviceModeClass {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "{}",
            match self {
                DeviceModeClass::ClassA => "A",
                DeviceModeClass::ClassC => "C",
            }
        )
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct MACCommandSet(Vec<MACCommand>);

impl Deref for MACCommandSet {
    type Target = Vec<MACCommand>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl DerefMut for MACCommandSet {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

impl MACCommandSet {
    pub fn new(macs: Vec<MACCommand>) -> Self {
        MACCommandSet(macs)
    }

    // This reads the CID from the first mac-command in the set. It is assumed
    // that all mac-commands in the set share the same CID.
    pub fn cid(&self) -> Result<CID> {
        self.0
            .first()
            .map(|v| v.cid())
            .ok_or_else(|| anyhow!("Set is empty"))
    }

    pub fn size(&self) -> Result<usize> {
        let b = self.to_vec()?;
        Ok(b.len())
    }

    pub fn from_slice(b: &[u8]) -> Self {
        // For LoRaWAN 1.1, this payload must be first decrypted before it can be parsed.
        // Therefore, the decoding into separate mac-commands is handled by decode_from_raw.
        MACCommandSet(vec![MACCommand::Raw(b.to_vec())])
    }

    pub fn push(&mut self, m: MACCommand) {
        self.0.push(m);
    }

    pub fn to_vec(&self) -> Result<Vec<u8>> {
        let mut out = Vec::new();

        for mac in &self.0 {
            match mac {
                // LoRaWAN
                MACCommand::ResetInd(pl) => {
                    out.push(mac.cid().to_u8());
                    out.extend_from_slice(&pl.encode()?);
                }
                MACCommand::ResetConf(pl) => {
                    out.push(mac.cid().to_u8());
                    out.extend_from_slice(&pl.encode()?);
                }
                MACCommand::LinkCheckReq => {
                    out.push(mac.cid().to_u8());
                }
                MACCommand::LinkCheckAns(pl) => {
                    out.push(mac.cid().to_u8());
                    out.extend_from_slice(&pl.encode()?);
                }
                MACCommand::LinkADRReq(pl) => {
                    out.push(mac.cid().to_u8());
                    out.extend_from_slice(&pl.encode()?);
                }
                MACCommand::LinkADRAns(pl) => {
                    out.push(mac.cid().to_u8());
                    out.extend_from_slice(&pl.encode()?);
                }
                MACCommand::DutyCycleReq(pl) => {
                    out.push(mac.cid().to_u8());
                    out.extend_from_slice(&pl.encode()?);
                }
                MACCommand::DutyCycleAns => {
                    out.push(mac.cid().to_u8());
                }
                MACCommand::RxParamSetupReq(pl) => {
                    out.push(mac.cid().to_u8());
                    out.extend_from_slice(&pl.encode()?);
                }
                MACCommand::RxParamSetupAns(pl) => {
                    out.push(mac.cid().to_u8());
                    out.extend_from_slice(&pl.encode()?);
                }
                MACCommand::DevStatusReq => {
                    out.push(mac.cid().to_u8());
                }
                MACCommand::DevStatusAns(pl) => {
                    out.push(mac.cid().to_u8());
                    out.extend_from_slice(&pl.encode()?);
                }
                MACCommand::NewChannelReq(pl) => {
                    out.push(mac.cid().to_u8());
                    out.extend_from_slice(&pl.encode()?);
                }
                MACCommand::NewChannelAns(pl) => {
                    out.push(mac.cid().to_u8());
                    out.extend_from_slice(&pl.encode()?);
                }
                MACCommand::RxTimingSetupReq(pl) => {
                    out.push(mac.cid().to_u8());
                    out.extend_from_slice(&pl.encode()?);
                }
                MACCommand::RxTimingSetupAns => {
                    out.push(mac.cid().to_u8());
                }
                MACCommand::TxParamSetupReq(pl) => {
                    out.push(mac.cid().to_u8());
                    out.extend_from_slice(&pl.encode()?);
                }
                MACCommand::TxParamSetupAns => {
                    out.push(mac.cid().to_u8());
                }
                MACCommand::DlChannelReq(pl) => {
                    out.push(mac.cid().to_u8());
                    out.extend_from_slice(&pl.encode()?);
                }
                MACCommand::DlChannelAns(pl) => {
                    out.push(mac.cid().to_u8());
                    out.extend_from_slice(&pl.encode()?);
                }
                MACCommand::RekeyConf(pl) => {
                    out.push(mac.cid().to_u8());
                    out.extend_from_slice(&pl.encode()?);
                }
                MACCommand::RekeyInd(pl) => {
                    out.push(mac.cid().to_u8());
                    out.extend_from_slice(&pl.encode()?);
                }
                MACCommand::ADRParamSetupReq(pl) => {
                    out.push(mac.cid().to_u8());
                    out.extend_from_slice(&pl.encode()?);
                }
                MACCommand::ADRParamSetupAns => {
                    out.push(mac.cid().to_u8());
                }
                MACCommand::DeviceTimeReq => {
                    out.push(mac.cid().to_u8());
                }
                MACCommand::DeviceTimeAns(pl) => {
                    out.push(mac.cid().to_u8());
                    out.extend_from_slice(&pl.encode()?);
                }
                MACCommand::ForceRejoinReq(pl) => {
                    out.push(mac.cid().to_u8());
                    out.extend_from_slice(&pl.encode()?);
                }
                MACCommand::RejoinParamSetupReq(pl) => {
                    out.push(mac.cid().to_u8());
                    out.extend_from_slice(&pl.encode()?);
                }
                MACCommand::RejoinParamSetupAns(pl) => {
                    out.push(mac.cid().to_u8());
                    out.extend_from_slice(&pl.encode()?);
                }
                MACCommand::PingSlotInfoReq(pl) => {
                    out.push(mac.cid().to_u8());
                    out.extend_from_slice(&pl.encode()?);
                }
                MACCommand::PingSlotInfoAns => {
                    out.push(mac.cid().to_u8());
                }
                MACCommand::PingSlotChannelReq(pl) => {
                    out.push(mac.cid().to_u8());
                    out.extend_from_slice(&pl.encode()?);
                }
                MACCommand::PingSlotChannelAns(pl) => {
                    out.push(mac.cid().to_u8());
                    out.extend_from_slice(&pl.encode()?);
                }
                MACCommand::BeaconFreqReq(pl) => {
                    out.push(mac.cid().to_u8());
                    out.extend_from_slice(&pl.encode()?);
                }
                MACCommand::BeaconFreqAns(pl) => {
                    out.push(mac.cid().to_u8());
                    out.extend_from_slice(&pl.encode()?);
                }
                MACCommand::DeviceModeInd(pl) => {
                    out.push(mac.cid().to_u8());
                    out.extend_from_slice(&pl.encode()?);
                }
                MACCommand::DeviceModeConf(pl) => {
                    out.push(mac.cid().to_u8());
                    out.extend_from_slice(&pl.encode()?);
                }
                // Relay
                MACCommand::RelayConfReq(pl) => {
                    out.push(mac.cid().to_u8());
                    out.extend_from_slice(&pl.encode()?);
                }
                MACCommand::RelayConfAns(pl) => {
                    out.push(mac.cid().to_u8());
                    out.extend_from_slice(&pl.encode()?);
                }
                MACCommand::EndDeviceConfReq(pl) => {
                    out.push(mac.cid().to_u8());
                    out.extend_from_slice(&pl.encode()?);
                }
                MACCommand::EndDeviceConfAns(pl) => {
                    out.push(mac.cid().to_u8());
                    out.extend_from_slice(&pl.encode()?);
                }
                MACCommand::FilterListReq(pl) => {
                    out.push(mac.cid().to_u8());
                    out.extend_from_slice(&pl.encode()?);
                }
                MACCommand::FilterListAns(pl) => {
                    out.push(mac.cid().to_u8());
                    out.extend_from_slice(&pl.encode()?);
                }
                MACCommand::UpdateUplinkListReq(pl) => {
                    out.push(mac.cid().to_u8());
                    out.extend_from_slice(&pl.encode()?);
                }
                MACCommand::UpdateUplinkListAns => out.push(mac.cid().to_u8()),
                MACCommand::CtrlUplinkListReq(pl) => {
                    out.push(mac.cid().to_u8());
                    out.extend_from_slice(&pl.encode()?);
                }
                MACCommand::CtrlUplinkListAns(pl) => {
                    out.push(mac.cid().to_u8());
                    out.extend_from_slice(&pl.encode()?);
                }
                MACCommand::ConfigureFwdLimitReq(pl) => {
                    out.push(mac.cid().to_u8());
                    out.extend_from_slice(&pl.encode()?);
                }
                MACCommand::ConfigureFwdLimitAns => out.push(mac.cid().to_u8()),
                MACCommand::NotifyNewEndDeviceReq(pl) => {
                    out.push(mac.cid().to_u8());
                    out.extend_from_slice(&pl.encode()?);
                }
                // Raw
                MACCommand::Raw(v) => out.extend_from_slice(v),
            };
        }

        Ok(out)
    }

    pub fn decode_from_raw(&mut self, uplink: bool) -> Result<()> {
        // nothing to parse
        if self.0.is_empty() {
            return Ok(());
        }

        // in any other case there must be exactly one MACCommand::Raw.
        if self.0.len() == 1 {
            if let MACCommand::Raw(b) = &self.0[0] {
                let mut cur = Cursor::new(b.clone());
                let mut commands = vec![];
                let mut b = [0; 1];

                loop {
                    // Try to read one byte to get the CID.
                    if cur.read_exact(&mut b).is_err() {
                        break;
                    }

                    let cid = match CID::from_u8(uplink, b[0]) {
                        Ok(v) => v,
                        Err(_) => {
                            let mut b = b.to_vec();
                            cur.read_to_end(&mut b)?;
                            commands.push(MACCommand::Raw(b));
                            break;
                        }
                    };

                    match cid {
                        CID::ResetInd => {
                            commands.push(MACCommand::ResetInd(ResetIndPayload::decode(&mut cur)?))
                        }
                        CID::ResetConf => commands
                            .push(MACCommand::ResetConf(ResetConfPayload::decode(&mut cur)?)),
                        CID::LinkCheckReq => commands.push(MACCommand::LinkCheckReq),
                        CID::LinkCheckAns => commands.push(MACCommand::LinkCheckAns(
                            LinkCheckAnsPayload::decode(&mut cur)?,
                        )),
                        CID::LinkADRReq => commands
                            .push(MACCommand::LinkADRReq(LinkADRReqPayload::decode(&mut cur)?)),
                        CID::LinkADRAns => commands
                            .push(MACCommand::LinkADRAns(LinkADRAnsPayload::decode(&mut cur)?)),
                        CID::DutyCycleReq => commands.push(MACCommand::DutyCycleReq(
                            DutyCycleReqPayload::decode(&mut cur)?,
                        )),
                        CID::DutyCycleAns => commands.push(MACCommand::DutyCycleAns),
                        CID::RxParamSetupReq => commands.push(MACCommand::RxParamSetupReq(
                            RxParamSetupReqPayload::decode(&mut cur)?,
                        )),
                        CID::RxParamSetupAns => commands.push(MACCommand::RxParamSetupAns(
                            RxParamSetupAnsPayload::decode(&mut cur)?,
                        )),
                        CID::DevStatusReq => commands.push(MACCommand::DevStatusReq),
                        CID::DevStatusAns => commands.push(MACCommand::DevStatusAns(
                            DevStatusAnsPayload::decode(&mut cur)?,
                        )),
                        CID::NewChannelReq => commands.push(MACCommand::NewChannelReq(
                            NewChannelReqPayload::decode(&mut cur)?,
                        )),
                        CID::NewChannelAns => commands.push(MACCommand::NewChannelAns(
                            NewChannelAnsPayload::decode(&mut cur)?,
                        )),
                        CID::RxTimingSetupReq => commands.push(MACCommand::RxTimingSetupReq(
                            RxTimingSetupReqPayload::decode(&mut cur)?,
                        )),
                        CID::RxTimingSetupAns => commands.push(MACCommand::RxTimingSetupAns),
                        CID::TxParamSetupReq => commands.push(MACCommand::TxParamSetupReq(
                            TxParamSetupReqPayload::decode(&mut cur)?,
                        )),
                        CID::TxParamSetupAns => commands.push(MACCommand::TxParamSetupAns),
                        CID::DlChannelReq => commands.push(MACCommand::DlChannelReq(
                            DlChannelReqPayload::decode(&mut cur)?,
                        )),
                        CID::DlChannelAns => commands.push(MACCommand::DlChannelAns(
                            DlChannelAnsPayload::decode(&mut cur)?,
                        )),
                        CID::RekeyConf => commands
                            .push(MACCommand::RekeyConf(RekeyConfPayload::decode(&mut cur)?)),
                        CID::RekeyInd => {
                            commands.push(MACCommand::RekeyInd(RekeyIndPayload::decode(&mut cur)?))
                        }
                        CID::ADRParamSetupReq => commands.push(MACCommand::ADRParamSetupReq(
                            ADRParamSetupReqPayload::decode(&mut cur)?,
                        )),
                        CID::ADRParamSetupAns => commands.push(MACCommand::ADRParamSetupAns),
                        CID::DeviceTimeReq => commands.push(MACCommand::DeviceTimeReq),
                        CID::DeviceTimeAns => commands.push(MACCommand::DeviceTimeAns(
                            DeviceTimeAnsPayload::decode(&mut cur)?,
                        )),
                        CID::ForceRejoinReq => commands.push(MACCommand::ForceRejoinReq(
                            ForceRejoinReqPayload::decode(&mut cur)?,
                        )),
                        CID::RejoinParamSetupReq => commands.push(MACCommand::RejoinParamSetupReq(
                            RejoinParamSetupReqPayload::decode(&mut cur)?,
                        )),
                        CID::RejoinParamSetupAns => commands.push(MACCommand::RejoinParamSetupAns(
                            RejoinParamSetupAnsPayload::decode(&mut cur)?,
                        )),
                        CID::PingSlotInfoReq => commands.push(MACCommand::PingSlotInfoReq(
                            PingSlotInfoReqPayload::decode(&mut cur)?,
                        )),
                        CID::PingSlotInfoAns => commands.push(MACCommand::PingSlotInfoAns),
                        CID::PingSlotChannelReq => commands.push(MACCommand::PingSlotChannelReq(
                            PingSlotChannelReqPayload::decode(&mut cur)?,
                        )),
                        CID::PingSlotChannelAns => commands.push(MACCommand::PingSlotChannelAns(
                            PingSlotChannelAnsPayload::decode(&mut cur)?,
                        )),
                        CID::BeaconFreqReq => commands.push(MACCommand::BeaconFreqReq(
                            BeaconFreqReqPayload::decode(&mut cur)?,
                        )),
                        CID::BeaconFreqAns => commands.push(MACCommand::BeaconFreqAns(
                            BeaconFreqAnsPayload::decode(&mut cur)?,
                        )),
                        CID::DeviceModeInd => commands.push(MACCommand::DeviceModeInd(
                            DeviceModeIndPayload::decode(&mut cur)?,
                        )),
                        CID::DeviceModeConf => commands.push(MACCommand::DeviceModeConf(
                            DeviceModeConfPayload::decode(&mut cur)?,
                        )),
                        CID::RelayConfReq => commands.push(MACCommand::RelayConfReq(
                            RelayConfReqPayload::decode(&mut cur)?,
                        )),
                        CID::RelayConfAns => commands.push(MACCommand::RelayConfAns(
                            RelayConfAnsPayload::decode(&mut cur)?,
                        )),
                        CID::EndDeviceConfReq => commands.push(MACCommand::EndDeviceConfReq(
                            EndDeviceConfReqPayload::decode(&mut cur)?,
                        )),
                        CID::EndDeviceConfAns => commands.push(MACCommand::EndDeviceConfAns(
                            EndDeviceConfAnsPayload::decode(&mut cur)?,
                        )),
                        CID::FilterListReq => commands.push(MACCommand::FilterListReq(
                            FilterListReqPayload::decode(&mut cur)?,
                        )),
                        CID::FilterListAns => commands.push(MACCommand::FilterListAns(
                            FilterListAnsPayload::decode(&mut cur)?,
                        )),
                        CID::UpdateUplinkListReq => commands.push(MACCommand::UpdateUplinkListReq(
                            UpdateUplinkListReqPayload::decode(&mut cur)?,
                        )),
                        CID::UpdateUplinkListAns => commands.push(MACCommand::UpdateUplinkListAns),
                        CID::CtrlUplinkListReq => commands.push(MACCommand::CtrlUplinkListReq(
                            CtrlUplinkListReqPayload::decode(&mut cur)?,
                        )),
                        CID::CtrlUplinkListAns => commands.push(MACCommand::CtrlUplinkListAns(
                            CtrlUplinkListAnsPayload::decode(&mut cur)?,
                        )),
                        CID::ConfigureFwdLimitReq => {
                            commands.push(MACCommand::ConfigureFwdLimitReq(
                                ConfigureFwdLimitReqPayload::decode(&mut cur)?,
                            ))
                        }
                        CID::ConfigureFwdLimitAns => {
                            commands.push(MACCommand::ConfigureFwdLimitAns)
                        }
                        CID::NotifyNewEndDeviceReq => {
                            commands.push(MACCommand::NotifyNewEndDeviceReq(
                                NotifyNewEndDeviceReqPayload::decode(&mut cur)?,
                            ))
                        }
                        CID::Raw => {}
                    }
                }

                // Overwrite with decoded mac-commands.
                self.0 = commands;
            }
        }

        Ok(())
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct ResetIndPayload {
    pub dev_lorawan_version: Version,
}

impl PayloadCodec for ResetIndPayload {
    fn decode(cur: &mut Cursor<Vec<u8>>) -> Result<Self> {
        let mut b = [0; 1];
        cur.read_exact(&mut b)?;

        Ok(ResetIndPayload {
            dev_lorawan_version: Version::from_u8(b[0])?,
        })
    }

    fn encode(&self) -> Result<Vec<u8>> {
        Ok(vec![self.dev_lorawan_version.to_u8()])
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct ResetConfPayload {
    pub serv_lorawan_version: Version,
}

impl PayloadCodec for ResetConfPayload {
    fn decode(cur: &mut Cursor<Vec<u8>>) -> Result<Self> {
        let mut b = [0; 1];
        cur.read_exact(&mut b)?;

        Ok(ResetConfPayload {
            serv_lorawan_version: Version::from_u8(b[0])?,
        })
    }

    fn encode(&self) -> Result<Vec<u8>> {
        Ok(vec![self.serv_lorawan_version.to_u8()])
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct LinkCheckAnsPayload {
    pub margin: u8,
    pub gw_cnt: u8,
}

impl PayloadCodec for LinkCheckAnsPayload {
    fn decode(cur: &mut Cursor<Vec<u8>>) -> Result<Self> {
        let mut b = [0; 2];
        cur.read_exact(&mut b)?;

        Ok(LinkCheckAnsPayload {
            margin: b[0],
            gw_cnt: b[1],
        })
    }

    fn encode(&self) -> Result<Vec<u8>> {
        Ok(vec![self.margin, self.gw_cnt])
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct LinkADRReqPayload {
    pub dr: u8,
    pub tx_power: u8,
    pub ch_mask: ChMask,
    pub redundancy: Redundancy,
}

impl PayloadCodec for LinkADRReqPayload {
    fn decode(cur: &mut Cursor<Vec<u8>>) -> Result<Self> {
        let mut b = [0; 4];
        cur.read_exact(&mut b)?;

        Ok(LinkADRReqPayload {
            dr: (b[0] & 0xf0) >> 4,
            tx_power: b[0] & 0x0f,
            ch_mask: ChMask::from_bytes([b[1], b[2]]),
            redundancy: Redundancy::from_u8(b[3]),
        })
    }

    fn encode(&self) -> Result<Vec<u8>> {
        let mut b = vec![0; 4];

        if self.dr > 15 {
            return Err(anyhow!("max value of dr is 15"));
        }

        if self.tx_power > 15 {
            return Err(anyhow!("max value of tx_power is 15"));
        }

        b[0] = self.tx_power | (self.dr << 4);
        b[1..3].clone_from_slice(&self.ch_mask.to_bytes());
        b[3] = self.redundancy.to_u8()?;

        Ok(b)
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct Redundancy {
    pub ch_mask_cntl: u8,
    pub nb_rep: u8,
}

impl Redundancy {
    pub fn to_u8(&self) -> Result<u8> {
        if self.nb_rep > 15 {
            return Err(anyhow!("max value of nb_rep is 15"));
        }
        if self.ch_mask_cntl > 7 {
            return Err(anyhow!("max value of ch_mask_cntl is 7"));
        }

        Ok(self.nb_rep | (self.ch_mask_cntl << 4))
    }

    pub fn from_u8(b: u8) -> Self {
        Redundancy {
            nb_rep: b & 0x0f,
            ch_mask_cntl: (b & 0x70) >> 4,
        }
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct LinkADRAnsPayload {
    pub ch_mask_ack: bool,
    pub dr_ack: bool,
    pub tx_power_ack: bool,
}

impl PayloadCodec for LinkADRAnsPayload {
    fn decode(cur: &mut Cursor<Vec<u8>>) -> Result<Self> {
        let mut b = [0; 1];
        cur.read_exact(&mut b)?;

        Ok(LinkADRAnsPayload {
            ch_mask_ack: b[0] & 0x01 != 0,
            dr_ack: b[0] & 0x02 != 0,
            tx_power_ack: b[0] & 0x04 != 0,
        })
    }

    fn encode(&self) -> Result<Vec<u8>> {
        let mut b: u8 = 0;

        if self.ch_mask_ack {
            b |= 0x01;
        }
        if self.dr_ack {
            b |= 0x02;
        }
        if self.tx_power_ack {
            b |= 0x04;
        }

        Ok(vec![b])
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct DutyCycleReqPayload {
    pub max_duty_cycle: u8,
}

impl PayloadCodec for DutyCycleReqPayload {
    fn decode(cur: &mut Cursor<Vec<u8>>) -> Result<Self> {
        let mut b = [0; 1];
        cur.read_exact(&mut b)?;

        Ok(DutyCycleReqPayload {
            max_duty_cycle: b[0],
        })
    }

    fn encode(&self) -> Result<Vec<u8>> {
        if self.max_duty_cycle > 15 && self.max_duty_cycle != 255 {
            return Err(anyhow!("max_duty_cycle must have value 0 - 15 or 255"));
        }

        Ok(vec![self.max_duty_cycle])
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct RxParamSetupReqPayload {
    pub frequency: u32,
    pub dl_settings: DLSettings,
}

impl PayloadCodec for RxParamSetupReqPayload {
    fn decode(cur: &mut Cursor<Vec<u8>>) -> Result<Self> {
        let mut b = [0; 4];
        cur.read_exact(&mut b)?;

        Ok(RxParamSetupReqPayload {
            dl_settings: DLSettings::from_le_bytes([b[0]]),
            frequency: decode_freq(&b[1..])?,
        })
    }

    fn encode(&self) -> Result<Vec<u8>> {
        let mut b = vec![0; 4];
        b[0..1].copy_from_slice(&self.dl_settings.to_le_bytes()?);
        b[1..4].copy_from_slice(&encode_freq(self.frequency)?);
        Ok(b)
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct RxParamSetupAnsPayload {
    pub channel_ack: bool,
    pub rx2_dr_ack: bool,
    pub rx1_dr_offset_ack: bool,
}

impl PayloadCodec for RxParamSetupAnsPayload {
    fn decode(cur: &mut Cursor<Vec<u8>>) -> Result<Self> {
        let mut b = [0; 1];
        cur.read_exact(&mut b)?;

        Ok(RxParamSetupAnsPayload {
            channel_ack: b[0] & 0x01 != 0,
            rx2_dr_ack: b[0] & 0x02 != 0,
            rx1_dr_offset_ack: b[0] & 0x04 != 0,
        })
    }

    fn encode(&self) -> Result<Vec<u8>> {
        let mut b: u8 = 0;
        if self.channel_ack {
            b |= 0x01;
        }
        if self.rx2_dr_ack {
            b |= 0x02;
        }
        if self.rx1_dr_offset_ack {
            b |= 0x04;
        }
        Ok(vec![b])
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct DevStatusAnsPayload {
    pub battery: u8,
    pub margin: i8,
}

impl PayloadCodec for DevStatusAnsPayload {
    fn decode(cur: &mut Cursor<Vec<u8>>) -> Result<Self> {
        let mut b = [0; 2];
        cur.read_exact(&mut b)?;

        Ok(DevStatusAnsPayload {
            battery: b[0],
            margin: {
                if b[1] > 31 {
                    (b[1] as i8) - 64
                } else {
                    b[1] as i8
                }
            },
        })
    }

    fn encode(&self) -> Result<Vec<u8>> {
        if self.margin < -32 {
            return Err(anyhow!("min margin value is -32"));
        }
        if self.margin > 31 {
            return Err(anyhow!("max margin value is 31"));
        }

        Ok(vec![self.battery, {
            if self.margin < 0 {
                (self.margin + 64) as u8
            } else {
                self.margin as u8
            }
        }])
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct NewChannelReqPayload {
    pub ch_index: u8,
    pub freq: u32,
    pub min_dr: u8,
    pub max_dr: u8,
}

impl PayloadCodec for NewChannelReqPayload {
    fn decode(cur: &mut Cursor<Vec<u8>>) -> Result<Self> {
        let mut b = [0; 5];
        cur.read_exact(&mut b)?;

        Ok(NewChannelReqPayload {
            ch_index: b[0],
            freq: decode_freq(&b[1..4])?,
            min_dr: b[4] & 0x0f,
            max_dr: (b[4] & 0xf0) >> 4,
        })
    }

    fn encode(&self) -> Result<Vec<u8>> {
        let mut b = vec![0; 5];
        b[0] = self.ch_index;
        b[1..4].copy_from_slice(&encode_freq(self.freq)?);
        b[4] = self.min_dr | (self.max_dr << 4);

        Ok(b)
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct NewChannelAnsPayload {
    pub channel_freq_ok: bool,
    pub dr_range_ok: bool,
}

impl PayloadCodec for NewChannelAnsPayload {
    fn decode(cur: &mut Cursor<Vec<u8>>) -> Result<Self> {
        let mut b = [0; 1];
        cur.read_exact(&mut b)?;

        Ok(NewChannelAnsPayload {
            channel_freq_ok: b[0] & 0x01 != 0,
            dr_range_ok: b[0] & 0x02 != 0,
        })
    }

    fn encode(&self) -> Result<Vec<u8>> {
        let mut b: u8 = 0;
        if self.channel_freq_ok {
            b = 0x01;
        }
        if self.dr_range_ok {
            b |= 0x02;
        }
        Ok(vec![b])
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct RxTimingSetupReqPayload {
    pub delay: u8,
}

impl PayloadCodec for RxTimingSetupReqPayload {
    fn decode(cur: &mut Cursor<Vec<u8>>) -> Result<Self> {
        let mut b = [0; 1];
        cur.read_exact(&mut b)?;
        Ok(RxTimingSetupReqPayload { delay: b[0] })
    }

    fn encode(&self) -> Result<Vec<u8>> {
        if self.delay > 15 {
            return Err(anyhow!("max delay value is 15"));
        }

        Ok(vec![self.delay])
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct TxParamSetupReqPayload {
    pub uplink_dwell_time: DwellTime,
    pub downlink_dwell_time: DwellTime,
    pub max_eirp: u8,
}

impl PayloadCodec for TxParamSetupReqPayload {
    fn decode(cur: &mut Cursor<Vec<u8>>) -> Result<Self> {
        let mut b = [0; 1];
        cur.read_exact(&mut b)?;

        Ok(TxParamSetupReqPayload {
            uplink_dwell_time: {
                if b[0] & 0x10 != 0 {
                    DwellTime::Limit400ms
                } else {
                    DwellTime::NoLimit
                }
            },
            downlink_dwell_time: {
                if b[0] & 0x20 != 0 {
                    DwellTime::Limit400ms
                } else {
                    DwellTime::NoLimit
                }
            },
            max_eirp: b[0] & 0x0f,
        })
    }

    fn encode(&self) -> Result<Vec<u8>> {
        if self.max_eirp > 15 {
            return Err(anyhow!("max max_eirp value is 15"));
        }

        let mut b = vec![self.max_eirp];
        if self.uplink_dwell_time == DwellTime::Limit400ms {
            b[0] |= 0x10;
        }
        if self.downlink_dwell_time == DwellTime::Limit400ms {
            b[0] |= 0x20;
        }

        Ok(b)
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct DlChannelReqPayload {
    pub ch_index: u8,
    pub freq: u32,
}

impl PayloadCodec for DlChannelReqPayload {
    fn decode(cur: &mut Cursor<Vec<u8>>) -> Result<Self> {
        let mut b = [0; 4];
        cur.read_exact(&mut b)?;

        Ok(DlChannelReqPayload {
            ch_index: b[0],
            freq: decode_freq(&b[1..4])?,
        })
    }

    fn encode(&self) -> Result<Vec<u8>> {
        let mut b = vec![0; 4];
        b[0] = self.ch_index;
        b[1..4].copy_from_slice(&encode_freq(self.freq)?);
        Ok(b)
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct DlChannelAnsPayload {
    pub uplink_freq_exists: bool,
    pub channel_freq_ok: bool,
}

impl PayloadCodec for DlChannelAnsPayload {
    fn decode(cur: &mut Cursor<Vec<u8>>) -> Result<Self> {
        let mut b = [0; 1];
        cur.read_exact(&mut b)?;

        Ok(DlChannelAnsPayload {
            channel_freq_ok: b[0] & 0x01 != 0,
            uplink_freq_exists: b[0] & 0x02 != 0,
        })
    }

    fn encode(&self) -> Result<Vec<u8>> {
        let mut b: u8 = 0;

        if self.channel_freq_ok {
            b |= 0x01;
        }
        if self.uplink_freq_exists {
            b |= 0x02;
        }

        Ok(vec![b])
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct RekeyConfPayload {
    pub serv_lorawan_version: Version,
}

impl PayloadCodec for RekeyConfPayload {
    fn decode(cur: &mut Cursor<Vec<u8>>) -> Result<Self> {
        let mut b = [0; 1];
        cur.read_exact(&mut b)?;

        Ok(RekeyConfPayload {
            serv_lorawan_version: Version::from_u8(b[0])?,
        })
    }

    fn encode(&self) -> Result<Vec<u8>> {
        Ok(vec![self.serv_lorawan_version.to_u8()])
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct RekeyIndPayload {
    pub dev_lorawan_version: Version,
}

impl PayloadCodec for RekeyIndPayload {
    fn decode(cur: &mut Cursor<Vec<u8>>) -> Result<Self> {
        let mut b = [0; 1];
        cur.read_exact(&mut b)?;

        Ok(RekeyIndPayload {
            dev_lorawan_version: Version::from_u8(b[0])?,
        })
    }

    fn encode(&self) -> Result<Vec<u8>> {
        Ok(vec![self.dev_lorawan_version.to_u8()])
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct ADRParamSetupReqPayload {
    pub adr_param: ADRParam,
}

impl PayloadCodec for ADRParamSetupReqPayload {
    fn decode(cur: &mut Cursor<Vec<u8>>) -> Result<Self> {
        let mut b = [0; 1];
        cur.read_exact(&mut b)?;

        Ok(ADRParamSetupReqPayload {
            adr_param: ADRParam::from_u8(b[0]),
        })
    }

    fn encode(&self) -> Result<Vec<u8>> {
        Ok(vec![self.adr_param.to_u8()?])
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct ADRParam {
    pub limit_exp: u8,
    pub delay_exp: u8,
}

impl ADRParam {
    pub fn from_u8(b: u8) -> Self {
        ADRParam {
            delay_exp: b & 0x0f,
            limit_exp: b >> 4,
        }
    }

    pub fn to_u8(&self) -> Result<u8> {
        if self.limit_exp > 15 {
            return Err(anyhow!("max limit_exp value is 15"));
        }
        if self.delay_exp > 15 {
            return Err(anyhow!("max delay_exp value is 15"));
        }

        Ok(self.delay_exp | (self.limit_exp << 4))
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct DeviceTimeAnsPayload {
    pub time_since_gps_epoch: Duration,
}

impl PayloadCodec for DeviceTimeAnsPayload {
    fn decode(cur: &mut Cursor<Vec<u8>>) -> Result<Self> {
        let mut b = [0; 5];
        cur.read_exact(&mut b)?;

        let secs = {
            let mut secs_b: [u8; 4] = [0; 4];
            secs_b.copy_from_slice(&b[0..4]);
            u32::from_le_bytes(secs_b)
        } as u64;

        let nanos = (b[4] as u32) * 3906250; // second / 256

        Ok(DeviceTimeAnsPayload {
            time_since_gps_epoch: Duration::new(secs, nanos),
        })
    }

    fn encode(&self) -> Result<Vec<u8>> {
        let mut b = vec![0; 5];
        b[0..4].copy_from_slice(&(self.time_since_gps_epoch.as_secs() as u32).to_le_bytes());
        b[4] = ((self.time_since_gps_epoch.as_nanos() % 1_000_000_000) / 3906250) as u8;
        Ok(b)
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct ForceRejoinReqPayload {
    pub period: u8,
    pub max_retries: u8,
    pub rejoin_type: u8,
    pub dr: u8,
}

impl PayloadCodec for ForceRejoinReqPayload {
    fn decode(cur: &mut Cursor<Vec<u8>>) -> Result<Self> {
        let mut b = [0; 2];
        cur.read_exact(&mut b)?;

        Ok(ForceRejoinReqPayload {
            dr: b[0] & 0x0f,
            rejoin_type: (b[0] & 0x70) >> 4,
            max_retries: b[1] & 0x07,
            period: (b[1] & 0x38) >> 3,
        })
    }

    fn encode(&self) -> Result<Vec<u8>> {
        if self.period > 7 {
            return Err(anyhow!("max period value is 7"));
        }
        if self.max_retries > 7 {
            return Err(anyhow!("max max_retries value is 7"));
        }
        if self.rejoin_type != 0 && self.rejoin_type != 2 {
            return Err(anyhow!("rejoin_type must be 0 or 2"));
        }
        if self.dr > 15 {
            return Err(anyhow!("max dr value is 15"));
        }

        Ok(vec![
            self.dr | (self.rejoin_type << 4),
            self.max_retries | (self.period << 3),
        ])
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct RejoinParamSetupReqPayload {
    pub max_time_n: u8,
    pub max_count_n: u8,
}

impl PayloadCodec for RejoinParamSetupReqPayload {
    fn decode(cur: &mut Cursor<Vec<u8>>) -> Result<Self> {
        let mut b = [0; 1];
        cur.read_exact(&mut b)?;

        Ok(RejoinParamSetupReqPayload {
            max_count_n: b[0] & 0x0f,
            max_time_n: (b[0] & 0xf0) >> 4,
        })
    }

    fn encode(&self) -> Result<Vec<u8>> {
        if self.max_time_n > 15 {
            return Err(anyhow!("max max_time_n value is 15"));
        }
        if self.max_count_n > 15 {
            return Err(anyhow!("max max_count_n value is 15"));
        }

        Ok(vec![self.max_count_n | (self.max_time_n << 4)])
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct RejoinParamSetupAnsPayload {
    pub time_ok: bool,
}

impl PayloadCodec for RejoinParamSetupAnsPayload {
    fn decode(cur: &mut Cursor<Vec<u8>>) -> Result<Self> {
        let mut b = [0; 1];
        cur.read_exact(&mut b)?;

        Ok(RejoinParamSetupAnsPayload {
            time_ok: b[0] & 0x01 != 0,
        })
    }

    fn encode(&self) -> Result<Vec<u8>> {
        let mut b: u8 = 0;
        if self.time_ok {
            b = 0x01;
        }
        Ok(vec![b])
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct PingSlotInfoReqPayload {
    pub periodicity: u8,
}

impl PayloadCodec for PingSlotInfoReqPayload {
    fn decode(cur: &mut Cursor<Vec<u8>>) -> Result<Self> {
        let mut b = [0; 1];
        cur.read_exact(&mut b)?;

        Ok(PingSlotInfoReqPayload {
            periodicity: b[0] & 0x07,
        })
    }

    fn encode(&self) -> Result<Vec<u8>> {
        if self.periodicity > 7 {
            return Err(anyhow!("max periodicity value is 7"));
        }

        Ok(vec![self.periodicity])
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct PingSlotChannelReqPayload {
    pub freq: u32,
    pub dr: u8,
}

impl PayloadCodec for PingSlotChannelReqPayload {
    fn decode(cur: &mut Cursor<Vec<u8>>) -> Result<Self> {
        let mut b = [0; 4];
        cur.read_exact(&mut b)?;

        Ok(PingSlotChannelReqPayload {
            freq: decode_freq(&b[0..3])?,
            dr: b[3] & 0x0f,
        })
    }

    fn encode(&self) -> Result<Vec<u8>> {
        let mut b = vec![0; 4];
        b[0..3].copy_from_slice(&encode_freq(self.freq)?);
        b[3] = self.dr;
        Ok(b)
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct PingSlotChannelAnsPayload {
    pub dr_ok: bool,
    pub channel_freq_ok: bool,
}

impl PayloadCodec for PingSlotChannelAnsPayload {
    fn decode(cur: &mut Cursor<Vec<u8>>) -> Result<Self> {
        let mut b = [0; 1];
        cur.read_exact(&mut b)?;

        Ok(PingSlotChannelAnsPayload {
            channel_freq_ok: b[0] & 0x01 != 0,
            dr_ok: b[0] & 0x02 != 0,
        })
    }

    fn encode(&self) -> Result<Vec<u8>> {
        let mut b = 0;
        if self.channel_freq_ok {
            b = 0x01;
        }
        if self.dr_ok {
            b |= 0x02;
        }
        Ok(vec![b])
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct BeaconFreqReqPayload {
    pub freq: u32,
}

impl PayloadCodec for BeaconFreqReqPayload {
    fn decode(cur: &mut Cursor<Vec<u8>>) -> Result<Self> {
        let mut b = [0; 3];
        cur.read_exact(&mut b)?;

        Ok(BeaconFreqReqPayload {
            freq: decode_freq(&b)?,
        })
    }

    fn encode(&self) -> Result<Vec<u8>> {
        Ok(encode_freq(self.freq)?.to_vec())
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct BeaconFreqAnsPayload {
    beacon_freq_ok: bool,
}

impl PayloadCodec for BeaconFreqAnsPayload {
    fn decode(cur: &mut Cursor<Vec<u8>>) -> Result<Self> {
        let mut b = [0; 1];
        cur.read_exact(&mut b)?;

        Ok(BeaconFreqAnsPayload {
            beacon_freq_ok: b[0] & 0x01 != 0,
        })
    }

    fn encode(&self) -> Result<Vec<u8>> {
        let mut b: u8 = 0;
        if self.beacon_freq_ok {
            b = 0x01;
        }
        Ok(vec![b])
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct DeviceModeIndPayload {
    pub class: DeviceModeClass,
}

impl PayloadCodec for DeviceModeIndPayload {
    fn decode(cur: &mut Cursor<Vec<u8>>) -> Result<Self> {
        let mut b = [0; 1];
        cur.read_exact(&mut b)?;

        Ok(DeviceModeIndPayload {
            class: DeviceModeClass::from_u8(b[0])?,
        })
    }

    fn encode(&self) -> Result<Vec<u8>> {
        Ok(vec![self.class.to_u8()])
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct DeviceModeConfPayload {
    pub class: DeviceModeClass,
}

impl PayloadCodec for DeviceModeConfPayload {
    fn decode(cur: &mut Cursor<Vec<u8>>) -> Result<Self> {
        let mut b = [0; 1];
        cur.read_exact(&mut b)?;

        Ok(DeviceModeConfPayload {
            class: DeviceModeClass::from_u8(b[0])?,
        })
    }

    fn encode(&self) -> Result<Vec<u8>> {
        Ok(vec![self.class.to_u8()])
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct ChannelSettingsRelay {
    pub start_stop: u8,
    pub cad_periodicity: u8,
    pub default_ch_idx: u8,
    pub second_ch_idx: u8,
    pub second_ch_dr: u8,
    pub second_ch_ack_offset: u8,
}

impl ChannelSettingsRelay {
    pub fn to_bytes(&self) -> Result<[u8; 2]> {
        if self.start_stop > 1 {
            return Err(anyhow!("max value of start_stop is 1"));
        }
        if self.cad_periodicity > 7 {
            return Err(anyhow!("max value of cad_periodicity is 7"));
        }
        if self.default_ch_idx > 1 {
            return Err(anyhow!("max value of default_ch_idx is 1"));
        }
        if self.second_ch_idx > 1 {
            return Err(anyhow!("max value of second_ch_idx is 1"));
        }
        if self.second_ch_dr > 15 {
            return Err(anyhow!("max value of second_ch_dr is 15"));
        }
        if self.second_ch_ack_offset > 7 {
            return Err(anyhow!("max value of second_ch_ack_offset is 7"));
        }

        Ok([
            self.second_ch_ack_offset | (self.second_ch_dr << 3) | (self.second_ch_idx << 7),
            (self.second_ch_idx >> 1)
                | (self.default_ch_idx << 1)
                | (self.cad_periodicity << 2)
                | (self.start_stop << 5),
        ])
    }

    pub fn from_bytes(b: [u8; 2]) -> Self {
        ChannelSettingsRelay {
            second_ch_ack_offset: b[0] & 0x07,
            second_ch_dr: (b[0] & 0x78) >> 3,
            second_ch_idx: ((b[0] & 0x80) >> 7) | ((b[1] & 0x01) << 1),
            default_ch_idx: (b[1] & 0x02) >> 1,
            cad_periodicity: (b[1] & 0x1c) >> 2,
            start_stop: (b[1] & 0x20) >> 5,
        }
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct RelayConfReqPayload {
    pub channel_settings_relay: ChannelSettingsRelay,
    pub second_ch_freq: u32,
}

impl PayloadCodec for RelayConfReqPayload {
    fn decode(cur: &mut Cursor<Vec<u8>>) -> Result<Self> {
        let mut b = [0; 5];
        cur.read_exact(&mut b)?;

        Ok(RelayConfReqPayload {
            channel_settings_relay: ChannelSettingsRelay::from_bytes([b[0], b[1]]),
            second_ch_freq: decode_freq(&b[2..5])?,
        })
    }

    fn encode(&self) -> Result<Vec<u8>> {
        let mut b = vec![0; 5];
        b[0..2].copy_from_slice(&self.channel_settings_relay.to_bytes()?);
        b[2..5].copy_from_slice(&encode_freq(self.second_ch_freq)?);
        Ok(b)
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct RelayConfAnsPayload {
    pub second_ch_freq_ack: bool,
    pub second_ch_ack_offset_ack: bool,
    pub second_ch_dr_ack: bool,
    pub second_ch_idx_ack: bool,
    pub default_ch_idx_ack: bool,
    pub cad_periodicity_ack: bool,
}

impl PayloadCodec for RelayConfAnsPayload {
    fn decode(cur: &mut Cursor<Vec<u8>>) -> Result<Self> {
        let mut b = [0; 1];
        cur.read_exact(&mut b)?;

        Ok(RelayConfAnsPayload {
            second_ch_freq_ack: b[0] & 0x01 != 0,
            second_ch_ack_offset_ack: b[0] & 0x02 != 0,
            second_ch_dr_ack: b[0] & 0x04 != 0,
            second_ch_idx_ack: b[0] & 0x08 != 0,
            default_ch_idx_ack: b[0] & 0x10 != 0,
            cad_periodicity_ack: b[0] & 0x20 != 0,
        })
    }

    fn encode(&self) -> Result<Vec<u8>> {
        let mut b: u8 = 0;

        if self.second_ch_freq_ack {
            b |= 0x01;
        }

        if self.second_ch_ack_offset_ack {
            b |= 0x02;
        }

        if self.second_ch_dr_ack {
            b |= 0x04;
        }

        if self.second_ch_idx_ack {
            b |= 0x08;
        }

        if self.default_ch_idx_ack {
            b |= 0x10;
        }

        if self.cad_periodicity_ack {
            b |= 0x20;
        }

        Ok(vec![b])
    }
}

#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum RelayModeActivation {
    #[default]
    DisableRelayMode,
    EnableRelayMode,
    Dynamic,
    EndDeviceControlled,
}

impl RelayModeActivation {
    pub fn to_u8(&self) -> u8 {
        match self {
            RelayModeActivation::DisableRelayMode => 0x00,
            RelayModeActivation::EnableRelayMode => 0x01,
            RelayModeActivation::Dynamic => 0x02,
            RelayModeActivation::EndDeviceControlled => 0x03,
        }
    }

    pub fn from_u8(v: u8) -> Result<Self> {
        Ok(match v {
            0x00 => RelayModeActivation::DisableRelayMode,
            0x01 => RelayModeActivation::EnableRelayMode,
            0x02 => RelayModeActivation::Dynamic,
            0x03 => RelayModeActivation::EndDeviceControlled,
            _ => {
                return Err(anyhow!("invalid RelayModeActivation: {}", v));
            }
        })
    }
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub enum ResetLimitCounter {
    TokenCounterToZero,
    TokenCounterToReloadRate,
    TokenCounterToMaxValue,
    NoChange,
}

impl ResetLimitCounter {
    pub fn to_u8(&self) -> u8 {
        match self {
            ResetLimitCounter::TokenCounterToZero => 0x00,
            ResetLimitCounter::TokenCounterToReloadRate => 0x01,
            ResetLimitCounter::TokenCounterToMaxValue => 0x02,
            ResetLimitCounter::NoChange => 0x03,
        }
    }

    pub fn from_u8(v: u8) -> Result<Self> {
        Ok(match v {
            0x00 => ResetLimitCounter::TokenCounterToZero,
            0x01 => ResetLimitCounter::TokenCounterToReloadRate,
            0x02 => ResetLimitCounter::TokenCounterToMaxValue,
            0x03 => ResetLimitCounter::NoChange,
            _ => {
                return Err(anyhow!("Invalid ResetLimitCounter value: {}", v));
            }
        })
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct ActivationRelayMode {
    pub relay_mode_activation: RelayModeActivation,
    pub smart_enable_level: u8,
}

impl ActivationRelayMode {
    pub fn from_u8(b: u8) -> Result<Self> {
        Ok(ActivationRelayMode {
            relay_mode_activation: RelayModeActivation::from_u8((b & 0x0c) >> 2)?,
            smart_enable_level: b & 0x03,
        })
    }

    pub fn to_u8(&self) -> Result<u8> {
        if self.smart_enable_level > 3 {
            return Err(anyhow!("max value of smart_enable_level is 3"));
        }

        Ok((self.relay_mode_activation.to_u8() << 2) | self.smart_enable_level)
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct ChannelSettingsED {
    pub second_ch_ack_offset: u8,
    pub second_ch_dr: u8,
    pub second_ch_idx: u8,
    pub backoff: u8,
}

impl ChannelSettingsED {
    pub fn from_bytes(b: [u8; 2]) -> Self {
        ChannelSettingsED {
            second_ch_ack_offset: b[0] & 0x07,
            second_ch_dr: (b[0] & 0x78) >> 3,
            second_ch_idx: ((b[0] & 0x80) >> 7) | ((b[1] & 0x01) << 1),
            backoff: (b[1] & 0x7e) >> 1,
        }
    }

    pub fn to_bytes(&self) -> Result<[u8; 2]> {
        if self.second_ch_ack_offset > 7 {
            return Err(anyhow!("max value of second_ch_ack_offset is 7"));
        }
        if self.second_ch_dr > 15 {
            return Err(anyhow!("max value of second_ch_dr is 15"));
        }
        if self.second_ch_idx > 3 {
            return Err(anyhow!("max value of second_ch_idx is 3"));
        }
        if self.backoff > 63 {
            return Err(anyhow!("max value of backoff is 63"));
        }

        Ok([
            self.second_ch_ack_offset | (self.second_ch_dr << 3) | (self.second_ch_idx << 7),
            (self.second_ch_idx >> 1) | (self.backoff << 1),
        ])
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct EndDeviceConfReqPayload {
    pub activation_relay_mode: ActivationRelayMode,
    pub channel_settings_ed: ChannelSettingsED,
    pub second_ch_freq: u32,
}

impl PayloadCodec for EndDeviceConfReqPayload {
    fn decode(cur: &mut Cursor<Vec<u8>>) -> Result<Self> {
        let mut b = [0; 6];
        cur.read_exact(&mut b)?;

        Ok(EndDeviceConfReqPayload {
            activation_relay_mode: ActivationRelayMode::from_u8(b[0])?,
            channel_settings_ed: ChannelSettingsED::from_bytes([b[1], b[2]]),
            second_ch_freq: decode_freq(&b[3..6])?,
        })
    }

    fn encode(&self) -> Result<Vec<u8>> {
        let mut b = vec![0; 6];
        b[0] = self.activation_relay_mode.to_u8()?;
        b[1..3].copy_from_slice(&self.channel_settings_ed.to_bytes()?);
        b[3..6].copy_from_slice(&encode_freq(self.second_ch_freq)?);
        Ok(b)
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct EndDeviceConfAnsPayload {
    pub second_ch_freq_ack: bool,
    pub second_ch_dr_ack: bool,
    pub second_ch_idx_ack: bool,
    pub backoff_ack: bool,
}

impl PayloadCodec for EndDeviceConfAnsPayload {
    fn decode(cur: &mut Cursor<Vec<u8>>) -> Result<Self> {
        let mut b = [0; 1];
        cur.read_exact(&mut b)?;

        Ok(EndDeviceConfAnsPayload {
            second_ch_freq_ack: b[0] & 0x01 != 0,
            second_ch_dr_ack: b[0] & 0x02 != 0,
            second_ch_idx_ack: b[0] & 0x04 != 0,
            backoff_ack: b[0] & 0x08 != 0,
        })
    }

    fn encode(&self) -> Result<Vec<u8>> {
        let mut b: u8 = 0;

        if self.second_ch_freq_ack {
            b |= 0x01;
        }
        if self.second_ch_dr_ack {
            b |= 0x02;
        }
        if self.second_ch_idx_ack {
            b |= 0x04;
        }
        if self.backoff_ack {
            b |= 0x08;
        }

        Ok(vec![b])
    }
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub enum FilterListAction {
    NoRule,
    Forward,
    Filter,
}

impl FilterListAction {
    pub fn to_u8(&self) -> u8 {
        match self {
            FilterListAction::NoRule => 0x00,
            FilterListAction::Forward => 0x01,
            FilterListAction::Filter => 0x02,
        }
    }

    pub fn from_u8(v: u8) -> Result<Self> {
        Ok(match v {
            0x00 => FilterListAction::NoRule,
            0x01 => FilterListAction::Forward,
            0x02 => FilterListAction::Filter,
            _ => {
                return Err(anyhow!("invalid FilterListAction: {}", v));
            }
        })
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct FilterListReqPayload {
    pub filter_list_idx: u8,
    pub filter_list_action: FilterListAction,
    pub filter_list_eui: Vec<u8>,
}

impl PayloadCodec for FilterListReqPayload {
    fn decode(cur: &mut Cursor<Vec<u8>>) -> Result<Self> {
        let mut b = vec![0; 2];
        cur.read_exact(&mut b)?;

        let len = b[0] & 0x17;
        let mut eui = vec![0; len as usize];
        cur.read_exact(&mut eui)?;

        // decode from little-endian
        eui.reverse();

        Ok(FilterListReqPayload {
            filter_list_action: FilterListAction::from_u8((b[0] & 0x60) >> 5)?,
            filter_list_idx: ((b[0] & 0x80) >> 7) | ((b[1] & 0x07) << 1),
            filter_list_eui: eui,
        })
    }

    fn encode(&self) -> Result<Vec<u8>> {
        if self.filter_list_idx > 15 {
            return Err(anyhow!("max filter_list_idx value is 15"));
        }
        if self.filter_list_eui.len() > 16 {
            return Err(anyhow!("max filter_list_eui length is 16"));
        }

        let mut b = vec![
            self.filter_list_eui.len() as u8
                | (self.filter_list_action.to_u8() << 5)
                | (self.filter_list_idx << 7),
            (self.filter_list_idx >> 1),
        ];

        // encode as little-endian
        let mut filter_list_eui = self.filter_list_eui.clone();
        filter_list_eui.reverse();

        b.extend_from_slice(&filter_list_eui);
        Ok(b)
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct FilterListAnsPayload {
    pub filter_list_action_ack: bool,
    pub filter_list_len_ack: bool,
    pub combined_rules_ack: bool,
}

impl PayloadCodec for FilterListAnsPayload {
    fn decode(cur: &mut Cursor<Vec<u8>>) -> Result<Self> {
        let mut b = [0; 1];
        cur.read_exact(&mut b)?;

        Ok(FilterListAnsPayload {
            filter_list_action_ack: b[0] & 0x01 != 0,
            filter_list_len_ack: b[0] & 0x02 != 0,
            combined_rules_ack: b[0] & 0x04 != 0,
        })
    }

    fn encode(&self) -> Result<Vec<u8>> {
        let mut b: u8 = 0;

        if self.filter_list_action_ack {
            b |= 0x01;
        }
        if self.filter_list_len_ack {
            b |= 0x02;
        }
        if self.combined_rules_ack {
            b |= 0x04;
        }

        Ok(vec![b])
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct UplinkLimitPL {
    pub reload_rate: u8,
    pub bucket_size: u8,
}

impl UplinkLimitPL {
    pub fn from_u8(v: u8) -> Self {
        UplinkLimitPL {
            reload_rate: v & 0x3f,
            bucket_size: (v & 0xc0) >> 6,
        }
    }

    pub fn to_u8(&self) -> Result<u8> {
        if self.reload_rate > 63 {
            return Err(anyhow!("max reload_rate value is 63"));
        }
        if self.bucket_size > 3 {
            return Err(anyhow!("max bucket_size value is 3"));
        }

        Ok(self.reload_rate | (self.bucket_size << 6))
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct UpdateUplinkListReqPayload {
    pub uplink_list_idx: u8,
    pub uplink_limit: UplinkLimitPL,
    pub dev_addr: crate::DevAddr,
    pub w_fcnt: u32,
    pub root_wor_s_key: crate::AES128Key,
}

impl PayloadCodec for UpdateUplinkListReqPayload {
    fn decode(cur: &mut Cursor<Vec<u8>>) -> Result<Self> {
        let mut b = [0; 26];
        cur.read_exact(&mut b)?;

        Ok(UpdateUplinkListReqPayload {
            uplink_list_idx: b[0] & 0x0f,
            uplink_limit: UplinkLimitPL::from_u8(b[1]),
            dev_addr: crate::DevAddr::from_le_bytes({
                let mut bb = [0; 4];
                bb.copy_from_slice(&b[2..6]);
                bb
            }),
            w_fcnt: u32::from_le_bytes({
                let mut bb = [0; 4];
                bb.copy_from_slice(&b[6..10]);
                bb
            }),
            root_wor_s_key: crate::AES128Key::from_bytes({
                let mut bb = [0; 16];
                bb.copy_from_slice(&b[10..26]);
                bb
            }),
        })
    }

    fn encode(&self) -> Result<Vec<u8>> {
        if self.uplink_list_idx > 15 {
            return Err(anyhow!("max uplink_list_idx value is 15"));
        }

        let mut b = vec![0; 26];
        b[0] = self.uplink_list_idx;
        b[1] = self.uplink_limit.to_u8()?;
        b[2..6].copy_from_slice(&self.dev_addr.to_le_bytes());
        b[6..10].copy_from_slice(&self.w_fcnt.to_le_bytes());
        b[10..26].copy_from_slice(&self.root_wor_s_key.to_bytes());
        Ok(b)
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct CtrlUplinkActionPL {
    pub uplink_list_idx: u8,
    pub ctrl_uplink_action: u8,
}

impl CtrlUplinkActionPL {
    pub fn to_u8(&self) -> Result<u8> {
        if self.uplink_list_idx > 15 {
            return Err(anyhow!("max uplink_list_idx value is 15"));
        }
        if self.ctrl_uplink_action > 1 {
            return Err(anyhow!("max ctrl_uplink_action is 1"));
        }

        Ok(self.uplink_list_idx | (self.ctrl_uplink_action << 4))
    }

    pub fn from_u8(v: u8) -> Self {
        CtrlUplinkActionPL {
            uplink_list_idx: v & 0x0f,
            ctrl_uplink_action: (v & 0x10) >> 4,
        }
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct CtrlUplinkListReqPayload {
    pub ctrl_uplink_action: CtrlUplinkActionPL,
}

impl PayloadCodec for CtrlUplinkListReqPayload {
    fn decode(cur: &mut Cursor<Vec<u8>>) -> Result<Self> {
        let mut b = [0; 1];
        cur.read_exact(&mut b)?;

        Ok(CtrlUplinkListReqPayload {
            ctrl_uplink_action: CtrlUplinkActionPL::from_u8(b[0]),
        })
    }

    fn encode(&self) -> Result<Vec<u8>> {
        Ok(vec![self.ctrl_uplink_action.to_u8()?])
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct CtrlUplinkListAnsPayload {
    pub uplink_list_idx_ack: bool,
    pub w_fcnt: u32,
}

impl PayloadCodec for CtrlUplinkListAnsPayload {
    fn decode(cur: &mut Cursor<Vec<u8>>) -> Result<Self> {
        let mut b = [0; 5];
        cur.read_exact(&mut b)?;

        Ok(CtrlUplinkListAnsPayload {
            uplink_list_idx_ack: b[0] & 0x01 > 0,
            w_fcnt: u32::from_le_bytes({
                let mut bb = [0; 4];
                bb.copy_from_slice(&b[1..5]);
                bb
            }),
        })
    }

    fn encode(&self) -> Result<Vec<u8>> {
        let mut b = vec![0; 5];
        if self.uplink_list_idx_ack {
            b[0] |= 0x01;
        }
        b[1..5].copy_from_slice(&self.w_fcnt.to_le_bytes());
        Ok(b)
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct FwdLimitReloadRatePL {
    pub overall_reload_rate: u8,
    pub global_uplink_reload_rate: u8,
    pub notify_reload_rate: u8,
    pub join_req_reload_rate: u8,
    pub reset_limit_counter: ResetLimitCounter,
}

impl FwdLimitReloadRatePL {
    pub fn from_bytes(b: [u8; 4]) -> Result<Self> {
        Ok(FwdLimitReloadRatePL {
            overall_reload_rate: b[0] & 0x7f,
            global_uplink_reload_rate: (b[0] >> 7) | ((b[1] & 0x3f) << 1),
            notify_reload_rate: (b[1] >> 6) | ((b[2] & 0x1f) << 2),
            join_req_reload_rate: (b[2] >> 5) | ((b[3] & 0x0f) << 3),
            reset_limit_counter: ResetLimitCounter::from_u8((b[3] & 0x30) >> 4)?,
        })
    }

    pub fn to_bytes(&self) -> Result<[u8; 4]> {
        if self.join_req_reload_rate > 127 {
            return Err(anyhow!("max join_req_reload_rate is 127"));
        }
        if self.notify_reload_rate > 127 {
            return Err(anyhow!("max notify_reload_rate is 127"));
        }
        if self.global_uplink_reload_rate > 127 {
            return Err(anyhow!("max global_uplink_reload_rate is 127"));
        }
        if self.overall_reload_rate > 127 {
            return Err(anyhow!("max overall_reload_rate is 127"));
        }

        Ok([
            self.overall_reload_rate | (self.global_uplink_reload_rate << 7),
            (self.global_uplink_reload_rate >> 1) | (self.notify_reload_rate << 6),
            (self.notify_reload_rate >> 2) | (self.join_req_reload_rate << 5),
            (self.join_req_reload_rate >> 3) | (self.reset_limit_counter.to_u8() << 4),
        ])
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct FwdLimitLoadCapacityPL {
    pub overall_limit_size: u8,
    pub global_uplink_limit_size: u8,
    pub notify_limit_size: u8,
    pub join_req_limit_size: u8,
}

impl FwdLimitLoadCapacityPL {
    pub fn from_u8(v: u8) -> Self {
        FwdLimitLoadCapacityPL {
            overall_limit_size: v & 0x03,
            global_uplink_limit_size: (v & 0x0c) >> 2,
            notify_limit_size: (v & 0x30) >> 4,
            join_req_limit_size: (v & 0xc0) >> 6,
        }
    }

    pub fn to_u8(&self) -> Result<u8> {
        if self.overall_limit_size > 3 {
            return Err(anyhow!("max overall_limit_size is 3"));
        }
        if self.global_uplink_limit_size > 3 {
            return Err(anyhow!("max global_uplink_limit_size is 3"));
        }
        if self.notify_limit_size > 3 {
            return Err(anyhow!("max notify_limit_size is 3"));
        }
        if self.join_req_limit_size > 3 {
            return Err(anyhow!("max join_req_limit_size is 3"));
        }

        Ok(self.overall_limit_size
            | (self.global_uplink_limit_size << 2)
            | (self.notify_limit_size << 4)
            | (self.join_req_limit_size << 6))
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct ConfigureFwdLimitReqPayload {
    pub reload_rate: FwdLimitReloadRatePL,
    pub load_capacity: FwdLimitLoadCapacityPL,
}

impl PayloadCodec for ConfigureFwdLimitReqPayload {
    fn decode(cur: &mut Cursor<Vec<u8>>) -> Result<Self> {
        let mut b = [0; 5];
        cur.read_exact(&mut b)?;

        Ok(ConfigureFwdLimitReqPayload {
            reload_rate: FwdLimitReloadRatePL::from_bytes({
                let mut bb = [0; 4];
                bb.copy_from_slice(&b[0..4]);
                bb
            })?,
            load_capacity: FwdLimitLoadCapacityPL::from_u8(b[4]),
        })
    }

    fn encode(&self) -> Result<Vec<u8>> {
        let mut b = vec![0; 5];
        b[0..4].copy_from_slice(&self.reload_rate.to_bytes()?);
        b[4] = self.load_capacity.to_u8()?;
        Ok(b)
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct PowerLevel {
    pub wor_snr: isize,
    pub wor_rssi: isize,
}

impl PowerLevel {
    pub fn from_bytes(b: [u8; 2]) -> Self {
        PowerLevel {
            wor_snr: (b[0] & 0x1f) as isize - 20,
            wor_rssi: -(((b[0] >> 5) | ((b[1] & 0x0f) << 3)) as isize) - 15,
        }
    }

    pub fn to_bytes(&self) -> [u8; 2] {
        let wor_snr = self.wor_snr.clamp(-20, 11);
        let wor_rssi = self.wor_rssi.clamp(-142, -15);

        // Encode values
        let wor_snr = (wor_snr + 20) as u8;
        let wor_rssi = -(wor_rssi + 15) as u8;

        [wor_snr | (wor_rssi << 5), wor_rssi >> 3]
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct NotifyNewEndDeviceReqPayload {
    pub dev_addr: crate::DevAddr,
    pub power_level: PowerLevel,
}

impl PayloadCodec for NotifyNewEndDeviceReqPayload {
    fn decode(cur: &mut Cursor<Vec<u8>>) -> Result<Self> {
        let mut b = [0; 6];
        cur.read_exact(&mut b)?;

        Ok(NotifyNewEndDeviceReqPayload {
            dev_addr: crate::DevAddr::from_le_bytes([b[0], b[1], b[2], b[3]]),
            power_level: PowerLevel::from_bytes([b[4], b[5]]),
        })
    }

    fn encode(&self) -> Result<Vec<u8>> {
        let mut b = vec![0; 6];
        b[0..4].copy_from_slice(&self.dev_addr.to_le_bytes());
        b[4..6].copy_from_slice(&self.power_level.to_bytes());
        Ok(b)
    }
}

#[cfg(test)]
mod test {
    use super::*;

    struct MacTest {
        uplink: bool,
        command: MACCommand,
        bytes: Vec<u8>,
    }

    #[test]
    fn test_command() {
        let tests = vec![
            MacTest {
                uplink: true,
                command: MACCommand::ResetInd(ResetIndPayload {
                    dev_lorawan_version: Version::LoRaWAN1_1,
                }),
                bytes: vec![0x01, 0x01],
            },
            MacTest {
                uplink: false,
                command: MACCommand::ResetConf(ResetConfPayload {
                    serv_lorawan_version: Version::LoRaWAN1_1,
                }),
                bytes: vec![0x01, 0x01],
            },
            MacTest {
                uplink: true,
                command: MACCommand::LinkCheckReq,
                bytes: vec![0x02],
            },
            MacTest {
                uplink: false,
                command: MACCommand::LinkCheckAns(LinkCheckAnsPayload {
                    margin: 10,
                    gw_cnt: 15,
                }),
                bytes: vec![0x02, 0x0a, 0x0f],
            },
            MacTest {
                uplink: false,
                command: MACCommand::LinkADRReq(LinkADRReqPayload {
                    dr: 1,
                    tx_power: 2,
                    ch_mask: ChMask::new({
                        let mut mask: [bool; 16] = [false; 16];
                        mask[2] = true;
                        mask
                    }),
                    redundancy: Redundancy {
                        ch_mask_cntl: 4,
                        nb_rep: 5,
                    },
                }),
                bytes: vec![0x03, 0x12, 0x04, 0x00, 0x45],
            },
            MacTest {
                uplink: true,
                command: MACCommand::LinkADRAns(LinkADRAnsPayload {
                    ch_mask_ack: true,
                    dr_ack: false,
                    tx_power_ack: false,
                }),
                bytes: vec![0x03, 0x01],
            },
            MacTest {
                uplink: true,
                command: MACCommand::LinkADRAns(LinkADRAnsPayload {
                    ch_mask_ack: false,
                    dr_ack: true,
                    tx_power_ack: false,
                }),
                bytes: vec![0x03, 0x02],
            },
            MacTest {
                uplink: true,
                command: MACCommand::LinkADRAns(LinkADRAnsPayload {
                    ch_mask_ack: false,
                    dr_ack: false,
                    tx_power_ack: true,
                }),
                bytes: vec![0x03, 0x04],
            },
            MacTest {
                uplink: true,
                command: MACCommand::LinkADRAns(LinkADRAnsPayload {
                    ch_mask_ack: true,
                    dr_ack: true,
                    tx_power_ack: true,
                }),
                bytes: vec![0x03, 0x07],
            },
            MacTest {
                uplink: false,
                command: MACCommand::DutyCycleReq(DutyCycleReqPayload { max_duty_cycle: 13 }),
                bytes: vec![0x04, 0x0d],
            },
            MacTest {
                uplink: true,
                command: MACCommand::DutyCycleAns,
                bytes: vec![0x04],
            },
            MacTest {
                uplink: false,
                command: MACCommand::RxParamSetupReq(RxParamSetupReqPayload {
                    frequency: 26265700,
                    dl_settings: DLSettings {
                        rx2_dr: 11,
                        rx1_dr_offset: 3,
                        opt_neg: false,
                    },
                }),
                bytes: vec![0x05, 0x3b, 0x01, 0x02, 0x04],
            },
            MacTest {
                uplink: true,
                command: MACCommand::RxParamSetupAns(RxParamSetupAnsPayload {
                    channel_ack: true,
                    rx2_dr_ack: false,
                    rx1_dr_offset_ack: true,
                }),
                bytes: vec![0x05, 0x05],
            },
            MacTest {
                uplink: false,
                command: MACCommand::DevStatusReq,
                bytes: vec![0x06],
            },
            MacTest {
                uplink: true,
                command: MACCommand::DevStatusAns(DevStatusAnsPayload {
                    battery: 0,
                    margin: -30,
                }),
                bytes: vec![0x06, 0x00, 0x22],
            },
            MacTest {
                uplink: true,
                command: MACCommand::DevStatusAns(DevStatusAnsPayload {
                    battery: 255,
                    margin: 30,
                }),
                bytes: vec![0x06, 0xff, 0x1e],
            },
            MacTest {
                uplink: true,
                command: MACCommand::DevStatusAns(DevStatusAnsPayload {
                    battery: 127,
                    margin: -1,
                }),
                bytes: vec![0x06, 0x7f, 0x3f],
            },
            MacTest {
                uplink: true,
                command: MACCommand::DevStatusAns(DevStatusAnsPayload {
                    battery: 127,
                    margin: 0,
                }),
                bytes: vec![0x06, 0x7f, 0x00],
            },
            MacTest {
                uplink: false,
                command: MACCommand::NewChannelReq(NewChannelReqPayload {
                    ch_index: 3,
                    freq: 26265700,
                    max_dr: 5,
                    min_dr: 10,
                }),
                bytes: vec![0x07, 0x03, 0x01, 0x02, 0x04, 0x5a],
            },
            MacTest {
                uplink: false,
                command: MACCommand::NewChannelReq(NewChannelReqPayload {
                    ch_index: 3,
                    freq: 2_410_000_000,
                    max_dr: 5,
                    min_dr: 0,
                }),
                bytes: vec![7, 3, 80, 222, 183, 80],
            },
            MacTest {
                uplink: true,
                command: MACCommand::NewChannelAns(NewChannelAnsPayload {
                    channel_freq_ok: false,
                    dr_range_ok: false,
                }),
                bytes: vec![0x07, 0x00],
            },
            MacTest {
                uplink: true,
                command: MACCommand::NewChannelAns(NewChannelAnsPayload {
                    channel_freq_ok: true,
                    dr_range_ok: false,
                }),
                bytes: vec![0x07, 0x01],
            },
            MacTest {
                uplink: true,
                command: MACCommand::NewChannelAns(NewChannelAnsPayload {
                    channel_freq_ok: false,
                    dr_range_ok: true,
                }),
                bytes: vec![0x07, 0x02],
            },
            MacTest {
                uplink: true,
                command: MACCommand::NewChannelAns(NewChannelAnsPayload {
                    channel_freq_ok: true,
                    dr_range_ok: true,
                }),
                bytes: vec![0x07, 0x03],
            },
            MacTest {
                uplink: false,
                command: MACCommand::RxTimingSetupReq(RxTimingSetupReqPayload { delay: 15 }),
                bytes: vec![0x08, 0x0f],
            },
            MacTest {
                uplink: true,
                command: MACCommand::RxTimingSetupAns,
                bytes: vec![0x08],
            },
            MacTest {
                uplink: false,
                command: MACCommand::TxParamSetupReq(TxParamSetupReqPayload {
                    uplink_dwell_time: DwellTime::NoLimit,
                    downlink_dwell_time: DwellTime::NoLimit,
                    max_eirp: 15,
                }),
                bytes: vec![0x09, 0x0f],
            },
            MacTest {
                uplink: false,
                command: MACCommand::TxParamSetupReq(TxParamSetupReqPayload {
                    uplink_dwell_time: DwellTime::Limit400ms,
                    downlink_dwell_time: DwellTime::NoLimit,
                    max_eirp: 15,
                }),
                bytes: vec![0x09, 0x1f],
            },
            MacTest {
                uplink: false,
                command: MACCommand::TxParamSetupReq(TxParamSetupReqPayload {
                    uplink_dwell_time: DwellTime::NoLimit,
                    downlink_dwell_time: DwellTime::Limit400ms,
                    max_eirp: 15,
                }),
                bytes: vec![0x09, 0x2f],
            },
            MacTest {
                uplink: true,
                command: MACCommand::TxParamSetupAns,
                bytes: vec![0x09],
            },
            MacTest {
                uplink: false,
                command: MACCommand::DlChannelReq(DlChannelReqPayload {
                    ch_index: 0,
                    freq: 868100000,
                }),
                bytes: vec![0x0a, 0x00, 0x28, 0x76, 0x84],
            },
            MacTest {
                uplink: false,
                command: MACCommand::DlChannelReq(DlChannelReqPayload {
                    ch_index: 1,
                    freq: 868200000,
                }),
                bytes: vec![0x0a, 0x01, 0x10, 0x7a, 0x84],
            },
            MacTest {
                uplink: true,
                command: MACCommand::DlChannelAns(DlChannelAnsPayload {
                    uplink_freq_exists: false,
                    channel_freq_ok: false,
                }),
                bytes: vec![0x0a, 0x00],
            },
            MacTest {
                uplink: true,
                command: MACCommand::DlChannelAns(DlChannelAnsPayload {
                    uplink_freq_exists: false,
                    channel_freq_ok: true,
                }),
                bytes: vec![0x0a, 0x01],
            },
            MacTest {
                uplink: true,
                command: MACCommand::DlChannelAns(DlChannelAnsPayload {
                    uplink_freq_exists: true,
                    channel_freq_ok: false,
                }),
                bytes: vec![0x0a, 0x02],
            },
            MacTest {
                uplink: true,
                command: MACCommand::DlChannelAns(DlChannelAnsPayload {
                    uplink_freq_exists: true,
                    channel_freq_ok: true,
                }),
                bytes: vec![0x0a, 0x03],
            },
            MacTest {
                uplink: false,
                command: MACCommand::RekeyConf(RekeyConfPayload {
                    serv_lorawan_version: Version::LoRaWAN1_1,
                }),
                bytes: vec![0x0b, 0x01],
            },
            MacTest {
                uplink: true,
                command: MACCommand::RekeyInd(RekeyIndPayload {
                    dev_lorawan_version: Version::LoRaWAN1_1,
                }),
                bytes: vec![0x0b, 0x01],
            },
            MacTest {
                uplink: false,
                command: MACCommand::ADRParamSetupReq(ADRParamSetupReqPayload {
                    adr_param: ADRParam {
                        limit_exp: 10,
                        delay_exp: 15,
                    },
                }),
                bytes: vec![0x0c, 0xaf],
            },
            MacTest {
                uplink: true,
                command: MACCommand::ADRParamSetupAns,
                bytes: vec![0x0c],
            },
            MacTest {
                uplink: true,
                command: MACCommand::DeviceTimeReq,
                bytes: vec![0x0d],
            },
            MacTest {
                uplink: false,
                command: MACCommand::DeviceTimeAns(DeviceTimeAnsPayload {
                    time_since_gps_epoch: Duration::from_secs(1),
                }),
                bytes: vec![0x0d, 0x01, 0x00, 0x00, 0x00, 0x00],
            },
            MacTest {
                uplink: false,
                command: MACCommand::DeviceTimeAns(DeviceTimeAnsPayload {
                    time_since_gps_epoch: Duration::new(1, 2 * 3906250),
                }),
                bytes: vec![0x0d, 0x01, 0x00, 0x00, 0x00, 0x02],
            },
            MacTest {
                uplink: false,
                command: MACCommand::ForceRejoinReq(ForceRejoinReqPayload {
                    period: 3,
                    max_retries: 4,
                    rejoin_type: 2,
                    dr: 5,
                }),
                bytes: vec![0x0e, 0x25, 0x1c],
            },
            MacTest {
                uplink: false,
                command: MACCommand::RejoinParamSetupReq(RejoinParamSetupReqPayload {
                    max_time_n: 14,
                    max_count_n: 15,
                }),
                bytes: vec![0x0f, 0xef],
            },
            MacTest {
                uplink: true,
                command: MACCommand::RejoinParamSetupAns(RejoinParamSetupAnsPayload {
                    time_ok: true,
                }),
                bytes: vec![0x0f, 0x01],
            },
            MacTest {
                uplink: true,
                command: MACCommand::PingSlotInfoReq(PingSlotInfoReqPayload { periodicity: 3 }),
                bytes: vec![0x10, 0x03],
            },
            MacTest {
                uplink: false,
                command: MACCommand::PingSlotInfoAns,
                bytes: vec![0x10],
            },
            MacTest {
                uplink: false,
                command: MACCommand::PingSlotChannelReq(PingSlotChannelReqPayload {
                    freq: 868100000,
                    dr: 5,
                }),
                bytes: vec![0x11, 0x28, 0x76, 0x84, 0x05],
            },
            MacTest {
                uplink: true,
                command: MACCommand::PingSlotChannelAns(PingSlotChannelAnsPayload {
                    dr_ok: false,
                    channel_freq_ok: false,
                }),
                bytes: vec![0x011, 0x00],
            },
            MacTest {
                uplink: true,
                command: MACCommand::PingSlotChannelAns(PingSlotChannelAnsPayload {
                    dr_ok: false,
                    channel_freq_ok: true,
                }),
                bytes: vec![0x011, 0x01],
            },
            MacTest {
                uplink: true,
                command: MACCommand::PingSlotChannelAns(PingSlotChannelAnsPayload {
                    dr_ok: true,
                    channel_freq_ok: false,
                }),
                bytes: vec![0x011, 0x02],
            },
            MacTest {
                uplink: true,
                command: MACCommand::PingSlotChannelAns(PingSlotChannelAnsPayload {
                    dr_ok: true,
                    channel_freq_ok: true,
                }),
                bytes: vec![0x11, 0x03],
            },
            MacTest {
                uplink: false,
                command: MACCommand::BeaconFreqReq(BeaconFreqReqPayload { freq: 868100000 }),
                bytes: vec![0x13, 0x28, 0x76, 0x84],
            },
            MacTest {
                uplink: true,
                command: MACCommand::BeaconFreqAns(BeaconFreqAnsPayload {
                    beacon_freq_ok: false,
                }),
                bytes: vec![0x13, 0x00],
            },
            MacTest {
                uplink: true,
                command: MACCommand::BeaconFreqAns(BeaconFreqAnsPayload {
                    beacon_freq_ok: true,
                }),
                bytes: vec![0x13, 0x01],
            },
            MacTest {
                uplink: true,
                command: MACCommand::DeviceModeInd(DeviceModeIndPayload {
                    class: DeviceModeClass::ClassA,
                }),
                bytes: vec![0x20, 0x00],
            },
            MacTest {
                uplink: true,
                command: MACCommand::DeviceModeInd(DeviceModeIndPayload {
                    class: DeviceModeClass::ClassC,
                }),
                bytes: vec![0x20, 0x02],
            },
            MacTest {
                uplink: false,
                command: MACCommand::DeviceModeConf(DeviceModeConfPayload {
                    class: DeviceModeClass::ClassA,
                }),
                bytes: vec![0x20, 0x00],
            },
            MacTest {
                uplink: false,
                command: MACCommand::DeviceModeConf(DeviceModeConfPayload {
                    class: DeviceModeClass::ClassC,
                }),
                bytes: vec![0x20, 0x02],
            },
            MacTest {
                uplink: false,
                command: MACCommand::RelayConfReq(RelayConfReqPayload {
                    channel_settings_relay: ChannelSettingsRelay {
                        start_stop: 1,
                        cad_periodicity: 3,
                        default_ch_idx: 0,
                        second_ch_idx: 1,
                        second_ch_dr: 4,
                        second_ch_ack_offset: 5,
                    },
                    second_ch_freq: 868100000,
                }),
                bytes: vec![64, 165, 44, 40, 118, 132],
            },
            MacTest {
                uplink: true,
                command: MACCommand::RelayConfAns(RelayConfAnsPayload {
                    second_ch_freq_ack: true,
                    second_ch_ack_offset_ack: true,
                    second_ch_dr_ack: true,
                    second_ch_idx_ack: true,
                    default_ch_idx_ack: true,
                    cad_periodicity_ack: true,
                }),
                bytes: vec![64, 63],
            },
            MacTest {
                uplink: true,
                command: MACCommand::RelayConfAns(RelayConfAnsPayload {
                    second_ch_freq_ack: true,
                    second_ch_ack_offset_ack: false,
                    second_ch_dr_ack: true,
                    second_ch_idx_ack: false,
                    default_ch_idx_ack: true,
                    cad_periodicity_ack: false,
                }),
                bytes: vec![64, 21],
            },
            MacTest {
                uplink: false,
                command: MACCommand::EndDeviceConfReq(EndDeviceConfReqPayload {
                    second_ch_freq: 868100000,
                    channel_settings_ed: ChannelSettingsED {
                        second_ch_ack_offset: 5,
                        second_ch_dr: 4,
                        second_ch_idx: 1,
                        backoff: 63,
                    },
                    activation_relay_mode: ActivationRelayMode {
                        relay_mode_activation: RelayModeActivation::Dynamic,
                        smart_enable_level: 3,
                    },
                }),
                bytes: vec![65, 11, 165, 126, 40, 118, 132],
            },
            MacTest {
                uplink: true,
                command: MACCommand::EndDeviceConfAns(EndDeviceConfAnsPayload {
                    second_ch_freq_ack: true,
                    second_ch_dr_ack: false,
                    second_ch_idx_ack: true,
                    backoff_ack: true,
                }),
                bytes: vec![65, 13],
            },
            MacTest {
                uplink: false,
                command: MACCommand::FilterListReq(FilterListReqPayload {
                    filter_list_idx: 3,
                    filter_list_action: FilterListAction::Forward,
                    filter_list_eui: vec![1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 2],
                }),
                bytes: vec![66, 176, 1, 2, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1],
            },
            MacTest {
                uplink: true,
                command: MACCommand::FilterListAns(FilterListAnsPayload {
                    filter_list_action_ack: false,
                    filter_list_len_ack: true,
                    combined_rules_ack: false,
                }),
                bytes: vec![66, 2],
            },
            MacTest {
                uplink: false,
                command: MACCommand::UpdateUplinkListReq(UpdateUplinkListReqPayload {
                    uplink_list_idx: 3,
                    uplink_limit: UplinkLimitPL {
                        reload_rate: 60,
                        bucket_size: 2,
                    },
                    dev_addr: crate::DevAddr::from_be_bytes([1, 2, 3, 4]),
                    w_fcnt: 128,
                    root_wor_s_key: crate::AES128Key::from_bytes([
                        1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8,
                    ]),
                }),
                bytes: vec![
                    67, 3, 188, 4, 3, 2, 1, 128, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6,
                    7, 8,
                ],
            },
            MacTest {
                uplink: true,
                command: MACCommand::UpdateUplinkListAns,
                bytes: vec![67],
            },
            MacTest {
                uplink: false,
                command: MACCommand::CtrlUplinkListReq(CtrlUplinkListReqPayload {
                    ctrl_uplink_action: CtrlUplinkActionPL {
                        uplink_list_idx: 3,
                        ctrl_uplink_action: 1,
                    },
                }),
                bytes: vec![68, 19],
            },
            MacTest {
                uplink: true,
                command: MACCommand::CtrlUplinkListAns(CtrlUplinkListAnsPayload {
                    uplink_list_idx_ack: true,
                    w_fcnt: 128,
                }),
                bytes: vec![68, 1, 128, 0, 0, 0],
            },
            MacTest {
                uplink: false,
                command: MACCommand::ConfigureFwdLimitReq(ConfigureFwdLimitReqPayload {
                    reload_rate: FwdLimitReloadRatePL {
                        overall_reload_rate: 100,
                        global_uplink_reload_rate: 90,
                        notify_reload_rate: 80,
                        join_req_reload_rate: 70,
                        reset_limit_counter: ResetLimitCounter::NoChange,
                    },
                    load_capacity: FwdLimitLoadCapacityPL {
                        overall_limit_size: 2,
                        global_uplink_limit_size: 2,
                        notify_limit_size: 1,
                        join_req_limit_size: 3,
                    },
                }),
                bytes: vec![69, 100, 45, 212, 56, 218],
            },
            MacTest {
                uplink: true,
                command: MACCommand::ConfigureFwdLimitAns,
                bytes: vec![69],
            },
            MacTest {
                uplink: true,
                command: MACCommand::NotifyNewEndDeviceReq(NotifyNewEndDeviceReqPayload {
                    dev_addr: crate::DevAddr::from_be_bytes([1, 2, 3, 4]),
                    power_level: PowerLevel {
                        wor_snr: -10,
                        wor_rssi: -120,
                    },
                }),
                bytes: vec![70, 4, 3, 2, 1, 42, 13],
            },
        ];

        for tst in tests {
            assert_eq!(
                tst.bytes,
                MACCommandSet::new(vec![tst.command.clone()])
                    .to_vec()
                    .unwrap()
            );

            let mut command = MACCommandSet::from_slice(&tst.bytes);
            command.decode_from_raw(tst.uplink).unwrap();

            assert_eq!(MACCommandSet::new(vec![tst.command.clone()]), command);
        }
    }
}
