|
| 1 | +import { bufferToString, stringToBuffer } from 'react-native-quick-crypto'; |
| 2 | +import { expect } from 'chai'; |
| 3 | +import { test } from '../util'; |
| 4 | + |
| 5 | +const SUITE = 'utils'; |
| 6 | + |
| 7 | +// --- Helper --- |
| 8 | + |
| 9 | +const toU8 = (ab: ArrayBuffer): Uint8Array => new Uint8Array(ab); |
| 10 | + |
| 11 | +// --- Hex --- |
| 12 | + |
| 13 | +test(SUITE, 'hex encode empty buffer', () => { |
| 14 | + const ab = new ArrayBuffer(0); |
| 15 | + expect(bufferToString(ab, 'hex')).to.equal(''); |
| 16 | +}); |
| 17 | + |
| 18 | +test(SUITE, 'hex decode empty string', () => { |
| 19 | + expect(toU8(stringToBuffer('', 'hex'))).to.deep.equal(new Uint8Array([])); |
| 20 | +}); |
| 21 | + |
| 22 | +test(SUITE, 'hex encode known bytes', () => { |
| 23 | + const ab = new Uint8Array([0xde, 0xad, 0xbe, 0xef]).buffer as ArrayBuffer; |
| 24 | + expect(bufferToString(ab, 'hex')).to.equal('deadbeef'); |
| 25 | +}); |
| 26 | + |
| 27 | +test(SUITE, 'hex decode known string', () => { |
| 28 | + expect(toU8(stringToBuffer('deadbeef', 'hex'))).to.deep.equal( |
| 29 | + new Uint8Array([0xde, 0xad, 0xbe, 0xef]), |
| 30 | + ); |
| 31 | +}); |
| 32 | + |
| 33 | +test(SUITE, 'hex roundtrip all byte values', () => { |
| 34 | + const bytes = new Uint8Array(256); |
| 35 | + for (let i = 0; i < 256; i++) bytes[i] = i; |
| 36 | + const ab = bytes.buffer as ArrayBuffer; |
| 37 | + const hex = bufferToString(ab, 'hex'); |
| 38 | + expect(hex.length).to.equal(512); |
| 39 | + expect(toU8(stringToBuffer(hex, 'hex'))).to.deep.equal(bytes); |
| 40 | +}); |
| 41 | + |
| 42 | +test(SUITE, 'hex decode is case-insensitive', () => { |
| 43 | + const lower = toU8(stringToBuffer('abcdef', 'hex')); |
| 44 | + const upper = toU8(stringToBuffer('ABCDEF', 'hex')); |
| 45 | + expect(lower).to.deep.equal(upper); |
| 46 | +}); |
| 47 | + |
| 48 | +test(SUITE, 'hex decode rejects odd-length string', () => { |
| 49 | + expect(() => stringToBuffer('abc', 'hex')).to.throw(); |
| 50 | +}); |
| 51 | + |
| 52 | +test(SUITE, 'hex decode rejects invalid characters', () => { |
| 53 | + expect(() => stringToBuffer('zzzz', 'hex')).to.throw(); |
| 54 | +}); |
| 55 | + |
| 56 | +// --- Base64 --- |
| 57 | + |
| 58 | +test(SUITE, 'base64 encode empty buffer', () => { |
| 59 | + const ab = new ArrayBuffer(0); |
| 60 | + expect(bufferToString(ab, 'base64')).to.equal(''); |
| 61 | +}); |
| 62 | + |
| 63 | +test(SUITE, 'base64 decode empty string', () => { |
| 64 | + expect(toU8(stringToBuffer('', 'base64'))).to.deep.equal(new Uint8Array([])); |
| 65 | +}); |
| 66 | + |
| 67 | +test(SUITE, 'base64 encode/decode RFC 4648 test vectors', () => { |
| 68 | + const vectors: [string, string][] = [ |
| 69 | + ['', ''], |
| 70 | + ['f', 'Zg=='], |
| 71 | + ['fo', 'Zm8='], |
| 72 | + ['foo', 'Zm9v'], |
| 73 | + ['foob', 'Zm9vYg=='], |
| 74 | + ['fooba', 'Zm9vYmE='], |
| 75 | + ['foobar', 'Zm9vYmFy'], |
| 76 | + ]; |
| 77 | + for (const [plain, encoded] of vectors) { |
| 78 | + const ab = new Uint8Array(plain.split('').map(c => c.charCodeAt(0))) |
| 79 | + .buffer as ArrayBuffer; |
| 80 | + expect(bufferToString(ab, 'base64')).to.equal(encoded); |
| 81 | + expect(toU8(stringToBuffer(encoded, 'base64'))).to.deep.equal( |
| 82 | + new Uint8Array(ab), |
| 83 | + ); |
| 84 | + } |
| 85 | +}); |
| 86 | + |
| 87 | +test(SUITE, 'base64 roundtrip binary data', () => { |
| 88 | + const bytes = new Uint8Array([0, 1, 127, 128, 254, 255]); |
| 89 | + const ab = bytes.buffer as ArrayBuffer; |
| 90 | + const b64 = bufferToString(ab, 'base64'); |
| 91 | + expect(toU8(stringToBuffer(b64, 'base64'))).to.deep.equal(bytes); |
| 92 | +}); |
| 93 | + |
| 94 | +// --- Base64url --- |
| 95 | + |
| 96 | +test(SUITE, 'base64url encode produces URL-safe characters', () => { |
| 97 | + // Bytes that produce + and / in standard base64 |
| 98 | + const bytes = new Uint8Array([0xfb, 0xff, 0xfe]); |
| 99 | + const ab = bytes.buffer as ArrayBuffer; |
| 100 | + const result = bufferToString(ab, 'base64url'); |
| 101 | + expect(result).to.not.include('+'); |
| 102 | + expect(result).to.not.include('/'); |
| 103 | + expect(result).to.not.include('='); |
| 104 | +}); |
| 105 | + |
| 106 | +test(SUITE, 'base64url roundtrip', () => { |
| 107 | + const bytes = new Uint8Array([0xfb, 0xff, 0xfe, 0x00, 0x42]); |
| 108 | + const ab = bytes.buffer as ArrayBuffer; |
| 109 | + const encoded = bufferToString(ab, 'base64url'); |
| 110 | + expect(toU8(stringToBuffer(encoded, 'base64url'))).to.deep.equal(bytes); |
| 111 | +}); |
| 112 | + |
| 113 | +// --- UTF-8 --- |
| 114 | + |
| 115 | +test(SUITE, 'utf8 encode/decode ASCII', () => { |
| 116 | + const str = 'hello world'; |
| 117 | + const ab = stringToBuffer(str, 'utf-8'); |
| 118 | + expect(bufferToString(ab, 'utf-8')).to.equal(str); |
| 119 | +}); |
| 120 | + |
| 121 | +test(SUITE, 'utf8 encode/decode multibyte', () => { |
| 122 | + const str = '\u00e9\u00fc\u00f1'; // éüñ |
| 123 | + const ab = stringToBuffer(str, 'utf-8'); |
| 124 | + expect(bufferToString(ab, 'utf-8')).to.equal(str); |
| 125 | +}); |
| 126 | + |
| 127 | +test(SUITE, 'utf8 alias "utf8" works', () => { |
| 128 | + const str = 'test'; |
| 129 | + const ab = stringToBuffer(str, 'utf8'); |
| 130 | + expect(bufferToString(ab, 'utf8')).to.equal(str); |
| 131 | +}); |
| 132 | + |
| 133 | +// --- Latin1 / Binary --- |
| 134 | + |
| 135 | +test( |
| 136 | + SUITE, |
| 137 | + 'latin1 encode: bytes 0x80-0xFF produce correct UTF-8 strings', |
| 138 | + () => { |
| 139 | + const bytes = new Uint8Array([0xe9, 0xfc, 0xf1]); // é, ü, ñ in Latin-1 |
| 140 | + const ab = bytes.buffer as ArrayBuffer; |
| 141 | + const str = bufferToString(ab, 'latin1'); |
| 142 | + expect(str).to.equal('\u00e9\u00fc\u00f1'); |
| 143 | + }, |
| 144 | +); |
| 145 | + |
| 146 | +test( |
| 147 | + SUITE, |
| 148 | + 'latin1 decode: UTF-8 string maps each code point to one byte', |
| 149 | + () => { |
| 150 | + const str = '\u00e9\u00fc\u00f1'; // é, ü, ñ |
| 151 | + const ab = stringToBuffer(str, 'latin1'); |
| 152 | + expect(toU8(ab)).to.deep.equal(new Uint8Array([0xe9, 0xfc, 0xf1])); |
| 153 | + }, |
| 154 | +); |
| 155 | + |
| 156 | +test(SUITE, 'latin1 roundtrip all byte values 0x00-0xFF', () => { |
| 157 | + const bytes = new Uint8Array(256); |
| 158 | + for (let i = 0; i < 256; i++) bytes[i] = i; |
| 159 | + const ab = bytes.buffer as ArrayBuffer; |
| 160 | + const str = bufferToString(ab, 'latin1'); |
| 161 | + const roundtripped = toU8(stringToBuffer(str, 'latin1')); |
| 162 | + expect(roundtripped).to.deep.equal(bytes); |
| 163 | +}); |
| 164 | + |
| 165 | +test(SUITE, 'binary is alias for latin1 (encode)', () => { |
| 166 | + const bytes = new Uint8Array([0xca, 0xfe]); |
| 167 | + const ab = bytes.buffer as ArrayBuffer; |
| 168 | + expect(bufferToString(ab, 'binary')).to.equal(bufferToString(ab, 'latin1')); |
| 169 | +}); |
| 170 | + |
| 171 | +test(SUITE, 'binary is alias for latin1 (decode)', () => { |
| 172 | + const str = '\u00ca\u00fe'; |
| 173 | + expect(toU8(stringToBuffer(str, 'binary'))).to.deep.equal( |
| 174 | + toU8(stringToBuffer(str, 'latin1')), |
| 175 | + ); |
| 176 | +}); |
| 177 | + |
| 178 | +test( |
| 179 | + SUITE, |
| 180 | + 'latin1 decode truncates code points above 0xFF to low byte', |
| 181 | + () => { |
| 182 | + // Node.js Buffer.from('\u0100', 'latin1') produces [0x00] (256 & 0xFF = 0) |
| 183 | + const ab = stringToBuffer('\u0100', 'latin1'); |
| 184 | + expect(toU8(ab)).to.deep.equal(new Uint8Array([0x00])); |
| 185 | + }, |
| 186 | +); |
| 187 | + |
| 188 | +// --- ASCII --- |
| 189 | + |
| 190 | +test(SUITE, 'ascii encode strips high bit', () => { |
| 191 | + const bytes = new Uint8Array([0x48, 0xc8]); // 'H', 0xC8 |
| 192 | + const ab = bytes.buffer as ArrayBuffer; |
| 193 | + const str = bufferToString(ab, 'ascii'); |
| 194 | + expect(str.charCodeAt(0)).to.equal(0x48); |
| 195 | + expect(str.charCodeAt(1)).to.equal(0x48); // 0xC8 & 0x7F = 0x48 |
| 196 | +}); |
| 197 | + |
| 198 | +test(SUITE, 'ascii decode strips high bit', () => { |
| 199 | + const str = String.fromCharCode(0xc8); // above 0x7F |
| 200 | + const ab = stringToBuffer(str, 'ascii'); |
| 201 | + expect(toU8(ab)[0]).to.equal(0x48); // 0xC8 & 0x7F = 0x48 |
| 202 | +}); |
| 203 | + |
| 204 | +test(SUITE, 'ascii roundtrip printable ASCII', () => { |
| 205 | + const str = 'Hello, World! 123'; |
| 206 | + const ab = stringToBuffer(str, 'ascii'); |
| 207 | + expect(bufferToString(ab, 'ascii')).to.equal(str); |
| 208 | +}); |
| 209 | + |
| 210 | +// --- Unsupported encoding --- |
| 211 | + |
| 212 | +test(SUITE, 'bufferToString throws for unsupported encoding', () => { |
| 213 | + const ab = new ArrayBuffer(1); |
| 214 | + expect(() => bufferToString(ab, 'ucs2')).to.throw(); |
| 215 | +}); |
| 216 | + |
| 217 | +test(SUITE, 'stringToBuffer throws for unsupported encoding', () => { |
| 218 | + expect(() => stringToBuffer('test', 'ucs2')).to.throw(); |
| 219 | +}); |
0 commit comments