use super::fragment::{FragmentData, KfxFragment};
use super::ion::{IonValue, IonWriter};
use super::symbols::KfxSymbol;
pub struct SerializedEntity {
pub id: u32,
pub entity_type: u32,
pub data: Vec<u8>,
}
const CONTAINER_MAGIC: &[u8; 4] = b"CONT";
const ENTITY_MAGIC: &[u8; 4] = b"ENTY";
#[allow(clippy::vec_init_then_push)]
pub fn serialize_container(
container_id: &str,
entities: &[SerializedEntity],
symtab_ion: &[u8],
format_caps_ion: &[u8],
) -> Vec<u8> {
let mut entity_table = Vec::new();
let mut current_offset = 0u64;
for entity in entities {
entity_table.extend_from_slice(&entity.id.to_le_bytes());
entity_table.extend_from_slice(&entity.entity_type.to_le_bytes());
entity_table.extend_from_slice(¤t_offset.to_le_bytes());
entity_table.extend_from_slice(&(entity.data.len() as u64).to_le_bytes());
current_offset += entity.data.len() as u64;
}
let mut entity_data = Vec::new();
for entity in entities {
entity_data.extend_from_slice(&entity.data);
}
let payload_sha1 = simple_hash(&entity_data);
const HEADER_SIZE: usize = 18;
let entity_table_offset = HEADER_SIZE;
let symtab_offset = entity_table_offset + entity_table.len();
let format_caps_offset = symtab_offset + symtab_ion.len();
let mut container_info_fields = Vec::new();
container_info_fields.push((
KfxSymbol::Bccontid as u64,
IonValue::String(container_id.to_string()),
));
container_info_fields.push((KfxSymbol::Bccomprtype as u64, IonValue::Int(0)));
container_info_fields.push((KfxSymbol::Bcdrmscheme as u64, IonValue::Int(0)));
container_info_fields.push((KfxSymbol::Bcchunksize as u64, IonValue::Int(4096)));
container_info_fields.push((
KfxSymbol::Bcindextaboffset as u64,
IonValue::Int(entity_table_offset as i64),
));
container_info_fields.push((
KfxSymbol::Bcindextablength as u64,
IonValue::Int(entity_table.len() as i64),
));
container_info_fields.push((
KfxSymbol::Bcdocsymboloffset as u64,
IonValue::Int(symtab_offset as i64),
));
container_info_fields.push((
KfxSymbol::Bcdocsymbollength as u64,
IonValue::Int(symtab_ion.len() as i64),
));
if !format_caps_ion.is_empty() {
container_info_fields.push((
KfxSymbol::Bcfcapabilitiesoffset as u64,
IonValue::Int(format_caps_offset as i64),
));
container_info_fields.push((
KfxSymbol::Bcfcapabilitieslength as u64,
IonValue::Int(format_caps_ion.len() as i64),
));
}
let mut ion_writer = IonWriter::new();
ion_writer.write_bvm();
ion_writer.write_value(&IonValue::Struct(container_info_fields));
let container_info_data = ion_writer.into_bytes();
let container_info_offset = format_caps_offset + format_caps_ion.len();
let kfxgen_info = format!(
r#"[{{key:kfxgen_package_version,value:boko-{}}},{{key:kfxgen_application_version,value:boko}},{{key:kfxgen_payload_sha1,value:{}}},{{key:kfxgen_acr,value:{}}}]"#,
env!("CARGO_PKG_VERSION"),
payload_sha1,
container_id
);
let header_len = container_info_offset + container_info_data.len() + kfxgen_info.len();
let mut output = Vec::new();
output.extend_from_slice(CONTAINER_MAGIC);
output.extend_from_slice(&2u16.to_le_bytes()); output.extend_from_slice(&(header_len as u32).to_le_bytes());
output.extend_from_slice(&(container_info_offset as u32).to_le_bytes());
output.extend_from_slice(&(container_info_data.len() as u32).to_le_bytes());
output.extend_from_slice(&entity_table);
output.extend_from_slice(symtab_ion);
output.extend_from_slice(format_caps_ion);
output.extend_from_slice(&container_info_data);
output.extend_from_slice(kfxgen_info.as_bytes());
output.extend_from_slice(&entity_data);
output
}
pub fn create_entity_data(value: &IonValue) -> Vec<u8> {
let header_fields = vec![
(KfxSymbol::Bccomprtype as u64, IonValue::Int(0)),
(KfxSymbol::Bcdrmscheme as u64, IonValue::Int(0)),
];
let mut header_writer = IonWriter::new();
header_writer.write_bvm();
header_writer.write_value(&IonValue::Struct(header_fields));
let header_ion = header_writer.into_bytes();
let mut content_writer = IonWriter::new();
content_writer.write_bvm();
content_writer.write_value(value);
let content_ion = content_writer.into_bytes();
let header_len = 10 + header_ion.len();
let mut data = Vec::new();
data.extend_from_slice(ENTITY_MAGIC);
data.extend_from_slice(&1u16.to_le_bytes()); data.extend_from_slice(&(header_len as u32).to_le_bytes());
data.extend_from_slice(&header_ion);
data.extend_from_slice(&content_ion);
data
}
pub fn create_raw_media_data(raw_bytes: &[u8]) -> Vec<u8> {
let header_fields = vec![
(KfxSymbol::Bccomprtype as u64, IonValue::Int(0)),
(KfxSymbol::Bcdrmscheme as u64, IonValue::Int(0)),
];
let mut header_writer = IonWriter::new();
header_writer.write_bvm();
header_writer.write_value(&IonValue::Struct(header_fields));
let header_ion = header_writer.into_bytes();
let header_len = 10 + header_ion.len();
let mut data = Vec::new();
data.extend_from_slice(ENTITY_MAGIC);
data.extend_from_slice(&1u16.to_le_bytes()); data.extend_from_slice(&(header_len as u32).to_le_bytes());
data.extend_from_slice(&header_ion);
data.extend_from_slice(raw_bytes);
data
}
pub fn serialize_annotated_ion(annotation_id: u64, value: &IonValue) -> Vec<u8> {
let annotated = IonValue::Annotated(vec![annotation_id], Box::new(value.clone()));
let mut writer = IonWriter::new();
writer.write_bvm();
writer.write_value(&annotated);
writer.into_bytes()
}
pub fn serialize_fragment(fragment: &KfxFragment) -> Vec<u8> {
match &fragment.data {
FragmentData::Ion(value) => create_entity_data(value),
FragmentData::Raw(bytes) => create_raw_media_data(bytes),
}
}
pub fn generate_container_id() -> String {
#[cfg(target_arch = "wasm32")]
let seed = {
(js_sys::Date::now() as u128) * 1_000_000 };
#[cfg(not(target_arch = "wasm32"))]
let seed = {
use std::time::{SystemTime, UNIX_EPOCH};
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos())
.unwrap_or(0)
};
let mut state = seed;
let chars: Vec<char> = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".chars().collect();
let mut id = String::from("CR!");
for _ in 0..28 {
state = state.wrapping_mul(6364136223846793005).wrapping_add(1);
let idx = ((state >> 56) as usize) % chars.len();
id.push(chars[idx]);
}
id
}
fn simple_hash(data: &[u8]) -> String {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
data.hash(&mut hasher);
let hash = hasher.finish();
format!(
"{:016x}{:016x}{:08x}",
hash,
hash.rotate_left(32),
(hash >> 32) as u32
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_container_id_format() {
let id = generate_container_id();
assert!(id.starts_with("CR!"));
assert_eq!(id.len(), 31);
let suffix = &id[3..];
assert!(
suffix
.chars()
.all(|c| c.is_ascii_uppercase() || c.is_ascii_digit()),
"Container ID should only contain uppercase alphanumeric: {}",
id
);
}
#[test]
fn test_create_entity_data() {
let value = IonValue::Struct(vec![(
KfxSymbol::Title as u64,
IonValue::String("Test".into()),
)]);
let data = create_entity_data(&value);
assert_eq!(&data[..4], b"ENTY");
assert_eq!(u16::from_le_bytes([data[4], data[5]]), 1);
}
#[test]
fn test_create_raw_media_data() {
let raw = vec![0xFF, 0xD8, 0xFF, 0xE0]; let data = create_raw_media_data(&raw);
assert_eq!(&data[..4], b"ENTY");
assert!(data.ends_with(&raw));
}
#[test]
fn test_serialize_annotated_ion() {
let value = IonValue::List(vec![IonValue::String("symbol1".into())]);
let data = serialize_annotated_ion(3, &value);
assert_eq!(&data[..4], &[0xe0, 0x01, 0x00, 0xea]);
}
}