// SM4 分组密码算法 —— 适配层
// 核心单块加解密委托给 2d5rrr333/toolsfx-moonbit/sm(ISC License,
// 上游按 GB/T 32907-2016 实现并通过国标向量)。
// 本包在上游原语之上提供工作模式:ECB/CBC 委托上游(含 PKCS7 填充);
// CTR 与 GCM 由本项目实现——其中 GCM 为 RFC 8998 附录 A.1
// 官方向量逐字节对拍的符合性实现(上游 GCM 无法通过该对拍,见 README)。

///|
/// SM4 相关错误类型。
pub(all) enum Sm4Error {
  /// 密钥长度不是 16 字节,实际为 `Int` 字节
  InvalidKeyLength(Int)
  /// IV 长度不是 16 字节,实际为 `Int` 字节
  InvalidIvLength(Int)
  /// 密文长度不是 16 的整数倍
  InvalidCiphertextLength(Int)
  /// 密文 PKCS7 填充不合法
  InvalidPadding
  /// GCM 认证标签校验失败(密文、标签或 AAD 被篡改)
  AuthenticationFailed
} derive(Eq, Debug)

///|
pub extend Sm4Error with Eq::{not_equal, equal}

///|
pub extend Sm4Error with @moonbitlang/core/debug.Debug::{to_repr}

///|
/// 将错误转换为可读字符串。
pub fn Sm4Error::to_string(self : Sm4Error) -> String {
  match self {
    InvalidKeyLength(n) =>
      "sm4: 密钥长度必须为 16 字节,实际 \{n} 字节"
    InvalidIvLength(n) =>
      "sm4: IV 长度必须为 16 字节,实际 \{n} 字节"
    InvalidCiphertextLength(n) =>
      "sm4: 密文长度必须为 16 的整数倍,实际 \{n} 字节"
    InvalidPadding => "sm4: PKCS7 填充不合法"
    AuthenticationFailed => "sm4: GCM 认证标签校验失败"
  }
}

///|
/// SM4 密码器:保存已校验的 16 字节密钥,可反复加解密数据块。
pub struct Sm4 {
  /// 16 字节密钥(创建时已校验)
  key : Bytes
}

///|
/// 从 16 字节密钥创建 SM4 密码器。密钥长度错误时返回 [InvalidKeyLength]。
pub fn Sm4::new(key : Bytes) -> Result[Sm4, Sm4Error] {
  if key.length() != 16 {
    return Err(InvalidKeyLength(key.length()))
  }
  Ok({ key, })
}

///|
/// 加密一个 16 字节块。调用契约:`block` 为 16 字节、密钥已校验,
/// 此时上游单块加密不会失败;违反契约视为程序错误。
pub fn Sm4::encrypt_block(self : Sm4, block : Bytes) -> Bytes {
  @sm.sm4_encrypt_block(self.key, block) catch {
    _ =>
      abort(
        "sm4: 上游单块加密失败(契约:16 字节块 + 已校验密钥)",
      )
  }
}

///|
/// 解密一个 16 字节块。调用契约同 [Sm4::encrypt_block]。
pub fn Sm4::decrypt_block(self : Sm4, block : Bytes) -> Bytes {
  @sm.sm4_decrypt_block(self.key, block) catch {
    _ =>
      abort(
        "sm4: 上游单块解密失败(契约:16 字节块 + 已校验密钥)",
      )
  }
}

///|
/// 校验密钥长度并创建密码器(内部便捷函数)。
fn sm4_of(key : Bytes) -> Result[Sm4, Sm4Error] {
  Sm4::new(key)
}

///|
/// ECB 模式加密(含 PKCS7 填充,委托上游实现)。
pub fn encrypt_ecb(key : Bytes, plaintext : Bytes) -> Result[Bytes, Sm4Error] {
  if key.length() != 16 {
    return Err(InvalidKeyLength(key.length()))
  }
  Ok(@sm.sm4_ecb_encrypt(key, plaintext)) catch {
    _ => Err(InvalidKeyLength(key.length()))
  }
}

///|
/// ECB 模式解密(去 PKCS7 填充,委托上游实现)。
pub fn decrypt_ecb(key : Bytes, ciphertext : Bytes) -> Result[Bytes, Sm4Error] {
  if key.length() != 16 {
    return Err(InvalidKeyLength(key.length()))
  }
  if ciphertext.length() == 0 || ciphertext.length() % 16 != 0 {
    return Err(InvalidCiphertextLength(ciphertext.length()))
  }
  Ok(@sm.sm4_ecb_decrypt(key, ciphertext)) catch {
    // 长度已预检,上游报错只可能是填充不合法
    _ => Err(InvalidPadding)
  }
}

///|
/// CBC 模式加密(含 PKCS7 填充,委托上游实现)。`iv` 必须为 16 字节。
pub fn encrypt_cbc(
  key : Bytes,
  iv : Bytes,
  plaintext : Bytes,
) -> Result[Bytes, Sm4Error] {
  if key.length() != 16 {
    return Err(InvalidKeyLength(key.length()))
  }
  if iv.length() != 16 {
    return Err(InvalidIvLength(iv.length()))
  }
  Ok(@sm.sm4_cbc_encrypt(key, iv, plaintext)) catch {
    _ => Err(InvalidKeyLength(key.length()))
  }
}

///|
/// CBC 模式解密(去 PKCS7 填充,委托上游实现)。`iv` 必须为 16 字节。
pub fn decrypt_cbc(
  key : Bytes,
  iv : Bytes,
  ciphertext : Bytes,
) -> Result[Bytes, Sm4Error] {
  if key.length() != 16 {
    return Err(InvalidKeyLength(key.length()))
  }
  if iv.length() != 16 {
    return Err(InvalidIvLength(iv.length()))
  }
  if ciphertext.length() == 0 || ciphertext.length() % 16 != 0 {
    return Err(InvalidCiphertextLength(ciphertext.length()))
  }
  Ok(@sm.sm4_cbc_decrypt(key, iv, ciphertext)) catch {
    // 长度已预检,上游报错只可能是填充不合法
    _ => Err(InvalidPadding)
  }
}