use io::{Read, ProtoRead, Write, ProtoWrite, Error as IoError};

#[derive(Fail, Debug)]
pub enum Error<T> {
    #[fail(display = "unknown packet {:#02x}", _0)]
    UnknownPacket(u8),
    #[fail(display = "{}", _0)]
    Io(#[cause] IoError<T>)
}

impl<T> From<IoError<T>> for Error<T> {
    fn from(value: IoError<T>) -> Error<T> {
        Error::Io(value)
    }
}

// maximum size of arbitrary payloads
// used by satellite -> master analyzer, subkernel exceptions
pub const SAT_PAYLOAD_MAX_SIZE: usize  = /*max size*/512 - /*CRC*/4 - /*packet ID*/1 - /*last*/1 - /*length*/2;
// used by DDMA, subkernel program data (need to provide extra ID and destination)
pub const MASTER_PAYLOAD_MAX_SIZE: usize = SAT_PAYLOAD_MAX_SIZE - /*source*/1 - /*destination*/1 - /*ID*/4;

#[derive(PartialEq, Clone, Copy, Debug)]
#[repr(u8)]
pub enum PayloadStatus {
    Middle = 0,
    First = 1,
    Last = 2,
    FirstAndLast = 3,
}

impl From<u8> for PayloadStatus {
    fn from(value: u8) -> PayloadStatus {
        match value {
            0 => PayloadStatus::Middle,
            1 => PayloadStatus::First,
            2 => PayloadStatus::Last,
            3 => PayloadStatus::FirstAndLast,
            _ => unreachable!(),
        }
    }
}

impl PayloadStatus {
    pub fn is_first(self) -> bool {
        self == PayloadStatus::First || self == PayloadStatus::FirstAndLast
    }

    pub fn is_last(self) -> bool {
        self == PayloadStatus::Last || self == PayloadStatus::FirstAndLast
    }

    pub fn from_status(first: bool, last: bool) -> PayloadStatus {
        match (first, last) {
            (true, true) => PayloadStatus::FirstAndLast,
            (true, false) => PayloadStatus::First,
            (false, true) => PayloadStatus::Last,
            (false, false) => PayloadStatus::Middle
        }
    }
}

#[derive(PartialEq, Debug)]
pub enum Packet {
    EchoRequest,
    EchoReply,
    ResetRequest,
    ResetAck,
    TSCAck,

    DestinationStatusRequest { destination: u8 },
    DestinationDownReply,
    DestinationOkReply,
    DestinationSequenceErrorReply { channel: u16 },
    DestinationCollisionReply { channel: u16 },
    DestinationBusyReply { channel: u16 },

    RoutingSetPath { destination: u8, hops: [u8; 32] },
    RoutingSetRank { rank: u8 },
    RoutingRetrievePackets,
    RoutingNoPackets,
    RoutingAck,

    MonitorRequest { destination: u8, channel: u16, probe: u8 },
    MonitorReply { value: u64 },
    InjectionRequest { destination: u8, channel: u16, overrd: u8, value: u8 },
    InjectionStatusRequest { destination: u8, channel: u16, overrd: u8 },
    InjectionStatusReply { value: u8 },

    I2cStartRequest { destination: u8, busno: u8 },
    I2cRestartRequest { destination: u8, busno: u8 },
    I2cStopRequest { destination: u8, busno: u8 },
    I2cWriteRequest { destination: u8, busno: u8, data: u8 },
    I2cWriteReply { succeeded: bool, ack: bool },
    I2cReadRequest { destination: u8, busno: u8, ack: bool },
    I2cReadReply { succeeded: bool, data: u8 },
    I2cBasicReply { succeeded: bool },
    I2cSwitchSelectRequest { destination: u8, busno: u8, address: u8, mask: u8 },

    SpiSetConfigRequest { destination: u8, busno: u8, flags: u8, length: u8, div: u8, cs: u8 },
    SpiWriteRequest { destination: u8, busno: u8, data: u32 },
    SpiReadRequest { destination: u8, busno: u8 },
    SpiReadReply { succeeded: bool, data: u32 },
    SpiBasicReply { succeeded: bool },

