use serde::{Deserialize, Serialize};
pub const V3_MAGIC: [u8; 2] = [0x53, 0x45];
pub const V3_PAYLOAD_VERSION: u8 = 3;
pub const V3_MAX_EMBEDDED_SIZE: usize = 256;
pub const V3_MAX_EXTENSION_SIZE: usize = 128;
pub const V3_MAX_KEY_ID_LEN: usize = 32;
pub const V3_DOMAIN_STRING: &[u8] = b"StegoEggo-v3";
pub const V3_CORE_SIZE: usize = 32;
pub const V3_MAX_EXTENSION_COUNT: usize = 32;
pub const END_OF_EXTENSIONS: u16 = 0xFFFF;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum AuthAlgorithm {
None = 0,
Crc32 = 1,
HmacSha256Truncated = 2,
Ed25519 = 3,
}
impl AuthAlgorithm {
#[must_use]
pub fn from_byte(b: u8) -> Option<Self> {
match b {
0 => Some(Self::None),
1 => Some(Self::Crc32),
2 => Some(Self::HmacSha256Truncated),
3 => Some(Self::Ed25519),
_ => None,
}
}
#[must_use]
pub fn tag_length(self) -> Option<usize> {
match self {
Self::None => Some(0),
Self::Crc32 => Some(4),
Self::HmacSha256Truncated => Some(16),
Self::Ed25519 => Some(64),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProtectionChannels {
pub rights_metadata: bool,
pub hidden_marker: bool,
pub authentication: bool,
}
impl ProtectionChannels {
#[must_use]
pub fn to_bits(self) -> u16 {
let mut bits = 0u16;
if self.rights_metadata {
bits |= 1;
}
if self.hidden_marker {
bits |= 1 << 1;
}
if self.authentication {
bits |= 1 << 2;
}
bits
}
#[must_use]
pub fn from_bits(bits: u16) -> Option<Self> {
if bits & !0x0007 != 0 {
return None;
}
Some(Self {
rights_metadata: bits & 1 != 0,
hidden_marker: bits & (1 << 1) != 0,
authentication: bits & (1 << 2) != 0,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ExtensionEntry {
pub extension_type: u16,
pub critical: bool,
pub data: Vec<u8>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PayloadFlags {
pub has_extensions: bool,
pub has_key_id: bool,
pub tiled: bool,
pub progressive_jpeg: bool,
pub critical_extension: bool,
pub signed: bool,
pub reserved: u16,
}
impl PayloadFlags {
pub const HAS_EXTENSIONS: u16 = 0x0001;
pub const HAS_KEY_ID: u16 = 0x0002;
pub const TILED: u16 = 0x0004;
pub const PROGRESSIVE_JPEG: u16 = 0x0008;
pub const CRITICAL_EXTENSION: u16 = 0x0100;
pub const SIGNED: u16 = 0x0200;
#[must_use]
pub fn to_bits(self) -> u16 {
let mut bits = self.reserved & 0xF0F0;
if self.has_extensions {
bits |= Self::HAS_EXTENSIONS;
}
if self.has_key_id {
bits |= Self::HAS_KEY_ID;
}
if self.tiled {
bits |= Self::TILED;
}
if self.progressive_jpeg {
bits |= Self::PROGRESSIVE_JPEG;
}
if self.critical_extension {
bits |= Self::CRITICAL_EXTENSION;
}
if self.signed {
bits |= Self::SIGNED;
}
bits
}
#[must_use]
pub fn from_bits(bits: u16) -> Self {
Self {
has_extensions: bits & Self::HAS_EXTENSIONS != 0,
has_key_id: bits & Self::HAS_KEY_ID != 0,
tiled: bits & Self::TILED != 0,
progressive_jpeg: bits & Self::PROGRESSIVE_JPEG != 0,
critical_extension: bits & Self::CRITICAL_EXTENSION != 0,
signed: bits & Self::SIGNED != 0,
reserved: bits & 0xF0F0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum ExtensionType {
Timestamp = 0x0001,
CreatorFingerprint = 0x0002,
InstanceId = 0x0003,
LegalNoticeHash = 0x0004,
VersionString = 0x0005,
ProcessingHistory = 0x0006,
Ed25519PublicKey = 0x0010,
Ed25519DetachedSig = 0x0011,
VendorPrefix = 0x00FF,
}
impl ExtensionType {
#[must_use]
pub fn from_u16(v: u16) -> Option<Self> {
match v {
0x0001 => Some(Self::Timestamp),
0x0002 => Some(Self::CreatorFingerprint),
0x0003 => Some(Self::InstanceId),
0x0004 => Some(Self::LegalNoticeHash),
0x0005 => Some(Self::VersionString),
0x0006 => Some(Self::ProcessingHistory),
0x0010 => Some(Self::Ed25519PublicKey),
0x0011 => Some(Self::Ed25519DetachedSig),
0x00FF => Some(Self::VendorPrefix),
_ => None,
}
}
#[must_use]
pub fn is_critical(v: u16) -> bool {
matches!(v, 0x0011)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_auth_algorithm_roundtrip() {
for i in 0..=3u8 {
let algo = AuthAlgorithm::from_byte(i).unwrap();
assert_eq!(algo as u8, i);
}
assert!(AuthAlgorithm::from_byte(4).is_none());
}
#[test]
fn test_protection_channels_roundtrip() {
let channels = ProtectionChannels {
rights_metadata: true,
hidden_marker: true,
authentication: false,
};
let bits = channels.to_bits();
let decoded = ProtectionChannels::from_bits(bits).unwrap();
assert_eq!(channels, decoded);
}
#[test]
fn test_protection_channels_invalid_bits() {
assert!(ProtectionChannels::from_bits(0xFFF8).is_none());
}
#[test]
fn test_payload_flags_roundtrip() {
let flags = PayloadFlags {
has_extensions: true,
has_key_id: false,
tiled: true,
progressive_jpeg: false,
critical_extension: true,
signed: false,
reserved: 0,
};
let bits = flags.to_bits();
let decoded = PayloadFlags::from_bits(bits);
assert_eq!(flags, decoded);
}
#[test]
fn test_extension_type_criticality() {
assert!(ExtensionType::is_critical(0x0011));
assert!(!ExtensionType::is_critical(0x0001));
assert!(!ExtensionType::is_critical(0x0100));
}
}