// message.mbt — start lines, header block, body framing and full-message parse.
//
// Framing rules implemented here (RFC 3261 §7 and §18.3):
//
//   * The start line and every header field end with CRLF; the header block ends
//     with an empty line.
//   * The body is `Content-Length` octets long. When the header is absent, the
//     remainder of the datagram is the body.
//   * Octets after the declared body are NOT silently discarded: they are kept in
//     `trailing`, because RFC 4475 §3.1.1.8 ("trailingspace") exists precisely to
//     test whether an implementation notices them.
//
// RFC 4475 §3.1.2.19 ("bigcode") returns a status code far outside 3DIGIT. Such a
// message must not crash a parser, so the code is read as a saturating integer and
// reported by the diagnostics layer instead of raising.

///|
/// Sentinel used when a decimal run exceeds `Int` range.
pub const SATURATED : Int32 = 2147483647

///|
/// A request start line.
pub struct RequestLine {
  verb : Bytes
  uri : RequestUri
  version : Bytes
  raw : Bytes
}

///|
/// The method token.
pub fn RequestLine::verb(self : RequestLine) -> Bytes {
  self.verb
}

///|
/// The request URI, parsed or kept verbatim for a non-SIP scheme.
pub fn RequestLine::uri(self : RequestLine) -> RequestUri {
  self.uri
}

///|
/// The protocol version octets, for example `SIP/2.0`.
pub fn RequestLine::version(self : RequestLine) -> Bytes {
  self.version
}

///|
/// A response start line.
pub struct StatusLine {
  version : Bytes
  code : Int32
  reason : Bytes
  raw : Bytes
}

///|
/// The protocol version octets.
pub fn StatusLine::version(self : StatusLine) -> Bytes {
  self.version
}

///|
/// The numeric status code, saturated when the digits overflow.
pub fn StatusLine::code(self : StatusLine) -> Int32 {
  self.code
}

///|
/// The reason phrase, which may be empty.
pub fn StatusLine::reason(self : StatusLine) -> Bytes {
  self.reason
}

///|
/// Request-URI as parsed. `Other` keeps a syntactically valid absolute URI whose
/// scheme is not sip/sips: RFC 3261 permits `absoluteURI` here, and RFC 4475
/// §3.3.2 expects such a message to parse and be rejected one layer up.
pub enum RequestUri {
  Parsed(SipUri)
  Other(Bytes)
}

///|
pub extend RequestUri with Show::{to_string, output}

///|
pub impl Show for RequestUri with fn to_string(self : RequestUri) -> String {
  match self {
    Parsed(u) => u.raw().to_string()
    Other(raw) => ascii_of(raw.exact_view())
  }
}

///|
pub enum StartLine {
  Request(RequestLine)
  Response(StatusLine)
}

///|
pub struct SipMessage {
  start : StartLine
  headers : Array[HeaderField]
  body : Bytes
  trailing : Bytes
  consumed : Int32
}

///|
/// The start line, request or response.
pub fn SipMessage::start_line(self : SipMessage) -> StartLine {
  self.start
}

///|
/// All header fields in wire order, duplicates included.
pub fn SipMessage::headers(self : SipMessage) -> Array[HeaderField] {
  self.headers
}

///|
/// The body, framed by `Content-Length`.
pub fn SipMessage::body(self : SipMessage) -> Bytes {
  self.body
}

///|
/// Octets that followed the declared body in the same datagram.
pub fn SipMessage::trailing(self : SipMessage) -> Bytes {
  self.trailing
}

///|
/// Number of octets belonging to this message.
pub fn SipMessage::consumed(self : SipMessage) -> Int32 {
  self.consumed
}

///|
/// The request method, or `None` for a response.
pub fn SipMessage::verb(self : SipMessage) -> Bytes? {
  match self.start {
    Request(r) => Some(r.verb)
    Response(_) => None
  }
}

///|
/// The status code, or `None` for a request.
pub fn SipMessage::status_code(self : SipMessage) -> Int32? {
  match self.start {
    Request(_) => None
    Response(r) => Some(r.code)
  }
}

///|
/// All header fields whose name matches, in wire order. Duplicates are preserved
/// because their presence is itself a diagnosable condition.
pub fn SipMessage::all_headers(
  self : SipMessage,
  name : String,
) -> Array[HeaderField] {
  let out : Array[HeaderField] = []
  for h in self.headers {
    if h.name_is(name) {
      out.push(h)
    }
  }
  out
}

///|
/// First header field with the given name.
pub fn SipMessage::header(self : SipMessage, name : String) -> HeaderField? {
  for h in self.headers {
    if h.name_is(name) {
      return Some(h)
    }
  }
  None
}