    AnalyzerHeaderRequest { destination: u8 },
    AnalyzerHeader { sent_bytes: u32, total_byte_count: u64, overflow_occurred: bool },
    AnalyzerDataRequest { destination: u8 },
    AnalyzerData { last: bool, length: u16, data: [u8; SAT_PAYLOAD_MAX_SIZE]},

    DmaAddTraceRequest { 
        source: u8, destination: u8, 
        id: u32, status: PayloadStatus, 
        length: u16, trace: [u8; MASTER_PAYLOAD_MAX_SIZE] 
    },
    DmaAddTraceReply { source: u8, destination: u8, id: u32, succeeded: bool },
    DmaRemoveTraceRequest { source: u8, destination: u8, id: u32 },
    DmaRemoveTraceReply { destination: u8, succeeded: bool },
    DmaPlaybackRequest { source: u8, destination: u8, id: u32, timestamp: u64 },
    DmaPlaybackReply { destination: u8, succeeded: bool },
    DmaPlaybackStatus { source: u8, destination: u8, id: u32, error: u8, channel: u32, timestamp: u64 },

    SubkernelAddDataRequest { destination: u8, id: u32, status: PayloadStatus, length: u16, data: [u8; MASTER_PAYLOAD_MAX_SIZE] },
    SubkernelAddDataReply { succeeded: bool },
    SubkernelLoadRunRequest { source: u8, destination: u8, id: u32, run: bool },
    SubkernelLoadRunReply { destination: u8, succeeded: bool },
    SubkernelFinished { destination: u8, id: u32, with_exception: bool, exception_src: u8 },
    SubkernelExceptionRequest { destination: u8 },
    SubkernelException { last: bool, length: u16, data: [u8; SAT_PAYLOAD_MAX_SIZE] },
    SubkernelMessage { source: u8, destination: u8, id: u32, status: PayloadStatus, length: u16, data: [u8; MASTER_PAYLOAD_MAX_SIZE] },
    SubkernelMessageAck { destination: u8 },
}

impl Packet {
    pub fn read_from<R>(reader: &mut R) -> Result<Self, Error<R::ReadError>>
        where R: Read + ?Sized
    {
        Ok(match reader.read_u8()? {
            0x00 => Packet::EchoRequest,
            0x01 => Packet::EchoReply,
            0x02 => Packet::ResetRequest,
            0x03 => Packet::ResetAck,
            0x04 => Packet::TSCAck,

            0x20 => Packet::DestinationStatusRequest {
                destination: reader.read_u8()?
            },
            0x21 => Packet::DestinationDownReply,
            0x22 => Packet::DestinationOkReply,
            0x23 => Packet::DestinationSequenceErrorReply {
                channel: reader.read_u16()?
            },
            0x24 => Packet::DestinationCollisionReply {
                channel: reader.read_u16()?
            },
            0x25 => Packet::DestinationBusyReply {
                channel: reader.read_u16()?
            },

            0x30 => {
                let destination = reader.read_u8()?;
                let mut hops = [0; 32];
                reader.read_exact(&mut hops)?;
                Packet::RoutingSetPath {
                    destination: destination,
                    hops: hops
                }
            },
            0x31 => Packet::RoutingSetRank {
                rank: reader.read_u8()?
            },
            0x32 => Packet::RoutingAck,
            0x33 => Packet::RoutingRetrievePackets,
            0x34 => Packet::RoutingNoPackets,

            0x40 => Packet::MonitorRequest {
                destination: reader.read_u8()?,
                channel: reader.read_u16()?,
                probe: reader.read_u8()?
            },
            0x41 => Packet::MonitorReply {
                value: reader.read_u64()?
            },
            0x50 => Packet::InjectionRequest {
                destination: reader.read_u8()?,
                channel: reader.read_u16()?,
                overrd: reader.read_u8()?,
                value: reader.read_u8()?
            },
            0x51 => Packet::InjectionStatusRequest {
                destination: reader.read_u8()?,
                channel: reader.read_u16()?,
                overrd: reader.read_u8()?
            },
            0x52 => Packet::InjectionStatusReply {
                value: reader.read_u8()?
            },

            0x80 => Packet::I2cStartRequest {
                destination: reader.read_u8()?,
                busno: reader.read_u8()?
            },
            0x81 => Packet::I2cRestartRequest {
                destination: reader.read_u8()?,
                busno: reader.read_u8()?
            },
            0x82 => Packet::I2cStopRequest {
                destination: reader.read_u8()?,
                busno: reader.read_u8()?
            },
            0x83 => Packet::I2cWriteRequest {
                destination: reader.read_u8()?,
                busno: reader.read_u8()?,
                data: reader.read_u8()?
            },
            0x84 => Packet::I2cWriteReply {
                succeeded: reader.read_bool()?,
                ack: reader.read_bool()?
            },
            0x85 => Packet::I2cReadRequest {
                destination: reader.read_u8()?,
                busno: reader.read_u8()?,
                ack: reader.read_bool()?
            },
            0x86 => Packet::I2cReadReply {
                succeeded: reader.read_bool()?,
                data: reader.read_u8()?
            },
            0x87 => Packet::I2cBasicReply {
                succeeded: reader.read_bool()?
            },
            0x88 => Packet::I2cSwitchSelectRequest {
                destination: reader.read_u8()?,
                busno: reader.read_u8()?,
                address: reader.read_u8()?,
                mask: reader.read_u8()?,
            },

            0x90 => Packet::SpiSetConfigRequest {
                destination: reader.read_u8()?,
                busno: reader.read_u8()?,
                flags: reader.read_u8()?,
                length: reader.read_u8()?,
                div: reader.read_u8()?,
                cs: reader.read_u8()?
            },
            /* 0x91: was Packet::SpiSetXferRequest */
            0x92 => Packet::SpiWriteRequest {
                destination: reader.read_u8()?,
                busno: reader.read_u8()?,
                data: reader.read_u32()?
            },
            0x93 => Packet::SpiReadRequest {
                destination: reader.read_u8()?,
                busno: reader.read_u8()?
            },
            0x94 => Packet::SpiReadReply {
                succeeded: reader.read_bool()?,
                data: reader.read_u32()?
            },
            0x95 => Packet::SpiBasicReply {
                succeeded: reader.read_bool()?
            },

            0xa0 => Packet::AnalyzerHeaderRequest {
                destination: reader.read_u8()?
            },
            0xa1 => Packet::AnalyzerHeader {
                sent_bytes: reader.read_u32()?, 
                total_byte_count: reader.read_u64()?, 
                overflow_occurred: reader.read_bool()?,
            },
            0xa2 => Packet::AnalyzerDataRequest {
                destination: reader.read_u8()?
            },
            0xa3 => {
                let last = reader.read_bool()?;
                let length = reader.read_u16()?;
                let mut data: [u8; SAT_PAYLOAD_MAX_SIZE] = [0; SAT_PAYLOAD_MAX_SIZE];
                reader.read_exact(&mut data[0..length as usize])?;
                Packet::AnalyzerData {
                    last: last,
                    length: length,
                    data: data
                }
            },

            0xb0 => {
                let source = reader.read_u8()?;
                let destination = reader.read_u8()?;
                let id = reader.read_u32()?;
                let status = reader.read_u8()?;
                let length = reader.read_u16()?;
                let mut trace: [u8; MASTER_PAYLOAD_MAX_SIZE] = [0; MASTER_PAYLOAD_MAX_SIZE];
                reader.read_exact(&mut trace[0..length as usize])?;
                Packet::DmaAddTraceRequest {
                    source: source,
                    destination: destination,
                    id: id,
                    status: PayloadStatus::from(status),
                    length: length as u16,
                    trace: trace,
                }
            },
            0xb1 => Packet::DmaAddTraceReply {
                source: reader.read_u8()?,
                destination: reader.read_u8()?,
                id: reader.read_u32()?,
                succeeded: reader.read_bool()?
            },
            0xb2 => Packet::DmaRemoveTraceRequest {
                source: reader.read_u8()?,
                destination: reader.read_u8()?,
                id: reader.read_u32()?
            },
            0xb3 => Packet::DmaRemoveTraceReply {
                destination: reader.read_u8()?,
                succeeded: reader.read_bool()?
            },
            0xb4 => Packet::DmaPlaybackRequest {
                source: reader.read_u8()?,
                destination: reader.read_u8()?,
                id: reader.read_u32()?,
                timestamp: reader.read_u64()?
            },
            0xb5 => Packet::DmaPlaybackReply {
                destination: reader.read_u8()?,
                succeeded: reader.read_bool()?
            },
            0xb6 => Packet::DmaPlaybackStatus {
                source: reader.read_u8()?,
                destination: reader.read_u8()?,
                id: reader.read_u32()?,
                error: reader.read_u8()?,
                channel: reader.read_u32()?,
                timestamp: reader.read_u64()?
            },

            0xc0 => { 
                let destination = reader.read_u8()?;
                let id = reader.read_u32()?;
                let status = reader.read_u8()?;
                let length = reader.read_u16()?;
                let mut data: [u8; MASTER_PAYLOAD_MAX_SIZE] = [0; MASTER_PAYLOAD_MAX_SIZE];
                reader.read_exact(&mut data[0..length as usize])?;
                Packet::SubkernelAddDataRequest {
                    destination: destination,
                    id: id,
                    status: PayloadStatus::from(status),
                    length: length as u16,
                    data: data,
                }
            },
            0xc1 => Packet::SubkernelAddDataReply {
                succeeded: reader.read_bool()?
            },
            0xc4 => Packet::SubkernelLoadRunRequest {
                source: reader.read_u8()?,
                destination: reader.read_u8()?,
                id: reader.read_u32()?,
                run: reader.read_bool()?
            },
            0xc5 => Packet::SubkernelLoadRunReply {
                destination: reader.read_u8()?,
                succeeded: reader.read_bool()?
            },
            0xc8 => Packet::SubkernelFinished {
                destination: reader.read_u8()?,
                id: reader.read_u32()?,
                with_exception: reader.read_bool()?,
                exception_src: reader.read_u8()?
            },
            0xc9 => Packet::SubkernelExceptionRequest {
                destination: reader.read_u8()?
            },
            0xca => {
                let last = reader.read_bool()?;
                let length = reader.read_u16()?;
                let mut data: [u8; SAT_PAYLOAD_MAX_SIZE] = [0; SAT_PAYLOAD_MAX_SIZE];
                reader.read_exact(&mut data[0..length as usize])?;
                Packet::SubkernelException {
                    last: last,
                    length: length,
                    data: data
                }
            },
            0xcb => {
                let source = reader.read_u8()?;
                let destination = reader.read_u8()?;
                let id = reader.read_u32()?;
                let status = reader.read_u8()?;
                let length = reader.read_u16()?;
                let mut data: [u8; MASTER_PAYLOAD_MAX_SIZE] = [0; MASTER_PAYLOAD_MAX_SIZE];
                reader.read_exact(&mut data[0..length as usize])?;
                Packet::SubkernelMessage {
                    source: source,
                    destination: destination,
                    id: id,
                    status: PayloadStatus::from(status),
                    length: length as u16,
                    data: data,
                }
            },
            0xcc => Packet::SubkernelMessageAck {
                destination: reader.read_u8()?
            },

            ty => return Err(Error::UnknownPacket(ty))
        })
    }

