#![cfg_attr(not(test), no_std)]
mod access;
pub use access::{BitMut, DWordMut, LWordMut, WordMut};
#[cfg(feature = "allow_unaligned_tags")]
#[doc(hidden)]
#[macro_export]
macro_rules! alignment_assert {
($align:literal, $addr:expr) => {};
}
#[cfg(not(feature = "allow_unaligned_tags"))]
#[doc(hidden)]
#[macro_export]
macro_rules! alignment_assert {
(2, $addr:expr) => {
assert!($addr % 2 == 0, "Word address must be divisible by 2");
};
(4, $addr:expr) => {
assert!($addr % 4 == 0, "Double word address must be divisible by 4");
};
(8, $addr:expr) => {
assert!($addr % 8 == 0, "Long word address must be divisible by 8");
};
}
#[macro_export]
macro_rules! tag {
($buf:expr, X, $addr1:expr, $addr2:expr) => {{
let buffer: &[u8] = $buf;
buffer[$addr1] & (1 << $addr2) != 0
}};
($buf:expr, B, $addr:expr) => {{
let buffer: &[u8] = $buf;
buffer[$addr]
}};
($buf:expr, W, $addr:expr) => {{
let buffer: &[u8] = $buf;
$crate::alignment_assert!(2, $addr);
u16::from_be_bytes(buffer[$addr..$addr + 2].try_into().unwrap())
}};
($buf:expr, D, $addr:expr) => {{
let buffer: &[u8] = $buf;
$crate::alignment_assert!(4, $addr);
u32::from_be_bytes(buffer[$addr..$addr + 4].try_into().unwrap())
}};
($buf:expr, L, $addr:expr) => {{
let buffer: &[u8] = $buf;
$crate::alignment_assert!(8, $addr);
u64::from_be_bytes(buffer[$addr..$addr + 8].try_into().unwrap())
}};
($buf:expr, $addr1:expr, $addr2:expr) => {{
let buffer: &[u8] = $buf;
buffer[$addr1] & (1 << $addr2) != 0
}};
}
#[macro_export]
macro_rules! tag_mut {
($buf:expr, X, $addr1:expr, $addr2:expr) => {{
let buffer: &mut [u8] = $buf;
$crate::BitMut::new(&mut buffer[$addr1], $addr2)
}};
($buf:expr, B, $addr:expr) => {{
let buffer: &mut [u8] = $buf;
&mut buffer[$addr]
}};
($buf:expr, W, $addr:expr) => {{
let buffer: &mut [u8] = $buf;
$crate::alignment_assert!(2, $addr);
$crate::WordMut::new((&mut buffer[$addr..$addr + 2]).try_into().unwrap())
}};
($buf:expr, D, $addr:expr) => {{
let buffer: &mut [u8] = $buf;
$crate::alignment_assert!(4, $addr);
$crate::DWordMut::new((&mut buffer[$addr..$addr + 4]).try_into().unwrap())
}};
($buf:expr, L, $addr:expr) => {{
let buffer: &mut [u8] = $buf;
$crate::alignment_assert!(8, $addr);
$crate::LWordMut::new((&mut buffer[$addr..$addr + 8]).try_into().unwrap())
}};
($buf:expr, $addr1:expr, $addr2:expr) => {{
let buffer: &mut [u8] = $buf;
$crate::BitMut::new(&mut buffer[$addr1], $addr2)
}};
}
#[doc(hidden)]
#[macro_export]
macro_rules! tag_method {
($vis:vis, $name:ident, mut, X, $addr1:literal, $addr2:literal) => {
#[inline(always)]
$vis fn $name(&mut self) -> $crate::BitMut<'_> {
$crate::BitMut::new(&mut self.buf[$addr1], $addr2)
}
};
($vis:vis, $name:ident, mut, B, $addr:literal) => {
#[inline(always)]
$vis fn $name(&mut self) -> &mut u8 {
&mut self.buf[$addr]
}
};
($vis:vis, $name:ident, mut, W, $addr:literal) => {
#[inline(always)]
$vis fn $name(&mut self) -> $crate::WordMut<'_> {
$crate::alignment_assert!(2, $addr);
$crate::WordMut::new((&mut self.buf[$addr..$addr + 2]).try_into().unwrap())
}
};
($vis:vis, $name:ident, mut, D, $addr:literal) => {
#[inline(always)]
$vis fn $name(&mut self) -> $crate::DWordMut<'_> {
$crate::alignment_assert!(4, $addr);
$crate::DWordMut::new((&mut self.buf[$addr..$addr + 4]).try_into().unwrap())
}
};
($vis:vis, $name:ident, mut, L, $addr:literal) => {
#[inline(always)]
$vis fn $name(&mut self) -> $crate::LWordMut<'_> {
$crate::alignment_assert!(8, $addr);
$crate::LWordMut::new((&mut self.buf[$addr..$addr + 8]).try_into().unwrap())
}
};
($vis:vis, $name:ident, mut, $addr1:literal, $addr2:literal) => {
#[inline(always)]
$vis fn $name(&mut self) -> $crate::BitMut<'_> {
$crate::BitMut::new(&mut self.buf[$addr1], $addr2)
}
};
($vis:vis, $name:ident, const, X, $addr1:literal, $addr2:literal) => {
#[inline(always)]
$vis fn $name(&self) -> bool {
self.buf[$addr1] & (1 << $addr2) != 0
}
};
($vis:vis, $name:ident, const, B, $addr:literal) => {
#[inline(always)]
$vis fn $name(&self) -> u8 {
self.buf[$addr]
}
};
($vis:vis, $name:ident, const, W, $addr:literal) => {
#[inline(always)]
$vis fn $name(&self) -> u16 {
$crate::alignment_assert!(2, $addr);
u16::from_be_bytes(self.buf[$addr..$addr + 2].try_into().unwrap())
}
};
($vis:vis, $name:ident, const, D, $addr:literal) => {
#[inline(always)]
$vis fn $name(&self) -> u32 {
$crate::alignment_assert!(4, $addr);
u32::from_be_bytes(self.buf[$addr..$addr + 4].try_into().unwrap())
}
};
($vis:vis, $name:ident, const, L, $addr:literal) => {
#[inline(always)]
$vis fn $name(&self) -> u64 {
$crate::alignment_assert!(8, $addr);
u64::from_be_bytes(self.buf[$addr..$addr + 8].try_into().unwrap())
}
};
($vis:vis, $name:ident, const, $addr1:literal, $addr2:literal) => {
#[inline(always)]
$vis fn $name(&self) -> bool {
self.buf[$addr1] & (1 << $addr2) != 0
}
};
}
#[macro_export]
macro_rules! process_image {
(
$( #[$meta:meta] )*
$vis:vis struct $ProcessImage:ident, mut $ProcessImageMut:ident: $SIZE:literal {
$(
$( #[$field_meta:meta] )*
$field_vis:vis $field_name:ident: ($($tag:tt)+)
),*
$(,)?
}
) => {
$( #[$meta] )*
$vis struct $ProcessImage<'a> {
buf: &'a [u8; $SIZE],
}
impl<'a> $ProcessImage<'a> {
$(
$( #[$field_meta] )*
$crate::tag_method!($vis, $field_name, const, $($tag)+);
)*
}
impl<'a> ::core::convert::From<&'a [u8; $SIZE]> for $ProcessImage<'a> {
#[inline(always)]
fn from(buf: &'a [u8; $SIZE]) -> Self {
Self { buf }
}
}
impl<'a> ::core::convert::TryFrom<&'a [u8]> for $ProcessImage<'a> {
type Error = ::core::array::TryFromSliceError;
#[inline(always)]
fn try_from(buf: &'a [u8]) -> Result<Self, Self::Error> {
buf.try_into().map(|buf| Self { buf })
}
}
impl<'a> ::core::convert::AsRef<[u8]> for $ProcessImage<'a> {
#[inline(always)]
fn as_ref(&self) -> &[u8] {
&self.buf[..]
}
}
impl<'a> ::core::fmt::Debug for $ProcessImage<'a> {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
f.debug_struct(::core::stringify!($ProcessImage))
$(
.field(::core::stringify!($field_name), &self.$field_name())
)*
.finish()
}
}
$( #[$meta] )*
$vis struct $ProcessImageMut<'a> {
buf: &'a mut [u8; $SIZE],
}
impl<'a> ::core::convert::From<&'a mut [u8; $SIZE]> for $ProcessImageMut<'a> {
#[inline(always)]
fn from(buf: &'a mut [u8; $SIZE]) -> Self {
Self { buf }
}
}
impl<'a> ::core::convert::TryFrom<&'a mut [u8]> for $ProcessImageMut<'a> {
type Error = ::core::array::TryFromSliceError;
#[inline(always)]
fn try_from(buf: &'a mut [u8]) -> Result<Self, Self::Error> {
buf.try_into().map(|buf| Self { buf })
}
}
impl<'a> ::core::convert::AsRef<[u8]> for $ProcessImageMut<'a> {
#[inline(always)]
fn as_ref(&self) -> &[u8] {
&self.buf[..]
}
}
impl<'a> ::core::convert::AsMut<[u8]> for $ProcessImageMut<'a> {
#[inline(always)]
fn as_mut(&mut self) -> &mut [u8] {
&mut self.buf[..]
}
}
impl<'a> ::core::fmt::Debug for $ProcessImageMut<'a> {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
let pi = $ProcessImage::from(&*self.buf);
f.debug_struct(::core::stringify!($ProcessImageMut))
$(
.field(::core::stringify!($field_name), &pi.$field_name())
)*
.finish()
}
}
impl<'a> $ProcessImageMut<'a> {
$(
$( #[$field_meta] )*
$crate::tag_method!($vis, $field_name, mut, $($tag)+);
)*
}
};
(
$( #[$meta:meta] )*
$vis:vis struct mut $ProcessImageMut:ident: $SIZE:literal {
$(
$( #[$field_meta:meta] )*
$field_vis:vis $field_name:ident: ($($tag:tt)+)
),*
$(,)?
}
) => {
$( #[$meta] )*
$vis struct $ProcessImageMut<'a> {
buf: &'a mut [u8; $SIZE],
}
impl<'a> ::core::convert::From<&'a mut [u8; $SIZE]> for $ProcessImageMut<'a> {
#[inline(always)]
fn from(buf: &'a mut [u8; $SIZE]) -> Self {
Self { buf }
}
}
impl<'a> ::core::convert::TryFrom<&'a mut [u8]> for $ProcessImageMut<'a> {
type Error = ::core::array::TryFromSliceError;
#[inline(always)]
fn try_from(buf: &'a mut [u8]) -> Result<Self, Self::Error> {
buf.try_into().map(|buf| Self { buf })
}
}
impl<'a> ::core::convert::AsRef<[u8]> for $ProcessImageMut<'a> {
#[inline(always)]
fn as_ref(&self) -> &[u8] {
&self.buf[..]
}
}
impl<'a> ::core::convert::AsMut<[u8]> for $ProcessImageMut<'a> {
#[inline(always)]
fn as_mut(&mut self) -> &mut [u8] {
&mut self.buf[..]
}
}
impl<'a> $ProcessImageMut<'a> {
$(
$( #[$field_meta] )*
$crate::tag_method!($vis, $field_name, mut, $($tag)+);
)*
}
};
(
$( #[$meta:meta] )*
$vis:vis struct $ProcessImage:ident: $SIZE:literal {
$(
$( #[$field_meta:meta] )*
$field_vis:vis $field_name:ident: ($($tag:tt)+)
),*
$(,)?
}
) => {
$( #[$meta] )*
$vis struct $ProcessImage<'a> {
buf: &'a [u8; $SIZE],
}
impl<'a> $ProcessImage<'a> {
$(
$( #[$field_meta] )*
$crate::tag_method!($vis, $field_name, const, $($tag)+);
)*
}
impl<'a> ::core::convert::From<&'a [u8; $SIZE]> for $ProcessImage<'a> {
#[inline(always)]
fn from(buf: &'a [u8; $SIZE]) -> Self {
Self { buf }
}
}
impl<'a> ::core::convert::TryFrom<&'a [u8]> for $ProcessImage<'a> {
type Error = ::core::array::TryFromSliceError;
#[inline(always)]
fn try_from(buf: &'a [u8]) -> Result<Self, Self::Error> {
buf.try_into().map(|buf| Self { buf })
}
}
impl<'a> ::core::convert::AsRef<[u8]> for $ProcessImage<'a> {
#[inline(always)]
fn as_ref(&self) -> &[u8] {
&self.buf[..]
}
}
impl<'a> ::core::fmt::Debug for $ProcessImage<'a> {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
f.debug_struct(::core::stringify!($ProcessImage))
$(
.field(::core::stringify!($field_name), &self.$field_name())
)*
.finish()
}
}
};
}
#[macro_export]
macro_rules! process_image_owned {
(
$( #[$meta:meta] )*
$vis:vis struct $ProcessImage:ident, mut $ProcessImageMut:ident: $SIZE:literal {
$(
$( #[$field_meta:meta] )*
$field_vis:vis $field_name:ident: ($($tag:tt)+)
),*
$(,)?
}
) => {
$( #[$meta] )*
$vis struct $ProcessImage {
buf: [u8; $SIZE],
}
impl $ProcessImage {
#[allow(dead_code)]
#[inline(always)]
pub fn new_zeroed() -> Self {
Self {
buf: [0u8; $SIZE],
}
}
#[allow(dead_code)]
#[inline(always)]
pub fn as_mut(&mut self) -> $ProcessImageMut {
$ProcessImageMut::from(&mut self.buf)
}
#[allow(dead_code)]
#[inline(always)]
pub fn as_slice(&self) -> &[u8] {
&self.buf[..]
}
#[allow(dead_code)]
#[inline(always)]
pub fn as_slice_mut(&mut self) -> &mut [u8] {
&mut self.buf[..]
}
$(
$( #[$field_meta] )*
$crate::tag_method!($vis, $field_name, const, $($tag)+);
)*
}
impl ::core::convert::From<&[u8; $SIZE]> for $ProcessImage {
#[inline(always)]
fn from(buf_in: &[u8; $SIZE]) -> Self {
let mut buf = [0u8; $SIZE];
buf.copy_from_slice(buf_in);
Self { buf }
}
}
impl ::core::convert::TryFrom<&[u8]> for $ProcessImage {
type Error = ::core::array::TryFromSliceError;
#[inline(always)]
fn try_from(buf: &[u8]) -> Result<Self, Self::Error> {
buf.try_into().map(|buf: &[u8; $SIZE]| Self { buf: buf.clone() })
}
}
impl ::core::convert::AsRef<[u8]> for $ProcessImage {
#[inline(always)]
fn as_ref(&self) -> &[u8] {
&self.buf[..]
}
}
impl ::core::convert::AsMut<[u8]> for $ProcessImage {
#[inline(always)]
fn as_mut(&mut self) -> &mut [u8] {
&mut self.buf[..]
}
}
impl ::core::fmt::Debug for $ProcessImage {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
f.debug_struct(::core::stringify!($ProcessImage))
$(
.field(::core::stringify!($field_name), &self.$field_name())
)*
.finish()
}
}
$crate::process_image! {
$(#[$meta])*
$vis struct mut $ProcessImageMut: $SIZE {
$(
$(#[$field_meta])*
$field_vis $field_name: ($($tag)+),
)*
}
}
};
}
#[cfg(test)]
mod tests {
#[test]
fn tag_macro_smoke1() {
let mut pi = [0x55, 0xaa, 0x00, 0xff];
assert_eq!(tag!(&pi, X, 2, 0), false);
assert_eq!(tag!(&pi, 3, 0), true);
assert_eq!(tag!(&pi, X, 0, 0), true);
assert_eq!(tag!(&pi, 0, 1), false);
*tag_mut!(&mut pi, X, 0, 0) = false;
assert_eq!(tag!(&pi, X, 0, 0), false);
assert_eq!(tag!(&pi, B, 2), 0x00);
*tag_mut!(&mut pi, X, 2, 7) = true;
assert_eq!(tag!(&pi, B, 2), 0x80);
assert_eq!(tag!(&pi, W, 2), 0x80ff);
assert_eq!(*tag_mut!(&mut pi, W, 2), 0x80ff);
let addr = 2;
assert_eq!(*tag_mut!(&mut pi, W, addr), 0x80ff);
assert_eq!(tag!(&pi, D, 0), 0x54aa80ff);
assert_eq!(*tag_mut!(&mut pi, D, 0), 0x54aa80ff);
*tag_mut!(&mut pi, W, 2) = 0xbeef;
assert_eq!(tag!(&pi, W, 2), 0xbeef);
}
process_image! {
pub struct TestPi, mut TestPiMut: 4 {
pub btn_start: (X, 1, 0),
pub btn_stop: (1, 1),
pub btn_reset: (X, 1, 2),
pub speed: (W, 2),
pub length: (B, 0),
}
}
#[test]
fn pi_macro_smoke1() {
let mut pi_buffer = [128, 0x55, 0xde, 0xad];
let pi = TestPi::try_from(&pi_buffer).unwrap();
assert_eq!(pi.btn_start(), true);
assert_eq!(pi.btn_stop(), false);
assert_eq!(pi.btn_reset(), true);
assert_eq!(pi.speed(), 0xdead);
assert_eq!(pi.length(), 128);
let mut pi = TestPiMut::try_from(&mut pi_buffer).unwrap();
assert_eq!(*pi.btn_start(), true);
assert_eq!(*pi.btn_stop(), false);
assert_eq!(*pi.btn_reset(), true);
assert_eq!(*pi.speed(), 0xdead);
assert_eq!(*pi.length(), 128);
*pi.btn_start() = false;
*pi.btn_stop() = true;
*pi.speed() = 1337;
*pi.length() = 1;
let pi = TestPi::try_from(&pi_buffer).unwrap();
assert_eq!(pi.btn_start(), false);
assert_eq!(pi.btn_stop(), true);
assert_eq!(pi.btn_reset(), true);
assert_eq!(pi.speed(), 1337);
assert_eq!(pi.length(), 1);
assert_eq!(tag!(&pi_buffer, 1, 0), false);
assert_eq!(tag!(&pi_buffer, W, 2), 1337);
assert_eq!(tag!(&pi_buffer, B, 0), 1);
}
process_image_owned! {
pub struct TestPiOwned, mut TestPiOwnedMut: 4 {
pub btn_start: (X, 1, 0),
pub btn_stop: (1, 1),
pub btn_reset: (X, 1, 2),
pub speed: (W, 2),
pub length: (B, 0),
}
}
#[test]
fn pi_owned_macro_smoke() {
let pi_buffer = [128, 0x55, 0xde, 0xad];
let mut pi = TestPiOwned::new_zeroed();
assert_eq!(pi.btn_start(), false);
assert_eq!(pi.btn_stop(), false);
assert_eq!(pi.btn_reset(), false);
assert_eq!(pi.speed(), 0);
assert_eq!(pi.length(), 0);
pi.as_slice_mut().copy_from_slice(&pi_buffer);
assert_eq!(pi.btn_start(), true);
assert_eq!(pi.btn_stop(), false);
assert_eq!(pi.btn_reset(), true);
assert_eq!(pi.speed(), 0xdead);
assert_eq!(pi.length(), 128);
let mut pi = TestPiOwned::try_from(&pi_buffer).unwrap();
assert_eq!(pi.btn_start(), true);
assert_eq!(pi.btn_stop(), false);
assert_eq!(pi.btn_reset(), true);
assert_eq!(pi.speed(), 0xdead);
assert_eq!(pi.length(), 128);
*pi.as_mut().btn_start() = false;
*pi.as_mut().btn_stop() = true;
*pi.as_mut().btn_reset() = true;
*pi.as_mut().speed() = 1337;
*pi.as_mut().length() = 1;
assert_eq!(pi.btn_start(), false);
assert_eq!(pi.btn_stop(), true);
assert_eq!(pi.btn_reset(), true);
assert_eq!(pi.speed(), 1337);
assert_eq!(pi.length(), 1);
let pi_buffer = pi.as_slice();
assert_eq!(tag!(&pi_buffer, 1, 0), false);
assert_eq!(tag!(&pi_buffer, W, 2), 1337);
assert_eq!(tag!(&pi_buffer, B, 0), 1);
}
#[test]
#[cfg_attr(
not(feature = "allow_unaligned_tags"),
should_panic(expected = "Word address must be divisible by 2")
)]
fn test_unaligned_word_tag() {
let buf = [
0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
];
assert_eq!(tag!(&buf, W, 1), 0xadbe);
}
#[test]
#[cfg_attr(
not(feature = "allow_unaligned_tags"),
should_panic(expected = "Word address must be divisible by 2")
)]
fn test_unaligned_word_tag_mut() {
let mut buf = [
0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
];
*tag_mut!(&mut buf, W, 1) = 0xcafe;
assert_eq!(tag!(&buf, W, 1), 0xcafe);
}
process_image_owned! {
pub struct TestPiPanic, mut TestPiPanicMut: 12 {
pub unaligned_word: (W, 1),
pub unaligned_dword: (D, 2),
pub unaligned_lword: (L, 4),
}
}
#[test]
#[cfg_attr(
not(feature = "allow_unaligned_tags"),
should_panic(expected = "Word address must be divisible by 2")
)]
fn test_unaligned_word() {
let pi = TestPiPanic::try_from(&[
0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
])
.unwrap();
assert_eq!(pi.unaligned_word(), 0xadbe);
}
#[test]
#[cfg_attr(
not(feature = "allow_unaligned_tags"),
should_panic(expected = "Word address must be divisible by 2")
)]
fn test_unaligned_word_mut() {
let mut pi = TestPiPanic::try_from(&[
0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
])
.unwrap();
*pi.as_mut().unaligned_word() = 0xcafe;
assert_eq!(pi.unaligned_word(), 0xcafe);
}
#[test]
#[cfg_attr(
not(feature = "allow_unaligned_tags"),
should_panic(expected = "Double word address must be divisible by 4")
)]
fn test_unaligned_dword() {
let pi = TestPiPanic::try_from(&[
0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
])
.unwrap();
assert_eq!(pi.unaligned_dword(), 0xbeefdead);
}
#[test]
#[cfg_attr(
not(feature = "allow_unaligned_tags"),
should_panic(expected = "Double word address must be divisible by 4")
)]
fn test_unaligned_dword_mut() {
let mut pi = TestPiPanic::try_from(&[
0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
])
.unwrap();
*pi.as_mut().unaligned_dword() = 0xc0ffee77;
assert_eq!(pi.unaligned_dword(), 0xc0ffee77);
}
#[test]
#[cfg_attr(
not(feature = "allow_unaligned_tags"),
should_panic(expected = "Long word address must be divisible by 8")
)]
fn test_unaligned_lword() {
let pi = TestPiPanic::try_from(&[
0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
])
.unwrap();
assert_eq!(pi.unaligned_lword(), 0xdeadbeefdeadbeef);
}
#[test]
#[cfg_attr(
not(feature = "allow_unaligned_tags"),
should_panic(expected = "Long word address must be divisible by 8")
)]
fn test_unaligned_lword_mut() {
let mut pi = TestPiPanic::try_from(&[
0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
])
.unwrap();
*pi.as_mut().unaligned_lword() = 0x7fff000000c0ffee;
assert_eq!(pi.unaligned_lword(), 0x7fff000000c0ffee);
}
}