Skip to content

Commit a947c0f

Browse files
committed
fix(digest): return correct encoded string for latin1 encoding, validate with test.
1 parent 5aeee8c commit a947c0f

File tree

3 files changed

+110
-8
lines changed

3 files changed

+110
-8
lines changed

packages/graalvm/api/graalvm.api

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4366,6 +4366,7 @@ public abstract interface class elide/runtime/intrinsics/js/node/ConsoleAPI : el
43664366
}
43674367

43684368
public abstract interface class elide/runtime/intrinsics/js/node/CryptoAPI : elide/runtime/intrinsics/js/node/NodeAPI {
4369+
public abstract fun createHash (Ljava/lang/String;)Lelide/runtime/node/crypto/NodeHash;
43694370
public abstract fun randomUUID (Lorg/graalvm/polyglot/Value;)Ljava/lang/String;
43704371
public static synthetic fun randomUUID$default (Lelide/runtime/intrinsics/js/node/CryptoAPI;Lorg/graalvm/polyglot/Value;ILjava/lang/Object;)Ljava/lang/String;
43714372
}
@@ -8890,6 +8891,15 @@ public final synthetic class elide/runtime/node/crypto/$NodeCryptoModule$Introsp
88908891
public fun isBuildable ()Z
88918892
}
88928893

