///|
pub(all) struct Header {
  padding : Bool
  count : Byte
  packet_type : Byte
  length_words_minus_one : UInt16
} derive(Debug, Eq)

///|
pub fn Header::padding(self : Header) -> Bool {
  self.padding
}

///|
pub fn Header::count(self : Header) -> Byte {
  self.count
}

///|
pub fn Header::packet_type(self : Header) -> Byte {
  self.packet_type
}

///|
pub fn Header::length_words_minus_one(self : Header) -> UInt16 {
  self.length_words_minus_one
}

///|
fn rtcp_read_u16(data : Bytes, offset : Int) -> UInt16 raise RtcpError {
  if offset < 0 || offset + 2 > data.length() {
    raise InvalidPacket("truncated RTCP 16-bit field")
  }
  ((data[offset].to_uint() << 8) | data[offset + 1].to_uint()).to_uint16()
}

///|
fn rtcp_read_u24(data : Bytes, offset : Int) -> UInt raise RtcpError {
  if offset < 0 || offset + 3 > data.length() {
    raise InvalidPacket("truncated RTCP 24-bit field")
  }
  (data[offset].to_uint() << 16) |
  (data[offset + 1].to_uint() << 8) |
  data[offset + 2].to_uint()
}

///|
fn rtcp_read_u32(data : Bytes, offset : Int) -> UInt raise RtcpError {
  if offset < 0 || offset + 4 > data.length() {
    raise InvalidPacket("truncated RTCP 32-bit field")
  }
  (data[offset].to_uint() << 24) |
  (data[offset + 1].to_uint() << 16) |
  (data[offset + 2].to_uint() << 8) |
  data[offset + 3].to_uint()
}

///|
fn rtcp_read_u64(data : Bytes, offset : Int) -> UInt64 raise RtcpError {
  if offset < 0 || offset + 8 > data.length() {
    raise InvalidPacket("truncated RTCP 64-bit field")
  }
  (data[offset].to_uint64() << 56) |
  (data[offset + 1].to_uint64() << 48) |
  (data[offset + 2].to_uint64() << 40) |
  (data[offset + 3].to_uint64() << 32) |
  (data[offset + 4].to_uint64() << 24) |
  (data[offset + 5].to_uint64() << 16) |
  (data[offset + 6].to_uint64() << 8) |
  data[offset + 7].to_uint64()
}

///|
fn rtcp_write_u16(output : Array[Byte], value : UInt16) -> Unit {
  output.push((value >> 8).to_byte())
  output.push(value.to_byte())
}

///|
fn rtcp_write_u24(output : Array[Byte], value : UInt) -> Unit {
  output.push((value >> 16).to_byte())
  output.push((value >> 8).to_byte())
  output.push(value.to_byte())
}

///|
fn rtcp_write_u32(output : Array[Byte], value : UInt) -> Unit {
  output.push((value >> 24).to_byte())
  output.push((value >> 16).to_byte())
  output.push((value >> 8).to_byte())
  output.push(value.to_byte())
}

///|
fn rtcp_write_u64(output : Array[Byte], value : UInt64) -> Unit {
  output.push((value >> 56).to_byte())
  output.push((value >> 48).to_byte())
  output.push((value >> 40).to_byte())
  output.push((value >> 32).to_byte())
  output.push((value >> 24).to_byte())
  output.push((value >> 16).to_byte())
  output.push((value >> 8).to_byte())
  output.push(value.to_byte())
}

///|
fn decode_header(data : Bytes) -> Header raise RtcpError {
  if data.length() < 4 {
    raise InvalidPacket("RTCP packet is shorter than its header")
  }
  if data[0] >> 6 != 2 {
    raise InvalidPacket("unsupported RTCP version")
  }
  {
    padding: (data[0] & 0x20) != 0,
    count: data[0] & 0x1f,
    packet_type: data[1],
    length_words_minus_one: rtcp_read_u16(data, 2),
  }
}

///|
pub fn Packet::header(self : Packet) -> Header raise RtcpError {
  decode_header(self.marshal())
}

///|
fn packet_wire_bytes(packet : Packet) -> Bytes {
  match packet {
    SenderReport(data)
    | ReceiverReport(data)
    | SourceDescription(data)
    | Goodbye(data)
    | ApplicationDefined(data)
    | TransportFeedback(data)
    | PayloadFeedback(data)
    | ExtendedReport(data)
    | Unknown(data) => data
  }
}

///|
fn classify_packet(data : Bytes, packet_type : Byte) -> Packet {
  match packet_type {
    200 => SenderReport(data)
    201 => ReceiverReport(data)
    202 => SourceDescription(data)
    203 => Goodbye(data)
    204 => ApplicationDefined(data)
    205 => TransportFeedback(data)
    206 => PayloadFeedback(data)
    207 => ExtendedReport(data)
    _ => Unknown(data)
  }
}

