///|
pub(all) suberror DtlsError {
  InvalidRecord(String)
  InvalidHandshake(String)
  UnsupportedVersion
  UnsupportedCipherSuite(UInt16)
  UnsupportedSrtpProfile(UInt16)
  HandshakeFailed(String)
  FingerprintMismatch
  ReplayRejected
  CryptoUnavailable(String)
  Time(@transport.TimeError)
  Closed
} derive(Debug, Eq)

///|
pub(all) enum Role {
  Auto
  Client
  Server
} derive(Debug, Eq)

///|
pub(all) enum State {
  New
  Connecting
  Connected
  Closing
  Closed
  Failed
} derive(Debug, Eq)

///|
pub(all) enum FingerprintAlgorithm {
  Sha256
  Sha384
  Sha512
} derive(Debug, Eq)

///|
pub(all) enum CertificateKeyType {
  EcdsaCertificate
  RsaCertificate
} derive(Debug, Eq)

///|
pub struct Fingerprint {
  algorithm : FingerprintAlgorithm
  value : Bytes
} derive(Debug, Eq)

///|
pub fn Fingerprint::new(
  algorithm~ : FingerprintAlgorithm,
  value~ : Bytes,
) -> Fingerprint raise DtlsError {
  let expected_length = match algorithm {
    Sha256 => 32
    Sha384 => 48
    Sha512 => 64
  }
  if value.length() != expected_length {
    raise InvalidHandshake(
      "fingerprint length \{value.length()} does not match its digest",
    )
  }
  { algorithm, value, }
}

///|
pub fn Fingerprint::algorithm(self : Fingerprint) -> FingerprintAlgorithm {
  self.algorithm
}

///|
pub fn Fingerprint::value(self : Fingerprint) -> Bytes {
  self.value
}

///|
pub(all) enum ContentType {
  ChangeCipherSpec
  Alert
  Handshake
  ApplicationData
} derive(Debug, Eq)

///|
pub fn ContentType::code(self : ContentType) -> Byte {
  match self {
    ChangeCipherSpec => 20
    Alert => 21
    Handshake => 22
    ApplicationData => 23
  }
}

///|
pub(all) enum ProtocolVersion {
  Dtls10
  Dtls12
} derive(Debug, Eq)

///|
pub fn ProtocolVersion::major(_self : ProtocolVersion) -> Byte {
  0xfe
}

///|
pub fn ProtocolVersion::minor(self : ProtocolVersion) -> Byte {
  match self {
    Dtls10 => 0xff
    Dtls12 => 0xfd
  }
}

///|
pub(all) enum CipherSuite {
  EcdheEcdsaAes128Ccm
  EcdheEcdsaAes128Ccm8
  EcdheEcdsaAes128GcmSha256
  EcdheRsaAes128GcmSha256
  EcdheEcdsaAes256CbcSha
  EcdheRsaAes256CbcSha
  PskAes128Ccm
  PskAes128Ccm8
  PskAes128GcmSha256
  EcdheRsaChacha20Poly1305Sha256
  EcdheEcdsaChacha20Poly1305Sha256
} derive(Debug, Eq)

///|
pub fn CipherSuite::code(self : CipherSuite) -> UInt16 {
  match self {
    EcdheEcdsaAes128Ccm => 0xc0ac
    EcdheEcdsaAes128Ccm8 => 0xc0ae
    EcdheEcdsaAes128GcmSha256 => 0xc02b
    EcdheRsaAes128GcmSha256 => 0xc02f
    EcdheEcdsaAes256CbcSha => 0xc00a
    EcdheRsaAes256CbcSha => 0xc014
    PskAes128Ccm => 0xc0a4
    PskAes128Ccm8 => 0xc0a8
    PskAes128GcmSha256 => 0x00a8
    EcdheRsaChacha20Poly1305Sha256 => 0xcca8
    EcdheEcdsaChacha20Poly1305Sha256 => 0xcca9
  }
}

