Skip to content

BREAKING(encoding/unstable): Replace encodeRaw with encodeInto & Remove decodeRaw #6513

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Apr 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
383ca5f
rename(encoding): calcMax to calcBase32Size
BlackAsLight Mar 25, 2025
eb2752a
rename(encoding): calcMax to calcBase64Size
BlackAsLight Mar 25, 2025
0d77cd1
rename(encoding): calcMax to calcHexSize
BlackAsLight Mar 25, 2025
fb8657f
move(encoding): variables from unstable_base32.ts to _common32.ts
BlackAsLight Mar 25, 2025
390a0d6
refactor(encoding): base32 stream not depend on encode/decodeRawBase32
BlackAsLight Mar 25, 2025
0f1020f
convert(encoding): encodeRawBase32 to encodeBase32Into
BlackAsLight Mar 25, 2025
8ba3f96
remove(encoding): decodeRawBase32
BlackAsLight Mar 25, 2025
4c5652a
move(encoding): variables from unstable_base64.ts to _common64.ts
BlackAsLight Mar 25, 2025
52c298a
refactor(encoding): base64 stream not depend on encode/decodeRawBase64
BlackAsLight Mar 25, 2025
a2cf484
convert(encoding): encodeRawBase64 to encodeBase64Into
BlackAsLight Mar 25, 2025
7bb1b59
remove(encoding): decodeRawBase64
BlackAsLight Mar 25, 2025
ad7605d
move(encoding): variables from unstable_hex.ts to _common16.ts
BlackAsLight Mar 25, 2025
4bdb75f
refactor(encoding): hex stream not depend on encode/decodeRawHex
BlackAsLight Mar 25, 2025
e2a48dc
convert(encoding): encodeRawHex to encodeHexInto
BlackAsLight Mar 25, 2025
1ece6d9
remove(encoding): decodeRawHex
BlackAsLight Mar 25, 2025
fee5816
fix(encoding): error message
BlackAsLight Mar 25, 2025
43c9a38
fix(encoding): typings for older version of typescript
BlackAsLight Mar 26, 2025
a86788d
rename(encoding): calcBase64Size to calcSizeBase64 & encodeBase64Into…
BlackAsLight Mar 27, 2025
fe83c8b
Merge branch 'main' into encoding_raw_to_into
BlackAsLight Mar 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions encoding/_common16.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
import type { Uint8Array_ } from "./_types.ts";
export type { Uint8Array_ };

export const alphabet = new TextEncoder().encode("0123456789abcdef");
export const rAlphabet = new Uint8Array(128).fill(16); // alphabet.Hex.length
alphabet.forEach((byte, i) => rAlphabet[byte] = i);
new TextEncoder()
.encode("ABCDEF")
.forEach((byte, i) => rAlphabet[byte] = i + 10);

