///|
pub suberror NfsError {
  InvalidArgument(String)
  ProtocolError(String)
  RpcFailure(String)
  Status(UInt)
  OutcomeUnknown(String)
}

///|
pub fn NfsError::message(self : NfsError) -> String {
  match self {
    InvalidArgument(reason) => "invalid argument: " + reason
    ProtocolError(reason) => "invalid NFS/RPC response: " + reason
    RpcFailure(reason) => "RPC failure: " + reason
    Status(code) => "NFS status \{status_name(code)} (\{code})"
    OutcomeUnknown(reason) => "operation outcome unknown: " + reason
  }
}

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

///|
pub impl Show for NfsError with fn output(self, logger) {
  logger.write_string(self.message())
}

///|
/// TCP connections are sequential: one outstanding RPC per connection.
pub(all) struct Options {
  uid : UInt
  gid : UInt
  groups : Array[UInt]
  timeout_ms : Int
  block_size : Int
  max_record : Int
  max_entries : Int
}

///|
pub fn Options::default() -> Options {
  {
    uid: 65534U,
    gid: 65534U,
    groups: [],
    timeout_ms: 10000,
    block_size: 65536,
    max_record: 1048576,
    max_entries: 100000,
  }
}

///|
pub fn Options::validate(self : Options) -> Unit raise NfsError {
  if self.timeout_ms <= 0 ||
    self.block_size <= 0 ||
    self.max_record < 4096 ||
    self.max_record > 67108864 ||
    self.block_size > self.max_record - 4096 ||
    self.groups.length() > 16 ||
    self.max_entries <= 0 {
    raise InvalidArgument(
      "invalid timeout, buffer budget, entry budget or group count",
    )
  }
}

///|
pub fn checked_offset(offset : UInt64, count : Int) -> UInt64 raise NfsError {
  if count < 0 || offset > 18446744073709551615UL - count.to_uint64() {
    raise InvalidArgument("file offset overflow")
  }
  offset + count.to_uint64()
}

///|
test "default resource limits and offset overflow" {
  Options::default().validate()
  assert_eq(checked_offset(4294967296UL, 1), 4294967297UL)
  try {
    ignore(checked_offset(18446744073709551615UL, 1))
    fail("expected overflow")
  } catch {
    InvalidArgument(_) => ()
    other => raise other
  }
}