///|
fn validate_single_packet(data : Bytes) -> Header raise RtcpError {
  let header = decode_header(data)
  let expected = (header.length_words_minus_one.to_int() + 1) * 4
  if expected != data.length() {
    raise InvalidPacket("RTCP length field does not match packet length")
  }
  if header.padding {
    let padding_length = data[data.length() - 1].to_int()
    if padding_length == 0 || padding_length > data.length() - 4 {
      raise InvalidPacket("invalid RTCP padding length")
    }
  }
  header
}

///|
pub fn Packet::marshal(self : Packet) -> Bytes raise RtcpError {
  let data = packet_wire_bytes(self)
  let header = validate_single_packet(data)
  match self {
    SenderReport(_) if header.packet_type != 200 =>
      raise InvalidPacket(
        "RTCP sender-report variant has the wrong packet type",
      )
    ReceiverReport(_) if header.packet_type != 201 =>
      raise InvalidPacket(
        "RTCP receiver-report variant has the wrong packet type",
      )
    SourceDescription(_) if header.packet_type != 202 =>
      raise InvalidPacket("RTCP SDES variant has the wrong packet type")
    Goodbye(_) if header.packet_type != 203 =>
      raise InvalidPacket("RTCP BYE variant has the wrong packet type")
    ApplicationDefined(_) if header.packet_type != 204 =>
      raise InvalidPacket("RTCP APP variant has the wrong packet type")
    TransportFeedback(_) if header.packet_type != 205 =>
      raise InvalidPacket("RTCP RTPFB variant has the wrong packet type")
    PayloadFeedback(_) if header.packet_type != 206 =>
      raise InvalidPacket("RTCP PSFB variant has the wrong packet type")
    ExtendedReport(_) if header.packet_type != 207 =>
      raise InvalidPacket("RTCP XR variant has the wrong packet type")
    _ => ()
  }
  data
}

///|
pub fn Packet::unmarshal(data : Bytes) -> Packet raise RtcpError {
  let packets = decode_compound(data)
  guard packets is [packet] else {
    raise InvalidPacket("expected exactly one RTCP packet")
  }
  packet
}

///|
pub fn Packet::decode(data : Bytes) -> Packet raise RtcpError {
  Packet::unmarshal(data)
}

///|
pub fn decode_compound(data : Bytes) -> Array[Packet] raise RtcpError {
  if data.is_empty() {
    raise InvalidCompoundPacket("RTCP compound packet is empty")
  }
  let packets : Array[Packet] = []
  let mut offset = 0
  while offset < data.length() {
    if data.length() - offset < 4 {
      raise InvalidCompoundPacket("truncated RTCP compound packet")
    }
    let view = data[offset:].to_owned()
    let header = decode_header(view)
    let packet_length = (header.length_words_minus_one.to_int() + 1) * 4
    if packet_length < 4 || offset + packet_length > data.length() {
      raise InvalidCompoundPacket("RTCP packet length exceeds compound packet")
    }
    if header.padding && offset + packet_length != data.length() {
      raise InvalidCompoundPacket(
        "only the last RTCP packet may contain padding",
      )
    }
    let packet_data = data[offset:offset + packet_length].to_owned()
    ignore(validate_single_packet(packet_data))
    packets.push(classify_packet(packet_data, header.packet_type))
    offset += packet_length
  }
  packets
}

///|
pub fn encode_compound(packets : Array[Packet]) -> Bytes raise RtcpError {
  if packets.is_empty() {
    raise InvalidCompoundPacket("RTCP compound packet is empty")
  }
  let output : Array[Byte] = []
  for index = 0; index < packets.length(); index = index + 1 {
    let wire = packets[index].marshal()
    let header = decode_header(wire)
    if header.padding && index != packets.length() - 1 {
      raise InvalidCompoundPacket(
        "only the last RTCP packet may contain padding",
      )
    }
    for byte in wire {
      output.push(byte)
    }
  }
  Bytes::from_array(output)
}

///|
fn build_packet(
  count : Byte,
  packet_type : Byte,
  body : Bytes,
) -> Bytes raise RtcpError {
  if count > 31 {
    raise InvalidPacket("RTCP count/format exceeds five bits")
  }
  let total_length = 4 + body.length()
  if total_length % 4 != 0 {
    raise InvalidPacket("RTCP packet body must be 32-bit aligned")
  }
  if total_length > 262144 {
    raise PacketTooLarge(total_length)
  }
  let word_length = total_length / 4 - 1
  if word_length > 0xffff {
    raise PacketTooLarge(total_length)
  }
  let output : Array[Byte] = Array(capacity=total_length)
  output.push(0x80 | count)
  output.push(packet_type)
  rtcp_write_u16(output, word_length.to_uint16())
  for byte in body {
    output.push(byte)
  }
  Bytes::from_array(output)
}

