use crate::core::matter::code::CesrCode;
use crate::core::matter::matter::Matter;
use crate::core::primitives::Tholder;
#[cfg(feature = "alloc")]
#[allow(
unused_imports,
reason = "alloc prelude items; subset used per cfg/feature combination"
)]
use alloc::{borrow::ToOwned, format, string::String, string::ToString, vec, vec::Vec};
use core::ops::Range;
use super::{EventLayout, EventRef, EventSerializer, weight_to_string};
use crate::keri::{ConfigTrait, Identifier, InceptionEvent, InteractionEvent, RotationEvent, Seal};
use crate::serder::error::SerderError;
use crate::serder::primitives::{identifier_to_qb64_string, sn_to_hex, to_qb64_string};
use crate::serder::version::VersionString;
#[derive(Debug, Clone, Copy, Default)]
pub struct DirectJson;
impl EventSerializer for DirectJson {
fn render(
&self,
event: EventRef<'_>,
said_placeholder: &str,
buf: &mut Vec<u8>,
) -> Result<EventLayout, SerderError> {
match event {
EventRef::Inception(e) => render_icp(buf, e, said_placeholder, "icp", None),
EventRef::Rotation(e) => render_rot(buf, e, said_placeholder, "rot"),
EventRef::Interaction(e) => render_ixn(buf, e, said_placeholder),
EventRef::DelegatedInception(e) => {
let delegator = identifier_to_qb64_string(e.delegator());
render_icp(
buf,
e.inception(),
said_placeholder,
"dip",
Some(&delegator),
)
}
EventRef::DelegatedRotation(e) => {
render_rot(buf, e.rotation(), said_placeholder, "drt")
}
}
}
}
fn write_head(
buf: &mut Vec<u8>,
ilk: &str,
placeholder: &str,
) -> Result<(Range<usize>, Range<usize>), SerderError> {
let vs = VersionString::keri_json_v1().to_str()?;
buf.extend_from_slice(b"{\"v\":\"");
let vs_start = buf.len();
buf.extend_from_slice(vs.as_bytes());
let size_start = vs_start
.checked_add(10)
.ok_or(SerderError::InvalidEventLayout("size slot offset overflow"))?;
let size_end = size_start
.checked_add(6)
.ok_or(SerderError::InvalidEventLayout("size slot offset overflow"))?;
buf.extend_from_slice(b"\",\"t\":");
write_str(buf, ilk);
buf.extend_from_slice(b",\"d\":\"");
let d_start = buf.len();
buf.extend_from_slice(placeholder.as_bytes());
let d_end = buf.len();
buf.push(b'"');
Ok((size_start..size_end, d_start..d_end))
}
fn render_icp(
buf: &mut Vec<u8>,
e: &InceptionEvent,
placeholder: &str,
ilk: &str,
delegator: Option<&str>,
) -> Result<EventLayout, SerderError> {
let (size_slot, said_slot) = write_head(buf, ilk, placeholder)?;
let prefix_slot = match e.prefix() {
Identifier::SelfAddressing(_) => {
buf.extend_from_slice(b",\"i\":\"");
let i_start = buf.len();
buf.extend_from_slice(placeholder.as_bytes());
let slot = i_start..buf.len();
buf.push(b'"');
Some(slot)
}
Identifier::Basic(p) => {
buf.extend_from_slice(b",\"i\":");
write_str(buf, &to_qb64_string(p));
None
}
};
buf.extend_from_slice(b",\"s\":");
write_str(buf, &sn_to_hex(e.sn().value()));
buf.extend_from_slice(b",\"kt\":");
write_tholder(buf, e.threshold());
buf.extend_from_slice(b",\"k\":");
write_qb64_array(buf, e.keys());
buf.extend_from_slice(b",\"nt\":");
write_tholder(buf, e.next_threshold());
buf.extend_from_slice(b",\"n\":");
write_qb64_array(buf, e.next_keys());
buf.extend_from_slice(b",\"bt\":");
write_str(buf, &sn_to_hex(u128::from(e.witness_threshold())));
buf.extend_from_slice(b",\"b\":");
write_qb64_array(buf, e.witnesses());
buf.extend_from_slice(b",\"c\":");
write_config_array(buf, e.config());
buf.extend_from_slice(b",\"a\":");
write_seal_array(buf, e.anchors());
if let Some(di) = delegator {
buf.extend_from_slice(b",\"di\":");
write_str(buf, di);
}
buf.push(b'}');
Ok(EventLayout {
size_slot,
said_slot,
prefix_slot,
})
}
fn render_rot(
buf: &mut Vec<u8>,
e: &RotationEvent,
placeholder: &str,
ilk: &str,
) -> Result<EventLayout, SerderError> {
let (size_slot, said_slot) = write_head(buf, ilk, placeholder)?;
buf.extend_from_slice(b",\"i\":");
write_str(buf, &identifier_to_qb64_string(e.prefix()));
buf.extend_from_slice(b",\"s\":");
write_str(buf, &sn_to_hex(e.sn().value()));
buf.extend_from_slice(b",\"p\":");
write_str(buf, &to_qb64_string(e.prior_event_said()));
buf.extend_from_slice(b",\"kt\":");
write_tholder(buf, e.threshold());
buf.extend_from_slice(b",\"k\":");
write_qb64_array(buf, e.keys());
buf.extend_from_slice(b",\"nt\":");
write_tholder(buf, e.next_threshold());
buf.extend_from_slice(b",\"n\":");
write_qb64_array(buf, e.next_keys());
buf.extend_from_slice(b",\"bt\":");
write_str(buf, &sn_to_hex(u128::from(e.witness_threshold())));
buf.extend_from_slice(b",\"br\":");
write_qb64_array(buf, e.witness_removals());
buf.extend_from_slice(b",\"ba\":");
write_qb64_array(buf, e.witness_additions());
buf.extend_from_slice(b",\"a\":");
write_seal_array(buf, e.anchors());
buf.push(b'}');
Ok(EventLayout {
size_slot,
said_slot,
prefix_slot: None,
})
}
fn render_ixn(
buf: &mut Vec<u8>,
e: &InteractionEvent,
placeholder: &str,
) -> Result<EventLayout, SerderError> {
let (size_slot, said_slot) = write_head(buf, "ixn", placeholder)?;
buf.extend_from_slice(b",\"i\":");
write_str(buf, &identifier_to_qb64_string(e.prefix()));
buf.extend_from_slice(b",\"s\":");
write_str(buf, &sn_to_hex(e.sn().value()));
buf.extend_from_slice(b",\"p\":");
write_str(buf, &to_qb64_string(e.prior_event_said()));
buf.extend_from_slice(b",\"a\":");
write_seal_array(buf, e.anchors());
buf.push(b'}');
Ok(EventLayout {
size_slot,
said_slot,
prefix_slot: None,
})
}
const HEX: [u8; 16] = *b"0123456789abcdef";
fn write_str(buf: &mut Vec<u8>, s: &str) {
buf.push(b'"');
for &byte in s.as_bytes() {
match byte {
b'"' => buf.extend_from_slice(b"\\\""),
b'\\' => buf.extend_from_slice(b"\\\\"),
0x08 => buf.extend_from_slice(b"\\b"),
0x09 => buf.extend_from_slice(b"\\t"),
0x0A => buf.extend_from_slice(b"\\n"),
0x0C => buf.extend_from_slice(b"\\f"),
0x0D => buf.extend_from_slice(b"\\r"),
b if b < 0x20 => {
buf.extend_from_slice(b"\\u00");
buf.push(HEX[usize::from(b >> 4)]);
buf.push(HEX[usize::from(b & 0x0F)]);
}
b => buf.push(b),
}
}
buf.push(b'"');
}
fn write_qb64_array<C: CesrCode>(buf: &mut Vec<u8>, matters: &[Matter<'_, C>]) {
buf.push(b'[');
for (idx, m) in matters.iter().enumerate() {
if idx > 0 {
buf.push(b',');
}
write_str(buf, &to_qb64_string(m));
}
buf.push(b']');
}
fn write_tholder(buf: &mut Vec<u8>, tholder: &Tholder) {
match tholder {
Tholder::Simple(n) => write_str(buf, &format!("{n:x}")),
Tholder::Weighted(clauses) => {
if let [single] = clauses.as_slice() {
write_weight_clause(buf, single);
} else {
buf.push(b'[');
for (idx, clause) in clauses.iter().enumerate() {
if idx > 0 {
buf.push(b',');
}
write_weight_clause(buf, clause);
}
buf.push(b']');
}
}
}
}
fn write_weight_clause(buf: &mut Vec<u8>, clause: &[(u64, u64)]) {
buf.push(b'[');
for (idx, (num, den)) in clause.iter().enumerate() {
if idx > 0 {
buf.push(b',');
}
write_str(buf, &weight_to_string(*num, *den));
}
buf.push(b']');
}
fn write_config_array(buf: &mut Vec<u8>, config: &[ConfigTrait]) {
buf.push(b'[');
for (idx, c) in config.iter().enumerate() {
if idx > 0 {
buf.push(b',');
}
write_str(buf, c.code());
}
buf.push(b']');
}
fn write_seal(buf: &mut Vec<u8>, seal: &Seal) {
match seal {
Seal::Digest { d } => {
buf.extend_from_slice(b"{\"d\":");
write_str(buf, &to_qb64_string(d));
buf.push(b'}');
}
Seal::Root { rd } => {
buf.extend_from_slice(b"{\"rd\":");
write_str(buf, &to_qb64_string(rd));
buf.push(b'}');
}
Seal::Source { s, d } => {
buf.extend_from_slice(b"{\"s\":");
write_str(buf, &sn_to_hex(s.value()));
buf.extend_from_slice(b",\"d\":");
write_str(buf, &to_qb64_string(d));
buf.push(b'}');
}
Seal::Event { i, s, d } => {
buf.extend_from_slice(b"{\"i\":");
write_str(buf, &to_qb64_string(i));
buf.extend_from_slice(b",\"s\":");
write_str(buf, &sn_to_hex(s.value()));
buf.extend_from_slice(b",\"d\":");
write_str(buf, &to_qb64_string(d));
buf.push(b'}');
}
Seal::Last { i } => {
buf.extend_from_slice(b"{\"i\":");
write_str(buf, &to_qb64_string(i));
buf.push(b'}');
}
}
}
fn write_seal_array(buf: &mut Vec<u8>, seals: &[Seal]) {
buf.push(b'[');
for (idx, seal) in seals.iter().enumerate() {
if idx > 0 {
buf.push(b',');
}
write_seal(buf, seal);
}
buf.push(b']');
}
#[cfg(test)]
mod tests {
use super::super::{SerdeJson, serialize_with};
use super::*;
use crate::core::primitives::Seqner;
use crate::keri::{DelegatedInceptionEvent, DelegatedRotationEvent, Identifier};
use crate::serder::deserialize::deserialize_inception;
use crate::serder::event_strategies::{
IdSpec, build_icp, build_identifier, build_ixn, build_rot, icp_strategy, ixn_strategy,
prefixer, rot_strategy, saider,
};
use proptest::prelude::*;
fn assert_backends_identical(event: EventRef<'_>) {
let reference = serialize_with(&SerdeJson, event).unwrap();
let direct = serialize_with(&DirectJson, event).unwrap();
assert_eq!(
core::str::from_utf8(reference.as_bytes()).unwrap(),
core::str::from_utf8(direct.as_bytes()).unwrap(),
"direct backend must be byte-identical to the serde_json reference"
);
assert_eq!(
to_qb64_string(reference.said()),
to_qb64_string(direct.said())
);
assert_eq!(reference.size(), direct.size());
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(64))]
#[test]
fn icp_backends_byte_identical(spec in icp_strategy()) {
let event = build_icp(spec);
assert_backends_identical(EventRef::Inception(&event));
}
#[test]
fn rot_backends_byte_identical(spec in rot_strategy()) {
let event = build_rot(spec);
assert_backends_identical(EventRef::Rotation(&event));
}
#[test]
fn ixn_backends_byte_identical(spec in ixn_strategy()) {
let event = build_ixn(spec);
assert_backends_identical(EventRef::Interaction(&event));
}
#[test]
fn dip_backends_byte_identical(
spec in icp_strategy(),
delegator in any::<IdSpec>(),
) {
let dip =
DelegatedInceptionEvent::new(build_icp(spec), build_identifier(delegator));
assert_backends_identical(EventRef::DelegatedInception(&dip));
}
#[test]
fn drt_backends_byte_identical(spec in rot_strategy()) {
let drt = DelegatedRotationEvent::new(build_rot(spec));
assert_backends_identical(EventRef::DelegatedRotation(&drt));
}
#[test]
fn escaper_matches_serde_json_arbitrary_unicode(s in any::<String>()) {
let mut buf = Vec::new();
write_str(&mut buf, &s);
let expected =
serde_json::to_string(&serde_json::Value::String(s.clone())).unwrap();
prop_assert_eq!(core::str::from_utf8(&buf).unwrap(), expected.as_str());
}
#[test]
fn escaper_matches_serde_json(s in ".*") {
let mut buf = Vec::new();
write_str(&mut buf, &s);
let expected =
serde_json::to_string(&serde_json::Value::String(s.clone())).unwrap();
prop_assert_eq!(core::str::from_utf8(&buf).unwrap(), expected.as_str());
}
}
#[test]
fn escaper_covers_every_escape_class() {
let s = "q\" b\\ \u{8}\t\n\u{c}\r \u{0}\u{1f}\u{7f} héllo → 日本";
let mut buf = Vec::new();
write_str(&mut buf, s);
let expected = serde_json::to_string(&serde_json::Value::String(s.to_owned())).unwrap();
assert_eq!(core::str::from_utf8(&buf).unwrap(), expected);
}
#[test]
fn empty_weighted_thresholds_are_byte_identical_across_backends() {
for kt in [
Tholder::Weighted(vec![]),
Tholder::Weighted(vec![vec![]]),
Tholder::Weighted(vec![vec![], vec![]]),
] {
let event = InceptionEvent::new(
Identifier::Basic(prefixer([0; 32])),
Seqner::new(0),
saider([1; 32]),
vec![prefixer([2; 32])],
kt,
vec![saider([3; 32])],
Tholder::Simple(1),
vec![],
0,
vec![],
vec![],
);
assert_backends_identical(EventRef::Inception(&event));
}
}
#[test]
fn direct_render_into_prefilled_buffer_reports_absolute_slots() {
let event = build_ixn(((true, [0; 32]), 1, [1; 32], [2; 32], vec![]));
let placeholder = "#".repeat(44);
let mut buf = b"JUNK".to_vec();
let layout = DirectJson
.render(EventRef::Interaction(&event), &placeholder, &mut buf)
.unwrap();
assert_eq!(&buf[..4], b"JUNK", "render must append, not overwrite");
assert_eq!(&buf[layout.size_slot], b"000000");
assert_eq!(&buf[layout.said_slot], placeholder.as_bytes());
assert!(layout.prefix_slot.is_none(), "ixn is single-SAID");
}
#[test]
fn direct_output_verifies_through_unchanged_read_path() {
let event = InceptionEvent::new(
Identifier::Basic(prefixer([0; 32])),
Seqner::new(0),
saider([1; 32]),
vec![prefixer([2; 32])],
Tholder::Simple(1),
vec![saider([3; 32])],
Tholder::Simple(1),
vec![prefixer([4; 32])],
1,
vec![ConfigTrait::EstOnly],
vec![Seal::Digest { d: saider([5; 32]) }],
);
let direct = serialize_with(&DirectJson, EventRef::Inception(&event)).unwrap();
let parsed = deserialize_inception(direct.as_bytes()).unwrap();
assert_eq!(
to_qb64_string(parsed.said()),
to_qb64_string(direct.said()),
"direct-rendered event must SAID-verify through the strict canonical read path"
);
}
}