///|
/// Build and encrypt the plaintext of a SEIPD v1 packet (RFC 9580 §5.13.1):
/// random prefix (bs+2 octets) || inner packets || 0xD3 0x14 || SHA-1 MDC,
/// all in one CFB context with an all-zero IV and no resynchronization.
pub fn seipd_v1_encrypt(
  bc : &@primitives.BlockCipher,
  inner : BytesView,
) -> Bytes raise {
  let prefix = random_prefix(bc.block_size())
  let tail = Bytes::from_array([0xD3, 0x14])
  // Single accumulating buffer (F12): the old Bytes::add chain made three
  // full copies of the plaintext (prefix||inner||tail, then +MDC); this
  // makes exactly one intermediate copy for the SHA-1 input.
  let buf = Buffer(size_hint=prefix.length() + inner.length() + 22)
  buf.write_bytes(prefix.exact_view())
  buf.write_bytes(inner)
  buf.write_bytes(tail.exact_view())
  let mdc = @primitives.Sha1.digest(buf.to_bytes().exact_view())
  buf.write_bytes(mdc.exact_view())
  let zeros = Bytes::make(bc.block_size(), b'\x00')
  cfb_encrypt(bc, zeros, buf.to_bytes().exact_view())
}

///|
/// Decrypt and integrity-check a SEIPD v1 ciphertext. Returns the inner
/// packet stream (prefix and MDC stripped). Raises when the quick check
/// octets or the MDC do not match.
pub fn seipd_v1_decrypt(
  bc : &@primitives.BlockCipher,
  ciphertext : BytesView,
) -> Bytes raise {
  let bs = bc.block_size()
  // Plaintext minimum: (bs+2)-octet prefix || inner || 0xD3 0x14 ||
  // 20-octet MDC. An 18-octet (AES-shaped) bound would still admit
  // ciphertexts whose inner slice inverts to empty for 64-bit ciphers,
  // so the bound follows the block size.
  guard ciphertext.length() >= bs + 2 + 22 else {
    @error.fail("seipd: ciphertext too short")
  }
  let zeros = Bytes::make(bs, b'\x00')
  let full = cfb_decrypt(bc, zeros, ciphertext)
  let len = full.length()
  let stored_mdc = full[len - 20:len]
  let hashed = full[0:len - 20]
  // The two octets before the MDC must be the 0xD3 0x14 constants.
  guard hashed[len - 22] == b'\xD3' && hashed[len - 21] == b'\x14' else {
    @error.fail("seipd: missing MDC header octets")
  }
  let computed = @primitives.Sha1.digest(hashed.exact_view())
  guard @primitives.ct_equal(computed.exact_view(), stored_mdc.exact_view()) else {
    @error.fail("seipd: MDC mismatch (message modified)")
  }
  // Quick check: the last two prefix octets repeat after them. (The old
  // hardcoded 16-byte-block offsets silently rejected every 8-byte-block
  // cipher — CAST5/3DES — exposed by the M2 gpg legacy fixtures.)
  guard full[bs - 2] == full[bs] && full[bs - 1] == full[bs + 1] else {
    @error.fail("seipd: prefix quick check failed (wrong session key?)")
  }
  hashed[bs + 2:len - 22].to_owned()
}