///|
fn encode_reception_report(
  output : Array[Byte],
  report : ReceptionReport,
) -> Unit raise RtcpError {
  if report.cumulative_lost < -8388608 || report.cumulative_lost > 8388607 {
    raise InvalidPacket("RTCP cumulative loss does not fit in signed 24 bits")
  }
  rtcp_write_u32(output, report.ssrc)
  output.push(report.fraction_lost)
  rtcp_write_u24(
    output,
    report.cumulative_lost.reinterpret_as_uint() & 0xffffffU,
  )
  rtcp_write_u32(output, report.extended_sequence_number)
  rtcp_write_u32(output, report.jitter)
  rtcp_write_u32(output, report.last_sender_report)
  rtcp_write_u32(output, report.delay_since_last_sender_report)
}

///|
fn decode_reception_report(
  data : Bytes,
  offset : Int,
) -> ReceptionReport raise RtcpError {
  if offset + 24 > data.length() {
    raise InvalidPacket("truncated RTCP reception report")
  }
  let lost_raw = rtcp_read_u24(data, offset + 5)
  let cumulative_lost = if (lost_raw & 0x800000U) != 0U {
    lost_raw.reinterpret_as_int() - 0x1000000
  } else {
    lost_raw.reinterpret_as_int()
  }
  ReceptionReport::new(
    ssrc=rtcp_read_u32(data, offset),
    fraction_lost=data[offset + 4],
    cumulative_lost~,
    extended_sequence_number=rtcp_read_u32(data, offset + 8),
    jitter=rtcp_read_u32(data, offset + 12),
    last_sender_report=rtcp_read_u32(data, offset + 16),
    delay_since_last_sender_report=rtcp_read_u32(data, offset + 20),
  )
}

///|
pub fn SenderReportPacket::to_packet(
  self : SenderReportPacket,
) -> Packet raise RtcpError {
  if self.reports.length() > 31 {
    raise InvalidPacket("RTCP sender report contains more than 31 reports")
  }
  if self.profile_extensions.length() % 4 != 0 {
    raise InvalidPacket("RTCP profile extension must be 32-bit aligned")
  }
  let body : Array[Byte] = []
  rtcp_write_u32(body, self.sender_ssrc)
  rtcp_write_u64(body, self.ntp_timestamp)
  rtcp_write_u32(body, self.rtp_timestamp)
  rtcp_write_u32(body, self.packet_count)
  rtcp_write_u32(body, self.octet_count)
  for report in self.reports {
    encode_reception_report(body, report)
  }
  for byte in self.profile_extensions {
    body.push(byte)
  }
  SenderReport(
    build_packet(self.reports.length().to_byte(), 200, Bytes::from_array(body)),
  )
}

///|
pub fn SenderReportPacket::marshal(
  self : SenderReportPacket,
) -> Bytes raise RtcpError {
  self.to_packet().marshal()
}

///|
pub fn Packet::as_sender_report(
  self : Packet,
) -> SenderReportPacket raise RtcpError {
  guard self is SenderReport(data) else {
    raise InvalidPacket("RTCP packet is not a sender report")
  }
  let header = validate_single_packet(data)
  let body_end = unpadded_end(data, header)
  let report_count = header.count.to_int()
  let required = 28 + report_count * 24
  if body_end < required {
    raise InvalidPacket("truncated RTCP sender report")
  }
  let reports : Array[ReceptionReport] = []
  for index = 0; index < report_count; index = index + 1 {
    reports.push(decode_reception_report(data, 28 + index * 24))
  }
  SenderReportPacket::new(
    sender_ssrc=rtcp_read_u32(data, 4),
    ntp_timestamp=rtcp_read_u64(data, 8),
    rtp_timestamp=rtcp_read_u32(data, 16),
    packet_count=rtcp_read_u32(data, 20),
    octet_count=rtcp_read_u32(data, 24),
    reports~,
    profile_extensions=data[required:body_end].to_owned(),
  )
}

///|
pub fn ReceiverReportPacket::to_packet(
  self : ReceiverReportPacket,
) -> Packet raise RtcpError {
  if self.reports.length() > 31 || self.profile_extensions.length() % 4 != 0 {
    raise InvalidPacket("invalid RTCP receiver report")
  }
  let body : Array[Byte] = []
  rtcp_write_u32(body, self.sender_ssrc)
  for report in self.reports {
    encode_reception_report(body, report)
  }
  for byte in self.profile_extensions {
    body.push(byte)
  }
  ReceiverReport(
    build_packet(self.reports.length().to_byte(), 201, Bytes::from_array(body)),
  )
}

