//! HDLC framing for the Reticulum TCP transport (`TCPInterface`).
//!
//! A frame is `FLAG + byte-stuffed(payload) + FLAG`. Stuffing escapes any FLAG
//! or ESC byte in the payload as `ESC, (byte ^ ESC_MASK)`. Byte-exact with the
//! RNS reference (validated against `RNS.Interfaces.TCPInterface.HDLC.escape`).

/// Frame delimiter.
pub const FLAG: u8 = 0x7E;
/// Escape byte.
pub const ESC: u8 = 0x7D;
/// XOR mask applied to escaped bytes.
pub const ESC_MASK: u8 = 0x20;

/// HDLC-frame `payload` into `FLAG + escaped(payload) + FLAG`.
#[must_use]
pub fn frame(payload: &[u8]) -> Vec<u8> {
    let mut out = Vec::with_capacity(payload.len() + 2);
    out.push(FLAG);
    for &b in payload {
        if b == FLAG || b == ESC {
            out.push(ESC);
            out.push(b ^ ESC_MASK);
        } else {
            out.push(b);
        }
    }
    out.push(FLAG);
    out
}

/// Streaming deframer for an HDLC byte stream.
///
/// TCP delivers an arbitrary byte stream, so this reassembles complete frames
/// across reads and unescapes them. Adjacent frames may share a single FLAG
/// byte, and empty inter-frame runs are ignored.
#[derive(Debug, Default)]
pub struct Deframer {
    buf: Vec<u8>,
    in_frame: bool,
    escaping: bool,
}

impl Deframer {
    /// Create an empty deframer.
    #[must_use]
    pub fn new() -> Self {
        Self::default()
    }

    /// Feed received bytes; append any newly-completed frames to `out`.
    pub fn push(&mut self, data: &[u8], out: &mut Vec<Vec<u8>>) {
        for &b in data {
            if b == FLAG {
                if self.in_frame && !self.buf.is_empty() {
                    out.push(std::mem::take(&mut self.buf));
                }
                self.in_frame = true;
                self.escaping = false;
                self.buf.clear();
                continue;
            }
            if !self.in_frame {
                continue; // stray bytes before the first FLAG
            }
            if self.escaping {
                self.buf.push(b ^ ESC_MASK);
                self.escaping = false;
            } else if b == ESC {
                self.escaping = true;
            } else {
                self.buf.push(b);
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::{frame, Deframer};
    use hex_literal::hex;

    // The captured RNS announce packet and its RNS-framed form (HDLC.escape).
    // Note the dest hash begins with 0x7D, which exercises escaping (→ 7D 5D).
    const PKT: [u8; 172] = hex!("01007dc45220f1867c50ead139c7e4083324008f40c5adb68f25624ae5b214ea767a6ec94d829d3d7b5e1ad1ba6f3e2138285f29acbae141bccaf0b22e1a94d34d0bc7361e526d0bfe12c89794bc9322966dd7038b878cc925d841c2c5807c57e2ff006a13360ba271fe3b5e15eab7525c18201eed47ea5f6dbf4197ae27f5169c0210c58001b5e33a27f3b2493405e52565225ca5f32a39df2e3e95a7ddf0df2e72fbb23fb00868656c6c6f");
    const FRAMED: [u8; 175] = hex!("7e01007d5dc45220f1867c50ead139c7e4083324008f40c5adb68f25624ae5b214ea767a6ec94d829d3d7b5e1ad1ba6f3e2138285f29acbae141bccaf0b22e1a94d34d0bc7361e526d0bfe12c89794bc9322966dd7038b878cc925d841c2c5807c57e2ff006a13360ba271fe3b5e15eab7525c18201eed47ea5f6dbf4197ae27f5169c0210c58001b5e33a27f3b2493405e52565225ca5f32a39df2e3e95a7ddf0df2e72fbb23fb00868656c6c6f7e");

    #[test]
    fn frame_matches_rns_reference() {
        assert_eq!(frame(&PKT), FRAMED);
    }

    #[test]
    fn deframe_reference_frame() {
        let mut d = Deframer::new();
        let mut out = Vec::new();
        d.push(&FRAMED, &mut out);
        assert_eq!(out.len(), 1);
        assert_eq!(out[0], PKT);
    }

    #[test]
    fn round_trips_bytes_needing_escape() {
        let data = [0x00, FLAG_TEST, ESC_TEST, 0xFF, FLAG_TEST, ESC_TEST];
        let mut d = Deframer::new();
        let mut out = Vec::new();
        d.push(&frame(&data), &mut out);
        assert_eq!(out, vec![data.to_vec()]);
    }
    const FLAG_TEST: u8 = 0x7E;
    const ESC_TEST: u8 = 0x7D;

    #[test]
    fn reassembles_across_split_reads() {
        let f = frame(b"hello");
        let mut d = Deframer::new();
        let mut out = Vec::new();
        d.push(&f[..3], &mut out);
        assert!(out.is_empty());
        d.push(&f[3..], &mut out);
        assert_eq!(out, vec![b"hello".to_vec()]);
    }

    #[test]
    fn handles_back_to_back_frames_sharing_a_flag() {
        let mut stream = frame(b"aa");
        stream.extend(frame(b"bb"));
        let mut d = Deframer::new();
        let mut out = Vec::new();
        d.push(&stream, &mut out);
        assert_eq!(out, vec![b"aa".to_vec(), b"bb".to_vec()]);
    }
}
