use std::collections::BTreeMap;
use base64::Engine;
use serde::{Deserialize, Serialize};
use zeroize::{Zeroize, Zeroizing};
use crate::grant::WrappingKey;
use crate::Result;
pub type PeerMap = BTreeMap<String, WrappingKey>;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ProtectedState {
#[serde(default)]
pub targets: BTreeMap<String, TargetValue>,
#[serde(default)]
pub peers: PeerMap,
#[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
pub aux: serde_json::Value,
}
#[derive(Clone, Default, Serialize, Deserialize, Zeroize)]
#[serde(transparent)]
pub struct TargetValue(#[serde(with = "crate::wire::b64bytes")] pub Vec<u8>);
impl core::fmt::Debug for TargetValue {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "TargetValue(<{} bytes redacted>)", self.0.len())
}
}
impl TargetValue {
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
pub fn from_bytes(bytes: impl Into<Vec<u8>>) -> Self {
Self(bytes.into())
}
}
impl Drop for TargetValue {
fn drop(&mut self) {
self.0.zeroize();
}
}
impl ProtectedState {
pub fn new() -> Self {
Self::default()
}
pub fn target(&self, name: &str) -> Result<&[u8]> {
self.targets
.get(name)
.map(|v| v.as_bytes())
.ok_or_else(|| crate::Error::TargetNotFound(name.to_string()))
}
pub fn put_target(&mut self, name: impl Into<String>, value: impl Into<Vec<u8>>) {
self.targets
.insert(name.into(), TargetValue::from_bytes(value));
}
pub fn remove_target(&mut self, name: &str) -> Option<TargetValue> {
self.targets.remove(name)
}
pub fn to_canonical(&self) -> Result<Zeroizing<Vec<u8>>> {
let mut out = Zeroizing::new(Vec::with_capacity(256));
out.push(b'{');
let mut wrote_field = false;
if !self.aux.is_null() {
write_field_key(&mut out, "aux", &mut wrote_field);
let aux_bytes = crate::canonical::canonicalize_strict(&self.aux)?;
out.extend_from_slice(&aux_bytes);
}
write_field_key(&mut out, "peers", &mut wrote_field);
out.push(b'{');
for (i, (cid_b64, w)) in self.peers.iter().enumerate() {
if i > 0 {
out.push(b',');
}
write_json_string(&mut out, cid_b64);
out.push(b':');
write_base64_string(&mut out, w.as_bytes());
}
out.push(b'}');
write_field_key(&mut out, "targets", &mut wrote_field);
out.push(b'{');
for (i, (path, val)) in self.targets.iter().enumerate() {
if i > 0 {
out.push(b',');
}
write_json_string(&mut out, path);
out.push(b':');
write_base64_string(&mut out, val.as_bytes());
}
out.push(b'}');
out.push(b'}');
Ok(out)
}
pub fn from_canonical(bytes: &[u8]) -> Result<Self> {
serde_json::from_slice(bytes)
.map_err(|_| crate::Error::Encoding("ProtectedState canonical parse"))
}
}
fn write_field_key(out: &mut Vec<u8>, key: &str, wrote_field: &mut bool) {
if *wrote_field {
out.push(b',');
}
*wrote_field = true;
write_json_string(out, key);
out.push(b':');
}
fn write_json_string(out: &mut Vec<u8>, s: &str) {
out.push(b'"');
for byte in s.bytes() {
match byte {
b'"' => out.extend_from_slice(b"\\\""),
b'\\' => out.extend_from_slice(b"\\\\"),
0x08 => out.extend_from_slice(b"\\b"),
0x0c => out.extend_from_slice(b"\\f"),
b'\n' => out.extend_from_slice(b"\\n"),
b'\r' => out.extend_from_slice(b"\\r"),
b'\t' => out.extend_from_slice(b"\\t"),
c if c < 0x20 => {
let s = format!("\\u{:04x}", c);
out.extend_from_slice(s.as_bytes());
}
c => out.push(c),
}
}
out.push(b'"');
}
fn write_base64_string(out: &mut Vec<u8>, bytes: &[u8]) {
let b64 = Zeroizing::new(base64::engine::general_purpose::STANDARD.encode(bytes));
out.push(b'"');
out.extend_from_slice(b64.as_bytes());
out.push(b'"');
}