myc

Self-custodial remote signer for Radroots apps
git clone https://radroots.dev/git/myc.git
Log | Files | Refs | README | LICENSE

nip46_admission.rs (30733B)


      1 //! Allocation-bounded structural admission for encrypted NIP-46 events and plaintext requests.
      2 
      3 use core::fmt;
      4 use std::error::Error;
      5 
      6 use radroots_nostr_connect::{
      7     message::{
      8         REQUEST_ID_MAX_BYTES, REQUEST_PARAM_COUNT_MAX, REQUEST_PARAM_MAX_BYTES,
      9         REQUEST_PARAMS_MAX_BYTES,
     10     },
     11     method::METHOD_MAX_BYTES,
     12 };
     13 use serde::Deserialize;
     14 use serde::de::{self, DeserializeSeed, IgnoredAny, SeqAccess, Visitor};
     15 use serde_json::value::RawValue;
     16 
     17 use crate::MycConfigDocumentV1;
     18 
     19 /// Maximum encoded Nostr event identifier length admitted before event parsing.
     20 pub const MYC_NIP46_EVENT_ID_MAX_BYTES: usize = 64;
     21 
     22 /// Maximum encoded Nostr public-key length admitted before event parsing.
     23 pub const MYC_NIP46_PUBLIC_KEY_MAX_BYTES: usize = 64;
     24 
     25 /// Maximum encoded Nostr signature length admitted before event parsing.
     26 pub const MYC_NIP46_SIGNATURE_MAX_BYTES: usize = 128;
     27 
     28 const TAG_COUNT_SENTINEL: &str = "myc-tag-count-limit";
     29 const TAG_ELEMENT_COUNT_SENTINEL: &str = "myc-tag-element-count-limit";
     30 const TAG_ELEMENT_BYTES_SENTINEL: &str = "myc-tag-element-bytes-limit";
     31 const TAG_TOTAL_BYTES_SENTINEL: &str = "myc-tag-total-bytes-limit";
     32 const PARAM_COUNT_SENTINEL: &str = "myc-param-count-limit";
     33 const PARAM_BYTES_SENTINEL: &str = "myc-param-bytes-limit";
     34 const PARAM_TOTAL_BYTES_SENTINEL: &str = "myc-param-total-bytes-limit";
     35 
     36 /// Immutable limits projected from one admitted Myc configuration document.
     37 #[derive(Clone, Copy, PartialEq, Eq)]
     38 pub struct MycNip46AdmissionLimits {
     39     event_wire_bytes: usize,
     40     event_content_bytes: usize,
     41     event_tag_count: usize,
     42     event_tag_total_elements: usize,
     43     event_tag_element_bytes: usize,
     44     event_tag_total_bytes: usize,
     45     decrypted_plaintext_bytes: usize,
     46 }
     47 
     48 impl MycNip46AdmissionLimits {
     49     /// Projects the exact event limits from a validated immutable configuration.
     50     pub fn from_config(
     51         configuration: &MycConfigDocumentV1,
     52     ) -> Result<Self, MycNip46AdmissionError> {
     53         Ok(Self {
     54             event_wire_bytes: config_limit(configuration, "/resource_limits/events/wire_bytes")?,
     55             event_content_bytes: config_limit(
     56                 configuration,
     57                 "/resource_limits/events/content_bytes",
     58             )?,
     59             event_tag_count: config_limit(configuration, "/resource_limits/events/tag_count")?,
     60             event_tag_total_elements: config_limit(
     61                 configuration,
     62                 "/resource_limits/events/tag_total_elements",
     63             )?,
     64             event_tag_element_bytes: config_limit(
     65                 configuration,
     66                 "/resource_limits/events/tag_element_bytes",
     67             )?,
     68             event_tag_total_bytes: config_limit(
     69                 configuration,
     70                 "/resource_limits/events/tag_total_bytes",
     71             )?,
     72             decrypted_plaintext_bytes: config_limit(
     73                 configuration,
     74                 "/resource_limits/events/decrypted_plaintext_bytes",
     75             )?,
     76         })
     77     }
     78 
     79     /// Returns the original event-wire byte cap.
     80     #[must_use]
     81     pub const fn event_wire_bytes(self) -> usize {
     82         self.event_wire_bytes
     83     }
     84 
     85     /// Returns the decoded event-content byte cap.
     86     #[must_use]
     87     pub const fn event_content_bytes(self) -> usize {
     88         self.event_content_bytes
     89     }
     90 
     91     /// Returns the outer event-tag count cap.
     92     #[must_use]
     93     pub const fn event_tag_count(self) -> usize {
     94         self.event_tag_count
     95     }
     96 
     97     /// Returns the aggregate tag-element count cap.
     98     #[must_use]
     99     pub const fn event_tag_total_elements(self) -> usize {
    100         self.event_tag_total_elements
    101     }
    102 
    103     /// Returns the decoded byte cap for one tag element.
    104     #[must_use]
    105     pub const fn event_tag_element_bytes(self) -> usize {
    106         self.event_tag_element_bytes
    107     }
    108 
    109     /// Returns the aggregate decoded tag-element byte cap.
    110     #[must_use]
    111     pub const fn event_tag_total_bytes(self) -> usize {
    112         self.event_tag_total_bytes
    113     }
    114 
    115     /// Returns the decrypted NIP-46 plaintext byte cap.
    116     #[must_use]
    117     pub const fn decrypted_plaintext_bytes(self) -> usize {
    118         self.decrypted_plaintext_bytes
    119     }
    120 }
    121 
    122 impl fmt::Debug for MycNip46AdmissionLimits {
    123     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
    124         formatter
    125             .debug_struct("MycNip46AdmissionLimits")
    126             .field("event_wire_bytes", &self.event_wire_bytes)
    127             .field("event_content_bytes", &self.event_content_bytes)
    128             .field("event_tag_count", &self.event_tag_count)
    129             .field("event_tag_total_elements", &self.event_tag_total_elements)
    130             .field("event_tag_element_bytes", &self.event_tag_element_bytes)
    131             .field("event_tag_total_bytes", &self.event_tag_total_bytes)
    132             .field("decrypted_plaintext_bytes", &self.decrypted_plaintext_bytes)
    133             .finish()
    134     }
    135 }
    136 
    137 /// Stable source-free classification for NIP-46 resource-admission failures.
    138 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
    139 pub enum MycNip46AdmissionErrorKind {
    140     InvalidLimits,
    141     EmptyEvent,
    142     EventTooLarge,
    143     InvalidEventUtf8,
    144     MalformedEvent,
    145     EventIdentifierTooLarge,
    146     EventContentTooLarge,
    147     TooManyTags,
    148     TooManyTagElements,
    149     TagElementTooLarge,
    150     TagsTooLarge,
    151     EmptyPlaintext,
    152     PlaintextTooLarge,
    153     InvalidPlaintextUtf8,
    154     MalformedRequest,
    155     RequestIdentifierTooLarge,
    156     RequestMethodTooLarge,
    157     TooManyRequestParameters,
    158     RequestParameterTooLarge,
    159     RequestParametersTooLarge,
    160 }
    161 
    162 impl MycNip46AdmissionErrorKind {
    163     const fn message(self) -> &'static str {
    164         match self {
    165             Self::InvalidLimits => "NIP-46 admission limits are invalid",
    166             Self::EmptyEvent => "NIP-46 event bytes are empty",
    167             Self::EventTooLarge => "NIP-46 event exceeds its wire limit",
    168             Self::InvalidEventUtf8 => "NIP-46 event is not valid UTF-8",
    169             Self::MalformedEvent => "NIP-46 event structure is invalid",
    170             Self::EventIdentifierTooLarge => "NIP-46 event identifier exceeds its limit",
    171             Self::EventContentTooLarge => "NIP-46 event content exceeds its limit",
    172             Self::TooManyTags => "NIP-46 event tag count exceeds its limit",
    173             Self::TooManyTagElements => "NIP-46 event tag elements exceed their count limit",
    174             Self::TagElementTooLarge => "NIP-46 event tag element exceeds its byte limit",
    175             Self::TagsTooLarge => "NIP-46 event tags exceed their aggregate byte limit",
    176             Self::EmptyPlaintext => "NIP-46 request plaintext is empty",
    177             Self::PlaintextTooLarge => "NIP-46 request plaintext exceeds its limit",
    178             Self::InvalidPlaintextUtf8 => "NIP-46 request plaintext is not valid UTF-8",
    179             Self::MalformedRequest => "NIP-46 request structure is invalid",
    180             Self::RequestIdentifierTooLarge => "NIP-46 request identifier exceeds its limit",
    181             Self::RequestMethodTooLarge => "NIP-46 request method exceeds its limit",
    182             Self::TooManyRequestParameters => "NIP-46 request parameter count exceeds its limit",
    183             Self::RequestParameterTooLarge => "NIP-46 request parameter exceeds its byte limit",
    184             Self::RequestParametersTooLarge => {
    185                 "NIP-46 request parameters exceed their aggregate byte limit"
    186             }
    187         }
    188     }
    189 }
    190 
    191 /// One redacted NIP-46 resource-admission failure.
    192 #[derive(Clone, Copy, PartialEq, Eq)]
    193 pub struct MycNip46AdmissionError {
    194     kind: MycNip46AdmissionErrorKind,
    195 }
    196 
    197 impl MycNip46AdmissionError {
    198     const fn new(kind: MycNip46AdmissionErrorKind) -> Self {
    199         Self { kind }
    200     }
    201 
    202     /// Returns the stable failure classification.
    203     #[must_use]
    204     pub const fn kind(self) -> MycNip46AdmissionErrorKind {
    205         self.kind
    206     }
    207 }
    208 
    209 impl fmt::Debug for MycNip46AdmissionError {
    210     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
    211         formatter
    212             .debug_struct("MycNip46AdmissionError")
    213             .field("kind", &self.kind)
    214             .finish()
    215     }
    216 }
    217 
    218 impl fmt::Display for MycNip46AdmissionError {
    219     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
    220         formatter.write_str(self.kind.message())
    221     }
    222 }
    223 
    224 impl Error for MycNip46AdmissionError {}
    225 
    226 /// One structurally admitted encrypted NIP-46 event.
    227 pub struct MycBoundedNip46Event {
    228     original: Box<[u8]>,
    229     encrypted_content: Box<str>,
    230     tag_count: usize,
    231     tag_element_count: usize,
    232     tag_bytes: usize,
    233 }
    234 
    235 impl MycBoundedNip46Event {
    236     /// Returns the exact original event bytes retained for later verification.
    237     #[must_use]
    238     pub fn original_bytes(&self) -> &[u8] {
    239         &self.original
    240     }
    241 
    242     /// Returns the bounded ciphertext without decrypting it.
    243     #[must_use]
    244     pub fn encrypted_content(&self) -> &str {
    245         &self.encrypted_content
    246     }
    247 
    248     /// Returns the number of admitted tags.
    249     #[must_use]
    250     pub const fn tag_count(&self) -> usize {
    251         self.tag_count
    252     }
    253 
    254     /// Returns the aggregate number of admitted tag elements.
    255     #[must_use]
    256     pub const fn tag_element_count(&self) -> usize {
    257         self.tag_element_count
    258     }
    259 
    260     /// Returns the aggregate decoded UTF-8 bytes in all tag elements.
    261     #[must_use]
    262     pub const fn tag_bytes(&self) -> usize {
    263         self.tag_bytes
    264     }
    265 
    266     pub(crate) fn into_verification_parts(self) -> (Box<[u8]>, Box<str>) {
    267         (self.original, self.encrypted_content)
    268     }
    269 }
    270 
    271 impl fmt::Debug for MycBoundedNip46Event {
    272     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
    273         formatter
    274             .debug_struct("MycBoundedNip46Event")
    275             .field("wire_bytes", &self.original.len())
    276             .field("content_bytes", &self.encrypted_content.len())
    277             .field("tag_count", &self.tag_count)
    278             .field("tag_element_count", &self.tag_element_count)
    279             .field("tag_bytes", &self.tag_bytes)
    280             .finish()
    281     }
    282 }
    283 
    284 /// One structurally admitted decrypted NIP-46 request.
    285 pub struct MycBoundedNip46Request {
    286     plaintext: Box<[u8]>,
    287     request_id_bytes: usize,
    288     method_bytes: usize,
    289     parameter_count: usize,
    290     parameter_bytes: usize,
    291 }
    292 
    293 impl MycBoundedNip46Request {
    294     /// Returns the admitted plaintext byte count without exposing its content.
    295     #[must_use]
    296     pub fn plaintext_bytes(&self) -> usize {
    297         self.plaintext.len()
    298     }
    299 
    300     /// Returns the decoded request-identifier byte count.
    301     #[must_use]
    302     pub const fn request_id_bytes(&self) -> usize {
    303         self.request_id_bytes
    304     }
    305 
    306     /// Returns the decoded request-method byte count.
    307     #[must_use]
    308     pub const fn method_bytes(&self) -> usize {
    309         self.method_bytes
    310     }
    311 
    312     /// Returns the admitted request-parameter count.
    313     #[must_use]
    314     pub const fn parameter_count(&self) -> usize {
    315         self.parameter_count
    316     }
    317 
    318     /// Returns aggregate decoded UTF-8 bytes in request parameters.
    319     #[must_use]
    320     pub const fn parameter_bytes(&self) -> usize {
    321         self.parameter_bytes
    322     }
    323 
    324     pub(crate) fn into_verification_parts(self) -> (Box<[u8]>, usize, usize) {
    325         (self.plaintext, self.parameter_count, self.parameter_bytes)
    326     }
    327 }
    328 
    329 impl fmt::Debug for MycBoundedNip46Request {
    330     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
    331         formatter
    332             .debug_struct("MycBoundedNip46Request")
    333             .field("plaintext_bytes", &self.plaintext.len())
    334             .field("request_id_bytes", &self.request_id_bytes)
    335             .field("method_bytes", &self.method_bytes)
    336             .field("parameter_count", &self.parameter_count)
    337             .field("parameter_bytes", &self.parameter_bytes)
    338             .finish()
    339     }
    340 }
    341 
    342 /// Bounds and structurally admits original encrypted event bytes before decryption.
    343 pub fn admit_myc_nip46_event(
    344     limits: MycNip46AdmissionLimits,
    345     original: &[u8],
    346 ) -> Result<MycBoundedNip46Event, MycNip46AdmissionError> {
    347     if original.is_empty() {
    348         return Err(failure(MycNip46AdmissionErrorKind::EmptyEvent));
    349     }
    350     if original.len() > limits.event_wire_bytes {
    351         return Err(failure(MycNip46AdmissionErrorKind::EventTooLarge));
    352     }
    353     let source = std::str::from_utf8(original)
    354         .map_err(|_| failure(MycNip46AdmissionErrorKind::InvalidEventUtf8))?;
    355     let raw: RawEvent<'_> = parse_exact(source, MycNip46AdmissionErrorKind::MalformedEvent)?;
    356 
    357     validate_bounded_string(
    358         raw.id,
    359         MYC_NIP46_EVENT_ID_MAX_BYTES,
    360         MycNip46AdmissionErrorKind::EventIdentifierTooLarge,
    361         MycNip46AdmissionErrorKind::MalformedEvent,
    362     )?;
    363     validate_bounded_string(
    364         raw.pubkey,
    365         MYC_NIP46_PUBLIC_KEY_MAX_BYTES,
    366         MycNip46AdmissionErrorKind::EventIdentifierTooLarge,
    367         MycNip46AdmissionErrorKind::MalformedEvent,
    368     )?;
    369     validate_bounded_string(
    370         raw.sig,
    371         MYC_NIP46_SIGNATURE_MAX_BYTES,
    372         MycNip46AdmissionErrorKind::EventIdentifierTooLarge,
    373         MycNip46AdmissionErrorKind::MalformedEvent,
    374     )?;
    375     parse_scalar::<u64>(raw.created_at, MycNip46AdmissionErrorKind::MalformedEvent)?;
    376     parse_scalar::<u64>(raw.kind, MycNip46AdmissionErrorKind::MalformedEvent)?;
    377     let encrypted_content = decode_bounded_string(
    378         raw.content,
    379         limits.event_content_bytes,
    380         MycNip46AdmissionErrorKind::EventContentTooLarge,
    381         MycNip46AdmissionErrorKind::MalformedEvent,
    382     )?;
    383     let tags = measure_tags(raw.tags, limits)?;
    384 
    385     Ok(MycBoundedNip46Event {
    386         original: original.into(),
    387         encrypted_content: encrypted_content.into_boxed_str(),
    388         tag_count: tags.count,
    389         tag_element_count: tags.elements,
    390         tag_bytes: tags.bytes,
    391     })
    392 }
    393 
    394 /// Bounds and structurally admits decrypted request bytes before typed decoding.
    395 pub fn admit_myc_nip46_request(
    396     limits: MycNip46AdmissionLimits,
    397     plaintext: &[u8],
    398 ) -> Result<MycBoundedNip46Request, MycNip46AdmissionError> {
    399     if plaintext.is_empty() {
    400         return Err(failure(MycNip46AdmissionErrorKind::EmptyPlaintext));
    401     }
    402     if plaintext.len() > limits.decrypted_plaintext_bytes {
    403         return Err(failure(MycNip46AdmissionErrorKind::PlaintextTooLarge));
    404     }
    405     let source = std::str::from_utf8(plaintext)
    406         .map_err(|_| failure(MycNip46AdmissionErrorKind::InvalidPlaintextUtf8))?;
    407     let raw: RawRequest<'_> = parse_exact(source, MycNip46AdmissionErrorKind::MalformedRequest)?;
    408     let request_id_bytes = validate_bounded_string(
    409         raw.id,
    410         REQUEST_ID_MAX_BYTES,
    411         MycNip46AdmissionErrorKind::RequestIdentifierTooLarge,
    412         MycNip46AdmissionErrorKind::MalformedRequest,
    413     )?;
    414     let method_bytes = validate_bounded_string(
    415         raw.method,
    416         METHOD_MAX_BYTES,
    417         MycNip46AdmissionErrorKind::RequestMethodTooLarge,
    418         MycNip46AdmissionErrorKind::MalformedRequest,
    419     )?;
    420     let parameters = measure_parameters(raw.params)?;
    421 
    422     Ok(MycBoundedNip46Request {
    423         plaintext: plaintext.into(),
    424         request_id_bytes,
    425         method_bytes,
    426         parameter_count: parameters.count,
    427         parameter_bytes: parameters.bytes,
    428     })
    429 }
    430 
    431 #[derive(Deserialize)]
    432 #[serde(deny_unknown_fields)]
    433 struct RawEvent<'a> {
    434     #[serde(borrow)]
    435     id: &'a RawValue,
    436     #[serde(borrow)]
    437     pubkey: &'a RawValue,
    438     #[serde(borrow)]
    439     created_at: &'a RawValue,
    440     #[serde(borrow)]
    441     kind: &'a RawValue,
    442     #[serde(borrow)]
    443     tags: &'a RawValue,
    444     #[serde(borrow)]
    445     content: &'a RawValue,
    446     #[serde(borrow)]
    447     sig: &'a RawValue,
    448 }
    449 
    450 #[derive(Deserialize)]
    451 #[serde(deny_unknown_fields)]
    452 struct RawRequest<'a> {
    453     #[serde(borrow)]
    454     id: &'a RawValue,
    455     #[serde(borrow)]
    456     method: &'a RawValue,
    457     #[serde(borrow)]
    458     params: &'a RawValue,
    459 }
    460 
    461 #[derive(Clone, Copy)]
    462 struct Measurement {
    463     count: usize,
    464     elements: usize,
    465     bytes: usize,
    466 }
    467 
    468 fn config_limit(
    469     configuration: &MycConfigDocumentV1,
    470     pointer: &str,
    471 ) -> Result<usize, MycNip46AdmissionError> {
    472     configuration
    473         .normalized()
    474         .pointer(pointer)
    475         .and_then(serde_json::Value::as_u64)
    476         .and_then(|value| usize::try_from(value).ok())
    477         .filter(|value| *value > 0)
    478         .ok_or_else(|| failure(MycNip46AdmissionErrorKind::InvalidLimits))
    479 }
    480 
    481 fn parse_exact<'a, T>(
    482     source: &'a str,
    483     malformed: MycNip46AdmissionErrorKind,
    484 ) -> Result<T, MycNip46AdmissionError>
    485 where
    486     T: Deserialize<'a>,
    487 {
    488     let mut deserializer = serde_json::Deserializer::from_str(source);
    489     let value = T::deserialize(&mut deserializer).map_err(|_| failure(malformed))?;
    490     deserializer.end().map_err(|_| failure(malformed))?;
    491     Ok(value)
    492 }
    493 
    494 fn parse_scalar<T>(
    495     raw: &RawValue,
    496     malformed: MycNip46AdmissionErrorKind,
    497 ) -> Result<T, MycNip46AdmissionError>
    498 where
    499     T: serde::de::DeserializeOwned,
    500 {
    501     serde_json::from_str(raw.get()).map_err(|_| failure(malformed))
    502 }
    503 
    504 fn validate_bounded_string(
    505     raw: &RawValue,
    506     maximum: usize,
    507     too_large: MycNip46AdmissionErrorKind,
    508     malformed: MycNip46AdmissionErrorKind,
    509 ) -> Result<usize, MycNip46AdmissionError> {
    510     let length = decoded_json_string_utf8_bytes(raw.get()).ok_or_else(|| failure(malformed))?;
    511     if length > maximum {
    512         return Err(failure(too_large));
    513     }
    514     Ok(length)
    515 }
    516 
    517 fn decode_bounded_string(
    518     raw: &RawValue,
    519     maximum: usize,
    520     too_large: MycNip46AdmissionErrorKind,
    521     malformed: MycNip46AdmissionErrorKind,
    522 ) -> Result<String, MycNip46AdmissionError> {
    523     validate_bounded_string(raw, maximum, too_large, malformed)?;
    524     serde_json::from_str(raw.get()).map_err(|_| failure(malformed))
    525 }
    526 
    527 fn decoded_json_string_utf8_bytes(raw: &str) -> Option<usize> {
    528     let bytes = raw.as_bytes();
    529     if bytes.len() < 2 || bytes.first() != Some(&b'"') || bytes.last() != Some(&b'"') {
    530         return None;
    531     }
    532     let end = bytes.len() - 1;
    533     let mut index = 1;
    534     let mut length = 0usize;
    535     while index < end {
    536         let byte = bytes[index];
    537         if byte == b'\\' {
    538             index = index.checked_add(1)?;
    539             let escaped = *bytes.get(index)?;
    540             match escaped {
    541                 b'"' | b'\\' | b'/' | b'b' | b'f' | b'n' | b'r' | b't' => {
    542                     length = length.checked_add(1)?;
    543                     index = index.checked_add(1)?;
    544                 }
    545                 b'u' => {
    546                     let first = parse_hex_u16(bytes.get(index + 1..index + 5)?)?;
    547                     index = index.checked_add(5)?;
    548                     let scalar = if (0xd800..=0xdbff).contains(&first) {
    549                         if bytes.get(index..index + 2)? != b"\\u" {
    550                             return None;
    551                         }
    552                         let second = parse_hex_u16(bytes.get(index + 2..index + 6)?)?;
    553                         if !(0xdc00..=0xdfff).contains(&second) {
    554                             return None;
    555                         }
    556                         index = index.checked_add(6)?;
    557                         0x1_0000
    558                             + ((u32::from(first) - 0xd800) << 10)
    559                             + (u32::from(second) - 0xdc00)
    560                     } else if (0xdc00..=0xdfff).contains(&first) {
    561                         return None;
    562                     } else {
    563                         u32::from(first)
    564                     };
    565                     length = length.checked_add(char::from_u32(scalar)?.len_utf8())?;
    566                 }
    567                 _ => return None,
    568             }
    569         } else if byte < 0x80 {
    570             if byte < 0x20 || byte == b'"' {
    571                 return None;
    572             }
    573             length = length.checked_add(1)?;
    574             index = index.checked_add(1)?;
    575         } else {
    576             let character = raw.get(index..end)?.chars().next()?;
    577             let width = character.len_utf8();
    578             length = length.checked_add(width)?;
    579             index = index.checked_add(width)?;
    580         }
    581     }
    582     (index == end).then_some(length)
    583 }
    584 
    585 fn parse_hex_u16(bytes: &[u8]) -> Option<u16> {
    586     if bytes.len() != 4 {
    587         return None;
    588     }
    589     bytes.iter().try_fold(0u16, |value, byte| {
    590         let digit = match byte {
    591             b'0'..=b'9' => u16::from(byte - b'0'),
    592             b'a'..=b'f' => u16::from(byte - b'a') + 10,
    593             b'A'..=b'F' => u16::from(byte - b'A') + 10,
    594             _ => return None,
    595         };
    596         value.checked_mul(16)?.checked_add(digit)
    597     })
    598 }
    599 
    600 fn measure_tags(
    601     raw: &RawValue,
    602     limits: MycNip46AdmissionLimits,
    603 ) -> Result<Measurement, MycNip46AdmissionError> {
    604     let mut deserializer = serde_json::Deserializer::from_str(raw.get());
    605     let result = TagsSeed { limits }.deserialize(&mut deserializer);
    606     let measurement = result.map_err(classify_tag_error)?;
    607     deserializer
    608         .end()
    609         .map_err(|_| failure(MycNip46AdmissionErrorKind::MalformedEvent))?;
    610     Ok(measurement)
    611 }
    612 
    613 struct TagsSeed {
    614     limits: MycNip46AdmissionLimits,
    615 }
    616 
    617 impl<'de> DeserializeSeed<'de> for TagsSeed {
    618     type Value = Measurement;
    619 
    620     fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
    621     where
    622         D: serde::Deserializer<'de>,
    623     {
    624         deserializer.deserialize_seq(TagsVisitor {
    625             limits: self.limits,
    626         })
    627     }
    628 }
    629 
    630 struct TagsVisitor {
    631     limits: MycNip46AdmissionLimits,
    632 }
    633 
    634 impl<'de> Visitor<'de> for TagsVisitor {
    635     type Value = Measurement;
    636 
    637     fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
    638         formatter.write_str("a bounded array of Nostr tags")
    639     }
    640 
    641     fn visit_seq<A>(self, mut sequence: A) -> Result<Self::Value, A::Error>
    642     where
    643         A: SeqAccess<'de>,
    644     {
    645         let mut result = Measurement {
    646             count: 0,
    647             elements: 0,
    648             bytes: 0,
    649         };
    650         while result.count < self.limits.event_tag_count {
    651             let remaining_elements = self
    652                 .limits
    653                 .event_tag_total_elements
    654                 .checked_sub(result.elements)
    655                 .ok_or_else(|| de::Error::custom(TAG_ELEMENT_COUNT_SENTINEL))?;
    656             let Some(tag) = sequence.next_element_seed(TagSeed {
    657                 maximum_elements: remaining_elements,
    658                 maximum_element_bytes: self.limits.event_tag_element_bytes,
    659             })?
    660             else {
    661                 return Ok(result);
    662             };
    663             result.count += 1;
    664             result.elements = result
    665                 .elements
    666                 .checked_add(tag.elements)
    667                 .ok_or_else(|| de::Error::custom(TAG_ELEMENT_COUNT_SENTINEL))?;
    668             result.bytes = result
    669                 .bytes
    670                 .checked_add(tag.bytes)
    671                 .ok_or_else(|| de::Error::custom(TAG_TOTAL_BYTES_SENTINEL))?;
    672             if result.bytes > self.limits.event_tag_total_bytes {
    673                 return Err(de::Error::custom(TAG_TOTAL_BYTES_SENTINEL));
    674             }
    675         }
    676         if sequence.next_element::<IgnoredAny>()?.is_some() {
    677             return Err(de::Error::custom(TAG_COUNT_SENTINEL));
    678         }
    679         Ok(result)
    680     }
    681 }
    682 
    683 struct TagSeed {
    684     maximum_elements: usize,
    685     maximum_element_bytes: usize,
    686 }
    687 
    688 impl<'de> DeserializeSeed<'de> for TagSeed {
    689     type Value = Measurement;
    690 
    691     fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
    692     where
    693         D: serde::Deserializer<'de>,
    694     {
    695         deserializer.deserialize_seq(TagVisitor {
    696             maximum_elements: self.maximum_elements,
    697             maximum_element_bytes: self.maximum_element_bytes,
    698         })
    699     }
    700 }
    701 
    702 struct TagVisitor {
    703     maximum_elements: usize,
    704     maximum_element_bytes: usize,
    705 }
    706 
    707 impl<'de> Visitor<'de> for TagVisitor {
    708     type Value = Measurement;
    709 
    710     fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
    711         formatter.write_str("a bounded Nostr tag")
    712     }
    713 
    714     fn visit_seq<A>(self, mut sequence: A) -> Result<Self::Value, A::Error>
    715     where
    716         A: SeqAccess<'de>,
    717     {
    718         let mut result = Measurement {
    719             count: 1,
    720             elements: 0,
    721             bytes: 0,
    722         };
    723         while result.elements < self.maximum_elements {
    724             let Some(length) = sequence.next_element_seed(StringLengthSeed {
    725                 maximum: self.maximum_element_bytes,
    726                 sentinel: TAG_ELEMENT_BYTES_SENTINEL,
    727             })?
    728             else {
    729                 return Ok(result);
    730             };
    731             result.elements += 1;
    732             result.bytes = result
    733                 .bytes
    734                 .checked_add(length)
    735                 .ok_or_else(|| de::Error::custom(TAG_TOTAL_BYTES_SENTINEL))?;
    736         }
    737         if sequence.next_element::<IgnoredAny>()?.is_some() {
    738             return Err(de::Error::custom(TAG_ELEMENT_COUNT_SENTINEL));
    739         }
    740         Ok(result)
    741     }
    742 }
    743 
    744 fn measure_parameters(raw: &RawValue) -> Result<Measurement, MycNip46AdmissionError> {
    745     let mut deserializer = serde_json::Deserializer::from_str(raw.get());
    746     let result = StringSequenceSeed {
    747         maximum_count: REQUEST_PARAM_COUNT_MAX,
    748         maximum_element_bytes: REQUEST_PARAM_MAX_BYTES,
    749         maximum_total_bytes: REQUEST_PARAMS_MAX_BYTES,
    750         count_sentinel: PARAM_COUNT_SENTINEL,
    751         element_sentinel: PARAM_BYTES_SENTINEL,
    752         total_sentinel: PARAM_TOTAL_BYTES_SENTINEL,
    753     }
    754     .deserialize(&mut deserializer);
    755     let measurement = result.map_err(classify_param_error)?;
    756     deserializer
    757         .end()
    758         .map_err(|_| failure(MycNip46AdmissionErrorKind::MalformedRequest))?;
    759     Ok(measurement)
    760 }
    761 
    762 struct StringSequenceSeed {
    763     maximum_count: usize,
    764     maximum_element_bytes: usize,
    765     maximum_total_bytes: usize,
    766     count_sentinel: &'static str,
    767     element_sentinel: &'static str,
    768     total_sentinel: &'static str,
    769 }
    770 
    771 impl<'de> DeserializeSeed<'de> for StringSequenceSeed {
    772     type Value = Measurement;
    773 
    774     fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
    775     where
    776         D: serde::Deserializer<'de>,
    777     {
    778         deserializer.deserialize_seq(StringSequenceVisitor { seed: self })
    779     }
    780 }
    781 
    782 struct StringSequenceVisitor {
    783     seed: StringSequenceSeed,
    784 }
    785 
    786 impl<'de> Visitor<'de> for StringSequenceVisitor {
    787     type Value = Measurement;
    788 
    789     fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
    790         formatter.write_str("a bounded array of strings")
    791     }
    792 
    793     fn visit_seq<A>(self, mut sequence: A) -> Result<Self::Value, A::Error>
    794     where
    795         A: SeqAccess<'de>,
    796     {
    797         let mut result = Measurement {
    798             count: 0,
    799             elements: 0,
    800             bytes: 0,
    801         };
    802         while result.count < self.seed.maximum_count {
    803             let Some(length) = sequence.next_element_seed(StringLengthSeed {
    804                 maximum: self.seed.maximum_element_bytes,
    805                 sentinel: self.seed.element_sentinel,
    806             })?
    807             else {
    808                 return Ok(result);
    809             };
    810             result.count += 1;
    811             result.elements = result.count;
    812             result.bytes = result
    813                 .bytes
    814                 .checked_add(length)
    815                 .ok_or_else(|| de::Error::custom(self.seed.total_sentinel))?;
    816             if result.bytes > self.seed.maximum_total_bytes {
    817                 return Err(de::Error::custom(self.seed.total_sentinel));
    818             }
    819         }
    820         if sequence.next_element::<IgnoredAny>()?.is_some() {
    821             return Err(de::Error::custom(self.seed.count_sentinel));
    822         }
    823         Ok(result)
    824     }
    825 }
    826 
    827 struct StringLengthSeed {
    828     maximum: usize,
    829     sentinel: &'static str,
    830 }
    831 
    832 impl<'de> DeserializeSeed<'de> for StringLengthSeed {
    833     type Value = usize;
    834 
    835     fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
    836     where
    837         D: serde::Deserializer<'de>,
    838     {
    839         let raw = <&RawValue>::deserialize(deserializer)?;
    840         let length = decoded_json_string_utf8_bytes(raw.get())
    841             .ok_or_else(|| de::Error::invalid_type(de::Unexpected::Other("non-string"), &self))?;
    842         if length > self.maximum {
    843             return Err(de::Error::custom(self.sentinel));
    844         }
    845         Ok(length)
    846     }
    847 }
    848 
    849 impl de::Expected for StringLengthSeed {
    850     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
    851         formatter.write_str("a JSON string")
    852     }
    853 }
    854 
    855 fn classify_tag_error(error: serde_json::Error) -> MycNip46AdmissionError {
    856     let rendered = error.to_string();
    857     let kind = if rendered.contains(TAG_COUNT_SENTINEL) {
    858         MycNip46AdmissionErrorKind::TooManyTags
    859     } else if rendered.contains(TAG_ELEMENT_COUNT_SENTINEL) {
    860         MycNip46AdmissionErrorKind::TooManyTagElements
    861     } else if rendered.contains(TAG_ELEMENT_BYTES_SENTINEL) {
    862         MycNip46AdmissionErrorKind::TagElementTooLarge
    863     } else if rendered.contains(TAG_TOTAL_BYTES_SENTINEL) {
    864         MycNip46AdmissionErrorKind::TagsTooLarge
    865     } else {
    866         MycNip46AdmissionErrorKind::MalformedEvent
    867     };
    868     failure(kind)
    869 }
    870 
    871 fn classify_param_error(error: serde_json::Error) -> MycNip46AdmissionError {
    872     let rendered = error.to_string();
    873     let kind = if rendered.contains(PARAM_COUNT_SENTINEL) {
    874         MycNip46AdmissionErrorKind::TooManyRequestParameters
    875     } else if rendered.contains(PARAM_BYTES_SENTINEL) {
    876         MycNip46AdmissionErrorKind::RequestParameterTooLarge
    877     } else if rendered.contains(PARAM_TOTAL_BYTES_SENTINEL) {
    878         MycNip46AdmissionErrorKind::RequestParametersTooLarge
    879     } else {
    880         MycNip46AdmissionErrorKind::MalformedRequest
    881     };
    882     failure(kind)
    883 }
    884 
    885 const fn failure(kind: MycNip46AdmissionErrorKind) -> MycNip46AdmissionError {
    886     MycNip46AdmissionError::new(kind)
    887 }
    888 
    889 #[cfg(test)]
    890 mod tests {
    891     use super::{MycNip46AdmissionErrorKind, RawValue, measure_parameters};
    892 
    893     fn raw_parameters(lengths: &[usize]) -> Box<RawValue> {
    894         let encoded = serde_json::to_string(
    895             &lengths
    896                 .iter()
    897                 .map(|length| "x".repeat(*length))
    898                 .collect::<Vec<_>>(),
    899         )
    900         .expect("parameter JSON");
    901         RawValue::from_string(encoded).expect("raw parameter JSON")
    902     }
    903 
    904     #[test]
    905     fn request_parameter_aggregate_bound_is_exact_and_independent() {
    906         let exact = raw_parameters(&[65_536, 65_536, 65_536, 65_536]);
    907         let measurement = measure_parameters(&exact).expect("exact aggregate bound");
    908         assert_eq!(measurement.count, 4);
    909         assert_eq!(measurement.bytes, 262_144);
    910 
    911         let over = raw_parameters(&[65_536, 65_536, 65_536, 65_536, 1]);
    912         let error = match measure_parameters(&over) {
    913             Ok(_) => panic!("over aggregate bound must fail"),
    914             Err(error) => error,
    915         };
    916         assert_eq!(
    917             error.kind(),
    918             MycNip46AdmissionErrorKind::RequestParametersTooLarge
    919         );
    920     }
    921 }