diff --git a/migration.md b/migration.md index 52ea276e..a72105cc 100644 --- a/migration.md +++ b/migration.md @@ -89,7 +89,8 @@ these modules for cross-runtime consumption. make a request, the request could fail with a RequestError or TimeoutError. The RequestError in turn will contain the `cause` such as `NoRespondersError`. This also means that in TypeScript, the callback signature has been relaxed to - just `(Error, Msg)=>void`. For more information see the JsDocs. + just `(Error, Msg)=>void | Promise`. For more information see the + JsDocs. - Previous versions of the client provided the enums `Events` and `DebugEvents` for the `type` values in the notification received via `status(): AsyncIterable` iterator. Starting with this release, @@ -102,7 +103,8 @@ these modules for cross-runtime consumption. - RequestStrategy "Jitter" is now called "stall" to adopt the term used by new implementations in other clients and the RequestStrategy enum is now a type alias to simple strings "timer", "count", "stall", "sentinel". -- `SHA256` a support type for object store has been moved to @nats-io/obj +- `SHA256` a support type for object store has been removed. Client replaced + this library with a dependency. - `Base64Codec`, `Base64UrlCodec`, `Base64UrlPaddedCodec` support types for object store have been moved to @nats-io/obj. @@ -207,6 +209,24 @@ Use `KvOptions.storage` and `KvStatus.storage`. > } > ``` +> [!IMPORTANT] +> +> Clients that use @nats-io/obj prior to 3.0.0-34, potentially generated sha-256 +> digests that were incorrect due to a bug on a 3rd party library bundled in the +> client. The library has been replaced with npm:js-sha256. Errors in the +> digests usually affected 500 MB or larger objects. +> +> If you are upgrading from a previous pre-release or from the previous +> generation clients (these were also updated) you may want to re-put your +> objects or use the tool +> [fix-os-hashes](https://github.com/nats-io/nats.deno/blob/main/bin/fix-os-hashes.ts) +> to be compatible with 3.0.0-34 and beyond (and other NATS tools). To run the +> tool install [deno](https://deno.com/) and: +> +> ``` +> deno run -A https://github.com/nats-io/nats.deno/blob/main/bin/fix-os-hashes.ts -h +> ``` + To use ObjectStore, you must install and import `@nats-io/obj`. ### Watch diff --git a/obj/deno.json b/obj/deno.json index 70465b23..1cdefcdd 100644 --- a/obj/deno.json +++ b/obj/deno.json @@ -34,6 +34,7 @@ }, "imports": { "@nats-io/nats-core": "jsr:@nats-io/nats-core@~3.0.0-50", - "@nats-io/jetstream": "jsr:@nats-io/jetstream@~3.0.0-37" + "@nats-io/jetstream": "jsr:@nats-io/jetstream@~3.0.0-37", + "js-sha256": "npm:js-sha256@0.11.0" } } diff --git a/obj/package.json b/obj/package.json index 9f464beb..f0951bdb 100644 --- a/obj/package.json +++ b/obj/package.json @@ -34,7 +34,8 @@ "description": "obj library - this library implements all the base functionality for NATS objectstore for javascript clients", "dependencies": { "@nats-io/jetstream": "3.0.0-37", - "@nats-io/nats-core": "3.0.0-50" + "@nats-io/nats-core": "3.0.0-50", + "js-sha256": "^0.11.0" }, "devDependencies": { "@types/node": "^22.10.10", diff --git a/obj/src/internal_mod.ts b/obj/src/internal_mod.ts index b6ec8447..d6c03a3f 100644 --- a/obj/src/internal_mod.ts +++ b/obj/src/internal_mod.ts @@ -16,6 +16,4 @@ export { StorageType } from "./types.ts"; export { Objm } from "./objectstore.ts"; -export { SHA256, sha256 } from "./sha256.ts"; - export { Base64Codec, Base64UrlCodec, Base64UrlPaddedCodec } from "./base64.ts"; diff --git a/obj/src/objectstore.ts b/obj/src/objectstore.ts index 671a008e..7a5658cf 100644 --- a/obj/src/objectstore.ts +++ b/obj/src/objectstore.ts @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 The NATS Authors + * Copyright 2022-2025 The NATS Authors * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -68,8 +68,9 @@ import type { ObjectStoreStatus, ObjectWatchInfo, } from "./types.ts"; -import { SHA256 } from "./sha256.ts"; import { Base64UrlPaddedCodec } from "./base64.ts"; +import { sha256 } from "js-sha256"; +import { checkSha256, parseSha256 } from "./sha_digest.parser.ts"; export const osPrefix = "OBJ_"; export const digestType = "SHA-256="; @@ -470,7 +471,7 @@ export class ObjectStoreImpl implements ObjectStore { const db = new DataBuffer(); try { const reader = rs ? rs.getReader() : null; - const sha = new SHA256(); + const sha = sha256.create(); while (true) { const { done, value } = reader @@ -491,10 +492,11 @@ export class ObjectStoreImpl implements ObjectStore { // prepare the metadata info.mtime = new Date().toISOString(); - const digest = sha.digest("base64"); - const pad = digest.length % 3; - const padding = pad > 0 ? "=".repeat(pad) : ""; - info.digest = `${digestType}${digest}${padding}`; + const digest = Base64UrlPaddedCodec.encode( + Uint8Array.from(sha.digest()), + ); + info.digest = `${digestType}${digest}`; + info.deleted = false; // trailing md for the object @@ -640,6 +642,16 @@ export class ObjectStoreImpl implements ObjectStore { return os.get(ln); } + if (!info.digest.startsWith(digestType)) { + return Promise.reject(new Error(`unknown digest type: ${info.digest}`)); + } + const digest = parseSha256(info.digest.substring(8)); + if (digest === null) { + return Promise.reject( + new Error(`unable to parse digest: ${info.digest}`), + ); + } + const d = deferred(); const r: Partial = { @@ -652,7 +664,7 @@ export class ObjectStoreImpl implements ObjectStore { return Promise.resolve(r as ObjectResult); } - const sha = new SHA256(); + const sha = sha256.create(); let controller: ReadableStreamDefaultController; const cc: Partial = {}; @@ -667,12 +679,8 @@ export class ObjectStoreImpl implements ObjectStore { controller!.enqueue(jm.data); } if (jm.info.pending === 0) { - const hash = sha.digest("base64"); - // go pads the hash - which should be multiple of 3 - otherwise pads with '=' - const pad = hash.length % 3; - const padding = pad > 0 ? "=".repeat(pad) : ""; - const digest = `${digestType}${hash}${padding}`; - if (digest !== info.digest) { + const digest = Uint8Array.from(sha.digest()); + if (!checkSha256(digest, Uint8Array.from(sha.digest()))) { controller!.error( new Error( `received a corrupt object, digests do not match received: ${info.digest} calculated ${digest}`, diff --git a/obj/src/sha256.ts b/obj/src/sha256.ts deleted file mode 100644 index ff8f9c7c..00000000 --- a/obj/src/sha256.ts +++ /dev/null @@ -1,360 +0,0 @@ -// deno-fmt-ignore-file -// deno-lint-ignore-file -// This code was bundled using `deno bundle` and it's not recommended to edit it manually - -// deno bundle https://deno.land/x/sha256@v1.0.2/mod.ts - -// The MIT License (MIT) -// -// Original work (c) Marco Paland (marco@paland.com) 2015-2018, PALANDesign Hannover, Germany -// -// Deno port Copyright (c) 2019 Noah Anabiik Schwarz -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -function getLengths(b64: string): [number, number] { - const len = b64.length; - let validLen = b64.indexOf("="); - if (validLen === -1) { - validLen = len; - } - const placeHoldersLen = validLen === len ? 0 : 4 - validLen % 4; - return [ - validLen, - placeHoldersLen - ]; -} -function init(lookup: string[], revLookup: number[], urlsafe: boolean = false) { - function _byteLength(validLen: number, placeHoldersLen: number): number { - return Math.floor((validLen + placeHoldersLen) * 3 / 4 - placeHoldersLen); - } - function tripletToBase64(num: number): string { - return lookup[num >> 18 & 0x3f] + lookup[num >> 12 & 0x3f] + lookup[num >> 6 & 0x3f] + lookup[num & 0x3f]; - } - function encodeChunk(buf: Uint8Array, start: number, end: number): string { - const out = new Array((end - start) / 3); - for(let i = start, curTriplet = 0; i < end; i += 3){ - out[curTriplet++] = tripletToBase64((buf[i] << 16) + (buf[i + 1] << 8) + buf[i + 2]); - } - return out.join(""); - } - return { - byteLength (b64: string): number { - return _byteLength.apply(null, getLengths(b64)); - }, - toUint8Array (b64: string): Uint8Array { - const [validLen, placeHoldersLen] = getLengths(b64); - const buf = new Uint8Array(_byteLength(validLen, placeHoldersLen)); - const len = placeHoldersLen ? validLen - 4 : validLen; - let tmp; - let curByte = 0; - let i; - for(i = 0; i < len; i += 4){ - tmp = revLookup[b64.charCodeAt(i)] << 18 | revLookup[b64.charCodeAt(i + 1)] << 12 | revLookup[b64.charCodeAt(i + 2)] << 6 | revLookup[b64.charCodeAt(i + 3)]; - buf[curByte++] = tmp >> 16 & 0xff; - buf[curByte++] = tmp >> 8 & 0xff; - buf[curByte++] = tmp & 0xff; - } - if (placeHoldersLen === 2) { - tmp = revLookup[b64.charCodeAt(i)] << 2 | revLookup[b64.charCodeAt(i + 1)] >> 4; - buf[curByte++] = tmp & 0xff; - } else if (placeHoldersLen === 1) { - tmp = revLookup[b64.charCodeAt(i)] << 10 | revLookup[b64.charCodeAt(i + 1)] << 4 | revLookup[b64.charCodeAt(i + 2)] >> 2; - buf[curByte++] = tmp >> 8 & 0xff; - buf[curByte++] = tmp & 0xff; - } - return buf; - }, - fromUint8Array (buf: Uint8Array): string { - const maxChunkLength = 16383; - const len = buf.length; - const extraBytes = len % 3; - const len2 = len - extraBytes; - const parts = new Array(Math.ceil(len2 / 16383) + (extraBytes ? 1 : 0)); - let curChunk = 0; - let chunkEnd; - for(let i = 0; i < len2; i += maxChunkLength){ - chunkEnd = i + maxChunkLength; - parts[curChunk++] = encodeChunk(buf, i, chunkEnd > len2 ? len2 : chunkEnd); - } - let tmp; - if (extraBytes === 1) { - tmp = buf[len2]; - parts[curChunk] = lookup[tmp >> 2] + lookup[tmp << 4 & 0x3f]; - if (!urlsafe) parts[curChunk] += "=="; - } else if (extraBytes === 2) { - tmp = buf[len2] << 8 | buf[len2 + 1] & 0xff; - parts[curChunk] = lookup[tmp >> 10] + lookup[tmp >> 4 & 0x3f] + lookup[tmp << 2 & 0x3f]; - if (!urlsafe) parts[curChunk] += "="; - } - return parts.join(""); - } - }; -} -const lookup: string[] = []; -const revLookup = []; -const code = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; -for(let i = 0, l = code.length; i < l; ++i){ - lookup[i] = code[i]; - revLookup[code.charCodeAt(i)] = i; -} -const { byteLength , toUint8Array , fromUint8Array } = init(lookup, revLookup, true); -const decoder = new TextDecoder(); -const encoder = new TextEncoder(); -function toHexString(buf: Uint8Array) { - return buf.reduce((hex, __byte)=>`${hex}${__byte < 16 ? "0" : ""}${__byte.toString(16)}`, ""); -} -function fromHexString(hex: string): Uint8Array { - const len = hex.length; - if (len % 2 || !/^[0-9a-fA-F]+$/.test(hex)) { - throw new TypeError("Invalid hex string."); - } - hex = hex.toLowerCase(); - const buf = new Uint8Array(Math.floor(len / 2)); - const end = len / 2; - for(let i = 0; i < end; ++i){ - buf[i] = parseInt(hex.substr(i * 2, 2), 16); - } - return buf; -} -function decode(buf: Uint8Array, encoding: string = "utf8"): string { - if (/^utf-?8$/i.test(encoding)) { - return decoder.decode(buf); - } else if (/^base64$/i.test(encoding)) { - return fromUint8Array(buf); - } else if (/^hex(?:adecimal)?$/i.test(encoding)) { - return toHexString(buf); - } else { - throw new TypeError("Unsupported string encoding."); - } -} -function encode(str: string, encoding: string = "utf8"): Uint8Array { - if (/^utf-?8$/i.test(encoding)) { - return encoder.encode(str); - } else if (/^base64$/i.test(encoding)) { - return toUint8Array(str); - } else if (/^hex(?:adecimal)?$/i.test(encoding)) { - return fromHexString(str); - } else { - throw new TypeError("Unsupported string encoding."); - } -} -const BYTES: number = 32; -class SHA256 { - hashSize: number = 32; - _buf: Uint8Array; - _bufIdx!: number; - _count!: Uint32Array; - _K: Uint32Array; - _H!: Uint32Array; - _finalized!: boolean; - constructor(){ - this._buf = new Uint8Array(64); - this._K = new Uint32Array([ - 0x428a2f98, - 0x71374491, - 0xb5c0fbcf, - 0xe9b5dba5, - 0x3956c25b, - 0x59f111f1, - 0x923f82a4, - 0xab1c5ed5, - 0xd807aa98, - 0x12835b01, - 0x243185be, - 0x550c7dc3, - 0x72be5d74, - 0x80deb1fe, - 0x9bdc06a7, - 0xc19bf174, - 0xe49b69c1, - 0xefbe4786, - 0x0fc19dc6, - 0x240ca1cc, - 0x2de92c6f, - 0x4a7484aa, - 0x5cb0a9dc, - 0x76f988da, - 0x983e5152, - 0xa831c66d, - 0xb00327c8, - 0xbf597fc7, - 0xc6e00bf3, - 0xd5a79147, - 0x06ca6351, - 0x14292967, - 0x27b70a85, - 0x2e1b2138, - 0x4d2c6dfc, - 0x53380d13, - 0x650a7354, - 0x766a0abb, - 0x81c2c92e, - 0x92722c85, - 0xa2bfe8a1, - 0xa81a664b, - 0xc24b8b70, - 0xc76c51a3, - 0xd192e819, - 0xd6990624, - 0xf40e3585, - 0x106aa070, - 0x19a4c116, - 0x1e376c08, - 0x2748774c, - 0x34b0bcb5, - 0x391c0cb3, - 0x4ed8aa4a, - 0x5b9cca4f, - 0x682e6ff3, - 0x748f82ee, - 0x78a5636f, - 0x84c87814, - 0x8cc70208, - 0x90befffa, - 0xa4506ceb, - 0xbef9a3f7, - 0xc67178f2 - ]); - this.init(); - } - init(): this { - this._H = new Uint32Array([ - 0x6a09e667, - 0xbb67ae85, - 0x3c6ef372, - 0xa54ff53a, - 0x510e527f, - 0x9b05688c, - 0x1f83d9ab, - 0x5be0cd19 - ]); - this._bufIdx = 0; - this._count = new Uint32Array(2); - this._buf.fill(0); - this._finalized = false; - return this; - } - update(msg: string|Uint8Array|null, inputEncoding?: string): this { - if (msg === null) { - throw new TypeError("msg must be a string or Uint8Array."); - } else if (typeof msg === "string") { - msg = encode(msg, inputEncoding); - } - for(let i = 0, len = msg.length; i < len; i++){ - this._buf[this._bufIdx++] = msg[i]; - if (this._bufIdx === 64) { - this._transform(); - this._bufIdx = 0; - } - } - const c = this._count; - if ((c[0] += msg.length << 3) < msg.length << 3) { - c[1]++; - } - c[1] += msg.length >>> 29; - return this; - } - digest(outputEncoding: string): string|Uint8Array { - if (this._finalized) { - throw new Error("digest has already been called."); - } - this._finalized = true; - const b = this._buf; - let idx = this._bufIdx; - b[idx++] = 0x80; - while(idx !== 56){ - if (idx === 64) { - this._transform(); - idx = 0; - } - b[idx++] = 0; - } - const c = this._count; - b[56] = c[1] >>> 24 & 0xff; - b[57] = c[1] >>> 16 & 0xff; - b[58] = c[1] >>> 8 & 0xff; - b[59] = c[1] >>> 0 & 0xff; - b[60] = c[0] >>> 24 & 0xff; - b[61] = c[0] >>> 16 & 0xff; - b[62] = c[0] >>> 8 & 0xff; - b[63] = c[0] >>> 0 & 0xff; - this._transform(); - const hash = new Uint8Array(32); - for(let i = 0; i < 8; i++){ - hash[(i << 2) + 0] = this._H[i] >>> 24 & 0xff; - hash[(i << 2) + 1] = this._H[i] >>> 16 & 0xff; - hash[(i << 2) + 2] = this._H[i] >>> 8 & 0xff; - hash[(i << 2) + 3] = this._H[i] >>> 0 & 0xff; - } - this.init(); - return outputEncoding ? decode(hash, outputEncoding) : hash; - } - _transform(): void { - const h = this._H; - let h0 = h[0]; - let h1 = h[1]; - let h2 = h[2]; - let h3 = h[3]; - let h4 = h[4]; - let h5 = h[5]; - let h6 = h[6]; - let h7 = h[7]; - const w = new Uint32Array(16); - let i; - for(i = 0; i < 16; i++){ - w[i] = this._buf[(i << 2) + 3] | this._buf[(i << 2) + 2] << 8 | this._buf[(i << 2) + 1] << 16 | this._buf[i << 2] << 24; - } - for(i = 0; i < 64; i++){ - let tmp; - if (i < 16) { - tmp = w[i]; - } else { - let a = w[i + 1 & 15]; - let b = w[i + 14 & 15]; - tmp = w[i & 15] = (a >>> 7 ^ a >>> 18 ^ a >>> 3 ^ a << 25 ^ a << 14) + (b >>> 17 ^ b >>> 19 ^ b >>> 10 ^ b << 15 ^ b << 13) + w[i & 15] + w[i + 9 & 15] | 0; - } - tmp = tmp + h7 + (h4 >>> 6 ^ h4 >>> 11 ^ h4 >>> 25 ^ h4 << 26 ^ h4 << 21 ^ h4 << 7) + (h6 ^ h4 & (h5 ^ h6)) + this._K[i] | 0; - h7 = h6; - h6 = h5; - h5 = h4; - h4 = h3 + tmp; - h3 = h2; - h2 = h1; - h1 = h0; - h0 = tmp + (h1 & h2 ^ h3 & (h1 ^ h2)) + (h1 >>> 2 ^ h1 >>> 13 ^ h1 >>> 22 ^ h1 << 30 ^ h1 << 19 ^ h1 << 10) | 0; - } - h[0] = h[0] + h0 | 0; - h[1] = h[1] + h1 | 0; - h[2] = h[2] + h2 | 0; - h[3] = h[3] + h3 | 0; - h[4] = h[4] + h4 | 0; - h[5] = h[5] + h5 | 0; - h[6] = h[6] + h6 | 0; - h[7] = h[7] + h7 | 0; - } -} -function sha256(msg: string|Uint8Array|null, inputEncoding: string, outputEncoding: string): string|Uint8Array { - return new SHA256().update(msg, inputEncoding).digest(outputEncoding); -} -export { BYTES as BYTES }; -export { SHA256 as SHA256 }; -export { sha256 as sha256 }; - - diff --git a/obj/src/sha_digest.parser.ts b/obj/src/sha_digest.parser.ts new file mode 100644 index 00000000..d663b491 --- /dev/null +++ b/obj/src/sha_digest.parser.ts @@ -0,0 +1,104 @@ +/* + * Copyright 2025 The NATS Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export function parseSha256(s: string): Uint8Array | null { + return toByteArray(s); +} + +function isHex(s: string): boolean { + // contains valid hex characters only + const hexRegex = /^[0-9A-Fa-f]+$/; + if (!hexRegex.test(s)) { + // non-hex characters + return false; + } + + // check for mixed-case strings - paranoid base64 sneaked in + const isAllUpperCase = /^[0-9A-F]+$/.test(s); + const isAllLowerCase = /^[0-9a-f]+$/.test(s); + if (!(isAllUpperCase || isAllLowerCase)) { + return false; + } + + // ensure the input string length is even + return s.length % 2 === 0; +} + +function isBase64(s: string): boolean { + // test for padded or normal base64 + return /^[A-Za-z0-9\-_]*(={0,2})?$/.test(s) || + /^[A-Za-z0-9+/]*(={0,2})?$/.test(s); +} + +function detectEncoding(input: string): "hex" | "b64" | "" { + // hex is more reliable to flush out... + if (isHex(input)) { + return "hex"; + } else if (isBase64(input)) { + return "b64"; + } + return ""; +} + +function hexToByteArray(s: string): Uint8Array { + if (s.length % 2 !== 0) { + throw new Error("hex string must have an even length"); + } + const a = new Uint8Array(s.length / 2); + for (let i = 0; i < s.length; i += 2) { + // parse hex two chars at a time + a[i / 2] = parseInt(s.substring(i, i + 2), 16); + } + return a; +} + +function base64ToByteArray(s: string): Uint8Array { + // could be url friendly + s = s.replace(/-/g, "+"); + s = s.replace(/_/g, "/"); + const sbin = atob(s); + return Uint8Array.from(sbin, (c) => c.charCodeAt(0)); +} + +function toByteArray(input: string): Uint8Array | null { + const encoding = detectEncoding(input); + switch (encoding) { + case "hex": + return hexToByteArray(input); + case "b64": + return base64ToByteArray(input); + } + return null; +} + +export function checkSha256( + a: string | Uint8Array, + b: string | Uint8Array, +): boolean { + const aBytes = typeof a === "string" ? parseSha256(a) : a; + const bBytes = typeof b === "string" ? parseSha256(b) : b; + if (aBytes === null || bBytes === null) { + return false; + } + if (aBytes.length !== bBytes.length) { + return false; + } + for (let i = 0; i < aBytes.length; i++) { + if (aBytes[i] !== bBytes[i]) { + return false; + } + } + return true; +} diff --git a/obj/tests/objectstore_test.ts b/obj/tests/objectstore_test.ts index 00921b08..18e44ffc 100644 --- a/obj/tests/objectstore_test.ts +++ b/obj/tests/objectstore_test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 The NATS Authors + * Copyright 2022-2025 The NATS Authors * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -38,8 +38,8 @@ import type { ObjectInfo, ObjectStoreMeta } from "../src/types.ts"; import { jetstreamManager, StorageType } from "@nats-io/jetstream"; import { equals } from "https://deno.land/std@0.221.0/bytes/mod.ts"; import { digestType, Objm } from "../src/objectstore.ts"; -import { SHA256 } from "../src/sha256.ts"; import { Base64UrlPaddedCodec } from "../src/base64.ts"; +import { sha256 } from "js-sha256"; function readableStreamFrom(data: Uint8Array): ReadableStream { return new ReadableStream({ @@ -85,12 +85,10 @@ function makeData(n: number): Uint8Array { } function digest(data: Uint8Array): string { - const sha = new SHA256(); + const sha = sha256.create(); sha.update(data); - const digest = sha.digest("base64"); - const pad = digest.length % 3; - const padding = pad > 0 ? "=".repeat(pad) : ""; - return `${digestType}${digest}${padding}`; + const digest = Base64UrlPaddedCodec.encode(Uint8Array.from(sha.digest())); + return `${digestType}${digest}`; } Deno.test("objectstore - basics", async () => {