Sans-IO SIP message parsing, canonical re-emission and conformance diagnostics for MoonBit, validated against the IETF RFC 4475 torture-test corpus.
RFC 4475 torture corpus 49 messages extracted mechanically from the RFC text
45 verdicts asserted, 4 role-dependent (not asserted)
all 45 asserted verdicts match
every accepted message round-trips byte-for-byte *
Robustness >4,000 generated inputs (random, truncated, mutated,
spliced) with zero panics
Coverage 50 uncovered lines in the library (about 97% of 1,698
executable lines), measured with `moon coverage analyze`
Tests 79, green on wasm · wasm-gc · js · native
Dependencies none
Panics in the library none — no `unwrap`, `panic` or `abort` outside tests
API documentation 112 of 112 public functions carry a doc comment| Capability | Detail |
|---|---|
| Parse | Request-Line, Status-Line, header block with folding, body framing by Content-Length |
| URI | sip: / sips: per RFC 3261 §19.1: userinfo, IPv4/IPv6 host, port, parameters, URI headers, single-pass % unescaping |
| Addresses | name-addr / addr-spec / absoluteURI, quoted and token display names, header parameters, comma-separated lists |
| Lexical | token, quoted-string with quoted-pair, nested comment with a depth cap, LWS including CRLF folding |
| Re-emit | to_bytes returns the input octets unchanged for any CRLF-framed message |
| Diagnose | 18 defect codes carrying byte offsets, each citing the torture case it implements |
| Compact names | RFC 3261 Table 2 single-letter forms (i, l, v, t, f, m, c, s, k, e) |
python3 tools/extract_rfc4475.py --rfc rfc4475.txt --out-dir .
python3 tools/gen_tests.py
moon test| Generator | Inputs | What it reaches |
|---|---|---|
| Pseudo-random octets | 1,200 | Early grammar exits; the error paths |
| Structural octets (CR, LF, delimiters, high bits) | 1,500 | Adversarial whitespace and delimiter runs |
| Truncations of every corpus message | ~294 | Partial messages, unterminated constructs |
| Single-octet mutations of every corpus message | 1,176 | Deep grammar and diagnostics |
| Insertions and deletions (including an injected CRLF) | ~98 | Framing boundaries |
moon add BenPaoBaMingWangXing/sipkitlet input : Bytes =
b"INVITE sip:bob@example.com SIP/2.0\r\nTo: <sip:bob@example.com>\r\nFrom: <sip:alice@example.com>;tag=1\r\nCall-ID: a1\r\nCSeq: 1 INVITE\r\nVia: SIP/2.0/UDP host;branch=z9hG4bK1\r\nMax-Forwards: 70\r\nContent-Length: 0\r\n\r\n"
let a = analyze(input)
println(a.verdict()) // well-formed
let msg = a.message().unwrap()
println(ascii_of(msg.verb().unwrap().view())) // INVITE
assert_eq(to_bytes(msg).length(), input.length())match analyze(b"garbage").syntax() {
Some(pair) => println("rejected: \{pair.0} at byte \{pair.1}")
None => ()
}for d in analyze(input).diagnostics() {
println("\{d}") // error:content-length-mismatch@214 declared 152 but body is 0
}moon run examples/parse_dump # parse an INVITE, print every recovered field
moon run examples/diagnose # the three verdicts, side by side
moon run examples/uri # URI decomposition, including the awkward casesstart line
method INVITE
uri.scheme sip
uri.user bob
uri.host example.com
uri.port 5060
uri.params 1
;transport=tcp
headers (9), in wire order
line 2 Via: SIP/2.0/UDP alice-pc.example.com:5060;branch=z9hG4bK776asdhds
...
round-trip identical: true--- semantic-rejected
verdict: semantic-rejected
[error] content-length-mismatch at byte 236
declared 42 but body is 5
[error] cseq-method-mismatch at byte 120
CSeq method INVITE != start line OPTIONS
--- well-formed, with a warning
verdict: well-formed
[warning] missing-required-header at byte 0
request has no Max-Forwards header--- sip:al%41ice@example.com
user al%41ice
user* alAice (decoded once)
--- sip:al%2541ice@example.com
user* al%41ice (decoded once) <- not `alAice`: decoding is single-pass
--- sip:user;par=u%40example.net@example.com
user user;par=u%40example.net
user* user;par=u@example.net <- the ; belongs to the user part| Project | Scope | Relationship |
|---|---|---|
| moonbit-community/rtc | WebRTC media stack | Media plane; sipkit is the signalling plane |
| coke089/moon-sdp | SDP (RFC 8866) offer/answer | Session description; sipkit keeps bodies opaque |
| moonbitstack/moonurl | RFC 3986 URI | Generic URI; SIP URI has its own grammar (§19.1) |
| moonbitstack/moonhttp | HTTP / WebSocket | Different protocol family |
python3 tools/extract_rfc4475.py --rfc rfc4475.txt --out-dir . # needs rfc4475.txt
python3 tools/gen_tests.py
moon check
moon fmt --check
moon test --target wasm
moon test --target wasm-gc
moon test --target js
moon coverage analyze # line-level coverage; ~50 uncovered lines in the librarypub struct Analysis {
message : SipMessage?
syntax : (SyntaxKind, Int)?
diagnostics : Array[Diagnostic]
}pub struct Cursor {
src : BytesView
pos : Int
}pub(all) enum DiagCode {
MissingRequiredHeader
MissingViaBranch
CSeqMethodMismatch
ContentLengthMismatch
MultipleContentLengthValues
DuplicateSingleValueHeader
UnsupportedVersion
StatusCodeOutOfRange
ScalarOutOfRange
UnknownRequestUriScheme
TrailingOctets
ExtraneousSeparator
UnbracketedUriWithHeaders
MissingContactInDialogRequest
UnsupportedExtension
EscapedHeadersInRequestUri
MalformedDate
MalformedFieldValue
InvalidUtf8
}impl Show for Diagnosticpub struct HeaderField {
name : Bytes
value : Bytes
raw : Bytes
offset : Int
line : Int
}pub struct HeaderParam {
name : Bytes
value : Bytes?
raw : Bytes
}pub enum Host {
Name(Bytes)
Ipv4(Bytes)
Ipv6Reference(Bytes)
}pub struct NameAddr {
raw : Bytes
display : Bytes?
display_quoted : Bool
uri : AddrSpec
params : Array[HeaderParam]
had_angle_brackets : Bool
}pub struct QuotedString {
raw : Bytes
value : Bytes
}impl Show for RequestUripub enum Severity {
Error
Warning
}pub struct SipMessage {
start : StartLine
headers : Array[HeaderField]
body : Bytes
trailing : Bytes
consumed : Int
}pub struct StatusLine {
version : Bytes
code : Int
reason : Bytes
raw : Bytes
}pub enum SyntaxKind {
EmptyInput
UnexpectedEnd
IllegalOctet
ExpectedCrlf
ExpectedToken
ExpectedQuotedString
UnterminatedQuotedString
UnterminatedComment
TrailingGarbage
BadEscape
BadContentLength
MalformedDate
}impl Show for SyntaxKindpub struct UriHeader {
name : Bytes
value : Bytes
}pub struct UriParam {
name : Bytes
value : Bytes?
raw : Bytes
}pub enum UriScheme {
Sip
Sips
Other(Bytes)
}pub enum Verdict {
WellFormed
SyntaxRejected
SemanticRejected
}fn ascii_bytes_of(s : String) -> Bytesfn ascii_of(b : BytesView) -> Stringfn bytes_eq_ignore_ascii_case(a : BytesView, b : BytesView) -> Boolfn bytes_from_hex(s : String) -> Bytesfn contains_ignore_case(hay : BytesView, needle : BytesView) -> Boolfn copy_range(src : BytesView, start : Int, end : Int) -> Bytesfn eq_ignore_ascii_case(a : Byte, b : Byte) -> Boolfn hex_of(b : BytesView) -> Stringfn hex_value(b : Byte) -> Intfn is_ctl(b : Byte) -> Boolfn is_high_octet(b : Byte) -> Boolfn is_lws(b : Byte) -> Boolfn is_separator(b : Byte) -> Boolfn is_sip_2_0(v : Bytes) -> Boolfn is_token_char(b : Byte) -> Boolfn is_unreserved(b : Byte) -> Boolfn is_valid_utf8(v : BytesView) -> Boolfn lower_ascii(b : Byte) -> Bytefn range_eq_ignore_case(src : BytesView, start : Int, end : Int, lit : String) -> Boolfn unescape_once(src : BytesView) -> BytesInstall
Download zipSans-IO SIP message parsing, canonical re-emission and conformance diagnostics for MoonBit, validated against the IETF RFC 4475 torture-test corpus.