///|
/// Reads a decimal run, saturating instead of overflowing. Returns the value and
/// the octet count consumed.
fn read_decimal(c : Cursor) -> (Int32, Int32) {
  let start = c.position()
  let mut v = 0
  let mut overflow = false
  while c.pos < c.length() {
    let b = c.src[c.pos]
    if is_digit(b) {
      if v > 214748363 {
        overflow = true
      }
      v = v * 10 + (b - b'0').to_int()
      c.pos = c.pos + 1
    } else {
      break
    }
  }
  (if overflow { SATURATED } else { v }, c.position() - start)
}

///|
/// Parses `SIP/2.0` style version tokens. Returns the raw octets.
fn parse_version(c : Cursor) -> Bytes raise SipError {
  let start = c.position()
  if c.starts_with_ignore_case("SIP/") {
    c.pos = c.pos + 4
  } else {
    raise Syntax(IllegalOctet, c.position())
  }
  let (_, n1) = read_decimal(c)
  if n1 == 0 {
    raise Syntax(IllegalOctet, c.position())
  }
  if c.eof() || c.src[c.pos] != DOT {
    raise Syntax(IllegalOctet, c.position())
  }
  c.pos = c.pos + 1
  let (_, n2) = read_decimal(c)
  if n2 == 0 {
    raise Syntax(IllegalOctet, c.position())
  }
  copy_range(c.src, start, c.position())
}

///|
/// True when the version octets are exactly `SIP/2.0` ignoring case.
pub fn is_sip_2_0(v : Bytes) -> Bool {
  bytes_eq_ignore_ascii_case(v.exact_view(), b"SIP/2.0".exact_view())
}

///|
/// Consumes exactly one CRLF. A bare LF is accepted (every deployed stack does,
/// and RFC 4475 has messages that rely on leniency) but is reported downstream.
fn consume_crlf(c : Cursor) -> Unit raise SipError {
  if c.eof() {
    raise Syntax(ExpectedCrlf, c.position())
  }
  if c.src[c.pos] == CR {
    c.pos = c.pos + 1
    if c.pos < c.length() && c.src[c.pos] == LF {
      c.pos = c.pos + 1
      return
    }
    raise Syntax(ExpectedCrlf, c.position())
  }
  if c.src[c.pos] == LF {
    c.pos = c.pos + 1
    return
  }
  raise Syntax(ExpectedCrlf, c.position())
}

///|
/// Parses a Request-Line. Exactly one SP separates each element: RFC 4475
/// §3.1.2.9 and §3.1.2.10 mark multiple or trailing SPs as invalid.
fn parse_request_line(c : Cursor) -> RequestLine raise SipError {
  let start = c.position()
  let mstart = c.position()
  while c.pos < c.length() {
    let b = c.src[c.pos]
    if is_token_char(b) {
      c.pos = c.pos + 1
    } else {
      break
    }
  }
  if c.pos == mstart {
    raise Syntax(ExpectedToken, mstart)
  }
  let verb = copy_range(c.src, mstart, c.position())
  if c.eof() || c.src[c.pos] != SP {
    raise Syntax(IllegalOctet, c.position())
  }
  c.pos = c.pos + 1
  if c.pos < c.length() && c.src[c.pos] == SP {
    // RFC 4475 §3.1.2.9: multiple SP between Request-Line elements.
    raise Syntax(IllegalOctet, c.position())
  }
  let ustart = c.position()
  while c.pos < c.length() {
    let b = c.src[c.pos]
    if b == SP || b == CR || b == LF {
      break
    }
    c.pos = c.pos + 1
  }
  if c.pos == ustart {
    raise Syntax(IllegalOctet, ustart)
  }
  let uend = c.position()
  let uri = parse_request_uri(c.src.exact_view(start=ustart, end=uend))
  if c.eof() || c.src[c.pos] != SP {
    raise Syntax(IllegalOctet, c.position())
  }
  c.pos = c.pos + 1
  if c.pos < c.length() && c.src[c.pos] == SP {
    // RFC 4475 §3.1.2.10: SP at the end of the Request-Line.
    raise Syntax(IllegalOctet, c.position())
  }
  let version = parse_version(c)
  consume_crlf(c)
  { verb, uri, version, raw: copy_range(c.src, start, c.position()), }
}