///|
pub fn CipherSuite::from_code(code : UInt16) -> CipherSuite raise DtlsError {
  match code {
    0xc0ac => EcdheEcdsaAes128Ccm
    0xc0ae => EcdheEcdsaAes128Ccm8
    0xc02b => EcdheEcdsaAes128GcmSha256
    0xc02f => EcdheRsaAes128GcmSha256
    0xc00a => EcdheEcdsaAes256CbcSha
    0xc014 => EcdheRsaAes256CbcSha
    0xc0a4 => PskAes128Ccm
    0xc0a8 => PskAes128Ccm8
    0x00a8 => PskAes128GcmSha256
    0xcca8 => EcdheRsaChacha20Poly1305Sha256
    0xcca9 => EcdheEcdsaChacha20Poly1305Sha256
    _ => raise UnsupportedCipherSuite(code)
  }
}

///|
pub fn CipherSuite::is_psk(self : CipherSuite) -> Bool {
  match self {
    PskAes128Ccm | PskAes128Ccm8 | PskAes128GcmSha256 => true
    _ => false
  }
}

///|
pub fn CipherSuite::uses_rsa(self : CipherSuite) -> Bool {
  match self {
    EcdheRsaAes128GcmSha256
    | EcdheRsaAes256CbcSha
    | EcdheRsaChacha20Poly1305Sha256 => true
    _ => false
  }
}

///|
pub fn CipherSuite::maximum_record_expansion(self : CipherSuite) -> Int {
  match self {
    EcdheEcdsaAes128Ccm8 | PskAes128Ccm8 => 16
    EcdheEcdsaAes256CbcSha | EcdheRsaAes256CbcSha => 52
    EcdheRsaChacha20Poly1305Sha256 | EcdheEcdsaChacha20Poly1305Sha256 => 16
    _ => 24
  }
}

///|
pub(all) enum SrtpProtectionProfile {
  SrtpAes128CmHmacSha1_80
  SrtpAes128CmHmacSha1_32
  SrtpAeadAes128Gcm
  SrtpAeadAes256Gcm
} derive(Debug, Eq)

///|
pub fn SrtpProtectionProfile::aes128_cm_hmac_sha1_80() -> SrtpProtectionProfile {
  SrtpAes128CmHmacSha1_80
}

///|
pub fn SrtpProtectionProfile::aes128_cm_hmac_sha1_32() -> SrtpProtectionProfile {
  SrtpAes128CmHmacSha1_32
}

///|
pub fn SrtpProtectionProfile::aead_aes128_gcm() -> SrtpProtectionProfile {
  SrtpAeadAes128Gcm
}

///|
pub fn SrtpProtectionProfile::aead_aes256_gcm() -> SrtpProtectionProfile {
  SrtpAeadAes256Gcm
}

///|
pub fn SrtpProtectionProfile::code(self : SrtpProtectionProfile) -> UInt16 {
  match self {
    SrtpAes128CmHmacSha1_80 => 0x0001
    SrtpAes128CmHmacSha1_32 => 0x0002
    SrtpAeadAes128Gcm => 0x0007
    SrtpAeadAes256Gcm => 0x0008
  }
}

///|
pub struct RecordHeader {
  content_type : ContentType
  version : ProtocolVersion
  epoch : UInt16
  sequence_number : UInt64
  content_length : UInt16
} derive(Debug, Eq)

///|
pub fn RecordHeader::new(
  content_type~ : ContentType,
  version? : ProtocolVersion = Dtls12,
  epoch~ : UInt16,
  sequence_number~ : UInt64,
  content_length~ : UInt16,
) -> RecordHeader raise DtlsError {
  if sequence_number > 0x0000ffffffffffffUL {
    raise InvalidRecord("DTLS record sequence number exceeds 48 bits")
  }
  { content_type, version, epoch, sequence_number, content_length, }
}

///|
pub fn RecordHeader::content_type(self : RecordHeader) -> ContentType {
  self.content_type
}

///|
pub fn RecordHeader::version(self : RecordHeader) -> ProtocolVersion {
  self.version
}

