bitchat/bitchatTests/Protocols/StickerRefCodecTests.swift
Vincenzo Palazzo 5960b7f343 Address review: split semantics, status note, DM size constraint, vacuous parity test
- Parsing Rule 1 now says 'at most 5 parts' and spells out the
  Swift maxSplits:4 vs Rust splitn(5) off-by-one, which would otherwise
  make a splitn(4) implementor reject every valid reference.
- Overview gains an explicit Status note: nothing parses, routes, fetches,
  or renders sticker references yet (reviewer ask on #1544).
- Document the 255-byte DM content constraint for maximal-length
  references, pointing at #784 (Codex P2 thread).
- parseAcceptsSonarFfiEncodedContent now #requires the parse result:
  #expect(ref == makeRef(...)) could pass vacuously as nil == nil.

Flagged by the multi-model review panel.
2026-07-31 14:19:50 +02:00

173 lines
7.7 KiB
Swift

//
// StickerRefCodecTests.swift
// bitchatTests
//
// Tests for the Sonar sticker-ref wire codec: round-trip, sonar-ffi parity
// vectors, strict shape rejection, and adversarial (separator-only / huge /
// emoji) input. The codec parses attacker-controlled mesh content, so
// "never crash" matters as much as "parse correctly".
//
import Foundation
import Testing
@testable import bitchat
struct StickerRefCodecTests {
// MARK: - Builders
private let pubkey = String(repeating: "ab", count: 32) // 64 lowercase hex
private let sha256 = String(repeating: "deadbeef", count: 8) // 64 lowercase hex
private func makeRef(
coordinate: String? = nil,
shortcode: String = "wave",
hash: String? = nil
) -> StickerRef? {
StickerRef(
packCoordinate: coordinate ?? "30031:\(pubkey):my-pack",
shortcode: shortcode,
plaintextSha256: hash ?? sha256
)
}
// MARK: - Round trip
@Test func encodeParseRoundTrip() {
let ref = makeRef()
#expect(ref != nil)
let parsed = StickerRefCodec.parse(StickerRefCodec.encode(ref!))
#expect(parsed == ref)
#expect(ref!.content == StickerRefCodec.encode(ref!))
}
// MARK: - sonar-ffi parity vectors
@Test func encodeMatchesSonarFfiBytes() {
// mesh_sticker_content output must be byte-identical.
let ref = makeRef()!
let expected = "\u{1F}sticker\u{1F}30031:\(pubkey):my-pack\u{1F}wave\u{1F}\(sha256)"
#expect(StickerRefCodec.encode(ref) == expected)
#expect(StickerRefCodec.encode(ref).hasPrefix("\u{1F}sticker\u{1F}"))
}
@Test func parseAcceptsSonarFfiEncodedContent() throws {
// Hand-built exactly as mesh_parse_sticker_content would see it.
let wire = "\u{1F}sticker\u{1F}30031:\(pubkey):pack\u{1F}wave\u{1F}\(sha256)"
// #require (not #expect against makeRef): if a regression made parse
// return nil here, `nil == nil` would let the test pass vacuously.
let ref = try #require(StickerRefCodec.parse(wire))
#expect(ref == makeRef(coordinate: "30031:\(pubkey):pack"))
}
// MARK: - Not a sticker at all
@Test func rejectsNonStickerContent() {
#expect(StickerRefCodec.parse("hello world") == nil)
#expect(StickerRefCodec.parse("") == nil)
#expect(StickerRefCodec.parse("sticker:fake") == nil)
// "sticker" tag without the leading Unit Separator sentinel.
#expect(StickerRefCodec.parse("sticker\u{1F}30031:\(pubkey):pack\u{1F}wave\u{1F}\(sha256)") == nil)
// Right tag, wrong sentinel position.
#expect(StickerRefCodec.parse("x\u{1F}sticker\u{1F}30031:\(pubkey):pack\u{1F}wave\u{1F}\(sha256)") == nil)
}
// MARK: - Coordinate validation
@Test func rejectsBadCoordinates() {
func ref(with coordinate: String) -> StickerRef? { makeRef(coordinate: coordinate) }
#expect(ref(with: "30032:\(pubkey):pack") == nil) // wrong kind
#expect(ref(with: "30031:\(pubkey.uppercased()):pack") == nil) // uppercase hex
#expect(ref(with: "30031:\(String(pubkey.dropLast())):pack") == nil) // short pubkey
#expect(ref(with: "30031:\(pubkey)00:pack") == nil) // long pubkey
#expect(ref(with: "30031:\(pubkey):bad id") == nil) // space in identifier
#expect(ref(with: "30031:\(pubkey):bad/id") == nil) // slash in identifier
#expect(ref(with: "30031:\(pubkey):") == nil) // empty identifier
#expect(ref(with: "30031:\(pubkey):\(String(repeating: "a", count: 81))") == nil) // 81-char identifier
#expect(ref(with: "30031:\(pubkey)") == nil) // missing identifier field
#expect(ref(with: ":\(pubkey):pack") == nil) // empty kind
}
@Test func acceptsCoordinateBoundaryIdentifiers() {
#expect(makeRef(coordinate: "30031:\(pubkey):a") != nil)
#expect(makeRef(coordinate: "30031:\(pubkey):\(String(repeating: "a", count: 80))") != nil)
#expect(makeRef(coordinate: "30031:\(pubkey):A-Z_a.z-09") != nil)
}
// MARK: - Shortcode validation
@Test func rejectsBadShortcodes() {
#expect(makeRef(shortcode: "") == nil)
#expect(makeRef(shortcode: String(repeating: "w", count: 65)) == nil)
#expect(makeRef(shortcode: "wavé") == nil) // non-ASCII
#expect(makeRef(shortcode: "so-wave") == nil) // dash not allowed
#expect(makeRef(shortcode: "so wave") == nil)
}
@Test func acceptsShortcodeBoundaries() {
#expect(makeRef(shortcode: "a") != nil)
#expect(makeRef(shortcode: String(repeating: "w", count: 64)) != nil)
#expect(makeRef(shortcode: "Wave_09") != nil)
}
// MARK: - SHA-256 validation
@Test func rejectsBadSha256() {
#expect(makeRef(hash: String(repeating: "a", count: 63)) == nil)
#expect(makeRef(hash: String(repeating: "a", count: 65)) == nil)
#expect(makeRef(hash: sha256.uppercased()) == nil)
#expect(makeRef(hash: String(repeating: "g", count: 64)) == nil) // non-hex
}
// MARK: - Field count / framing
@Test func rejectsWrongFieldCounts() {
let base = "\u{1F}sticker\u{1F}30031:\(pubkey):pack\u{1F}wave\u{1F}\(sha256)"
#expect(StickerRefCodec.parse(base + "\u{1F}extra") == nil) // trailing field
#expect(StickerRefCodec.parse(base + "\u{1F}\u{1F}\u{1F}") == nil) // >4 splits
#expect(StickerRefCodec.parse("\u{1F}sticker\u{1F}30031:\(pubkey):pack\u{1F}wave") == nil) // missing hash
#expect(StickerRefCodec.parse("\u{1F}sticker\u{1F}30031:\(pubkey):pack\u{1F}\u{1F}\(sha256)") == nil) // empty shortcode field
}
// MARK: - Truncated / oversized wire input
@Test func rejectsTruncatedAndOversizedWireRefs() {
let wire = "\u{1F}sticker\u{1F}30031:\(pubkey):pack\u{1F}wave\u{1F}\(sha256)"
// Truncated at a field boundary, mid-hash, and mid-tag: a cut
// packet must never parse as a valid ref.
for cut in [wire.count - 1, wire.count - 32, wire.count / 2, 9, 1] {
#expect(StickerRefCodec.parse(String(wire.prefix(cut))) == nil)
}
// Valid framing with a hash truncated to 63 chars.
#expect(StickerRefCodec.parse("\u{1F}sticker\u{1F}30031:\(pubkey):pack\u{1F}wave\u{1F}\(String(sha256.dropLast()))") == nil)
// Oversized fields (64KiB) must be rejected by the length guards
// before any field is trusted, so a bad packet cannot make the
// decoder retain an inflated buffer.
let huge = String(repeating: "a", count: 65_536)
#expect(StickerRefCodec.parse("\u{1F}sticker\u{1F}30031:\(pubkey):\(huge)\u{1F}wave\u{1F}\(sha256)") == nil)
#expect(StickerRefCodec.parse("\u{1F}sticker\u{1F}30031:\(pubkey):pack\u{1F}\(huge)\u{1F}\(sha256)") == nil)
#expect(StickerRefCodec.parse("\u{1F}sticker\u{1F}30031:\(pubkey):pack\u{1F}wave\u{1F}\(huge)") == nil)
}
// MARK: - Never-crash fuzz-ish
@Test func pathologicalInputsNeverCrashAndNeverParse() {
let cases = [
String(repeating: "\u{1F}", count: 5), // all separators
String(repeating: "\u{1F}", count: 4096),
"\u{1F}sticker" + String(repeating: "\u{1F}", count: 100),
String(repeating: "a", count: 100_000), // very long
"\u{1F}sticker\u{1F}🌊🌊🌊\u{1F}🌊\u{1F}🌊", // emoji fields
"\u{1F}sticker\u{1F}30031:\(pubkey):pack\u{1F}wave\u{1F}\(sha256)\u{1F}",
"\u{1F}sticker\u{1F}\u{1F}\u{1F}\u{1F}",
"sticker\u{1F}\u{1F}\u{1F}\u{1F}\u{1F}",
]
for content in cases {
#expect(StickerRefCodec.parse(content) == nil)
}
}
}