package gs.std.v0 import java.security.MessageDigest import java.nio.charset.Charset import java.nio.charset.StandardCharsets /** * Opaque type representing a MD5 hash. */ opaque type MD5 = Array[Byte] /** * Opaque type representing a MD5 hash. */ object MD5: /** * MD5 hashes are exactly 16 bytes. */ final val NumberOfBytes: ByteCount = ByteCount(16) /** * The algorithm name is "MD5". */ final val Algorithm: String = "MD5" /** * Instantiate a [[MD5]] from the given byte array. This function does not * know whether the array is actually a calculated hash. * * Typically used for loading pre-validated hashes (e.g. from a database). * * @param bytes The bytes - must contain exactly 16 bytes. * @return The new [[MD5]] instance. */ def fromBytes(bytes: Array[Byte]): MD5 = if NumberOfBytes.equal(bytes.length) then bytes else throw IllegalArgumentException(s"MD5 values must be exactly $NumberOfBytes bytes.") /** * Calculate the MD5 hash for the given byte array. * * @param data The byte array. * @return The calculated [[MD5]]. */ def calculate(data: Array[Byte]): MD5 = MessageDigest.getInstance(Algorithm).digest(data) /* * Calculate the MD5 hash for the given string. * * @param data The string data. * @param charset The character set of the string. Defaults to UTF-8. * @return The calculated [[MD5]]. */ def calculate(data: String, charset: Charset = StandardCharsets.UTF_8): MD5 = calculate(data.getBytes(charset)) extension (md5: MD5) /** * @return The underlying byte array. */ def unwrap(): Array[Byte] = md5 /** * @return The underlying byte array. */ def toBytes(): Array[Byte] = md5 /** * Get the byte at the given index (0 to 15). * * Throws an exception if an out-of-bound index is given. * * @param index The byte index (0 to 15). * @return The byte at the specified index. */ def getByte(index: Int): Byte = if index < 0 || index >= NumberOfBytes.unwrap() then throw IndexOutOfBoundsException(s"Index $index out of MD5 bound of $NumberOfBytes bytes.") else md5.apply(index) /** * Determine if this hash is the same as some other hash. Compares each byte * in order. * * @param other The [[MD5]] to compare against. * @return True if the hashes are identical, false otherwise. */ def isSame(other: MD5): Boolean = md5.sameElements(other) /** * @return This hash encoded to a Base64 string. */ def base64(): EncodedString = Base64Encoder.encode(md5) /** * @return This hash encoded to a Hexadecimal string. */ def hex(): EncodedString = HexEncoder.encode(md5) end MD5