    pub fn write_to<W>(&self, writer: &mut W) -> Result<(), IoError<W::WriteError>>
        where W: Write + ?Sized
    {
        match *self {
            Packet::EchoRequest =>
                writer.write_u8(0x00)?,
            Packet::EchoReply =>
                writer.write_u8(0x01)?,
            Packet::ResetRequest =>
                writer.write_u8(0x02)?,
            Packet::ResetAck =>
                writer.write_u8(0x03)?,
            Packet::TSCAck =>
                writer.write_u8(0x04)?,

            Packet::DestinationStatusRequest { destination } => {
                writer.write_u8(0x20)?;
                writer.write_u8(destination)?;
            },
            Packet::DestinationDownReply =>
                writer.write_u8(0x21)?,
            Packet::DestinationOkReply =>
                writer.write_u8(0x22)?,
            Packet::DestinationSequenceErrorReply { channel } => {
                writer.write_u8(0x23)?;
                writer.write_u16(channel)?;
            },
            Packet::DestinationCollisionReply { channel } => {
                writer.write_u8(0x24)?;
                writer.write_u16(channel)?;
            },
            Packet::DestinationBusyReply { channel } => {
                writer.write_u8(0x25)?;
                writer.write_u16(channel)?;
            },

            Packet::RoutingSetPath { destination, hops } => {
                writer.write_u8(0x30)?;
                writer.write_u8(destination)?;
                writer.write_all(&hops)?;
            },
            Packet::RoutingSetRank { rank } => {
                writer.write_u8(0x31)?;
                writer.write_u8(rank)?;
            },
            Packet::RoutingAck =>
                writer.write_u8(0x32)?,
            Packet::RoutingRetrievePackets =>
                writer.write_u8(0x33)?,
            Packet::RoutingNoPackets =>
                writer.write_u8(0x34)?,

            Packet::MonitorRequest { destination, channel, probe } => {
                writer.write_u8(0x40)?;
                writer.write_u8(destination)?;
                writer.write_u16(channel)?;
                writer.write_u8(probe)?;
            },
            Packet::MonitorReply { value } => {
                writer.write_u8(0x41)?;
                writer.write_u64(value)?;
            },
            Packet::InjectionRequest { destination, channel, overrd, value } => {
                writer.write_u8(0x50)?;
                writer.write_u8(destination)?;
                writer.write_u16(channel)?;
                writer.write_u8(overrd)?;
                writer.write_u8(value)?;
            },
            Packet::InjectionStatusRequest { destination, channel, overrd } => {
                writer.write_u8(0x51)?;
                writer.write_u8(destination)?;
                writer.write_u16(channel)?;
                writer.write_u8(overrd)?;
            },
            Packet::InjectionStatusReply { value } => {
                writer.write_u8(0x52)?;
                writer.write_u8(value)?;
            },

            Packet::I2cStartRequest { destination, busno } => {
                writer.write_u8(0x80)?;
                writer.write_u8(destination)?;
                writer.write_u8(busno)?;
            },
            Packet::I2cRestartRequest { destination, busno } => {
                writer.write_u8(0x81)?;
                writer.write_u8(destination)?;
                writer.write_u8(busno)?;
            },
            Packet::I2cStopRequest { destination, busno } => {
                writer.write_u8(0x82)?;
                writer.write_u8(destination)?;
                writer.write_u8(busno)?;
            },
            Packet::I2cWriteRequest { destination, busno, data } => {
                writer.write_u8(0x83)?;
                writer.write_u8(destination)?;
                writer.write_u8(busno)?;
                writer.write_u8(data)?;
            },
            Packet::I2cWriteReply { succeeded, ack } => {
                writer.write_u8(0x84)?;
                writer.write_bool(succeeded)?;
                writer.write_bool(ack)?;
            },
            Packet::I2cReadRequest { destination, busno, ack } => {
                writer.write_u8(0x85)?;
                writer.write_u8(destination)?;
                writer.write_u8(busno)?;
                writer.write_bool(ack)?;
            },
            Packet::I2cReadReply { succeeded, data } => {
                writer.write_u8(0x86)?;
                writer.write_bool(succeeded)?;
                writer.write_u8(data)?;
            },
            Packet::I2cBasicReply { succeeded } => {
                writer.write_u8(0x87)?;
                writer.write_bool(succeeded)?;
            },
            Packet::I2cSwitchSelectRequest { destination, busno, address, mask } => {
                writer.write_u8(0x88)?;
                writer.write_u8(destination)?;
                writer.write_u8(busno)?;
                writer.write_u8(address)?;
                writer.write_u8(mask)?;
            },

            Packet::SpiSetConfigRequest { destination, busno, flags, length, div, cs } => {
                writer.write_u8(0x90)?;
                writer.write_u8(destination)?;
                writer.write_u8(busno)?;
                writer.write_u8(flags)?;
                writer.write_u8(length)?;
                writer.write_u8(div)?;
                writer.write_u8(cs)?;
            },
            Packet::SpiWriteRequest { destination, busno, data } => {
                writer.write_u8(0x92)?;
                writer.write_u8(destination)?;
                writer.write_u8(busno)?;
                writer.write_u32(data)?;
            },
            Packet::SpiReadRequest { destination, busno } => {
                writer.write_u8(0x93)?;
                writer.write_u8(destination)?;
                writer.write_u8(busno)?;
            },
            Packet::SpiReadReply { succeeded, data } => {
                writer.write_u8(0x94)?;
                writer.write_bool(succeeded)?;
                writer.write_u32(data)?;
            },
            Packet::SpiBasicReply { succeeded } => {
                writer.write_u8(0x95)?;
                writer.write_bool(succeeded)?;
            },

            Packet::AnalyzerHeaderRequest { destination } => {
                writer.write_u8(0xa0)?;
                writer.write_u8(destination)?;
            },
            Packet::AnalyzerHeader { sent_bytes, total_byte_count, overflow_occurred } => { 
                writer.write_u8(0xa1)?;
                writer.write_u32(sent_bytes)?;
                writer.write_u64(total_byte_count)?;
                writer.write_bool(overflow_occurred)?;
            },
            Packet::AnalyzerDataRequest { destination } => {
                writer.write_u8(0xa2)?;
                writer.write_u8(destination)?;
            },
            Packet::AnalyzerData { last, length, data } => {
                writer.write_u8(0xa3)?;
                writer.write_bool(last)?;
                writer.write_u16(length)?;
                writer.write_all(&data[0..length as usize])?;
            },

            Packet::DmaAddTraceRequest { source, destination, id, status, trace, length } => {
                writer.write_u8(0xb0)?;
                writer.write_u8(source)?;
                writer.write_u8(destination)?;
                writer.write_u32(id)?;
                writer.write_u8(status as u8)?;
                // trace may be broken down to fit within drtio aux memory limit
                // will be reconstructed by satellite
                writer.write_u16(length)?;
                writer.write_all(&trace[0..length as usize])?;
            },
            Packet::DmaAddTraceReply { source, destination, id, succeeded } => {
                writer.write_u8(0xb1)?;
                writer.write_u8(source)?;
                writer.write_u8(destination)?;
                writer.write_u32(id)?;
                writer.write_bool(succeeded)?;
            },
            Packet::DmaRemoveTraceRequest { source, destination, id } => {
                writer.write_u8(0xb2)?;
                writer.write_u8(source)?;
                writer.write_u8(destination)?;
                writer.write_u32(id)?;
            },
            Packet::DmaRemoveTraceReply { destination, succeeded } => {
                writer.write_u8(0xb3)?;
                writer.write_u8(destination)?;
                writer.write_bool(succeeded)?;
            },
            Packet::DmaPlaybackRequest { source, destination, id, timestamp } => {
                writer.write_u8(0xb4)?;
                writer.write_u8(source)?;
                writer.write_u8(destination)?;
                writer.write_u32(id)?;
                writer.write_u64(timestamp)?;
            },
            Packet::DmaPlaybackReply { destination, succeeded } => {
                writer.write_u8(0xb5)?;
                writer.write_u8(destination)?;
                writer.write_bool(succeeded)?;
            },
            Packet::DmaPlaybackStatus { source, destination, id, error, channel, timestamp } => {
                writer.write_u8(0xb6)?;
                writer.write_u8(source)?;
                writer.write_u8(destination)?;
                writer.write_u32(id)?;
                writer.write_u8(error)?;
                writer.write_u32(channel)?;
                writer.write_u64(timestamp)?;
            },

            Packet::SubkernelAddDataRequest { destination, id, status, data, length } => {
                writer.write_u8(0xc0)?;
                writer.write_u8(destination)?;
                writer.write_u32(id)?;
                writer.write_u8(status as u8)?;
                writer.write_u16(length)?;
                writer.write_all(&data[0..length as usize])?;
            },
            Packet::SubkernelAddDataReply { succeeded } => {
                writer.write_u8(0xc1)?;
                writer.write_bool(succeeded)?;
            },
            Packet::SubkernelLoadRunRequest { source, destination, id, run } => {
                writer.write_u8(0xc4)?;
                writer.write_u8(source)?;
                writer.write_u8(destination)?;
                writer.write_u32(id)?;
                writer.write_bool(run)?;
            },
            Packet::SubkernelLoadRunReply { destination, succeeded } => {
                writer.write_u8(0xc5)?;
                writer.write_u8(destination)?;
                writer.write_bool(succeeded)?;
            },
            Packet::SubkernelFinished { destination, id, with_exception, exception_src } => {
                writer.write_u8(0xc8)?;
                writer.write_u8(destination)?;
                writer.write_u32(id)?;
                writer.write_bool(with_exception)?;
                writer.write_u8(exception_src)?;
            },
            Packet::SubkernelExceptionRequest { destination } => {
                writer.write_u8(0xc9)?;
                writer.write_u8(destination)?;
            },
            Packet::SubkernelException { last, length, data } => {
                writer.write_u8(0xca)?;
                writer.write_bool(last)?;
                writer.write_u16(length)?;
                writer.write_all(&data[0..length as usize])?;
            },
            Packet::SubkernelMessage { source, destination, id, status, data, length } => {
                writer.write_u8(0xcb)?;
                writer.write_u8(source)?;
                writer.write_u8(destination)?;
                writer.write_u32(id)?;
                writer.write_u8(status as u8)?;
                writer.write_u16(length)?;
                writer.write_all(&data[0..length as usize])?;
            },
            Packet::SubkernelMessageAck { destination } => {
                writer.write_u8(0xcc)?;
                writer.write_u8(destination)?;
            },
        }
        Ok(())
    }

