use crate::crypto::{CipherId, Fek, ZERO_NONCE};
use crate::error::{Error, Result};
use crate::header::{FILENAME_BUCKET, parse_limits};
use crate::util::{Cursor, Writer};
pub const TLV_HEADER_LEN: usize = 8;
pub const INDEX_ENTRY_LEN: usize = 16;
pub mod types {
pub const FILENAME: u16 = 0x0001;
pub const PLAIN_EXT: u16 = 0x0002;
pub const THUMBNAIL: u16 = 0x0003;
pub const MEDIA_META: u16 = 0x0004;
pub const COMPRESSION_INDEX: u16 = 0x0005;
pub const MOOV_CACHE: u16 = 0x0006;
pub const SHARD_INFO: u16 = 0x0007;
pub const ORIGINAL_META: u16 = 0x0008;
pub const CONTENT_HASH: u16 = 0x0009;
pub const ZSTD_DICT: u16 = 0x000A;
pub const PORTABLE_SLOT: u16 = 0x000B;
pub const SUBTITLES: u16 = 0x000C;
pub const TRANSCODE_INFO: u16 = 0x000D;
pub const FOLDER_INDEX: u16 = 0x000E;
pub const DISGUISE_INFO: u16 = 0x000F;
pub const SLOT_DIRECTORY: u16 = 0x0010;
pub const PRIVATE_RANGE_START: u16 = 0x8000;
#[must_use]
pub const fn is_known(t: u16) -> bool {
matches!(
t,
FILENAME
| PLAIN_EXT
| THUMBNAIL
| MEDIA_META
| COMPRESSION_INDEX
| MOOV_CACHE
| SHARD_INFO
| ORIGINAL_META
| CONTENT_HASH
| ZSTD_DICT
| PORTABLE_SLOT
| SUBTITLES
| TRANSCODE_INFO
| FOLDER_INDEX
| DISGUISE_INFO
| SLOT_DIRECTORY
)
}
}
pub mod tlv_flags {
pub const CRITICAL: u16 = 1 << 0;
pub const ENCRYPTED: u16 = 1 << 1;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TlvEntry {
pub tlv_type: u16,
pub flags: u16,
pub value: Vec<u8>,
}
impl TlvEntry {
#[must_use]
pub const fn new(tlv_type: u16, flags: u16, value: Vec<u8>) -> Self {
Self { tlv_type, flags, value }
}
#[must_use]
pub const fn is_critical(&self) -> bool {
self.flags & tlv_flags::CRITICAL != 0
}
#[must_use]
pub const fn is_encrypted(&self) -> bool {
self.flags & tlv_flags::ENCRYPTED != 0
}
#[must_use]
pub const fn encoded_len(&self) -> usize {
TLV_HEADER_LEN.saturating_add(self.value.len())
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct TlvSet {
entries: Vec<TlvEntry>,
}
impl TlvSet {
#[must_use]
pub const fn new() -> Self {
Self { entries: Vec::new() }
}
pub fn push(&mut self, e: TlvEntry) -> &mut Self {
self.entries.push(e);
self
}
#[must_use]
pub fn entries(&self) -> &[TlvEntry] {
&self.entries
}
#[must_use]
pub fn len(&self) -> usize {
self.entries.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
#[must_use]
pub fn find(&self, tlv_type: u16) -> Option<&TlvEntry> {
self.entries.iter().find(|e| e.tlv_type == tlv_type)
}
#[must_use]
pub fn encoded_len(&self) -> usize {
self.entries.iter().map(TlvEntry::encoded_len).sum()
}
pub fn parse(data: &[u8]) -> Result<Self> {
let mut c = Cursor::new(data, "tlv area");
let mut entries = Vec::new();
while c.remaining() > 0 {
if entries.len() >= parse_limits::MAX_TLV_ENTRIES {
return Err(Error::MalformedTlv {
tlv_type: 0,
reason: "too many TLV entries",
});
}
if c.remaining() < TLV_HEADER_LEN {
return Err(Error::Truncated {
context: "tlv entry header",
need: TLV_HEADER_LEN,
got: c.remaining(),
});
}
let tlv_type = c.u16le()?;
let flags = c.u16le()?;
let length = c.u32le()? as usize;
let value = c.take(length)?.to_vec();
entries.push(TlvEntry { tlv_type, flags, value });
}
Ok(Self { entries })
}
pub fn check_critical(&self) -> Result<()> {
for e in &self.entries {
if e.is_critical()
&& !types::is_known(e.tlv_type)
&& e.tlv_type < types::PRIVATE_RANGE_START
{
return Err(Error::UnknownCriticalTlv { tlv_type: e.tlv_type });
}
}
Ok(())
}
#[must_use]
pub fn to_bytes(&self) -> Vec<u8> {
let mut w = Writer::with_capacity(self.encoded_len());
for e in &self.entries {
let len = u32::try_from(e.value.len()).unwrap_or(u32::MAX);
w.u16le(e.tlv_type).u16le(e.flags).u32le(len).bytes(&e.value);
}
w.into_vec()
}
pub fn decrypt_value(&self, tlv_type: u16, fek: &Fek, cipher: CipherId) -> Result<Vec<u8>> {
let e = self.find(tlv_type).ok_or(Error::MissingTlv { tlv_type })?;
if !e.is_encrypted() {
return Err(Error::MalformedTlv {
tlv_type,
reason: "entry is not marked ENCRYPTED",
});
}
let key = fek.derive_tlv_key(tlv_type);
cipher
.decrypt(&key, &ZERO_NONCE, &e.value, &[])?
.ok_or(Error::MalformedTlv { tlv_type, reason: "AEAD authentication failed" })
}
}
pub fn encrypt_entry(
tlv_type: u16,
extra_flags: u16,
plaintext: &[u8],
fek: &Fek,
cipher: CipherId,
) -> Result<TlvEntry> {
let key = fek.derive_tlv_key(tlv_type);
let ct = cipher.encrypt(&key, &ZERO_NONCE, plaintext, &[])?;
Ok(TlvEntry::new(tlv_type, extra_flags | tlv_flags::ENCRYPTED, ct))
}
pub fn pad_filename(name: &str) -> Result<Vec<u8>> {
let bytes = name.as_bytes();
let real_len = u16::try_from(bytes.len()).map_err(|_| Error::MalformedTlv {
tlv_type: types::FILENAME,
reason: "filename longer than 65535 bytes",
})?;
let need = bytes.len().saturating_add(2);
let bucket = need.div_ceil(FILENAME_BUCKET).saturating_mul(FILENAME_BUCKET);
debug_assert!(bucket >= need, "bucket 必须不小于 need,否则填充量为负");
let mut w = Writer::with_capacity(bucket);
w.u16le(real_len).bytes(bytes).zeros(bucket.saturating_sub(need));
let out = w.into_vec();
debug_assert_eq!(out.len(), bucket, "padded 长度必须恰好等于桶大小");
Ok(out)
}
pub fn unpad_filename(padded: &[u8]) -> Result<String> {
let mut c = Cursor::new(padded, "filename tlv");
let real_len = c.u16le().map_err(|_| Error::MalformedTlv {
tlv_type: types::FILENAME,
reason: "missing length prefix",
})? as usize;
let name = c.take(real_len).map_err(|_| Error::MalformedTlv {
tlv_type: types::FILENAME,
reason: "declared length exceeds padded data",
})?;
String::from_utf8(name.to_vec()).map_err(|_| Error::MalformedTlv {
tlv_type: types::FILENAME,
reason: "filename is not valid UTF-8",
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CompressionIndexEntry {
pub ct_offset: u64,
pub ct_len: u32,
pub plain_len: u32,
}
#[must_use]
pub fn encode_compression_index(entries: &[CompressionIndexEntry]) -> Vec<u8> {
let count = u32::try_from(entries.len()).unwrap_or(u32::MAX);
let mut w = Writer::with_capacity(
4usize.saturating_add(entries.len().saturating_mul(INDEX_ENTRY_LEN)),
);
w.u32le(count);
for e in entries {
w.u64le(e.ct_offset).u32le(e.ct_len).u32le(e.plain_len);
}
w.into_vec()
}
pub fn decode_compression_index(data: &[u8]) -> Result<Vec<CompressionIndexEntry>> {
let mut c = Cursor::new(data, "compression index");
let count_u32 = c.u32le().map_err(|_| Error::MalformedTlv {
tlv_type: types::COMPRESSION_INDEX,
reason: "missing entry_count",
})?;
if count_u32 > parse_limits::MAX_INDEX_ENTRIES {
return Err(Error::MalformedTlv {
tlv_type: types::COMPRESSION_INDEX,
reason: "entry_count exceeds the parse limit",
});
}
let count = count_u32 as usize;
let need = count.checked_mul(INDEX_ENTRY_LEN).ok_or(Error::MalformedTlv {
tlv_type: types::COMPRESSION_INDEX,
reason: "entry_count overflows",
})?;
if c.remaining() != need {
return Err(Error::MalformedTlv {
tlv_type: types::COMPRESSION_INDEX,
reason: "entry_count does not match data length",
});
}
let mut out = Vec::with_capacity(count);
for _ in 0..count {
let ct_offset = c.u64le()?;
let ct_len = c.u32le()?;
let plain_len = c.u32le()?;
out.push(CompressionIndexEntry { ct_offset, ct_len, plain_len });
}
Ok(out)
}