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 }