///|
pub fn ReceiverReportPacket::marshal(
  self : ReceiverReportPacket,
) -> Bytes raise RtcpError {
  self.to_packet().marshal()
}

///|
pub fn Packet::as_receiver_report(
  self : Packet,
) -> ReceiverReportPacket raise RtcpError {
  guard self is ReceiverReport(data) else {
    raise InvalidPacket("RTCP packet is not a receiver report")
  }
  let header = validate_single_packet(data)
  let body_end = unpadded_end(data, header)
  let report_count = header.count.to_int()
  let required = 8 + report_count * 24
  if body_end < required {
    raise InvalidPacket("truncated RTCP receiver report")
  }
  let reports : Array[ReceptionReport] = []
  for index = 0; index < report_count; index = index + 1 {
    reports.push(decode_reception_report(data, 8 + index * 24))
  }
  ReceiverReportPacket::new(
    sender_ssrc=rtcp_read_u32(data, 4),
    reports~,
    profile_extensions=data[required:body_end].to_owned(),
  )
}

///|
fn unpadded_end(data : Bytes, header : Header) -> Int {
  if header.padding {
    data.length() - data[data.length() - 1].to_int()
  } else {
    data.length()
  }
}

///|
fn sdes_type_code(item_type : SourceDescriptionType) -> Byte {
  match item_type {
    Cname => 1
    Name => 2
    Email => 3
    Phone => 4
    Location => 5
    Tool => 6
    Note => 7
    Private => 8
    Unknown(code) => code
  }
}

///|
fn sdes_type_from_code(code : Byte) -> SourceDescriptionType {
  match code {
    1 => Cname
    2 => Name
    3 => Email
    4 => Phone
    5 => Location
    6 => Tool
    7 => Note
    8 => Private
    _ => Unknown(code)
  }
}

///|
pub fn SourceDescriptionPacket::to_packet(
  self : SourceDescriptionPacket,
) -> Packet raise RtcpError {
  if self.chunks.length() > 31 {
    raise InvalidPacket("RTCP SDES packet contains more than 31 chunks")
  }
  let body : Array[Byte] = []
  for chunk in self.chunks {
    rtcp_write_u32(body, chunk.source)
    for item in chunk.items {
      let code = sdes_type_code(item.item_type)
      if code == 0 || item.value.length() > 255 {
        raise InvalidPacket("invalid RTCP SDES item")
      }
      body.push(code)
      body.push(item.value.length().to_byte())
      for byte in item.value {
        body.push(byte)
      }
    }
    body.push(0)
    while body.length() % 4 != 0 {
      body.push(0)
    }
  }
  SourceDescription(
    build_packet(self.chunks.length().to_byte(), 202, Bytes::from_array(body)),
  )
}

///|
pub fn SourceDescriptionPacket::marshal(
  self : SourceDescriptionPacket,
) -> Bytes raise RtcpError {
  self.to_packet().marshal()
}

///|
pub fn Packet::as_source_description(
  self : Packet,
) -> SourceDescriptionPacket raise RtcpError {
  guard self is SourceDescription(data) else {
    raise InvalidPacket("RTCP packet is not SDES")
  }
  let header = validate_single_packet(data)
  let end = unpadded_end(data, header)
  let chunks : Array[SourceDescriptionChunk] = []
  let mut offset = 4
  for chunk_index = 0
      chunk_index < header.count.to_int()
      chunk_index = chunk_index + 1 {
    if offset + 4 > end {
      raise InvalidPacket("truncated RTCP SDES chunk")
    }
    let source = rtcp_read_u32(data, offset)
    offset += 4
    let items : Array[SourceDescriptionItem] = []
    let mut terminated = false
    while offset < end {
      let code = data[offset]
      offset += 1
      if code == 0 {
        terminated = true
        break
      }
      if offset >= end {
        raise InvalidPacket("truncated RTCP SDES item length")
      }
      let length = data[offset].to_int()
      offset += 1
      if offset + length > end {
        raise InvalidPacket("truncated RTCP SDES item value")
      }
      items.push(
        SourceDescriptionItem::new(
          item_type=sdes_type_from_code(code),
          value=data[offset:offset + length].to_owned(),
        ),
      )
      offset += length
    }
    if !terminated {
      raise InvalidPacket("RTCP SDES chunk has no terminator")
    }
    while offset % 4 != 0 && offset < end {
      if data[offset] != 0 {
        raise InvalidPacket("RTCP SDES padding must be zero")
      }
      offset += 1
    }
    chunks.push(SourceDescriptionChunk::new(source~, items~))
  }
  if offset != end {
    raise InvalidPacket("RTCP SDES contains trailing data")
  }
  SourceDescriptionPacket::new(chunks~)
}

