km003c-lib 0.4.0

Protocol parser and async USB library for the ChargerLAB POWER-Z KM003C
Documentation
//! Tests for packet serialization functionality

mod common;

use common::*;
use km003c_lib::{AdcQueueData, GraphSampleRate};

#[test]
fn test_rawpacket_to_bytes_ctrl() {
    // Test direct conversion of Ctrl packet to bytes
    let raw_packet = RawPacket::Ctrl {
        header: CtrlHeader::new()
            .with_packet_type(2)
            .with_reserved_flag(false)
            .with_id(1)
            .with_attribute(0),
        payload: Bytes::new().to_vec(),
    };

    let bytes = Bytes::from(raw_packet);
    let expected_bytes = [0x02, 0x01, 0x00, 0x00];

    assert_eq!(
        bytes.as_ref(),
        &expected_bytes,
        "Ctrl packet should convert to [02, 01, 00, 00], got {:02x?}",
        bytes.as_ref()
    );
}

#[test]
fn test_pd_monitor_commands_use_documented_wire_parameters() {
    let enable = Bytes::from(Packet::EnablePdMonitor.to_raw_packet(3).unwrap());
    let disable = Bytes::from(Packet::DisablePdMonitor.to_raw_packet(4).unwrap());

    assert_eq!(enable.as_ref(), &[0x10, 0x03, 0x02, 0x00]);
    assert_eq!(disable.as_ref(), &[0x11, 0x04, 0x00, 0x00]);
}

#[test]
fn auth_requests_use_their_special_wire_headers() {
    let memory = Bytes::from(
        Packet::MemoryRead {
            address: 0x420,
            size: 64,
        }
        .to_raw_packet(2)
        .unwrap(),
    );
    let hardware_id = km003c_lib::auth::HardwareId::from_bytes([
        0x30, 0x37, 0x31, 0x4b, 0x42, 0x50, 0x0d, 0xff, 0x11, 0x0a, 0xff, 0xff,
    ]);
    let streaming_auth = Bytes::from(
        Packet::StreamingAuth {
            credential: (&hardware_id).into(),
        }
        .to_raw_packet(6)
        .unwrap(),
    );

    assert_eq!(&memory[..4], &[0x44, 0x02, 0x01, 0x01]);
    assert_eq!(&streaming_auth[..4], &[0x4c, 0x06, 0x00, 0x02]);
}

#[test]
fn pd_monitor_commands_round_trip_through_the_parser() {
    // Regression: these two commands serialized but parsed back as Generic,
    // so captures of the library's own traffic were left unclassified.
    for (command, bytes) in [
        (Packet::EnablePdMonitor, [0x10, 0x03, 0x02, 0x00]),
        (Packet::DisablePdMonitor, [0x11, 0x04, 0x00, 0x00]),
    ] {
        let parsed = Packet::try_from(RawPacket::try_from(Bytes::from(bytes.to_vec())).unwrap()).unwrap();
        assert_eq!(parsed, command, "{bytes:02x?} must parse back into {command:?}");
    }
}

#[test]
fn start_graph_round_trips_every_documented_rate() {
    for rate in [
        GraphSampleRate::Sps2,
        GraphSampleRate::Sps10,
        GraphSampleRate::Sps50,
        GraphSampleRate::Sps1000,
    ] {
        let bytes = Bytes::from(Packet::StartGraph { rate }.to_raw_packet(7).unwrap());
        let parsed = Packet::try_from(RawPacket::try_from(bytes).unwrap()).unwrap();

        assert_eq!(parsed, Packet::StartGraph { rate });
    }
}

#[test]
fn start_graph_with_an_unknown_rate_index_stays_generic() {
    // Byte 2 holds the rate index shifted left by one unused bit, so 0x40
    // decodes to index 32, which is not one of the four documented rates.
    let parsed = Packet::try_from(RawPacket::try_from(Bytes::from(vec![0x0e, 0x07, 0x40, 0x00])).unwrap()).unwrap();

    assert!(
        matches!(parsed, Packet::Generic(_)),
        "an unknown rate index must be preserved verbatim, got {parsed:?}"
    );
}

