use crate::metadata::ir::{self, Access};
use super::descriptor::Descriptor;
use super::types::{EncodeError, EncodeInput, Value};
impl Descriptor {
pub fn decode<'a>(&self, buf: &'a [u8], path: &str) -> Option<Value<'a>> {
let info = self.describe(path)?;
let start = info.byte_offset as usize;
if info.bit_size > 64 {
let end = start + ((info.bit_size + 7) / 8) as usize;
if end > buf.len() {
return None;
}
return Some(Value::Bytes(&buf[start..end]));
}
let bit_end = info.bit_offset + info.bit_size;
let byte_end = start + ((bit_end + 7) / 8) as usize;
if byte_end > buf.len() {
return None;
}
let mut raw: u64 = 0;
for i in 0..(byte_end - start) {
raw |= (buf[start + i] as u64) << (8 * i);
}
let mask = if info.bit_size == 64 {
u64::MAX
} else {
(1u64 << info.bit_size) - 1
};
let value = (raw >> info.bit_offset) & mask;
if let Some(enumm) = info.enumm {
if let Some(v) = enumm.variants.iter().find(|v| v.value == value) {
return Some(Value::Variant(v.name));
}
}
Some(Value::Literal(value))
}
pub fn encode(
&self,
buf: &mut [u8],
path: &str,
input: EncodeInput,
) -> Result<(), EncodeError> {
let (entry_name, field_name) = match path.split_once('.') {
Some((e, f)) => (e, Some(f)),
None => (path, None),
};
let item = self.item(entry_name).ok_or(EncodeError::NoSuchEntry)?;
let reg = Self::register_of(item).ok_or(EncodeError::NoSuchEntry)?;
if matches!(reg.access, Access::Read) {
return Err(EncodeError::ReadOnly);
}
let entry_start = item.byte_offset as usize;
let entry_size = ((reg.bit_size + 7) / 8) as usize;
if entry_start + entry_size > buf.len() {
return Err(EncodeError::BufferTooShort);
}
match field_name {
None => write_entry(
&mut buf[entry_start..entry_start + entry_size],
reg.bit_size,
input,
)?,
Some(fname) => {
let fs = self.fieldset_of(item).ok_or(EncodeError::NoSuchField)?;
let field = Self::field_in(fs, fname).ok_or(EncodeError::NoSuchField)?;
let bit_offset = match &field.bit_offset {
ir::BitOffset::Regular(r) => r.offset,
ir::BitOffset::Cursed(_) => return Err(EncodeError::NoSuchField),
};
let value = resolve_field_value(field, self, input)?;
write_field_bits(
&mut buf[entry_start..entry_start + entry_size],
bit_offset,
field.bit_size,
value,
);
}
}
self.apply_complement(buf, item)
}
pub(super) fn apply_complement(
&self,
buf: &mut [u8],
item: &ir::BlockItem,
) -> Result<(), EncodeError> {
let block = match self.block() {
Some(b) => b,
None => return Ok(()),
};
for sibling in block.items {
if !is_n_complement(sibling.name, item.name) {
continue;
}
let sib_reg = match Self::register_of(sibling) {
Some(r) => r,
None => continue,
};
let src_size = match Self::register_of(item) {
Some(r) => ((r.bit_size + 7) / 8) as usize,
None => continue,
};
let dst_size = ((sib_reg.bit_size + 7) / 8) as usize;
if src_size != dst_size {
continue;
}
let src = item.byte_offset as usize;
let dst = sibling.byte_offset as usize;
if dst + dst_size > buf.len() {
return Err(EncodeError::BufferTooShort);
}
for i in 0..src_size {
buf[dst + i] = !buf[src + i];
}
return Ok(());
}
Ok(())
}
}
pub(super) fn is_n_complement(sibling: &str, source: &str) -> bool {
let s = sibling.as_bytes();
let src = source.as_bytes();
s.len() == src.len() + 1 && s[0].eq_ignore_ascii_case(&b'N') && s[1..].eq_ignore_ascii_case(src)
}
pub(super) fn bit_mask(bit_size: u32) -> u64 {
if bit_size >= 64 {
u64::MAX
} else {
(1u64 << bit_size) - 1
}
}
fn write_entry(dst: &mut [u8], bit_size: u32, input: EncodeInput) -> Result<(), EncodeError> {
match input {
EncodeInput::Bytes(bytes) => {
if bytes.len() != dst.len() {
return Err(EncodeError::OutOfRange);
}
dst.copy_from_slice(bytes);
Ok(())
}
EncodeInput::Literal(val) => {
if bit_size < 64 && val > bit_mask(bit_size) {
return Err(EncodeError::OutOfRange);
}
for i in 0..dst.len() {
dst[i] = ((val >> (8 * i)) & 0xff) as u8;
}
Ok(())
}
EncodeInput::Variant(_) => Err(EncodeError::NoSuchVariant),
}
}
fn resolve_field_value(
field: &ir::Field,
desc: &Descriptor,
input: EncodeInput,
) -> Result<u64, EncodeError> {
let max = bit_mask(field.bit_size);
match input {
EncodeInput::Variant(name) => {
let enumm = field
.enumm
.and_then(|n| desc.enumm_named(n))
.ok_or(EncodeError::NoSuchVariant)?;
enumm
.variants
.iter()
.find(|v| v.name.eq_ignore_ascii_case(name))
.map(|v| v.value)
.ok_or(EncodeError::NoSuchVariant)
}
EncodeInput::Literal(v) => {
if v > max {
Err(EncodeError::OutOfRange)
} else {
Ok(v)
}
}
EncodeInput::Bytes(_) => Err(EncodeError::OutOfRange),
}
}
fn write_field_bits(dst: &mut [u8], bit_offset: u32, bit_size: u32, value: u64) {
let bit_end = bit_offset + bit_size;
let bytes_touched = ((bit_end + 7) / 8) as usize;
let mut raw: u64 = 0;
for i in 0..bytes_touched {
raw |= (dst[i] as u64) << (8 * i);
}
let mask = bit_mask(bit_size) << bit_offset;
raw = (raw & !mask) | ((value << bit_offset) & mask);
for i in 0..bytes_touched {
dst[i] = ((raw >> (8 * i)) & 0xff) as u8;
}
}