use der::{asn1::OctetStringRef, Decode, Encode, Reader, Writer};
use indexmap::IndexMap;
use log::warn;
use snafu::prelude::*;
use crate::NaifId;
pub const KEY_NAME_LEN: usize = 32;
#[derive(Debug, Snafu, PartialEq)]
#[snafu(visibility(pub(crate)))]
#[non_exhaustive]
pub enum LutError {
#[snafu(display("must provide either an ID or a name for a loop up, but provided neither"))]
NoKeyProvided,
#[snafu(display("ID {id} not in look up table"))]
UnknownId { id: NaifId },
#[snafu(display("name {name} not in look up table"))]
UnknownName { name: String },
#[snafu(display("Look up table index is not in dataset"))]
InvalidIndex { index: u32 },
}
#[derive(Clone, Default, Debug, PartialEq, Eq)]
pub struct LookUpTable {
pub by_id: IndexMap<NaifId, u32>,
pub by_name: IndexMap<String, u32>,
}
impl LookUpTable {
pub fn append(&mut self, id: i32, name: &str, index: u32) -> Result<(), LutError> {
self.by_id.insert(id, index);
self.by_name.insert(name.to_string(), index);
Ok(())
}
pub fn append_id(&mut self, id: i32, index: u32) -> Result<(), LutError> {
self.by_id.insert(id, index);
Ok(())
}
pub fn append_name(&mut self, name: &str, index: u32) -> Result<(), LutError> {
self.by_name.insert(name.to_string(), index);
Ok(())
}
pub fn entries(&self) -> IndexMap<u32, (Option<NaifId>, Option<String>)> {
let mut rtn = IndexMap::with_capacity(self.by_id.len() + self.by_name.len());
for (id, entry) in &self.by_id {
rtn.insert(*entry, (Some(*id), None));
}
for (name, entry) in &self.by_name {
if !rtn.contains_key(entry) {
rtn.insert(*entry, (None, Some(name.clone())));
} else {
let val = rtn.get_mut(entry).unwrap();
val.1 = Some(name.clone());
}
}
rtn
}
pub fn reid(&mut self, current_id: i32, new_id: i32) -> Result<(), LutError> {
if let Some(entry) = self.by_id.swap_remove(¤t_id) {
self.by_id.insert(new_id, entry);
Ok(())
} else {
Err(LutError::UnknownId { id: current_id })
}
}
pub fn rmid(&mut self, id: i32) -> Result<(), LutError> {
if self.by_id.swap_remove(&id).is_none() {
Err(LutError::UnknownId { id })
} else {
Ok(())
}
}
pub fn rename(&mut self, current_name: &str, new_name: &str) -> Result<(), LutError> {
if let Some(entry) = self.by_name.swap_remove(current_name) {
self.by_name.insert(new_name.to_string(), entry);
Ok(())
} else {
Err(LutError::UnknownName {
name: current_name.to_string(),
})
}
}
pub fn rmname(&mut self, name: &str) -> Result<(), LutError> {
if self.by_name.swap_remove(name).is_none() {
Err(LutError::UnknownName {
name: name.to_string(),
})
} else {
Ok(())
}
}
pub fn len(&self) -> usize {
self.by_id.len().max(self.by_name.len())
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub(crate) fn check_integrity(&self) -> bool {
if self.by_id.is_empty() || self.by_name.is_empty() {
true
} else if self.by_id.len() != self.by_name.len() {
false
} else {
for entry in self.by_id.values() {
if !self.by_name.values().any(|name_entry| name_entry == entry) {
return false;
}
}
true
}
}
fn der_encoding(&self) -> (Vec<i32>, Vec<u32>, Vec<OctetStringRef<'_>>, Vec<u32>) {
let mut id_entries = Vec::<u32>::with_capacity(self.by_id.len());
let mut name_entries = Vec::<u32>::with_capacity(self.by_name.len());
let mut ids = Vec::<i32>::with_capacity(self.by_id.len());
for (id, index) in &self.by_id {
ids.push(*id);
id_entries.push(*index);
}
let mut names = Vec::<OctetStringRef>::with_capacity(self.by_name.len());
for (name, index) in &self.by_name {
names.push(OctetStringRef::new(name.as_bytes()).unwrap());
name_entries.push(*index);
}
(ids, id_entries, names, name_entries)
}
}
impl Encode for LookUpTable {
fn encoded_len(&self) -> der::Result<der::Length> {
let (ids, names, id_entries, name_entries) = self.der_encoding();
ids.encoded_len()?
+ names.encoded_len()?
+ id_entries.encoded_len()?
+ name_entries.encoded_len()?
}
fn encode(&self, encoder: &mut impl Writer) -> der::Result<()> {
let (ids, names, id_entries, name_entries) = self.der_encoding();
ids.encode(encoder)?;
names.encode(encoder)?;
id_entries.encode(encoder)?;
name_entries.encode(encoder)
}
}
impl<'a> Decode<'a> for LookUpTable {
fn decode<R: Reader<'a>>(decoder: &mut R) -> der::Result<Self> {
let mut lut = Self::default();
let ids: Vec<i32> = decoder.decode()?;
let id_entries: Vec<u32> = decoder.decode()?;
let names: Vec<OctetStringRef> = decoder.decode()?;
let name_entries: Vec<u32> = decoder.decode()?;
for (id, index) in ids.iter().zip(id_entries.iter()) {
lut.by_id.insert(*id, *index);
}
for (name, entry) in names.iter().zip(name_entries.iter()) {
let key = core::str::from_utf8(name.as_bytes())?;
lut.by_name
.insert(key[..KEY_NAME_LEN.min(key.len())].to_string(), *entry);
}
if !lut.check_integrity() {
warn!(
"decoded lookup table is not integral: {} names but {} ids",
lut.by_name.len(),
lut.by_id.len()
);
}
Ok(lut)
}
}
#[cfg(test)]
mod lut_ut {
use super::{Decode, Encode, LookUpTable};
#[test]
fn zero_repr() {
let repr = LookUpTable::default();
let mut buf = vec![];
repr.encode_to_vec(&mut buf).unwrap();
let repr_dec = LookUpTable::from_der(&buf).unwrap();
assert_eq!(repr, repr_dec);
dbg!(repr);
assert_eq!(core::mem::size_of::<LookUpTable>(), 144);
}
#[test]
fn repr_ids_only() {
let mut repr = LookUpTable::default();
for i in 0..32 {
let id = -20 - i;
repr.append_id(id, 0).unwrap();
}
let mut buf = vec![];
repr.encode_to_vec(&mut buf).unwrap();
let repr_dec = LookUpTable::from_der(&buf).unwrap();
assert_eq!(repr, repr_dec);
}
#[test]
fn repr_names_only() {
const LUT_SIZE: usize = 32;
let mut names = Vec::new();
let mut repr = LookUpTable::default();
for i in 0..LUT_SIZE {
names.push(format!("Name{i}"));
}
for (i, name) in names.iter().enumerate().take(LUT_SIZE) {
repr.append_name(name, i as u32).unwrap();
}
let mut buf = vec![];
repr.encode_to_vec(&mut buf).unwrap();
let repr_dec = LookUpTable::from_der(&buf).unwrap();
assert_eq!(repr, repr_dec);
}
#[test]
fn test_integrity_checker() {
let mut lut = LookUpTable::default();
assert!(lut.check_integrity());
lut.append(1, "a", 0).unwrap();
assert!(lut.check_integrity());
lut.append_name("a", 0).unwrap();
assert!(lut.check_integrity());
lut.append(2, "b", 11).unwrap();
assert!(lut.check_integrity());
lut.append_name("b", 11).unwrap();
assert!(lut.check_integrity()); }
}