#[test]
fn test_unsupported_semantic_payload_is_not_silently_dropped() {
    let packet = Packet::DataResponse {
        payloads: vec![PayloadData::AdcQueue(AdcQueueData {
            rate: GraphSampleRate::Sps50,
            samples: Vec::new(),
        })],
    };

    assert!(matches!(
        packet.to_raw_packet(0),
        Err(KMError::UnsupportedSerialization { packet: "AdcQueue" })
    ));
}

#[test]
fn semantic_adc_response_uses_recorded_header_encoding() {
    let recorded = Bytes::from(REAL_ADC_RESPONSE.to_vec());
    let packet = Packet::try_from(RawPacket::try_from(recorded.clone()).unwrap()).unwrap();

    let serialized = Bytes::from(packet.to_raw_packet(0).unwrap());

    assert_eq!(&serialized[..8], &recorded[..8]);
    assert_eq!(
        serialized, recorded,
        "semantic ADC parsing must preserve every wire field"
    );
    assert_eq!(serialized[0], 0x41, "PutData does not set the reserved bit");
    assert_eq!(&serialized[2..4], &[0x80, 0x02], "recorded ADC obj_count is 10 words");
}

#[test]
fn unknown_adc_sample_rate_is_preserved_without_a_fallback() {
    let mut recorded = REAL_ADC_RESPONSE.to_vec();
    recorded[8 + 36] = 0xfe;

    let packet = Packet::try_from(RawPacket::try_from(Bytes::from(recorded.clone())).unwrap()).unwrap();
    let adc = packet.get_adc().unwrap();
    assert_eq!(adc.sample_rate, None);
    assert_eq!(adc.sample_rate_raw, 0xfe);

    let serialized = Bytes::from(packet.to_raw_packet(0).unwrap());
    assert_eq!(serialized.as_ref(), recorded);
}

#[test]
fn test_rawpacket_to_bytes_simple_data() {
    // Test direct conversion of SimpleData packet to bytes
    let payload = Bytes::from_static(&[0xAA, 0xBB, 0xCC, 0xDD]).to_vec();
    let raw_packet = RawPacket::SimpleData {
        header: DataHeader::new()
            .with_packet_type(64)
            .with_reserved_flag(false)
            .with_id(1)
            .with_obj_count_words(4),
        payload,
    };

    let bytes = Bytes::from(raw_packet);
    let expected_bytes = [0x40, 0x01, 0x00, 0x01, 0xAA, 0xBB, 0xCC, 0xDD];

    assert_eq!(
        bytes.as_ref(),
        &expected_bytes,
        "SimpleData packet should convert to [40, 01, 00, 01, AA, BB, CC, DD], got {:02x?}",
        bytes.as_ref()
    );
}

#[test]
fn test_rawpacket_to_bytes_data_with_logical_packet() {
    // Test direct conversion of Data packet with logical packet to bytes
    let payload = Bytes::from_static(&[0xAA, 0xBB, 0xCC, 0xDD]).to_vec();

    let logical_packet = LogicalPacket {
        attribute: Attribute::Adc,
        next: false,
        chunk: 0,
        size: 4,
        payload,
    };

    let raw_packet = RawPacket::Data {
        header: DataHeader::new()
            .with_packet_type(65)
            .with_reserved_flag(true)
            .with_id(12)
            .with_obj_count_words(2), // (4 ext header + 4 payload) / 4 = 2
        logical_packets: vec![logical_packet],
    };

    let bytes = Bytes::from(raw_packet);

    // Verify total length (4 byte header + 4 byte ext header + 4 byte payload = 12 bytes)
    assert_eq!(bytes.len(), 12, "Data packet should be 12 bytes total");

    // Parse back and verify structure and fields
    let parsed = RawPacket::try_from(bytes.clone()).expect("Failed to parse generated bytes");
    match parsed {
        RawPacket::Data {
            header,
            logical_packets,
        } => {
            assert_eq!(header.packet_type(), 65);
            assert_eq!(logical_packets.len(), 1);
            let lp = &logical_packets[0];
            assert!(!lp.next);
            assert_eq!(lp.attribute, Attribute::Adc);
            assert_eq!(lp.size, 4);
            assert_eq!(&lp.payload, &[0xAA, 0xBB, 0xCC, 0xDD]);
        }
        _ => panic!("Expected Data after parsing serialized bytes"),
    }
}

