use crate::error::Error;
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum ScsType {
Scs11 = 0x11,
Scs12 = 0x12,
Scs13 = 0x13,
Scs14 = 0x14,
Scs15 = 0x15,
Scs16 = 0x16,
Scs17 = 0x17,
Scs18 = 0x18,
}
impl ScsType {
pub const fn from_byte(b: u8) -> Result<Self, Error> {
Ok(match b {
0x11 => Self::Scs11,
0x12 => Self::Scs12,
0x13 => Self::Scs13,
0x14 => Self::Scs14,
0x15 => Self::Scs15,
0x16 => Self::Scs16,
0x17 => Self::Scs17,
0x18 => Self::Scs18,
other => return Err(Error::BadSecurityBlock(other)),
})
}
pub const fn as_byte(self) -> u8 {
self as u8
}
pub const fn has_mac(self) -> bool {
matches!(self, Self::Scs15 | Self::Scs16 | Self::Scs17 | Self::Scs18)
}
pub const fn is_encrypted(self) -> bool {
matches!(self, Self::Scs17 | Self::Scs18)
}
pub const fn is_handshake(self) -> bool {
matches!(self, Self::Scs11 | Self::Scs12 | Self::Scs13 | Self::Scs14)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ScbView<'a> {
pub ty: ScsType,
pub data: &'a [u8],
}
impl<'a> ScbView<'a> {
pub fn wire_len(&self) -> usize {
self.data.len() + 2
}
pub fn parse(buf: &'a [u8]) -> Result<Self, Error> {
if buf.len() < 2 {
return Err(Error::Truncated {
have: buf.len(),
need: 2,
});
}
let len = buf[0] as usize;
if len < 2 {
return Err(Error::BadSecurityBlockLength(buf[1]));
}
if buf.len() < len {
return Err(Error::Truncated {
have: buf.len(),
need: len,
});
}
let ty = ScsType::from_byte(buf[1])?;
Ok(Self {
ty,
data: &buf[2..len],
})
}
}
#[cfg(feature = "alloc")]
mod alloc_impls {
use super::*;
use alloc::vec::Vec;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Scb {
pub ty: ScsType,
pub data: Vec<u8>,
}
impl Scb {
pub fn new(ty: ScsType, data: impl Into<Vec<u8>>) -> Self {
Self {
ty,
data: data.into(),
}
}
pub fn as_view(&self) -> ScbView<'_> {
ScbView {
ty: self.ty,
data: &self.data,
}
}
pub fn encode(&self, out: &mut Vec<u8>) {
let len = (self.data.len() + 2) as u8;
out.push(len);
out.push(self.ty.as_byte());
out.extend_from_slice(&self.data);
}
}
impl From<ScbView<'_>> for Scb {
fn from(v: ScbView<'_>) -> Self {
Self {
ty: v.ty,
data: v.data.to_vec(),
}
}
}
}
#[cfg(feature = "alloc")]
pub use alloc_impls::Scb;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn scs_type_roundtrip() {
for byte in [0x11u8, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18] {
assert_eq!(ScsType::from_byte(byte).unwrap().as_byte(), byte);
}
}
#[test]
fn scs_type_rejects_unknown() {
assert!(ScsType::from_byte(0x10).is_err());
assert!(ScsType::from_byte(0x19).is_err());
assert!(ScsType::from_byte(0x00).is_err());
}
#[test]
fn parse_minimal() {
let buf = [0x02u8, 0x11];
let scb = ScbView::parse(&buf).unwrap();
assert_eq!(scb.ty, ScsType::Scs11);
assert!(scb.data.is_empty());
assert_eq!(scb.wire_len(), 2);
}
#[test]
fn parse_with_data() {
let buf = [0x05u8, 0x12, 0xAA, 0xBB, 0xCC];
let scb = ScbView::parse(&buf).unwrap();
assert_eq!(scb.ty, ScsType::Scs12);
assert_eq!(scb.data, &[0xAA, 0xBB, 0xCC]);
}
#[test]
fn parse_rejects_short_len() {
assert!(ScbView::parse(&[0x01u8, 0x11]).is_err());
assert!(ScbView::parse(&[]).is_err());
}
#[test]
fn mac_predicate() {
for ty in [
ScsType::Scs15,
ScsType::Scs16,
ScsType::Scs17,
ScsType::Scs18,
] {
assert!(ty.has_mac());
}
for ty in [
ScsType::Scs11,
ScsType::Scs12,
ScsType::Scs13,
ScsType::Scs14,
] {
assert!(!ty.has_mac());
}
}
}