Skip to content

Commit

Permalink
Add Base64 encoding/decoding.
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-kulcsar committed Oct 28, 2024
1 parent ab8de17 commit e34c0e7
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 74 deletions.
123 changes: 123 additions & 0 deletions base64.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
namespace Base64 {
const PADCHAR: string = '='
const ALPHA: string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'

export function decodeBuffer(s: string): Uint8Array {
let size: number = s.length

if (size === 0) {
return new Uint8Array(0)
}
if (size % 4 !== 0) {
throw `Bad string length: ${size}`
}
for (let c of s) {
if (ALPHA.indexOf(c) < 0 && c != PADCHAR) {
throw `Invalid string encoding character: ${c}.`
}
}
/**
* Every four base64 characters = 24 bits = 3 bytes.
* But, we also need to figure out padding, if any.
*/
let bytes: number = 3 * size / 4
let numPad: number = 0
if (s.charAt(size - 1) === '=') {
numPad++
bytes--
}
if (s.charAt(size - 2) === '=') {
numPad++
bytes--
}
const buffer: Uint8Array = new Uint8Array(bytes)
let index: number = 0,
bufferIndex: number = 0,
quantum: number
if (numPad > 0) {
size -= 4 // Handle the last one specially.
}
while (index < size) {
quantum = 0
for (let i: number = 0; i < 4; ++i) {
quantum = (quantum << 6) |
ALPHA.indexOf(s.charAt(index + i))
}
buffer.set(bufferIndex++, (quantum >> 16) & 0xff)
buffer.set(bufferIndex++, (quantum >> 8) & 0xff)
buffer.set(bufferIndex++, quantum & 0xff)
index += 4
}
if (numPad > 0) {
/**
* If numPad == 1, then there is one =, and we have 18 bits with 2 zeroes at the end.
* If numPad == 2, then there are two =, and we have 12 bits with 4 zeroes at the end.
* First, grab the quantum.
*/
quantum = 0
for (let i: number = 0; i < 4 - numPad; ++i) {
quantum = (quantum << 6) |
ALPHA.indexOf(s.charAt(index + i))
}
if (numPad === 1) {
// quantum is 18 bits, but really represents two bytes.
quantum = quantum >> 2
buffer.set(bufferIndex++, (quantum >> 8) & 0xff)
buffer.set(bufferIndex++, quantum & 0xff)
} else {
// quantum is 12 bits, but really represents only one byte.
quantum = quantum >> 4
buffer.set(bufferIndex++, quantum & 0xff)
}
}

return buffer
}

export function encodeBuffer(bytes: ArrayBuffer): string {
const array: Uint8Array = new Uint8Array(0)
array.fromArrayBuffer(bytes)
const base64: string[] = []
let index: number = 0,
quantum: number,
value: number
// Grab as many sets of 3 bytes as we can, which form 24 bits.
while (index + 2 < array.byteLength) {
quantum = (array.get(index) << 16) |
(array.get(index + 1) << 8) |
array.get(index + 2)
value = (quantum >> 18) & 0x3f
base64.push(ALPHA[value])
value = (quantum >> 12) & 0x3f
base64.push(ALPHA[value])
value = (quantum >> 6) & 0x3f
base64.push(ALPHA[value])
value = quantum & 0x3f
base64.push(ALPHA[value])
index += 3
}

// At this point, there are 0, 1, or 2 bytes left.
if (index + 1 === array.byteLength) {
// 8 bits; shift by 4 to pad on the right with 0s to make 12 bits total.
quantum = array.get(index) << 4
value = (quantum >> 6) & 0x3f
base64.push(ALPHA[value])
value = quantum & 0x3f
base64.push(ALPHA[value])
base64.push('==')
} else if (index + 2 === array.byteLength) {
// 16 bits; shift by 2 to pad on the right with 0s to make 18 bits total.
quantum = (array.get(index) << 10) |
(array.get(index + 1) << 2)
value = (quantum >> 12) & 0x3f
base64.push(ALPHA[value])
value = (quantum >> 6) & 0x3f
base64.push(ALPHA[value])
value = quantum & 0x3f
base64.push(ALPHA[value])
base64.push('=')
}
return base64.join('')
}
}
3 changes: 2 additions & 1 deletion pxt.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"int16array.ts",
"unit16array.ts",
"int32array.ts",
"unit32array.ts"
"unit32array.ts",
"base64.ts"
],
"testFiles": [
"test.ts"
Expand Down
73 changes: 0 additions & 73 deletions test.ts
Original file line number Diff line number Diff line change
@@ -1,73 +0,0 @@
let allPassed: boolean = true

function areEquivalent(a: number[], b: ArrayBuffer): boolean {
if (a.length != b.byteLength) {
return false
}
for (let i: number = 0; i < a.length; i++) {
if (a[i] != b.bytes[i]) {
return false
}
}
return true
}

function create(bytes: number[]): ArrayBuffer {
let buffer: ArrayBuffer = new ArrayBuffer(bytes.length)
let array: Uint8Array = new Uint8Array()
array.fromArrayBuffer(buffer)

for (let i: number = 0; i < bytes.length; i++) {
array.set(i, bytes[i])
}

return buffer
}

let buf: ArrayBuffer = create([0, 1, 2, 3, 4, 5, 6, 7, 8, 9,])
if (buf.byteLength != 10) {
game.splash("Array buffer slice test 1 failed.")
allPassed = false
}
if (buf.slice(5).byteLength != 5) {
game.splash("Array buffer slice test 2 failed.")
allPassed = false
}
if (buf.slice(-2).byteLength != 2) {
game.splash("Array buffer slice test 3 failed.")
allPassed = false
}
if (buf.slice(-4, -2).byteLength != 2) {
game.splash("Array buffer slice test 4 failed.")
allPassed = false
}
if (buf.slice(-1000, 5).byteLength != 5) {
game.splash("Array buffer slice test 5 failed.")
allPassed = false
}
if (!areEquivalent([5, 6, 7, 8, 9,], buf.slice(5))) {
game.splash("Array buffer slice test 6 failed.")
allPassed = false
}
if (!areEquivalent([0, 1, 2, 3, 4,], buf.slice(0, 5))) {
game.splash("Array buffer slice test 7 failed.")
allPassed = false
}
if (!areEquivalent([5, 6,], buf.slice(5, 7))) {
game.splash("Array buffer slice test 8 failed.")
allPassed = false
}
if (!areEquivalent([6, 7,], buf.slice(-4, -2))) {
game.splash("Array buffer slice test 9 failed.")
allPassed = false
}
if (!areEquivalent([2, 3, 4, 5, 6, 7,], buf.slice(2, -2))) {
game.splash("Array buffer slice test 10 failed.")
allPassed = false
}

if (allPassed) {
game.splash("All tests passed!")
} else {
game.splash("At least one test failed.")
}

0 comments on commit e34c0e7

Please sign in to comment.