///|
pub fn GoodbyePacket::to_packet(self : GoodbyePacket) -> Packet raise RtcpError {
  if self.sources.is_empty() || self.sources.length() > 31 {
    raise InvalidPacket("RTCP BYE requires between one and 31 sources")
  }
  let body : Array[Byte] = []
  for source in self.sources {
    rtcp_write_u32(body, source)
  }
  match self.reason {
    Some(reason) => {
      if reason.length() > 255 {
        raise InvalidPacket("RTCP BYE reason exceeds 255 bytes")
      }
      body.push(reason.length().to_byte())
      for byte in reason {
        body.push(byte)
      }
      while body.length() % 4 != 0 {
        body.push(0)
      }
    }
    None => ()
  }
  Goodbye(
    build_packet(self.sources.length().to_byte(), 203, Bytes::from_array(body)),
  )
}

///|
pub fn GoodbyePacket::marshal(self : GoodbyePacket) -> Bytes raise RtcpError {
  self.to_packet().marshal()
}

///|
pub fn Packet::as_goodbye(self : Packet) -> GoodbyePacket raise RtcpError {
  guard self is Goodbye(data) else {
    raise InvalidPacket("RTCP packet is not BYE")
  }
  let header = validate_single_packet(data)
  let end = unpadded_end(data, header)
  let source_count = header.count.to_int()
  let required = 4 + source_count * 4
  if source_count == 0 || required > end {
    raise InvalidPacket("truncated RTCP BYE")
  }
  let sources : Array[UInt] = []
  for index = 0; index < source_count; index = index + 1 {
    sources.push(rtcp_read_u32(data, 4 + index * 4))
  }
  let reason : Bytes? = if required == end {
    None
  } else {
    let reason_length = data[required].to_int()
    if required + 1 + reason_length > end {
      raise InvalidPacket("truncated RTCP BYE reason")
    }
    for byte in data[required + 1 + reason_length:end] {
      if byte != 0 {
        raise InvalidPacket("RTCP BYE padding must be zero")
      }
    }
    Some(data[required + 1:required + 1 + reason_length].to_owned())
  }
  GoodbyePacket::new(sources~, reason?)
}

///|
pub fn ApplicationDefinedPacket::to_packet(
  self : ApplicationDefinedPacket,
) -> Packet raise RtcpError {
  if self.subtype > 31 || self.name.length() != 4 || self.data.length() % 4 != 0 {
    raise InvalidPacket("invalid RTCP APP packet")
  }
  let body : Array[Byte] = []
  rtcp_write_u32(body, self.ssrc)
  for byte in self.name {
    body.push(byte)
  }
  for byte in self.data {
    body.push(byte)
  }
  ApplicationDefined(build_packet(self.subtype, 204, Bytes::from_array(body)))
}

///|
pub fn ApplicationDefinedPacket::marshal(
  self : ApplicationDefinedPacket,
) -> Bytes raise RtcpError {
  self.to_packet().marshal()
}

///|
pub fn Packet::as_application_defined(
  self : Packet,
) -> ApplicationDefinedPacket raise RtcpError {
  guard self is ApplicationDefined(data) else {
    raise InvalidPacket("RTCP packet is not APP")
  }
  let header = validate_single_packet(data)
  let end = unpadded_end(data, header)
  if end < 12 {
    raise InvalidPacket("truncated RTCP APP packet")
  }
  ApplicationDefinedPacket::new(
    subtype=header.count,
    ssrc=rtcp_read_u32(data, 4),
    name=data[8:12].to_owned(),
    data=data[12:end].to_owned(),
  )
}

///|
pub fn TransportLayerNackPacket::to_packet(
  self : TransportLayerNackPacket,
) -> Packet raise RtcpError {
  let body : Array[Byte] = []
  rtcp_write_u32(body, self.sender_ssrc)
  rtcp_write_u32(body, self.media_ssrc)
  for nack in self.nacks {
    rtcp_write_u16(body, nack.packet_id)
    rtcp_write_u16(body, nack.lost_packets)
  }
  TransportFeedback(build_packet(1, 205, Bytes::from_array(body)))
}

///|
pub fn TransportLayerNackPacket::marshal(
  self : TransportLayerNackPacket,
) -> Bytes raise RtcpError {
  self.to_packet().marshal()
}

///|
pub fn Packet::as_transport_layer_nack(
  self : Packet,
) -> TransportLayerNackPacket raise RtcpError {
  guard self is TransportFeedback(data) else {
    raise InvalidPacket("RTCP packet is not transport feedback")
  }
  let header = validate_single_packet(data)
  let end = unpadded_end(data, header)
  if header.count != 1 || end < 12 || (end - 12) % 4 != 0 {
    raise InvalidPacket("RTCP feedback packet is not a valid NACK")
  }
  let nacks : Array[NackPair] = []
  for offset = 12; offset < end; offset = offset + 4 {
    nacks.push({
      packet_id: rtcp_read_u16(data, offset),
      lost_packets: rtcp_read_u16(data, offset + 2),
    })
  }
  {
    sender_ssrc: rtcp_read_u32(data, 4),
    media_ssrc: rtcp_read_u32(data, 8),
    nacks,
  }
}