    pub fn routable_destination(&self) -> Option<u8> {
        // only for packets that could be re-routed, not only forwarded
        match self {
            Packet::DmaAddTraceRequest      { destination, .. } => Some(*destination),
            Packet::DmaAddTraceReply        { destination, .. } => Some(*destination),
            Packet::DmaRemoveTraceRequest   { destination, .. } => Some(*destination),
            Packet::DmaRemoveTraceReply     { destination, .. } => Some(*destination),
            Packet::DmaPlaybackRequest      { destination, .. } => Some(*destination),
            Packet::DmaPlaybackReply        { destination, .. } => Some(*destination),
            Packet::SubkernelLoadRunRequest { destination, .. } => Some(*destination),
            Packet::SubkernelLoadRunReply   { destination, .. } => Some(*destination),
            Packet::SubkernelMessage        { destination, .. } => Some(*destination),
            Packet::SubkernelMessageAck     { destination, .. } => Some(*destination),
            Packet::DmaPlaybackStatus       { destination, .. } => Some(*destination),
            Packet::SubkernelFinished       { destination, .. } => Some(*destination),
            _ => None
        }
    }

    pub fn expects_response(&self) -> bool {
        // returns true if the routable packet should elicit a response
        // e.g. reply, ACK packets end a conversation,
        // and firmware should not wait for response
        match self {
            Packet::DmaAddTraceReply { .. } | Packet::DmaRemoveTraceReply { .. } |
                Packet::DmaPlaybackReply { .. } | Packet::SubkernelLoadRunReply { .. } |
                Packet::SubkernelMessageAck { .. } | Packet::DmaPlaybackStatus { .. } |
                Packet::SubkernelFinished { .. } => false,
            _ => true
        }
    }
}