///|
/// Parses a Request-URI from a bounded view. The view must be fully consumed:
/// leftover octets mean the URI grammar and the delimiter scan disagree, which is
/// a defect rather than something to paper over.
fn parse_request_uri(v : BytesView) -> RequestUri raise SipError {
  let sub = Cursor::new(v)
  let out = if looks_like_sip_uri(sub) {
    RequestUri::Parsed(parse_sip_uri(sub))
  } else {
    // Keep the raw octets; scheme-level rejection is an application-layer test
    // (RFC 4475 §3.3.2 / §3.3.3), not a parse failure.
    let mut i = 0
    while i < v.length() &&
          (
            is_alpha(v[i]) ||
            is_digit(v[i]) ||
            v[i] == b'+' ||
            v[i] == b'-' ||
            v[i] == b'.'
          ) {
      i = i + 1
    }
    if i == 0 || i >= v.length() || v[i] != COLON {
      raise Syntax(IllegalOctet, 0)
    }
    RequestUri::Other(copy_range(v, 0, v.length()))
  }
  if sub.position() != v.length() {
    match out {
      Parsed(_) => raise Syntax(TrailingGarbage, sub.position())
      Other(_) => ()
    }
  }
  out
}

///|
/// Parses a Status-Line.
fn parse_status_line(c : Cursor) -> StatusLine raise SipError {
  let start = c.position()
  let version = parse_version(c)
  if c.eof() || c.src[c.pos] != SP {
    raise Syntax(IllegalOctet, c.position())
  }
  c.pos = c.pos + 1
  let (code, digits) = read_decimal(c)
  if digits == 0 {
    raise Syntax(IllegalOctet, c.position())
  }
  let mut reason : Bytes = Bytes::new(0)
  if c.pos < c.length() && c.src[c.pos] == SP {
    c.pos = c.pos + 1
    let rstart = c.position()
    while c.pos < c.length() {
      let b = c.src[c.pos]
      if b == CR || b == LF {
        break
      }
      c.pos = c.pos + 1
    }
    reason = copy_range(c.src, rstart, c.position())
  }
  consume_crlf(c)
  { version, code, reason, raw: copy_range(c.src, start, c.position()), }
}

///|
/// Finds the `Content-Length` value, if any, using the first occurrence.
pub fn content_length_of(headers : Array[HeaderField]) -> Bytes? {
  for h in headers {
    if h.name_is("Content-Length") || h.name_is("l") {
      return Some(h.value)
    }
  }
  None
}

///|
/// Header fields whose value grammar is a name-addr list. Their values are parsed
/// structurally during message parse, not merely stored as octets: RFC 4475
/// §3.1.2.14 (spaces inside `< >`) and §3.1.2.15 (unquoted display name) are
/// grammar errors that only a structural parse can see.
fn is_address_header(name : Bytes) -> Bool {
  for
    cand in [
      b"From", b"To", b"Contact", b"Route", b"Record-Route", b"f", b"t", b"m",
    ] {
    if bytes_eq_ignore_ascii_case(name.exact_view(), cand.exact_view()) {
      return true
    }
  }
  false
}

///|
/// `SIP-date` per RFC 3261 §25.1: RFC 1123 date with the zone fixed to `GMT`.
/// Full grammar check (weekday names, month names, digit fields, ranges) plus
/// weekday/date agreement via Zeller's congruence, so `Mon, 01 Jan 2010` fails
/// on the weekday even though every token looks plausible. Other zone
/// designations that mean the same thing (`UT`, `UTC`, `UCT`) are explicitly
/// invalid -- that is exactly RFC 4475 §3.1.2.12 ("baddate").
fn is_sip_date(v : BytesView) -> Bool {
  // `Wkd, DD Mon YYYY HH:MM:SS GMT` is exactly 29 octets -- the grammar fixes
  // 2DIGIT day, 4DIGIT year and a 3-letter zone, so anything else is malformed.
  if v.length() != 29 {
    return false
  }
  if !bytes_eq_ignore_ascii_case(
      v.exact_view(start=26, end=29),
      b"GMT".exact_view(),
    ) {
    return false
  }
  if v[3] != COMMA || v[4] != SP || v[7] != SP || v[11] != SP || v[16] != SP {
    return false
  }
  if v[19] != COLON || v[22] != COLON {
    return false
  }
  let wnum = weekday_num(v.exact_view(start=0, end=3))
  if wnum < 0 {
    return false
  }
  let mon = month_num(v.exact_view(start=8, end=11))
  if mon < 0 {
    return false
  }
  for i in [5, 6, 12, 13, 14, 15, 17, 18, 20, 21, 23, 24] {
    if !is_digit(v[i]) {
      return false
    }
  }
  let day = digit2(v, 5)
  let year = digit2(v, 12) * 100 + digit2(v, 14)
  let hh = digit2(v, 17)
  let mm = digit2(v, 20)
  let ss = digit2(v, 23)
  if day < 1 || day > days_in_month(mon, year) {
    return false
  }
  // 60 seconds is legal exactly as a leap second.
  if hh > 23 || mm > 59 || ss > 60 {
    return false
  }
  // Weekday/date agreement: Zeller's congruence, h = 0 is Saturday.
  let zmon = if mon < 3 { mon + 12 } else { mon }
  let zyear = if mon < 3 { year - 1 } else { year }
  let k = zyear % 100
  let j = zyear / 100
  let h = (day + 13 * (zmon + 1) / 5 + k + k / 4 + j / 4 + 5 * j) % 7
  zeller_to_mon0(h) == wnum
}

