use crate::GpioError;
pub const RESERVED_FROM: u8 = 0x78;
pub const RESERVED_BELOW: u8 = 0x08;
const MAX_SEVEN_BIT: u16 = 0x7F;
const MAX_TEN_BIT: u16 = 0x3FF;
const TEN_BIT_PREFIX: u8 = 0xF0;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Direction {
Write,
Read,
}
impl Direction {
pub fn rw_bit(self) -> u8 {
match self {
Direction::Write => 0,
Direction::Read => 1,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Address {
value: u16,
ten_bit: bool,
}
impl Address {
pub fn seven_bit(address: u8) -> Result<Address, GpioError> {
if address as u16 > MAX_SEVEN_BIT {
return Err(GpioError::AddressOutOfRange);
}
Ok(Address {
value: address as u16,
ten_bit: false,
})
}
pub fn ten_bit(address: u16) -> Result<Address, GpioError> {
if address > MAX_TEN_BIT {
return Err(GpioError::AddressOutOfRange);
}
Ok(Address {
value: address,
ten_bit: true,
})
}
pub fn value(self) -> u16 {
self.value
}
pub fn is_ten_bit(self) -> bool {
self.ten_bit
}
pub fn frame_len(self) -> usize {
if self.ten_bit {
2
} else {
1
}
}
pub fn is_reserved(self) -> bool {
!self.ten_bit
&& (self.value < u16::from(RESERVED_BELOW) || self.value >= u16::from(RESERVED_FROM))
}
pub fn is_general_call(self) -> bool {
!self.ten_bit && self.value == 0x00
}
pub fn frame(self, direction: Direction) -> AddressFrame {
let mut bytes = [0u8; 2];
let len = self.write_frame(direction, &mut bytes).unwrap_or_default();
AddressFrame { bytes, len }
}
pub fn write_frame(self, direction: Direction, out: &mut [u8]) -> Result<usize, GpioError> {
let rw = direction.rw_bit();
if self.ten_bit {
if out.len() < 2 {
return Err(GpioError::BufferTooSmall);
}
let high = ((self.value >> 8) as u8) & 0x03;
out[0] = TEN_BIT_PREFIX | (high << 1) | rw;
out[1] = (self.value & 0xFF) as u8;
Ok(2)
} else {
if out.is_empty() {
return Err(GpioError::BufferTooSmall);
}
out[0] = ((self.value as u8) << 1) | rw;
Ok(1)
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct AddressFrame {
bytes: [u8; 2],
len: usize,
}
impl AddressFrame {
pub fn as_bytes(&self) -> &[u8] {
&self.bytes[..self.len]
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_frame(address: Address, direction: Direction, expected: &[u8]) {
let mut buf = [0u8; 2];
let n = address.write_frame(direction, &mut buf).unwrap();
assert_eq!(&buf[..n], expected);
}
#[test]
fn seven_bit_frames_match_known_device_bytes() {
assert_frame(Address::seven_bit(0x68).unwrap(), Direction::Write, &[0xD0]);
assert_frame(Address::seven_bit(0x68).unwrap(), Direction::Read, &[0xD1]);
assert_frame(Address::seven_bit(0x3C).unwrap(), Direction::Write, &[0x78]);
assert_frame(Address::seven_bit(0x3C).unwrap(), Direction::Read, &[0x79]);
assert_frame(Address::seven_bit(0x50).unwrap(), Direction::Write, &[0xA0]);
assert_frame(Address::seven_bit(0x50).unwrap(), Direction::Read, &[0xA1]);
}
#[test]
fn ten_bit_frame_matches_spec_worked_example() {
let addr = Address::ten_bit(0x2A5).unwrap();
assert_frame(addr, Direction::Write, &[0xF4, 0xA5]);
assert_frame(addr, Direction::Read, &[0xF5, 0xA5]);
}
#[test]
fn ten_bit_frame_at_the_range_bounds() {
assert_frame(
Address::ten_bit(0x000).unwrap(),
Direction::Write,
&[0xF0, 0x00],
);
assert_frame(
Address::ten_bit(0x3FF).unwrap(),
Direction::Write,
&[0xF6, 0xFF],
);
assert_frame(
Address::ten_bit(0x3FF).unwrap(),
Direction::Read,
&[0xF7, 0xFF],
);
}
#[test]
fn out_of_range_addresses_are_rejected() {
assert_eq!(Address::seven_bit(0x80), Err(GpioError::AddressOutOfRange));
assert_eq!(Address::ten_bit(0x400), Err(GpioError::AddressOutOfRange));
assert!(Address::seven_bit(0x7F).is_ok());
assert!(Address::ten_bit(0x3FF).is_ok());
}
#[test]
fn reserved_ranges_match_the_spec() {
for addr in (0x00..=0x07).chain(0x78..=0x7F) {
assert!(
Address::seven_bit(addr).unwrap().is_reserved(),
"{addr:#04x}"
);
}
for addr in 0x08..=0x77 {
assert!(
!Address::seven_bit(addr).unwrap().is_reserved(),
"{addr:#04x}"
);
}
assert!(!Address::ten_bit(0x002).unwrap().is_reserved());
}
#[test]
fn general_call_is_address_zero() {
assert!(Address::seven_bit(0x00).unwrap().is_general_call());
assert!(!Address::seven_bit(0x01).unwrap().is_general_call());
assert!(!Address::ten_bit(0x000).unwrap().is_general_call());
}
#[test]
fn frame_len_and_too_small_buffers() {
assert_eq!(Address::seven_bit(0x40).unwrap().frame_len(), 1);
assert_eq!(Address::ten_bit(0x100).unwrap().frame_len(), 2);
let mut empty = [];
assert_eq!(
Address::seven_bit(0x40)
.unwrap()
.write_frame(Direction::Write, &mut empty),
Err(GpioError::BufferTooSmall)
);
let mut one = [0u8; 1];
assert_eq!(
Address::ten_bit(0x100)
.unwrap()
.write_frame(Direction::Write, &mut one),
Err(GpioError::BufferTooSmall)
);
}
}