Skip to main content

colorlight/
lib.rs

1//! Colorlight receiver-card layer-2 protocol: raw Ethernet frames whose
2//! 2-byte "EtherType" is a packet type. Frame builders and reply parsers only;
3//! sequencing lives in the callers. Byte layouts: `docs/pixel-protocol.md`.
4
5pub mod discovery;
6pub mod eeprom;
7pub mod flash;
8pub mod params;
9pub mod pixel;
10pub mod upgrade;
11
12pub use discovery::*;
13pub use flash::*;
14pub use pixel::*;
15
16pub const CARD_MAC: [u8; 6] = [0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
17pub const SENDER_MAC: [u8; 6] = [0x22, 0x22, 0x33, 0x44, 0x55, 0x66];
18
19/// Receiver index addressing every card on the link. The vendor uses it even
20/// for one card, and a card with a corrupt cabinet record answers only to it.
21pub const BROADCAST: u16 = 0xffff;
22
23/// Two MACs and the type.
24pub const HEADER_LEN: usize = 14;
25
26pub(crate) fn write_header(f: &mut [u8], ethertype: [u8; 2]) {
27    f[..6].copy_from_slice(&CARD_MAC);
28    f[6..12].copy_from_slice(&SENDER_MAC);
29    f[12..14].copy_from_slice(&ethertype);
30}
31
32/// A frame with a zeroed `payload_len`-byte payload that `fill` writes in place.
33#[must_use]
34pub fn frame_with(ethertype: [u8; 2], payload_len: usize, fill: impl FnOnce(&mut [u8])) -> Vec<u8> {
35    let mut f = vec![0u8; HEADER_LEN + payload_len];
36    write_header(&mut f, ethertype);
37    fill(&mut f[HEADER_LEN..]);
38    f
39}
40
41#[must_use]
42pub fn frame(ethertype: [u8; 2], payload: &[u8]) -> Vec<u8> {
43    frame_with(ethertype, payload.len(), |p| p.copy_from_slice(payload))
44}
45
46/// The header shared by the 0x0600/0x1900/0x2300 command payloads: `[1..3]`
47/// receiver index BE, `[3]` opcode.
48pub(crate) fn indexed(p: &mut [u8], rcv_index: u16, opcode: u8) {
49    p[1..3].copy_from_slice(&rcv_index.to_be_bytes());
50    p[3] = opcode;
51}
52
53/// A type-0x0600 command with no data; `flags` land at payload offset 8.
54pub(crate) fn command(rcv_index: u16, opcode: u8, flags: &[u8]) -> Vec<u8> {
55    frame_with([0x06, 0x00], 126, |p| {
56        indexed(p, rcv_index, opcode);
57        p[8..8 + flags.len()].copy_from_slice(flags);
58    })
59}
60
61/// One vendor's wire protocol as frame builders and reply parsers, the
62/// surface `ops` drives a card through.
63///
64/// [`Colorlight`] is the only implementation; a second vendor implements
65/// this in its own crate (docs/cards.md). Sequencing and sockets stay with
66/// the caller.
67pub trait Protocol {
68    /// The discovery request.
69    fn discover(&self) -> Vec<u8>;
70    /// The card a discovery reply describes, if `frame` is one.
71    fn discovery_reply(&self, frame: &[u8]) -> Option<DiscoveryInfo>;
72    /// One row packet into `buf`: screen row `row`, pixels from `x`.
73    fn row(&self, buf: &mut Vec<u8>, row: u16, x: u16, rgb: &[[u8; 3]], order: ColorOrder);
74    /// The latch frame that applies the rows sent since the last one.
75    fn latch(&self, brightness: u8) -> Vec<u8>;
76    /// The brightness frame sent before the rows.
77    fn brightness(&self, brightness: u8) -> Vec<u8>;
78    /// Read one chunk of flash at a 256-byte page index.
79    fn flash_read(&self, index: u16, page: u16) -> Vec<u8>;
80    /// The flash bytes a reply carries, if `frame` is a flash reply.
81    fn flash_reply<'a>(&self, frame: &'a [u8]) -> Option<&'a [u8]>;
82    /// Write one page of the parameter block; refused outside `map`.
83    ///
84    /// # Errors
85    /// Refuses a block outside the map or a payload that is not one page.
86    fn flash_write(&self, map: &FlashMap, index: u16, block: u8, page: u8, data: &[u8]) -> Result<Vec<u8>, WriteError>;
87    /// Write one EEPROM record at its own address and length.
88    fn eeprom_write(&self, addr: u16, data: &[u8]) -> Vec<u8>;
89}
90
91/// The Colorlight receiving-card protocol: the free functions of this crate
92/// behind [`Protocol`].
93#[derive(Debug, Clone, Copy, Default)]
94pub struct Colorlight;
95
96impl Protocol for Colorlight {
97    fn discover(&self) -> Vec<u8> {
98        discovery()
99    }
100
101    fn discovery_reply(&self, frame: &[u8]) -> Option<DiscoveryInfo> {
102        parse_discovery_response(frame)
103    }
104
105    fn row(&self, buf: &mut Vec<u8>, row: u16, x: u16, rgb: &[[u8; 3]], order: ColorOrder) {
106        pixel_row_into(buf, row, x, rgb, order);
107    }
108
109    fn latch(&self, brightness: u8) -> Vec<u8> {
110        sync(brightness).to_vec()
111    }
112
113    fn brightness(&self, brightness: u8) -> Vec<u8> {
114        pixel::brightness(brightness).to_vec()
115    }
116
117    fn flash_read(&self, index: u16, page: u16) -> Vec<u8> {
118        read_flash(index, page)
119    }
120
121    fn flash_reply<'a>(&self, frame: &'a [u8]) -> Option<&'a [u8]> {
122        flash_reply_data(frame)
123    }
124
125    fn flash_write(&self, map: &FlashMap, index: u16, block: u8, page: u8, data: &[u8]) -> Result<Vec<u8>, WriteError> {
126        map.write_page(index, block, page, data)
127    }
128
129    fn eeprom_write(&self, addr: u16, data: &[u8]) -> Vec<u8> {
130        eeprom::write(addr, data)
131    }
132}
133
134#[cfg(test)]
135mod protocol_tests {
136    use super::*;
137
138    #[test]
139    fn the_trait_builds_the_same_frames_as_the_functions() {
140        let p = Colorlight;
141        assert_eq!(p.discover(), discovery());
142        assert_eq!(p.latch(40), sync(40).to_vec());
143        assert_eq!(p.brightness(40), pixel::brightness(40).to_vec());
144        assert_eq!(p.flash_read(0, FLASH_PAGE_BASIC_PARAM), read_flash(0, FLASH_PAGE_BASIC_PARAM));
145        assert_eq!(p.eeprom_write(0x02, &[0; 42]), eeprom::write(0x02, &[0; 42]));
146        let page = [0u8; FLASH_PAGE_BYTES];
147        assert_eq!(p.flash_write(&E120, 0, PARAM_BLOCK, 0x80, &page), E120.write_page(0, PARAM_BLOCK, 0x80, &page));
148        assert_eq!(p.flash_write(&E120, 0, 0x00, 0, &page), Err(WriteError::ForbiddenBlock(0)));
149        let mut a = Vec::new();
150        p.row(&mut a, 3, 0, &[[1, 2, 3]; 4], ColorOrder::Bgr);
151        assert_eq!(a, pixel_row(3, 0, &[[1, 2, 3]; 4], ColorOrder::Bgr));
152        assert!(p.discovery_reply(&[0; 20]).is_none());
153        assert!(p.flash_reply(&[0; 20]).is_none());
154    }
155}