neurosky 0.0.1

Rust library and TUI for NeuroSky MindWave EEG headsets via the ThinkGear serial protocol
Documentation
//! SHA-256 integrity verification and ThinkGear packet validation.
//!
//! Provides:
//! - A pure-Rust SHA-256 implementation (no external crates)
//! - [`verify_packet`] — validates a raw ThinkGear packet's sync bytes and checksum
//! - [`make_packet`]   — builds a correctly framed ThinkGear packet from a payload
//!
//! The SHA-256 implementation can be used as a general-purpose data-integrity
//! utility; the packet helpers expose the same checksum logic that the
//! [`Parser`](crate::parser::Parser) uses internally.

use crate::error::NeuroSkyError;
use crate::types::SYNC_BYTE;

// ── SHA-256 ───────────────────────────────────────────────────────────────────

/// Compute the SHA-256 digest of `data`.
pub fn sha256(data: &[u8]) -> [u8; 32] {
    const K: [u32; 64] = [
        0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,
        0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,
        0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,
        0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,
        0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,
        0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,
        0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,
        0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2,
    ];
    let bit_len = (data.len() as u64) * 8;
    let mut msg = data.to_vec();
    msg.push(0x80);
    while (msg.len() % 64) != 56 { msg.push(0); }
    msg.extend_from_slice(&bit_len.to_be_bytes());
    let mut h: [u32; 8] = [
        0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,
        0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19,
    ];
    for chunk in msg.chunks(64) {
        let mut w = [0u32; 64];
        for i in 0..16 {
            w[i] = u32::from_be_bytes([chunk[4*i], chunk[4*i+1], chunk[4*i+2], chunk[4*i+3]]);
        }
        for i in 16..64 {
            let s0 = w[i-15].rotate_right(7) ^ w[i-15].rotate_right(18) ^ (w[i-15] >> 3);
            let s1 = w[i-2].rotate_right(17) ^ w[i-2].rotate_right(19)  ^ (w[i-2]  >> 10);
            w[i] = w[i-16].wrapping_add(s0).wrapping_add(w[i-7]).wrapping_add(s1);
        }
        let (mut a,mut b,mut c,mut d,mut e,mut f,mut g,mut hh) =
            (h[0],h[1],h[2],h[3],h[4],h[5],h[6],h[7]);
        for i in 0..64 {
            let s1  = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25);
            let ch  = (e & f) ^ ((!e) & g);
            let t1  = hh.wrapping_add(s1).wrapping_add(ch).wrapping_add(K[i]).wrapping_add(w[i]);
            let s0  = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22);
            let maj = (a & b) ^ (a & c) ^ (b & c);
            let t2  = s0.wrapping_add(maj);
            hh=g; g=f; f=e; e=d.wrapping_add(t1); d=c; c=b; b=a; a=t1.wrapping_add(t2);
        }
        for (i, v) in [a,b,c,d,e,f,g,hh].iter().enumerate() {
            h[i] = h[i].wrapping_add(*v);
        }
    }
    let mut out = [0u8; 32];
    for (i, &v) in h.iter().enumerate() { out[4*i..4*i+4].copy_from_slice(&v.to_be_bytes()); }
    out
}

/// Encode a byte slice as a lowercase hex string.
pub fn bytes_to_hex(bytes: &[u8]) -> String {
    bytes.iter().map(|b| format!("{:02x}", b)).collect()
}

// ── ThinkGear packet helpers ──────────────────────────────────────────────────

/// Validate a raw ThinkGear packet (sync bytes + checksum).
///
/// Returns `Ok(&payload)` on success, `Err` if the packet is malformed or the
/// checksum doesn't match.
///
/// Packet format: `[0xAA, 0xAA, PLENGTH, payload…, CHECKSUM]`
pub fn verify_packet(packet: &[u8]) -> Result<&[u8], NeuroSkyError> {
    if packet.len() < 4 {
        return Err(NeuroSkyError::InvalidPacket("Too short".into()));
    }
    if packet[0] != SYNC_BYTE || packet[1] != SYNC_BYTE {
        return Err(NeuroSkyError::InvalidPacket("Missing sync bytes".into()));
    }
    let plength = packet[2] as usize;
    if packet.len() < 3 + plength + 1 {
        return Err(NeuroSkyError::InvalidPacket("Truncated payload".into()));
    }
    let payload  = &packet[3..3 + plength];
    let computed = !payload.iter().fold(0u8, |a, &b| a.wrapping_add(b));
    let received = packet[3 + plength];
    if computed != received {
        return Err(NeuroSkyError::ChecksumMismatch);
    }
    Ok(payload)
}

/// Build a correctly framed ThinkGear packet from a payload slice.
pub fn make_packet(payload: &[u8]) -> Vec<u8> {
    let checksum = !payload.iter().fold(0u8, |a, &b| a.wrapping_add(b));
    let mut pkt  = vec![SYNC_BYTE, SYNC_BYTE, payload.len() as u8];
    pkt.extend_from_slice(payload);
    pkt.push(checksum);
    pkt
}

// ── Tests ─────────────────────────────────────────────────────────────────────

#[cfg(test)]
mod tests {
    use super::*;
    use crate::types::CODE_ATTENTION;

    #[test]
    fn test_sha256_abc() {
        assert_eq!(
            bytes_to_hex(&sha256(b"abc")),
            "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
        );
    }

    #[test]
    fn test_sha256_empty() {
        assert_eq!(
            bytes_to_hex(&sha256(b"")),
            "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
        );
    }

    #[test]
    fn test_verify_valid_packet() {
        let payload = [CODE_ATTENTION, 75];
        let pkt     = make_packet(&payload);
        let result  = verify_packet(&pkt);
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), &payload);
    }

    #[test]
    fn test_verify_bad_checksum() {
        let mut pkt = make_packet(&[CODE_ATTENTION, 75]);
        *pkt.last_mut().unwrap() ^= 0xFF;
        assert!(matches!(verify_packet(&pkt), Err(NeuroSkyError::ChecksumMismatch)));
    }

    #[test]
    fn test_verify_bad_sync() {
        let mut pkt = make_packet(&[CODE_ATTENTION, 75]);
        pkt[0] = 0x00;
        assert!(verify_packet(&pkt).is_err());
    }

    #[test]
    fn test_verify_too_short() {
        assert!(verify_packet(&[0xAA, 0xAA]).is_err());
    }

    #[test]
    fn test_make_packet_roundtrip() {
        let payload = [0x04, 80, 0x05, 50];
        let pkt     = make_packet(&payload);
        let result  = verify_packet(&pkt).unwrap();
        assert_eq!(result, &payload);
    }
}