use crate::{error::CryptoError, Result};
use base64::{engine::general_purpose::STANDARD, Engine};
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PqcMetadata {
pub kem_params: Option<KemParameters>,
pub sig_params: Option<SigParameters>,
pub enc_params: EncParameters,
pub compression_params: Option<CompressionParameters>,
pub custom: HashMap<String, Vec<u8>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct KemParameters {
pub public_key: Vec<u8>,
pub ciphertext: Vec<u8>,
pub params: HashMap<String, Vec<u8>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SigParameters {
pub public_key: Vec<u8>,
pub signature: Vec<u8>,
pub params: HashMap<String, Vec<u8>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct EncParameters {
pub iv: Vec<u8>,
pub tag: Vec<u8>,
pub params: HashMap<String, Vec<u8>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CompressionParameters {
pub algorithm: String,
pub level: u8,
pub original_size: u64,
pub params: HashMap<String, Vec<u8>>,
}
impl PqcMetadata {
#[must_use]
pub fn new() -> Self {
Self {
kem_params: None,
sig_params: None,
enc_params: EncParameters {
iv: Vec::new(),
tag: Vec::new(),
params: HashMap::new(),
},
compression_params: None,
custom: HashMap::new(),
}
}
pub fn validate(&self) -> Result<()> {
if self.enc_params.iv.is_empty() {
return Err(CryptoError::MetadataError(
"Encryption IV cannot be empty".to_string(),
));
}
Ok(())
}
pub fn add_custom(&mut self, key: String, value: Vec<u8>) {
self.custom.insert(key, value);
}
#[must_use]
pub fn get_custom(&self, key: &str) -> Option<&[u8]> {
self.custom.get(key).map(Vec::as_slice)
}
#[must_use]
pub fn to_json_bytes(&self) -> Vec<u8> {
let mut root = Map::new();
let mut enc = Map::new();
enc.insert(
"iv".into(),
Value::String(STANDARD.encode(&self.enc_params.iv)),
);
enc.insert(
"tag".into(),
Value::String(STANDARD.encode(&self.enc_params.tag)),
);
enc.insert("params".into(), bytes_map_to_json(&self.enc_params.params));
root.insert("encryption_params".into(), Value::Object(enc));
if let Some(ref kem) = self.kem_params {
let mut m = Map::new();
m.insert(
"public_key".into(),
Value::String(STANDARD.encode(&kem.public_key)),
);
m.insert(
"ciphertext".into(),
Value::String(STANDARD.encode(&kem.ciphertext)),
);
m.insert("params".into(), bytes_map_to_json(&kem.params));
root.insert("kem_params".into(), Value::Object(m));
}
if let Some(ref sig) = self.sig_params {
let mut m = Map::new();
m.insert(
"public_key".into(),
Value::String(STANDARD.encode(&sig.public_key)),
);
m.insert(
"signature".into(),
Value::String(STANDARD.encode(&sig.signature)),
);
m.insert("params".into(), bytes_map_to_json(&sig.params));
root.insert("signature_params".into(), Value::Object(m));
}
if let Some(ref comp) = self.compression_params {
let mut m = Map::new();
m.insert("algorithm".into(), Value::String(comp.algorithm.clone()));
m.insert("level".into(), Value::Number(comp.level.into()));
m.insert(
"original_size".into(),
Value::Number(comp.original_size.into()),
);
m.insert("params".into(), bytes_map_to_json(&comp.params));
root.insert("compression_params".into(), Value::Object(m));
}
if !self.custom.is_empty() {
root.insert("custom".into(), bytes_map_to_json(&self.custom));
}
serde_json::to_vec(&Value::Object(root)).unwrap_or_default()
}
pub fn from_json_bytes(bytes: &[u8]) -> Result<Self> {
let root: Value = serde_json::from_slice(bytes)
.map_err(|e| CryptoError::MetadataError(format!("Invalid metadata JSON: {e}")))?;
let obj = root
.as_object()
.ok_or_else(|| CryptoError::MetadataError("Metadata must be a JSON object".into()))?;
let enc_obj = obj
.get("encryption_params")
.and_then(Value::as_object)
.ok_or_else(|| CryptoError::MetadataError("Missing encryption_params".into()))?;
let enc_params = EncParameters {
iv: decode_field(enc_obj, "iv")?,
tag: decode_field(enc_obj, "tag")?,
params: json_to_bytes_map(enc_obj.get("params"))?,
};
let kem_params = match obj.get("kem_params").and_then(Value::as_object) {
Some(m) => Some(KemParameters {
public_key: decode_field(m, "public_key")?,
ciphertext: decode_field(m, "ciphertext")?,
params: json_to_bytes_map(m.get("params"))?,
}),
None => None,
};
let sig_params = match obj.get("signature_params").and_then(Value::as_object) {
Some(m) => Some(SigParameters {
public_key: decode_field(m, "public_key")?,
signature: decode_field(m, "signature")?,
params: json_to_bytes_map(m.get("params"))?,
}),
None => None,
};
let compression_params = match obj.get("compression_params").and_then(Value::as_object) {
Some(m) => Some(CompressionParameters {
algorithm: m
.get("algorithm")
.and_then(Value::as_str)
.unwrap_or_default()
.to_string(),
level: m
.get("level")
.and_then(Value::as_u64)
.and_then(|n| u8::try_from(n).ok())
.unwrap_or(0),
original_size: m.get("original_size").and_then(Value::as_u64).unwrap_or(0),
params: json_to_bytes_map(m.get("params"))?,
}),
None => None,
};
Ok(Self {
kem_params,
sig_params,
enc_params,
compression_params,
custom: json_to_bytes_map(obj.get("custom"))?,
})
}
}
fn bytes_map_to_json(map: &HashMap<String, Vec<u8>>) -> Value {
let mut out = Map::new();
for (k, v) in map {
out.insert(k.clone(), Value::String(STANDARD.encode(v)));
}
Value::Object(out)
}
fn json_to_bytes_map(value: Option<&Value>) -> Result<HashMap<String, Vec<u8>>> {
let mut out = HashMap::new();
if let Some(Value::Object(m)) = value {
for (k, v) in m {
let s = v.as_str().ok_or_else(|| {
CryptoError::MetadataError(format!("Param '{k}' is not a string"))
})?;
let decoded = STANDARD
.decode(s)
.map_err(|e| CryptoError::MetadataError(format!("Param '{k}' base64: {e}")))?;
out.insert(k.clone(), decoded);
}
}
Ok(out)
}
fn decode_field(obj: &Map<String, Value>, key: &str) -> Result<Vec<u8>> {
let s = obj
.get(key)
.and_then(Value::as_str)
.ok_or_else(|| CryptoError::MetadataError(format!("Missing metadata field '{key}'")))?;
STANDARD
.decode(s)
.map_err(|e| CryptoError::MetadataError(format!("Field '{key}' base64: {e}")))
}
impl Default for PqcMetadata {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_metadata_validation() {
let mut metadata = PqcMetadata::new();
assert!(metadata.validate().is_err());
metadata.enc_params.iv = vec![1; 12];
assert!(metadata.validate().is_ok());
}
#[test]
fn test_custom_parameters() {
let mut metadata = PqcMetadata::new();
metadata.add_custom("test".to_string(), vec![1, 2, 3]);
assert_eq!(metadata.get_custom("test"), Some(&[1, 2, 3][..]));
assert_eq!(metadata.get_custom("missing"), None);
}
}