]> foleosoft.com Git - RosadoAPI.git/commitdiff
Mon Jan 20 03:08:09 PM EST 2025
authormiha-q <>
Mon, 20 Jan 2025 20:08:09 +0000 (15:08 -0500)
committermiha-q <>
Mon, 20 Jan 2025 20:08:09 +0000 (15:08 -0500)
build.sh
src/main.c
src/www/favicon.ico
src/www/frontend.html
src/www/rosado-api.js [new file with mode: 0644]

index 514cc742d36dd7c6bf267c6591821162ec66ac7b..3f2cfeb905468abfbd2cacec85f56c6cb9b12d8a 100644 (file)
--- a/build.sh
+++ b/build.sh
@@ -1,10 +1,10 @@
 #!/bin/bash
 if [ "$1" == "run" ]
 then
-    while [ 1 ]
-    do
-        ./bin/rosado
-    done
+       while [ 1 ]
+       do
+               ./bin/rosado
+       done
 elif [ "$1" = "install" ]
 then
        sudo cp bin/rosado /usr/local/bin/rosado
@@ -12,8 +12,12 @@ elif [ "$1" = "uninstall" ]
 then
        sudo rm /usr/local/bin/rosado
 else
-    xxd -i ./rcs/frontend.html ./src/frontend.h
-    gcc ./src/main.c -o ./bin/rosado $(mysql_config --cflags --libs) -lCryptoFoleo
-    rm ./src/frontend.h
-    cp ./rcs/* ./bin/
+
+       python << EOF | xxd -i > src/frontend.h
+with open("src/www/frontend.html", "r") as f1:
+       with open("src/www/rosado-api.js", "r") as f2:
+                print(f1.read().replace(" src=\"rosado-api.js\">", ">\n" + f2.read() + "\n"))
+EOF
+       gcc ./src/main.c -o ./bin/rosado $(mysql_config --cflags --libs) -lCryptoFoleo
+       rm ./src/frontend.h
 fi
index 50ecbfa185f4c85fbf824a6f6c978aa569e710df..b42318dcd16b1fa0bef02605a45610b5e5e3f6ca 100644 (file)
@@ -3,7 +3,6 @@
 #include <pthread.h>
 #include <unistd.h>
 #include "http.c"
-#include "frontend.h"
 #include "CryptoFoleo.h"
 
 uint8_t HOSTNAME[1024];
@@ -11,6 +10,11 @@ uint8_t HOSTNAME_I[1024];
 uint8_t SQLPASS[1024];
 uint8_t WEBPORT[1024];
 
+uint8_t FRONTEND[] =
+{
+#include "frontend.h"
+};
+
 //grabs auth info from the database
 uint8_t* getDatabaseInfo(uint8_t type, uint8_t* a, uint8_t* b, uint8_t* c)
 {
@@ -346,9 +350,9 @@ void *handleRequest(void *vargp)
                        info = httpParse("Path", buff, buff_len);
                        if ( strcmp(info, "/") == 0 )
                        {
-                               uint8_t* frontend = malloc(__rcs_frontend_html_len + 1);
-                               memcpy(frontend, __rcs_frontend_html, __rcs_frontend_html_len);
-                               frontend[__rcs_frontend_html_len] = 0;
+                               uint8_t* frontend = malloc(sizeof(FRONTEND) + 1);
+                               memcpy(frontend, FRONTEND, sizeof(FRONTEND));
+                               frontend[sizeof(FRONTEND)] = 0;
                                httpStringOut(session_fd, frontend);
                                free(frontend);
                        }
index e33686d557f32b82b35bace7efce56cfd93932b1..96c484bde02ff9e6c28189cb5542a95ebd12107b 100644 (file)
Binary files a/src/www/favicon.ico and b/src/www/favicon.ico differ
index b77d9833a8f634129cfe4bea9fa0f7a131aa44bd..f1326174227c7ecc938a8d77d9a383b5e91e822e 100644 (file)
 <!DOCTYPE html>
 <html>
-<head>
-       <title>EstoulsAPI</title>
-       <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-</head>
-<body>
-<script>
-
-var EstoulsAPI =
-{
-       username: undefined,
-       apikey: undefined,
-       endpoint: undefined,
-       
-       Math32:
-       {
-               clamp: a =>
-               {
-                       while (a < 0 || a > 0xFFFFFFFF)
-                               a += a < 0 ? 0x100000000 : -0x100000000;
-                       return a;
-               },
-               shl: (a, b) => EstoulsAPI.Math32.clamp(a << b),
-               shr: (a, b) => EstoulsAPI.Math32.clamp(a >>> b),
-               rtl: (a, b) => EstoulsAPI.Math32.clamp((a << b) | (a >>> (32 - b))),
-               rtr: (a, b) => EstoulsAPI.Math32.clamp((a >>> b) | (a << (32 - b))),
-               add: (a, b) => EstoulsAPI.Math32.clamp(a + b),
-               sub: (a, b) => EstoulsAPI.Math32.clamp(a + b),
-               or: (a, b) => EstoulsAPI.Math32.clamp(a | b),
-               xor: (a, b) => EstoulsAPI.Math32.clamp(a ^ b)
-       },
-       HMACSHA256:
-       {
-               sign: async (key, msg) =>
-               {
-                       key = new Uint8Array(key);
-                       msg = new Uint8Array(msg);
-                       var ikey = await crypto.subtle.importKey
-                       (
-                               "raw",
-                               key,
-                               { name: "HMAC", hash: { name: "SHA-256" }},
-                               false,
-                               ["sign", "verify"]
-                       );
-                       return new Uint8Array(await window.crypto.subtle.sign({ name: "HMAC", }, ikey, msg));
-               }
-       },
-       ChaCha20:
-       {
-               QR: (cc, a, b, c, d) =>
-               {
-                       cc[a] = EstoulsAPI.Math32.add(cc[a], cc[b]);
-                       cc[d] = EstoulsAPI.Math32.xor(cc[d], cc[a]);
-                       cc[d] = EstoulsAPI.Math32.rtl(cc[d], 16);
-
-                       cc[c] = EstoulsAPI.Math32.add(cc[c], cc[d]);
-                       cc[b] = EstoulsAPI.Math32.xor(cc[b], cc[c]);
-                       cc[b] = EstoulsAPI.Math32.rtl(cc[b], 12);
-
-                       cc[a] = EstoulsAPI.Math32.add(cc[a], cc[b]);
-                       cc[d] = EstoulsAPI.Math32.xor(cc[d], cc[a]);
-                       cc[d] = EstoulsAPI.Math32.rtl(cc[d], 8);
-                       
-                       cc[c] = EstoulsAPI.Math32.add(cc[c], cc[d]);
-                       cc[b] = EstoulsAPI.Math32.xor(cc[b], cc[c]);
-                       cc[b] = EstoulsAPI.Math32.rtl(cc[b], 7);
-               },
-               DR: (cc) =>
-               {       
-                       EstoulsAPI.ChaCha20.QR(cc, 0, 4,  8, 12);
-                       EstoulsAPI.ChaCha20.QR(cc, 1, 5,  9, 13);
-                       EstoulsAPI.ChaCha20.QR(cc, 2, 6, 10, 14);
-                       EstoulsAPI.ChaCha20.QR(cc, 3, 7, 11, 15);
-                       EstoulsAPI.ChaCha20.QR(cc, 0, 5, 10, 15);
-                       EstoulsAPI.ChaCha20.QR(cc, 1, 6, 11, 12);
-                       EstoulsAPI.ChaCha20.QR(cc, 2, 7,  8, 13);
-                       EstoulsAPI.ChaCha20.QR(cc, 3, 4,  9, 14);                               
-               },
-               CB: (cc) =>
-               {
-                       var i;
-                       var x = new Array(16);
-                       for (i = 0; i < 16; i++)
-                       {
-                               x[i] = cc[i];
-                       }
-                       for (i = 0; i < 10; i++)
-                       {
-                               EstoulsAPI.ChaCha20.DR(cc);
-                       }
-                       for (i = 0; i < 16; i++)
-                       {
-                               cc[i] = EstoulsAPI.Math32.add(cc[i], x[i]);
-                       }
-               },
-               S: (cc, cs) =>
-               {
-                       for (var i = 0; i < 16; i++)
-                       {
-                               cs[4 * i] = (cc[i] & 0xFF);
-                               cs[4 * i + 1] = ((cc[i] >> 8) & 0xFF);
-                               cs[4 * i + 2] = ((cc[i] >> 16) & 0xFF);
-                               cs[4 * i + 3] = ((cc[i] >> 24) & 0xFF);
-                       }
-               },
-               B: (key, nonce, block, out) =>
-               {
-                       var cc =
-                       [
-                               0x61707865, 0x3320646e, 0x79622d32, 0x6b206574,
-
-                               key[0] | (key[1] << 8) | (key[2] << 16) | (key[3] << 24),
-                               key[4] | (key[5] << 8) | (key[6] << 16) | (key[7] << 24),
-                               key[8] | (key[9] << 8) | (key[10] << 16) | (key[11] << 24),
-                               key[12] | (key[13] << 8) | (key[14] << 16) | (key[15] << 24),
-
-                               key[16] | (key[17] << 8) | (key[18] << 16) | (key[19] << 24),
-                               key[20] | (key[21] << 8) | (key[22] << 16) | (key[23] << 24),
-                               key[24] | (key[25] << 8) | (key[26] << 16) | (key[27] << 24),
-                               key[28] | (key[29] << 8) | (key[30] << 16) | (key[31] << 24),
-
-                               block,
-
-                               nonce[0] | (nonce[1] << 8) | (nonce[2] << 16) | (nonce[3] << 24),
-                               nonce[4] | (nonce[5] << 8) | (nonce[6] << 16) | (nonce[7] << 24),
-                               nonce[8] | (nonce[9] << 8) | (nonce[10] << 16) | (nonce[11] << 24)
-                       ];
-
-                       EstoulsAPI.ChaCha20.CB(cc);
-                       EstoulsAPI.ChaCha20.S(cc, out);
-               },
-               encrypt: async (key, nonce, block, data) =>
-               {
-                       var count = data.length;
-                       if (count > (274877906944 - block * 64)) return null;
-                       var ret = new Array(0);
-                       var ccblock = new Array(64);
-                       var size = 0;
-                       while (count > 64)
-                       {
-                               ret.length = size + 64;
-                               EstoulsAPI.ChaCha20.B(key, nonce, block++, ccblock);
-                               for (var i = 0; i < 64; i++) ret[size + i] = ccblock[i];
-                               size += 64;
-                               count -= 64;
-                       }
-                       if (count > 0)
-                       {
-                               ret.length = size + count;
-                               EstoulsAPI.ChaCha20.B(key, nonce, block, ccblock);
-                               for (var i = 0; i < count; i++) ret[size + i] = ccblock[i];
-                       }
-                       for (var i = 0; i < data.length; i++) ret[i] ^= data[i];
-                       return new Uint8Array(ret);
-               }
-       },
-       
-       Base64:
-       {
-               encode: async (x) =>
-               {
-                       return await new Promise(r =>
-                       {
-                               const reader = new FileReader();
-                               reader.addEventListener("load", () => r(reader.result.split(",")[1]));
-                               reader.readAsDataURL(new Blob([new Uint8Array(x)]));                            
-                       });
-               },
-               
-               decode: (b64) =>
-               {
-                       var dec1 = (v) =>
-                       {
-                               switch (v)
-                               {
-                                       case 'A': return  0;
-                                       case 'B': return  1;
-                                       case 'C': return  2;
-                                       case 'D': return  3;
-                                       case 'E': return  4;
-                                       case 'F': return  5;
-                                       case 'G': return  6;
-                                       case 'H': return  7;
-                                       case 'I': return  8;
-                                       case 'J': return  9;
-                                       case 'K': return 10;
-                                       case 'L': return 11;
-                                       case 'M': return 12;
-                                       case 'N': return 13;
-                                       case 'O': return 14;
-                                       case 'P': return 15;
-                                       case 'Q': return 16;
-                                       case 'R': return 17;
-                                       case 'S': return 18;
-                                       case 'T': return 19;
-                                       case 'U': return 20;
-                                       case 'V': return 21;
-                                       case 'W': return 22;
-                                       case 'X': return 23;
-                                       case 'Y': return 24;
-                                       case 'Z': return 25;
-                                       case 'a': return 26;
-                                       case 'b': return 27;
-                                       case 'c': return 28;
-                                       case 'd': return 29;
-                                       case 'e': return 30;
-                                       case 'f': return 31;
-                                       case 'g': return 32;
-                                       case 'h': return 33;
-                                       case 'i': return 34;
-                                       case 'j': return 35;
-                                       case 'k': return 36;
-                                       case 'l': return 37;
-                                       case 'm': return 38;
-                                       case 'n': return 39;
-                                       case 'o': return 40;
-                                       case 'p': return 41;
-                                       case 'q': return 42;
-                                       case 'r': return 43;
-                                       case 's': return 44;
-                                       case 't': return 45;
-                                       case 'u': return 46;
-                                       case 'v': return 47;
-                                       case 'w': return 48;
-                                       case 'x': return 49;
-                                       case 'y': return 50;
-                                       case 'z': return 51;
-                                       case '0': return 52;
-                                       case '1': return 53;
-                                       case '2': return 54;
-                                       case '3': return 55;
-                                       case '4': return 56;
-                                       case '5': return 57;
-                                       case '6': return 58;
-                                       case '7': return 59;
-                                       case '8': return 60;
-                                       case '9': return 61;
-                                       case '+': return 62;
-                                       case '/': return 63;
-                               }
-                               return 255;
-                       };
-                       
-                       var ret = new Array(0);
-                       var buffer = 0;
-                       var bufferS = 0;
-                       for (var i = 0; i < b64.length; i++)
-                       {
-                               if (b64[i] == '=') { bufferS = 0; continue; };
-                               var val = dec1(b64[i]);
-                               if (val == 255) return null;
-                               buffer = ((buffer << 6) | val) & 0xFFFF;
-                               bufferS += 6;
-                               if (bufferS >= 8)
+       <head>
+               <title>RosadoAPI</title>
+               <script type="text/javascript" src="rosado-api.js"></script>
+               <style type="text/css">
+                       .tdlabel { width: 10%; text-align: center; }
+                       .tdfield { width: 85%; text-align: center; }
+                       .tdfield input, button, textarea { width: 100%; }
+               </style>
+       </head>
+       <body>
+               <h1><img src="favicon.ico" / width=48 height=48>&nbsp;RosadoAPI</h3>
+               <table style="width: 100%;">
+                       <tr>
+                               <td class="tdlabel">Address:</td>
+                               <td class="tdfield"><input id="addr" type="text" /></td>
+                       </tr>
+                       <tr>
+                               <td class="tdlabel">Service:</td>
+                               <td class="tdfield"><input id="srvc" type="text" /></td>
+                       </tr>
+                       <tr>
+                               <td class="tdlabel">Username:</td>
+                               <td class="tdfield"><input id="user" type="text" /></td>
+                       </tr>
+                       <tr>
+                               <td class="tdlabel">Authkey:</td>
+                               <td class="tdfield"><input id="auth" type="text" /></td>
+                       </tr>
+                       <tr>
+                               <td class="tdlabel">Request:</td>
+                               <td class="tdfield"><textarea id="rqst" rows="8"></textarea></td>
+                       </tr>
+                       <tr>
+                               <td class="tdlabel">Response:</td>
+                               <td class="tdfield"><textarea id="resp" rows="8" disabled></textarea></td>
+                       </tr>
+                       <tr>
+                               <td class="tdlabel"></td>
+                               <td class="tdfield"><button type="button">Send</button></td>
+                       </tr>
+               </table>
+               <script type="text/javascript">
+                       $ = x => document.querySelector(x);
+                       $("button").addEventListener("click", e =>
+                       {
+                               var addr = $("#addr").value;
+                               var srvc = $("#srvc").value;
+                               var user = $("#user").value;
+                               var auth = $("#auth").value;
+                               var rqst = $("#rqst").value;
+
+                               localStorage.setItem("addr", addr);
+                               localStorage.setItem("srvc", srvc);
+                               localStorage.setItem("user", user);
+                               localStorage.setItem("auth", auth);
+                               localStorage.setItem("rqst", rqst);
+
+                               RosadoAPI.username = user;
+                               RosadoAPI.apikey = auth;
+                               RosadoAPI.endpoint = addr + "/" + srvc;
+                               RosadoAPI.request(rqst).then(x =>
                                {
-                                       var shift = (16 - bufferS) & 0xFF;
-                                       buffer = (buffer << shift) & 0xFFFF;
-                                       ret[ret.length] = (buffer >>> 8) & 0xFF;
-                                       buffer = buffer & 0x00FF;
-                                       buffer = (buffer >>> shift) & 0xFFFF;
-                                       bufferS -= 8;
-                               }
-                       }
-                       if (bufferS > 0)
-                       {
-                               buffer = (buffer << (16 - bufferS)) & 0xFFFF;
-                               ret[ret.length] = (buffer >>> 8) & 0xFF;
-                       }
-                       return new Uint8Array(ret);
-               }
-       },
-       
-       Hex:
-       {
-               encode: (x) =>
-               {
-                       var ret = "";
-                       x.forEach(y => { ret += y.toString(16).padStart(2, "0"); });
-                       return ret;
-               },
-               decode: (x) =>
-               {
-                       var arr = new Array(0);
-                       for (var i = 0; i < x.length; i += 2)
-                       {
-                               arr[arr.length] = parseInt(x.charAt(i) + x.charAt(i + 1), 16);
-                       }
-                       return new Uint8Array(arr);
-               }
-       },
-       
-       generateRequest: async (msg) =>
-       {
-               msg = new TextEncoder().encode(msg);
-               var key = EstoulsAPI.Hex.decode(EstoulsAPI.apikey);
-               //var sess = crypto.getRandomValues(new Uint8Array(12));
-               var sess = new Uint8Array([0xba, 0x1f, 0x34, 0xdf, 0xf1, 0xb1, 0xd1, 0xce, 0xfb, 0x72, 0x28, 0x2b]);
-               var rawdata = await EstoulsAPI.ChaCha20.encrypt(key, sess, 0, msg);
-               var dgst = await EstoulsAPI.Hex.encode(await EstoulsAPI.HMACSHA256.sign(key, rawdata));
-               var data = await EstoulsAPI.Base64.encode(rawdata);
-               var resp = "user=" + EstoulsAPI.username;
-               resp += "&sess=" + EstoulsAPI.Hex.encode(sess);
-               resp += "&dgst=" + dgst;
-               resp += "&data=" + data;
-               return resp;
-       },
-       
-       parseResponse: async (msg) =>
-       {
-               if (!msg.includes("user=") || !msg.includes("&sess=") || !msg.includes("&dgst=") || !msg.includes("&data="))
-               {
-                       return { success: false, response: "invalid response" };
-               }
-               var user = msg.split("user=")[1].split("&")[0].trim();
-               var sess = msg.split("&sess=")[1].split("&")[0].trim();
-               var dgst = msg.split("&dgst=")[1].split("&")[0].trim();
-               var data = msg.split("&data=")[1].split("&")[0].trim();
-               
-               if (user != EstoulsAPI.username)
-               {
-                       return { success: false, response: "invalid user in response" };
-               }
-               data = EstoulsAPI.Base64.decode(data);
-               sess = EstoulsAPI.Hex.decode(sess);
-               var key = EstoulsAPI.Hex.decode(EstoulsAPI.apikey);
-               var sig = await EstoulsAPI.Hex.encode(await EstoulsAPI.HMACSHA256.sign(key, data));
-               if (sig != dgst)
-               {
-                       return { success: false, response: "invalid signature" };
-               }
-               var resp = await EstoulsAPI.ChaCha20.encrypt(key, sess, 0, data);
-               return { success: true, response: new TextDecoder().decode(resp) };
-       },
-       
-       request: async (msg) =>
-       {
-               if (msg.trim().length == 0) return { success: true, response: "" };
-               var req = await EstoulsAPI.generateRequest(msg);
-               return await new Promise(r =>
-               {
-                       const xhr = new XMLHttpRequest();
-                       xhr.open("POST", EstoulsAPI.endpoint, true);
-                       xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
-                       xhr.onreadystatechange = (x) =>
-                       {
-                               if (xhr.readyState === XMLHttpRequest.DONE)
-                               {
-                                       if (xhr.status === 200)
-                                       {
-                                               EstoulsAPI.parseResponse(xhr.responseText).then(pr => { r(pr); }).catch(x => alert("ERR(3)"));
-                                       }
-                                       else
-                                       {
-                                               r({success: false, response: xhr.status});
-                                       }
-                               }
-                       }
-                       xhr.send(req);
-               }).catch(x => alert("ERR(1): " + x));
-       }
-       
-};
-</script>
-
-
-
-<style>
-body { background-color: #1f1f1f; }
-table { background-color: #1f1f1f; }
-.basis
-{
-       font-size: 14px;
-       position: absolute;
-       font-family: monospace;
-       color: rgba(255, 255, 255, 0.5);
-       width: 100%;
-       background-color: rgba(0, 0, 0, 0);
-       overflow-y: auto;
-       outline: none;
-       white-space: nowrap;
-}
-.overlay
-{
-       font-size: 14px;
-       pointer-events: none;
-       font-family: monospace;
-       width: 100%;
-       background-color: #1f1f1f;
-       color: #9cdcfe;
-       font-weight: bold;
-       overflow-y: auto;
-       outline: none;
-       white-space: nowrap;
-}
-.linecounter
-{
-       font-size: 14px;
-       width: 100%;
-       text-align: center;
-       font-weight: bold;
-       font-family: monospace;
-       background-color: #1f1f1f;
-       color: #6e7681;
-       overflow-y: auto;
-       &::-webkit-scrollbar { display: none; }
-       -ms-overflow-style: none;
-       scrollbar-width: none;
-}
-
-.bracket { color: #da70d6; }
-.number { color: #b5cea8; }
-.comment { color: #6a9955; }
-.keyword { color: #dcdcaa; }
-.semicolon { color: #ffffff; }
-
-table { width: 100%; }
-
-.response
-{
-       color: red;
-       font-weight: bold;
-       font-family: monospace;
-}
-
-.optionspanel
-{
-       text-align: right;
-       width: 100%;
-}
-.option
-{
-       font-size: 24px;
-       font-weight: bold;
-       font-family: monospace;
-       cursor: pointer;
-       background-color: rgba(0,0,0,0);
-       padding: 0;
-       margin: 0;
-       border: 0;
-       display: inline-block;
-}
-#run { color: green; }
-#setup { font-size: 28px; color: grey; }
-#save,#help { font-size: 20px; display: none; }
-#load,#copy,#clear,#apk { font-size: 20px; }
-#apkwrapper { display: none; }
-
-</style>
-
-<div id="horizontal">
-
-<div class="optionspanel">
-       <button class="option" id="load">&#128194;</button>&nbsp;&nbsp;
-       <button class="option" id="copy">&#128203;</button>&nbsp;&nbsp;
-       <button class="option" id="clear">&#129529;</button>&nbsp;&nbsp;
-       <button class="option" id="save">&#128190;</button><!--&nbsp;&nbsp;-->
-       <button class="option" id="run">&#9654;</button><!--&nbsp;&nbsp;-->
-       <button class="option" id="help">&#10067;</button>&nbsp;&nbsp;
-       <div id="apkwrapper"><button class="option" id="apk">&#x1F4F2;</button>&nbsp;&nbsp;</div>
-       <button class="option" id="setup">&#9881;</button>
-</div>
-<table border="0">
-       <tr>
-               <td style="width: 3%;"><div class="linecounter">1</div></td>
-               <td style="width: 96%; text-align: top;">
-                       <div class="basis" contenteditable spellcheck="false" autocorrect="off" autocapitalization="off"></div>
-                       <div class="overlay"></div>
-               </td>
-       </tr>
-       <!-- <div>qreg q[2];</div><div>h q[0];</div><div>cx q[0], q[1];</div><div>sample;</div> -->
-</table>
-<hr style='border-color: #6e7681;' />
-<div class="response"></div>
-</div>
-
-<script>
-window.$ = x => document.querySelectorAll(x);
-var escapables =
-[
-       [ "&amp;", "&" ],
-       [ "&nbsp;", " " ],
-       [ "&gt;", ">" ],
-       [ "&lt;", "<" ],
-];
-
-function htmlToText(x, y)
-{
-       var str;
-       if (y == undefined)
-       {
-               var div = document.createElement("div");
-               div.innerHTML = x;
-               str = htmlToText(x, div);
-               if (str.length > 0)
-               {
-                       str = str.substring(0, str.length - 1);
-               }
-               return str;
-       }
-       
-       if (y.nodeType == Node.TEXT_NODE)
-       {
-               return y.textContent + "\n";
-       }
-
-       str = "";
-       if (y.tagName == "BR")
-       {
-               var prev = y.previousSibling;
-               var next = y.nextSibling;
-               window.brb = y;
-               
-               str = "\n";
-               /*if (prev == null && next == null)
-               {
-                       str = "\n";
-               }
-               else if (prev != null && prev.nodeType != Node.TEXT_NODE)
-               {
-                       str = "\n";
-               }
-               else if (next != null && next.nodeType != Node.TEXT_NODE)
-               {
-                       str = "\n";
-               }*/
-               if (next != null && next.tagName == "DIV")
-               {
-                       str = "";
-                       if (prev == null)
-                       {
-                               str = "\n";
-                       }
-               }
-       }
-       
-       for (var i = 0; i < y.childNodes.length; i++)
-       {
-               str += htmlToText(x, y.childNodes[i]);
-       }
-       return str;
-}
-
-function removeEmptyNodes(v)
-{
-       if (window.pauseCleaning) return;
-       if (v == undefined)
-       {
-               removeEmptyNodes($(".basis")[0]);
-               return;
-       }
-       /*
-       if (v.tagName == "SPAN" && v.childNodes.length > 0)
-       {
-               v.parentNode.insertBefore(v.childNodes[0], v);
-               v.remove();
-               return;
-       }
-       */;
-       if (v.tagName != "DIV")
-       {
-               return;
-       }
-       if (v.className != "basis")
-       {
-               if (v.innerHTML.length == 0)
-               {
-                       v.remove();
-                       return;
-               }
-       }
-       for (var i = 0; i < v.childNodes.length; i++)
-       {
-               removeEmptyNodes(v.childNodes[i]);
-       }
-}
-
-function getText(x, y, z)
-{
-       if (x == undefined || y == undefined)
-       {
-               z = [];
-               getText([], $(".basis")[0], z);
-               if (z.length == 0) return "";
-               z.sort((a, b) => a[0] - b[0]);
-               while (z.length > 0 && z[0][0] == 0)
-               {
-                       z.splice(0, 1);
-               }
-               if (z.length == 0) return "";
-               var str = "";
-               var prevline = z[0][0];
-               for (var i = 0; i < z.length; i++)
-               {
-                       if (Math.abs(prevline - z[i][0]) > 5)
-                       {
-                               str += "\n";
-                       }
-                       str += z[i][1];
-                       prevline = z[i][0];
-               }
-               return str.replaceAll(String.fromCharCode(160), " ");
-       }
-       
-       if (y.nodeType == Node.TEXT_NODE)
-       {
-               var r = document.createRange();
-               r.selectNodeContents(y);
-               var n = parseFloat(r.getBoundingClientRect().y.toString());
-               n += parseFloat($(".basis")[0].scrollTop.toString());
-               z[z.length] = [ r.getBoundingClientRect().y + $(".basis")[0].scrollTop, y.textContent ];
-               return;
-       }
-       else if (y.className != "basis")
-       {
-               var n = parseFloat(y.getBoundingClientRect().y.toString());
-               n += parseFloat($(".basis")[0].scrollTop.toString());
-               z[z.length] = [ y.getBoundingClientRect().y + $(".basis")[0].scrollTop, "" ];
-       }
-
-       for (var i = 0; i < y.childNodes.length; i++)
-       {
-               getText(x, y.childNodes[i], z);
-       }
-}
-
-function textToNodes(txt)
-{
-       escapables.forEach(x => { txt = txt.replaceAll(x[1], x[0]); });
-       var out = [];
-       txt = txt.replaceAll("\r", "").replaceAll(" ", "&nbsp;").split("\n");
-       for (var i = 0; i < txt.length; i++)
-       {
-               var tmp = txt[i].trim();
-               if (i == 0)
-               {
-                       if (tmp.length > 0)
-                       {
-                               escapables.forEach(x => tmp = tmp.replaceAll(x[0], x[1]));
-                               out[out.length] = document.createTextNode(tmp);
-                       }
-               }
-               else
-               {
-                       out[out.length] = document.createElement("div");
-                       if (tmp.length == 0)
-                       {
-                               out[out.length - 1].innerHTML = "<br>";
-                       }
-                       else
-                       {
-                               out[out.length - 1].innerHTML = tmp;
-                       }
-               }
-       }
-       return out;
-}
-
-function setText(txt)
-{
-       escapables.forEach(x => { txt = txt.replaceAll(x[1], x[0]); });
-       var out = "";
-       txt = txt.trim().replaceAll("\r", "").split("\n");
-       for (var i = 0; i < txt.length; i++)
-       {
-               var tmp = txt[i].trim();
-               if (tmp.length == 0)
-               {
-                       out += "<div><br></div>";
-               }
-               else
-               {
-                       out += "<div>" + tmp + "</div>";
-               }
-       }
-       out = out.replaceAll(" ", "&nbsp;");
-       $(".basis")[0].innerHTML = out;
-}
-
-function highlightForQAnsel(txt)
-{
-       var lhs = "!!!" + Math.random().toString().replace(".", "0") + "!!!";
-       var rhs = "!!!" + Math.random().toString().replace(".", "1") + "!!!";
-       txt = txt.replaceAll(/[&].*?[;]/g, x => lhs + x.replace("&", "").replace(";", "") + rhs);
-       txt = txt.replaceAll(/[;]/g, "<span class='semicolon'>;</span>");
-       txt = txt.replaceAll(new RegExp(lhs + ".*?" + rhs, "g"), x => "&" + x.replace(lhs, "").replace(rhs, "") + ";");
-
-       txt = txt.replaceAll
-       (
-               /[\[][0-9][0-9]*[\]]/g,
-               x =>
-               {
-                       x = x.replace("[", "<span class='bracket'>[</span><span class='number'>");
-                       x = x.replace("]", "</span><span class='bracket'>]</span>");
-                       return x;
-               }
-       );
-       //u(#,#,#); rx(#); ry(#); rz(#); if(c==#); if(c[#]==%i)
-       var keywords =
-       [
-               "qreg",
-               "creg",
-               "density",
-               "h",
-               "x",
-               "y",
-               "z",
-               "t",
-               "s",
-               "cx",
-               "swap",
-               "cswap",
-               "fredkin",
-               "ccx",
-               "toffoli",
-               "measure",
-               "print",
-               "sample",
-               "reset",
-               "barrier",
-               "hvar",
-               "rand",
-               "born"
-       ];
-               
-       for (var i = 0; i < keywords.length; i++)
-       {
-               var tmp = new RegExp("(^|[ ]|[;]|[>])" + keywords[i] + "($|[ ]|[<]|[&])", "g");
-               txt = txt.replaceAll(tmp, x =>
-               {
-                       var c = x.charAt(0);
-                       var tagonL = "";
-                       if (c == " " || c == ";" || c == ">")
-                       {
-                               tagonL = c;
-                               x = x.substring(1, x.length);
-                       }
-                       var tagonR = "";
-                       c = x.charAt(x.length - 1);
-                       if (c == " " || c == "<" || c == "&")
-                       {
-                               tagonR = c;
-                               x = x.substring(0, x.length - 1);
-                       }               
-                       return tagonL + "<span class='keyword'>" + x + "</span>" + tagonR;
-               });
-       }
-       txt = txt.replaceAll
-       (
-               /[/][/].*?([<]div|[<]br|$)/g,
-               x =>
-               {
-                       var tagon = "";
-                       var tagons = [ "<div", "<br" ];
-                       for (var i = 0; i < tagons.length; i++)
-                       {
-                               if (x.substring(x.length - tagons[i].length, x.length) == tagons[i])
-                               {
-                                       x = x.substring(0, x.length - tagons[i].length);
-                                       tagon = tagons[i];
-                                       break;
-                               }
-                       }
-                       x = x.replaceAll(/[<]span[ ]class[=]['].*?['][>]/g, "<span>");
-                       x = "<span class='comment'>" + x + "</span>" + tagon;
-                       return x;
-               }
-       );
-       return txt;
-}
-
-if (window.location.hash == "#mobile")
-{
-       $("#horizontal")[0].remove();
-       var iframe = document.createElement("iframe");
-       iframe.src = window.location.href.split("#")[0];
-       iframe.style.width = window.innerHeight + "px";
-       iframe.style.height = window.innerWidth + "px";
-       iframe.style.transformOrigin = "top left";
-       iframe.style.transform = "rotate(90deg)";
-       iframe.style.position = "fixed";
-       iframe.style.top = "0px";
-       iframe.style.left = window.innerWidth + "px";
-       iframe.frameBorder = "0";
-       document.body.appendChild(iframe);
-       
-}
-else
-{
-       window.addEventListener("DOMContentLoaded", () =>
-       {
-               if (window.location.href.includes(".com") || window.location.href.includes("192."))
-               {
-                       $("#apkwrapper")[0].style.display = "inline-block";
-               }
-               setTimeout(function()
-               {
-                       try
-                       {
-                               var tmp = Math.round($(".overlay")[0].getBoundingClientRect().width) + "px";
-                               $(".overlay")[0].style.width = tmp;
-                               $(".basis")[0].style.width = tmp;
-                               $(".basis,.overlay,.linecounter").forEach(x =>
-                               {
-                                       x.style.height = (window.innerHeight * 0.40) + "px";
-                               });
-                               $(".response")[0].style.height = (window.innerHeight * 0.40) + "px";
-                               
-                               var prevText = localStorage.getItem("previous_text");
-                               if (prevText)
-                               {
-                                       if (typeof(prevText) == "string")
+                                       if (x.success && x.response.trim().length > 0)
+                                       {
+                                               $("#resp").style.color = "green";
+                                               $("#resp").value = x.response;
+                                       }
+                                       else if (x.response.trim().length == 0)
                                        {
-                                               setText(prevText);
+                                               $("#resp").style.color = "brown";
+                                               $("#resp").value = "Empty response.";
+                                       }
+                                       else
+                                       {
+                                               $("#resp").style.color = "red";
+                                               $("#resp").value = x.response;
                                        }
-                               }
-                       }
-                       catch (x)
-                       {
-                               alert("ERR(4): " + x);
-                       }
-               }, 100);
-               
-               //$(".basis")[0].style.top = Math.round($(".overlay")[0].getBoundingClientRect().top) + "px";
-               //$(".basis")[0].style.left = Math.round($(".overlay")[0].getBoundingClientRect().left) + "px";
-       });
-
-       $("#clear")[0].addEventListener("click", () =>
-       {
-               setText("");
-       });
-
-       $("#apk")[0].addEventListener("click", () =>
-       {
-               var a = document.createElement("a");
-               a.href = "/apk";
-               a.download = "EstoulsAPI.apk";
-               a.style.display = "none";
-               document.body.appendChild(a);
-               a.click();
-               a.remove();
-       });
-
-       $("#save")[0].addEventListener("click", () =>
-       {
-               var txt = btoa($(".basis")[0].innerText);
-               txt = "data:text/plain;base64," + txt;
-               var a = document.createElement("a");
-               a.href = txt;
-               a.download = "program.txt";
-               a.style.display = "none";
-               document.body.appendChild(a);
-               a.click();
-               a.remove();
-       });
-
-       $("#copy")[0].addEventListener("click", () =>
-       {
-               var tmp = document.createElement('textarea');
-               tmp.value = getText();
-               document.body.appendChild(tmp);
-               tmp.focus();
-               tmp.select();
-               document.execCommand('copy');
-               tmp.remove();
-       });
-
-
-       $("#help")[0].addEventListener("click", () =>
-       {
-               window.location.href = "https://doi.org/10.48550/arXiv.1707.03429";
-       });
-
-       $("#load")[0].addEventListener("click", () =>
-       {
-               var load = document.createElement("input");
-               load.type = "file";
-               load.style.display = "none";
-               /*while ($("input[type=file]").length > 0)
-               {
-                       $("input[type=file]")[0].remove();
-               }*/
-               window.pr = new Promise(r =>
-               {
-                       load.addEventListener("change", () =>
-                       {
-                               var reader = new FileReader();
-                               reader.addEventListener("load", (e) =>
-                               {
-                                       r(atob(e.target.result.split(",")[1]));
-                                       load.remove();          
                                });
-                               reader.readAsDataURL(load.files[0]);
                        });
-                       document.body.appendChild(load);
-                       load.click();
-               }).then(text =>
-               {
-                       var tmp = "!!!" + Math.random().toString().replace(".", "0") + "!!!";
-                       text = text.trim().replaceAll("\n", tmp).replaceAll("\r", "");
-                       tmp = text.replaceAll(tmp, "</div><div>");
-                       if (tmp.substring(0, 5) != "<div>")
-                       {
-                               tmp = "<div>" + tmp;
-                       }
-                       if (tmp.substring(tmp.length - 5, tmp.length) == "<div>")
-                       {
-                               tmp = tmp.substring(0, tmp.length - 5);
-                       }
-                       tmp = tmp.replaceAll("<div></div>", "<div><br></div>");
-                       $(".basis")[0].innerHTML = tmp;
-               });
-       });
-
-       $("#setup")[0].addEventListener("click", () =>
-       {
-               var msg = "username:apikey:endpoint";
-               var dat = localStorage.getItem("api_info");
-               dat = dat == null ? prompt(msg) : prompt(msg, dat);
-               if (dat == null) return;
-               dat = dat.replace("http://", "").replace("https://", "");
-               localStorage.setItem("api_info", dat);
-       });
-
-       $("#run")[0].addEventListener("click", () =>
-       {
-               var dat = localStorage.getItem("api_info");
-               if (dat == null || dat == undefined)
-               {
-                       $(".response")[0].style.color = "red";
-                       $(".response")[0].innerText = "API info not set.\nPlease press the gear on the top-right.";
-                       return;
-               }
-               dat = dat.split(":");
-               if (dat.length != 3)
-               {
-                       $(".response")[0].style.color = "red";
-                       $(".response")[0].innerText = "API info not set correctly.\nPlease press the gear on the top-right.";
-                       return;
-               }
-               
-               EstoulsAPI.username = dat[0];
-               EstoulsAPI.apikey = dat[1];
-               EstoulsAPI.endpoint = "https://" + dat[2];
-               EstoulsAPI.request(getText()).then(x =>
-               {
-                       if (x.success && x.response.trim().length > 0)
-                       {
-                               $(".response")[0].style.color = "green";
-                               $(".response")[0].innerText = x.response;
-                       }
-                       else if (x.success && x.response.trim().length == 0)
-                       {
-                               $(".response")[0].style.color = "yellow";
-                               $(".response")[0].innerText = "Empty response.";
-                       }
-                       else
-                       {
-                               $(".response")[0].style.color = "red";
-                               $(".response")[0].innerText = x.response;
-                       }
-               }).catch(x => alert("ERR(2)" + x));
-       });
-
-       $(".basis")[0].addEventListener("paste", (e) =>
-       {
-               window.pauseCleaning = true;
-               e.preventDefault();
-               var text = (event.clipboardData || window.clipboardData).getData('text');
-               //text = htmlToText(text);
-               var sel = window.getSelection();
-               if (sel.rangeCount > 0)
-               {
-                       var range = sel.getRangeAt(0);
-                       range.deleteContents();
-                       if (!text.includes("\n"))
-                       {
-                               var textNode = document.createTextNode(text);
-                               range.insertNode(textNode);
-                               range.setStartAfter(textNode);
-                               range.setEndAfter(textNode);
-                               sel.removeAllRanges();
-                               sel.addRange(range);
-                       }
-                       else
+                       document.addEventListener("DOMContentLoaded", () =>
                        {
-                               var last;
-                               textToNodes(text).forEach(x =>
+                               var has = x => localStorage.getItem(x) != null && localStorage.getItem(x) != "";
+                               if (!has("addr")) localStorage.setItem("addr", window.location.origin);
+                               for (var field of [ "addr", "srvc", "user", "auth", "rqst" ])
                                {
-                                       range.insertNode(x);
-                                       range.setStartAfter(x);
-                                       range.setEndAfter(x);
-                               });
-                               sel.removeAllRanges();
-                               sel.addRange(range);
-                       }
-               }
-               window.pauseCleaning = undefined;
-       });
-
-       setInterval(() =>
-       {
-               if (window.stopMainLoop) return;
-               try
-               {
-                       removeEmptyNodes();
-                       $(".overlay")[0].scrollTop = $(".basis")[0].scrollTop;
-                       $(".overlay")[0].scrollLeft = $(".basis")[0].scrollLeft;
-                       $(".linecounter")[0].scrollTop = $(".basis")[0].scrollTop;
-                       $(".linecounter")[0].scrollTop = $(".basis")[0].scrollTop;
-                       var txt = $(".basis")[0].innerHTML;
-                       if (txt == window.oldHTML) return;
-                       localStorage.setItem("previous_text", getText());
-                       
-                       //<fix for a mobile bug>
-                       /*
-                       var pos = 0;
-                       var found = false;
-                       for (; pos < txt.length; pos++)
-                       {
-                               if (txt.substring(pos, pos + "<div>".length) == "<div>")
+                                       $("#" + field).value = has(field) ? localStorage.getItem(field) : "";
+                               }
+                               if (localStorage.getItem("addr") == null || localStorage.getItem("addr") == "")
                                {
-                                       found = true;
-                                       break;
+                                       localStorage.setItem("addr", window.location.origin);
                                }
-                       }
-                       if (pos != 0 && found)
-                       {
-                               var lhs = txt.substring(0, pos);
-                               var rhs = txt.substring(pos, txt.length);
-                               txt = "<div>" + lhs + "</div>" + rhs;
-                               $(".basis")[0].innerHTML = txt;
-                       }
-                       if (txt.includes("</div><br><div>"))
-                       {
-                               txt = txt.replaceAll("</div><br><div>", "");
-                               $(".basis")[0].innerHTML = txt;
-                       
-                       }*/
-                       //</fix for a mobile bug>
-                       
-                       if (txt.includes("qreg") || txt.includes("creg"))
-                       {
-                               txt = highlightForQAnsel(txt);
-                       }
-                       
-                       $(".overlay")[0].innerHTML = txt;
-                       window.oldHTML = $(".basis")[0].innerHTML;
-                       txt = $(".basis")[0].innerHTML;
-                       var linecount = getText().split("\n").length;
-                       txt = "";
-                       for (var i = 0; i < linecount; i++)
-                       {
-                               txt += (i + 1) + "<br />";
-                       }
-                       $(".linecounter")[0].innerHTML = txt;
-               }
-               catch (x)
-               {
-                       alert("ERR(3): " + x);
-                       window.stopMainLoop = true;
-               }
-       }, 10); 
-}
-</script>
-</body>
-</html>
+                               $("#resp").value = "";
+                       });
+               </script>
 
