use crate::cir::model::{ CIR_VERSION_V1, CanonicalInputRepresentation, CirHashAlgorithm, CirObject, CirTal, }; use crate::data_model::oid::OID_SHA256_RAW; #[derive(Debug, thiserror::Error)] pub enum CirEncodeError { #[error("CIR model validation failed: {0}")] Validate(String), } pub fn encode_cir(cir: &CanonicalInputRepresentation) -> Result, CirEncodeError> { cir.validate().map_err(CirEncodeError::Validate)?; Ok(encode_sequence(&[ encode_integer_u32(cir.version), encode_oid(match cir.hash_alg { CirHashAlgorithm::Sha256 => OID_SHA256_RAW, }), encode_generalized_time(cir.validation_time), encode_sequence( &cir.objects .iter() .map(encode_object) .collect::, _>>()?, ), encode_sequence( &cir.tals .iter() .map(encode_tal) .collect::, _>>()?, ), ])) } fn encode_object(object: &CirObject) -> Result, CirEncodeError> { object.validate().map_err(CirEncodeError::Validate)?; Ok(encode_sequence(&[ encode_ia5_string(object.rsync_uri.as_bytes()), encode_octet_string(&object.sha256), ])) } fn encode_tal(tal: &CirTal) -> Result, CirEncodeError> { tal.validate().map_err(CirEncodeError::Validate)?; Ok(encode_sequence(&[ encode_ia5_string(tal.tal_uri.as_bytes()), encode_octet_string(&tal.tal_bytes), ])) } fn encode_generalized_time(t: time::OffsetDateTime) -> Vec { let t = t.to_offset(time::UtcOffset::UTC); let s = format!( "{:04}{:02}{:02}{:02}{:02}{:02}Z", t.year(), u8::from(t.month()), t.day(), t.hour(), t.minute(), t.second() ); encode_tlv(0x18, s.into_bytes()) } fn encode_integer_u32(v: u32) -> Vec { encode_integer_bytes(unsigned_integer_bytes(v as u64)) } fn encode_integer_bytes(mut bytes: Vec) -> Vec { if bytes.is_empty() { bytes.push(0); } if bytes[0] & 0x80 != 0 { bytes.insert(0, 0); } encode_tlv(0x02, bytes) } fn unsigned_integer_bytes(v: u64) -> Vec { if v == 0 { return vec![0]; } let mut out = Vec::new(); let mut n = v; while n > 0 { out.push((n & 0xFF) as u8); n >>= 8; } out.reverse(); out } fn encode_oid(raw_body: &[u8]) -> Vec { encode_tlv(0x06, raw_body.to_vec()) } fn encode_ia5_string(bytes: &[u8]) -> Vec { encode_tlv(0x16, bytes.to_vec()) } fn encode_octet_string(bytes: &[u8]) -> Vec { encode_tlv(0x04, bytes.to_vec()) } fn encode_sequence(elements: &[Vec]) -> Vec { let mut body = Vec::new(); for element in elements { body.extend_from_slice(element); } encode_tlv(0x30, body) } fn encode_tlv(tag: u8, value: Vec) -> Vec { let mut out = Vec::with_capacity(1 + encoded_len_len(value.len()) + value.len()); out.push(tag); encode_len_into(value.len(), &mut out); out.extend_from_slice(&value); out } fn encoded_len_len(len: usize) -> usize { if len < 0x80 { 1 } else { 1 + len.to_be_bytes().iter().skip_while(|&&b| b == 0).count() } } fn encode_len_into(len: usize, out: &mut Vec) { if len < 0x80 { out.push(len as u8); return; } let bytes = len.to_be_bytes(); let first_non_zero = bytes .iter() .position(|&b| b != 0) .unwrap_or(bytes.len() - 1); let len_bytes = &bytes[first_non_zero..]; out.push(0x80 | (len_bytes.len() as u8)); out.extend_from_slice(len_bytes); } #[allow(dead_code)] const _: () = { let _ = CIR_VERSION_V1; };