///|
fn transport_wide_symbol(status : TransportWideStatus) -> UInt16 {
  match status {
    PacketNotReceived => 0
    PacketReceivedSmallDelta(_) => 1
    PacketReceivedLargeDelta(_) => 2
    PacketReceivedWithoutDelta => 3
  }
}

///|
fn validate_transport_wide_status(
  status : TransportWideStatus,
) -> Unit raise RtcpError {
  match status {
    PacketReceivedSmallDelta(delta) if delta < 0 ||
      delta > 63750 ||
      delta % 250 != 0 =>
      raise InvalidPacket("invalid transport-cc small receive delta")
    PacketReceivedLargeDelta(delta) if delta < -8192000 ||
      delta > 8191750 ||
      delta % 250 != 0 =>
      raise InvalidPacket("invalid transport-cc large receive delta")
    _ => ()
  }
}

///|
pub fn TransportWideCcPacket::to_packet(
  self : TransportWideCcPacket,
) -> Packet raise RtcpError {
  if self.reference_time > 0xffffffU || self.statuses.length() > 0xffff {
    raise InvalidPacket("invalid RTCP transport-cc header")
  }
  let body : Array[Byte] = []
  rtcp_write_u32(body, self.sender_ssrc)
  rtcp_write_u32(body, self.media_ssrc)
  rtcp_write_u16(body, self.base_sequence_number)
  rtcp_write_u16(body, self.statuses.length().to_uint16())
  rtcp_write_u24(body, self.reference_time)
  body.push(self.feedback_packet_count)
  let mut offset = 0
  while offset < self.statuses.length() {
    let symbol = transport_wide_symbol(self.statuses[offset])
    let mut run_length = 1
    while offset + run_length < self.statuses.length() &&
          run_length < 0x1fff &&
          transport_wide_symbol(self.statuses[offset + run_length]) == symbol {
      run_length += 1
    }
    rtcp_write_u16(body, (symbol << 13) | run_length.to_uint16())
    offset += run_length
  }
  for status in self.statuses {
    validate_transport_wide_status(status)
    match status {
      PacketReceivedSmallDelta(delta) => body.push((delta / 250).to_byte())
      PacketReceivedLargeDelta(delta) =>
        rtcp_write_u16(body, (delta / 250).reinterpret_as_uint().to_uint16())
      PacketNotReceived | PacketReceivedWithoutDelta => ()
    }
  }
  while body.length() % 4 != 0 {
    body.push(0)
  }
  TransportFeedback(build_packet(15, 205, Bytes::from_array(body)))
}

///|
pub fn TransportWideCcPacket::marshal(
  self : TransportWideCcPacket,
) -> Bytes raise RtcpError {
  self.to_packet().marshal()
}

///|
pub fn Packet::as_transport_wide_cc(
  self : Packet,
) -> TransportWideCcPacket raise RtcpError {
  guard self is TransportFeedback(data) else {
    raise InvalidPacket("RTCP packet is not transport feedback")
  }
  let header = validate_single_packet(data)
  let end = unpadded_end(data, header)
  if header.count != 15 || end < 20 {
    raise InvalidPacket("RTCP feedback packet is not transport-cc")
  }
  let status_count = rtcp_read_u16(data, 14).to_int()
  let symbols : Array[UInt16] = []
  let mut offset = 20
  while symbols.length() < status_count {
    if offset + 2 > end {
      raise InvalidPacket("truncated transport-cc status chunks")
    }
    let chunk = rtcp_read_u16(data, offset)
    offset += 2
    if (chunk & 0x8000) == 0 {
      let symbol = (chunk >> 13) & 0x0003
      let run_length = (chunk & 0x1fff).to_int()
      if run_length == 0 || symbols.length() + run_length > status_count {
        raise InvalidPacket("invalid transport-cc run-length chunk")
      }
      for run_index = 0; run_index < run_length; run_index = run_index + 1 {
        symbols.push(symbol)
      }
    } else if (chunk & 0x4000) == 0 {
      for bit = 13; bit >= 0 && symbols.length() < status_count; bit = bit - 1 {
        symbols.push((chunk >> bit) & 0x0001)
      }
    } else {
      for pair = 0; pair < 7 && symbols.length() < status_count; pair = pair + 1 {
        let shift = 12 - pair * 2
        symbols.push((chunk >> shift) & 0x0003)
      }
    }
  }
  let statuses : Array[TransportWideStatus] = []
  for symbol in symbols {
    match symbol {
      0 => statuses.push(PacketNotReceived)
      1 => {
        if offset >= end {
          raise InvalidPacket("truncated transport-cc small delta")
        }
        statuses.push(PacketReceivedSmallDelta(data[offset].to_int() * 250))
        offset += 1
      }
      2 => {
        if offset + 2 > end {
          raise InvalidPacket("truncated transport-cc large delta")
        }
        let encoded = rtcp_read_u16(data, offset).to_uint()
        let units = if (encoded & 0x8000U) != 0U {
          encoded.reinterpret_as_int() - 0x10000
        } else {
          encoded.reinterpret_as_int()
        }
        statuses.push(PacketReceivedLargeDelta(units * 250))
        offset += 2
      }
      _ => statuses.push(PacketReceivedWithoutDelta)
    }
  }
  if end - offset > 3 {
    raise InvalidPacket("transport-cc packet contains trailing data")
  }
  for byte in data[offset:end] {
    if byte != 0 {
      raise InvalidPacket("transport-cc alignment padding must be zero")
    }
  }
  TransportWideCcPacket::new(
    sender_ssrc=rtcp_read_u32(data, 4),
    media_ssrc=rtcp_read_u32(data, 8),
    base_sequence_number=rtcp_read_u16(data, 12),
    reference_time=rtcp_read_u24(data, 16),
    feedback_packet_count=data[19],
    statuses~,
  )
}