///|
/// Two decimal digits starting at `at`, as an integer.
fn digit2(v : BytesView, at : Int32) -> Int32 {
  (v[at] - b'0').to_int() * 10 + (v[at + 1] - b'0').to_int()
}

///|
/// 0 = Mon .. 6 = Sun, or -1 when the name is not one of the seven.
fn weekday_num(w : BytesView) -> Int32 {
  let days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
  for i in 0.. Int32 {
  let months = [
    "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov",
    "Dec",
  ]
  for i in 0.. Int32 {
  let dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
  let base = dim[mon - 1]
  if mon == 2 && year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) {
    return 29
  }
  base
}

///|
/// Maps Zeller's h (0 = Saturday) to the 0 = Mon numbering used above.
fn zeller_to_mon0(h : Int32) -> Int32 {
  match h {
    2 => 0
    3 => 1
    4 => 2
    5 => 3
    6 => 4
    0 => 5
    1 => 6
    _ => -1
  }
}

///|
/// Structurally validates an address-valued header. Raises with an offset already
/// translated into whole-message coordinates, so callers never see a view-relative
/// number.
fn validate_header_value(h : HeaderField) -> Unit raise SipError {
  if h.name_is("Date") && !is_sip_date(h.value.exact_view()) {
    raise Syntax(MalformedDate, h.offset)
  }
  if !is_address_header(h.name) {
    return
  }
  if h.value.length() == 0 {
    // An empty value is allowed by the grammar for optional fields; leave the
    // decision to the diagnostics layer.
    return
  }
  let sub = Cursor::new(h.value.exact_view())
  ignore(parse_name_addr_list(sub)) catch {
    Syntax(kind, at) => raise Syntax(kind, h.offset + at)
  }
  if sub.pos != sub.length() {
    raise Syntax(TrailingGarbage, h.offset + sub.pos)
  }
}

///|
/// Parses a whole message from a datagram or a buffered stream chunk.
pub fn parse_message(input : Bytes) -> SipMessage raise SipError {
  let c = Cursor::from_bytes(input)
  if input.length() == 0 {
    raise Syntax(EmptyInput, 0)
  }
  let start = if c.starts_with_ignore_case("SIP/") {
    StartLine::Response(parse_status_line(c))
  } else {
    StartLine::Request(parse_request_line(c))
  }
  let headers : Array[HeaderField] = []
  let mut line = 2
  while true {
    if c.eof() {
      raise Syntax(UnexpectedEnd, c.position())
    }
    // Empty line terminates the header block.
    if c.src[c.pos] == CR || c.src[c.pos] == LF {
      consume_crlf(c)
      break
    }
    let h = parse_header_field(c, line)
    validate_header_value(h)
    headers.push(h)
    consume_crlf(c)
    line = line + 1
  }
  let remaining = c.length() - c.position()
  let mut body : Bytes = Bytes::new(0)
  let mut trailing : Bytes = Bytes::new(0)
  match content_length_of(headers) {
    Some(raw) => {
      // Tolerate a leading `-` so a negative length becomes a diagnostic rather
      // than a parse failure (RFC 4475 §3.1.2.3).
      let sub = Cursor::new(raw.exact_view())
      let mut negative = false
      if sub.pos < sub.length() && sub.src[sub.pos] == MINUS {
        negative = true
        sub.pos = sub.pos + 1
      }
      // `Content-Length = 1*DIGIT` — a sign or trailing junk makes the field
      // ill-formed, not merely out of range (RFC 4475 §3.1.2.3 "ncl").
      let (declared, digits) = read_decimal(sub)
      if digits == 0 || sub.pos != sub.length() || negative {
        raise Syntax(BadContentLength, c.position())
      }
      let want = declared
      if want > remaining {
        // RFC 4475 §3.1.2.2: Content-Length larger than the message.
        body = copy_range(c.src, c.position(), c.length())
      } else {
        body = copy_range(c.src, c.position(), c.position() + want)
        trailing = copy_range(c.src, c.position() + want, c.length())
      }
    }
    None =>
      if remaining > 0 {
        body = copy_range(c.src, c.position(), c.length())
      }
  }
  { start, headers, body, trailing, consumed: c.position() + body.length(), }
}