///|
async fn negotiate_structured(tcp : @socket.Tcp) -> Unit {
  send_option(tcp, 8U, b"")
  let header = tcp.read_exactly(20)
  if get64(header, 0) != 0x0003e889045565a9UL || get32(header, 8) != 8U {
    raise Protocol("invalid structured negotiation reply")
  }
  let kind = get32(header, 12)
  let len = get32(header, 16)
  if len > 65536U {
    raise Protocol("option reply too large")
  }
  ignore(tcp.read_exactly(len.reinterpret_as_int()))
  if (kind & 0x80000000U) != 0U {
    raise Server(kind)
  }
  if kind != 1U || len != 0U {
    raise Protocol("invalid structured acknowledgement")
  }
}

///|
async fn structured_command(
  tcp : @socket.Tcp,
  header : Bytes,
  cookie : UInt64,
  offset : UInt64,
  length : Int32,
) -> Bytes {
  if get64(header, 8) != cookie {
    raise Protocol("structured cookie mismatch")
  }
  let flags = get16(header, 4)
  let kind = get16(header, 6)
  let len = get32(tcp.read_exactly(4), 0)
  if len > 4110U {
    raise Protocol("structured command payload too large")
  }
  let body = tcp.read_exactly(len.reinterpret_as_int())
  if (kind & 0x8000U) != 0U {
    raise Server(structured_error_code(kind, body, offset, length))
  }
  if kind != 0U || len != 0U || (flags & 1U) == 0U {
    raise Protocol("invalid structured command reply")
  }
  b""
}

///|
/// Assemble out-of-order chunks, rejecting overlaps and incomplete replies.
async fn read_structured(
  tcp : @socket.Tcp,
  first : Bytes,
  cookie : UInt64,
  start : UInt64,
  length : Int32,
) -> Bytes {
  let output : Array[Byte] = Array::make(length, b'\x00')
  let covered : Array[Bool] = Array::make(length, false)
  let mut total = 0
  let mut header = first
  for _ in 0..<65536 {
    if get32(header, 0) != 0x668e33efU || get64(header, 8) != cookie {
      raise Protocol("invalid structured reply header or cookie")
    }
    let flags = get16(header, 4)
    let kind = get16(header, 6)
    let payload_len = get32(tcp.read_exactly(4), 0)
    if payload_len > length.reinterpret_as_uint() + 8U && payload_len > 4110U {
      raise Protocol("structured payload exceeds request")
    }
    let body = tcp.read_exactly(payload_len.reinterpret_as_int())
    if (kind & 0x8000U) != 0U {
      raise Server(structured_error_code(kind, body, start, length))
    }
    match kind {
      0U =>
        if payload_len != 0U || (flags & 1U) == 0U {
          raise Protocol("invalid empty reply")
        }
      1U | 2U => {
        if (kind == 1U && payload_len <= 8U) ||
          (kind == 2U && payload_len != 12U) {
          raise Protocol("invalid structured data chunk")
        }
        let offset = get64(body, 0)
        let count = if kind == 1U { payload_len - 8U } else { get32(body, 8) }
        if count == 0U ||
          offset < start ||
          offset - start > length.to_uint64() ||
          count.to_uint64() > length.to_uint64() - (offset - start) {
          raise Protocol("structured chunk outside requested range")
        }
        let relative = (offset - start).to_int()
        for i in 0.. raise Protocol("unexpected structured chunk type")
    }
    if (flags & 1U) != 0U {
      if total != length {
        raise Protocol("incomplete structured read")
      }
      return Bytes::from_array(output)
    }
    header = tcp.read_exactly(16)
  }
  raise Protocol("too many structured chunks")
}

///|
fn structured_error_code(
  kind : UInt32,
  body : Bytes,
  start : UInt64,
  length : Int32,
) -> UInt32 raise {
  let fixed = if kind == 0x8002U { 14 } else { 6 }
  if body.length() < fixed || get32(body, 0) == 0U {
    raise Protocol("invalid structured error")
  }
  let message_length = get16(body, 4).reinterpret_as_int()
  if message_length > 4096 || message_length > body.length() - fixed {
    raise Protocol("invalid structured error message length")
  }
  if kind == 0x8002U {
    let offset = get64(body, 6 + message_length)
    if length <= 0 || offset < start || offset - start >= length.to_uint64() {
      raise Protocol("structured error offset outside request")
    }
  }
  get32(body, 0)
}