// JA4 and JA4S Client/Server Fingerprinting implementation

///|
fn sort_u16(arr : Array[UInt16]) -> Array[UInt16] {
  let out = Array::new(capacity=arr.length())
  for v in arr {
    out.push(v)
  }
  let len = out.length()
  for i in 0.. String {
  let parts : Array[String] = []
  for v in arr {
    parts.push(uint16_to_hex4(v))
  }
  parts.join(",")
}

///|
pub fn ClientHello::ja4_a_str(self : ClientHello) -> String {
  let ver_str = match self.record_version.to_int() {
    0x0304 => "13"
    0x0303 => "12"
    0x0302 => "11"
    0x0301 => "10"
    _ => "12"
  }
  let sni_mark = if self.server_name is Some(_) { "d" } else { "i" }
  let active_ciphers = self.cipher_suites.filter(fn(v) { !is_grease(v) })
  let cipher_count = active_ciphers.length()
  let active_exts = self.extensions.filter(fn(v) { !is_grease(v) })
  let ext_count = active_exts.length()
  let alpn_str = if self.alpn_protocols.length() > 0 {
    let first = self.alpn_protocols[0]
    if first.length() >= 2 {
      first[0:2]
    } else if first.length() == 1 {
      first + "0"
    } else {
      "00"
    }
  } else {
    "00"
  }
  let cc = if cipher_count >= 99 {
    "99"
  } else {
    cipher_count.to_string().pad_start(2, '0')
  }
  let ec = if ext_count >= 99 {
    "99"
  } else {
    ext_count.to_string().pad_start(2, '0')
  }
  "t\{ver_str}\{sni_mark}\{cc}\{ec}\{alpn_str}"
}

///|
pub fn ClientHello::ja4_b_str(self : ClientHello) -> String {
  let active_ciphers = self.cipher_suites.filter(fn(v) { !is_grease(v) })
  let sorted = sort_u16(active_ciphers)
  let formatted = format_u16_hex_list(sorted)
  let hash = sha256_hex(ascii_bytes(formatted))
  if hash.length() >= 12 {
    hash[0:12].to_owned()
  } else {
    hash
  }
}

///|
pub fn ClientHello::ja4_c_str(self : ClientHello) -> String {
  let active_exts = self.extensions.filter(fn(v) { !is_grease(v) })
  let sorted_exts = sort_u16(active_exts)
  let formatted_exts = format_u16_hex_list(sorted_exts)

  let active_sigs = self.signature_algorithms.filter(fn(v) { !is_grease(v) })
  let sorted_sigs = sort_u16(active_sigs)
  let formatted_sigs = format_u16_hex_list(sorted_sigs)

  let formatted = "\{formatted_exts}_\{formatted_sigs}"
  let hash = sha256_hex(ascii_bytes(formatted))
  if hash.length() >= 12 {
    hash[0:12].to_owned()
  } else {
    hash
  }
}

///|
pub fn ClientHello::ja4(self : ClientHello) -> String {
  self.ja4_a_str() + "_" + self.ja4_b_str() + "_" + self.ja4_c_str()
}

///|
pub fn ServerHello::ja4s_a_str(self : ServerHello) -> String {
  let ver_str = match self.version.to_int() {
    0x0304 => "13"
    0x0303 => "12"
    0x0302 => "11"
    0x0301 => "10"
    _ => "12"
  }
  let alpn_str = if self.selected_alpn is Some(al) {
    if al.length() >= 2 {
      al[0:2]
    } else if al.length() == 1 {
      al + "0"
    } else {
      "00"
    }
  } else {
    "00"
  }
  let active_exts = self.extensions.filter(fn(v) { !is_grease(v) })
  let ext_count = active_exts.length()
  let ec = if ext_count >= 99 {
    "99"
  } else {
    ext_count.to_string().pad_start(2, '0')
  }
  "s\{ver_str}s\{alpn_str}\{ec}"
}

///|
pub fn ServerHello::ja4s_b_str(self : ServerHello) -> String {
  uint16_to_hex4(self.cipher_suite)
}

///|
pub fn ServerHello::ja4s_c_str(self : ServerHello) -> String {
  let active_exts = self.extensions.filter(fn(v) { !is_grease(v) })
  let sorted = sort_u16(active_exts)
  let formatted = format_u16_hex_list(sorted)
  let hash = sha256_hex(ascii_bytes(formatted))
  if hash.length() >= 12 {
    hash[0:12].to_owned()
  } else {
    hash
  }
}

///|
pub fn ServerHello::ja4s(self : ServerHello) -> String {
  self.ja4s_a_str() + "_" + self.ja4s_b_str() + "_" + self.ja4s_c_str()
}