import * as Either from "../../Either.js"
import type * as Encoding from "../../Encoding.js"
import { DecodeException } from "./common.js"
/** @internal */
export const encode = (bytes: Uint8Array) => {
const length = bytes.length
let result = ""
let i: number
for (i = 2; i < length; i += 3) {
result += base64abc[bytes[i - 2] >> 2]
result += base64abc[((bytes[i - 2] & 0x03) << 4) | (bytes[i - 1] >> 4)]
result += base64abc[((bytes[i - 1] & 0x0f) << 2) | (bytes[i] >> 6)]
result += base64abc[bytes[i] & 0x3f]
}
if (i === length + 1) {
// 1 octet yet to write
result += base64abc[bytes[i - 2] >> 2]
result += base64abc[(bytes[i - 2] & 0x03) << 4]
result += "=="
}
if (i === length) {
// 2 octets yet to write
result += base64abc[bytes[i - 2] >> 2]
result += base64abc[((bytes[i - 2] & 0x03) << 4) | (bytes[i - 1] >> 4)]
result += base64abc[(bytes[i - 1] & 0x0f) << 2]
result += "="
}
return result
}
/** @internal */
export const decode = (str: string): Either.Either<Uint8Array, Encoding.DecodeException> => {
const stripped = stripCrlf(str)
const length = stripped.length
if (length % 4 !== 0) {
return Either.left(
DecodeException(stripped, `Length must be a multiple of 4, but is ${length}`)
)
}
const index = stripped.indexOf("=")
if (index !== -1 && ((index < length - 2) || (index === length - 2 && stripped[length - 1] !== "="))) {
return Either.left(
DecodeException(stripped, "Found a '=' character, but it is not at the end")
)
}
try {
const missingOctets = stripped.endsWith("==") ? 2 : stripped.endsWith("=") ? 1 : 0
const result = new Uint8Array(3 * (length / 4) - missingOctets)
for (let i = 0, j = 0; i < length; i += 4, j += 3) {
const buffer = getBase64Code(stripped.charCodeAt(i)) << 18 |
getBase64Code(stripped.charCodeAt(i + 1)) << 12 |
getBase64Code(stripped.charCodeAt(i + 2)) << 6 |
getBase64Code(stripped.charCodeAt(i + 3))
result[j] = buffer >> 16
result[j + 1] = (buffer >> 8) & 0xff
result[j + 2] = buffer & 0xff
}
return Either.right(result)
} catch (e) {
return Either.left(
DecodeException(stripped, e instanceof Error ? e.message : "Invalid input")
)
}
}
/** @internal */
export const stripCrlf = (str: string) => str.replace(/[\n\r]/g, "")
/** @internal */
function getBase64Code(charCode: number) {
if (charCode >= base64codes.length) {
throw new TypeError(`Invalid character ${String.fromCharCode(charCode)}`)
}
const code = base64codes[charCode]
if (code === 255) {
throw new TypeError(`Invalid character ${String.fromCharCode(charCode)}`)
}
return code
}
/** @internal */
const base64abc = [
"A",
"B",
"C",
"D",
"E",
"F",
"G",
"H",
"I",
"J",
"K",
"L",
"M",
"N",
"O",
"P",
"Q",
"R",
"S",
"T",
"U",
"V",
"W",
"X",
"Y",
"Z",
"a",
"b",
"c",
"d",
"e",
"f",
"g",
"h",
"i",
"j",
"k",
"l",
"m",
"n",
"o",
"p",
"q",
"r",
"s",
"t",
"u",
"v",
"w",
"x",
"y",
"z",
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"+",
"/"
]
/** @internal */
const base64codes = [
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
62,
255,
255,
255,
63,
52,
53,
54,
55,
56,
57,
58,
59,
60,
61,
255,
255,
255,
0,
255,
255,
255,
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23,
24,
25,
255,
255,
255,
255,
255,
255,
26,
27,
28,
29,
30,
31,
32,
33,
34,
35,
36,
37,
38,
39,
40,
41,
42,
43,
44,
45,
46,
47,
48,
49,
50,
51
]