Update TODO
[chachachat.git] / client / conversions.js
blob2686765000d6089f015ced33998b87c65af5f036
1 // -*- mode: javascript; tab-width: 4; indent-tabs-mode: nil; -*-
2 //------------------------------------------------------------------------------
3 // This is free and unencumbered software released into the public domain.
4 //
5 // Anyone is free to copy, modify, publish, use, compile, sell, or
6 // distribute this software, either in source code form or as a compiled
7 // binary, for any purpose, commercial or non-commercial, and by any
8 // means.
9 //
10 // In jurisdictions that recognize copyright laws, the author or authors
11 // of this software dedicate any and all copyright interest in the
12 // software to the public domain. We make this dedication for the benefit
13 // of the public at large and to the detriment of our heirs and
14 // successors. We intend this dedication to be an overt act of
15 // relinquishment in perpetuity of all present and future rights to this
16 // software under copyright law.
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
21 // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
22 // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
23 // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
24 // OTHER DEALINGS IN THE SOFTWARE.
26 // For more information, please refer to <https://unlicense.org/>
27 //------------------------------------------------------------------------------
29 const BIN2HEX_LUT = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"];
31 export function binToHex(uint8Array) {
32     let result = "";
33     for (const b of uint8Array) {
34         result += BIN2HEX_LUT[(b >> 4) & 15];
35         result += BIN2HEX_LUT[b & 15];
36     }
37     return result;
40 export function hexToBin(hexStr) {
41     function fromHexChar(b) {
42         if (b <= 57) {
43             return b - 48;  // "0" - "9"
44         } else if (b <= 70) {
45             return b - 55;  // "A" - "F"
46         } else {
47             return b - 87;  // "a" - "f"
48         }
49     }
51     let result = new Uint8Array(hexStr.length / 2);
52     for (let i = 0; i < result.length; ++i) {
53         const c1 = fromHexChar(hexStr.charCodeAt(i * 2));
54         const c2 = fromHexChar(hexStr.charCodeAt(i * 2 + 1));
55         result[i] = (c1 << 4) | c2;
56     }
57     return result;
60 // Convert to base64url (RFC 4648).
61 export function binToB64(uint8Array) {
62     function codeToB64(c) {
63         if (c <= 25) {  // A-Z
64             return String.fromCharCode(c + 65);
65         } else if (c <= 51) {  // a-z
66             return String.fromCharCode(c + 71);
67         } else if (c <= 61) {  // 0-9
68             return String.fromCharCode(c - 4);
69         } else if (c == 62) {
70             return "-";
71         } else {
72             return "_";
73         }
74     };
76     let result = "";
77     const triplets = Math.ceil(uint8Array.length / 3);
78     for (let i = 0; i < triplets; ++i) {
79         const b1 = uint8Array[i * 3 + 0] | 0;
80         const b2 = uint8Array[i * 3 + 1] | 0;
81         const b3 = uint8Array[i * 3 + 2] | 0;
82         result += codeToB64(b1 >> 2);
83         result += codeToB64(((b1 & 3) << 4) | (b2 >> 4));
84         result += codeToB64(((b2 & 15) << 2) | (b3 >> 6));
85         result += codeToB64(b3 & 63);
86     }
88     const actualLength = Math.ceil((uint8Array.length * 4) / 3);
89     return result.slice(0, actualLength);
92 // Convert from base64url (RFC 4648).
93 export function b64ToBin(str) {
94     function b64ToCode(c) {
95         if (c === 45) {  // -
96             return 62;
97         } else if (c == 95) {  // _
98             return 63;
99         } else if (c <= 57) {  // 0-9
100             return c + 4;
101         } else if (c <= 90) {  // A-Z
102             return c - 65;
103         } else {  // a-z
104             return c - 71;
105         }
106     };
108     const quads = Math.ceil(str.length / 4);
109     let result = new Uint8Array(quads * 4);
110     for (let i = 0; i < quads; ++i) {
111         const c1 = b64ToCode(str.charCodeAt(i * 4 + 0) | 0);
112         const c2 = b64ToCode(str.charCodeAt(i * 4 + 1) | 0);
113         const c3 = b64ToCode(str.charCodeAt(i * 4 + 2) | 0);
114         const c4 = b64ToCode(str.charCodeAt(i * 4 + 3) | 0);
115         result[i * 3 + 0] = (c1 << 2) | (c2 >> 4);
116         result[i * 3 + 1] = (c2 << 4) | (c3 >> 2);
117         result[i * 3 + 2] = (c3 << 6) | c4;
118     }
120     const actualLength = Math.floor((str.length * 3) / 4);
121     return result.subarray(0, actualLength);
124 export function binToDataUri(bin, mimeType) {
125     let CHUNK_SIZE = 0x8000;
126     let index = 0;
127     let length = bin.length;
128     let result = '';
129     let slice;
130     while (index < length) {
131         slice = bin.subarray(index, Math.min(index + CHUNK_SIZE, length));
132         result += String.fromCharCode.apply(null, slice);
133         index += CHUNK_SIZE;
134     }
135     return `data:${mimeType};base64,${btoa(result)}`;
138 export function strToBin(str) {
139     const textEncoder = new TextEncoder();
140     return textEncoder.encode(str);
143 export function binToStr(bin) {
144     const textDecoder = new TextDecoder();
145     return textDecoder.decode(bin);
148 export function int64ToBin(x) {
149     x = BigInt(x);
150     let shift = BigInt(8);
151     let mask = BigInt(0xff);
152     let uint8Array = new Uint8Array(8);
153     for (let i = 0; i < uint8Array.length; ++i) {
154         uint8Array[i] = Number(x & mask);
155         x = x >> shift;
156     }
157     return uint8Array;
160 export function binToInt64(uint8Array) {
161     let x = BigInt(0);
162     let shift = BigInt(8);
163     for (let i = uint8Array.length - 1; i >= 0; --i) {
164         x = (x << shift) + BigInt(uint8Array[i]);
165     }
166     return x;
169 export function senderNameToBin(senderName) {
170     // The sender ID is encoded as UTF-8, maximum 16 bytes (without zero termination).
171     const MAX_NUM_BYTES = 16;
172     let bin = new Uint8Array(MAX_NUM_BYTES);
173     const textEncoder = new TextEncoder();
174     textEncoder.encodeInto(senderName, bin);
175     return bin;
178 export function binToSenderName(bin) {
179     const textDecoder = new TextDecoder();
180     return textDecoder.decode(bin).replace(/\0.*$/g,'');
183 const MIME_TYPE_TO_ENUM_LUT = {
184     "application/octet-stream": 1,
185     "application/gzip": 2,
186     "application/zip": 3,
187     "application/vnd.chachachat.geolocation": 4,
189     "text/plain": 101,
190     "text/html": 102,
191     "text/markdown": 103,
193     "image/jpeg": 201,
194     "image/gif": 202,
195     "image/png": 203,
196     "image/webp": 204,
197     "image/svg+xml": 205,
199     "audio/wav": 301,
200     "audio/mpeg": 302,
201     "audio/ogg": 303,
203     "video/webm": 401,
204     "video/mp4": 402,
207 export function isSupportedMessageFileType(mimeType) {
208     return MIME_TYPE_TO_ENUM_LUT[mimeType] ? true : false;
211 export function mimeTypeToBin(mimeType) {
212     // Convert the mime type string to an integer.
213     let t = MIME_TYPE_TO_ENUM_LUT[mimeType];
214     if (!t) {
215         t = MIME_TYPE_TO_ENUM_LUT["application/octet-stream"];
216     }
218     // Encode the integer as a 32-bit integer (little endian).
219     let bin = new Uint8Array(4);
220     bin[0] = t & 255;
221     bin[1] = (t >> 8) & 255;
222     bin[2] = (t >> 16) & 255;
223     bin[3] = (t >> 24) & 255;
224     return bin;
227 export function binToMimeType(bin) {
228     // Get the type integer (32 bit, little endian).
229     const t = bin[0] | (bin[1] << 8) | (bin[2] << 16) | (bin[3] << 24);
231     // Look up the mime type string.
232     // TODO: Use an O(1) reverse dict instead of this O(n) loop.
233     for (const [mimeType, value] of Object.entries(MIME_TYPE_TO_ENUM_LUT)) {
234         if (value === t) {
235             return mimeType;
236         }
237     }
238     return "application/octet-stream";
241 export function u8ArraysEqual(a, b)
243     if (a.length != b.length) {
244         return false;
245     }
246     for (let i = 0 ; i != a.length ; ++i)
247     {
248         if (a[i] != b[i]) {
249             return false;
250         }
251     }
252     return true;