///|
pub fn PictureLossIndicationPacket::to_packet(
  self : PictureLossIndicationPacket,
) -> Packet raise RtcpError {
  let body : Array[Byte] = []
  rtcp_write_u32(body, self.sender_ssrc)
  rtcp_write_u32(body, self.media_ssrc)
  PayloadFeedback(build_packet(1, 206, Bytes::from_array(body)))
}

///|
pub fn PictureLossIndicationPacket::marshal(
  self : PictureLossIndicationPacket,
) -> Bytes raise RtcpError {
  self.to_packet().marshal()
}

///|
pub fn Packet::as_picture_loss_indication(
  self : Packet,
) -> PictureLossIndicationPacket raise RtcpError {
  guard self is PayloadFeedback(data) else {
    raise InvalidPacket("RTCP packet is not payload feedback")
  }
  let header = validate_single_packet(data)
  let end = unpadded_end(data, header)
  if header.count != 1 || end != 12 {
    raise InvalidPacket("RTCP feedback packet is not a valid PLI")
  }
  { sender_ssrc: rtcp_read_u32(data, 4), media_ssrc: rtcp_read_u32(data, 8), }
}

///|
pub fn FullIntraRequestPacket::to_packet(
  self : FullIntraRequestPacket,
) -> Packet raise RtcpError {
  let body : Array[Byte] = []
  rtcp_write_u32(body, self.sender_ssrc)
  rtcp_write_u32(body, self.media_ssrc)
  for entry in self.entries {
    rtcp_write_u32(body, entry.ssrc)
    body.push(entry.sequence_number)
    body.push(0)
    body.push(0)
    body.push(0)
  }
  PayloadFeedback(build_packet(4, 206, Bytes::from_array(body)))
}

///|
pub fn FullIntraRequestPacket::marshal(
  self : FullIntraRequestPacket,
) -> Bytes raise RtcpError {
  self.to_packet().marshal()
}

///|
pub fn Packet::as_full_intra_request(
  self : Packet,
) -> FullIntraRequestPacket raise RtcpError {
  guard self is PayloadFeedback(data) else {
    raise InvalidPacket("RTCP packet is not payload feedback")
  }
  let header = validate_single_packet(data)
  let end = unpadded_end(data, header)
  if header.count != 4 || end < 12 || (end - 12) % 8 != 0 {
    raise InvalidPacket("RTCP feedback packet is not a valid FIR")
  }
  let entries : Array[FullIntraRequestEntry] = []
  for offset = 12; offset < end; offset = offset + 8 {
    if data[offset + 5] != 0 || data[offset + 6] != 0 || data[offset + 7] != 0 {
      raise InvalidPacket("RTCP FIR reserved bytes are nonzero")
    }
    entries.push({
      ssrc: rtcp_read_u32(data, offset),
      sequence_number: data[offset + 4],
    })
  }
  {
    sender_ssrc: rtcp_read_u32(data, 4),
    media_ssrc: rtcp_read_u32(data, 8),
    entries,
  }
}

///|
fn encode_remb_bitrate(bitrate : UInt64) -> (Byte, UInt) raise RtcpError {
  let mut exponent : Byte = 0
  let mut mantissa = bitrate
  while mantissa > 0x3ffffUL && exponent < 63 {
    mantissa = mantissa >> 1
    exponent += 1
  }
  if mantissa > 0x3ffffUL {
    raise PacketTooLarge(0)
  }
  (exponent, mantissa.to_uint())
}