+       </body>
+</html>
diff --git a/src/www/rosado-api.js b/src/www/rosado-api.js
new file mode 100644 (file)
index 0000000..d126d61
--- /dev/null
@@ -0,0 +1,358 @@
+var RosadoAPI =
+{
+       username: undefined,
+       apikey: undefined,
+       endpoint: undefined,
+
+       Math32:
+       {
+               clamp: a =>
+               {
+                       while (a < 0 || a > 0xFFFFFFFF)
+                               a += a < 0 ? 0x100000000 : -0x100000000;
+                       return a;
+               },
+               shl: (a, b) => RosadoAPI.Math32.clamp(a << b),
+               shr: (a, b) => RosadoAPI.Math32.clamp(a >>> b),
+               rtl: (a, b) => RosadoAPI.Math32.clamp((a << b) | (a >>> (32 - b))),
+               rtr: (a, b) => RosadoAPI.Math32.clamp((a >>> b) | (a << (32 - b))),
+               add: (a, b) => RosadoAPI.Math32.clamp(a + b),
+               sub: (a, b) => RosadoAPI.Math32.clamp(a + b),
+               or: (a, b) => RosadoAPI.Math32.clamp(a | b),
+               xor: (a, b) => RosadoAPI.Math32.clamp(a ^ b)
+       },
+       HMACSHA256:
+       {
+               sign: async (key, msg) =>
+               {
+                       key = new Uint8Array(key);
+                       msg = new Uint8Array(msg);
+                       var ikey = await crypto.subtle.importKey
+                       (
+                               "raw",
+                               key,
+                               { name: "HMAC", hash: { name: "SHA-256" }},
+                               false,
+                               ["sign", "verify"]
+                       );
+                       return new Uint8Array(await window.crypto.subtle.sign({ name: "HMAC", }, ikey, msg));
+               }
+       },
+       ChaCha20:
+       {
+               QR: (cc, a, b, c, d) =>
+               {
+                       cc[a] = RosadoAPI.Math32.add(cc[a], cc[b]);
+                       cc[d] = RosadoAPI.Math32.xor(cc[d], cc[a]);
+                       cc[d] = RosadoAPI.Math32.rtl(cc[d], 16);
+
+                       cc[c] = RosadoAPI.Math32.add(cc[c], cc[d]);
+                       cc[b] = RosadoAPI.Math32.xor(cc[b], cc[c]);
+                       cc[b] = RosadoAPI.Math32.rtl(cc[b], 12);
+
+                       cc[a] = RosadoAPI.Math32.add(cc[a], cc[b]);
+                       cc[d] = RosadoAPI.Math32.xor(cc[d], cc[a]);
+                       cc[d] = RosadoAPI.Math32.rtl(cc[d], 8);
+
+                       cc[c] = RosadoAPI.Math32.add(cc[c], cc[d]);
+                       cc[b] = RosadoAPI.Math32.xor(cc[b], cc[c]);
+                       cc[b] = RosadoAPI.Math32.rtl(cc[b], 7);
+               },
+               DR: (cc) =>
+               {
+                       RosadoAPI.ChaCha20.QR(cc, 0, 4,  8, 12);
+                       RosadoAPI.ChaCha20.QR(cc, 1, 5,  9, 13);
+                       RosadoAPI.ChaCha20.QR(cc, 2, 6, 10, 14);
+                       RosadoAPI.ChaCha20.QR(cc, 3, 7, 11, 15);
+                       RosadoAPI.ChaCha20.QR(cc, 0, 5, 10, 15);
+                       RosadoAPI.ChaCha20.QR(cc, 1, 6, 11, 12);
+                       RosadoAPI.ChaCha20.QR(cc, 2, 7,  8, 13);
+                       RosadoAPI.ChaCha20.QR(cc, 3, 4,  9, 14);
+               },
+               CB: (cc) =>
+               {
+                       var i;
+                       var x = new Array(16);
+                       for (i = 0; i < 16; i++)
+                       {
+                               x[i] = cc[i];
+                       }
+                       for (i = 0; i < 10; i++)
+                       {
+                               RosadoAPI.ChaCha20.DR(cc);
+                       }
+                       for (i = 0; i < 16; i++)
+                       {
+                               cc[i] = RosadoAPI.Math32.add(cc[i], x[i]);
+                       }
+               },
+               S: (cc, cs) =>
+               {
+                       for (var i = 0; i < 16; i++)
+                       {
+                               cs[4 * i] = (cc[i] & 0xFF);
+                               cs[4 * i + 1] = ((cc[i] >> 8) & 0xFF);
+                               cs[4 * i + 2] = ((cc[i] >> 16) & 0xFF);
+                               cs[4 * i + 3] = ((cc[i] >> 24) & 0xFF);
+                       }
+               },
+               B: (key, nonce, block, out) =>
+               {
+                       var cc =
+                       [
+                               0x61707865, 0x3320646e, 0x79622d32, 0x6b206574,
+
+                               key[0] | (key[1] << 8) | (key[2] << 16) | (key[3] << 24),
+                               key[4] | (key[5] << 8) | (key[6] << 16) | (key[7] << 24),
+                               key[8] | (key[9] << 8) | (key[10] << 16) | (key[11] << 24),
+                               key[12] | (key[13] << 8) | (key[14] << 16) | (key[15] << 24),
+
+                               key[16] | (key[17] << 8) | (key[18] << 16) | (key[19] << 24),
+                               key[20] | (key[21] << 8) | (key[22] << 16) | (key[23] << 24),
+                               key[24] | (key[25] << 8) | (key[26] << 16) | (key[27] << 24),
+                               key[28] | (key[29] << 8) | (key[30] << 16) | (key[31] << 24),
+
+                               block,
+
+                               nonce[0] | (nonce[1] << 8) | (nonce[2] << 16) | (nonce[3] << 24),
+                               nonce[4] | (nonce[5] << 8) | (nonce[6] << 16) | (nonce[7] << 24),
+                               nonce[8] | (nonce[9] << 8) | (nonce[10] << 16) | (nonce[11] << 24)
+                       ];
+
+                       RosadoAPI.ChaCha20.CB(cc);
+                       RosadoAPI.ChaCha20.S(cc, out);
+               },
+               encrypt: async (key, nonce, block, data) =>
+               {
+                       var count = data.length;
+                       if (count > (274877906944 - block * 64)) return null;
+                       var ret = new Array(0);
+                       var ccblock = new Array(64);
+                       var size = 0;
+                       while (count > 64)
+                       {
+                               ret.length = size + 64;
+                               RosadoAPI.ChaCha20.B(key, nonce, block++, ccblock);
+                               for (var i = 0; i < 64; i++) ret[size + i] = ccblock[i];
+                               size += 64;
+                               count -= 64;
+                       }
+                       if (count > 0)
+                       {
+                               ret.length = size + count;
+                               RosadoAPI.ChaCha20.B(key, nonce, block, ccblock);
+                               for (var i = 0; i < count; i++) ret[size + i] = ccblock[i];
+                       }
+                       for (var i = 0; i < data.length; i++) ret[i] ^= data[i];
+                       return new Uint8Array(ret);
+               }
+       },
+
+       Base64:
+       {
+               encode: async (x) =>
+               {
+                       return await new Promise(r =>
+                       {
+                               const reader = new FileReader();
+                               reader.addEventListener("load", () => r(reader.result.split(",")[1]));
+                               reader.readAsDataURL(new Blob([new Uint8Array(x)]));
+                       });
+               },
+
+               decode: (b64) =>
+               {
+                       var dec1 = (v) =>
+                       {
+                               switch (v)
+                               {
+                                       case 'A': return  0;
+                                       case 'B': return  1;
+                                       case 'C': return  2;
+                                       case 'D': return  3;
+                                       case 'E': return  4;
+                                       case 'F': return  5;
+                                       case 'G': return  6;
+                                       case 'H': return  7;
+                                       case 'I': return  8;
+                                       case 'J': return  9;
+                                       case 'K': return 10;
+                                       case 'L': return 11;
+                                       case 'M': return 12;
+                                       case 'N': return 13;
+                                       case 'O': return 14;
+                                       case 'P': return 15;
+                                       case 'Q': return 16;
+                                       case 'R': return 17;
+                                       case 'S': return 18;
+                                       case 'T': return 19;
+                                       case 'U': return 20;
+                                       case 'V': return 21;
+                                       case 'W': return 22;
+                                       case 'X': return 23;
+                                       case 'Y': return 24;
+                                       case 'Z': return 25;
+                                       case 'a': return 26;
+                                       case 'b': return 27;
+                                       case 'c': return 28;
+                                       case 'd': return 29;
+                                       case 'e': return 30;
+                                       case 'f': return 31;
+                                       case 'g': return 32;
+                                       case 'h': return 33;
+                                       case 'i': return 34;
+                                       case 'j': return 35;
+                                       case 'k': return 36;
+                                       case 'l': return 37;
+                                       case 'm': return 38;
+                                       case 'n': return 39;
+                                       case 'o': return 40;
+                                       case 'p': return 41;
+                                       case 'q': return 42;
+                                       case 'r': return 43;
+                                       case 's': return 44;
+                                       case 't': return 45;
+                                       case 'u': return 46;
+                                       case 'v': return 47;
+                                       case 'w': return 48;
+                                       case 'x': return 49;
+                                       case 'y': return 50;
+                                       case 'z': return 51;
+                                       case '0': return 52;
+                                       case '1': return 53;
+                                       case '2': return 54;
+                                       case '3': return 55;
+                                       case '4': return 56;
+                                       case '5': return 57;
+                                       case '6': return 58;
+                                       case '7': return 59;
+                                       case '8': return 60;
+                                       case '9': return 61;
+                                       case '+': return 62;
+                                       case '/': return 63;
+                               }
+                               return 255;
+                       };
+
+                       var ret = new Array(0);
+                       var buffer = 0;
+                       var bufferS = 0;
+                       for (var i = 0; i < b64.length; i++)
+                       {
+                               if (b64[i] == '=') { bufferS = 0; continue; };
+                               var val = dec1(b64[i]);
+                               if (val == 255) return null;
+                               buffer = ((buffer << 6) | val) & 0xFFFF;
+                               bufferS += 6;
+                               if (bufferS >= 8)
+                               {
+                                       var shift = (16 - bufferS) & 0xFF;
+                                       buffer = (buffer << shift) & 0xFFFF;
+                                       ret[ret.length] = (buffer >>> 8) & 0xFF;
+                                       buffer = buffer & 0x00FF;
+                                       buffer = (buffer >>> shift) & 0xFFFF;
+                                       bufferS -= 8;
+                               }
+                       }
+                       if (bufferS > 0)
+                       {
+                               buffer = (buffer << (16 - bufferS)) & 0xFFFF;
+                               ret[ret.length] = (buffer >>> 8) & 0xFF;
+                       }
+                       return new Uint8Array(ret);
+               }
+       },
+
+       Hex:
+       {
+               encode: (x) =>
+               {
+                       var ret = "";
+                       x.forEach(y => { ret += y.toString(16).padStart(2, "0"); });
+                       return ret;
+               },
+               decode: (x) =>
+               {
+                       var arr = new Array(0);
+                       for (var i = 0; i < x.length; i += 2)
+                       {
+                               arr[arr.length] = parseInt(x.charAt(i) + x.charAt(i + 1), 16);
+                       }
+                       return new Uint8Array(arr);
+               }
+       },
+
+       generateRequest: async (msg) =>
+       {
+               msg = new TextEncoder().encode(msg);
+               var key = RosadoAPI.Hex.decode(RosadoAPI.apikey);
+               //var sess = crypto.getRandomValues(new Uint8Array(12));
+               var r = () => { return Math.floor(Math.random() * 10000) % 256 };;
+               var sess = new Uint8Array([r(), r(), r(), r(), r(), r(), r(), r(), r(), r(), r(), r()]);
+               var rawdata = await RosadoAPI.ChaCha20.encrypt(key, sess, 0, msg);
+               var dgst = await RosadoAPI.Hex.encode(await RosadoAPI.HMACSHA256.sign(key, rawdata));
+               var data = await RosadoAPI.Base64.encode(rawdata);
+               var resp = "user=" + RosadoAPI.username;
+               resp += "&sess=" + RosadoAPI.Hex.encode(sess);
+               resp += "&dgst=" + dgst;
+               resp += "&data=" + data;
+               return resp;
+       },
+
+       parseResponse: async (msg) =>
+       {
+               if (!msg.includes("user=") || !msg.includes("&sess=") || !msg.includes("&dgst=") || !msg.includes("&data="))
+               {
+                       return { success: false, response: "invalid response" };
+               }
+               var user = msg.split("user=")[1].split("&")[0].trim();
+               var sess = msg.split("&sess=")[1].split("&")[0].trim();
+               var dgst = msg.split("&dgst=")[1].split("&")[0].trim();
+               var data = msg.split("&data=")[1].split("&")[0].trim();
+
+               if (user != RosadoAPI.username)
+               {
+                       return { success: false, response: "invalid user in response" };
+               }
+               data = RosadoAPI.Base64.decode(data);
+               sess = RosadoAPI.Hex.decode(sess);
+               var key = RosadoAPI.Hex.decode(RosadoAPI.apikey);
+               var sig = await RosadoAPI.Hex.encode(await RosadoAPI.HMACSHA256.sign(key, data));
+               if (sig != dgst)
+               {
+                       return { success: false, response: "invalid signature" };
+               }
+               var resp = await RosadoAPI.ChaCha20.encrypt(key, sess, 0, data);
+               resp = new TextDecoder().decode(resp);
+               resp = resp ? resp : "(null)";
+               return { success: true, response: resp };
+       },
+
+       request: async (msg) =>
+       {
+               if (msg.trim().length == 0) return { success: true, response: "" };
+               var req = await RosadoAPI.generateRequest(msg);
+               return await new Promise(r =>
+               {
+                       const xhr = new XMLHttpRequest();
+                       xhr.open("POST", RosadoAPI.endpoint, true);
+                       xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
+                       xhr.onreadystatechange = (x) =>
+                       {
+                               if (xhr.readyState === XMLHttpRequest.DONE)
+                               {
+                                       if (xhr.status === 200)
+                                       {
+                                               RosadoAPI.parseResponse(xhr.responseText).then(pr => { r(pr); }).catch(x => alert("ERR(3)"));
+                                       }
+                                       else
+                                       {
+                                               r({success: false, response: xhr.status.toString()});
+                                       }
+                               }
+                       }
+                       xhr.send(req);
+               }).catch(x => alert("ERR(1): " + x));
+       }
+};
+