8894+
public final class elide/runtime/node/crypto/NodeHash {
8895+
public fun <init> (Ljava/lang/String;Ljava/security/MessageDigest;Z)V
8896+
public synthetic fun <init> (Ljava/lang/String;Ljava/security/MessageDigest;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V
8897+
public final fun copy ()Lelide/runtime/node/crypto/NodeHash;
8898+
public final fun digest ()Ljava/lang/Object;
8899+
public final fun digest (Ljava/lang/String;)Ljava/lang/Object;
8900+
public final fun update (Ljava/lang/Object;)Lelide/runtime/node/crypto/NodeHash;
8901+
}
8902+
88938903
public synthetic class elide/runtime/node/dgram/$NodeDatagramModule$Definition : io/micronaut/context/AbstractInitializableBeanDefinitionAndReference {
88948904
public static final field $ANNOTATION_METADATA Lio/micronaut/core/annotation/AnnotationMetadata;
88958905
public fun <init> ()V

packages/graalvm/src/main/kotlin/elide/runtime/node/crypto/NodeCryptoHash.kt

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,6 @@ public class NodeHash(
6262
public fun update(data: Any): NodeHash {
6363
if (digested) throw IllegalStateException("Digest already called")
6464

65-
// @TODO(elijahkotyluk) Remove debug line once tests are passing correctly.
66-
// println("NodeHash.update called with data of type: ${data::class}, value: $data")
6765
val bytes = when (data) {
6866
is String -> data.toByteArray(Charsets.UTF_8)
6967
is ByteArray -> data
@@ -118,22 +116,20 @@ public class NodeHash(
118116
* - `null` or `"buffer"`: returns a [ByteArray]
119117
* - `"hex"`: returns a hexadecimal [String]
120118
* - `"base64"`: returns a Base64-encoded [String]
121-
* - `"latin1"`: returns a Latin-1 encoded [String]
119+
* - `"latin1"`: returns a ISO-8859-1 encoded [String]
122120
* @return The computed digest in the specified encoding.
123121
*/
124122
private fun digestInternal(encoding: String? = null): Any {
125-
if (digested) throw IllegalStateException("Digest already called")
123+
if (digested) throw IllegalStateException("Digest has already been called on this Hash instance.")
126124
digested = true
127125
val result = md.digest()
128126

129127
return when (encoding?.lowercase()) {
130128
null, "buffer" -> NodeHostBuffer.wrap(result)
131129
"hex" -> result.joinToString("") { "%02x".format(it) }
132130
"base64" -> Base64.getEncoder().encodeToString(result)
133-
// @TODO(elijahkotyluk) take some time to test and validate this encoding
134-
"latin1" -> result.decodeToString() // ISO-8859-1 is effectively Latin-1 in JVM
135-
// @TODO(elijahkotyluk) better error messaging should be added here
136-
else -> throw IllegalArgumentException("Unsupported encoding: $encoding")
131+
"latin1" -> result.toString(Charsets.ISO_8859_1)
132+
else -> throw IllegalArgumentException("Encoding: ${encoding} is not currently supported.")
137133
}
138134
}
139135

packages/graalvm/src/test/kotlin/elide/runtime/node/NodeCryptoTest.kt

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@
1313
package elide.runtime.node
1414

1515
import org.graalvm.polyglot.Value
16+
import org.junit.jupiter.api.Assertions.assertArrayEquals
1617
import org.junit.jupiter.api.assertThrows
18+
import java.nio.charset.Charset
19+
import java.security.MessageDigest
1720
import kotlin.test.Test
1821
import kotlin.test.assertEquals
1922
import kotlin.test.assertIs
@@ -786,4 +789,97 @@ import elide.testing.annotations.TestCase
786789
);
787790
"""
788791
}
792+
793+
@Test fun `digest should support base64, buffer, hex, and latin1 encodings when specified`() = conforms {
794+
// Base64 encoding
795+
val base64 = crypto.provide().createHash("sha256")
796+
base64.update("hello world")
797+
val base64Digest = base64.digest("base64")
798+
assertEquals(
799+
"uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=",
800+
base64Digest,
801+
"Base64 digest should match expected value"
802+
)
803+
804+
// Buffer encoding
805+
val buffer = crypto.provide().createHash("sha256")
806+
buffer.update("hello world")
807+
val bufferDigest = buffer.digest("buffer")
808+
assertIs<NodeHostBuffer>(bufferDigest, "Buffer digest should be a NodeHostBuffer")
809+
assertEquals(
810+
"b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
811+
bufferDigest.toString("hex", null, null),
812+
"Buffer digest should match expected value"
813+
)
814+
815+
// Hex encoding
816+
val hex = crypto.provide().createHash("sha256")
817+
hex.update("hello world")
818+
819+
val hexDigest = hex.digest("hex")
820+
assertEquals(
821+
"b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
822+
hexDigest,
823+
"Hex digest should match expected value"
824+
)
825+
826+
// Latin1 encoding
827+
val latin1 = crypto.provide().createHash("sha256")
828+
latin1.update("hello world")
829+
val latin1String = latin1.digest("latin1")
830+
831+
assertIs<String>(latin1String, "Latin1 digest should be a String")
832+
val latin1Bytes = latin1String.toByteArray(Charset.forName("ISO-8859-1"))
833+
val expectedLatin1Bytes = MessageDigest.getInstance("SHA-256")
834+
.digest("hello world".toByteArray(Charsets.UTF_8))
835+
836+
assertArrayEquals(expectedLatin1Bytes, latin1Bytes)
837+
}.guest {
838+
//language=javascript
839+
"""
840+
const crypto = require("crypto")
841+
const assert = require("assert")
842+
const { Buffer } = require("buffer");
843+
844+
// Base64 encoding
845+
const base64Digest = crypto.createHash("sha256").update("hello world").digest("base64");
846+
847+
assert.equal(
848+
"uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=",
849+
base64Digest,
850+
"Base64 digest should match expected value"
851+
);
852+
853+
// Buffer encoding
854+
const bufferDigest = crypto.createHash("sha256").update("hello world").digest("buffer")
855+
856+
assert.equal(Buffer.isBuffer(bufferDigest), true, "Default digest output should be a Buffer");
857+
858+
const expectedBufferHex = "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9";
859+
const actualBufferHex = bufferDigest.toString("hex");
860+
861+
assert.equal(
862+
expectedBufferHex,
863+
actualBufferHex,
864+
"Buffer digest should match expected value"
865+
);
866+
867+
// Hex encoding
868+
const hexDigest = crypto.createHash("sha256").update("hello world").digest("hex");
869+
870+
assert.equal(
871+
"b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
872+
hexDigest,
873+
"Hex digest should match expected value"
874+
);
875+
876+
// Latin1 encoding
877+
const latin1Digest = crypto.createHash("sha256").update("hello world").digest("latin1");
878+
879+
const actualLatin1ToHex = Buffer.from(latin1Digest, "latin1").toString("hex");
880+
const expectedLatin1ToHex = "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9";
881+
882+
assert.strict(expectedLatin1ToHex, actualLatin1ToHex, "Latin1 digest bytes should match expected value");
883+
"""
884+
}
789885
}

0 commit comments

Comments
 (0)