]> foleosoft.com Git - RosadoAPI.git/commitdiff
Sun Jan 19 09:43:18 PM EST 2025
authorserver <[email protected]>
Mon, 20 Jan 2025 02:43:18 +0000 (21:43 -0500)
committerserver <[email protected]>
Mon, 20 Jan 2025 02:43:18 +0000 (21:43 -0500)
README.md [new file with mode: 0644]
bin/EstoulsAPI.apk [new file with mode: 0644]
bin/favicon.ico [new file with mode: 0644]
bin/frontend.html [new file with mode: 0644]
build.sh [new file with mode: 0644]
rcs/EstoulsAPI.apk [new file with mode: 0644]
rcs/favicon.ico [new file with mode: 0644]
rcs/frontend.html [new file with mode: 0644]
src/CryptoFoleo.h [new file with mode: 0644]
src/http.c [new file with mode: 0644]
src/main.c [new file with mode: 0644]

diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..f15b7de
--- /dev/null
+++ b/README.md
@@ -0,0 +1,147 @@
+# EstousAPI
+
+Easily transform as many CLI programs as you wish into web APIs.
+
+## Compilation
+
+Please click the `tree` link at the top and navigate to the
+`bin` folder and download the following file to get the source
+code for the simulator.
+
+```
+       qansel-source-base.zip
+```
+
+To compile it, navigate to the folder it installed within and
+run the following commands within that folder.
+
+```sh
+       mkdir qansel
+       mv *qansel-source-base.zip qansel
+       cd qansel
+       unzip *qansel-source-base.zip
+       make
+```
+
+If you are compiling for a device which cannot support
+hardware acceleration, then you can use `make simple` which
+will build the program with those features stripped out.
+
+## Usage
+
+
+
+
+
+Many example programs that can be executed inside of QAnsel can
+be found by clicking the `tree` link at the top fo the page and
+then navigating to the `examples` folder. Simply click on one of
+the examples in order to see the source code.
+
+## Usage
+
+The QAnsel simulator expects programs to be written in a language
+similar to OpenQASM 2.0. These programs must be piped into QAnsel
+as standard input and the simulation results will be displayed as
+standard output. Below is an example using a here document.
+
+```sh
+       $ ./QAnsel << EOF
+       > qreg q[2];
+       > creg c[2];
+       > h q[0];
+       > cx q[0], q[1];
+       > measure q[0] -> c[0];
+       > measure q[1] -> c[1];
+       > sample c;
+       > EOF
+       00: 50.7%
+       01: 0.0%
+       10: 0.0%
+       11: 49.3%
+```
+
+Please use the `-?` flag to see a help document.
+
+## Special Hardware
+
+To enable a hardware random number generator, the `-r`
+flag must be used. This flag will select the hardware
+random number generator based on an order of precedence.
+The order is as follows.
+
+1. Quantis-PCIe-40M
+2. TrueRNG V3
+3. Secure Key Technology
+
+To enable GPU acceleration, the `-oX` flag has to be
+set replacing `X` with an optimization level equal to
+4 or greater.
+
+It is recommended that you run a simple program with the
+`-v` flag which will produce output stating which hardware
+devices were actually found and enabled.
+
+QAnsel can handle up to 16 qubits, however, qubit counts
+greater than 14 will not fit into most consumer-end GPUs.
+The amount of qubits that can fit is limited by the
+amount of VRAM. The GPU must have at least 8 GB for 14
+qubits, 16 GB for 15 qubits, and 48 GB for 16 qubits.
+
+## API and Web Interface
+
+There is a drag-and-drop interface that runs in the browser
+that can be found by clicking the `tree` button and navigating
+to the `bin` folder and downloading the following file.
+
+```
+       qansel-source-web.zip
+```
+
+This web front end expects to connect to an API. The API
+service is handled through FoleoAPI. Please see the FoleoAPI
+project in order to set up this service.
+
+Once FoleoAPI is running, a ServiceInfo record will need to be
+added to the APIService schema. Below is an example of how this
+entry may look.
+
+```sql
+       MariaDB [APIService]> select * from ServiceInfo;
+       +----+---------+---------------------------------+-------------+
+       | id | service | path                            | parameters  |
+       +----+---------+---------------------------------+-------------+
+       |  1 | qansel  | /fakepath/QAnsel                | -q14 -o5 -r |
+       +----+---------+---------------------------------+-------------+
+```
+
+The `-qX` parameter is useful here as it allows one to cap 
+the maximum qubit count allowed for the process in case the
+hardware running the simulator is not sufficient to handle large
+qubit counts. In this case, it is capped at 14 to make sure that
+programs that exceed the limitations of the GPU are rejected.
+
+The web interface expects an API key. The API key is formed in three
+parts `A:B:C` where `A` is the `APIService.AuthInfo.username`, `B` is
+the `APIService.AuthInfo.authkey`, and `C` is the URL which it needs
+to ping. It is important that the address does not contain `http://`
+or `https://`. For example, if the address is `http://example.com/api`,
+then `C` should simply be written as `example.com/api`.
+
+Once `A:B:C` is filled out, the entire thing needs to be converted to
+a base64 string. This can be done in JavaScript in the developer console
+in the web browser using the `btoa()` function. This final encoded string
+functions as the API key for the web interface. Any equal signs at the
+end of the API key should be removed.
+
+The web interface allows for appending the URL with `?apikey=` followed by
+an API key to auto fill the API key. This can be useful if the service is
+linked from another source (such as another website or an email) and will
+prevent the user from having to input the APIkey themselves.
+
+## Android
+
+The Android app version of this project merely is enclose a web view
+containing the web interface. This means it requires pinging a server
+running the API for it to work. It must be compiled using Android Studio.
+
diff --git a/bin/EstoulsAPI.apk b/bin/EstoulsAPI.apk
new file mode 100644 (file)
index 0000000..fb5f8d6
Binary files /dev/null and b/bin/EstoulsAPI.apk differ
diff --git a/bin/favicon.ico b/bin/favicon.ico
new file mode 100644 (file)
index 0000000..e33686d
Binary files /dev/null and b/bin/favicon.ico differ
diff --git a/bin/frontend.html b/bin/frontend.html
new file mode 100644 (file)
index 0000000..b77d983
--- /dev/null
@@ -0,0 +1,1069 @@
+<!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)
+                               {
+                                       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")
+                                       {
+                                               setText(prevText);
+                                       }
+                               }
+                       }
+                       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
+                       {
+                               var last;
+                               textToNodes(text).forEach(x =>
+                               {
+                                       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>")
+                               {
+                                       found = true;
+                                       break;
+                               }
+                       }
+                       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>
+
diff --git a/build.sh b/build.sh
new file mode 100644 (file)
index 0000000..5efefdf
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+if [ "$1" == "run" ]
+then
+    while [ 1 ]
+    do
+        ./bin/APIServer
+    done
+else
+    xxd -i ./rcs/frontend.html ./src/frontend.h
+    gcc ./src/main.c -o ./bin/APIServer $(mysql_config --cflags --libs) -lCryptoFoleo
+    rm ./src/frontend.h
+    cp ./rcs/* ./bin/
+fi
diff --git a/rcs/EstoulsAPI.apk b/rcs/EstoulsAPI.apk
new file mode 100644 (file)
index 0000000..fb5f8d6
Binary files /dev/null and b/rcs/EstoulsAPI.apk differ
diff --git a/rcs/favicon.ico b/rcs/favicon.ico
new file mode 100644 (file)
index 0000000..e33686d
Binary files /dev/null and b/rcs/favicon.ico differ
diff --git a/rcs/frontend.html b/rcs/frontend.html
new file mode 100644 (file)
index 0000000..b77d983
--- /dev/null
@@ -0,0 +1,1069 @@
+<!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)
+                               {
+                                       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")
+                                       {
+                                               setText(prevText);
+                                       }
+                               }
+                       }
+                       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
+                       {
+                               var last;
+                               textToNodes(text).forEach(x =>
+                               {
+                                       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>")
+                               {
+                                       found = true;
+                                       break;
+                               }
+                       }
+                       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>
+
diff --git a/src/CryptoFoleo.h b/src/CryptoFoleo.h
new file mode 100644 (file)
index 0000000..e906681
--- /dev/null
@@ -0,0 +1,62 @@
+#ifndef __HEADERS__
+#define __HEADERS__
+#include <stdint.h>
+#include <gmp.h>
+
+uint8_t* foleo_chacha20(uint8_t[32], uint8_t[12], uint32_t, size_t);
+uint8_t* foleo_chacha20_poly1305(uint8_t[32], uint8_t[12], uint8_t*, size_t);
+uint8_t* foleo_dhke(uint8_t*, uint8_t*);
+uint16_t foleo_dhke_modsize();
+
+uint8_t* foleo_poly1305(uint8_t[32], uint8_t*, size_t);
+#define FOLEO_RSA_PADDING_NONE 99
+#define FOLEO_RSA_PADDING_ENCRYPTION 1
+#define FOLEO_RSA_PADDING_SIGNATURE 2
+#define FOLEO_RSA_PADDING_OAEP 3
+#define FOLEO_RSA_PADDING_PSS 4
+#define FOLEO_RAND_MODE_DEVR 1
+#define FOLEO_RAND_MODE_DEV 2
+#ifdef ENABLE_X86
+#define FOLEO_RAND_MODE_X86 3
+#endif
+typedef struct
+{
+    mpz_t n, k;
+    uint8_t* label;
+    uint16_t bitWidth;
+} rsakey_t;
+void foleo_rsa_import(rsakey_t*, uint8_t*);
+uint8_t* foleo_rsa_export(rsakey_t*);
+void foleo_rsa_free(rsakey_t*);
+void foleo_rsa_keygen(uint16_t, rsakey_t*, rsakey_t*);
+
+//The maximum message block size that can be used
+//  for a particular padding scheme.
+uint16_t foleo_rsa_msgsize(rsakey_t*, uint8_t);
+
+//Size of the rsakey struct
+uint16_t foleo_rsa_keysize();
+
+//Size in bytes of RSA modulus, same thing as the number
+//  of bytes the encrypt() function will return
+uint16_t foleo_rsa_modsize(rsakey_t*);
+
+uint8_t* foleo_rsa_encrypt(rsakey_t*, uint8_t, uint8_t*, uint16_t);
+uint8_t* foleo_rsa_decrypt(rsakey_t*, uint8_t, uint8_t*, uint16_t*);
+uint8_t* foleo_sha256(uint8_t*, uint32_t);
+
+#define FOLEO_SHA256 1
+uint8_t* foleo_hmac(uint8_t, uint8_t*, uint32_t, uint8_t*, uint32_t);
+uint8_t* foleo_hmac_hkdf(uint8_t, uint32_t, uint8_t*, uint32_t, uint8_t*, uint32_t, uint8_t*, uint32_t);
+uint8_t* foleo_hmac_prf(uint8_t, uint32_t, uint8_t*, uint32_t, uint8_t*, uint32_t, uint8_t*, uint32_t);
+
+uint8_t foleo_hash_size(uint8_t);
+
+void foleo_rand_mode(uint8_t, uint8_t*);
+
+uint8_t* foleo_decode_base64(uint8_t*, size_t*);
+uint8_t* foleo_decode_hex(uint8_t*);
+uint8_t* foleo_encode_hex(uint8_t*, size_t);
+uint8_t* foleo_encode_base64(uint8_t*, size_t);
+
+#endif
diff --git a/src/http.c b/src/http.c
new file mode 100644 (file)
index 0000000..1ba45a7
--- /dev/null
@@ -0,0 +1,403 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <netinet/in.h>
+#include <fcntl.h>
+
+char *HTTP_SERVER = "Server: FoleoSoft\n";
+
+int httpOpenSocket(char* ip, char* port)
+{
+       //get info about the server socket
+       struct addrinfo hints;
+       memset(&hints, 0, sizeof(hints));
+       hints.ai_family = AF_UNSPEC; //IPv4 or IPv6
+       hints.ai_protocol = 0; //Only useful if family is specific
+       hints.ai_socktype = SOCK_STREAM; //TCP only (excludes UDP)
+       hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;
+       struct addrinfo *sockinfo = 0;
+       int err = getaddrinfo(ip, port, &hints, &sockinfo);
+       if (err != 0)
+       {
+               printf("getaddrinfo(): %i\n", err);
+               return -1;
+       }
+
+       //create the server socket from that info
+       int server_fd = socket(sockinfo->ai_family, sockinfo->ai_socktype, sockinfo->ai_protocol);
+       if (server_fd == -1)
+       {
+               printf("socket(): %s", strerror(errno));
+               return -1;
+       }
+
+       //Allow network service to be restarted when need-be
+       int reuseaddr = 1;
+       if ( setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr)) == -1 )
+       {
+               printf("setsockopt(): %i\n", errno);
+               return -1;
+       }
+
+       //Bind IP to socket
+       if ( bind(server_fd, sockinfo->ai_addr, sockinfo->ai_addrlen) == -1 )
+       {
+               printf("bind(): %i\n", errno);
+               return -1;
+       }
+
+       //Free no longer needed sockinfo
+       freeaddrinfo(sockinfo);
+
+       //Start listening on the socket
+       if ( listen(server_fd, SOMAXCONN) )
+       {
+               printf("listen(): %i\n", errno);
+               return -1;
+       }
+
+       return server_fd;
+}
+
+char* httpParseNext(char end, char* buff, int* buff_pos, int buff_len)
+{
+       char* ret = malloc(0);
+       int ret_len = 0;
+       for (int i = *buff_pos; i < buff_len && buff[i] != end; i++)
+       {
+               ret = realloc(ret, ret_len + 1);
+               ret[ret_len] = buff[i];
+               *buff_pos += 1;
+               ret_len += 1;
+       }
+       ret = realloc(ret, ret_len + 1);
+       ret[ret_len] = 0;
+       *buff_pos += 1;
+       return ret;
+}
+
+void httpNext(char end, char* buff, int* buff_pos, int buff_len)
+{
+       int pos = 0;
+       for (int i = *buff_pos; i < buff_len && buff[i] != end; i++)
+       {
+               *buff_pos += 1;
+       }
+       *buff_pos += 1;
+}
+
+char* httpParse(char* search, char* buff, long buff_len)
+{
+       int buff_pos = 0;
+
+       if ( strcmp(search, "Method") == 0 )
+       {
+               return httpParseNext(' ', buff, &buff_pos, buff_len);
+       }
+       else if ( strcmp(search, "Path") == 0 )
+       {
+               httpNext(' ', buff, &buff_pos, buff_len);
+               return httpParseNext(' ', buff, &buff_pos, buff_len);
+       }
+       else if ( strcmp(search, "Version") == 0 )
+       {
+               if (buff[0] == 'H' && buff[1] == 'T' && buff[2] == 'T')
+               {
+                       return httpParseNext(' ', buff, &buff_pos, buff_len);
+               }
+               else
+               {
+                       httpNext(' ', buff, &buff_pos, buff_len);
+                       httpNext(' ', buff, &buff_pos, buff_len);
+                       return httpParseNext('\n', buff, &buff_pos, buff_len);
+               }
+       }
+       else if ( strcmp(search, "Content") == 0 )
+       {
+               for (int i = 3; i < buff_len; i++)
+               { 
+                       if (buff[i - 3] == '\r' && buff[i - 2] == '\n' && buff[i - 1] == '\r' && buff[i - 0] == '\n')
+                       {
+                               i++;
+                               char* ret = malloc(buff_len - i);
+                               memcpy(ret, buff + i, buff_len - i);
+                               return ret;
+                       }
+               }
+               printf("hit a null\n");
+               return NULL;
+       }
+       else
+       {
+               httpNext('\n', buff, &buff_pos, buff_len);
+               while (buff_pos < buff_len)
+               {
+                       char* tmp = httpParseNext(':', buff, &buff_pos, buff_len);
+                       if ( strcmp(tmp, search) == 0 )
+                       {
+                               free(tmp);
+                               buff_pos++;
+                               return httpParseNext('\n', buff, &buff_pos, buff_len);
+                       }
+                       free(tmp);
+                       httpNext('\n', buff, &buff_pos, buff_len);
+               }
+               return NULL;
+       }
+}
+
+char* httpParsePost(char* search, char* post, long post_len)
+{
+       int rsearchL = strlen(search) + 1;
+       char* rsearch = malloc(rsearchL);
+       memcpy(rsearch, search, rsearchL);
+       rsearch[rsearchL - 1] = '=';
+       for (int i = 0; i < post_len - rsearchL; i++)
+       {
+               if (memcmp(rsearch, post + i, rsearchL) == 0)
+               {
+                       i += rsearchL;
+                       char* ret = malloc(1);
+                       int retL = 1;
+                       ret[0] = 0;
+                       for (; i < post_len; i++)
+                       {
+                               if (post[i] == '&') break;
+                               ret = realloc(ret, ++retL);
+                               ret[retL - 2] = post[i];
+                               ret[retL - 1] = 0;
+                       }
+                       free(rsearch);
+                       return ret;
+               }
+       }
+       free(rsearch);
+       return NULL;
+}
+
+void httpBegin(int socket_fd, int* session_fd)
+{
+       *session_fd = accept(socket_fd, 0, 0);
+}
+
+void httpEnd(int session_fd)
+{
+       close(session_fd);
+}
+
+void httpRespond(int session_fd, char* str)
+{
+       size_t strL = strlen(str);
+       for (size_t i = 0; i < strL; i++)
+       {
+               if (write(session_fd, str + i, 1) <= 0)
+               {
+                       i -= 1;
+               }
+       }
+}
+
+void httpRespondByte(int session_fd, uint8_t byte)
+{
+       while (write(session_fd, &byte, 1) <= 0) ;
+}
+
+void httpError(int session_fd, int error, char *msg)
+{
+       char headerTemplate[] = "HTTP/1.1 %i %s\n";
+       int headerLen = snprintf(NULL, 0, headerTemplate, error, msg);
+       char *header = malloc(headerLen);
+       sprintf(header, headerTemplate, error, msg);
+
+       char pageTemplate[] = "Error %i: %s\n";
+       int pageLen = snprintf(NULL, 0, pageTemplate, error, msg);
+       char *page = malloc(pageLen);
+       sprintf(page, pageTemplate, error, msg);
+
+       char contentLengthTemplate[] = "Content-Length: %i\n\n";
+       int contentLengthLen = snprintf(NULL, 0, contentLengthTemplate, pageLen);
+       char *contentLength = malloc(contentLengthLen);
+       sprintf(contentLength, contentLengthTemplate, pageLen);
+
+       httpRespond(session_fd, header);
+       httpRespond(session_fd, HTTP_SERVER);
+       httpRespond(session_fd, contentLength);
+       httpRespond(session_fd, page);
+
+       free(header);
+       free(contentLength);
+       free(page);
+}
+
+int httpFileOut(int session_fd, char* path, uint8_t fileType)
+{
+       char* safePath;
+       int safePathLen;
+       int pathLen = strlen(path);
+       if (pathLen == 0) return 0;
+       if (path[0] == '/')
+       {
+               safePath = malloc(1);
+               safePath[0] = '.';
+               safePathLen = 1;
+       }
+       else
+       {
+               safePath = malloc(0);
+               safePathLen = 0;
+       }
+       for (int i = 0; i < pathLen; i++)
+       {
+               if (i > 0 && path[i] == '.')
+               {
+                       if (path[i - 1] == '/' || path[i - 1] == '.')
+                       {
+                               continue;
+                       }
+               }
+               if (path[i] == '/' && safePathLen > 0)
+               {
+                       if (safePath[safePathLen - 1] == '/')
+                       {
+                               continue;
+                       }
+               }
+               safePath = realloc(safePath, ++safePathLen);
+               safePath[safePathLen - 1] = path[i];
+       }
+       safePath = realloc(safePath, ++safePathLen);
+       safePath[safePathLen - 1] = 0;
+
+       FILE *fd = fopen(safePath, "r");
+       free(safePath);
+       if (fd == NULL)
+       {
+               httpError(session_fd, 404, "Page Not Found");
+               return 1;
+       }
+       fseek(fd, 0, SEEK_END);
+       size_t fileSize = ftell(fd);
+       rewind(fd);
+       char* fileData = malloc(fileSize);
+       fread(fileData, 1, fileSize, fd);
+       fclose(fd);
+
+
+       if (fileSize < 0)
+       {
+               httpError(session_fd, 404, "Page Not Found");
+               return 1;
+       }
+
+       //Good header
+       httpRespond(session_fd, "HTTP/1.1 200 OK\n");
+       httpRespond(session_fd, HTTP_SERVER);
+
+       //Spit out file content length part of the HTTP message
+       char* contentLengthTemplate;
+       const char contentLengthTemplate0[] = "Access-Control-Allow-Origin: *\nContent-Type: application/octet-stream\nContent-Length: %i\n\n";
+       const char contentLengthTemplate1[] = "Access-Control-Allow-Origin: *\nContent-Type: image/x-icon\nContent-Length: %i\n\n";
+       switch (fileType)
+       {
+               case 0:
+                       contentLengthTemplate = malloc(strlen(contentLengthTemplate0) + 1);
+                       strcpy(contentLengthTemplate, contentLengthTemplate0);
+               break;
+               case 1:
+                       contentLengthTemplate = malloc(strlen(contentLengthTemplate1) + 1);
+                       strcpy(contentLengthTemplate, contentLengthTemplate1);
+               break;
+       }
+       size_t contentLengthLen = snprintf(NULL, 0, contentLengthTemplate, fileSize);
+       char* contentLength = malloc(contentLengthLen + 1);
+       sprintf(contentLength, contentLengthTemplate, fileSize);
+       httpRespond(session_fd, contentLength);
+       free(contentLength);
+       for (size_t i = 0; i < fileSize; i++)
+       {
+               if (i == 0)
+               {
+                       printf(">%02X<\n", fileData[i]);
+               }
+               httpRespondByte(session_fd, fileData[i]);
+       }
+       free(fileData);
+       free(contentLengthTemplate);
+}
+
+int httpStringOut(int session_fd, char* str)
+{
+       //Good header
+       httpRespond(session_fd, "HTTP/1.1 200 OK\n");
+       httpRespond(session_fd, HTTP_SERVER);
+
+       int strSize = strlen(str);
+       //Spit out file content length part of the HTTP message
+       const char *contentLengthTemplate = "Access-Control-Allow-Origin: *\nContent-Type: text/html\nContent-Length: %i\n\n";
+       int contentLengthLen = snprintf(NULL, 0, contentLengthTemplate, strSize);
+       char* contentLength = malloc(contentLengthLen + 1);
+       sprintf(contentLength, contentLengthTemplate, strSize);
+       httpRespond(session_fd, contentLength);
+       free(contentLength);
+
+       httpRespond(session_fd, str);
+       httpRespond(session_fd, "\n");
+}
+
+void httpRequest(int session_fd, char** str, int* strlen)
+{
+       int flags = fcntl(session_fd, F_GETFL, 0);
+       fcntl(session_fd, F_SETFL, flags | O_NONBLOCK);
+
+       *strlen = 0;
+       *str = malloc(0);
+       uint8_t buffer[1024];
+       int32_t length;
+       int8_t tries = 3;
+       
+       while (tries > 0)
+       {
+               while ( (length = read(session_fd, buffer, sizeof(buffer))) > 0 )
+               {
+                       *str = realloc(*str, (*strlen) + length);
+                       memcpy((*str) + (*strlen), buffer, length);
+                       *strlen += length;
+               }
+               usleep(1000);
+               tries--;
+       }
+       
+       *str = realloc(*str, (*strlen) + 1);
+       (*str)[*strlen] = 0;
+       *strlen += 1;
+       
+       /*
+       ioctl(session_fd, FIONREAD, strlen);
+       if (*strlen > 0)
+       {
+               *str = malloc(*strlen * sizeof(char));
+               read(session_fd, *str, *strlen);
+       }
+       else
+       {
+               *str = NULL;
+               *strlen = 0;
+       }
+       */
+
+       printf("-------------------------------\n");
+       printf("Length: %i\n", *strlen);
+       printf("-------------------------------\n");
+       for (int i = 0; i < *strlen; i++)
+       {
+               putchar((*str)[i]);
+       }
+       putchar('\n');
+       printf("-------------------------------\n");
+}
+
diff --git a/src/main.c b/src/main.c
new file mode 100644 (file)
index 0000000..82bd2e8
--- /dev/null
@@ -0,0 +1,494 @@
+#include <mysql.h>
+#include <stdio.h>
+#include <pthread.h>
+#include <unistd.h>
+#include "http.c"
+#include "frontend.h"
+#include "CryptoFoleo.h"
+
+uint8_t HOSTNAME[1024];
+uint8_t HOSTNAME_I[1024];
+
+//grabs auth info from the database
+uint8_t* getDatabaseInfo(uint8_t type, uint8_t* a, uint8_t* b, uint8_t* c)
+{
+       uint8_t* ret = NULL;
+    MYSQL *conn;
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    conn = mysql_init(NULL);
+       FILE *f = fopen("/usr/share/estous/password", "r");
+       if (!f)
+       {
+               fprintf(stderr, "Could not open /usr/share/estousapi/password\n");
+               exit(1);
+       }
+       char* p = malloc(0);
+       int c;
+       while ( (c = fgetc(f)) != EOF)
+
+    if (!mysql_real_connect(conn, "localhost", HOSTNAME, "", "APIService", 0, NULL, 0)) {
+        fprintf(stderr, "%s\n", mysql_error(conn));
+        return NULL;
+    }
+
+       if (type == 1)
+       {
+               uint8_t* prequery = "select %s from AuthInfo where service='%s' and username='%s' limit 1;";
+               uint16_t queryLength = snprintf(NULL, 0, prequery, b, c, a);
+               uint8_t query[queryLength + 1];
+               sprintf(query, prequery, b, c, a);
+               if (mysql_query(conn, query))
+               {
+                       fprintf(stderr, "%s\n", mysql_error(conn));
+                       return NULL;
+               }
+       }
+       else
+       {
+               uint8_t* prequery = "select %s from ServiceInfo where service='%s' limit 1;";
+               uint16_t queryLength = snprintf(NULL, 0, prequery, b, a);
+               uint8_t query[queryLength + 1];
+               sprintf(query, prequery, b, a);
+               if (mysql_query(conn, query))
+               {
+                       fprintf(stderr, "%s\n", mysql_error(conn));
+                       return NULL;
+               }
+       }
+
+       res = mysql_use_result(conn);
+       if ((row = mysql_fetch_row(res)) != NULL)
+       {
+               ret = malloc(strlen(row[0]) + 1);
+               strcpy(ret, row[0]);
+    }
+    mysql_free_result(res);
+    mysql_close(conn);
+
+       return ret;
+}
+
+uint8_t* runService(uint8_t* service_name, uint8_t* service_data, uint32_t* service_dataL, uint32_t timeout)
+{
+       uint8_t* data = malloc(strlen(service_data) + 1);
+       strcpy(data, service_data);
+       uint8_t* service_path = getDatabaseInfo(2, service_name, "path", "");
+       uint8_t* service_parms = getDatabaseInfo(2, service_name, "parameters", "");
+       if (service_path == NULL || service_parms == NULL)
+       {
+               free(service_path);
+               free(service_parms);
+               return NULL;
+       }
+
+       uint8_t heredoc[] = "HERE";
+       uint16_t heredocL = strlen(heredoc);
+       uint32_t dataL = strlen(data);
+       for (uint32_t i = heredocL; i < dataL; i++)
+       {
+               if (memcmp(data + (i - heredocL), heredoc, heredocL) == 0)
+               {
+                       memset(data + (i - heredocL), ' ', heredocL);
+               }
+       }
+
+       uint8_t template[] = "/tmp/apiserviceXXXXXX";
+       int8_t fd = mkstemp(template);
+       if (fd == -1)
+       {
+               perror("mkstemp");
+               exit(EXIT_FAILURE);
+       }
+       write(fd, data, dataL);
+       close(fd);
+
+       uint8_t precommand[] = "(cat %s | (timeout %is %s %s) || echo Timed out.)2>&1";
+       uint16_t commandL = snprintf(NULL, 0, precommand, template, timeout, service_path, service_parms);
+       uint8_t command[commandL + 1];
+       sprintf(command, precommand, template, timeout, service_path, service_parms);
+       free(service_path);
+       free(service_parms);
+       free(data);
+
+       printf("-------------Command--------------\n");
+       printf("%s\n", command);
+       printf("----------------------------------\n");
+       FILE *service;
+    service = popen(command, "r");
+    if (service == NULL)
+       {
+        perror("popen");
+               return NULL;
+    }
+
+       uint8_t* ret = malloc(0);
+       uint32_t retL = 0;
+       int32_t c;
+       while ((c = fgetc(service)) != EOF)
+       {
+               retL++;
+               ret = realloc(ret, retL);
+               ret[retL - 1] = (uint8_t)c;
+       }
+
+    int status = pclose(service);
+    if (status == -1)
+       {
+               free(ret);
+        perror("pclose");
+        return NULL;
+    }
+       *service_dataL = retL;
+    unlink(template);
+       return ret;
+}
+
+//process a connection that was provided necessary info
+void processUserAndData(int session_fd, char* user, char* data, char* session, char* digest, char* service)
+{
+       for (int i = 0; i < strlen(user); i++)
+       {
+               if (user[i] == '\'')
+               {
+                       httpError(session_fd, 400, "Bad Request (6)");
+                       return;
+               }
+       }
+
+       if (strlen(digest) != (32 * 2) || strlen(session) != (12 * 2))
+       {
+               httpError(session_fd, 400, "Bad Request (7)");
+               return;
+       }
+
+       char* authkey = getDatabaseInfo(1, user, "authkey", service);
+       if (authkey == NULL)
+       {
+               free(authkey);
+               httpError(session_fd, 403, "Forbidden (1)");
+               return;
+       }
+
+       int keyid = 0;
+       uint8_t* sessionAsBinary = foleo_decode_hex(session);
+       uint8_t* authkeyAsBinary = foleo_decode_hex(authkey);
+       uint8_t* digestAsBinary = foleo_decode_hex(digest);
+       size_t dataSize;
+       uint8_t* dataAsBinary = foleo_decode_base64(data, &dataSize);
+       free(authkey);
+
+       if (sessionAsBinary == NULL || authkeyAsBinary == NULL || dataAsBinary == NULL || digestAsBinary == NULL || dataSize == 0)
+       {
+               free(sessionAsBinary);
+               free(authkeyAsBinary);
+               free(dataAsBinary);
+               free(digestAsBinary);
+               httpError(session_fd, 400, "Bad Request (8)");
+               return;
+       }
+
+       uint8_t* mac = foleo_hmac(FOLEO_SHA256, authkeyAsBinary, 256 / 8, dataAsBinary, dataSize);
+       if (memcmp(mac, digestAsBinary, 256 / 8) != 0)
+       {
+               free(sessionAsBinary);
+               free(authkeyAsBinary);
+               free(dataAsBinary);
+               free(digestAsBinary);
+               free(mac);
+               httpError(session_fd, 403, "Forbidden (2)");
+               return;
+       }
+       free(digestAsBinary);
+       free(mac);
+
+       uint8_t* ret = foleo_chacha20(authkeyAsBinary, sessionAsBinary, 0, dataSize);
+       for (int i = 0; i < dataSize; i++) dataAsBinary[i] ^= ret[i];
+       free(ret);
+
+       uint32_t receivedL = dataSize + 1;
+       uint8_t* received = malloc(receivedL);
+       memcpy(received, dataAsBinary, receivedL - 1);
+       received[receivedL - 1] = 0;
+       free(dataAsBinary);
+
+       uint32_t timeout = 10;
+       char* stimeout = getDatabaseInfo(1, user, "timeout", service);
+       timeout = atoi(stimeout);
+       free(stimeout);
+
+       uint32_t resultL;
+       uint8_t* result = runService(service, received, &resultL, timeout);
+       free(received);
+       if (result != NULL)
+       {
+               uint8_t carry = 1;
+               for (int8_t i = 11; i >= 0; i--)
+               {
+                       if (sessionAsBinary[i] == 0xFF)
+                       {
+                               if (carry == 1) sessionAsBinary[i] = 0;
+                               else carry = 0;
+                       }
+                       else
+                       {
+                               if (carry == 1) sessionAsBinary[i]++;
+                               carry = 0;
+                       }
+               }
+               uint8_t* ret_sess = foleo_encode_hex(sessionAsBinary, 12);
+               uint8_t* ret_data = foleo_chacha20(authkeyAsBinary, sessionAsBinary, 0, resultL);
+               for (uint32_t i = 0; i < resultL; i++) ret_data[i] ^= result[i];
+               uint8_t* ret_data_b64 = foleo_encode_base64(ret_data, resultL);
+               uint8_t* ret_mac = foleo_hmac(FOLEO_SHA256, authkeyAsBinary, 256 / 8, ret_data, resultL);
+               uint8_t* ret_mac_hex = foleo_encode_hex(ret_mac, 256 / 8);
+
+               uint32_t finalL = snprintf(NULL, 0, "user=%s&sess=%s&dgst=%s&data=%s\n", user, ret_sess, ret_mac_hex, ret_data_b64);
+               uint8_t final[finalL + 1];
+               sprintf(final, "user=%s&sess=%s&dgst=%s&data=%s\n", user, ret_sess, ret_mac_hex, ret_data_b64);
+               httpStringOut(session_fd, final);
+
+               free(ret_sess);
+               free(ret_data);
+               free(ret_data_b64);
+               free(ret_mac);
+               free(ret_mac_hex);
+       }
+       free(result);
+       free(authkeyAsBinary);
+       free(sessionAsBinary);
+
+       return;
+}
+
+void *handleRequest(void *vargp)
+{
+       int session_fd;
+       memcpy(&session_fd, vargp, sizeof(int));
+       free(vargp);
+
+       char *buff;
+       char *info;
+       char *user;
+       char *data;
+       char *sess;
+       char *dgst;
+       int buff_len;
+       httpRequest(session_fd, &buff, &buff_len);
+       printf("-----Request-----\n");
+       for (int32_t i = 0; i < buff_len; i++)
+       {
+               putchar(buff[i]);
+       }
+       printf("\n-----------------\n");
+       if (buff != NULL)
+       {
+               info = httpParse("Method", buff, buff_len);
+               if (info == NULL)
+               {
+                       httpError(session_fd, 400, "Bad Request (1)");
+               }
+               else if ( strcmp(info, "POST") == 0 )
+               {
+                       free(info);
+                       info = httpParse("Path", buff, buff_len);
+                       uint8_t* isValidService = getDatabaseInfo(2, info + 1, "1", "");
+                       if (isValidService == NULL)
+                       {
+                               httpError(session_fd, 400, "Bad Request (2)");
+                       }
+                       else
+                       {
+                               uint8_t service[strlen(info + 1) + 1];
+                               strcpy(service, info + 1);
+                               free(isValidService);
+                               free(info);
+                               info = httpParse("Content-Length", buff, buff_len);
+                               if (info == NULL)
+                               {
+                                       httpError(session_fd, 400, "Bad Request (3)");
+                               }
+                               else
+                               {
+                                       uint32_t contentLength = atoi(info);
+                                       if (contentLength == 0)
+                                       {
+                                               httpError(session_fd, 400, "Bad Request (4)");
+                                       }
+                                       else
+                                       {
+                                               free(info);
+                                               info = httpParse("Content", buff, buff_len);
+                                               if (info == NULL)
+                                               {
+                                                       httpError(session_fd, 400, "Bad Request (5)");
+                                               }
+                                               else
+                                               {
+                                                       user = httpParsePost("user", info, contentLength);
+                                                       data = httpParsePost("data", info, contentLength);
+                                                       sess = httpParsePost("sess", info, contentLength);
+                                                       dgst = httpParsePost("dgst", info, contentLength);
+                                                       if (user != NULL && data != NULL && dgst != NULL)
+                                                       {
+                                                               processUserAndData(session_fd, user, data, sess, dgst, service);
+                                                       }
+                                                       else
+                                                       {
+                                                               httpError(session_fd, 400, "Bad Request (6)");
+                                                       }
+                                                       if (user != NULL) free(user);
+                                                       if (data != NULL) free(data);
+                                                       if (sess != NULL) free(sess);
+                                                       if (dgst != NULL) free(dgst);
+                                               }
+                                       }
+                               }
+                       }
+               }
+               else if ( strcmp(info, "GET") == 0 )
+               {
+                       free(info);
+                       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;
+                               httpStringOut(session_fd, frontend);
+                               free(frontend);
+                       }
+                       else if ( strcmp(info, "/apk") == 0 )
+                       {
+                               httpFileOut(session_fd, "./EstoulsAPI.apk", 0);
+                       }
+                       else if ( strcmp(info, "/favicon.ico") == 0 )
+                       {
+                               httpFileOut(session_fd, "./favicon.ico", 1);
+                       }
+                       else
+                       {
+                               httpError(session_fd, 400, "Bad Request (7)");
+                       }
+               }
+               else
+               {
+                       httpError(session_fd, 400, "Bad Request (8)");
+               }
+               free(info);
+               free(buff);
+       }
+
+       if (session_fd == -1 && errno != EINTR)
+       {
+               printf("Failed to accept connection %i.\n", errno);
+       }
+
+       //End session
+       httpEnd(session_fd);
+}
+
+void get_hostname()
+{
+       for (uint8_t i = 0; i <= 1; i++)
+       {
+               FILE* proc;
+               if (i) proc = popen("hostname -I | awk '{print $1}'", "r");
+               else proc = popen("hostname", "r");
+               char* hostname = malloc(1);
+               hostname[0] = 0;
+               size_t hostname_len = 1;
+               if (proc == NULL)
+               {
+                       fprintf(stderr, "Unknown error in get_hostname().\n");
+                       exit(1);
+               }
+               int c;
+               while ( (c = fgetc(proc)) != EOF )
+               {
+                       if (c == '\n' || c == '\r') continue;
+                       hostname = realloc(hostname, ++hostname_len);
+                       hostname[hostname_len - 2] = c;
+                       hostname[hostname_len - 1] = 0;
+               }
+               if (strlen(hostname) >= 1023)
+               {
+                       hostname[1023] = 0;
+               }
+               if (i) strcpy(HOSTNAME_I, hostname);
+               else strcpy(HOSTNAME, hostname);
+               free(hostname);
+       }
+}
+
+void main()
+{
+       get_hostname();
+       
+    MYSQL *conn;
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+
+    // Initialize the connection handler
+    conn = mysql_init(NULL);
+
+    // Connect to the database
+    if (!mysql_real_connect(conn, "localhost", HOSTNAME, "PAAAAAAAAAAAA", "APIService", 0, NULL, 0)) {
+        fprintf(stderr, "%s\n", mysql_error(conn));
+        return;
+    }
+
+    if (mysql_query(conn, "select 1 from information_schema.tables where table_schema='APIService' and table_name='AuthInfo' limit 1;"))
+       {
+        fprintf(stderr, "%s\n", mysql_error(conn));
+        return;
+       }
+       res = mysql_use_result(conn);
+       uint8_t createTable = ((row = mysql_fetch_row(res)) == NULL);
+       if (createTable)
+       {
+               if (mysql_query(conn, "create table AuthInfo ( id int auto_increment primary key, service text, username text, authkey text, timeout int );"))
+               {
+                       fprintf(stderr, "%s\n", mysql_error(conn));
+                       return;
+               }
+       }
+       mysql_free_result(res);
+
+    if (mysql_query(conn, "select 1 from information_schema.tables where table_schema='APIService' and table_name='ServiceInfo' limit 1;"))
+       {
+        fprintf(stderr, "%s\n", mysql_error(conn));
+        return;
+       }
+       res = mysql_use_result(conn);
+       createTable = ((row = mysql_fetch_row(res)) == NULL);
+       if (createTable)
+       {
+               if (mysql_query(conn, "create table ServiceInfo ( id int auto_increment primary key, service text, path text, parameters text );"))
+               {
+                       fprintf(stderr, "%s\n", mysql_error(conn));
+                       return;
+               }
+       }
+
+    mysql_free_result(res);
+    mysql_close(conn);
+
+       //Open the socket
+       int socket_fd = httpOpenSocket(HOSTNAME_I, "8080");
+
+       //Begin processing packets
+       for (;;)
+       //for (int i = 0; i < 3; i++)
+       {
+               int session_fd;
+               httpBegin(socket_fd, &session_fd);
+               char* arg = malloc(sizeof(int));
+               memcpy(arg, &session_fd, sizeof(int));
+               pthread_t thread_id;
+               pthread_create(&thread_id, NULL, handleRequest, arg);
+               //pthread_join(thread_id, NULL);
+       }
+
+       mysql_library_end();
+}