#[test]
fn test_rawpacket_to_bytes_ctrl_with_payload() {
    // Test Ctrl packet with non-empty payload
    let payload = Bytes::from_static(&[0x12, 0x34]).to_vec();
    let raw_packet = RawPacket::Ctrl {
        header: CtrlHeader::new()
            .with_packet_type(5)
            .with_reserved_flag(true)
            .with_id(7)
            .with_attribute(0x123),
        payload,
    };

    let bytes = Bytes::from(raw_packet.clone());

    // Verify the structure by doing a round-trip conversion
    let original_bytes = bytes.clone();
    let parsed_packet = RawPacket::try_from(original_bytes).expect("Failed to parse generated bytes");

    // Verify the parsed packet matches our original
    match parsed_packet {
        RawPacket::Ctrl {
            header,
            payload: parsed_payload,
        } => {
            assert_eq!(header.packet_type(), 5, "Packet type should be 5");
            assert!(header.reserved_flag(), "Reserved flag should be true");
            assert_eq!(header.id(), 7, "ID should be 7");
            assert_eq!(header.attribute(), 0x123, "Attribute should be 0x123");
            assert_eq!(&parsed_payload, &[0x12, 0x34], "Payload should match");
        }
        _ => panic!("Expected Ctrl packet"),
    }

    // Verify total length (4 byte header + 2 byte payload = 6 bytes)
    assert_eq!(bytes.len(), 6, "Total packet should be 6 bytes");

    // Verify payload is correctly appended
    assert_eq!(&bytes[4..], &[0x12, 0x34], "Payload should be correctly appended");
}

#[test]
fn test_rawpacket_to_bytes_empty_payload() {
    // Test edge case with empty payloads for both packet types
    let ctrl_packet = RawPacket::Ctrl {
        header: CtrlHeader::new()
            .with_packet_type(12)
            .with_reserved_flag(false)
            .with_id(0)
            .with_attribute(1),
        payload: Bytes::new().to_vec(),
    };

    let data_packet = RawPacket::SimpleData {
        header: DataHeader::new()
            .with_packet_type(64)
            .with_reserved_flag(false)
            .with_id(0)
            .with_obj_count_words(0),
        payload: Bytes::new().to_vec(),
    };

    let ctrl_bytes = Bytes::from(ctrl_packet);
    let data_bytes = Bytes::from(data_packet);

    assert_eq!(ctrl_bytes.len(), 4, "Ctrl packet with empty payload should be 4 bytes");
    assert_eq!(data_bytes.len(), 4, "Data packet with empty payload should be 4 bytes");
}

#[test]
fn test_rawpacket_to_bytes_large_payload() {
    // Test with a larger payload to ensure the trait handles size correctly
    let large_payload: Vec<u8> = (0..=255u8).collect();
    let raw_packet = RawPacket::SimpleData {
        header: DataHeader::new()
            .with_packet_type(65)
            .with_reserved_flag(true)
            .with_id(42)
            .with_obj_count_words(64), // 256 bytes = 64 words (assuming 4 bytes per word)
        payload: Bytes::from(large_payload.clone()).to_vec(),
    };

    let bytes = Bytes::from(raw_packet);

    // Verify total length (4 byte header + 256 byte payload = 260 bytes)
    assert_eq!(bytes.len(), 260, "Large payload packet should be 260 bytes total");

    // Verify payload is correctly appended
    assert_eq!(
        &bytes[4..],
        &large_payload,
        "Payload should be correctly appended after header"
    );
}