///|
pub fn ReceiverEstimatedMaximumBitratePacket::to_packet(
  self : ReceiverEstimatedMaximumBitratePacket,
) -> Packet raise RtcpError {
  if self.ssrcs.length() > 255 {
    raise InvalidPacket("RTCP REMB contains more than 255 SSRC values")
  }
  let (exponent, mantissa) = encode_remb_bitrate(self.bitrate)
  let body : Array[Byte] = []
  rtcp_write_u32(body, self.sender_ssrc)
  rtcp_write_u32(body, 0U)
  body.push(0x52)
  body.push(0x45)
  body.push(0x4d)
  body.push(0x42)
  body.push(self.ssrcs.length().to_byte())
  body.push(((exponent.to_uint() << 2) | (mantissa >> 16)).to_byte())
  body.push((mantissa >> 8).to_byte())
  body.push(mantissa.to_byte())
  for ssrc in self.ssrcs {
    rtcp_write_u32(body, ssrc)
  }
  PayloadFeedback(build_packet(15, 206, Bytes::from_array(body)))
}

///|
pub fn ReceiverEstimatedMaximumBitratePacket::marshal(
  self : ReceiverEstimatedMaximumBitratePacket,
) -> Bytes raise RtcpError {
  self.to_packet().marshal()
}

///|
pub fn Packet::as_receiver_estimated_maximum_bitrate(
  self : Packet,
) -> ReceiverEstimatedMaximumBitratePacket raise RtcpError {
  guard self is PayloadFeedback(data) else {
    raise InvalidPacket("RTCP packet is not payload feedback")
  }
  let header = validate_single_packet(data)
  let end = unpadded_end(data, header)
  if header.count != 15 || end < 20 || data[12:16] != b"REMB" {
    raise InvalidPacket("RTCP feedback packet is not REMB")
  }
  let count = data[16].to_int()
  if end != 20 + count * 4 {
    raise InvalidPacket("RTCP REMB SSRC count does not match its length")
  }
  let exponent = data[17].to_uint() >> 2
  let mantissa = ((data[17].to_uint() & 0x03) << 16) |
    (data[18].to_uint() << 8) |
    data[19].to_uint()
  let ssrcs : Array[UInt] = []
  for index = 0; index < count; index = index + 1 {
    ssrcs.push(rtcp_read_u32(data, 20 + index * 4))
  }
  ReceiverEstimatedMaximumBitratePacket::new(
    sender_ssrc=rtcp_read_u32(data, 4),
    bitrate=mantissa.to_uint64() << exponent.reinterpret_as_int(),
    ssrcs~,
  )
}

///|
pub fn ExtendedReportPacket::to_packet(
  self : ExtendedReportPacket,
) -> Packet raise RtcpError {
  let body : Array[Byte] = []
  rtcp_write_u32(body, self.sender_ssrc)
  for block in self.blocks {
    if block.payload.length() % 4 != 0 || block.payload.length() / 4 > 0xffff {
      raise InvalidPacket("invalid RTCP XR block length")
    }
    body.push(block.block_type)
    body.push(block.type_specific)
    rtcp_write_u16(body, (block.payload.length() / 4).to_uint16())
    for byte in block.payload {
      body.push(byte)
    }
  }
  ExtendedReport(build_packet(0, 207, Bytes::from_array(body)))
}

///|
pub fn ExtendedReportPacket::marshal(
  self : ExtendedReportPacket,
) -> Bytes raise RtcpError {
  self.to_packet().marshal()
}

///|
pub fn Packet::as_extended_report(
  self : Packet,
) -> ExtendedReportPacket raise RtcpError {
  guard self is ExtendedReport(data) else {
    raise InvalidPacket("RTCP packet is not an extended report")
  }
  let header = validate_single_packet(data)
  let end = unpadded_end(data, header)
  if header.count != 0 || end < 8 {
    raise InvalidPacket("invalid RTCP extended report header")
  }
  let blocks : Array[ExtendedReportBlock] = []
  let mut offset = 8
  while offset < end {
    if offset + 4 > end {
      raise InvalidPacket("truncated RTCP XR block header")
    }
    let payload_length = rtcp_read_u16(data, offset + 2).to_int() * 4
    if offset + 4 + payload_length > end {
      raise InvalidPacket("truncated RTCP XR block")
    }
    blocks.push(
      ExtendedReportBlock::new(
        block_type=data[offset],
        type_specific=data[offset + 1],
        payload=data[offset + 4:offset + 4 + payload_length].to_owned(),
      ),
    )
    offset += 4 + payload_length
  }
  { sender_ssrc: rtcp_read_u32(data, 4), blocks, }
}