///|
pub fn RecordHeader::epoch(self : RecordHeader) -> UInt16 {
  self.epoch
}

///|
pub fn RecordHeader::sequence_number(self : RecordHeader) -> UInt64 {
  self.sequence_number
}

///|
pub fn RecordHeader::content_length(self : RecordHeader) -> UInt16 {
  self.content_length
}

///|
pub struct Record {
  header : RecordHeader
  payload : Bytes
} derive(Debug, Eq)

///|
pub fn Record::new(
  content_type~ : ContentType,
  version? : ProtocolVersion = Dtls12,
  epoch~ : UInt16,
  sequence_number~ : UInt64,
  payload~ : Bytes,
) -> Record raise DtlsError {
  if payload.length() > 0xffff {
    raise InvalidRecord("DTLS record payload exceeds 65535 bytes")
  }
  {
    header: RecordHeader::new(
      content_type~,
      version~,
      epoch~,
      sequence_number~,
      content_length=payload.length().to_uint16(),
    ),
    payload,
  }
}

///|
pub fn Record::header(self : Record) -> RecordHeader {
  self.header
}

///|
pub fn Record::payload(self : Record) -> Bytes {
  self.payload
}

///|
pub(all) enum HandshakeType {
  HelloRequest
  ClientHello
  ServerHello
  HelloVerifyRequest
  Certificate
  ServerKeyExchange
  CertificateRequest
  ServerHelloDone
  CertificateVerify
  ClientKeyExchange
  Finished
  UnknownHandshake(Byte)
} derive(Debug, Eq)

///|
pub fn HandshakeType::code(self : HandshakeType) -> Byte {
  match self {
    HelloRequest => 0
    ClientHello => 1
    ServerHello => 2
    HelloVerifyRequest => 3
    Certificate => 11
    ServerKeyExchange => 12
    CertificateRequest => 13
    ServerHelloDone => 14
    CertificateVerify => 15
    ClientKeyExchange => 16
    Finished => 20
    UnknownHandshake(value) => value
  }
}

///|
pub struct HandshakeFragment {
  handshake_type : HandshakeType
  total_length : UInt
  message_sequence : UInt16
  fragment_offset : UInt
  body : Bytes
} derive(Debug, Eq)

///|
pub fn HandshakeFragment::new(
  handshake_type~ : HandshakeType,
  total_length~ : UInt,
  message_sequence~ : UInt16,
  fragment_offset~ : UInt,
  body~ : Bytes,
) -> HandshakeFragment raise DtlsError {
  if total_length > 0xffffffU ||
    fragment_offset > total_length ||
    body.length() > 0xffffff ||
    fragment_offset.to_uint64() + body.length().to_uint64() >
    total_length.to_uint64() {
    raise InvalidHandshake("invalid DTLS handshake fragment bounds")
  }
  { handshake_type, total_length, message_sequence, fragment_offset, body, }
}

///|
pub fn HandshakeFragment::handshake_type(
  self : HandshakeFragment,
) -> HandshakeType {
  self.handshake_type
}

///|
pub fn HandshakeFragment::total_length(self : HandshakeFragment) -> UInt {
  self.total_length
}

///|
pub fn HandshakeFragment::message_sequence(self : HandshakeFragment) -> UInt16 {
  self.message_sequence
}

///|
pub fn HandshakeFragment::fragment_offset(self : HandshakeFragment) -> UInt {
  self.fragment_offset
}

///|
pub fn HandshakeFragment::body(self : HandshakeFragment) -> Bytes {
  self.body
}

///|
pub fn HandshakeFragment::is_complete(self : HandshakeFragment) -> Bool {
  self.fragment_offset == 0U &&
  self.body.length().reinterpret_as_uint() == self.total_length
}

///|
priv struct EncryptionKeys {
  client_mac_key : Bytes
  server_mac_key : Bytes
  client_write_key : Bytes
  server_write_key : Bytes
  client_write_iv : Bytes
  server_write_iv : Bytes
}