/**
* Calculate the output size needed to encode a given input size for
* {@linkcode encodeRawHex}.
Expand All @@ -13,12 +20,12 @@ export type { Uint8Array_ };
* @example Basic Usage
* ```ts
* import { assertEquals } from "@std/assert";
* import { calcMax } from "@std/encoding/unstable-hex";
* import { calcSizeHex } from "@std/encoding/unstable-hex";
*
* assertEquals(calcMax(1), 2);
* assertEquals(calcSizeHex(1), 2);
* ```
*/
export function calcMax(originalSize: number): number {
export function calcSizeHex(originalSize: number): number {
return originalSize * 2;
}

Expand Down
33 changes: 28 additions & 5 deletions encoding/_common32.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,46 @@
import type { Uint8Array_ } from "./_types.ts";
export type { Uint8Array_ };

export const padding = "=".charCodeAt(0);
export const alphabet: Record<Base32Format, Uint8Array> = {
Base32: new TextEncoder().encode("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"),
Base32Hex: new TextEncoder().encode("0123456789ABCDEFGHIJKLMNOPQRSTUV"),
Base32Crockford: new TextEncoder().encode("0123456789ABCDEFGHJKMNPQRSTVWXYZ"),
};
export const rAlphabet: Record<Base32Format, Uint8Array> = {
Base32: new Uint8Array(128).fill(32), // alphabet.Base32.length
Base32Hex: new Uint8Array(128).fill(32),
Base32Crockford: new Uint8Array(128).fill(32),
};
alphabet.Base32
.forEach((byte, i) => rAlphabet.Base32[byte] = i);
alphabet.Base32Hex
.forEach((byte, i) => rAlphabet.Base32Hex[byte] = i);
alphabet.Base32Crockford
.forEach((byte, i) => rAlphabet.Base32Crockford[byte] = i);

/**
* The base 32 encoding formats.
*/
export type Base32Format = "Base32" | "Base32Hex" | "Base32Crockford";

/**
* Calculate the output size needed to encode a given input size for
* {@linkcode encodeRawBase32}.
*
* @param originalSize The size of the input buffer.
* @param rawSize The size of the input buffer.
* @returns The size of the output buffer.
*
* @example Basic Usage
* ```ts
* import { assertEquals } from "@std/assert";
* import { calcMax } from "@std/encoding/unstable-base32";
* import { calcSizeBase32 } from "@std/encoding/unstable-base32";
*
* assertEquals(calcMax(1), 8);
* assertEquals(calcSizeBase32(1), 8);
* ```
*/
export function calcMax(originalSize: number): number {
return ((originalSize + 4) / 5 | 0) * 8;
export function calcSizeBase32(rawSize: number): number {
return ((rawSize + 4) / 5 | 0) * 8;
}

export function encode(
Expand Down
27 changes: 24 additions & 3 deletions encoding/_common64.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,27 @@
import type { Uint8Array_ } from "./_types.ts";
export type { Uint8Array_ };

export const padding = "=".charCodeAt(0);
export const alphabet: Record<Base64Format, Uint8Array> = {
Base64: new TextEncoder()
.encode("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"),
Base64Url: new TextEncoder()
.encode("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"),
};
export const rAlphabet: Record<Base64Format, Uint8Array> = {
Base64: new Uint8Array(128).fill(64), // alphabet.Base64.length
Base64Url: new Uint8Array(128).fill(64),
};
alphabet.Base64
.forEach((byte, i) => rAlphabet.Base64[byte] = i);
alphabet.Base64Url
.forEach((byte, i) => rAlphabet.Base64Url[byte] = i);

/**
* The base 64 encoding formats.
*/
export type Base64Format = "Base64" | "Base64Url";

/**
* Calculate the output size needed to encode a given input size for
* {@linkcode encodeRawBase64}.
Expand All @@ -13,12 +34,12 @@ export type { Uint8Array_ };
* @example Basic Usage
* ```ts
* import { assertEquals } from "@std/assert";
* import { calcMax } from "@std/encoding/unstable-base64";
* import { calcSizeBase64 } from "@std/encoding/unstable-base64";
*
* assertEquals(calcMax(1), 4);
* assertEquals(calcSizeBase64(1), 4);
* ```
*/
export function calcMax(originalSize: number): number {
export function calcSizeBase64(originalSize: number): number {
return ((originalSize + 2) / 3 | 0) * 4;
}

Expand Down
4 changes: 2 additions & 2 deletions encoding/base32.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
* @module
*/

import { calcMax, decode, encode } from "./_common32.ts";
import { calcSizeBase32, decode, encode } from "./_common32.ts";
import { detach } from "./_common_detach.ts";
import type { Uint8Array_ } from "./_types.ts";
export type { Uint8Array_ };
Expand Down Expand Up @@ -58,7 +58,7 @@ export function encodeBase32(data: ArrayBuffer | Uint8Array | string): string {
else data = data.slice();
const [output, i] = detach(
data as Uint8Array_,
calcMax((data as Uint8Array_).length),
calcSizeBase32((data as Uint8Array_).length),
);
encode(output, i, 0, alphabet, padding);
return new TextDecoder().decode(output);
Expand Down
4 changes: 2 additions & 2 deletions encoding/base64.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
* @module
*/

import { calcMax, decode, encode } from "./_common64.ts";
import { calcSizeBase64, decode, encode } from "./_common64.ts";
import { detach } from "./_common_detach.ts";
import type { Uint8Array_ } from "./_types.ts";
export type { Uint8Array_ };
Expand Down Expand Up @@ -56,7 +56,7 @@ export function encodeBase64(data: ArrayBuffer | Uint8Array | string): string {
else data = data.slice();
const [output, i] = detach(
data as Uint8Array_,
calcMax((data as Uint8Array_).length),
calcSizeBase64((data as Uint8Array_).length),
);
encode(output, i, 0, alphabet, padding);
return new TextDecoder().decode(output);
Expand Down
4 changes: 2 additions & 2 deletions encoding/base64url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* @module
*/

import { calcMax, decode, encode } from "./_common64.ts";
import { calcSizeBase64, decode, encode } from "./_common64.ts";
import { detach } from "./_common_detach.ts";
import type { Uint8Array_ } from "./_types.ts";
export type { Uint8Array_ };
Expand Down Expand Up @@ -45,7 +45,7 @@ export function encodeBase64Url(
else data = data.slice();
const [output, i] = detach(
data as Uint8Array_,
calcMax((data as Uint8Array_).length),
calcSizeBase64((data as Uint8Array_).length),
);
let o = encode(output, i, 0, alphabet, padding);
o = output.indexOf(padding, o - 2);
Expand Down
4 changes: 2 additions & 2 deletions encoding/hex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
* @module
*/

import { calcMax, decode, encode } from "./_common16.ts";
import { calcSizeHex, decode, encode } from "./_common16.ts";
import { detach } from "./_common_detach.ts";
import type { Uint8Array_ } from "./_types.ts";
export type { Uint8Array_ };
Expand Down Expand Up @@ -61,7 +61,7 @@ export function encodeHex(src: string | Uint8Array | ArrayBuffer): string {
else src = src.slice();
const [output, i] = detach(
src as Uint8Array_,
calcMax((src as Uint8Array_).length),
calcSizeHex((src as Uint8Array_).length),
);
encode(output, i, 0, alphabet);
return new TextDecoder().decode(output);
Expand Down
150 changes: 43 additions & 107 deletions encoding/unstable_base32.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,33 +23,18 @@

import type { Uint8Array_ } from "./_types.ts";
export type { Uint8Array_ };
import { calcMax, decode, encode } from "./_common32.ts";
export { calcMax };
import {
alphabet,
type Base32Format,
calcSizeBase32,
decode,
encode,
padding,
rAlphabet,
} from "./_common32.ts";
export { type Base32Format, calcSizeBase32 };
import { detach } from "./_common_detach.ts";

const padding = "=".charCodeAt(0);
const alphabet: Record<Base32Format, Uint8Array> = {
Base32: new TextEncoder().encode("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"),
Base32Hex: new TextEncoder().encode("0123456789ABCDEFGHIJKLMNOPQRSTUV"),
Base32Crockford: new TextEncoder().encode("0123456789ABCDEFGHJKMNPQRSTVWXYZ"),
};
const rAlphabet: Record<Base32Format, Uint8Array> = {
Base32: new Uint8Array(128).fill(32), // alphabet.Base32.length
Base32Hex: new Uint8Array(128).fill(32),
Base32Crockford: new Uint8Array(128).fill(32),
};
alphabet.Base32
.forEach((byte, i) => rAlphabet.Base32[byte] = i);
alphabet.Base32Hex
.forEach((byte, i) => rAlphabet.Base32Hex[byte] = i);
alphabet.Base32Crockford
.forEach((byte, i) => rAlphabet.Base32Crockford[byte] = i);

/**
* The base 32 encoding formats.
*/
export type Base32Format = "Base32" | "Base32Hex" | "Base32Crockford";

/**
* `encodeBase32` takes an input source and encodes it into a base32 string. If
* a {@linkcode Uint8Array<ArrayBuffer>} or {@linkcode ArrayBuffer} is provided,
Expand Down Expand Up @@ -98,63 +83,63 @@
}
const [output, i] = detach(
input as Uint8Array_,
calcMax((input as Uint8Array_).length),
calcSizeBase32((input as Uint8Array_).length),
);
encode(output, i, 0, alphabet[format], padding);
return new TextDecoder().decode(output);
}

/**
* `encodeRawBase32` is a low-level function that encodes a
* {@linkcode Uint8Array<ArrayBuffer>} to base32 in place. The function assumes
* that the raw data starts at param {@linkcode i} and ends at the end of the
* buffer, and that the entire buffer provided is large enough to hold the
* encoded data.
* `encodeIntoBase32` takes an input source and encodes it as base32 into the
* output buffer.
*
* @experimental **UNSTABLE**: New API, yet to be vetted.
*
* @param buffer The buffer to encode in place.
* @param i The index of where the raw data starts reading from.
* @param o The index of where the encoded data starts writing to.
* @param format The format to use for encoding.
* @returns The index of where the encoded data finished writing to.
* @param input the source to encode.
* @param output the buffer to write the encoded source to.
* @param format the format to use for encoding.
* @returns the number of bytes written to the buffer.
*
* @example Basic Usage
* ```ts
* import { assertEquals } from "@std/assert";
* import { calcMax, encodeBase32, encodeRawBase32 } from "@std/encoding/unstable-base32";
* import {
* calcSizeBase32,
* encodeBase32,
* encodeIntoBase32,
* } from "@std/encoding/unstable-base32";
*
* const prefix = new TextEncoder().encode("data:url/fake,");
* const prefix = "data:url/fake,";
* const input = await Deno.readFile("./deno.lock");
* const output = new Uint8Array(prefix.length + calcSizeBase32(input.length));
*
* const originalSize = input.length;
* const newSize = prefix.length + calcMax(originalSize);
* const i = newSize - originalSize;
* const o = prefix.length;
*
* // deno-lint-ignore no-explicit-any
* const output = new Uint8Array((input.buffer as any).transfer(newSize));
* output.set(output.subarray(0, originalSize), i);
* output.set(prefix);
*
* encodeRawBase32(output, i, o, "Base32");
* const o = new TextEncoder().encodeInto(prefix, output).written;
* encodeIntoBase32(input, output.subarray(o), "Base32");
* assertEquals(
* new TextDecoder().decode(output),
* "data:url/fake," + encodeBase32(await Deno.readFile("./deno.lock"), "Base32"),
* "data:url/fake," +
* encodeBase32(await Deno.readFile("./deno.lock"), "Base32"),
* );
* ```
*/
export function encodeRawBase32(
buffer: Uint8Array_,
i: number,
o: number,
export function encodeIntoBase32(
input: string | Uint8Array_ | ArrayBuffer,
output: Uint8Array_,
format: Base32Format = "Base32",
): number {
const max = calcMax(buffer.length - i);
if (max > buffer.length - o) {
throw new RangeError("Cannot encode buffer as base32: Buffer too small");
if (typeof input === "string") {
input = new TextEncoder().encode(input) as Uint8Array_;

Check warning on line 131 in encoding/unstable_base32.ts

View check run for this annotation

Codecov / codecov/patch

encoding/unstable_base32.ts#L131

Added line #L131 was not covered by tests
} else if (input instanceof ArrayBuffer) {
input = new Uint8Array(input);
}
return encode(buffer, i, o, alphabet[format], padding);
const min = calcSizeBase32((input as Uint8Array_).length);
if (output.length < min) {
throw new RangeError("Cannot encode input as base32: Output too small");
}
output = output.subarray(0, min);
const i = min - (input as Uint8Array_).length;
output.set(input as Uint8Array_, i);
return encode(output, i, 0, alphabet[format], padding);
}

/**
Expand Down Expand Up @@ -199,52 +184,3 @@
}
return input.subarray(0, decode(input, 0, 0, rAlphabet[format], padding));
}

/**
* `decodeRawHex` is a low-level function that decodes a
* {@linkcode Uint8Array<ArrayBuffer>} from hexadecimal in place. Param
* {@linkcode i} must be greater than or equal to param {@linkcode o}. The
* function assumes that the encoded data starts at param {@linkcode i} and ends
* at the end of the buffer.
*
* @experimental **UNSTABLE**: New API, yet to be vetted.
*
* @param buffer The buffer to decode in place.
* @param i The index of where the encoded data starts reading from.
* @param o The index of where the decoded data starts writing to.
* @param format The format to use for decoding.
* @returns The index of where the decoded data finished writing to.
*
* @example Basic Usage
* ```ts
* import { assertEquals } from "@std/assert";
* import {
* decodeRawBase32,
* encodeBase32,
* type Uint8Array_,
* } from "@std/encoding/unstable-base32";
*
* let buffer = new TextEncoder().encode(
* "data:url/fake," + encodeBase32(await Deno.readFile("./deno.lock"), "Base32"),
* ) as Uint8Array_;
*
* const i = buffer.indexOf(",".charCodeAt(0)) + 1;
* const o = decodeRawBase32(buffer, i, i, "Base32");
*
* buffer = buffer.subarray(i, o);
* assertEquals(buffer, await Deno.readFile("./deno.lock"));
* ```
*/
export function decodeRawBase32(
buffer: Uint8Array_,
i: number,
o: number,
format: Base32Format = "Base32",
): number {
if (i < o) {
throw new RangeError(
"Cannot decode buffer as base32: Input (i) must be greater than or equal to output (o)",
);
}
return decode(buffer, i, o, rAlphabet[format], padding);
}
Loading
Loading