├── .gitattributes ├── README.md ├── UNLICENSE ├── config.nims ├── src ├── wavecore.nim └── wavecorepkg │ ├── client.nim │ ├── client │ ├── emscripten.nim │ ├── native.nim │ └── wavecore_emscripten.c │ ├── common.nim │ ├── db.nim │ ├── db │ ├── db_common.nim │ ├── db_sqlite.nim │ ├── entities.nim │ ├── sqlite3.c │ ├── sqlite3.h │ ├── sqlite3.nim │ ├── sqlite3ext.h │ └── vfs.nim │ ├── ed25519.nim │ ├── ed25519 │ ├── add_scalar.c │ ├── ed25519.h │ ├── fe.c │ ├── fe.h │ ├── fixedint.h │ ├── ge.c │ ├── ge.h │ ├── key_exchange.c │ ├── keypair.c │ ├── license.txt │ ├── precomp_data.h │ ├── sc.c │ ├── sc.h │ ├── seed.c │ ├── sha512.c │ ├── sha512.h │ ├── sign.c │ └── verify.c │ ├── paths.nim │ ├── server.nim │ ├── testrun.nim │ └── wavescript.nim ├── tests ├── config.nims ├── hello.ansiwave ├── hulk.ansiwave ├── privkey ├── test1.nim └── variables.ansiwave └── wavecore.nimble /.gitattributes: -------------------------------------------------------------------------------- 1 | **/*.c linguist-vendored 2 | **/*.h linguist-vendored 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is the server implementation of ANSIWAVE BBS, along with the client networking / database code so they can be tested together. To build an executable ready for running on a server, do: 2 | 3 | ``` 4 | nimble build -d:release 5 | ``` 6 | 7 | To set up your first board, you'll need to create the sysop key for it. Using the ansiwave terminal client, run `ansiwave --gen-login-key=~/.ansiwave/login-key.png` on your local machine (you do *not* need to do this on the server you're running wavecore). It will create the filename specified, and will also print out your public key. 8 | 9 | On the server, you now should create a directory called `bbs`, and inside of it, a directory called `boards`. Finally, create a diretory named after your public key inside of that. For example, if your public key is `Q8BTY324cY7nl5kce6ctEfk8IRIrtsM8NfKL29B-3UE`, you could do: 10 | 11 | ``` 12 | mkdir -p ~/bbs/boards/Q8BTY324cY7nl5kce6ctEfk8IRIrtsM8NfKL29B-3UE 13 | ``` 14 | 15 | Now run the wavecore server in the same working directory as `bbs` with no arguments. If it can't find the `bbs` directory, it will quit with an error. This directory will contain all the public files that must be served to users. 16 | 17 | Never expose the wavecore server directly to external connections. Instead, put it behind a proxy server like nginx. Make your proxy server serve the `bbs` directory as static files, and additionally forward the `/ansiwave` endpoint to the wavecore server. For example, if you're using nginx, edit the config at `/etc/nginx/sites-enabled/default` and somewhere in the `server` block add: 18 | 19 | ``` 20 | server { 21 | ... 22 | 23 | location /ansiwave { 24 | proxy_pass http://127.0.0.1:3000/ansiwave; 25 | } 26 | 27 | root /path/to/bbs; 28 | 29 | ... 30 | } 31 | ``` 32 | 33 | Assuming nginx is serving on port 80, you should then be able to connect to it with the ansiwave terminal client like this: 34 | 35 | ``` 36 | ansiwave http://localhost:80/#board:Q8BTY324cY7nl5kce6ctEfk8IRIrtsM8NfKL29B-3UE 37 | ``` 38 | 39 | Replace `localhost` with your public IP or hostname to connect to it remotely. 40 | 41 | Assuming your ansiwave terminal client is using the sysop key you generated (i.e., it is located at `~/.ansiwave/login-key.png`), you should see a `create banner` and `create new subboard` button. If these don't appear, the board in the URL you gave to ansiwave doesn't match your login key. Once you've made the subboards, other users can begin making posts there. 42 | 43 | Unless you run with the `--disable-limbo` flag, all new users besides the sysop will start off in "limbo". This is a separate database, so incoming spam will not touch the main database. You can find these users by searching for "modlimbo" and selecting "tags". These users won't appear in the subboards yet. You can bring them out of limbo by going to their user page and hitting ctrl x (you must wait for the page to finish loading completely). A small edit field will appear, where you can delete the "modlimbo" tag and hit enter to complete. 44 | 45 | You can tag users with "modhide" to hide their posts, or "modban" to prevent them from posting anymore. To give a user mod power, you can tag them with "modleader" or "moderator" (the former has the power to create other moderators, while the latter can only ban/hide people but cannot change anyone's moderator status). Lastly, you can tag a user with "modpurge" to completely delete their posts (only a modleader can do this). 46 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /config.nims: -------------------------------------------------------------------------------- 1 | # these defaults are only relevant when building the clients, 2 | # not when building the server, but we need to set them anyway 3 | switch("define", "defaultGetAddress:") 4 | switch("define", "defaultPostAddress:") 5 | switch("define", "defaultBoard:Q8BTY324cY7nl5kce6ctEfk8IRIrtsM8NfKL29B-3UE") 6 | 7 | --threads:on 8 | --define:staticSqlite 9 | --gc:orc 10 | -------------------------------------------------------------------------------- /src/wavecore.nim: -------------------------------------------------------------------------------- 1 | from ./wavecorepkg/testrun import nil 2 | from ./wavecorepkg/server import nil 3 | from ./wavecorepkg/db/vfs import nil 4 | from ./wavecorepkg/paths import nil 5 | from os import `/` 6 | from parseopt import nil 7 | import tables 8 | 9 | const port = 3000 10 | 11 | when isMainModule: 12 | var 13 | p = parseopt.initOptParser() 14 | options: Table[string, string] 15 | while true: 16 | parseopt.next(p) 17 | case p.kind: 18 | of parseopt.cmdEnd: 19 | break 20 | of parseopt.cmdShortOption, parseopt.cmdLongOption: 21 | options[p.key] = p.val 22 | of parseopt.cmdArgument: 23 | continue 24 | 25 | if not os.dirExists(paths.staticFileDir): 26 | quit "Can't find directory: " & paths.staticFileDir 27 | 28 | vfs.register() 29 | var s = server.initServer("localhost", port, paths.staticFileDir, options) 30 | server.start(s) 31 | if "testrun" in options: 32 | testrun.main(port) 33 | when defined(release): 34 | while true: 35 | os.sleep(1000) 36 | if os.fileExists("stop"): 37 | server.stop(s) 38 | else: 39 | discard readLine(stdin) 40 | server.stop(s) 41 | -------------------------------------------------------------------------------- /src/wavecorepkg/client.nim: -------------------------------------------------------------------------------- 1 | from ./db/entities import nil 2 | from paths import nil 3 | 4 | when defined(emscripten): 5 | import client/emscripten 6 | else: 7 | import client/native 8 | 9 | type 10 | ClientException* = object of CatchableError 11 | 12 | export fetch, start, stop, get, Client, ClientKind, ChannelValue, Result, Request, Response, Header 13 | 14 | const 15 | Valid* = ResultKind.Valid 16 | Error* = ResultKind.Error 17 | 18 | proc initClient*(address: string, postAddress: string = address): Client = 19 | Client(kind: Online, address: address, postAddress: postAddress) 20 | 21 | proc request*(url: string, data: string, verb: string): string = 22 | let response: Response = fetch(Request(url: url, verb: verb, body: data)) 23 | if response.code != 200: 24 | raise newException(ClientException, "Error code " & $response.code & ": " & response.body) 25 | return response.body 26 | 27 | proc post*(client: Client, endpoint: string, data: string): string = 28 | let url = paths.initUrl(client.postAddress, endpoint) 29 | request(url, data, "post") 30 | 31 | proc query*(client: Client, endpoint: string): ChannelValue[Response] = 32 | let url = 33 | case client.kind: 34 | of Online: 35 | paths.initUrl(client.address, endpoint) 36 | of Offline: 37 | endpoint 38 | let request = Request(url: url, verb: "get", body: "") 39 | result = initChannelValue[Response]() 40 | sendFetch(client, request, result.chan) 41 | 42 | proc submit*(client: Client, endpoint: string, body: string): ChannelValue[Response] = 43 | let url = paths.initUrl(client.postAddress, endpoint) 44 | let request = Request(url: url, verb: "post", body: body) 45 | result = initChannelValue[Response]() 46 | sendFetch(client, request, result.chan) 47 | 48 | proc queryUser*(client: Client, filename: string, publicKey: string): ChannelValue[entities.User] = 49 | result = initChannelValue[entities.User]() 50 | sendUserQuery(client, filename, publicKey, result.chan) 51 | 52 | proc queryPost*(client: Client, filename: string, sig: string): ChannelValue[entities.Post] = 53 | result = initChannelValue[entities.Post]() 54 | sendPostQuery(client, filename, sig, result.chan) 55 | 56 | proc queryPostChildren*(client: Client, filename: string, sig: string, sortBy: entities.SortBy = entities.Score, offset: int = 0): ChannelValue[seq[entities.Post]] = 57 | result = initChannelValue[seq[entities.Post]]() 58 | sendPostChildrenQuery(client, filename, sig, sortBy, offset, result.chan) 59 | 60 | proc queryUserPosts*(client: Client, filename: string, publicKey: string, offset: int = 0): ChannelValue[seq[entities.Post]] = 61 | result = initChannelValue[seq[entities.Post]]() 62 | sendUserPostsQuery(client, filename, publicKey, offset, result.chan) 63 | 64 | proc queryUserReplies*(client: Client, filename: string, publicKey: string, offset: int = 0): ChannelValue[seq[entities.Post]] = 65 | result = initChannelValue[seq[entities.Post]]() 66 | sendUserRepliesQuery(client, filename, publicKey, offset, result.chan) 67 | 68 | proc search*(client: Client, filename: string, kind: entities.SearchKind, term: string, offset: int = 0): ChannelValue[seq[entities.Post]] = 69 | result = initChannelValue[seq[entities.Post]]() 70 | sendSearchQuery(client, filename, kind, term, offset, result.chan) 71 | 72 | -------------------------------------------------------------------------------- /src/wavecorepkg/client/emscripten.nim: -------------------------------------------------------------------------------- 1 | from flatty import nil 2 | from flatty/binny import nil 3 | from strutils import nil 4 | import json 5 | import tables 6 | from base64 import nil 7 | from times import nil 8 | 9 | from ../db import nil 10 | from ../db/entities import nil 11 | from ../paths import nil 12 | 13 | type 14 | Channel = object 15 | dataAvailable: bool 16 | data: string 17 | url: string 18 | ChannelPtr* = ptr Channel 19 | Request* = object 20 | url*: string 21 | headers*: seq[Header] 22 | verb*: string 23 | body*: string 24 | Response* = object 25 | body*: string 26 | headers*: seq[Header] 27 | code*: int 28 | Header* = object 29 | key*: string 30 | value*: string 31 | ResultKind* = enum 32 | Valid, Error, 33 | Result*[T] = object 34 | case kind*: ResultKind 35 | of Valid: 36 | valid*: T 37 | of Error: 38 | error*: string 39 | ActionKind* = enum 40 | Stop, Fetch, QueryUser, QueryPost, QueryPostChildren, QueryUserPosts, QueryUserReplies, SearchPosts, 41 | Action* = object 42 | case kind*: ActionKind 43 | of Stop: 44 | discard 45 | of Fetch: 46 | request*: Request 47 | of QueryUser: 48 | publicKey*: string 49 | of QueryPost: 50 | postSig*: string 51 | of QueryPostChildren: 52 | postParentSig*: string 53 | sortBy*: entities.SortBy 54 | of QueryUserPosts: 55 | userPostsPublicKey*: string 56 | of QueryUserReplies: 57 | userRepliesPublicKey*: string 58 | of SearchPosts: 59 | searchKind*: entities.SearchKind 60 | searchTerm*: string 61 | dbFilename*: string 62 | offset: int 63 | WorkerRequest = object 64 | action: Action 65 | channel: int64 66 | WorkerResponse = object 67 | data: string 68 | channel: int64 69 | ClientKind* = enum 70 | Online, Offline, 71 | Client* = ref object 72 | case kind*: ClientKind 73 | of Online: 74 | address*: string 75 | of Offline: 76 | discard 77 | postAddress*: string 78 | worker: cint 79 | ChannelValue*[T] = object 80 | started*: bool 81 | chan*: ChannelPtr 82 | value*: Result[T] 83 | ready*: bool 84 | readyTime*: float 85 | 86 | proc emscripten_create_worker(url: cstring): cint {.importc.} 87 | proc emscripten_destroy_worker(worker: cint) {.importc.} 88 | proc emscripten_call_worker(worker: cint, funcname: cstring, data: cstring, size: cint, callback: proc (data: pointer, size: cint, arg: pointer) {.cdecl.}, arg: pointer) {.importc.} 89 | proc emscripten_worker_respond(data: cstring, size: cint) {.importc.} 90 | proc emscripten_async_wget_data(url: cstring, arg: pointer, onload: pointer, onerror: pointer) {.importc.} 91 | proc wavecore_fetch(url: cstring, verb: cstring, headers: cstring, body: cstring): cstring {.importc.} 92 | proc free(p: pointer) {.importc.} 93 | 94 | {.compile: "wavecore_emscripten.c".} 95 | 96 | proc fetch*(request: Request): Response = 97 | let 98 | url = request.url 99 | reqHeaders = block: 100 | var o = json.newJObject() 101 | for header in request.headers: 102 | o.fields[header.key] = json.newJString(header.value) 103 | $o 104 | res = wavecore_fetch(url.cstring, request.verb, reqHeaders.cstring, request.body) 105 | json = json.parseJson($res) 106 | body = base64.decode(json["body"].str) 107 | code = json["code"].num.int 108 | resHeaders = block: 109 | var hs: seq[Header] 110 | for k, v in json["headers"].fields: 111 | if v.kind == JString: 112 | hs.add(Header(key: k, value: v.str)) 113 | hs 114 | result = Response(body: body, code: code, headers: resHeaders) 115 | free(res) 116 | 117 | proc initChannelValue*[T](): ChannelValue[T] = 118 | result = ChannelValue[T]( 119 | started: true, 120 | chan: cast[ChannelPtr]( 121 | allocShared0(sizeof(Channel)) 122 | ) 123 | ) 124 | 125 | proc get*[T](cv: var ChannelValue[T]) = 126 | if cv.started and not cv.ready: 127 | if cv.chan[].dataAvailable: 128 | cv.value = flatty.fromFlatty(cv.chan[].data, Result[T]) 129 | cv.ready = true 130 | cv.readyTime = times.epochTime() 131 | deallocShared(cv.chan) 132 | 133 | proc callback(data: pointer, size: cint, arg: pointer) {.cdecl.} = 134 | var s = newString(size) 135 | copyMem(s[0].addr, data, size) 136 | let 137 | res = flatty.fromFlatty(s, WorkerResponse) 138 | chan = cast[ptr Channel](res.channel) 139 | chan[].data = res.data 140 | chan[].dataAvailable = true 141 | 142 | proc sendAction*(client: Client, action: Action, chan: ptr Channel) = 143 | let data = flatty.toFlatty(WorkerRequest(action: action, channel: cast[int64](chan))) 144 | emscripten_call_worker(client.worker, "recvAction", data, data.len.cint, callback, nil) 145 | 146 | proc sendFetch*(client: Client, request: Request, chan: ChannelPtr) = 147 | if request.verb != "get" or request.headers.len > 0: 148 | sendAction(client, Action(kind: Fetch, request: request), chan) 149 | else: 150 | chan[].url = request.url 151 | 152 | proc onload(arg: pointer, data: pointer, size: cint) {.cdecl.} = 153 | var s = newString(size) 154 | copyMem(s[0].addr, data, size) 155 | let chan = cast[ptr Channel](arg) 156 | chan[].data = flatty.toFlatty(Result[Response](kind: Valid, valid: Response(code: 200, body: s))) 157 | chan[].dataAvailable = true 158 | 159 | proc onerror(arg: pointer) {.cdecl.} = 160 | let chan = cast[ptr Channel](arg) 161 | chan[].data = flatty.toFlatty(Result[Response](kind: Error, error: "")) 162 | chan[].dataAvailable = true 163 | 164 | emscripten_async_wget_data(request.url, chan, onload, onerror) 165 | 166 | proc sendUserQuery*(client: Client, filename: string, publicKey: string, chan: ChannelPtr) = 167 | sendAction(client, Action(kind: QueryUser, dbFilename: filename, publicKey: publicKey), chan) 168 | 169 | proc sendPostQuery*(client: Client, filename: string, sig: string, chan: ChannelPtr) = 170 | sendAction(client, Action(kind: QueryPost, dbFilename: filename, postSig: sig), chan) 171 | 172 | proc sendPostChildrenQuery*(client: Client, filename: string, sig: string, sortBy: entities.SortBy, offset: int, chan: ChannelPtr) = 173 | sendAction(client, Action(kind: QueryPostChildren, dbFilename: filename, sortBy: sortBy, offset: offset, postParentSig: sig), chan) 174 | 175 | proc sendUserPostsQuery*(client: Client, filename: string, publicKey: string, offset: int, chan: ChannelPtr) = 176 | sendAction(client, Action(kind: QueryUserPosts, dbFilename: filename, offset: offset, userPostsPublicKey: publicKey), chan) 177 | 178 | proc sendUserRepliesQuery*(client: Client, filename: string, publicKey: string, offset: int, chan: ChannelPtr) = 179 | sendAction(client, Action(kind: QueryUserReplies, dbFilename: filename, offset: offset, userRepliesPublicKey: publicKey), chan) 180 | 181 | proc sendSearchQuery*(client: Client, filename: string, kind: entities.SearchKind, term: string, offset: int, chan: ChannelPtr) = 182 | sendAction(client, Action(kind: SearchPosts, dbFilename: filename, searchKind: kind, searchTerm: term, offset: offset), chan) 183 | 184 | proc recvAction(data: pointer, size: cint) {.exportc.} = 185 | var input = newString(size) 186 | copyMem(input[0].addr, data, size) 187 | let 188 | workerRequest = flatty.fromFlatty(input, WorkerRequest) 189 | action = workerRequest.action 190 | if action.dbFilename != "": 191 | paths.readUrl = paths.initUrl(paths.address, action.dbFilename) 192 | let res = 193 | case action.kind: 194 | of Stop: 195 | return 196 | of Fetch: 197 | var req = fetch(action.request) 198 | try: 199 | if req.code == 200: 200 | flatty.toFlatty(Result[Response](kind: Valid, valid: req)) 201 | else: 202 | flatty.toFlatty(Result[Response](kind: Error, error: req.body)) 203 | except Exception as ex: 204 | flatty.toFlatty(Result[Response](kind: Error, error: ex.msg)) 205 | of QueryUser: 206 | try: 207 | var s: string 208 | db.withOpen(conn, action.dbFilename, db.Http): 209 | let user = 210 | if entities.existsUser(conn, action.publicKey): 211 | entities.selectUser(conn, action.publicKey) 212 | else: 213 | entities.User() 214 | s = flatty.toFlatty(Result[entities.User](kind: Valid, valid: user)) 215 | s 216 | except Exception as ex: 217 | flatty.toFlatty(Result[seq[entities.Post]](kind: Error, error: ex.msg)) 218 | of QueryPost: 219 | try: 220 | var s: string 221 | db.withOpen(conn, action.dbFilename, db.Http): 222 | let post = entities.selectPost(conn, action.postSig) 223 | s = flatty.toFlatty(Result[entities.Post](kind: Valid, valid: post)) 224 | s 225 | except Exception as ex: 226 | flatty.toFlatty(Result[seq[entities.Post]](kind: Error, error: ex.msg)) 227 | of QueryPostChildren: 228 | try: 229 | var s: string 230 | db.withOpen(conn, action.dbFilename, db.Http): 231 | let posts = entities.selectPostChildren(conn, action.postParentSig, action.sortBy, action.offset) 232 | s = flatty.toFlatty(Result[seq[entities.Post]](kind: Valid, valid: posts)) 233 | s 234 | except Exception as ex: 235 | flatty.toFlatty(Result[seq[entities.Post]](kind: Error, error: ex.msg)) 236 | of QueryUserPosts: 237 | try: 238 | var s: string 239 | db.withOpen(conn, action.dbFilename, db.Http): 240 | let posts = entities.selectUserPosts(conn, action.userPostsPublicKey, action.offset) 241 | s = flatty.toFlatty(Result[seq[entities.Post]](kind: Valid, valid: posts)) 242 | s 243 | except Exception as ex: 244 | flatty.toFlatty(Result[seq[entities.Post]](kind: Error, error: ex.msg)) 245 | of QueryUserReplies: 246 | try: 247 | var s: string 248 | db.withOpen(conn, action.dbFilename, db.Http): 249 | let posts = entities.selectUserReplies(conn, action.userRepliesPublicKey, action.offset) 250 | s = flatty.toFlatty(Result[seq[entities.Post]](kind: Valid, valid: posts)) 251 | s 252 | except Exception as ex: 253 | flatty.toFlatty(Result[seq[entities.Post]](kind: Error, error: ex.msg)) 254 | of SearchPosts: 255 | try: 256 | var s: string 257 | db.withOpen(conn, action.dbFilename, db.Http): 258 | let posts = entities.search(conn, action.searchKind, action.searchTerm, action.offset) 259 | s = flatty.toFlatty(Result[seq[entities.Post]](kind: Valid, valid: posts)) 260 | s 261 | except Exception as ex: 262 | flatty.toFlatty(Result[seq[entities.Post]](kind: Error, error: ex.msg)) 263 | if res != "": 264 | let data = flatty.toFlatty(WorkerResponse(data: res, channel: workerRequest.channel)) 265 | emscripten_worker_respond(data, data.len.cint) 266 | 267 | proc start*(client: var Client) = 268 | client.worker = emscripten_create_worker("worker.js") 269 | 270 | proc stop*(client: var Client) = 271 | emscripten_destroy_worker(client.worker) 272 | 273 | -------------------------------------------------------------------------------- /src/wavecorepkg/client/native.nim: -------------------------------------------------------------------------------- 1 | import puppy 2 | from webby import `$` 3 | from strutils import nil 4 | from times import nil 5 | from os import `/` 6 | import threading/channels 7 | 8 | from ../db import nil 9 | from ../db/entities import nil 10 | from ../paths import nil 11 | 12 | type 13 | ResultKind* = enum 14 | Valid, Error, 15 | Result*[T] = object 16 | case kind*: ResultKind 17 | of Valid: 18 | valid*: T 19 | of Error: 20 | error*: string 21 | Request* = object 22 | url*: string 23 | headers*: webby.HttpHeaders 24 | verb*: string 25 | body*: string 26 | Response* = object 27 | headers*: webby.HttpHeaders 28 | code*: int 29 | body*: string 30 | ActionKind* = enum 31 | Stop, Fetch, QueryUser, QueryPost, QueryPostChildren, QueryUserPosts, QueryUserReplies, SearchPosts, 32 | Action* = object 33 | case kind*: ActionKind 34 | of Stop: 35 | discard 36 | of Fetch: 37 | request*: Request 38 | response*: Chan[Result[Response]] 39 | of QueryUser: 40 | publicKey*: string 41 | userResponse*: Chan[Result[entities.User]] 42 | of QueryPost: 43 | postSig*: string 44 | postResponse*: Chan[Result[entities.Post]] 45 | of QueryPostChildren: 46 | postParentSig*: string 47 | sortBy*: entities.SortBy 48 | postChildrenResponse*: Chan[Result[seq[entities.Post]]] 49 | of QueryUserPosts: 50 | userPostsPublicKey*: string 51 | userPostsResponse*: Chan[Result[seq[entities.Post]]] 52 | of QueryUserReplies: 53 | userRepliesPublicKey*: string 54 | userRepliesResponse*: Chan[Result[seq[entities.Post]]] 55 | of SearchPosts: 56 | searchKind*: entities.SearchKind 57 | searchTerm*: string 58 | searchResponse*: Chan[Result[seq[entities.Post]]] 59 | dbFilename*: string 60 | offset: int 61 | ClientKind* = enum 62 | Online, Offline, 63 | Client* = ref object 64 | case kind*: ClientKind 65 | of Online: 66 | address*: string 67 | of Offline: 68 | path*: string 69 | postAddress*: string 70 | requestThread*: Thread[Client] 71 | action*: Chan[Action] 72 | ChannelValue*[T] = object 73 | started*: bool 74 | chan*: Chan[Result[T]] 75 | value*: Result[T] 76 | ready*: bool 77 | readyTime*: float 78 | 79 | const channelSize = 100 80 | 81 | export puppy.Header 82 | 83 | proc initChannelValue*[T](): ChannelValue[T] = 84 | result = ChannelValue[T]( 85 | started: true, 86 | chan: newChan[Result[T]](channelSize), 87 | ) 88 | 89 | proc get*[T](cv: var ChannelValue[T], blocking: static[bool] = false) = 90 | if cv.started and not cv.ready: 91 | when blocking: 92 | cv.chan.recv(cv.value) 93 | cv.ready = true 94 | cv.readyTime = times.epochTime() 95 | else: 96 | if cv.chan.tryRecv(cv.value): 97 | cv.ready = true 98 | cv.readyTime = times.epochTime() 99 | 100 | proc sendAction*(client: Client, action: Action) = 101 | client.action.send(action) 102 | 103 | proc sendFetch*(client: Client, request: Request, chan: Chan) = 104 | sendAction(client, Action(kind: Fetch, request: request, response: chan)) 105 | 106 | proc sendUserQuery*(client: Client, filename: string, publicKey: string, chan: Chan) = 107 | sendAction(client, Action(kind: QueryUser, dbFilename: filename, publicKey: publicKey, userResponse: chan)) 108 | 109 | proc sendPostQuery*(client: Client, filename: string, sig: string, chan: Chan) = 110 | sendAction(client, Action(kind: QueryPost, dbFilename: filename, postSig: sig, postResponse: chan)) 111 | 112 | proc sendPostChildrenQuery*(client: Client, filename: string, sig: string, sortBy: entities.SortBy, offset: int, chan: Chan) = 113 | sendAction(client, Action(kind: QueryPostChildren, dbFilename: filename, postParentSig: sig, sortBy: sortBy, offset: offset, postChildrenResponse: chan)) 114 | 115 | proc sendUserPostsQuery*(client: Client, filename: string, publicKey: string, offset: int, chan: Chan) = 116 | sendAction(client, Action(kind: QueryUserPosts, dbFilename: filename, userPostsPublicKey: publicKey, offset: offset, userPostsResponse: chan)) 117 | 118 | proc sendUserRepliesQuery*(client: Client, filename: string, publicKey: string, offset: int, chan: Chan) = 119 | sendAction(client, Action(kind: QueryUserReplies, dbFilename: filename, userRepliesPublicKey: publicKey, offset: offset, userRepliesResponse: chan)) 120 | 121 | proc sendSearchQuery*(client: Client, filename: string, kind: entities.SearchKind, term: string, offset: int, chan: Chan) = 122 | sendAction(client, Action(kind: SearchPosts, dbFilename: filename, searchKind: kind, searchTerm: term, offset: offset, searchResponse: chan)) 123 | 124 | proc trimPath(path: string): string = 125 | let parts = strutils.split(path, '/') 126 | if parts.len < 4: 127 | raise newException(Exception, "Invalid path") 128 | strutils.join(parts[3 ..< parts.len], "/") 129 | 130 | proc toPuppy(request: Request): puppy.Request = 131 | new result 132 | result.url = webby.parseUrl(request.url) 133 | result.headers = request.headers 134 | result.verb = request.verb 135 | result.body = request.body 136 | 137 | proc fromPuppy(response: puppy.Response): Response = 138 | result.headers = response.headers 139 | result.code = response.code 140 | result.body = response.body 141 | 142 | proc fetch*(request: Request): Response = 143 | puppy.fetch(request.toPuppy).fromPuppy 144 | 145 | proc recvAction(client: Client) {.thread.} = 146 | while true: 147 | var action: Action 148 | client.action.recv(action) 149 | if client.kind == Online and action.dbFilename != "": 150 | {.cast(gcsafe).}: 151 | paths.readUrl = paths.initUrl(paths.address, action.dbFilename) 152 | case action.kind: 153 | of Stop: 154 | break 155 | of Fetch: 156 | try: 157 | let kind = 158 | # non-get requests always need to be online 159 | if action.request.verb == "get": 160 | client.kind 161 | else: 162 | Online 163 | let request = action.request.toPuppy 164 | case kind: 165 | of Online: 166 | {.cast(gcsafe).}: 167 | var res = fetch(request) 168 | if res.code == 200: 169 | action.response.send(Result[Response](kind: Valid, valid: res.fromPuppy)) 170 | else: 171 | action.response.send(Result[Response](kind: Error, error: res.body)) 172 | of Offline: 173 | let path = client.path / trimPath($request.url) 174 | var res = Response(code: 200, body: readFile(path)) 175 | action.response.send(Result[Response](kind: Valid, valid: res)) 176 | except Exception as ex: 177 | action.response.send(Result[Response](kind: Error, error: ex.msg)) 178 | of QueryUser: 179 | try: 180 | let (path, mode) = 181 | case client.kind: 182 | of Online: 183 | (action.dbFilename, db.Http) 184 | of Offline: 185 | (client.path / trimPath(action.dbFilename), db.Read) 186 | db.withOpen(conn, path, mode): 187 | if entities.existsUser(conn, action.publicKey): 188 | action.userResponse.send(Result[entities.User](kind: Valid, valid: entities.selectUser(conn, action.publicKey))) 189 | else: 190 | action.userResponse.send(Result[entities.User](kind: Valid, valid: entities.User())) 191 | except Exception as ex: 192 | action.userResponse.send(Result[entities.User](kind: Error, error: ex.msg)) 193 | of QueryPost: 194 | try: 195 | let (path, mode) = 196 | case client.kind: 197 | of Online: 198 | (action.dbFilename, db.Http) 199 | of Offline: 200 | (client.path / trimPath(action.dbFilename), db.Read) 201 | db.withOpen(conn, path, mode): 202 | action.postResponse.send(Result[entities.Post](kind: Valid, valid: entities.selectPost(conn, action.postSig))) 203 | except Exception as ex: 204 | action.postResponse.send(Result[entities.Post](kind: Error, error: ex.msg)) 205 | of QueryPostChildren: 206 | try: 207 | let (path, mode) = 208 | case client.kind: 209 | of Online: 210 | (action.dbFilename, db.Http) 211 | of Offline: 212 | (client.path / trimPath(action.dbFilename), db.Read) 213 | db.withOpen(conn, path, mode): 214 | action.postChildrenResponse.send(Result[seq[entities.Post]](kind: Valid, valid: entities.selectPostChildren(conn, action.postParentSig, action.sortBy, action.offset))) 215 | except Exception as ex: 216 | action.postChildrenResponse.send(Result[seq[entities.Post]](kind: Error, error: ex.msg)) 217 | of QueryUserPosts: 218 | try: 219 | let (path, mode) = 220 | case client.kind: 221 | of Online: 222 | (action.dbFilename, db.Http) 223 | of Offline: 224 | (client.path / trimPath(action.dbFilename), db.Read) 225 | db.withOpen(conn, path, mode): 226 | action.userPostsResponse.send(Result[seq[entities.Post]](kind: Valid, valid: entities.selectUserPosts(conn, action.userPostsPublicKey, action.offset))) 227 | except Exception as ex: 228 | action.userPostsResponse.send(Result[seq[entities.Post]](kind: Error, error: ex.msg)) 229 | of QueryUserReplies: 230 | try: 231 | let (path, mode) = 232 | case client.kind: 233 | of Online: 234 | (action.dbFilename, db.Http) 235 | of Offline: 236 | (client.path / trimPath(action.dbFilename), db.Read) 237 | db.withOpen(conn, path, mode): 238 | action.userRepliesResponse.send(Result[seq[entities.Post]](kind: Valid, valid: entities.selectUserReplies(conn, action.userRepliesPublicKey, action.offset))) 239 | except Exception as ex: 240 | action.userRepliesResponse.send(Result[seq[entities.Post]](kind: Error, error: ex.msg)) 241 | of SearchPosts: 242 | try: 243 | let (path, mode) = 244 | case client.kind: 245 | of Online: 246 | (action.dbFilename, db.Http) 247 | of Offline: 248 | (client.path / trimPath(action.dbFilename), db.Read) 249 | db.withOpen(conn, path, mode): 250 | action.searchResponse.send(Result[seq[entities.Post]](kind: Valid, valid: entities.search(conn, action.searchKind, action.searchTerm, action.offset))) 251 | except Exception as ex: 252 | action.searchResponse.send(Result[seq[entities.Post]](kind: Error, error: ex.msg)) 253 | 254 | proc initChan(client: var Client) = 255 | client.action = newChan[Action](channelSize) 256 | 257 | proc initThreads(client: var Client) = 258 | createThread(client.requestThread, recvAction, client) 259 | 260 | proc deinitThreads(client: var Client) = 261 | client.action.send(Action(kind: Stop)) 262 | client.requestThread.joinThread() 263 | 264 | proc start*(client: var Client) = 265 | initChan(client) 266 | initThreads(client) 267 | 268 | proc stop*(client: var Client) = 269 | deinitThreads(client) 270 | 271 | -------------------------------------------------------------------------------- /src/wavecorepkg/client/wavecore_emscripten.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | EM_JS(char*, wavecore_fetch, (const char* url, const char* verb, const char* headers, const char* body), { 4 | var request = new XMLHttpRequest(); 5 | request.open(UTF8ToString(verb), UTF8ToString(url), false); // `false` makes the request synchronous 6 | 7 | var headerMap = JSON.parse(UTF8ToString(headers)); 8 | for (key in headerMap) { 9 | request.setRequestHeader(key, headerMap[key]); 10 | } 11 | 12 | request.responseType = "arraybuffer"; 13 | request.send(UTF8ToString(body)); 14 | 15 | // convert response to a binary string 16 | var binary = ''; 17 | var bytes = new Uint8Array(request.response); 18 | var len = bytes.byteLength; 19 | for (var i = 0; i < len; i++) { 20 | binary += String.fromCharCode(bytes[i]); 21 | } 22 | 23 | var response = { 24 | "body": btoa(binary), 25 | "code": request.status, 26 | "headers": { 27 | "Content-Length": request.getResponseHeader("Content-Length"), 28 | "Content-Range": request.getResponseHeader("Content-Range"), 29 | "Content-Type": request.getResponseHeader("Content-Type") 30 | } 31 | }; 32 | 33 | var json = JSON.stringify(response); 34 | var lengthBytes = lengthBytesUTF8(json)+1; 35 | var stringOnWasmHeap = _malloc(lengthBytes); 36 | stringToUTF8(json, stringOnWasmHeap, lengthBytes); 37 | return stringOnWasmHeap; 38 | }); 39 | -------------------------------------------------------------------------------- /src/wavecorepkg/common.nim: -------------------------------------------------------------------------------- 1 | from ./wavescript import nil 2 | from ./paths import nil 3 | from ./ed25519 import nil 4 | from strutils import nil 5 | import tables, sets 6 | from times import nil 7 | import unicode 8 | from ansiutils/codes import nil 9 | 10 | proc parseTags*(tags: string): HashSet[string] = 11 | result = strutils.split(tags, ' ').toHashSet 12 | result.excl("") 13 | 14 | type 15 | HeaderKind* = enum 16 | New, Edit, Tags, ExtraTags, 17 | 18 | proc headers*(pubKey: string, target: string, kind: HeaderKind, board: string): string = 19 | strutils.join( 20 | [ 21 | "/key " & pubKey, 22 | "/algo ed25519", 23 | "/target " & target, 24 | "/type " & ( 25 | case kind: 26 | of New: "new" 27 | of Edit: "edit" 28 | of Tags: "tags" 29 | of ExtraTags: "extra-tags" 30 | ), 31 | "/board " & board, 32 | ], 33 | "\n", 34 | ) 35 | 36 | proc sign*(keyPair: ed25519.KeyPair, headers: string, content: string): tuple[body: string, sig: string] = 37 | result.body = "/time " & $times.toUnix(times.getTime()) & "\n" 38 | result.body &= headers & "\n\n" & content 39 | result.sig = paths.encode(ed25519.sign(keyPair, result.body)) 40 | result.body = "/sig " & result.sig & "\n" & result.body 41 | 42 | proc signWithHeaders*(keyPair: ed25519.KeyPair, content: string, target: string, kind: HeaderKind, board: string): tuple[body: string, sig: string] = 43 | sign(keyPair, headers(paths.encode(keyPair.public), target, kind, board), content) 44 | 45 | proc splitAfterHeaders*(content: string): seq[string] = 46 | let idx = strutils.find(content, "\n\n") 47 | if idx == -1: # this should never happen 48 | @[""] 49 | else: 50 | strutils.splitLines(content[idx + 2 ..< content.len]) 51 | 52 | proc parseAnsiwave*(ansiwave: string): tuple[cmds: Table[string, string], headersAndContent: string, content: string] = 53 | let col = unicode.validateUtf8(ansiwave) 54 | if col != -1: 55 | raise newException(Exception, "Invalid UTF8 data") 56 | var ctx = wavescript.initContext() 57 | ctx.stringCommands = ["/sig", "/time", "/key", "/algo", "/target", "/type", "/board"].toHashSet 58 | let 59 | newline = strutils.find(ansiwave, "\n") 60 | doubleNewline = strutils.find(ansiwave, "\n\n") 61 | if newline == -1 or doubleNewline == -1 or newline == doubleNewline: 62 | raise newException(Exception, "Invalid ansiwave") 63 | let 64 | sigLine = ansiwave[0 ..< newline] 65 | headers = strutils.splitLines(ansiwave[newline + 1 ..< doubleNewline]) 66 | content = ansiwave[doubleNewLine + 2 ..< ansiwave.len] 67 | sigCmd = wavescript.parse(ctx, sigLine) 68 | if sigCmd.kind != wavescript.Valid or sigCmd.name != "/sig": 69 | raise newException(Exception, "Invalid first header: " & sigLine) 70 | result.cmds[sigCmd.name] = sigCmd.args[0].name 71 | for header in headers: 72 | let cmd = wavescript.parse(ctx, header) 73 | if cmd.kind == wavescript.Valid: 74 | result.cmds[cmd.name] = cmd.args[0].name 75 | for cmd in ctx.stringCommands: 76 | if not result.cmds.hasKey(cmd): 77 | raise newException(Exception, "Required header not found: " & cmd) 78 | result.headersAndContent = ansiwave[newline + 1 ..< ansiwave.len] 79 | result.content = content 80 | 81 | proc stripUnsearchableText*(content: string): string = 82 | let idx = strutils.find(content, "\n\n") 83 | if idx == -1: # this should never happen 84 | return "" 85 | else: 86 | let body = content[idx + 2 ..< content.len] # remove headers 87 | var newLines: seq[string] 88 | for line in strutils.splitLines(body): 89 | var 90 | chars = codes.stripCodes(line.toRunes) # remove escape codes 91 | newLine: seq[string] 92 | # replace ansi block chars with spaces 93 | for ch in chars: 94 | let s = $ch 95 | if s in wavescript.whitespaceChars: 96 | newLine.add(" ") 97 | else: 98 | newLine.add(s) 99 | # delete trailing spaces in each line 100 | for i in countdown(newLine.len-1, 0): 101 | if newLine[i] == " ": 102 | newLine.delete(i) 103 | else: 104 | break 105 | newLines.add(strutils.join(newLine)) 106 | return strutils.join(newLines, "\n") 107 | 108 | -------------------------------------------------------------------------------- /src/wavecorepkg/db.nim: -------------------------------------------------------------------------------- 1 | {.passC: "-DSQLITE_ENABLE_FTS5".} 2 | 3 | import ./db/sqlite3 4 | from ./db/db_sqlite import sql 5 | from bitops import nil 6 | import tables 7 | from strutils import format 8 | from os import nil 9 | import json 10 | 11 | const 12 | SQLITE_OPEN_READONLY = 1 13 | SQLITE_OPEN_READWRITE = 2 14 | SQLITE_OPEN_CREATE = 4 15 | 16 | proc sqlite3_open_v2(filename: cstring, ppDb: var PSqlite3, flags: cint, zVfs: cstring): cint {.cdecl, importc.} 17 | 18 | type 19 | Mode* = enum 20 | Read, ReadWrite, Http, 21 | 22 | proc open*(filename: string, mode: Mode): PSqlite3 = 23 | let 24 | flags: cint = 25 | case mode: 26 | of Read, Http: 27 | SQLITE_OPEN_READONLY 28 | of ReadWrite: 29 | bitops.bitor(SQLITE_OPEN_READWRITE, SQLITE_OPEN_CREATE) 30 | vfs: cstring = 31 | case mode: 32 | of Http: 33 | "http".cstring 34 | of Read, ReadWrite: 35 | nil 36 | if sqlite3_open_v2(filename, result, flags, vfs) != SQLITE_OK: 37 | db_sqlite.dbError(result) 38 | 39 | template withOpen*(conn: untyped, filename: string, mode: Mode, body: untyped) = 40 | block: 41 | let conn = open(filename, mode) 42 | try: 43 | body 44 | finally: 45 | db_sqlite.close(conn) 46 | if mode == ReadWrite and os.fileExists(filename): 47 | writeFile(filename & ".json", $ %* {"total-size": os.getFileSize(filename)}) 48 | 49 | template withTransaction*(conn: PSqlite3, body: untyped) = 50 | db_sqlite.exec(conn, sql"BEGIN TRANSACTION") 51 | body 52 | db_sqlite.exec(conn, sql"COMMIT") 53 | 54 | template withStatement*(conn: PSqlite3, query: string, stmt: PStmt, body: untyped) = 55 | try: 56 | if prepare_v2(conn, query.cstring, query.len.cint, stmt, nil) != SQLITE_OK: 57 | db_sqlite.dbError(conn) 58 | body 59 | finally: 60 | if finalize(stmt) != SQLITE_OK: 61 | db_sqlite.dbError(conn) 62 | 63 | proc select*[T](conn: PSqlite3, init: proc (stmt: PStmt): T, query: string, args: varargs[string, `$`]): seq[T] = 64 | {.cast(gcsafe).}: 65 | var stmt: PStmt 66 | withStatement(conn, query, stmt): 67 | for i in 0 ..< args.len: 68 | db_sqlite.bindParam(db_sqlite.SqlPrepared(stmt), i+1, args[i]) 69 | while step(stmt) == SQLITE_ROW: 70 | result.add(init(stmt)) 71 | 72 | proc getVersion(stmt: PStmt): int = 73 | var cols = sqlite3.column_count(stmt) 74 | for col in 0 .. cols-1: 75 | let colName = $sqlite3.column_name(stmt, col) 76 | case colName: 77 | of "user_version": 78 | return sqlite3.column_int(stmt, col) 79 | 80 | proc createTables(conn: PSqlite3) = 81 | db_sqlite.exec conn, sql""" 82 | CREATE TABLE user ( 83 | user_id INTEGER PRIMARY KEY AUTOINCREMENT, 84 | ts INTEGER, 85 | public_key TEXT UNIQUE, 86 | public_key_algo TEXT, 87 | tags TEXT, 88 | tags_sig TEXT UNIQUE, 89 | extra TEXT, 90 | display_name TEXT UNIQUE 91 | ) STRICT 92 | """ 93 | db_sqlite.exec conn, sql"CREATE INDEX user__ts ON user(ts)" 94 | db_sqlite.exec conn, sql"CREATE INDEX user__public_key__ts ON user(public_key, ts)" 95 | db_sqlite.exec conn, sql""" 96 | CREATE TABLE post ( 97 | post_id INTEGER PRIMARY KEY AUTOINCREMENT, 98 | ts INTEGER, 99 | content_sig TEXT UNIQUE, 100 | content_sig_last TEXT UNIQUE, 101 | public_key TEXT, 102 | parent TEXT, 103 | parent_public_key TEXT, 104 | reply_count INTEGER, 105 | distinct_reply_count INTEGER, 106 | score INTEGER, 107 | partition INTEGER, 108 | visibility INTEGER, 109 | tags TEXT, 110 | extra TEXT, 111 | extra_tags TEXT, 112 | extra_tags_sig TEXT UNIQUE, 113 | display_name TEXT 114 | ) STRICT 115 | """ 116 | db_sqlite.exec conn, sql"CREATE INDEX post__ts ON post(ts)" 117 | db_sqlite.exec conn, sql"CREATE INDEX post__parent__ts ON post(parent, ts)" 118 | db_sqlite.exec conn, sql"CREATE INDEX post__public_key__ts ON post(public_key, ts)" 119 | db_sqlite.exec conn, sql"CREATE INDEX post__visibility__ts ON post(visibility, ts)" 120 | db_sqlite.exec conn, sql"CREATE INDEX post__visibility__parent__ts ON post(visibility, parent, ts)" 121 | db_sqlite.exec conn, sql"CREATE INDEX post__visibility__parent__score ON post(visibility, parent, score)" 122 | db_sqlite.exec conn, sql"CREATE INDEX post__visibility__public_key__parent__ts ON post(visibility, public_key, parent, ts)" 123 | db_sqlite.exec conn, sql"CREATE INDEX post__visibility__parent__public_key__ts ON post(visibility, parent_public_key, ts)" 124 | 125 | proc init*(conn: PSqlite3) = 126 | var version = select[int](conn, getVersion, "PRAGMA user_version")[0] 127 | withTransaction(conn): 128 | if version == 0: 129 | db_sqlite.exec conn, sql""" 130 | pragma journal_mode = delete 131 | """ 132 | db_sqlite.exec conn, sql""" 133 | pragma page_size = 1024 134 | """ 135 | createTables(conn) 136 | db_sqlite.exec conn, sql"CREATE VIRTUAL TABLE user_search USING fts5 (user_id, attribute, value, value_unindexed UNINDEXED)" 137 | db_sqlite.exec conn, sql"CREATE VIRTUAL TABLE post_search USING fts5 (post_id, user_id, attribute, value, value_unindexed UNINDEXED)" 138 | # version 1 does a full replacement of the user and post tables 139 | # since createTables already uses the latest schema, we can skip it 140 | version += 2 141 | db_sqlite.exec conn, sql("PRAGMA user_version = " & $version) 142 | if version == 1: 143 | echo "MIGRATING..." 144 | db_sqlite.exec conn, sql"ALTER TABLE user RENAME TO user_temp" 145 | db_sqlite.exec conn, sql"ALTER TABLE post RENAME TO post_temp" 146 | db_sqlite.exec conn, sql"DROP INDEX user__ts" 147 | db_sqlite.exec conn, sql"DROP INDEX user__public_key__ts" 148 | db_sqlite.exec conn, sql"DROP INDEX post__ts" 149 | db_sqlite.exec conn, sql"DROP INDEX post__parent__ts" 150 | db_sqlite.exec conn, sql"DROP INDEX post__public_key__ts" 151 | db_sqlite.exec conn, sql"DROP INDEX post__visibility__ts" 152 | db_sqlite.exec conn, sql"DROP INDEX post__visibility__parent__ts" 153 | db_sqlite.exec conn, sql"DROP INDEX post__visibility__parent__score" 154 | db_sqlite.exec conn, sql"DROP INDEX post__visibility__public_key__parent__ts" 155 | db_sqlite.exec conn, sql"DROP INDEX post__visibility__parent__public_key__ts" 156 | createTables(conn) 157 | db_sqlite.exec conn, sql"INSERT INTO user SELECT *, NULL AS display_name FROM user_temp ORDER BY user_id" 158 | db_sqlite.exec conn, sql"INSERT INTO post SELECT *, '' AS extra_tags, content_sig AS extra_tags_sig, '' AS display_name FROM post_temp ORDER BY post_id" 159 | db_sqlite.exec conn, sql"DROP TABLE user_temp" 160 | db_sqlite.exec conn, sql"DROP TABLE post_temp" 161 | echo "FINISHED MIGRATING" 162 | version += 1 163 | db_sqlite.exec conn, sql("PRAGMA user_version = " & $version) 164 | 165 | proc attach*(conn: PSqlite3, path: string, alias: string) = 166 | db_sqlite.exec conn, sql("ATTACH DATABASE '$1' AS $2".format(path, alias)) 167 | 168 | -------------------------------------------------------------------------------- /src/wavecorepkg/db/db_common.nim: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # Nim's Runtime Library 4 | # (c) Copyright 2015 Andreas Rumpf 5 | # 6 | # See the file "copying.txt", included in this 7 | # distribution, for details about the copyright. 8 | # 9 | 10 | ## Common datatypes and definitions for all `db_*.nim` ( 11 | ## `db_mysql `_, `db_postgres `_, 12 | ## and `db_sqlite `_) modules. 13 | 14 | type 15 | DbError* = object of IOError ## exception that is raised if a database error occurs 16 | 17 | SqlQuery* = distinct string ## an SQL query string 18 | 19 | 20 | DbEffect* = object of IOEffect ## effect that denotes a database operation 21 | ReadDbEffect* = object of DbEffect ## effect that denotes a read operation 22 | WriteDbEffect* = object of DbEffect ## effect that denotes a write operation 23 | 24 | DbTypeKind* = enum ## a superset of datatypes that might be supported. 25 | dbUnknown, ## unknown datatype 26 | dbSerial, ## datatype used for primary auto-increment keys 27 | dbNull, ## datatype used for the NULL value 28 | dbBit, ## bit datatype 29 | dbBool, ## boolean datatype 30 | dbBlob, ## blob datatype 31 | dbFixedChar, ## string of fixed length 32 | dbVarchar, ## string datatype 33 | dbJson, ## JSON datatype 34 | dbXml, ## XML datatype 35 | dbInt, ## some integer type 36 | dbUInt, ## some unsigned integer type 37 | dbDecimal, ## decimal numbers (fixed-point number) 38 | dbFloat, ## some floating point type 39 | dbDate, ## a year-month-day description 40 | dbTime, ## HH:MM:SS information 41 | dbDatetime, ## year-month-day and HH:MM:SS information, 42 | ## plus optional time or timezone information 43 | dbTimestamp, ## Timestamp values are stored as the number of seconds 44 | ## since the epoch ('1970-01-01 00:00:00' UTC). 45 | dbTimeInterval, ## an interval [a,b] of times 46 | dbEnum, ## some enum 47 | dbSet, ## set of enum values 48 | dbArray, ## an array of values 49 | dbComposite, ## composite type (record, struct, etc) 50 | dbUrl, ## a URL 51 | dbUuid, ## a UUID 52 | dbInet, ## an IP address 53 | dbMacAddress, ## a MAC address 54 | dbGeometry, ## some geometric type 55 | dbPoint, ## Point on a plane (x,y) 56 | dbLine, ## Infinite line ((x1,y1),(x2,y2)) 57 | dbLseg, ## Finite line segment ((x1,y1),(x2,y2)) 58 | dbBox, ## Rectangular box ((x1,y1),(x2,y2)) 59 | dbPath, ## Closed or open path (similar to polygon) ((x1,y1),...) 60 | dbPolygon, ## Polygon (similar to closed path) ((x1,y1),...) 61 | dbCircle, ## Circle <(x,y),r> (center point and radius) 62 | dbUser1, ## user definable datatype 1 (for unknown extensions) 63 | dbUser2, ## user definable datatype 2 (for unknown extensions) 64 | dbUser3, ## user definable datatype 3 (for unknown extensions) 65 | dbUser4, ## user definable datatype 4 (for unknown extensions) 66 | dbUser5 ## user definable datatype 5 (for unknown extensions) 67 | 68 | DbType* = object ## describes a database type 69 | kind*: DbTypeKind ## the kind of the described type 70 | notNull*: bool ## does the type contain NULL? 71 | name*: string ## the name of the type 72 | size*: Natural ## the size of the datatype; 0 if of variable size 73 | maxReprLen*: Natural ## maximal length required for the representation 74 | precision*, scale*: Natural ## precision and scale of the number 75 | min*, max*: BiggestInt ## the minimum and maximum of allowed values 76 | validValues*: seq[string] ## valid values of an enum or a set 77 | 78 | DbColumn* = object ## information about a database column 79 | name*: string ## name of the column 80 | tableName*: string ## name of the table the column belongs to (optional) 81 | typ*: DbType ## type of the column 82 | primaryKey*: bool ## is this a primary key? 83 | foreignKey*: bool ## is this a foreign key? 84 | DbColumns* = seq[DbColumn] 85 | 86 | template sql*(query: string): SqlQuery = 87 | ## constructs a SqlQuery from the string `query`. This is supposed to be 88 | ## used as a raw-string-literal modifier: 89 | ## `sql"update user set counter = counter + 1"` 90 | ## 91 | ## If assertions are turned off, it does nothing. If assertions are turned 92 | ## on, later versions will check the string for valid syntax. 93 | SqlQuery(query) 94 | 95 | proc dbError*(msg: string) {.noreturn, noinline.} = 96 | ## raises an DbError exception with message `msg`. 97 | var e: ref DbError 98 | new(e) 99 | e.msg = msg 100 | raise e 101 | -------------------------------------------------------------------------------- /src/wavecorepkg/db/sqlite3.nim: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # Nim's Runtime Library 4 | # (c) Copyright 2012 Andreas Rumpf 5 | # 6 | # See the file "copying.txt", included in this 7 | # distribution, for details about the copyright. 8 | # 9 | 10 | when defined(nimHasStyleChecks): 11 | {.push styleChecks: off.} 12 | 13 | when defined(windows): 14 | when defined(nimOldDlls): 15 | const Lib = "sqlite3.dll" 16 | elif defined(cpu64): 17 | const Lib = "sqlite3_64.dll" 18 | else: 19 | const Lib = "sqlite3_32.dll" 20 | elif defined(macosx): 21 | const 22 | Lib = "libsqlite3(|.0).dylib" 23 | else: 24 | const 25 | Lib = "libsqlite3.so(|.0)" 26 | 27 | when defined(staticSqlite): 28 | {.pragma: mylib.} 29 | {.compile: "sqlite3.c".} 30 | else: 31 | {.pragma: mylib.} 32 | 33 | const 34 | SQLITE_INTEGER* = 1 35 | SQLITE_FLOAT* = 2 36 | SQLITE_BLOB* = 4 37 | SQLITE_NULL* = 5 38 | SQLITE_TEXT* = 3 39 | SQLITE_UTF8* = 1 40 | SQLITE_UTF16LE* = 2 41 | SQLITE_UTF16BE* = 3 # Use native byte order 42 | SQLITE_UTF16* = 4 # sqlite3_create_function only 43 | SQLITE_ANY* = 5 #sqlite_exec return values 44 | SQLITE_OK* = 0 45 | SQLITE_ERROR* = 1 # SQL error or missing database 46 | SQLITE_INTERNAL* = 2 # An internal logic error in SQLite 47 | SQLITE_PERM* = 3 # Access permission denied 48 | SQLITE_ABORT* = 4 # Callback routine requested an abort 49 | SQLITE_BUSY* = 5 # The database file is locked 50 | SQLITE_LOCKED* = 6 # A table in the database is locked 51 | SQLITE_NOMEM* = 7 # A malloc() failed 52 | SQLITE_READONLY* = 8 # Attempt to write a readonly database 53 | SQLITE_INTERRUPT* = 9 # Operation terminated by sqlite3_interrupt() 54 | SQLITE_IOERR* = 10 # Some kind of disk I/O error occurred 55 | SQLITE_CORRUPT* = 11 # The database disk image is malformed 56 | SQLITE_NOTFOUND* = 12 # (Internal Only) Table or record not found 57 | SQLITE_FULL* = 13 # Insertion failed because database is full 58 | SQLITE_CANTOPEN* = 14 # Unable to open the database file 59 | SQLITE_PROTOCOL* = 15 # Database lock protocol error 60 | SQLITE_EMPTY* = 16 # Database is empty 61 | SQLITE_SCHEMA* = 17 # The database schema changed 62 | SQLITE_TOOBIG* = 18 # Too much data for one row of a table 63 | SQLITE_CONSTRAINT* = 19 # Abort due to constraint violation 64 | SQLITE_MISMATCH* = 20 # Data type mismatch 65 | SQLITE_MISUSE* = 21 # Library used incorrectly 66 | SQLITE_NOLFS* = 22 # Uses OS features not supported on host 67 | SQLITE_AUTH* = 23 # Authorization denied 68 | SQLITE_FORMAT* = 24 # Auxiliary database format error 69 | SQLITE_RANGE* = 25 # 2nd parameter to sqlite3_bind out of range 70 | SQLITE_NOTADB* = 26 # File opened that is not a database file 71 | SQLITE_ROW* = 100 # sqlite3_step() has another row ready 72 | SQLITE_DONE* = 101 # sqlite3_step() has finished executing 73 | SQLITE_COPY* = 0 74 | SQLITE_CREATE_INDEX* = 1 75 | SQLITE_CREATE_TABLE* = 2 76 | SQLITE_CREATE_TEMP_INDEX* = 3 77 | SQLITE_CREATE_TEMP_TABLE* = 4 78 | SQLITE_CREATE_TEMP_TRIGGER* = 5 79 | SQLITE_CREATE_TEMP_VIEW* = 6 80 | SQLITE_CREATE_TRIGGER* = 7 81 | SQLITE_CREATE_VIEW* = 8 82 | SQLITE_DELETE* = 9 83 | SQLITE_DROP_INDEX* = 10 84 | SQLITE_DROP_TABLE* = 11 85 | SQLITE_DROP_TEMP_INDEX* = 12 86 | SQLITE_DROP_TEMP_TABLE* = 13 87 | SQLITE_DROP_TEMP_TRIGGER* = 14 88 | SQLITE_DROP_TEMP_VIEW* = 15 89 | SQLITE_DROP_TRIGGER* = 16 90 | SQLITE_DROP_VIEW* = 17 91 | SQLITE_INSERT* = 18 92 | SQLITE_PRAGMA* = 19 93 | SQLITE_READ* = 20 94 | SQLITE_SELECT* = 21 95 | SQLITE_TRANSACTION* = 22 96 | SQLITE_UPDATE* = 23 97 | SQLITE_ATTACH* = 24 98 | SQLITE_DETACH* = 25 99 | SQLITE_ALTER_TABLE* = 26 100 | SQLITE_REINDEX* = 27 101 | SQLITE_DENY* = 1 102 | SQLITE_IGNORE* = 2 # Original from sqlite3.h: 103 | #define SQLITE_STATIC ((void(*)(void *))0) 104 | #define SQLITE_TRANSIENT ((void(*)(void *))-1) 105 | SQLITE_DETERMINISTIC* = 0x800 106 | 107 | type 108 | Sqlite3 {.pure, final.} = object 109 | PSqlite3* = ptr Sqlite3 110 | PPSqlite3* = ptr PSqlite3 111 | Sqlite3_Backup {.pure, final.} = object 112 | PSqlite3_Backup* = ptr Sqlite3_Backup 113 | PPSqlite3_Backup* = ptr PSqlite3_Backup 114 | Context{.pure, final.} = object 115 | Pcontext* = ptr Context 116 | TStmt{.pure, final.} = object 117 | PStmt* = ptr TStmt 118 | Value{.pure, final.} = object 119 | PValue* = ptr Value 120 | PValueArg* = array[0..127, PValue] 121 | 122 | Callback* = proc (para1: pointer, para2: int32, para3, 123 | para4: cstringArray): int32{.cdecl.} 124 | Tbind_destructor_func* = proc (para1: pointer){.cdecl, locks: 0, tags: [], gcsafe.} 125 | Create_function_step_func* = proc (para1: Pcontext, para2: int32, 126 | para3: PValueArg){.cdecl.} 127 | Create_function_func_func* = proc (para1: Pcontext, para2: int32, 128 | para3: PValueArg){.cdecl.} 129 | Create_function_final_func* = proc (para1: Pcontext){.cdecl.} 130 | Result_func* = proc (para1: pointer){.cdecl.} 131 | Create_collation_func* = proc (para1: pointer, para2: int32, para3: pointer, 132 | para4: int32, para5: pointer): int32{.cdecl.} 133 | Collation_needed_func* = proc (para1: pointer, para2: PSqlite3, eTextRep: int32, 134 | para4: cstring){.cdecl.} 135 | 136 | const 137 | SQLITE_STATIC* = nil 138 | SQLITE_TRANSIENT* = cast[Tbind_destructor_func](-1) 139 | 140 | proc close*(para1: PSqlite3): int32{.cdecl, mylib, importc: "sqlite3_close".} 141 | proc exec*(para1: PSqlite3, sql: cstring, para3: Callback, para4: pointer, 142 | errmsg: var cstring): int32{.cdecl, mylib, 143 | importc: "sqlite3_exec".} 144 | proc last_insert_rowid*(para1: PSqlite3): int64{.cdecl, mylib, 145 | importc: "sqlite3_last_insert_rowid".} 146 | proc changes*(para1: PSqlite3): int32{.cdecl, mylib, importc: "sqlite3_changes".} 147 | proc total_changes*(para1: PSqlite3): int32{.cdecl, mylib, 148 | importc: "sqlite3_total_changes".} 149 | proc interrupt*(para1: PSqlite3){.cdecl, mylib, importc: "sqlite3_interrupt".} 150 | proc complete*(sql: cstring): int32{.cdecl, mylib, 151 | importc: "sqlite3_complete".} 152 | proc complete16*(sql: pointer): int32{.cdecl, mylib, 153 | importc: "sqlite3_complete16".} 154 | proc busy_handler*(para1: PSqlite3, 155 | para2: proc (para1: pointer, para2: int32): int32{.cdecl.}, 156 | para3: pointer): int32{.cdecl, mylib, 157 | importc: "sqlite3_busy_handler".} 158 | proc busy_timeout*(para1: PSqlite3, ms: int32): int32{.cdecl, mylib, 159 | importc: "sqlite3_busy_timeout".} 160 | proc get_table*(para1: PSqlite3, sql: cstring, resultp: var cstringArray, 161 | nrow, ncolumn: var cint, errmsg: ptr cstring): int32{.cdecl, 162 | mylib, importc: "sqlite3_get_table".} 163 | proc free_table*(result: cstringArray){.cdecl, mylib, 164 | importc: "sqlite3_free_table".} 165 | # Todo: see how translate sqlite3_mprintf, sqlite3_vmprintf, sqlite3_snprintf 166 | # function sqlite3_mprintf(_para1:Pchar; args:array of const):Pchar;cdecl; external Sqlite3Lib name 'sqlite3_mprintf'; 167 | proc mprintf*(para1: cstring): cstring{.cdecl, varargs, mylib, 168 | importc: "sqlite3_mprintf".} 169 | #function sqlite3_vmprintf(_para1:Pchar; _para2:va_list):Pchar;cdecl; external Sqlite3Lib name 'sqlite3_vmprintf'; 170 | proc free*(z: cstring){.cdecl, mylib, importc: "sqlite3_free".} 171 | #function sqlite3_snprintf(_para1:longint; _para2:Pchar; _para3:Pchar; args:array of const):Pchar;cdecl; external Sqlite3Lib name 'sqlite3_snprintf'; 172 | proc snprintf*(para1: int32, para2: cstring, para3: cstring): cstring{.cdecl, 173 | mylib, varargs, importc: "sqlite3_snprintf".} 174 | proc set_authorizer*(para1: PSqlite3, xAuth: proc (para1: pointer, para2: int32, 175 | para3: cstring, para4: cstring, para5: cstring, para6: cstring): int32{. 176 | cdecl.}, pUserData: pointer): int32{.cdecl, mylib, 177 | importc: "sqlite3_set_authorizer".} 178 | proc trace*(para1: PSqlite3, xTrace: proc (para1: pointer, para2: cstring){.cdecl.}, 179 | para3: pointer): pointer{.cdecl, mylib, 180 | importc: "sqlite3_trace".} 181 | proc progress_handler*(para1: PSqlite3, para2: int32, 182 | para3: proc (para1: pointer): int32{.cdecl.}, 183 | para4: pointer){.cdecl, mylib, 184 | importc: "sqlite3_progress_handler".} 185 | proc commit_hook*(para1: PSqlite3, para2: proc (para1: pointer): int32{.cdecl.}, 186 | para3: pointer): pointer{.cdecl, mylib, 187 | importc: "sqlite3_commit_hook".} 188 | proc open*(filename: cstring, ppDb: var PSqlite3): int32{.cdecl, mylib, 189 | importc: "sqlite3_open".} 190 | proc open16*(filename: pointer, ppDb: var PSqlite3): int32{.cdecl, mylib, 191 | importc: "sqlite3_open16".} 192 | proc errcode*(db: PSqlite3): int32{.cdecl, mylib, importc: "sqlite3_errcode".} 193 | proc errmsg*(para1: PSqlite3): cstring{.cdecl, mylib, importc: "sqlite3_errmsg".} 194 | proc errmsg16*(para1: PSqlite3): pointer{.cdecl, mylib, 195 | importc: "sqlite3_errmsg16".} 196 | proc prepare*(db: PSqlite3, zSql: cstring, nBytes: int32, ppStmt: var PStmt, 197 | pzTail: ptr cstring): int32{.cdecl, mylib, 198 | importc: "sqlite3_prepare".} 199 | 200 | proc prepare_v2*(db: PSqlite3, zSql: cstring, nByte: cint, ppStmt: var PStmt, 201 | pzTail: ptr cstring): cint {. 202 | importc: "sqlite3_prepare_v2", cdecl, mylib.} 203 | 204 | proc prepare16*(db: PSqlite3, zSql: pointer, nBytes: int32, ppStmt: var PStmt, 205 | pzTail: var pointer): int32{.cdecl, mylib, 206 | importc: "sqlite3_prepare16".} 207 | proc bind_blob*(para1: PStmt, para2: int32, para3: pointer, n: int32, 208 | para5: Tbind_destructor_func): int32{.cdecl, mylib, 209 | importc: "sqlite3_bind_blob".} 210 | proc bind_double*(para1: PStmt, para2: int32, para3: float64): int32{.cdecl, 211 | mylib, importc: "sqlite3_bind_double".} 212 | proc bind_int*(para1: PStmt, para2: int32, para3: int32): int32{.cdecl, 213 | mylib, importc: "sqlite3_bind_int".} 214 | proc bind_int64*(para1: PStmt, para2: int32, para3: int64): int32{.cdecl, 215 | mylib, importc: "sqlite3_bind_int64".} 216 | proc bind_null*(para1: PStmt, para2: int32): int32{.cdecl, mylib, 217 | importc: "sqlite3_bind_null".} 218 | proc bind_text*(para1: PStmt, para2: int32, para3: cstring, n: int32, 219 | para5: Tbind_destructor_func): int32{.cdecl, mylib, 220 | importc: "sqlite3_bind_text".} 221 | proc bind_text16*(para1: PStmt, para2: int32, para3: pointer, para4: int32, 222 | para5: Tbind_destructor_func): int32{.cdecl, mylib, 223 | importc: "sqlite3_bind_text16".} 224 | #function sqlite3_bind_value(_para1:Psqlite3_stmt; _para2:longint; _para3:Psqlite3_value):longint;cdecl; external Sqlite3Lib name 'sqlite3_bind_value'; 225 | #These overloaded functions were introduced to allow the use of SQLITE_STATIC and SQLITE_TRANSIENT 226 | #It's the c world man ;-) 227 | proc bind_blob*(para1: PStmt, para2: int32, para3: pointer, n: int32, 228 | para5: int32): int32{.cdecl, mylib, 229 | importc: "sqlite3_bind_blob".} 230 | proc bind_text*(para1: PStmt, para2: int32, para3: cstring, n: int32, 231 | para5: int32): int32{.cdecl, mylib, 232 | importc: "sqlite3_bind_text".} 233 | proc bind_text16*(para1: PStmt, para2: int32, para3: pointer, para4: int32, 234 | para5: int32): int32{.cdecl, mylib, 235 | importc: "sqlite3_bind_text16".} 236 | proc bind_parameter_count*(para1: PStmt): int32{.cdecl, mylib, 237 | importc: "sqlite3_bind_parameter_count".} 238 | proc bind_parameter_name*(para1: PStmt, para2: int32): cstring{.cdecl, 239 | mylib, importc: "sqlite3_bind_parameter_name".} 240 | proc bind_parameter_index*(para1: PStmt, zName: cstring): int32{.cdecl, 241 | mylib, importc: "sqlite3_bind_parameter_index".} 242 | proc clear_bindings*(para1: PStmt): int32 {.cdecl, 243 | mylib, importc: "sqlite3_clear_bindings".} 244 | proc column_count*(PStmt: PStmt): int32{.cdecl, mylib, 245 | importc: "sqlite3_column_count".} 246 | proc column_name*(para1: PStmt, para2: int32): cstring{.cdecl, mylib, 247 | importc: "sqlite3_column_name".} 248 | proc column_table_name*(para1: PStmt; para2: int32): cstring{.cdecl, mylib, 249 | importc: "sqlite3_column_table_name".} 250 | proc column_name16*(para1: PStmt, para2: int32): pointer{.cdecl, mylib, 251 | importc: "sqlite3_column_name16".} 252 | proc column_decltype*(para1: PStmt, i: int32): cstring{.cdecl, mylib, 253 | importc: "sqlite3_column_decltype".} 254 | proc column_decltype16*(para1: PStmt, para2: int32): pointer{.cdecl, 255 | mylib, importc: "sqlite3_column_decltype16".} 256 | proc step*(para1: PStmt): int32{.cdecl, mylib, importc: "sqlite3_step".} 257 | proc data_count*(PStmt: PStmt): int32{.cdecl, mylib, 258 | importc: "sqlite3_data_count".} 259 | proc column_blob*(para1: PStmt, iCol: int32): pointer{.cdecl, mylib, 260 | importc: "sqlite3_column_blob".} 261 | proc column_bytes*(para1: PStmt, iCol: int32): int32{.cdecl, mylib, 262 | importc: "sqlite3_column_bytes".} 263 | proc column_bytes16*(para1: PStmt, iCol: int32): int32{.cdecl, mylib, 264 | importc: "sqlite3_column_bytes16".} 265 | proc column_double*(para1: PStmt, iCol: int32): float64{.cdecl, mylib, 266 | importc: "sqlite3_column_double".} 267 | proc column_int*(para1: PStmt, iCol: int32): int32{.cdecl, mylib, 268 | importc: "sqlite3_column_int".} 269 | proc column_int64*(para1: PStmt, iCol: int32): int64{.cdecl, mylib, 270 | importc: "sqlite3_column_int64".} 271 | proc column_text*(para1: PStmt, iCol: int32): cstring{.cdecl, mylib, 272 | importc: "sqlite3_column_text".} 273 | proc column_text16*(para1: PStmt, iCol: int32): pointer{.cdecl, mylib, 274 | importc: "sqlite3_column_text16".} 275 | proc column_type*(para1: PStmt, iCol: int32): int32{.cdecl, mylib, 276 | importc: "sqlite3_column_type".} 277 | proc finalize*(PStmt: PStmt): int32{.cdecl, mylib, 278 | importc: "sqlite3_finalize".} 279 | proc reset*(PStmt: PStmt): int32{.cdecl, mylib, importc: "sqlite3_reset".} 280 | proc create_function*(para1: PSqlite3, zFunctionName: cstring, nArg: int32, 281 | eTextRep: int32, para5: pointer, 282 | xFunc: Create_function_func_func, 283 | xStep: Create_function_step_func, 284 | xFinal: Create_function_final_func): int32{.cdecl, 285 | mylib, importc: "sqlite3_create_function".} 286 | proc create_function16*(para1: PSqlite3, zFunctionName: pointer, nArg: int32, 287 | eTextRep: int32, para5: pointer, 288 | xFunc: Create_function_func_func, 289 | xStep: Create_function_step_func, 290 | xFinal: Create_function_final_func): int32{.cdecl, 291 | mylib, importc: "sqlite3_create_function16".} 292 | proc aggregate_count*(para1: Pcontext): int32{.cdecl, mylib, 293 | importc: "sqlite3_aggregate_count".} 294 | proc value_blob*(para1: PValue): pointer{.cdecl, mylib, 295 | importc: "sqlite3_value_blob".} 296 | proc value_bytes*(para1: PValue): int32{.cdecl, mylib, 297 | importc: "sqlite3_value_bytes".} 298 | proc value_bytes16*(para1: PValue): int32{.cdecl, mylib, 299 | importc: "sqlite3_value_bytes16".} 300 | proc value_double*(para1: PValue): float64{.cdecl, mylib, 301 | importc: "sqlite3_value_double".} 302 | proc value_int*(para1: PValue): int32{.cdecl, mylib, 303 | importc: "sqlite3_value_int".} 304 | proc value_int64*(para1: PValue): int64{.cdecl, mylib, 305 | importc: "sqlite3_value_int64".} 306 | proc value_text*(para1: PValue): cstring{.cdecl, mylib, 307 | importc: "sqlite3_value_text".} 308 | proc value_text16*(para1: PValue): pointer{.cdecl, mylib, 309 | importc: "sqlite3_value_text16".} 310 | proc value_text16le*(para1: PValue): pointer{.cdecl, mylib, 311 | importc: "sqlite3_value_text16le".} 312 | proc value_text16be*(para1: PValue): pointer{.cdecl, mylib, 313 | importc: "sqlite3_value_text16be".} 314 | proc value_type*(para1: PValue): int32{.cdecl, mylib, 315 | importc: "sqlite3_value_type".} 316 | proc aggregate_context*(para1: Pcontext, nBytes: int32): pointer{.cdecl, 317 | mylib, importc: "sqlite3_aggregate_context".} 318 | proc user_data*(para1: Pcontext): pointer{.cdecl, mylib, 319 | importc: "sqlite3_user_data".} 320 | proc get_auxdata*(para1: Pcontext, para2: int32): pointer{.cdecl, mylib, 321 | importc: "sqlite3_get_auxdata".} 322 | proc set_auxdata*(para1: Pcontext, para2: int32, para3: pointer, 323 | para4: proc (para1: pointer){.cdecl.}){.cdecl, mylib, 324 | importc: "sqlite3_set_auxdata".} 325 | proc result_blob*(para1: Pcontext, para2: pointer, para3: int32, 326 | para4: Result_func){.cdecl, mylib, 327 | importc: "sqlite3_result_blob".} 328 | proc result_double*(para1: Pcontext, para2: float64){.cdecl, mylib, 329 | importc: "sqlite3_result_double".} 330 | proc result_error*(para1: Pcontext, para2: cstring, para3: int32){.cdecl, 331 | mylib, importc: "sqlite3_result_error".} 332 | proc result_error16*(para1: Pcontext, para2: pointer, para3: int32){.cdecl, 333 | mylib, importc: "sqlite3_result_error16".} 334 | proc result_int*(para1: Pcontext, para2: int32){.cdecl, mylib, 335 | importc: "sqlite3_result_int".} 336 | proc result_int64*(para1: Pcontext, para2: int64){.cdecl, mylib, 337 | importc: "sqlite3_result_int64".} 338 | proc result_null*(para1: Pcontext){.cdecl, mylib, 339 | importc: "sqlite3_result_null".} 340 | proc result_text*(para1: Pcontext, para2: cstring, para3: int32, 341 | para4: Result_func){.cdecl, mylib, 342 | importc: "sqlite3_result_text".} 343 | proc result_text16*(para1: Pcontext, para2: pointer, para3: int32, 344 | para4: Result_func){.cdecl, mylib, 345 | importc: "sqlite3_result_text16".} 346 | proc result_text16le*(para1: Pcontext, para2: pointer, para3: int32, 347 | para4: Result_func){.cdecl, mylib, 348 | importc: "sqlite3_result_text16le".} 349 | proc result_text16be*(para1: Pcontext, para2: pointer, para3: int32, 350 | para4: Result_func){.cdecl, mylib, 351 | importc: "sqlite3_result_text16be".} 352 | proc result_value*(para1: Pcontext, para2: PValue){.cdecl, mylib, 353 | importc: "sqlite3_result_value".} 354 | proc create_collation*(para1: PSqlite3, zName: cstring, eTextRep: int32, 355 | para4: pointer, xCompare: Create_collation_func): int32{. 356 | cdecl, mylib, importc: "sqlite3_create_collation".} 357 | proc create_collation16*(para1: PSqlite3, zName: cstring, eTextRep: int32, 358 | para4: pointer, xCompare: Create_collation_func): int32{. 359 | cdecl, mylib, importc: "sqlite3_create_collation16".} 360 | proc collation_needed*(para1: PSqlite3, para2: pointer, para3: Collation_needed_func): int32{. 361 | cdecl, mylib, importc: "sqlite3_collation_needed".} 362 | proc collation_needed16*(para1: PSqlite3, para2: pointer, para3: Collation_needed_func): int32{. 363 | cdecl, mylib, importc: "sqlite3_collation_needed16".} 364 | proc libversion*(): cstring{.cdecl, mylib, importc: "sqlite3_libversion".} 365 | #Alias for allowing better code portability (win32 is not working with external variables) 366 | proc version*(): cstring{.cdecl, mylib, importc: "sqlite3_libversion".} 367 | # Not published functions 368 | proc libversion_number*(): int32{.cdecl, mylib, 369 | importc: "sqlite3_libversion_number".} 370 | 371 | proc backup_init*(pDest: PSqlite3, zDestName: cstring, pSource: PSqlite3, zSourceName: cstring): PSqlite3_Backup {. 372 | cdecl, mylib, importc: "sqlite3_backup_init".} 373 | 374 | proc backup_step*(pBackup: PSqlite3_Backup, nPage: int32): int32 {.cdecl, mylib, importc: "sqlite3_backup_step".} 375 | 376 | proc backup_finish*(pBackup: PSqlite3_Backup): int32 {.cdecl, mylib, importc: "sqlite3_backup_finish".} 377 | 378 | proc backup_pagecount*(pBackup: PSqlite3_Backup): int32 {.cdecl, mylib, importc: "sqlite3_backup_pagecount".} 379 | 380 | proc backup_remaining*(pBackup: PSqlite3_Backup): int32 {.cdecl, mylib, importc: "sqlite3_backup_remaining".} 381 | 382 | proc sqlite3_sleep*(t: int64): int64 {.cdecl, mylib, importc: "sqlite3_sleep".} 383 | 384 | #function sqlite3_key(db:Psqlite3; pKey:pointer; nKey:longint):longint;cdecl; external Sqlite3Lib name 'sqlite3_key'; 385 | #function sqlite3_rekey(db:Psqlite3; pKey:pointer; nKey:longint):longint;cdecl; external Sqlite3Lib name 'sqlite3_rekey'; 386 | #function sqlite3_sleep(_para1:longint):longint;cdecl; external Sqlite3Lib name 'sqlite3_sleep'; 387 | #function sqlite3_expired(_para1:Psqlite3_stmt):longint;cdecl; external Sqlite3Lib name 'sqlite3_expired'; 388 | #function sqlite3_global_recover:longint;cdecl; external Sqlite3Lib name 'sqlite3_global_recover'; 389 | # implementation 390 | 391 | when defined(nimHasStyleChecks): 392 | {.pop.} 393 | -------------------------------------------------------------------------------- /src/wavecorepkg/db/vfs.nim: -------------------------------------------------------------------------------- 1 | import ./sqlite3 2 | from ../paths import nil 3 | 4 | import ../client 5 | import json 6 | 7 | type 8 | sqlite3_vfs* {.bycopy.} = object 9 | iVersion*: cint ## Structure version number (currently 3) 10 | szOsFile*: cint ## Size of subclassed sqlite3_file 11 | mxPathname*: cint ## Maximum file pathname length 12 | pNext*: ptr sqlite3_vfs ## Next registered VFS 13 | zName*: cstring ## Name of this virtual file system 14 | pAppData*: pointer ## Pointer to application-specific data 15 | xOpen*: proc (a1: ptr sqlite3_vfs; zName: cstring; a3: ptr sqlite3_file; flags: cint; 16 | pOutFlags: ptr cint): cint {.cdecl.} 17 | xDelete*: proc (a1: ptr sqlite3_vfs; zName: cstring; syncDir: cint): cint {.cdecl.} 18 | xAccess*: proc (a1: ptr sqlite3_vfs; zName: cstring; flags: cint; pResOut: ptr cint): cint {.cdecl.} 19 | xFullPathname*: proc (a1: ptr sqlite3_vfs; zName: cstring; nOut: cint; zOut: cstring): cint {.cdecl.} 20 | xDlOpen*: proc (a1: ptr sqlite3_vfs; zFilename: cstring): pointer {.cdecl.} 21 | xDlError*: proc (a1: ptr sqlite3_vfs; nByte: cint; zErrMsg: cstring) {.cdecl.} 22 | xDlSym*: proc (): proc (a1: ptr sqlite3_vfs; a2: pointer; zSymbol: cstring): pointer {.cdecl.} 23 | xDlClose*: proc (a1: ptr sqlite3_vfs; a2: pointer) {.cdecl.} 24 | xRandomness*: proc (a1: ptr sqlite3_vfs; nByte: cint; zOut: cstring): cint {.cdecl.} 25 | xSleep*: proc (a1: ptr sqlite3_vfs; microseconds: cint): cint {.cdecl.} 26 | xCurrentTime*: proc (a1: ptr sqlite3_vfs; a2: ptr cdouble): cint {.cdecl.} 27 | xGetLastError*: proc (a1: ptr sqlite3_vfs; a2: cint; a3: cstring): cint {.cdecl.} 28 | ## * The methods above are in version 1 of the sqlite_vfs object 29 | ## * definition. Those that follow are added in version 2 or later 30 | ## 31 | xCurrentTimeInt64*: proc (a1: ptr sqlite3_vfs; a2: ptr int64): cint {.cdecl.} 32 | ## * The methods above are in versions 1 and 2 of the sqlite_vfs object. 33 | ## * Those below are for version 3 and greater. 34 | ## 35 | xSetSystemCall*: proc (a1: ptr sqlite3_vfs; zName: cstring; a3: pointer): cint {.cdecl.} 36 | xGetSystemCall*: proc (a1: ptr sqlite3_vfs; zName: cstring): pointer {.cdecl.} 37 | xNextSystemCall*: proc (a1: ptr sqlite3_vfs; zName: cstring): cstring {.cdecl.} 38 | ## * The methods above are in versions 1 through 3 of the sqlite_vfs object. 39 | ## * New fields may be appended in future versions. The iVersion 40 | ## * value will increment whenever this happens. 41 | ## 42 | sqlite3_io_methods* {.bycopy.} = object 43 | iVersion*: cint 44 | xClose*: proc (a1: ptr sqlite3_file): cint {.cdecl.} 45 | xRead*: proc (a1: ptr sqlite3_file; a2: pointer; iAmt: cint; iOfst: int64): cint {.cdecl.} 46 | xWrite*: proc (a1: ptr sqlite3_file; a2: pointer; iAmt: cint; iOfst: int64): cint {.cdecl.} 47 | xTruncate*: proc (a1: ptr sqlite3_file; size: int64): cint {.cdecl.} 48 | xSync*: proc (a1: ptr sqlite3_file; flags: cint): cint {.cdecl.} 49 | xFileSize*: proc (a1: ptr sqlite3_file; pSize: ptr int64): cint {.cdecl.} 50 | xLock*: proc (a1: ptr sqlite3_file; a2: cint): cint {.cdecl.} 51 | xUnlock*: proc (a1: ptr sqlite3_file; a2: cint): cint {.cdecl.} 52 | xCheckReservedLock*: proc (a1: ptr sqlite3_file; pResOut: ptr cint): cint {.cdecl.} 53 | xFileControl*: proc (a1: ptr sqlite3_file; op: cint; pArg: pointer): cint {.cdecl.} 54 | xSectorSize*: proc (a1: ptr sqlite3_file): cint {.cdecl.} 55 | xDeviceCharacteristics*: proc (a1: ptr sqlite3_file): cint {.cdecl.} 56 | ## Methods above are valid for version 1 57 | xShmMap*: proc (a1: ptr sqlite3_file; iPg: cint; pgsz: cint; a4: cint; a5: ptr pointer): cint {.cdecl.} 58 | xShmLock*: proc (a1: ptr sqlite3_file; offset: cint; n: cint; flags: cint): cint {.cdecl.} 59 | xShmBarrier*: proc (a1: ptr sqlite3_file) {.cdecl.} 60 | xShmUnmap*: proc (a1: ptr sqlite3_file; deleteFlag: cint): cint {.cdecl.} 61 | ## Methods above are valid for version 2 62 | xFetch*: proc (a1: ptr sqlite3_file; iOfst: int64; iAmt: cint; pp: ptr pointer): cint {.cdecl.} 63 | xUnfetch*: proc (a1: ptr sqlite3_file; iOfst: int64; p: pointer): cint {.cdecl.} 64 | ## Methods above are valid for version 3 65 | ## Additional methods may be added in future releases 66 | sqlite3_file* {.bycopy.} = object 67 | pMethods*: ptr sqlite3_io_methods ## Methods for an open file 68 | 69 | const SQLITE_IOCAP_IMMUTABLE = 0x00002000 70 | 71 | let customMethods = sqlite3_io_methods( 72 | iVersion: 3, 73 | xClose: proc (a1: ptr sqlite3_file): cint {.cdecl.} = SQLITE_OK, 74 | xRead: proc (a1: ptr sqlite3_file; pBuf: pointer; iAmt: cint; iOfst: int64): cint {.cdecl.} = 75 | let 76 | firstByte = iOfst 77 | lastByte = firstByte + iAmt - 1 78 | var res = fetch(Request( 79 | url: paths.readUrl, 80 | verb: "get", 81 | headers: @[ 82 | Header(key: "Range", value: "bytes=" & $firstByte & "-" & $lastByte), 83 | Header(key: "Cache-Control", value: "no-cache, no-store"), 84 | ] 85 | )) 86 | if res.code == 206 and res.body.len == iAmt: 87 | copyMem(pBuf, res.body[0].addr, res.body.len) 88 | else: 89 | return SQLITE_ERROR 90 | SQLITE_OK 91 | , 92 | xWrite: proc (a1: ptr sqlite3_file; a2: pointer; iAmt: cint; iOfst: int64): cint {.cdecl.} = SQLITE_OK, 93 | xTruncate: proc (a1: ptr sqlite3_file; size: int64): cint {.cdecl.} = SQLITE_OK, 94 | xSync: proc (a1: ptr sqlite3_file; flags: cint): cint {.cdecl.} = SQLITE_OK, 95 | xFileSize: proc (a1: ptr sqlite3_file; pSize: ptr int64): cint {.cdecl.} = 96 | let res = fetch(Request( 97 | url: paths.readUrl & ".json", 98 | verb: "get", 99 | headers: @[ 100 | Header(key: "Cache-Control", value: "no-cache, no-store"), 101 | ] 102 | )) 103 | if res.code == 200: 104 | try: 105 | let data = parseJson(res.body) 106 | pSize[] = data["total-size"].num 107 | return SQLITE_OK 108 | except Exception as ex: 109 | return SQLITE_ERROR 110 | else: 111 | return SQLITE_ERROR 112 | , 113 | xLock: proc (a1: ptr sqlite3_file; a2: cint): cint {.cdecl.} = SQLITE_OK, 114 | xUnlock: proc (a1: ptr sqlite3_file; a2: cint): cint {.cdecl.} = SQLITE_OK, 115 | xCheckReservedLock: proc (a1: ptr sqlite3_file; pResOut: ptr cint): cint {.cdecl.} = SQLITE_OK, 116 | xFileControl: proc (a1: ptr sqlite3_file; op: cint; pArg: pointer): cint {.cdecl.} = SQLITE_OK, 117 | xSectorSize: proc (a1: ptr sqlite3_file): cint {.cdecl.} = 0, 118 | xDeviceCharacteristics: proc (a1: ptr sqlite3_file): cint {.cdecl.} = SQLITE_IOCAP_IMMUTABLE, 119 | xShmMap: nil, 120 | xShmLock: nil, 121 | xShmBarrier: nil, 122 | xShmUnmap: nil, 123 | xFetch: nil, 124 | xUnfetch: nil, 125 | ) 126 | 127 | let httpVfs = sqlite3_vfs( 128 | iVersion: 3, ## Structure version number (currently 3) 129 | szOsFile: cint(sizeof(sqlite3_file)), ## Size of subclassed sqlite3_file 130 | mxPathname: 100, ## Maximum file pathname length 131 | pNext: nil, ## Next registered VFS 132 | zName: "http", ## Name of this virtual file system 133 | pAppData: nil, ## Pointer to application-specific data 134 | xOpen: proc (a1: ptr sqlite3_vfs; zName: cstring; a3: ptr sqlite3_file; flags: cint; 135 | pOutFlags: ptr cint): cint {.cdecl.} = 136 | a3.pMethods = customMethods.unsafeAddr 137 | SQLITE_OK 138 | , 139 | xDelete: proc (a1: ptr sqlite3_vfs; zName: cstring; syncDir: cint): cint {.cdecl.} = 140 | SQLITE_OK 141 | , 142 | xAccess: proc (a1: ptr sqlite3_vfs; zName: cstring; flags: cint; pResOut: ptr cint): cint {.cdecl.} = 143 | SQLITE_OK 144 | , 145 | xFullPathname: proc (a1: ptr sqlite3_vfs; zName: cstring; nOut: cint; zOut: cstring): cint {.cdecl.} = 146 | SQLITE_OK 147 | , 148 | xDlOpen: nil, 149 | xDlError: nil, 150 | xDlSym: nil, 151 | xDlClose: nil, 152 | xRandomness: nil, 153 | xSleep: nil, 154 | xCurrentTime: nil, 155 | xGetLastError: nil, 156 | xCurrentTimeInt64: nil, 157 | xSetSystemCall: nil, 158 | xGetSystemCall: nil, 159 | xNextSystemCall: nil, 160 | ) 161 | 162 | proc sqlite3_vfs_register(vfs: ptr sqlite3_vfs, makeDflt: cint): cint {.cdecl, importc.} 163 | 164 | proc register*() = 165 | doAssert SQLITE_OK == sqlite3_vfs_register(httpVfs.unsafeAddr, 0) 166 | 167 | -------------------------------------------------------------------------------- /src/wavecorepkg/ed25519.nim: -------------------------------------------------------------------------------- 1 | {.compile: "ed25519/add_scalar.c".} 2 | {.compile: "ed25519/fe.c".} 3 | {.compile: "ed25519/keypair.c".} 4 | {.compile: "ed25519/sc.c".} 5 | {.compile: "ed25519/seed.c".} 6 | {.compile: "ed25519/verify.c".} 7 | {.compile: "ed25519/ge.c".} 8 | {.compile: "ed25519/key_exchange.c".} 9 | {.compile: "ed25519/sha512.c".} 10 | {.compile: "ed25519/sign.c".} 11 | 12 | type 13 | Seed* = array[32, uint8] 14 | PublicKey* = array[32, uint8] 15 | PrivateKey* = array[64, uint8] 16 | Signature* = array[64, uint8] 17 | 18 | proc ed25519_create_seed*(seed: ptr Seed): cint {.importc.} 19 | proc ed25519_create_keypair*(public_key: ptr PublicKey; private_key: ptr PrivateKey; 20 | seed: ptr Seed) {.importc.} 21 | proc ed25519_create_keypair_from_private_key*(public_key: ptr PublicKey; private_key: ptr PrivateKey) {.importc.} 22 | proc ed25519_sign*(signature: ptr Signature; message: cstring; message_len: csize_t; 23 | public_key: ptr PublicKey; private_key: ptr PrivateKey) {.importc.} 24 | proc ed25519_verify*(signature: ptr Signature; message: cstring; message_len: csize_t; 25 | public_key: ptr PublicKey): cint {.importc.} 26 | proc ed25519_add_scalar*(public_key: ptr PublicKey; private_key: ptr PrivateKey; 27 | scalar: pointer) {.importc.} 28 | proc ed25519_key_exchange*(shared_secret: pointer; public_key: ptr PublicKey; 29 | private_key: ptr PrivateKey) {.importc.} 30 | 31 | type 32 | KeyPair* = object 33 | public*: PublicKey 34 | private*: PrivateKey 35 | 36 | proc initKeyPair*(): KeyPair = 37 | var seed: Seed 38 | doAssert 0 == ed25519_create_seed(seed.addr) 39 | var 40 | pub: PublicKey 41 | priv: PrivateKey 42 | ed25519_create_keypair(pub.addr, priv.addr, seed.addr) 43 | result.public = pub 44 | result.private = priv 45 | 46 | proc initKeyPair*(private: PrivateKey): KeyPair = 47 | result.private = private 48 | ed25519_create_keypair_from_private_key(result.public.addr, private.unsafeAddr) 49 | 50 | proc sign*(keys: KeyPair, content: string): Signature = 51 | ed25519_sign(result.addr, content, content.len.csize_t, keys.public.unsafeAddr, keys.private.unsafeAddr) 52 | 53 | proc verify*(public: PublicKey, signature: Signature, content: string): bool = 54 | 1 == ed25519_verify(signature.unsafeAddr, content, content.len.csize_t, public.unsafeAddr) 55 | -------------------------------------------------------------------------------- /src/wavecorepkg/ed25519/add_scalar.c: -------------------------------------------------------------------------------- 1 | #include "ed25519.h" 2 | #include "ge.h" 3 | #include "sc.h" 4 | #include "sha512.h" 5 | 6 | 7 | /* see http://crypto.stackexchange.com/a/6215/4697 */ 8 | void ed25519_add_scalar(unsigned char *public_key, unsigned char *private_key, const unsigned char *scalar) { 9 | const unsigned char SC_1[32] = {1}; /* scalar with value 1 */ 10 | 11 | unsigned char n[32]; 12 | ge_p3 nB; 13 | ge_p1p1 A_p1p1; 14 | ge_p3 A; 15 | ge_p3 public_key_unpacked; 16 | ge_cached T; 17 | 18 | sha512_context hash; 19 | unsigned char hashbuf[64]; 20 | 21 | int i; 22 | 23 | /* copy the scalar and clear highest bit */ 24 | for (i = 0; i < 31; ++i) { 25 | n[i] = scalar[i]; 26 | } 27 | n[31] = scalar[31] & 127; 28 | 29 | /* private key: a = n + t */ 30 | if (private_key) { 31 | sc_muladd(private_key, SC_1, n, private_key); 32 | 33 | // https://github.com/orlp/ed25519/issues/3 34 | sha512_init(&hash); 35 | sha512_update(&hash, private_key + 32, 32); 36 | sha512_update(&hash, scalar, 32); 37 | sha512_final(&hash, hashbuf); 38 | for (i = 0; i < 32; ++i) { 39 | private_key[32 + i] = hashbuf[i]; 40 | } 41 | } 42 | 43 | /* public key: A = nB + T */ 44 | if (public_key) { 45 | /* if we know the private key we don't need a point addition, which is faster */ 46 | /* using a "timing attack" you could find out wether or not we know the private 47 | key, but this information seems rather useless - if this is important pass 48 | public_key and private_key seperately in 2 function calls */ 49 | if (private_key) { 50 | ge_scalarmult_base(&A, private_key); 51 | } else { 52 | /* unpack public key into T */ 53 | ge_frombytes_negate_vartime(&public_key_unpacked, public_key); 54 | fe_neg(public_key_unpacked.X, public_key_unpacked.X); /* undo negate */ 55 | fe_neg(public_key_unpacked.T, public_key_unpacked.T); /* undo negate */ 56 | ge_p3_to_cached(&T, &public_key_unpacked); 57 | 58 | /* calculate n*B */ 59 | ge_scalarmult_base(&nB, n); 60 | 61 | /* A = n*B + T */ 62 | ge_add(&A_p1p1, &nB, &T); 63 | ge_p1p1_to_p3(&A, &A_p1p1); 64 | } 65 | 66 | /* pack public key */ 67 | ge_p3_tobytes(public_key, &A); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/wavecorepkg/ed25519/ed25519.h: -------------------------------------------------------------------------------- 1 | #ifndef ED25519_H 2 | #define ED25519_H 3 | 4 | #include 5 | 6 | #if defined(_WIN32) 7 | #if defined(ED25519_BUILD_DLL) 8 | #define ED25519_DECLSPEC __declspec(dllexport) 9 | #elif defined(ED25519_DLL) 10 | #define ED25519_DECLSPEC __declspec(dllimport) 11 | #else 12 | #define ED25519_DECLSPEC 13 | #endif 14 | #else 15 | #define ED25519_DECLSPEC 16 | #endif 17 | 18 | 19 | #ifdef __cplusplus 20 | extern "C" { 21 | #endif 22 | 23 | #ifndef ED25519_NO_SEED 24 | int ED25519_DECLSPEC ed25519_create_seed(unsigned char *seed); 25 | #endif 26 | 27 | void ED25519_DECLSPEC ed25519_create_keypair(unsigned char *public_key, unsigned char *private_key, const unsigned char *seed); 28 | void ED25519_DECLSPEC ed25519_sign(unsigned char *signature, const unsigned char *message, size_t message_len, const unsigned char *public_key, const unsigned char *private_key); 29 | int ED25519_DECLSPEC ed25519_verify(const unsigned char *signature, const unsigned char *message, size_t message_len, const unsigned char *public_key); 30 | void ED25519_DECLSPEC ed25519_add_scalar(unsigned char *public_key, unsigned char *private_key, const unsigned char *scalar); 31 | void ED25519_DECLSPEC ed25519_key_exchange(unsigned char *shared_secret, const unsigned char *public_key, const unsigned char *private_key); 32 | 33 | 34 | #ifdef __cplusplus 35 | } 36 | #endif 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /src/wavecorepkg/ed25519/fe.h: -------------------------------------------------------------------------------- 1 | #ifndef FE_H 2 | #define FE_H 3 | 4 | #include "fixedint.h" 5 | 6 | 7 | /* 8 | fe means field element. 9 | Here the field is \Z/(2^255-19). 10 | An element t, entries t[0]...t[9], represents the integer 11 | t[0]+2^26 t[1]+2^51 t[2]+2^77 t[3]+2^102 t[4]+...+2^230 t[9]. 12 | Bounds on each t[i] vary depending on context. 13 | */ 14 | 15 | 16 | typedef int32_t fe[10]; 17 | 18 | 19 | void fe_0(fe h); 20 | void fe_1(fe h); 21 | 22 | void fe_frombytes(fe h, const unsigned char *s); 23 | void fe_tobytes(unsigned char *s, const fe h); 24 | 25 | void fe_copy(fe h, const fe f); 26 | int fe_isnegative(const fe f); 27 | int fe_isnonzero(const fe f); 28 | void fe_cmov(fe f, const fe g, unsigned int b); 29 | void fe_cswap(fe f, fe g, unsigned int b); 30 | 31 | void fe_neg(fe h, const fe f); 32 | void fe_add(fe h, const fe f, const fe g); 33 | void fe_invert(fe out, const fe z); 34 | void fe_sq(fe h, const fe f); 35 | void fe_sq2(fe h, const fe f); 36 | void fe_mul(fe h, const fe f, const fe g); 37 | void fe_mul121666(fe h, fe f); 38 | void fe_pow22523(fe out, const fe z); 39 | void fe_sub(fe h, const fe f, const fe g); 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /src/wavecorepkg/ed25519/fixedint.h: -------------------------------------------------------------------------------- 1 | /* 2 | Portable header to provide the 32 and 64 bits type. 3 | 4 | Not a compatible replacement for , do not blindly use it as such. 5 | */ 6 | 7 | #if ((defined(__STDC__) && __STDC__ && __STDC_VERSION__ >= 199901L) || (defined(__WATCOMC__) && (defined(_STDINT_H_INCLUDED) || __WATCOMC__ >= 1250)) || (defined(__GNUC__) && (defined(_STDINT_H) || defined(_STDINT_H_) || defined(__UINT_FAST64_TYPE__)) )) && !defined(FIXEDINT_H_INCLUDED) 8 | #include 9 | #define FIXEDINT_H_INCLUDED 10 | 11 | #if defined(__WATCOMC__) && __WATCOMC__ >= 1250 && !defined(UINT64_C) 12 | #include 13 | #define UINT64_C(x) (x + (UINT64_MAX - UINT64_MAX)) 14 | #endif 15 | #endif 16 | 17 | 18 | #ifndef FIXEDINT_H_INCLUDED 19 | #define FIXEDINT_H_INCLUDED 20 | 21 | #include 22 | 23 | /* (u)int32_t */ 24 | #ifndef uint32_t 25 | #if (ULONG_MAX == 0xffffffffUL) 26 | typedef unsigned long uint32_t; 27 | #elif (UINT_MAX == 0xffffffffUL) 28 | typedef unsigned int uint32_t; 29 | #elif (USHRT_MAX == 0xffffffffUL) 30 | typedef unsigned short uint32_t; 31 | #endif 32 | #endif 33 | 34 | 35 | #ifndef int32_t 36 | #if (LONG_MAX == 0x7fffffffL) 37 | typedef signed long int32_t; 38 | #elif (INT_MAX == 0x7fffffffL) 39 | typedef signed int int32_t; 40 | #elif (SHRT_MAX == 0x7fffffffL) 41 | typedef signed short int32_t; 42 | #endif 43 | #endif 44 | 45 | 46 | /* (u)int64_t */ 47 | #if (defined(__STDC__) && defined(__STDC_VERSION__) && __STDC__ && __STDC_VERSION__ >= 199901L) 48 | typedef long long int64_t; 49 | typedef unsigned long long uint64_t; 50 | 51 | #define UINT64_C(v) v ##ULL 52 | #define INT64_C(v) v ##LL 53 | #elif defined(__GNUC__) 54 | __extension__ typedef long long int64_t; 55 | __extension__ typedef unsigned long long uint64_t; 56 | 57 | #define UINT64_C(v) v ##ULL 58 | #define INT64_C(v) v ##LL 59 | #elif defined(__MWERKS__) || defined(__SUNPRO_C) || defined(__SUNPRO_CC) || defined(__APPLE_CC__) || defined(_LONG_LONG) || defined(_CRAYC) 60 | typedef long long int64_t; 61 | typedef unsigned long long uint64_t; 62 | 63 | #define UINT64_C(v) v ##ULL 64 | #define INT64_C(v) v ##LL 65 | #elif (defined(__WATCOMC__) && defined(__WATCOM_INT64__)) || (defined(_MSC_VER) && _INTEGRAL_MAX_BITS >= 64) || (defined(__BORLANDC__) && __BORLANDC__ > 0x460) || defined(__alpha) || defined(__DECC) 66 | typedef __int64 int64_t; 67 | typedef unsigned __int64 uint64_t; 68 | 69 | #define UINT64_C(v) v ##UI64 70 | #define INT64_C(v) v ##I64 71 | #endif 72 | #endif 73 | -------------------------------------------------------------------------------- /src/wavecorepkg/ed25519/ge.c: -------------------------------------------------------------------------------- 1 | #include "ge.h" 2 | #include "precomp_data.h" 3 | 4 | 5 | /* 6 | r = p + q 7 | */ 8 | 9 | void ge_add(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q) { 10 | fe t0; 11 | fe_add(r->X, p->Y, p->X); 12 | fe_sub(r->Y, p->Y, p->X); 13 | fe_mul(r->Z, r->X, q->YplusX); 14 | fe_mul(r->Y, r->Y, q->YminusX); 15 | fe_mul(r->T, q->T2d, p->T); 16 | fe_mul(r->X, p->Z, q->Z); 17 | fe_add(t0, r->X, r->X); 18 | fe_sub(r->X, r->Z, r->Y); 19 | fe_add(r->Y, r->Z, r->Y); 20 | fe_add(r->Z, t0, r->T); 21 | fe_sub(r->T, t0, r->T); 22 | } 23 | 24 | 25 | static void slide(signed char *r, const unsigned char *a) { 26 | int i; 27 | int b; 28 | int k; 29 | 30 | for (i = 0; i < 256; ++i) { 31 | r[i] = 1 & (a[i >> 3] >> (i & 7)); 32 | } 33 | 34 | for (i = 0; i < 256; ++i) 35 | if (r[i]) { 36 | for (b = 1; b <= 6 && i + b < 256; ++b) { 37 | if (r[i + b]) { 38 | if (r[i] + (r[i + b] << b) <= 15) { 39 | r[i] += r[i + b] << b; 40 | r[i + b] = 0; 41 | } else if (r[i] - (r[i + b] << b) >= -15) { 42 | r[i] -= r[i + b] << b; 43 | 44 | for (k = i + b; k < 256; ++k) { 45 | if (!r[k]) { 46 | r[k] = 1; 47 | break; 48 | } 49 | 50 | r[k] = 0; 51 | } 52 | } else { 53 | break; 54 | } 55 | } 56 | } 57 | } 58 | } 59 | 60 | /* 61 | r = a * A + b * B 62 | where a = a[0]+256*a[1]+...+256^31 a[31]. 63 | and b = b[0]+256*b[1]+...+256^31 b[31]. 64 | B is the Ed25519 base point (x,4/5) with x positive. 65 | */ 66 | 67 | void ge_double_scalarmult_vartime(ge_p2 *r, const unsigned char *a, const ge_p3 *A, const unsigned char *b) { 68 | signed char aslide[256]; 69 | signed char bslide[256]; 70 | ge_cached Ai[8]; /* A,3A,5A,7A,9A,11A,13A,15A */ 71 | ge_p1p1 t; 72 | ge_p3 u; 73 | ge_p3 A2; 74 | int i; 75 | slide(aslide, a); 76 | slide(bslide, b); 77 | ge_p3_to_cached(&Ai[0], A); 78 | ge_p3_dbl(&t, A); 79 | ge_p1p1_to_p3(&A2, &t); 80 | ge_add(&t, &A2, &Ai[0]); 81 | ge_p1p1_to_p3(&u, &t); 82 | ge_p3_to_cached(&Ai[1], &u); 83 | ge_add(&t, &A2, &Ai[1]); 84 | ge_p1p1_to_p3(&u, &t); 85 | ge_p3_to_cached(&Ai[2], &u); 86 | ge_add(&t, &A2, &Ai[2]); 87 | ge_p1p1_to_p3(&u, &t); 88 | ge_p3_to_cached(&Ai[3], &u); 89 | ge_add(&t, &A2, &Ai[3]); 90 | ge_p1p1_to_p3(&u, &t); 91 | ge_p3_to_cached(&Ai[4], &u); 92 | ge_add(&t, &A2, &Ai[4]); 93 | ge_p1p1_to_p3(&u, &t); 94 | ge_p3_to_cached(&Ai[5], &u); 95 | ge_add(&t, &A2, &Ai[5]); 96 | ge_p1p1_to_p3(&u, &t); 97 | ge_p3_to_cached(&Ai[6], &u); 98 | ge_add(&t, &A2, &Ai[6]); 99 | ge_p1p1_to_p3(&u, &t); 100 | ge_p3_to_cached(&Ai[7], &u); 101 | ge_p2_0(r); 102 | 103 | for (i = 255; i >= 0; --i) { 104 | if (aslide[i] || bslide[i]) { 105 | break; 106 | } 107 | } 108 | 109 | for (; i >= 0; --i) { 110 | ge_p2_dbl(&t, r); 111 | 112 | if (aslide[i] > 0) { 113 | ge_p1p1_to_p3(&u, &t); 114 | ge_add(&t, &u, &Ai[aslide[i] / 2]); 115 | } else if (aslide[i] < 0) { 116 | ge_p1p1_to_p3(&u, &t); 117 | ge_sub(&t, &u, &Ai[(-aslide[i]) / 2]); 118 | } 119 | 120 | if (bslide[i] > 0) { 121 | ge_p1p1_to_p3(&u, &t); 122 | ge_madd(&t, &u, &Bi[bslide[i] / 2]); 123 | } else if (bslide[i] < 0) { 124 | ge_p1p1_to_p3(&u, &t); 125 | ge_msub(&t, &u, &Bi[(-bslide[i]) / 2]); 126 | } 127 | 128 | ge_p1p1_to_p2(r, &t); 129 | } 130 | } 131 | 132 | 133 | static const fe d = { 134 | -10913610, 13857413, -15372611, 6949391, 114729, -8787816, -6275908, -3247719, -18696448, -12055116 135 | }; 136 | 137 | static const fe sqrtm1 = { 138 | -32595792, -7943725, 9377950, 3500415, 12389472, -272473, -25146209, -2005654, 326686, 11406482 139 | }; 140 | 141 | int ge_frombytes_negate_vartime(ge_p3 *h, const unsigned char *s) { 142 | fe u; 143 | fe v; 144 | fe v3; 145 | fe vxx; 146 | fe check; 147 | fe_frombytes(h->Y, s); 148 | fe_1(h->Z); 149 | fe_sq(u, h->Y); 150 | fe_mul(v, u, d); 151 | fe_sub(u, u, h->Z); /* u = y^2-1 */ 152 | fe_add(v, v, h->Z); /* v = dy^2+1 */ 153 | fe_sq(v3, v); 154 | fe_mul(v3, v3, v); /* v3 = v^3 */ 155 | fe_sq(h->X, v3); 156 | fe_mul(h->X, h->X, v); 157 | fe_mul(h->X, h->X, u); /* x = uv^7 */ 158 | fe_pow22523(h->X, h->X); /* x = (uv^7)^((q-5)/8) */ 159 | fe_mul(h->X, h->X, v3); 160 | fe_mul(h->X, h->X, u); /* x = uv^3(uv^7)^((q-5)/8) */ 161 | fe_sq(vxx, h->X); 162 | fe_mul(vxx, vxx, v); 163 | fe_sub(check, vxx, u); /* vx^2-u */ 164 | 165 | if (fe_isnonzero(check)) { 166 | fe_add(check, vxx, u); /* vx^2+u */ 167 | 168 | if (fe_isnonzero(check)) { 169 | return -1; 170 | } 171 | 172 | fe_mul(h->X, h->X, sqrtm1); 173 | } 174 | 175 | if (fe_isnegative(h->X) == (s[31] >> 7)) { 176 | fe_neg(h->X, h->X); 177 | } 178 | 179 | fe_mul(h->T, h->X, h->Y); 180 | return 0; 181 | } 182 | 183 | 184 | /* 185 | r = p + q 186 | */ 187 | 188 | void ge_madd(ge_p1p1 *r, const ge_p3 *p, const ge_precomp *q) { 189 | fe t0; 190 | fe_add(r->X, p->Y, p->X); 191 | fe_sub(r->Y, p->Y, p->X); 192 | fe_mul(r->Z, r->X, q->yplusx); 193 | fe_mul(r->Y, r->Y, q->yminusx); 194 | fe_mul(r->T, q->xy2d, p->T); 195 | fe_add(t0, p->Z, p->Z); 196 | fe_sub(r->X, r->Z, r->Y); 197 | fe_add(r->Y, r->Z, r->Y); 198 | fe_add(r->Z, t0, r->T); 199 | fe_sub(r->T, t0, r->T); 200 | } 201 | 202 | 203 | /* 204 | r = p - q 205 | */ 206 | 207 | void ge_msub(ge_p1p1 *r, const ge_p3 *p, const ge_precomp *q) { 208 | fe t0; 209 | 210 | fe_add(r->X, p->Y, p->X); 211 | fe_sub(r->Y, p->Y, p->X); 212 | fe_mul(r->Z, r->X, q->yminusx); 213 | fe_mul(r->Y, r->Y, q->yplusx); 214 | fe_mul(r->T, q->xy2d, p->T); 215 | fe_add(t0, p->Z, p->Z); 216 | fe_sub(r->X, r->Z, r->Y); 217 | fe_add(r->Y, r->Z, r->Y); 218 | fe_sub(r->Z, t0, r->T); 219 | fe_add(r->T, t0, r->T); 220 | } 221 | 222 | 223 | /* 224 | r = p 225 | */ 226 | 227 | void ge_p1p1_to_p2(ge_p2 *r, const ge_p1p1 *p) { 228 | fe_mul(r->X, p->X, p->T); 229 | fe_mul(r->Y, p->Y, p->Z); 230 | fe_mul(r->Z, p->Z, p->T); 231 | } 232 | 233 | 234 | 235 | /* 236 | r = p 237 | */ 238 | 239 | void ge_p1p1_to_p3(ge_p3 *r, const ge_p1p1 *p) { 240 | fe_mul(r->X, p->X, p->T); 241 | fe_mul(r->Y, p->Y, p->Z); 242 | fe_mul(r->Z, p->Z, p->T); 243 | fe_mul(r->T, p->X, p->Y); 244 | } 245 | 246 | 247 | void ge_p2_0(ge_p2 *h) { 248 | fe_0(h->X); 249 | fe_1(h->Y); 250 | fe_1(h->Z); 251 | } 252 | 253 | 254 | 255 | /* 256 | r = 2 * p 257 | */ 258 | 259 | void ge_p2_dbl(ge_p1p1 *r, const ge_p2 *p) { 260 | fe t0; 261 | 262 | fe_sq(r->X, p->X); 263 | fe_sq(r->Z, p->Y); 264 | fe_sq2(r->T, p->Z); 265 | fe_add(r->Y, p->X, p->Y); 266 | fe_sq(t0, r->Y); 267 | fe_add(r->Y, r->Z, r->X); 268 | fe_sub(r->Z, r->Z, r->X); 269 | fe_sub(r->X, t0, r->Y); 270 | fe_sub(r->T, r->T, r->Z); 271 | } 272 | 273 | 274 | void ge_p3_0(ge_p3 *h) { 275 | fe_0(h->X); 276 | fe_1(h->Y); 277 | fe_1(h->Z); 278 | fe_0(h->T); 279 | } 280 | 281 | 282 | /* 283 | r = 2 * p 284 | */ 285 | 286 | void ge_p3_dbl(ge_p1p1 *r, const ge_p3 *p) { 287 | ge_p2 q; 288 | ge_p3_to_p2(&q, p); 289 | ge_p2_dbl(r, &q); 290 | } 291 | 292 | 293 | 294 | /* 295 | r = p 296 | */ 297 | 298 | static const fe d2 = { 299 | -21827239, -5839606, -30745221, 13898782, 229458, 15978800, -12551817, -6495438, 29715968, 9444199 300 | }; 301 | 302 | void ge_p3_to_cached(ge_cached *r, const ge_p3 *p) { 303 | fe_add(r->YplusX, p->Y, p->X); 304 | fe_sub(r->YminusX, p->Y, p->X); 305 | fe_copy(r->Z, p->Z); 306 | fe_mul(r->T2d, p->T, d2); 307 | } 308 | 309 | 310 | /* 311 | r = p 312 | */ 313 | 314 | void ge_p3_to_p2(ge_p2 *r, const ge_p3 *p) { 315 | fe_copy(r->X, p->X); 316 | fe_copy(r->Y, p->Y); 317 | fe_copy(r->Z, p->Z); 318 | } 319 | 320 | 321 | void ge_p3_tobytes(unsigned char *s, const ge_p3 *h) { 322 | fe recip; 323 | fe x; 324 | fe y; 325 | fe_invert(recip, h->Z); 326 | fe_mul(x, h->X, recip); 327 | fe_mul(y, h->Y, recip); 328 | fe_tobytes(s, y); 329 | s[31] ^= fe_isnegative(x) << 7; 330 | } 331 | 332 | 333 | static unsigned char equal(signed char b, signed char c) { 334 | unsigned char ub = b; 335 | unsigned char uc = c; 336 | unsigned char x = ub ^ uc; /* 0: yes; 1..255: no */ 337 | uint64_t y = x; /* 0: yes; 1..255: no */ 338 | y -= 1; /* large: yes; 0..254: no */ 339 | y >>= 63; /* 1: yes; 0: no */ 340 | return (unsigned char) y; 341 | } 342 | 343 | static unsigned char negative(signed char b) { 344 | uint64_t x = b; /* 18446744073709551361..18446744073709551615: yes; 0..255: no */ 345 | x >>= 63; /* 1: yes; 0: no */ 346 | return (unsigned char) x; 347 | } 348 | 349 | static void cmov(ge_precomp *t, const ge_precomp *u, unsigned char b) { 350 | fe_cmov(t->yplusx, u->yplusx, b); 351 | fe_cmov(t->yminusx, u->yminusx, b); 352 | fe_cmov(t->xy2d, u->xy2d, b); 353 | } 354 | 355 | 356 | static void select(ge_precomp *t, int pos, signed char b) { 357 | ge_precomp minust; 358 | unsigned char bnegative = negative(b); 359 | unsigned char babs = b - (((-bnegative) & b) << 1); 360 | fe_1(t->yplusx); 361 | fe_1(t->yminusx); 362 | fe_0(t->xy2d); 363 | cmov(t, &base[pos][0], equal(babs, 1)); 364 | cmov(t, &base[pos][1], equal(babs, 2)); 365 | cmov(t, &base[pos][2], equal(babs, 3)); 366 | cmov(t, &base[pos][3], equal(babs, 4)); 367 | cmov(t, &base[pos][4], equal(babs, 5)); 368 | cmov(t, &base[pos][5], equal(babs, 6)); 369 | cmov(t, &base[pos][6], equal(babs, 7)); 370 | cmov(t, &base[pos][7], equal(babs, 8)); 371 | fe_copy(minust.yplusx, t->yminusx); 372 | fe_copy(minust.yminusx, t->yplusx); 373 | fe_neg(minust.xy2d, t->xy2d); 374 | cmov(t, &minust, bnegative); 375 | } 376 | 377 | /* 378 | h = a * B 379 | where a = a[0]+256*a[1]+...+256^31 a[31] 380 | B is the Ed25519 base point (x,4/5) with x positive. 381 | 382 | Preconditions: 383 | a[31] <= 127 384 | */ 385 | 386 | void ge_scalarmult_base(ge_p3 *h, const unsigned char *a) { 387 | signed char e[64]; 388 | signed char carry; 389 | ge_p1p1 r; 390 | ge_p2 s; 391 | ge_precomp t; 392 | int i; 393 | 394 | for (i = 0; i < 32; ++i) { 395 | e[2 * i + 0] = (a[i] >> 0) & 15; 396 | e[2 * i + 1] = (a[i] >> 4) & 15; 397 | } 398 | 399 | /* each e[i] is between 0 and 15 */ 400 | /* e[63] is between 0 and 7 */ 401 | carry = 0; 402 | 403 | for (i = 0; i < 63; ++i) { 404 | e[i] += carry; 405 | carry = e[i] + 8; 406 | carry >>= 4; 407 | e[i] -= carry << 4; 408 | } 409 | 410 | e[63] += carry; 411 | /* each e[i] is between -8 and 8 */ 412 | ge_p3_0(h); 413 | 414 | for (i = 1; i < 64; i += 2) { 415 | select(&t, i / 2, e[i]); 416 | ge_madd(&r, h, &t); 417 | ge_p1p1_to_p3(h, &r); 418 | } 419 | 420 | ge_p3_dbl(&r, h); 421 | ge_p1p1_to_p2(&s, &r); 422 | ge_p2_dbl(&r, &s); 423 | ge_p1p1_to_p2(&s, &r); 424 | ge_p2_dbl(&r, &s); 425 | ge_p1p1_to_p2(&s, &r); 426 | ge_p2_dbl(&r, &s); 427 | ge_p1p1_to_p3(h, &r); 428 | 429 | for (i = 0; i < 64; i += 2) { 430 | select(&t, i / 2, e[i]); 431 | ge_madd(&r, h, &t); 432 | ge_p1p1_to_p3(h, &r); 433 | } 434 | } 435 | 436 | 437 | /* 438 | r = p - q 439 | */ 440 | 441 | void ge_sub(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q) { 442 | fe t0; 443 | 444 | fe_add(r->X, p->Y, p->X); 445 | fe_sub(r->Y, p->Y, p->X); 446 | fe_mul(r->Z, r->X, q->YminusX); 447 | fe_mul(r->Y, r->Y, q->YplusX); 448 | fe_mul(r->T, q->T2d, p->T); 449 | fe_mul(r->X, p->Z, q->Z); 450 | fe_add(t0, r->X, r->X); 451 | fe_sub(r->X, r->Z, r->Y); 452 | fe_add(r->Y, r->Z, r->Y); 453 | fe_sub(r->Z, t0, r->T); 454 | fe_add(r->T, t0, r->T); 455 | } 456 | 457 | 458 | void ge_tobytes(unsigned char *s, const ge_p2 *h) { 459 | fe recip; 460 | fe x; 461 | fe y; 462 | fe_invert(recip, h->Z); 463 | fe_mul(x, h->X, recip); 464 | fe_mul(y, h->Y, recip); 465 | fe_tobytes(s, y); 466 | s[31] ^= fe_isnegative(x) << 7; 467 | } 468 | -------------------------------------------------------------------------------- /src/wavecorepkg/ed25519/ge.h: -------------------------------------------------------------------------------- 1 | #ifndef GE_H 2 | #define GE_H 3 | 4 | #include "fe.h" 5 | 6 | 7 | /* 8 | ge means group element. 9 | 10 | Here the group is the set of pairs (x,y) of field elements (see fe.h) 11 | satisfying -x^2 + y^2 = 1 + d x^2y^2 12 | where d = -121665/121666. 13 | 14 | Representations: 15 | ge_p2 (projective): (X:Y:Z) satisfying x=X/Z, y=Y/Z 16 | ge_p3 (extended): (X:Y:Z:T) satisfying x=X/Z, y=Y/Z, XY=ZT 17 | ge_p1p1 (completed): ((X:Z),(Y:T)) satisfying x=X/Z, y=Y/T 18 | ge_precomp (Duif): (y+x,y-x,2dxy) 19 | */ 20 | 21 | typedef struct { 22 | fe X; 23 | fe Y; 24 | fe Z; 25 | } ge_p2; 26 | 27 | typedef struct { 28 | fe X; 29 | fe Y; 30 | fe Z; 31 | fe T; 32 | } ge_p3; 33 | 34 | typedef struct { 35 | fe X; 36 | fe Y; 37 | fe Z; 38 | fe T; 39 | } ge_p1p1; 40 | 41 | typedef struct { 42 | fe yplusx; 43 | fe yminusx; 44 | fe xy2d; 45 | } ge_precomp; 46 | 47 | typedef struct { 48 | fe YplusX; 49 | fe YminusX; 50 | fe Z; 51 | fe T2d; 52 | } ge_cached; 53 | 54 | void ge_p3_tobytes(unsigned char *s, const ge_p3 *h); 55 | void ge_tobytes(unsigned char *s, const ge_p2 *h); 56 | int ge_frombytes_negate_vartime(ge_p3 *h, const unsigned char *s); 57 | 58 | void ge_add(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q); 59 | void ge_sub(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q); 60 | void ge_double_scalarmult_vartime(ge_p2 *r, const unsigned char *a, const ge_p3 *A, const unsigned char *b); 61 | void ge_madd(ge_p1p1 *r, const ge_p3 *p, const ge_precomp *q); 62 | void ge_msub(ge_p1p1 *r, const ge_p3 *p, const ge_precomp *q); 63 | void ge_scalarmult_base(ge_p3 *h, const unsigned char *a); 64 | 65 | void ge_p1p1_to_p2(ge_p2 *r, const ge_p1p1 *p); 66 | void ge_p1p1_to_p3(ge_p3 *r, const ge_p1p1 *p); 67 | void ge_p2_0(ge_p2 *h); 68 | void ge_p2_dbl(ge_p1p1 *r, const ge_p2 *p); 69 | void ge_p3_0(ge_p3 *h); 70 | void ge_p3_dbl(ge_p1p1 *r, const ge_p3 *p); 71 | void ge_p3_to_cached(ge_cached *r, const ge_p3 *p); 72 | void ge_p3_to_p2(ge_p2 *r, const ge_p3 *p); 73 | 74 | #endif 75 | -------------------------------------------------------------------------------- /src/wavecorepkg/ed25519/key_exchange.c: -------------------------------------------------------------------------------- 1 | #include "ed25519.h" 2 | #include "fe.h" 3 | 4 | void ed25519_key_exchange(unsigned char *shared_secret, const unsigned char *public_key, const unsigned char *private_key) { 5 | unsigned char e[32]; 6 | unsigned int i; 7 | 8 | fe x1; 9 | fe x2; 10 | fe z2; 11 | fe x3; 12 | fe z3; 13 | fe tmp0; 14 | fe tmp1; 15 | 16 | int pos; 17 | unsigned int swap; 18 | unsigned int b; 19 | 20 | /* copy the private key and make sure it's valid */ 21 | for (i = 0; i < 32; ++i) { 22 | e[i] = private_key[i]; 23 | } 24 | 25 | e[0] &= 248; 26 | e[31] &= 63; 27 | e[31] |= 64; 28 | 29 | /* unpack the public key and convert edwards to montgomery */ 30 | /* due to CodesInChaos: montgomeryX = (edwardsY + 1)*inverse(1 - edwardsY) mod p */ 31 | fe_frombytes(x1, public_key); 32 | fe_1(tmp1); 33 | fe_add(tmp0, x1, tmp1); 34 | fe_sub(tmp1, tmp1, x1); 35 | fe_invert(tmp1, tmp1); 36 | fe_mul(x1, tmp0, tmp1); 37 | 38 | fe_1(x2); 39 | fe_0(z2); 40 | fe_copy(x3, x1); 41 | fe_1(z3); 42 | 43 | swap = 0; 44 | for (pos = 254; pos >= 0; --pos) { 45 | b = e[pos / 8] >> (pos & 7); 46 | b &= 1; 47 | swap ^= b; 48 | fe_cswap(x2, x3, swap); 49 | fe_cswap(z2, z3, swap); 50 | swap = b; 51 | 52 | /* from montgomery.h */ 53 | fe_sub(tmp0, x3, z3); 54 | fe_sub(tmp1, x2, z2); 55 | fe_add(x2, x2, z2); 56 | fe_add(z2, x3, z3); 57 | fe_mul(z3, tmp0, x2); 58 | fe_mul(z2, z2, tmp1); 59 | fe_sq(tmp0, tmp1); 60 | fe_sq(tmp1, x2); 61 | fe_add(x3, z3, z2); 62 | fe_sub(z2, z3, z2); 63 | fe_mul(x2, tmp1, tmp0); 64 | fe_sub(tmp1, tmp1, tmp0); 65 | fe_sq(z2, z2); 66 | fe_mul121666(z3, tmp1); 67 | fe_sq(x3, x3); 68 | fe_add(tmp0, tmp0, z3); 69 | fe_mul(z3, x1, z2); 70 | fe_mul(z2, tmp1, tmp0); 71 | } 72 | 73 | fe_cswap(x2, x3, swap); 74 | fe_cswap(z2, z3, swap); 75 | 76 | fe_invert(z2, z2); 77 | fe_mul(x2, x2, z2); 78 | fe_tobytes(shared_secret, x2); 79 | } 80 | -------------------------------------------------------------------------------- /src/wavecorepkg/ed25519/keypair.c: -------------------------------------------------------------------------------- 1 | #include "ed25519.h" 2 | #include "sha512.h" 3 | #include "ge.h" 4 | 5 | 6 | void ed25519_create_keypair(unsigned char *public_key, unsigned char *private_key, const unsigned char *seed) { 7 | ge_p3 A; 8 | 9 | sha512(seed, 32, private_key); 10 | private_key[0] &= 248; 11 | private_key[31] &= 63; 12 | private_key[31] |= 64; 13 | 14 | ge_scalarmult_base(&A, private_key); 15 | ge_p3_tobytes(public_key, &A); 16 | } 17 | 18 | void ed25519_create_keypair_from_private_key(unsigned char *public_key, unsigned char *private_key) { 19 | ge_p3 A; 20 | ge_scalarmult_base(&A, private_key); 21 | ge_p3_tobytes(public_key, &A); 22 | } 23 | -------------------------------------------------------------------------------- /src/wavecorepkg/ed25519/license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Orson Peters 2 | 3 | This software is provided 'as-is', without any express or implied warranty. In no event will the 4 | authors be held liable for any damages arising from the use of this software. 5 | 6 | Permission is granted to anyone to use this software for any purpose, including commercial 7 | applications, and to alter it and redistribute it freely, subject to the following restrictions: 8 | 9 | 1. The origin of this software must not be misrepresented; you must not claim that you wrote the 10 | original software. If you use this software in a product, an acknowledgment in the product 11 | documentation would be appreciated but is not required. 12 | 13 | 2. Altered source versions must be plainly marked as such, and must not be misrepresented as 14 | being the original software. 15 | 16 | 3. This notice may not be removed or altered from any source distribution. 17 | -------------------------------------------------------------------------------- /src/wavecorepkg/ed25519/sc.c: -------------------------------------------------------------------------------- 1 | #include "fixedint.h" 2 | #include "sc.h" 3 | 4 | static uint64_t load_3(const unsigned char *in) { 5 | uint64_t result; 6 | 7 | result = (uint64_t) in[0]; 8 | result |= ((uint64_t) in[1]) << 8; 9 | result |= ((uint64_t) in[2]) << 16; 10 | 11 | return result; 12 | } 13 | 14 | static uint64_t load_4(const unsigned char *in) { 15 | uint64_t result; 16 | 17 | result = (uint64_t) in[0]; 18 | result |= ((uint64_t) in[1]) << 8; 19 | result |= ((uint64_t) in[2]) << 16; 20 | result |= ((uint64_t) in[3]) << 24; 21 | 22 | return result; 23 | } 24 | 25 | /* 26 | Input: 27 | s[0]+256*s[1]+...+256^63*s[63] = s 28 | 29 | Output: 30 | s[0]+256*s[1]+...+256^31*s[31] = s mod l 31 | where l = 2^252 + 27742317777372353535851937790883648493. 32 | Overwrites s in place. 33 | */ 34 | 35 | void sc_reduce(unsigned char *s) { 36 | int64_t s0 = 2097151 & load_3(s); 37 | int64_t s1 = 2097151 & (load_4(s + 2) >> 5); 38 | int64_t s2 = 2097151 & (load_3(s + 5) >> 2); 39 | int64_t s3 = 2097151 & (load_4(s + 7) >> 7); 40 | int64_t s4 = 2097151 & (load_4(s + 10) >> 4); 41 | int64_t s5 = 2097151 & (load_3(s + 13) >> 1); 42 | int64_t s6 = 2097151 & (load_4(s + 15) >> 6); 43 | int64_t s7 = 2097151 & (load_3(s + 18) >> 3); 44 | int64_t s8 = 2097151 & load_3(s + 21); 45 | int64_t s9 = 2097151 & (load_4(s + 23) >> 5); 46 | int64_t s10 = 2097151 & (load_3(s + 26) >> 2); 47 | int64_t s11 = 2097151 & (load_4(s + 28) >> 7); 48 | int64_t s12 = 2097151 & (load_4(s + 31) >> 4); 49 | int64_t s13 = 2097151 & (load_3(s + 34) >> 1); 50 | int64_t s14 = 2097151 & (load_4(s + 36) >> 6); 51 | int64_t s15 = 2097151 & (load_3(s + 39) >> 3); 52 | int64_t s16 = 2097151 & load_3(s + 42); 53 | int64_t s17 = 2097151 & (load_4(s + 44) >> 5); 54 | int64_t s18 = 2097151 & (load_3(s + 47) >> 2); 55 | int64_t s19 = 2097151 & (load_4(s + 49) >> 7); 56 | int64_t s20 = 2097151 & (load_4(s + 52) >> 4); 57 | int64_t s21 = 2097151 & (load_3(s + 55) >> 1); 58 | int64_t s22 = 2097151 & (load_4(s + 57) >> 6); 59 | int64_t s23 = (load_4(s + 60) >> 3); 60 | int64_t carry0; 61 | int64_t carry1; 62 | int64_t carry2; 63 | int64_t carry3; 64 | int64_t carry4; 65 | int64_t carry5; 66 | int64_t carry6; 67 | int64_t carry7; 68 | int64_t carry8; 69 | int64_t carry9; 70 | int64_t carry10; 71 | int64_t carry11; 72 | int64_t carry12; 73 | int64_t carry13; 74 | int64_t carry14; 75 | int64_t carry15; 76 | int64_t carry16; 77 | 78 | s11 += s23 * 666643; 79 | s12 += s23 * 470296; 80 | s13 += s23 * 654183; 81 | s14 -= s23 * 997805; 82 | s15 += s23 * 136657; 83 | s16 -= s23 * 683901; 84 | s23 = 0; 85 | s10 += s22 * 666643; 86 | s11 += s22 * 470296; 87 | s12 += s22 * 654183; 88 | s13 -= s22 * 997805; 89 | s14 += s22 * 136657; 90 | s15 -= s22 * 683901; 91 | s22 = 0; 92 | s9 += s21 * 666643; 93 | s10 += s21 * 470296; 94 | s11 += s21 * 654183; 95 | s12 -= s21 * 997805; 96 | s13 += s21 * 136657; 97 | s14 -= s21 * 683901; 98 | s21 = 0; 99 | s8 += s20 * 666643; 100 | s9 += s20 * 470296; 101 | s10 += s20 * 654183; 102 | s11 -= s20 * 997805; 103 | s12 += s20 * 136657; 104 | s13 -= s20 * 683901; 105 | s20 = 0; 106 | s7 += s19 * 666643; 107 | s8 += s19 * 470296; 108 | s9 += s19 * 654183; 109 | s10 -= s19 * 997805; 110 | s11 += s19 * 136657; 111 | s12 -= s19 * 683901; 112 | s19 = 0; 113 | s6 += s18 * 666643; 114 | s7 += s18 * 470296; 115 | s8 += s18 * 654183; 116 | s9 -= s18 * 997805; 117 | s10 += s18 * 136657; 118 | s11 -= s18 * 683901; 119 | s18 = 0; 120 | carry6 = (s6 + (1 << 20)) >> 21; 121 | s7 += carry6; 122 | s6 -= carry6 << 21; 123 | carry8 = (s8 + (1 << 20)) >> 21; 124 | s9 += carry8; 125 | s8 -= carry8 << 21; 126 | carry10 = (s10 + (1 << 20)) >> 21; 127 | s11 += carry10; 128 | s10 -= carry10 << 21; 129 | carry12 = (s12 + (1 << 20)) >> 21; 130 | s13 += carry12; 131 | s12 -= carry12 << 21; 132 | carry14 = (s14 + (1 << 20)) >> 21; 133 | s15 += carry14; 134 | s14 -= carry14 << 21; 135 | carry16 = (s16 + (1 << 20)) >> 21; 136 | s17 += carry16; 137 | s16 -= carry16 << 21; 138 | carry7 = (s7 + (1 << 20)) >> 21; 139 | s8 += carry7; 140 | s7 -= carry7 << 21; 141 | carry9 = (s9 + (1 << 20)) >> 21; 142 | s10 += carry9; 143 | s9 -= carry9 << 21; 144 | carry11 = (s11 + (1 << 20)) >> 21; 145 | s12 += carry11; 146 | s11 -= carry11 << 21; 147 | carry13 = (s13 + (1 << 20)) >> 21; 148 | s14 += carry13; 149 | s13 -= carry13 << 21; 150 | carry15 = (s15 + (1 << 20)) >> 21; 151 | s16 += carry15; 152 | s15 -= carry15 << 21; 153 | s5 += s17 * 666643; 154 | s6 += s17 * 470296; 155 | s7 += s17 * 654183; 156 | s8 -= s17 * 997805; 157 | s9 += s17 * 136657; 158 | s10 -= s17 * 683901; 159 | s17 = 0; 160 | s4 += s16 * 666643; 161 | s5 += s16 * 470296; 162 | s6 += s16 * 654183; 163 | s7 -= s16 * 997805; 164 | s8 += s16 * 136657; 165 | s9 -= s16 * 683901; 166 | s16 = 0; 167 | s3 += s15 * 666643; 168 | s4 += s15 * 470296; 169 | s5 += s15 * 654183; 170 | s6 -= s15 * 997805; 171 | s7 += s15 * 136657; 172 | s8 -= s15 * 683901; 173 | s15 = 0; 174 | s2 += s14 * 666643; 175 | s3 += s14 * 470296; 176 | s4 += s14 * 654183; 177 | s5 -= s14 * 997805; 178 | s6 += s14 * 136657; 179 | s7 -= s14 * 683901; 180 | s14 = 0; 181 | s1 += s13 * 666643; 182 | s2 += s13 * 470296; 183 | s3 += s13 * 654183; 184 | s4 -= s13 * 997805; 185 | s5 += s13 * 136657; 186 | s6 -= s13 * 683901; 187 | s13 = 0; 188 | s0 += s12 * 666643; 189 | s1 += s12 * 470296; 190 | s2 += s12 * 654183; 191 | s3 -= s12 * 997805; 192 | s4 += s12 * 136657; 193 | s5 -= s12 * 683901; 194 | s12 = 0; 195 | carry0 = (s0 + (1 << 20)) >> 21; 196 | s1 += carry0; 197 | s0 -= carry0 << 21; 198 | carry2 = (s2 + (1 << 20)) >> 21; 199 | s3 += carry2; 200 | s2 -= carry2 << 21; 201 | carry4 = (s4 + (1 << 20)) >> 21; 202 | s5 += carry4; 203 | s4 -= carry4 << 21; 204 | carry6 = (s6 + (1 << 20)) >> 21; 205 | s7 += carry6; 206 | s6 -= carry6 << 21; 207 | carry8 = (s8 + (1 << 20)) >> 21; 208 | s9 += carry8; 209 | s8 -= carry8 << 21; 210 | carry10 = (s10 + (1 << 20)) >> 21; 211 | s11 += carry10; 212 | s10 -= carry10 << 21; 213 | carry1 = (s1 + (1 << 20)) >> 21; 214 | s2 += carry1; 215 | s1 -= carry1 << 21; 216 | carry3 = (s3 + (1 << 20)) >> 21; 217 | s4 += carry3; 218 | s3 -= carry3 << 21; 219 | carry5 = (s5 + (1 << 20)) >> 21; 220 | s6 += carry5; 221 | s5 -= carry5 << 21; 222 | carry7 = (s7 + (1 << 20)) >> 21; 223 | s8 += carry7; 224 | s7 -= carry7 << 21; 225 | carry9 = (s9 + (1 << 20)) >> 21; 226 | s10 += carry9; 227 | s9 -= carry9 << 21; 228 | carry11 = (s11 + (1 << 20)) >> 21; 229 | s12 += carry11; 230 | s11 -= carry11 << 21; 231 | s0 += s12 * 666643; 232 | s1 += s12 * 470296; 233 | s2 += s12 * 654183; 234 | s3 -= s12 * 997805; 235 | s4 += s12 * 136657; 236 | s5 -= s12 * 683901; 237 | s12 = 0; 238 | carry0 = s0 >> 21; 239 | s1 += carry0; 240 | s0 -= carry0 << 21; 241 | carry1 = s1 >> 21; 242 | s2 += carry1; 243 | s1 -= carry1 << 21; 244 | carry2 = s2 >> 21; 245 | s3 += carry2; 246 | s2 -= carry2 << 21; 247 | carry3 = s3 >> 21; 248 | s4 += carry3; 249 | s3 -= carry3 << 21; 250 | carry4 = s4 >> 21; 251 | s5 += carry4; 252 | s4 -= carry4 << 21; 253 | carry5 = s5 >> 21; 254 | s6 += carry5; 255 | s5 -= carry5 << 21; 256 | carry6 = s6 >> 21; 257 | s7 += carry6; 258 | s6 -= carry6 << 21; 259 | carry7 = s7 >> 21; 260 | s8 += carry7; 261 | s7 -= carry7 << 21; 262 | carry8 = s8 >> 21; 263 | s9 += carry8; 264 | s8 -= carry8 << 21; 265 | carry9 = s9 >> 21; 266 | s10 += carry9; 267 | s9 -= carry9 << 21; 268 | carry10 = s10 >> 21; 269 | s11 += carry10; 270 | s10 -= carry10 << 21; 271 | carry11 = s11 >> 21; 272 | s12 += carry11; 273 | s11 -= carry11 << 21; 274 | s0 += s12 * 666643; 275 | s1 += s12 * 470296; 276 | s2 += s12 * 654183; 277 | s3 -= s12 * 997805; 278 | s4 += s12 * 136657; 279 | s5 -= s12 * 683901; 280 | s12 = 0; 281 | carry0 = s0 >> 21; 282 | s1 += carry0; 283 | s0 -= carry0 << 21; 284 | carry1 = s1 >> 21; 285 | s2 += carry1; 286 | s1 -= carry1 << 21; 287 | carry2 = s2 >> 21; 288 | s3 += carry2; 289 | s2 -= carry2 << 21; 290 | carry3 = s3 >> 21; 291 | s4 += carry3; 292 | s3 -= carry3 << 21; 293 | carry4 = s4 >> 21; 294 | s5 += carry4; 295 | s4 -= carry4 << 21; 296 | carry5 = s5 >> 21; 297 | s6 += carry5; 298 | s5 -= carry5 << 21; 299 | carry6 = s6 >> 21; 300 | s7 += carry6; 301 | s6 -= carry6 << 21; 302 | carry7 = s7 >> 21; 303 | s8 += carry7; 304 | s7 -= carry7 << 21; 305 | carry8 = s8 >> 21; 306 | s9 += carry8; 307 | s8 -= carry8 << 21; 308 | carry9 = s9 >> 21; 309 | s10 += carry9; 310 | s9 -= carry9 << 21; 311 | carry10 = s10 >> 21; 312 | s11 += carry10; 313 | s10 -= carry10 << 21; 314 | 315 | s[0] = (unsigned char) (s0 >> 0); 316 | s[1] = (unsigned char) (s0 >> 8); 317 | s[2] = (unsigned char) ((s0 >> 16) | (s1 << 5)); 318 | s[3] = (unsigned char) (s1 >> 3); 319 | s[4] = (unsigned char) (s1 >> 11); 320 | s[5] = (unsigned char) ((s1 >> 19) | (s2 << 2)); 321 | s[6] = (unsigned char) (s2 >> 6); 322 | s[7] = (unsigned char) ((s2 >> 14) | (s3 << 7)); 323 | s[8] = (unsigned char) (s3 >> 1); 324 | s[9] = (unsigned char) (s3 >> 9); 325 | s[10] = (unsigned char) ((s3 >> 17) | (s4 << 4)); 326 | s[11] = (unsigned char) (s4 >> 4); 327 | s[12] = (unsigned char) (s4 >> 12); 328 | s[13] = (unsigned char) ((s4 >> 20) | (s5 << 1)); 329 | s[14] = (unsigned char) (s5 >> 7); 330 | s[15] = (unsigned char) ((s5 >> 15) | (s6 << 6)); 331 | s[16] = (unsigned char) (s6 >> 2); 332 | s[17] = (unsigned char) (s6 >> 10); 333 | s[18] = (unsigned char) ((s6 >> 18) | (s7 << 3)); 334 | s[19] = (unsigned char) (s7 >> 5); 335 | s[20] = (unsigned char) (s7 >> 13); 336 | s[21] = (unsigned char) (s8 >> 0); 337 | s[22] = (unsigned char) (s8 >> 8); 338 | s[23] = (unsigned char) ((s8 >> 16) | (s9 << 5)); 339 | s[24] = (unsigned char) (s9 >> 3); 340 | s[25] = (unsigned char) (s9 >> 11); 341 | s[26] = (unsigned char) ((s9 >> 19) | (s10 << 2)); 342 | s[27] = (unsigned char) (s10 >> 6); 343 | s[28] = (unsigned char) ((s10 >> 14) | (s11 << 7)); 344 | s[29] = (unsigned char) (s11 >> 1); 345 | s[30] = (unsigned char) (s11 >> 9); 346 | s[31] = (unsigned char) (s11 >> 17); 347 | } 348 | 349 | 350 | 351 | /* 352 | Input: 353 | a[0]+256*a[1]+...+256^31*a[31] = a 354 | b[0]+256*b[1]+...+256^31*b[31] = b 355 | c[0]+256*c[1]+...+256^31*c[31] = c 356 | 357 | Output: 358 | s[0]+256*s[1]+...+256^31*s[31] = (ab+c) mod l 359 | where l = 2^252 + 27742317777372353535851937790883648493. 360 | */ 361 | 362 | void sc_muladd(unsigned char *s, const unsigned char *a, const unsigned char *b, const unsigned char *c) { 363 | int64_t a0 = 2097151 & load_3(a); 364 | int64_t a1 = 2097151 & (load_4(a + 2) >> 5); 365 | int64_t a2 = 2097151 & (load_3(a + 5) >> 2); 366 | int64_t a3 = 2097151 & (load_4(a + 7) >> 7); 367 | int64_t a4 = 2097151 & (load_4(a + 10) >> 4); 368 | int64_t a5 = 2097151 & (load_3(a + 13) >> 1); 369 | int64_t a6 = 2097151 & (load_4(a + 15) >> 6); 370 | int64_t a7 = 2097151 & (load_3(a + 18) >> 3); 371 | int64_t a8 = 2097151 & load_3(a + 21); 372 | int64_t a9 = 2097151 & (load_4(a + 23) >> 5); 373 | int64_t a10 = 2097151 & (load_3(a + 26) >> 2); 374 | int64_t a11 = (load_4(a + 28) >> 7); 375 | int64_t b0 = 2097151 & load_3(b); 376 | int64_t b1 = 2097151 & (load_4(b + 2) >> 5); 377 | int64_t b2 = 2097151 & (load_3(b + 5) >> 2); 378 | int64_t b3 = 2097151 & (load_4(b + 7) >> 7); 379 | int64_t b4 = 2097151 & (load_4(b + 10) >> 4); 380 | int64_t b5 = 2097151 & (load_3(b + 13) >> 1); 381 | int64_t b6 = 2097151 & (load_4(b + 15) >> 6); 382 | int64_t b7 = 2097151 & (load_3(b + 18) >> 3); 383 | int64_t b8 = 2097151 & load_3(b + 21); 384 | int64_t b9 = 2097151 & (load_4(b + 23) >> 5); 385 | int64_t b10 = 2097151 & (load_3(b + 26) >> 2); 386 | int64_t b11 = (load_4(b + 28) >> 7); 387 | int64_t c0 = 2097151 & load_3(c); 388 | int64_t c1 = 2097151 & (load_4(c + 2) >> 5); 389 | int64_t c2 = 2097151 & (load_3(c + 5) >> 2); 390 | int64_t c3 = 2097151 & (load_4(c + 7) >> 7); 391 | int64_t c4 = 2097151 & (load_4(c + 10) >> 4); 392 | int64_t c5 = 2097151 & (load_3(c + 13) >> 1); 393 | int64_t c6 = 2097151 & (load_4(c + 15) >> 6); 394 | int64_t c7 = 2097151 & (load_3(c + 18) >> 3); 395 | int64_t c8 = 2097151 & load_3(c + 21); 396 | int64_t c9 = 2097151 & (load_4(c + 23) >> 5); 397 | int64_t c10 = 2097151 & (load_3(c + 26) >> 2); 398 | int64_t c11 = (load_4(c + 28) >> 7); 399 | int64_t s0; 400 | int64_t s1; 401 | int64_t s2; 402 | int64_t s3; 403 | int64_t s4; 404 | int64_t s5; 405 | int64_t s6; 406 | int64_t s7; 407 | int64_t s8; 408 | int64_t s9; 409 | int64_t s10; 410 | int64_t s11; 411 | int64_t s12; 412 | int64_t s13; 413 | int64_t s14; 414 | int64_t s15; 415 | int64_t s16; 416 | int64_t s17; 417 | int64_t s18; 418 | int64_t s19; 419 | int64_t s20; 420 | int64_t s21; 421 | int64_t s22; 422 | int64_t s23; 423 | int64_t carry0; 424 | int64_t carry1; 425 | int64_t carry2; 426 | int64_t carry3; 427 | int64_t carry4; 428 | int64_t carry5; 429 | int64_t carry6; 430 | int64_t carry7; 431 | int64_t carry8; 432 | int64_t carry9; 433 | int64_t carry10; 434 | int64_t carry11; 435 | int64_t carry12; 436 | int64_t carry13; 437 | int64_t carry14; 438 | int64_t carry15; 439 | int64_t carry16; 440 | int64_t carry17; 441 | int64_t carry18; 442 | int64_t carry19; 443 | int64_t carry20; 444 | int64_t carry21; 445 | int64_t carry22; 446 | 447 | s0 = c0 + a0 * b0; 448 | s1 = c1 + a0 * b1 + a1 * b0; 449 | s2 = c2 + a0 * b2 + a1 * b1 + a2 * b0; 450 | s3 = c3 + a0 * b3 + a1 * b2 + a2 * b1 + a3 * b0; 451 | s4 = c4 + a0 * b4 + a1 * b3 + a2 * b2 + a3 * b1 + a4 * b0; 452 | s5 = c5 + a0 * b5 + a1 * b4 + a2 * b3 + a3 * b2 + a4 * b1 + a5 * b0; 453 | s6 = c6 + a0 * b6 + a1 * b5 + a2 * b4 + a3 * b3 + a4 * b2 + a5 * b1 + a6 * b0; 454 | s7 = c7 + a0 * b7 + a1 * b6 + a2 * b5 + a3 * b4 + a4 * b3 + a5 * b2 + a6 * b1 + a7 * b0; 455 | s8 = c8 + a0 * b8 + a1 * b7 + a2 * b6 + a3 * b5 + a4 * b4 + a5 * b3 + a6 * b2 + a7 * b1 + a8 * b0; 456 | s9 = c9 + a0 * b9 + a1 * b8 + a2 * b7 + a3 * b6 + a4 * b5 + a5 * b4 + a6 * b3 + a7 * b2 + a8 * b1 + a9 * b0; 457 | s10 = c10 + a0 * b10 + a1 * b9 + a2 * b8 + a3 * b7 + a4 * b6 + a5 * b5 + a6 * b4 + a7 * b3 + a8 * b2 + a9 * b1 + a10 * b0; 458 | s11 = c11 + a0 * b11 + a1 * b10 + a2 * b9 + a3 * b8 + a4 * b7 + a5 * b6 + a6 * b5 + a7 * b4 + a8 * b3 + a9 * b2 + a10 * b1 + a11 * b0; 459 | s12 = a1 * b11 + a2 * b10 + a3 * b9 + a4 * b8 + a5 * b7 + a6 * b6 + a7 * b5 + a8 * b4 + a9 * b3 + a10 * b2 + a11 * b1; 460 | s13 = a2 * b11 + a3 * b10 + a4 * b9 + a5 * b8 + a6 * b7 + a7 * b6 + a8 * b5 + a9 * b4 + a10 * b3 + a11 * b2; 461 | s14 = a3 * b11 + a4 * b10 + a5 * b9 + a6 * b8 + a7 * b7 + a8 * b6 + a9 * b5 + a10 * b4 + a11 * b3; 462 | s15 = a4 * b11 + a5 * b10 + a6 * b9 + a7 * b8 + a8 * b7 + a9 * b6 + a10 * b5 + a11 * b4; 463 | s16 = a5 * b11 + a6 * b10 + a7 * b9 + a8 * b8 + a9 * b7 + a10 * b6 + a11 * b5; 464 | s17 = a6 * b11 + a7 * b10 + a8 * b9 + a9 * b8 + a10 * b7 + a11 * b6; 465 | s18 = a7 * b11 + a8 * b10 + a9 * b9 + a10 * b8 + a11 * b7; 466 | s19 = a8 * b11 + a9 * b10 + a10 * b9 + a11 * b8; 467 | s20 = a9 * b11 + a10 * b10 + a11 * b9; 468 | s21 = a10 * b11 + a11 * b10; 469 | s22 = a11 * b11; 470 | s23 = 0; 471 | carry0 = (s0 + (1 << 20)) >> 21; 472 | s1 += carry0; 473 | s0 -= carry0 << 21; 474 | carry2 = (s2 + (1 << 20)) >> 21; 475 | s3 += carry2; 476 | s2 -= carry2 << 21; 477 | carry4 = (s4 + (1 << 20)) >> 21; 478 | s5 += carry4; 479 | s4 -= carry4 << 21; 480 | carry6 = (s6 + (1 << 20)) >> 21; 481 | s7 += carry6; 482 | s6 -= carry6 << 21; 483 | carry8 = (s8 + (1 << 20)) >> 21; 484 | s9 += carry8; 485 | s8 -= carry8 << 21; 486 | carry10 = (s10 + (1 << 20)) >> 21; 487 | s11 += carry10; 488 | s10 -= carry10 << 21; 489 | carry12 = (s12 + (1 << 20)) >> 21; 490 | s13 += carry12; 491 | s12 -= carry12 << 21; 492 | carry14 = (s14 + (1 << 20)) >> 21; 493 | s15 += carry14; 494 | s14 -= carry14 << 21; 495 | carry16 = (s16 + (1 << 20)) >> 21; 496 | s17 += carry16; 497 | s16 -= carry16 << 21; 498 | carry18 = (s18 + (1 << 20)) >> 21; 499 | s19 += carry18; 500 | s18 -= carry18 << 21; 501 | carry20 = (s20 + (1 << 20)) >> 21; 502 | s21 += carry20; 503 | s20 -= carry20 << 21; 504 | carry22 = (s22 + (1 << 20)) >> 21; 505 | s23 += carry22; 506 | s22 -= carry22 << 21; 507 | carry1 = (s1 + (1 << 20)) >> 21; 508 | s2 += carry1; 509 | s1 -= carry1 << 21; 510 | carry3 = (s3 + (1 << 20)) >> 21; 511 | s4 += carry3; 512 | s3 -= carry3 << 21; 513 | carry5 = (s5 + (1 << 20)) >> 21; 514 | s6 += carry5; 515 | s5 -= carry5 << 21; 516 | carry7 = (s7 + (1 << 20)) >> 21; 517 | s8 += carry7; 518 | s7 -= carry7 << 21; 519 | carry9 = (s9 + (1 << 20)) >> 21; 520 | s10 += carry9; 521 | s9 -= carry9 << 21; 522 | carry11 = (s11 + (1 << 20)) >> 21; 523 | s12 += carry11; 524 | s11 -= carry11 << 21; 525 | carry13 = (s13 + (1 << 20)) >> 21; 526 | s14 += carry13; 527 | s13 -= carry13 << 21; 528 | carry15 = (s15 + (1 << 20)) >> 21; 529 | s16 += carry15; 530 | s15 -= carry15 << 21; 531 | carry17 = (s17 + (1 << 20)) >> 21; 532 | s18 += carry17; 533 | s17 -= carry17 << 21; 534 | carry19 = (s19 + (1 << 20)) >> 21; 535 | s20 += carry19; 536 | s19 -= carry19 << 21; 537 | carry21 = (s21 + (1 << 20)) >> 21; 538 | s22 += carry21; 539 | s21 -= carry21 << 21; 540 | s11 += s23 * 666643; 541 | s12 += s23 * 470296; 542 | s13 += s23 * 654183; 543 | s14 -= s23 * 997805; 544 | s15 += s23 * 136657; 545 | s16 -= s23 * 683901; 546 | s23 = 0; 547 | s10 += s22 * 666643; 548 | s11 += s22 * 470296; 549 | s12 += s22 * 654183; 550 | s13 -= s22 * 997805; 551 | s14 += s22 * 136657; 552 | s15 -= s22 * 683901; 553 | s22 = 0; 554 | s9 += s21 * 666643; 555 | s10 += s21 * 470296; 556 | s11 += s21 * 654183; 557 | s12 -= s21 * 997805; 558 | s13 += s21 * 136657; 559 | s14 -= s21 * 683901; 560 | s21 = 0; 561 | s8 += s20 * 666643; 562 | s9 += s20 * 470296; 563 | s10 += s20 * 654183; 564 | s11 -= s20 * 997805; 565 | s12 += s20 * 136657; 566 | s13 -= s20 * 683901; 567 | s20 = 0; 568 | s7 += s19 * 666643; 569 | s8 += s19 * 470296; 570 | s9 += s19 * 654183; 571 | s10 -= s19 * 997805; 572 | s11 += s19 * 136657; 573 | s12 -= s19 * 683901; 574 | s19 = 0; 575 | s6 += s18 * 666643; 576 | s7 += s18 * 470296; 577 | s8 += s18 * 654183; 578 | s9 -= s18 * 997805; 579 | s10 += s18 * 136657; 580 | s11 -= s18 * 683901; 581 | s18 = 0; 582 | carry6 = (s6 + (1 << 20)) >> 21; 583 | s7 += carry6; 584 | s6 -= carry6 << 21; 585 | carry8 = (s8 + (1 << 20)) >> 21; 586 | s9 += carry8; 587 | s8 -= carry8 << 21; 588 | carry10 = (s10 + (1 << 20)) >> 21; 589 | s11 += carry10; 590 | s10 -= carry10 << 21; 591 | carry12 = (s12 + (1 << 20)) >> 21; 592 | s13 += carry12; 593 | s12 -= carry12 << 21; 594 | carry14 = (s14 + (1 << 20)) >> 21; 595 | s15 += carry14; 596 | s14 -= carry14 << 21; 597 | carry16 = (s16 + (1 << 20)) >> 21; 598 | s17 += carry16; 599 | s16 -= carry16 << 21; 600 | carry7 = (s7 + (1 << 20)) >> 21; 601 | s8 += carry7; 602 | s7 -= carry7 << 21; 603 | carry9 = (s9 + (1 << 20)) >> 21; 604 | s10 += carry9; 605 | s9 -= carry9 << 21; 606 | carry11 = (s11 + (1 << 20)) >> 21; 607 | s12 += carry11; 608 | s11 -= carry11 << 21; 609 | carry13 = (s13 + (1 << 20)) >> 21; 610 | s14 += carry13; 611 | s13 -= carry13 << 21; 612 | carry15 = (s15 + (1 << 20)) >> 21; 613 | s16 += carry15; 614 | s15 -= carry15 << 21; 615 | s5 += s17 * 666643; 616 | s6 += s17 * 470296; 617 | s7 += s17 * 654183; 618 | s8 -= s17 * 997805; 619 | s9 += s17 * 136657; 620 | s10 -= s17 * 683901; 621 | s17 = 0; 622 | s4 += s16 * 666643; 623 | s5 += s16 * 470296; 624 | s6 += s16 * 654183; 625 | s7 -= s16 * 997805; 626 | s8 += s16 * 136657; 627 | s9 -= s16 * 683901; 628 | s16 = 0; 629 | s3 += s15 * 666643; 630 | s4 += s15 * 470296; 631 | s5 += s15 * 654183; 632 | s6 -= s15 * 997805; 633 | s7 += s15 * 136657; 634 | s8 -= s15 * 683901; 635 | s15 = 0; 636 | s2 += s14 * 666643; 637 | s3 += s14 * 470296; 638 | s4 += s14 * 654183; 639 | s5 -= s14 * 997805; 640 | s6 += s14 * 136657; 641 | s7 -= s14 * 683901; 642 | s14 = 0; 643 | s1 += s13 * 666643; 644 | s2 += s13 * 470296; 645 | s3 += s13 * 654183; 646 | s4 -= s13 * 997805; 647 | s5 += s13 * 136657; 648 | s6 -= s13 * 683901; 649 | s13 = 0; 650 | s0 += s12 * 666643; 651 | s1 += s12 * 470296; 652 | s2 += s12 * 654183; 653 | s3 -= s12 * 997805; 654 | s4 += s12 * 136657; 655 | s5 -= s12 * 683901; 656 | s12 = 0; 657 | carry0 = (s0 + (1 << 20)) >> 21; 658 | s1 += carry0; 659 | s0 -= carry0 << 21; 660 | carry2 = (s2 + (1 << 20)) >> 21; 661 | s3 += carry2; 662 | s2 -= carry2 << 21; 663 | carry4 = (s4 + (1 << 20)) >> 21; 664 | s5 += carry4; 665 | s4 -= carry4 << 21; 666 | carry6 = (s6 + (1 << 20)) >> 21; 667 | s7 += carry6; 668 | s6 -= carry6 << 21; 669 | carry8 = (s8 + (1 << 20)) >> 21; 670 | s9 += carry8; 671 | s8 -= carry8 << 21; 672 | carry10 = (s10 + (1 << 20)) >> 21; 673 | s11 += carry10; 674 | s10 -= carry10 << 21; 675 | carry1 = (s1 + (1 << 20)) >> 21; 676 | s2 += carry1; 677 | s1 -= carry1 << 21; 678 | carry3 = (s3 + (1 << 20)) >> 21; 679 | s4 += carry3; 680 | s3 -= carry3 << 21; 681 | carry5 = (s5 + (1 << 20)) >> 21; 682 | s6 += carry5; 683 | s5 -= carry5 << 21; 684 | carry7 = (s7 + (1 << 20)) >> 21; 685 | s8 += carry7; 686 | s7 -= carry7 << 21; 687 | carry9 = (s9 + (1 << 20)) >> 21; 688 | s10 += carry9; 689 | s9 -= carry9 << 21; 690 | carry11 = (s11 + (1 << 20)) >> 21; 691 | s12 += carry11; 692 | s11 -= carry11 << 21; 693 | s0 += s12 * 666643; 694 | s1 += s12 * 470296; 695 | s2 += s12 * 654183; 696 | s3 -= s12 * 997805; 697 | s4 += s12 * 136657; 698 | s5 -= s12 * 683901; 699 | s12 = 0; 700 | carry0 = s0 >> 21; 701 | s1 += carry0; 702 | s0 -= carry0 << 21; 703 | carry1 = s1 >> 21; 704 | s2 += carry1; 705 | s1 -= carry1 << 21; 706 | carry2 = s2 >> 21; 707 | s3 += carry2; 708 | s2 -= carry2 << 21; 709 | carry3 = s3 >> 21; 710 | s4 += carry3; 711 | s3 -= carry3 << 21; 712 | carry4 = s4 >> 21; 713 | s5 += carry4; 714 | s4 -= carry4 << 21; 715 | carry5 = s5 >> 21; 716 | s6 += carry5; 717 | s5 -= carry5 << 21; 718 | carry6 = s6 >> 21; 719 | s7 += carry6; 720 | s6 -= carry6 << 21; 721 | carry7 = s7 >> 21; 722 | s8 += carry7; 723 | s7 -= carry7 << 21; 724 | carry8 = s8 >> 21; 725 | s9 += carry8; 726 | s8 -= carry8 << 21; 727 | carry9 = s9 >> 21; 728 | s10 += carry9; 729 | s9 -= carry9 << 21; 730 | carry10 = s10 >> 21; 731 | s11 += carry10; 732 | s10 -= carry10 << 21; 733 | carry11 = s11 >> 21; 734 | s12 += carry11; 735 | s11 -= carry11 << 21; 736 | s0 += s12 * 666643; 737 | s1 += s12 * 470296; 738 | s2 += s12 * 654183; 739 | s3 -= s12 * 997805; 740 | s4 += s12 * 136657; 741 | s5 -= s12 * 683901; 742 | s12 = 0; 743 | carry0 = s0 >> 21; 744 | s1 += carry0; 745 | s0 -= carry0 << 21; 746 | carry1 = s1 >> 21; 747 | s2 += carry1; 748 | s1 -= carry1 << 21; 749 | carry2 = s2 >> 21; 750 | s3 += carry2; 751 | s2 -= carry2 << 21; 752 | carry3 = s3 >> 21; 753 | s4 += carry3; 754 | s3 -= carry3 << 21; 755 | carry4 = s4 >> 21; 756 | s5 += carry4; 757 | s4 -= carry4 << 21; 758 | carry5 = s5 >> 21; 759 | s6 += carry5; 760 | s5 -= carry5 << 21; 761 | carry6 = s6 >> 21; 762 | s7 += carry6; 763 | s6 -= carry6 << 21; 764 | carry7 = s7 >> 21; 765 | s8 += carry7; 766 | s7 -= carry7 << 21; 767 | carry8 = s8 >> 21; 768 | s9 += carry8; 769 | s8 -= carry8 << 21; 770 | carry9 = s9 >> 21; 771 | s10 += carry9; 772 | s9 -= carry9 << 21; 773 | carry10 = s10 >> 21; 774 | s11 += carry10; 775 | s10 -= carry10 << 21; 776 | 777 | s[0] = (unsigned char) (s0 >> 0); 778 | s[1] = (unsigned char) (s0 >> 8); 779 | s[2] = (unsigned char) ((s0 >> 16) | (s1 << 5)); 780 | s[3] = (unsigned char) (s1 >> 3); 781 | s[4] = (unsigned char) (s1 >> 11); 782 | s[5] = (unsigned char) ((s1 >> 19) | (s2 << 2)); 783 | s[6] = (unsigned char) (s2 >> 6); 784 | s[7] = (unsigned char) ((s2 >> 14) | (s3 << 7)); 785 | s[8] = (unsigned char) (s3 >> 1); 786 | s[9] = (unsigned char) (s3 >> 9); 787 | s[10] = (unsigned char) ((s3 >> 17) | (s4 << 4)); 788 | s[11] = (unsigned char) (s4 >> 4); 789 | s[12] = (unsigned char) (s4 >> 12); 790 | s[13] = (unsigned char) ((s4 >> 20) | (s5 << 1)); 791 | s[14] = (unsigned char) (s5 >> 7); 792 | s[15] = (unsigned char) ((s5 >> 15) | (s6 << 6)); 793 | s[16] = (unsigned char) (s6 >> 2); 794 | s[17] = (unsigned char) (s6 >> 10); 795 | s[18] = (unsigned char) ((s6 >> 18) | (s7 << 3)); 796 | s[19] = (unsigned char) (s7 >> 5); 797 | s[20] = (unsigned char) (s7 >> 13); 798 | s[21] = (unsigned char) (s8 >> 0); 799 | s[22] = (unsigned char) (s8 >> 8); 800 | s[23] = (unsigned char) ((s8 >> 16) | (s9 << 5)); 801 | s[24] = (unsigned char) (s9 >> 3); 802 | s[25] = (unsigned char) (s9 >> 11); 803 | s[26] = (unsigned char) ((s9 >> 19) | (s10 << 2)); 804 | s[27] = (unsigned char) (s10 >> 6); 805 | s[28] = (unsigned char) ((s10 >> 14) | (s11 << 7)); 806 | s[29] = (unsigned char) (s11 >> 1); 807 | s[30] = (unsigned char) (s11 >> 9); 808 | s[31] = (unsigned char) (s11 >> 17); 809 | } 810 | -------------------------------------------------------------------------------- /src/wavecorepkg/ed25519/sc.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_H 2 | #define SC_H 3 | 4 | /* 5 | The set of scalars is \Z/l 6 | where l = 2^252 + 27742317777372353535851937790883648493. 7 | */ 8 | 9 | void sc_reduce(unsigned char *s); 10 | void sc_muladd(unsigned char *s, const unsigned char *a, const unsigned char *b, const unsigned char *c); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /src/wavecorepkg/ed25519/seed.c: -------------------------------------------------------------------------------- 1 | #include "ed25519.h" 2 | 3 | #ifndef ED25519_NO_SEED 4 | 5 | #ifdef _WIN32 6 | #include 7 | #include 8 | #else 9 | #include 10 | #endif 11 | 12 | int ed25519_create_seed(unsigned char *seed) { 13 | #ifdef _WIN32 14 | HCRYPTPROV prov; 15 | 16 | if (!CryptAcquireContext(&prov, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { 17 | return 1; 18 | } 19 | 20 | if (!CryptGenRandom(prov, 32, seed)) { 21 | CryptReleaseContext(prov, 0); 22 | return 1; 23 | } 24 | 25 | CryptReleaseContext(prov, 0); 26 | #else 27 | FILE *f = fopen("/dev/urandom", "rb"); 28 | 29 | if (f == NULL) { 30 | return 1; 31 | } 32 | 33 | fread(seed, 1, 32, f); 34 | fclose(f); 35 | #endif 36 | 37 | return 0; 38 | } 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /src/wavecorepkg/ed25519/sha512.c: -------------------------------------------------------------------------------- 1 | /* LibTomCrypt, modular cryptographic library -- Tom St Denis 2 | * 3 | * LibTomCrypt is a library that provides various cryptographic 4 | * algorithms in a highly modular and flexible manner. 5 | * 6 | * The library is free for all purposes without any express 7 | * guarantee it works. 8 | * 9 | * Tom St Denis, tomstdenis@gmail.com, http://libtom.org 10 | */ 11 | 12 | #include "fixedint.h" 13 | #include "sha512.h" 14 | 15 | /* the K array */ 16 | static const uint64_t K[80] = { 17 | UINT64_C(0x428a2f98d728ae22), UINT64_C(0x7137449123ef65cd), 18 | UINT64_C(0xb5c0fbcfec4d3b2f), UINT64_C(0xe9b5dba58189dbbc), 19 | UINT64_C(0x3956c25bf348b538), UINT64_C(0x59f111f1b605d019), 20 | UINT64_C(0x923f82a4af194f9b), UINT64_C(0xab1c5ed5da6d8118), 21 | UINT64_C(0xd807aa98a3030242), UINT64_C(0x12835b0145706fbe), 22 | UINT64_C(0x243185be4ee4b28c), UINT64_C(0x550c7dc3d5ffb4e2), 23 | UINT64_C(0x72be5d74f27b896f), UINT64_C(0x80deb1fe3b1696b1), 24 | UINT64_C(0x9bdc06a725c71235), UINT64_C(0xc19bf174cf692694), 25 | UINT64_C(0xe49b69c19ef14ad2), UINT64_C(0xefbe4786384f25e3), 26 | UINT64_C(0x0fc19dc68b8cd5b5), UINT64_C(0x240ca1cc77ac9c65), 27 | UINT64_C(0x2de92c6f592b0275), UINT64_C(0x4a7484aa6ea6e483), 28 | UINT64_C(0x5cb0a9dcbd41fbd4), UINT64_C(0x76f988da831153b5), 29 | UINT64_C(0x983e5152ee66dfab), UINT64_C(0xa831c66d2db43210), 30 | UINT64_C(0xb00327c898fb213f), UINT64_C(0xbf597fc7beef0ee4), 31 | UINT64_C(0xc6e00bf33da88fc2), UINT64_C(0xd5a79147930aa725), 32 | UINT64_C(0x06ca6351e003826f), UINT64_C(0x142929670a0e6e70), 33 | UINT64_C(0x27b70a8546d22ffc), UINT64_C(0x2e1b21385c26c926), 34 | UINT64_C(0x4d2c6dfc5ac42aed), UINT64_C(0x53380d139d95b3df), 35 | UINT64_C(0x650a73548baf63de), UINT64_C(0x766a0abb3c77b2a8), 36 | UINT64_C(0x81c2c92e47edaee6), UINT64_C(0x92722c851482353b), 37 | UINT64_C(0xa2bfe8a14cf10364), UINT64_C(0xa81a664bbc423001), 38 | UINT64_C(0xc24b8b70d0f89791), UINT64_C(0xc76c51a30654be30), 39 | UINT64_C(0xd192e819d6ef5218), UINT64_C(0xd69906245565a910), 40 | UINT64_C(0xf40e35855771202a), UINT64_C(0x106aa07032bbd1b8), 41 | UINT64_C(0x19a4c116b8d2d0c8), UINT64_C(0x1e376c085141ab53), 42 | UINT64_C(0x2748774cdf8eeb99), UINT64_C(0x34b0bcb5e19b48a8), 43 | UINT64_C(0x391c0cb3c5c95a63), UINT64_C(0x4ed8aa4ae3418acb), 44 | UINT64_C(0x5b9cca4f7763e373), UINT64_C(0x682e6ff3d6b2b8a3), 45 | UINT64_C(0x748f82ee5defb2fc), UINT64_C(0x78a5636f43172f60), 46 | UINT64_C(0x84c87814a1f0ab72), UINT64_C(0x8cc702081a6439ec), 47 | UINT64_C(0x90befffa23631e28), UINT64_C(0xa4506cebde82bde9), 48 | UINT64_C(0xbef9a3f7b2c67915), UINT64_C(0xc67178f2e372532b), 49 | UINT64_C(0xca273eceea26619c), UINT64_C(0xd186b8c721c0c207), 50 | UINT64_C(0xeada7dd6cde0eb1e), UINT64_C(0xf57d4f7fee6ed178), 51 | UINT64_C(0x06f067aa72176fba), UINT64_C(0x0a637dc5a2c898a6), 52 | UINT64_C(0x113f9804bef90dae), UINT64_C(0x1b710b35131c471b), 53 | UINT64_C(0x28db77f523047d84), UINT64_C(0x32caab7b40c72493), 54 | UINT64_C(0x3c9ebe0a15c9bebc), UINT64_C(0x431d67c49c100d4c), 55 | UINT64_C(0x4cc5d4becb3e42b6), UINT64_C(0x597f299cfc657e2a), 56 | UINT64_C(0x5fcb6fab3ad6faec), UINT64_C(0x6c44198c4a475817) 57 | }; 58 | 59 | /* Various logical functions */ 60 | 61 | #define ROR64c(x, y) \ 62 | ( ((((x)&UINT64_C(0xFFFFFFFFFFFFFFFF))>>((uint64_t)(y)&UINT64_C(63))) | \ 63 | ((x)<<((uint64_t)(64-((y)&UINT64_C(63)))))) & UINT64_C(0xFFFFFFFFFFFFFFFF)) 64 | 65 | #define STORE64H(x, y) \ 66 | { (y)[0] = (unsigned char)(((x)>>56)&255); (y)[1] = (unsigned char)(((x)>>48)&255); \ 67 | (y)[2] = (unsigned char)(((x)>>40)&255); (y)[3] = (unsigned char)(((x)>>32)&255); \ 68 | (y)[4] = (unsigned char)(((x)>>24)&255); (y)[5] = (unsigned char)(((x)>>16)&255); \ 69 | (y)[6] = (unsigned char)(((x)>>8)&255); (y)[7] = (unsigned char)((x)&255); } 70 | 71 | #define LOAD64H(x, y) \ 72 | { x = (((uint64_t)((y)[0] & 255))<<56)|(((uint64_t)((y)[1] & 255))<<48) | \ 73 | (((uint64_t)((y)[2] & 255))<<40)|(((uint64_t)((y)[3] & 255))<<32) | \ 74 | (((uint64_t)((y)[4] & 255))<<24)|(((uint64_t)((y)[5] & 255))<<16) | \ 75 | (((uint64_t)((y)[6] & 255))<<8)|(((uint64_t)((y)[7] & 255))); } 76 | 77 | 78 | #define Ch(x,y,z) (z ^ (x & (y ^ z))) 79 | #define Maj(x,y,z) (((x | y) & z) | (x & y)) 80 | #define S(x, n) ROR64c(x, n) 81 | #define R(x, n) (((x) &UINT64_C(0xFFFFFFFFFFFFFFFF))>>((uint64_t)n)) 82 | #define Sigma0(x) (S(x, 28) ^ S(x, 34) ^ S(x, 39)) 83 | #define Sigma1(x) (S(x, 14) ^ S(x, 18) ^ S(x, 41)) 84 | #define Gamma0(x) (S(x, 1) ^ S(x, 8) ^ R(x, 7)) 85 | #define Gamma1(x) (S(x, 19) ^ S(x, 61) ^ R(x, 6)) 86 | #ifndef MIN 87 | #define MIN(x, y) ( ((x)<(y))?(x):(y) ) 88 | #endif 89 | 90 | /* compress 1024-bits */ 91 | static int sha512_compress(sha512_context *md, unsigned char *buf) 92 | { 93 | uint64_t S[8], W[80], t0, t1; 94 | int i; 95 | 96 | /* copy state into S */ 97 | for (i = 0; i < 8; i++) { 98 | S[i] = md->state[i]; 99 | } 100 | 101 | /* copy the state into 1024-bits into W[0..15] */ 102 | for (i = 0; i < 16; i++) { 103 | LOAD64H(W[i], buf + (8*i)); 104 | } 105 | 106 | /* fill W[16..79] */ 107 | for (i = 16; i < 80; i++) { 108 | W[i] = Gamma1(W[i - 2]) + W[i - 7] + Gamma0(W[i - 15]) + W[i - 16]; 109 | } 110 | 111 | /* Compress */ 112 | #define RND(a,b,c,d,e,f,g,h,i) \ 113 | t0 = h + Sigma1(e) + Ch(e, f, g) + K[i] + W[i]; \ 114 | t1 = Sigma0(a) + Maj(a, b, c);\ 115 | d += t0; \ 116 | h = t0 + t1; 117 | 118 | for (i = 0; i < 80; i += 8) { 119 | RND(S[0],S[1],S[2],S[3],S[4],S[5],S[6],S[7],i+0); 120 | RND(S[7],S[0],S[1],S[2],S[3],S[4],S[5],S[6],i+1); 121 | RND(S[6],S[7],S[0],S[1],S[2],S[3],S[4],S[5],i+2); 122 | RND(S[5],S[6],S[7],S[0],S[1],S[2],S[3],S[4],i+3); 123 | RND(S[4],S[5],S[6],S[7],S[0],S[1],S[2],S[3],i+4); 124 | RND(S[3],S[4],S[5],S[6],S[7],S[0],S[1],S[2],i+5); 125 | RND(S[2],S[3],S[4],S[5],S[6],S[7],S[0],S[1],i+6); 126 | RND(S[1],S[2],S[3],S[4],S[5],S[6],S[7],S[0],i+7); 127 | } 128 | 129 | #undef RND 130 | 131 | 132 | 133 | /* feedback */ 134 | for (i = 0; i < 8; i++) { 135 | md->state[i] = md->state[i] + S[i]; 136 | } 137 | 138 | return 0; 139 | } 140 | 141 | 142 | /** 143 | Initialize the hash state 144 | @param md The hash state you wish to initialize 145 | @return 0 if successful 146 | */ 147 | int sha512_init(sha512_context * md) { 148 | if (md == NULL) return 1; 149 | 150 | md->curlen = 0; 151 | md->length = 0; 152 | md->state[0] = UINT64_C(0x6a09e667f3bcc908); 153 | md->state[1] = UINT64_C(0xbb67ae8584caa73b); 154 | md->state[2] = UINT64_C(0x3c6ef372fe94f82b); 155 | md->state[3] = UINT64_C(0xa54ff53a5f1d36f1); 156 | md->state[4] = UINT64_C(0x510e527fade682d1); 157 | md->state[5] = UINT64_C(0x9b05688c2b3e6c1f); 158 | md->state[6] = UINT64_C(0x1f83d9abfb41bd6b); 159 | md->state[7] = UINT64_C(0x5be0cd19137e2179); 160 | 161 | return 0; 162 | } 163 | 164 | /** 165 | Process a block of memory though the hash 166 | @param md The hash state 167 | @param in The data to hash 168 | @param inlen The length of the data (octets) 169 | @return 0 if successful 170 | */ 171 | int sha512_update (sha512_context * md, const unsigned char *in, size_t inlen) 172 | { 173 | size_t n; 174 | size_t i; 175 | int err; 176 | if (md == NULL) return 1; 177 | if (in == NULL) return 1; 178 | if (md->curlen > sizeof(md->buf)) { 179 | return 1; 180 | } 181 | while (inlen > 0) { 182 | if (md->curlen == 0 && inlen >= 128) { 183 | if ((err = sha512_compress (md, (unsigned char *)in)) != 0) { 184 | return err; 185 | } 186 | md->length += 128 * 8; 187 | in += 128; 188 | inlen -= 128; 189 | } else { 190 | n = MIN(inlen, (128 - md->curlen)); 191 | 192 | for (i = 0; i < n; i++) { 193 | md->buf[i + md->curlen] = in[i]; 194 | } 195 | 196 | 197 | md->curlen += n; 198 | in += n; 199 | inlen -= n; 200 | if (md->curlen == 128) { 201 | if ((err = sha512_compress (md, md->buf)) != 0) { 202 | return err; 203 | } 204 | md->length += 8*128; 205 | md->curlen = 0; 206 | } 207 | } 208 | } 209 | return 0; 210 | } 211 | 212 | /** 213 | Terminate the hash to get the digest 214 | @param md The hash state 215 | @param out [out] The destination of the hash (64 bytes) 216 | @return 0 if successful 217 | */ 218 | int sha512_final(sha512_context * md, unsigned char *out) 219 | { 220 | int i; 221 | 222 | if (md == NULL) return 1; 223 | if (out == NULL) return 1; 224 | 225 | if (md->curlen >= sizeof(md->buf)) { 226 | return 1; 227 | } 228 | 229 | /* increase the length of the message */ 230 | md->length += md->curlen * UINT64_C(8); 231 | 232 | /* append the '1' bit */ 233 | md->buf[md->curlen++] = (unsigned char)0x80; 234 | 235 | /* if the length is currently above 112 bytes we append zeros 236 | * then compress. Then we can fall back to padding zeros and length 237 | * encoding like normal. 238 | */ 239 | if (md->curlen > 112) { 240 | while (md->curlen < 128) { 241 | md->buf[md->curlen++] = (unsigned char)0; 242 | } 243 | sha512_compress(md, md->buf); 244 | md->curlen = 0; 245 | } 246 | 247 | /* pad upto 120 bytes of zeroes 248 | * note: that from 112 to 120 is the 64 MSB of the length. We assume that you won't hash 249 | * > 2^64 bits of data... :-) 250 | */ 251 | while (md->curlen < 120) { 252 | md->buf[md->curlen++] = (unsigned char)0; 253 | } 254 | 255 | /* store length */ 256 | STORE64H(md->length, md->buf+120); 257 | sha512_compress(md, md->buf); 258 | 259 | /* copy output */ 260 | for (i = 0; i < 8; i++) { 261 | STORE64H(md->state[i], out+(8*i)); 262 | } 263 | 264 | return 0; 265 | } 266 | 267 | int sha512(const unsigned char *message, size_t message_len, unsigned char *out) 268 | { 269 | sha512_context ctx; 270 | int ret; 271 | if ((ret = sha512_init(&ctx))) return ret; 272 | if ((ret = sha512_update(&ctx, message, message_len))) return ret; 273 | if ((ret = sha512_final(&ctx, out))) return ret; 274 | return 0; 275 | } 276 | -------------------------------------------------------------------------------- /src/wavecorepkg/ed25519/sha512.h: -------------------------------------------------------------------------------- 1 | #ifndef SHA512_H 2 | #define SHA512_H 3 | 4 | #include 5 | 6 | #include "fixedint.h" 7 | 8 | /* state */ 9 | typedef struct sha512_context_ { 10 | uint64_t length, state[8]; 11 | size_t curlen; 12 | unsigned char buf[128]; 13 | } sha512_context; 14 | 15 | 16 | int sha512_init(sha512_context * md); 17 | int sha512_final(sha512_context * md, unsigned char *out); 18 | int sha512_update(sha512_context * md, const unsigned char *in, size_t inlen); 19 | int sha512(const unsigned char *message, size_t message_len, unsigned char *out); 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /src/wavecorepkg/ed25519/sign.c: -------------------------------------------------------------------------------- 1 | #include "ed25519.h" 2 | #include "sha512.h" 3 | #include "ge.h" 4 | #include "sc.h" 5 | 6 | 7 | void ed25519_sign(unsigned char *signature, const unsigned char *message, size_t message_len, const unsigned char *public_key, const unsigned char *private_key) { 8 | sha512_context hash; 9 | unsigned char hram[64]; 10 | unsigned char r[64]; 11 | ge_p3 R; 12 | 13 | 14 | sha512_init(&hash); 15 | sha512_update(&hash, private_key + 32, 32); 16 | sha512_update(&hash, message, message_len); 17 | sha512_final(&hash, r); 18 | 19 | sc_reduce(r); 20 | ge_scalarmult_base(&R, r); 21 | ge_p3_tobytes(signature, &R); 22 | 23 | sha512_init(&hash); 24 | sha512_update(&hash, signature, 32); 25 | sha512_update(&hash, public_key, 32); 26 | sha512_update(&hash, message, message_len); 27 | sha512_final(&hash, hram); 28 | 29 | sc_reduce(hram); 30 | sc_muladd(signature + 32, hram, private_key, r); 31 | } 32 | -------------------------------------------------------------------------------- /src/wavecorepkg/ed25519/verify.c: -------------------------------------------------------------------------------- 1 | #include "ed25519.h" 2 | #include "sha512.h" 3 | #include "ge.h" 4 | #include "sc.h" 5 | 6 | static int consttime_equal(const unsigned char *x, const unsigned char *y) { 7 | unsigned char r = 0; 8 | 9 | r = x[0] ^ y[0]; 10 | #define F(i) r |= x[i] ^ y[i] 11 | F(1); 12 | F(2); 13 | F(3); 14 | F(4); 15 | F(5); 16 | F(6); 17 | F(7); 18 | F(8); 19 | F(9); 20 | F(10); 21 | F(11); 22 | F(12); 23 | F(13); 24 | F(14); 25 | F(15); 26 | F(16); 27 | F(17); 28 | F(18); 29 | F(19); 30 | F(20); 31 | F(21); 32 | F(22); 33 | F(23); 34 | F(24); 35 | F(25); 36 | F(26); 37 | F(27); 38 | F(28); 39 | F(29); 40 | F(30); 41 | F(31); 42 | #undef F 43 | 44 | return !r; 45 | } 46 | 47 | int ed25519_verify(const unsigned char *signature, const unsigned char *message, size_t message_len, const unsigned char *public_key) { 48 | unsigned char h[64]; 49 | unsigned char checker[32]; 50 | sha512_context hash; 51 | ge_p3 A; 52 | ge_p2 R; 53 | 54 | if (signature[63] & 224) { 55 | return 0; 56 | } 57 | 58 | if (ge_frombytes_negate_vartime(&A, public_key) != 0) { 59 | return 0; 60 | } 61 | 62 | sha512_init(&hash); 63 | sha512_update(&hash, signature, 32); 64 | sha512_update(&hash, public_key, 32); 65 | sha512_update(&hash, message, message_len); 66 | sha512_final(&hash, h); 67 | 68 | sc_reduce(h); 69 | ge_double_scalarmult_vartime(&R, h, &A, signature + 32); 70 | ge_tobytes(checker, &R); 71 | 72 | if (!consttime_equal(checker, signature)) { 73 | return 0; 74 | } 75 | 76 | return 1; 77 | } 78 | -------------------------------------------------------------------------------- /src/wavecorepkg/paths.nim: -------------------------------------------------------------------------------- 1 | from os import `/` 2 | from base64 import nil 3 | from strutils import format 4 | from webby import `$` 5 | 6 | const 7 | defaultGetAddress* {.strdefine.} = "undefined" 8 | defaultPostAddress* {.strdefine.} = "undefined" 9 | defaultBoard* {.strdefine.} = "undefined" 10 | 11 | when defaultGetAddress == "undefined": 12 | {.error: "You must define defaultGetAddress".} 13 | elif defaultPostAddress == "undefined": 14 | {.error: "You must define defaultPostAddress".} 15 | elif defaultBoard == "undefined": 16 | {.error: "You must define defaultBoard".} 17 | 18 | var 19 | address* = defaultGetAddress 20 | postAddress* = defaultPostAddress 21 | 22 | var readUrl*: string 23 | 24 | const 25 | staticFileDir* = "bbs" 26 | boardsDir* = "boards" 27 | boardDir* = "board" 28 | limboDir* = "limbo" 29 | ansiwaveDir* = "ansiwave" 30 | dbDir* = "db" 31 | dbFilename* = "board.db" 32 | 33 | proc db*(board: string, isUrl: bool = false, limbo: bool = false): string = 34 | if isUrl: 35 | if limbo: 36 | boardsDir & "/" & board & "/" & limboDir & "/" & dbDir & "/" & dbFilename 37 | else: 38 | boardsDir & "/" & board & "/" & boardDir & "/" & dbDir & "/" & dbFilename 39 | else: 40 | if limbo: 41 | boardsDir / board / limboDir / dbDir / dbFilename 42 | else: 43 | boardsDir / board / boardDir / dbDir / dbFilename 44 | 45 | proc ansiwave*(board: string, filename: string, isUrl: bool = false, limbo: bool = false): string = 46 | if isUrl: 47 | if limbo: 48 | boardsDir & "/" & board & "/" & limboDir & "/" & ansiwaveDir & "/" & filename & ".ansiwave" 49 | else: 50 | boardsDir & "/" & board & "/" & boardDir & "/" & ansiwaveDir & "/" & filename & ".ansiwave" 51 | else: 52 | if limbo: 53 | boardsDir / board / limboDir / ansiwaveDir / filename & ".ansiwave" 54 | else: 55 | boardsDir / board / boardDir / ansiwaveDir / filename & ".ansiwave" 56 | 57 | proc encode*[T](data: T): string = 58 | result = base64.encode(data, safe = true) 59 | var i = result.len - 1 60 | while i >= 0 and result[i] == '=': 61 | strutils.delete(result, i..i) 62 | i -= 1 63 | 64 | proc initUrl*(address: string; endpoint: string): string = 65 | if address == "" or strutils.endsWith(address, "/"): 66 | "$1$2".format(address, endpoint) 67 | else: 68 | # the address doesn't end in a slash, so assume the part at the end of the path 69 | # is a file and remove it. 70 | var url = webby.parseUrl(address) 71 | var paths = webby.paths(url) 72 | if paths.len > 0: 73 | discard paths.pop() 74 | url.path = strutils.join(paths, "/") 75 | let s = $url 76 | if strutils.endsWith(s, "/"): 77 | "$1$2".format(s, endpoint) 78 | else: 79 | "$1/$2".format(s, endpoint) 80 | 81 | export base64.decode 82 | -------------------------------------------------------------------------------- /src/wavecorepkg/server.nim: -------------------------------------------------------------------------------- 1 | import threadpool, net, os, selectors 2 | from uri import `$` 3 | from strutils import format 4 | from parseutils import nil 5 | from os import `/` 6 | from osproc import nil 7 | import httpcore 8 | from ./db import nil 9 | from ./db/entities import nil 10 | from ./paths import nil 11 | from ./ed25519 import nil 12 | from ./common import nil 13 | import tables, sets 14 | from times import nil 15 | import threading/channels 16 | 17 | type 18 | ListenActionKind {.pure.} = enum 19 | Stop, 20 | ListenAction = object 21 | case kind: ListenActionKind 22 | of ListenActionKind.Stop: 23 | discard 24 | StateActionKind {.pure.} = enum 25 | Stop, Log, InsertPost, EditPost, EditTags, 26 | StateAction = object 27 | case kind: StateActionKind 28 | of StateActionKind.Stop: 29 | discard 30 | of StateActionKind.Log: 31 | message: string 32 | of StateActionKind.InsertPost: 33 | post: entities.Post 34 | of StateActionKind.EditPost: 35 | content: entities.Content 36 | of StateActionKind.EditTags: 37 | tags: entities.Tags 38 | tagsSigLast: string 39 | extra: bool 40 | board: string 41 | key: string 42 | error: Chan[string] 43 | BackgroundActionKind {.pure.} = enum 44 | Stop, Push, 45 | BackgroundAction = object 46 | case kind: BackgroundActionKind 47 | of BackgroundActionKind.Stop: 48 | discard 49 | of BackgroundActionKind.Push: 50 | board: string 51 | ServerDetails = tuple 52 | hostname: string 53 | port: int 54 | staticFileDir: string 55 | options: Table[string, string] 56 | pushUrls: seq[string] 57 | ThreadData = tuple 58 | details: ServerDetails 59 | readyChan: Chan[bool] 60 | listenAction: Chan[ListenAction] 61 | stateAction: Chan[StateAction] 62 | backgroundAction: Chan[BackgroundAction] 63 | Server* = object 64 | details*: ServerDetails 65 | listenThread: Thread[ThreadData] 66 | listenReady: Chan[bool] 67 | listenAction: Chan[ListenAction] 68 | stateThread: Thread[ThreadData] 69 | stateAction: Chan[StateAction] 70 | stateReady: Chan[bool] 71 | backgroundThread: Thread[ThreadData] 72 | backgroundReady: Chan[bool] 73 | backgroundAction: Chan[BackgroundAction] 74 | Request = object 75 | uri: uri.Uri 76 | reqMethod: httpcore.HttpMethod 77 | headers: httpcore.HttpHeaders 78 | body: string 79 | BadRequestException = object of CatchableError 80 | NotFoundException = object of CatchableError 81 | ForbiddenException = object of CatchableError 82 | 83 | const 84 | selectTimeout = 85 | when defined(release): 86 | 1000 87 | # shorter timeout so tests run faster 88 | else: 89 | 100 90 | recvTimeout = 2000 91 | maxContentLength = 300000 92 | maxHeaderCount = 100 93 | channelSize = 100 94 | 95 | proc initServer*(hostname: string, port: int, staticFileDir: string = "", options: Table[string, string] = initTable[string, string]()): Server = 96 | let pushUrls = 97 | if "push-urls" in options: 98 | strutils.split(options["push-urls"], ",") 99 | else: 100 | @[] 101 | Server(details: (hostname: hostname, port: port, staticFileDir: staticFileDir, options: options, pushUrls: pushUrls)) 102 | 103 | proc insertPost*(details: ServerDetails, board: string, entity: entities.Post) = 104 | var limbo = false 105 | # try inserting it into the main db 106 | db.withOpen(conn, details.staticFileDir / paths.db(board), db.ReadWrite): 107 | db.withTransaction(conn): 108 | if not entities.existsUser(conn, entity.public_key): 109 | # if the user is the sysop, insert it 110 | if entity.public_key == board or "disable-limbo" in details.options: 111 | entities.insertUser(conn, entities.User(public_key: entity.public_key, tags: entities.Tags(value: ""))) 112 | else: 113 | limbo = true 114 | # if we're not inserting into limbo, insert the post 115 | if not limbo: 116 | let sig = entities.insertPost(conn, entity) 117 | writeFile(details.staticFileDir / paths.ansiwave(board, sig), entity.content.value) 118 | # insert into limbo 119 | if limbo: 120 | db.withOpen(conn, details.staticFileDir / paths.db(board, limbo = true), db.ReadWrite): 121 | db.withTransaction(conn): 122 | if not entities.existsUser(conn, entity.public_key): 123 | entities.insertUser(conn, entities.User(public_key: entity.public_key, tags: entities.Tags(value: "modlimbo"))) 124 | let sig = entities.insertPost(conn, entity, limbo = true) 125 | writeFile(details.staticFileDir / paths.ansiwave(board, sig, limbo = true), entity.content.value) 126 | 127 | proc editPost*(details: ServerDetails, board: string, content: entities.Content, key: string) = 128 | var limbo = false 129 | # try inserting it into the main db 130 | db.withOpen(conn, details.staticFileDir / paths.db(board), db.ReadWrite): 131 | db.withTransaction(conn): 132 | if not entities.existsUser(conn, key): 133 | # if the user is the sysop, insert it 134 | if key == board or "disable-limbo" in details.options: 135 | entities.insertUser(conn, entities.User(public_key: key, tags: entities.Tags(value: ""))) 136 | else: 137 | limbo = true 138 | # if we're not inserting into limbo, insert the post 139 | if not limbo: 140 | let sig = entities.editPost(conn, content, key) 141 | writeFile(details.staticFileDir / paths.ansiwave(board, sig), content.value) 142 | # insert into limbo 143 | if limbo: 144 | db.withOpen(conn, details.staticFileDir / paths.db(board, limbo = true), db.ReadWrite): 145 | db.withTransaction(conn): 146 | if not entities.existsUser(conn, key): 147 | entities.insertUser(conn, entities.User(public_key: key, tags: entities.Tags(value: "modlimbo"))) 148 | let sig = entities.editPost(conn, content, key, limbo = true) 149 | writeFile(details.staticFileDir / paths.ansiwave(board, sig, limbo = true), content.value) 150 | 151 | proc editTags*(details: ServerDetails, board: string, tags: entities.Tags, tagsSigLast: string, key: string, extra: bool) = 152 | var exitEarly = false 153 | # if we're editing a user in limbo 154 | if not extra: 155 | db.withOpen(conn, details.staticFileDir / paths.db(board, limbo = true), db.ReadWrite): 156 | db.withTransaction(conn): 157 | if entities.existsUser(conn, tagsSigLast): 158 | let 159 | content = common.splitAfterHeaders(tags.value) 160 | newTags = common.parseTags(content[0]) 161 | if "modlimbo" in newTags: 162 | raise newException(Exception, "You must remove modlimbo tag first") 163 | else: 164 | const alias = "board" 165 | db.attach(conn, details.staticFileDir / paths.db(board), alias) 166 | # try editing the tags to ensure the user is allowed to 167 | entities.editTags(conn, tags, tagsSigLast, board, key, dbPrefix = alias & ".") 168 | if "modpurge" in newTags: 169 | # we're deleting the user from limbo without bringing them into the main db 170 | exitEarly = true 171 | # delete ansiwave files 172 | var offset = 0 173 | while true: 174 | let posts = entities.selectAllUserPosts(conn, tagsSigLast, offset) 175 | if posts.len == 0: 176 | break 177 | for post in posts: 178 | os.removeFile(details.staticFileDir / paths.ansiwave(board, post.content.sig, limbo = true)) 179 | offset += entities.limit 180 | else: 181 | entities.insertUser(conn, entities.User(public_key: tagsSigLast, tags: entities.Tags(value: "")), dbPrefix = alias & ".") 182 | # insert posts into main db 183 | var offset = 0 184 | while true: 185 | let posts = entities.selectAllUserPosts(conn, tagsSigLast, offset) 186 | if posts.len == 0: 187 | break 188 | for post in posts: 189 | let 190 | src = details.staticFileDir / paths.ansiwave(board, post.content.sig, limbo = true) 191 | value = 192 | if os.fileExists(src): 193 | readFile(src) 194 | else: 195 | "" 196 | if post.parent == "": 197 | discard entities.editPost(conn, entities.Content(sig: post.content.sig_last, sig_last: post.public_key, value: value), post.public_key, dbPrefix = alias & ".") 198 | elif post.parent == post.public_key or entities.existsPost(conn, post.parent, dbPrefix = alias & "."): 199 | var p = post 200 | p.content.value = value 201 | discard entities.insertPost(conn, p, dbPrefix = alias & ".") 202 | else: 203 | echo "WARNING: Not moving invalid post from limbo: " & post.content.sig 204 | os.removeFile(details.staticFileDir / paths.ansiwave(board, post.content.sig, limbo = true)) 205 | offset += entities.limit 206 | # move ansiwave files 207 | offset = 0 208 | while true: 209 | let posts = entities.selectAllUserPosts(conn, tagsSigLast, offset) 210 | if posts.len == 0: 211 | break 212 | for post in posts: 213 | let 214 | src = details.staticFileDir / paths.ansiwave(board, post.content.sig, limbo = true) 215 | dest = details.staticFileDir / paths.ansiwave(board, post.content.sig) 216 | if os.fileExists(src): 217 | os.moveFile(src, dest) 218 | offset += entities.limit 219 | # delete user in limbo db 220 | entities.deleteUser(conn, tagsSigLast) 221 | if exitEarly: 222 | return 223 | # insert into main db 224 | db.withOpen(conn, details.staticFileDir / paths.db(board), db.ReadWrite): 225 | db.withTransaction(conn): 226 | if extra: 227 | entities.editExtraTags(conn, tags, tagsSigLast, board, key) 228 | else: 229 | var userToPurge: string 230 | entities.editTags(conn, tags, tagsSigLast, board, key, userToPurge) 231 | if userToPurge != "": 232 | # purge this user completely 233 | var offset = 0 234 | while true: 235 | let posts = entities.selectAllUserPosts(conn, userToPurge, offset) 236 | if posts.len == 0: 237 | break 238 | for post in posts: 239 | os.removeFile(details.staticFileDir / paths.ansiwave(board, post.content.sig)) 240 | offset += entities.limit 241 | entities.deleteUser(conn, userToPurge) 242 | 243 | proc sendAction[T](actionChan: Chan[T], action: T): string = 244 | let error = newChan[string](channelSize) 245 | var newAction = action 246 | newAction.error = error 247 | actionChan.send(newAction) 248 | error.recv(result) 249 | 250 | proc ansiwavePost(data: ThreadData, request: Request, headers: var string, body: var string) = 251 | if request.body.len == 0: 252 | raise newException(BadRequestException, "Invalid request") 253 | 254 | # parse the ansiwave 255 | let (cmds, headersAndContent, contentOnly) = 256 | try: 257 | common.parseAnsiwave(request.body) 258 | except Exception as ex: 259 | raise newException(BadRequestException, ex.msg) 260 | 261 | # check the board 262 | let board = cmds["/board"] 263 | if board != paths.encode(paths.decode(board)): 264 | raise newException(BadRequestException, "Invalid value in /board") 265 | if not os.dirExists(data.details.staticFileDir / paths.boardsDir / board): 266 | raise newException(BadRequestException, "Board does not exist") 267 | 268 | # check the sig 269 | if cmds["/algo"] != "ed25519": 270 | raise newException(BadRequestException, "Invalid value in /algo") 271 | let 272 | keyBase64 = cmds["/key"] 273 | keyBin = paths.decode(keyBase64) 274 | sigBase64 = cmds["/sig"] 275 | sigBin = paths.decode(sigBase64) 276 | var 277 | pubKey: ed25519.PublicKey 278 | sig: ed25519.Signature 279 | if keyBin.len != pubKey.len: 280 | raise newException(BadRequestException, "Invalid key length for /key") 281 | copyMem(pubKey.addr, keyBin[0].unsafeAddr, keyBin.len) 282 | if sigBin.len != sig.len: 283 | raise newException(BadRequestException, "Invalid key length for /sig") 284 | copyMem(sig.addr, sigBin[0].unsafeAddr, sigBin.len) 285 | if not ed25519.verify(pubKey, sig, headersAndContent): 286 | raise newException(ForbiddenException, "Invalid signature") 287 | 288 | case cmds["/type"]: 289 | of "new": 290 | let 291 | post = entities.Post( 292 | content: entities.Content(value: request.body, sig: sigBase64, sig_last: sigBase64), 293 | public_key: keyBase64, 294 | parent: cmds["/target"], 295 | ) 296 | error = sendAction(data.stateAction, StateAction(kind: InsertPost, board: board, post: post, key: keyBase64)) 297 | if error != "": 298 | raise newException(Exception, error) 299 | of "edit": 300 | let 301 | content = entities.Content(value: request.body, sig: sigBase64, sig_last: cmds["/target"]) 302 | error = sendAction(data.stateAction, StateAction(kind: EditPost, board: board, content: content, key: keyBase64)) 303 | if error != "": 304 | raise newException(Exception, error) 305 | of "tags": 306 | let 307 | tags = entities.Tags(value: request.body, sig: sigBase64) 308 | error = sendAction(data.stateAction, StateAction(kind: EditTags, board: board, tags: tags, tagsSigLast: cmds["/target"], key: keyBase64)) 309 | if error != "": 310 | raise newException(Exception, error) 311 | of "extra-tags": 312 | let 313 | tags = entities.Tags(value: request.body, sig: sigBase64) 314 | error = sendAction(data.stateAction, StateAction(kind: EditTags, board: board, tags: tags, tagsSigLast: cmds["/target"], key: keyBase64, extra: true)) 315 | if error != "": 316 | raise newException(Exception, error) 317 | else: 318 | raise newException(BadRequestException, "Invalid /type") 319 | 320 | body = "" 321 | headers = "HTTP/1.1 200 OK\r\LContent-Length: " & $body.len 322 | 323 | proc handleStatic(details: ServerDetails, request: Request, headers: var string, body: var string): bool = 324 | var filePath = "" 325 | if request.reqMethod == httpcore.HttpGet and details.staticFileDir != "": 326 | let path = details.staticFileDir / $request.uri 327 | if fileExists(path): 328 | filePath = path 329 | else: 330 | raise newException(NotFoundException, "Not found: " & request.uri.path) 331 | if filePath != "": 332 | let contentType = 333 | case os.splitFile(filePath).ext: 334 | of ".html": "text/html" 335 | of ".js": "text/javascript" 336 | of ".wasm": "application/wasm" 337 | else: "text/plain" 338 | body = readFile(filePath) 339 | if request.headers.hasKey("Range"): 340 | let range = strutils.split(strutils.split(request.headers["Range"], '=')[1], '-') 341 | var first, last: int 342 | discard parseutils.parseSaturatedNatural(range[0], first) 343 | discard parseutils.parseSaturatedNatural(range[1], last) 344 | if first <= last and last < body.len: 345 | let contentRange = "bytes " & $range[0] & "-" & $range[1] & "/" & $body.len 346 | body = body[first .. last] 347 | headers = "HTTP/1.1 206 OK\r\LContent-Length: " & $body.len & "\r\LContent-Range: " & contentRange & "\r\LContent-Type: " & contentType 348 | else: 349 | raise newException(BadRequestException, "Bad Request. Invalid Range.") 350 | else: 351 | headers = "HTTP/1.1 200 OK\r\LContent-Length: " & $body.len & "\r\LContent-Type: " & contentType 352 | return true 353 | return false 354 | 355 | proc handle(data: ThreadData, client: Socket) = 356 | var headers, body: string 357 | try: 358 | var request = Request(headers: httpcore.newHttpHeaders()) 359 | var firstLine = "" 360 | client.readLine(firstLine, recvTimeout) 361 | let parts = strutils.split(firstLine, ' ') 362 | if parts.len != 3: 363 | raise newException(Exception, "Invalid first line: " & firstLine) 364 | # request method 365 | case parts[0] 366 | of "GET": request.reqMethod = httpcore.HttpGet 367 | of "POST": request.reqMethod = httpcore.HttpPost 368 | of "HEAD": request.reqMethod = httpcore.HttpHead 369 | of "PUT": request.reqMethod = httpcore.HttpPut 370 | of "DELETE": request.reqMethod = httpcore.HttpDelete 371 | of "PATCH": request.reqMethod = httpcore.HttpPatch 372 | of "OPTIONS": request.reqMethod = httpcore.HttpOptions 373 | of "CONNECT": request.reqMethod = httpcore.HttpConnect 374 | of "TRACE": request.reqMethod = httpcore.HttpTrace 375 | # uri 376 | request.uri = uri.parseUri(parts[1]) 377 | # headers 378 | var headerCount = 0 379 | while true: 380 | if headerCount > maxHeaderCount: 381 | raise newException(BadRequestException, "Too many headers") 382 | else: 383 | headerCount += 1 384 | var line = "" 385 | client.readLine(line, recvTimeout) 386 | if line == "\c\L": 387 | break 388 | let (key, value) = httpcore.parseHeader(line) 389 | request.headers[key] = value 390 | # body 391 | if httpcore.hasKey(request.headers, "Content-Length"): 392 | var contentLength = 0 393 | if parseutils.parseSaturatedNatural(request.headers["Content-Length"], contentLength) == 0: 394 | raise newException(BadRequestException, "Invalid Content-Length") 395 | elif contentLength > maxContentLength: 396 | client.skip(contentLength, recvTimeout) 397 | raise newException(BadRequestException, "The Content-Length is too large") 398 | else: 399 | request.body = client.recv(contentLength, recvTimeout) 400 | # handle requests 401 | let dispatch = (reqMethod: request.reqMethod, path: request.uri.path) 402 | if dispatch == (httpcore.HttpPost, "/ansiwave"): 403 | ansiwavePost(data, request, headers, body) 404 | else: 405 | when not defined(release): 406 | if not handleStatic(data.details, request, headers, body): 407 | raise newException(NotFoundException, "Unhandled request: " & $dispatch) 408 | else: 409 | raise newException(NotFoundException, "Unhandled request: " & $dispatch) 410 | except BadRequestException as ex: 411 | headers = "HTTP/1.1 400 Bad Request" 412 | body = ex.msg 413 | discard sendAction(data.stateAction, StateAction(kind: Log, message: headers & " - " & body)) 414 | except ForbiddenException as ex: 415 | headers = "HTTP/1.1 403 Forbidden" 416 | body = ex.msg 417 | discard sendAction(data.stateAction, StateAction(kind: Log, message: headers & " - " & body)) 418 | except NotFoundException as ex: 419 | headers = "HTTP/1.1 404 Not Found" 420 | body = ex.msg 421 | discard sendAction(data.stateAction, StateAction(kind: Log, message: headers & " - " & body)) 422 | except Exception as ex: 423 | headers = "HTTP/1.1 500 Internal Server Error" 424 | body = ex.msg 425 | discard sendAction(data.stateAction, StateAction(kind: Log, message: headers & " - " & body)) 426 | finally: 427 | try: 428 | headers &= "\r\LAccess-Control-Allow-Origin: *" 429 | client.send(headers & "\r\L\r\L" & body) 430 | except Exception as ex: 431 | discard 432 | client.close() 433 | 434 | proc loop(data: ThreadData, socket: Socket) = 435 | var selector = newSelector[int]() 436 | selector.registerHandle(socket.getFD, {Event.Read}, 0) 437 | data.readyChan.send(true) 438 | while true: 439 | var action: ListenAction 440 | if data.listenAction.tryRecv(action): 441 | case action.kind: 442 | of ListenActionKind.Stop: 443 | break 444 | elif selector.select(selectTimeout).len > 0: 445 | var client: Socket = Socket() 446 | accept(socket, client) 447 | spawn handle(data, client) 448 | 449 | proc listen(data: ThreadData) {.thread.} = 450 | var socket = newSocket() 451 | try: 452 | socket.setSockOpt(OptReuseAddr, true) 453 | socket.bindAddr(port = Port(data.details.port)) 454 | socket.listen() 455 | echo("Server listening on port " & $data.details.port) 456 | loop(data, socket) 457 | finally: 458 | echo("Server closing on port " & $data.details.port) 459 | socket.close() 460 | 461 | proc execCmd(command: string, silent: bool = false): tuple[output: string, exitCode: int] = 462 | result = osproc.execCmdEx(command) 463 | if result.exitCode != 0 and not silent: 464 | raise newException(Exception, "Command failed: " & command & "\n" & result.output) 465 | 466 | proc recvAction(data: ThreadData) {.thread.} = 467 | data.readyChan.send(true) 468 | var 469 | initializedBoards: HashSet[string] 470 | keyToLastTs: Table[string, float] 471 | while true: 472 | var action: StateAction 473 | data.stateAction.recv(action) 474 | var resp = "" 475 | if action.board != "": 476 | # init board if necessary 477 | try: 478 | for subdir in [paths.boardDir, paths.limboDir]: 479 | let bbsGitDir = os.absolutePath(data.details.staticFileDir / paths.boardsDir / action.board / subdir) 480 | os.createDir(bbsGitDir / paths.ansiwaveDir) 481 | os.createDir(bbsGitDir / paths.dbDir) 482 | if data.details.pushUrls.len > 0 and not os.dirExists(bbsGitDir / ".git"): 483 | discard execCmd("git init $1".format(bbsGitDir)) 484 | if action.board notin initializedBoards: 485 | db.withOpen(conn, data.details.staticFileDir / paths.db(action.board), db.ReadWrite): 486 | db.init(conn) 487 | db.withOpen(conn, data.details.staticFileDir / paths.db(action.board, limbo = true), db.ReadWrite): 488 | db.init(conn) 489 | initializedBoards.incl(action.board) 490 | except Exception as ex: 491 | resp = "Error initializing board" 492 | stderr.writeLine(ex.msg) 493 | stderr.writeLine(getStackTrace(ex)) 494 | if resp == "": 495 | case action.kind: 496 | of StateActionKind.Stop: 497 | break 498 | of StateActionKind.Log: 499 | echo action.message 500 | of StateActionKind.InsertPost: 501 | try: 502 | if "testrun" notin data.details.options: 503 | const minInterval = 15 504 | let ts = times.epochTime() 505 | if action.key != action.board and action.key in keyToLastTs and ts - keyToLastTs[action.key] < minInterval: 506 | raise newException(Exception, "Posting too fast! Wait a few seconds.") 507 | keyToLastTs[action.key] = ts 508 | insertPost(data.details, action.board, action.post) 509 | except Exception as ex: 510 | resp = ex.msg 511 | of StateActionKind.EditPost: 512 | try: 513 | editPost(data.details, action.board, action.content, action.key) 514 | except Exception as ex: 515 | resp = ex.msg 516 | of StateActionKind.EditTags: 517 | try: 518 | editTags(data.details, action.board, action.tags, action.tagsSigLast, action.key, action.extra) 519 | except Exception as ex: 520 | resp = ex.msg 521 | if resp == "" and action.kind in {StateActionKind.InsertPost, StateActionKind.EditPost, StateActionKind.EditTags}: 522 | try: 523 | var errors: seq[string] 524 | for subdir in [paths.boardDir, paths.limboDir]: 525 | let bbsGitDir = os.absolutePath(data.details.staticFileDir / paths.boardsDir / action.board / subdir) 526 | if data.details.pushUrls.len > 0: 527 | discard execCmd("git -C $1 add .".format(bbsGitDir)) 528 | let res = execCmd("git -C $1 commit -m \"$2\"".format(bbsGitDir, $action.kind & " " & action.key), silent = true) 529 | if res.exitCode != 0: 530 | errors.add(res.output) 531 | # if only one failed, it's probably because there were no changes to commit. 532 | # if both failed, something went wrong, so print the errors out. 533 | if errors.len == 2: 534 | raise newException(Exception, errors[0] & "\n" & errors[1]) 535 | if data.details.pushUrls.len > 0: 536 | data.backgroundAction.send(BackgroundAction(kind: BackgroundActionKind.Push, board: action.board)) 537 | except Exception as ex: 538 | stderr.writeLine(ex.msg) 539 | stderr.writeLine(getStackTrace(ex)) 540 | action.error.send(resp) 541 | 542 | proc recvBackgroundAction(data: ThreadData) {.thread.} = 543 | data.readyChan.send(true) 544 | while true: 545 | var action: BackgroundAction 546 | data.backgroundAction.recv(action) 547 | case action.kind: 548 | of BackgroundActionKind.Stop: 549 | break 550 | of BackgroundActionKind.Push: 551 | for url in data.details.pushUrls: 552 | for subdir in [paths.boardDir, paths.limboDir]: 553 | let bbsGitDir = os.absolutePath(data.details.staticFileDir / paths.boardsDir / action.board / subdir) 554 | let cmd = "git -C $1 push $2/$3/$4/$5 master".format(bbsGitDir, url, paths.boardsDir, action.board, subdir) 555 | let res = execCmd(cmd, silent = true) 556 | if res.exitCode != 0: 557 | discard sendAction(data.stateAction, StateAction(kind: Log, message: "Failed push:\n$1\n$2".format(cmd, res.output))) 558 | else: 559 | discard sendAction(data.stateAction, StateAction(kind: Log, message: "Successful push:\n$1\n$2".format(cmd, res.output))) 560 | 561 | proc initChans(server: var Server) = 562 | # listen 563 | server.listenReady = newChan[bool](channelSize) 564 | server.listenAction = newChan[ListenAction](channelSize) 565 | # state 566 | server.stateReady = newChan[bool](channelSize) 567 | server.stateAction = newChan[StateAction](channelSize) 568 | # background 569 | if server.details.pushUrls.len > 0: 570 | server.backgroundReady = newChan[bool](channelSize) 571 | server.backgroundAction = newChan[BackgroundAction](channelSize) 572 | 573 | proc initThreads(server: var Server) = 574 | var res: bool 575 | createThread(server.listenThread, listen, (server.details, server.listenReady, server.listenAction, server.stateAction, server.backgroundAction)) 576 | server.listenReady.recv(res) 577 | createThread(server.stateThread, recvAction, (server.details, server.stateReady, server.listenAction, server.stateAction, server.backgroundAction)) 578 | server.stateReady.recv(res) 579 | if server.details.pushUrls.len > 0: 580 | createThread(server.backgroundThread, recvBackgroundAction, (server.details, server.backgroundReady, server.listenAction, server.stateAction, server.backgroundAction)) 581 | server.backgroundReady.recv(res) 582 | 583 | proc deinitThreads(server: var Server) = 584 | server.listenAction.send(ListenAction(kind: ListenActionKind.Stop)) 585 | server.listenThread.joinThread() 586 | server.stateAction.send(StateAction(kind: StateActionKind.Stop)) 587 | server.stateThread.joinThread() 588 | if server.details.pushUrls.len > 0: 589 | server.backgroundAction.send(BackgroundAction(kind: BackgroundActionKind.Stop)) 590 | server.backgroundThread.joinThread() 591 | 592 | proc start*(server: var Server) = 593 | initChans(server) 594 | initThreads(server) 595 | 596 | proc stop*(server: var Server) = 597 | deinitThreads(server) 598 | 599 | -------------------------------------------------------------------------------- /src/wavecorepkg/wavescript.nim: -------------------------------------------------------------------------------- 1 | import unicode, tables, paramidi/constants 2 | from strutils import format 3 | import json, sets 4 | from webby import nil 5 | from ansiutils/codes import nil 6 | 7 | type 8 | CommandText* = object 9 | text*: string 10 | line*: int 11 | FormKind = enum 12 | Whitespace, Symbol, Operator, Number, Command, 13 | Form = object 14 | case kind: FormKind 15 | of Whitespace: 16 | discard 17 | of Symbol, Operator, Number: 18 | name*: string 19 | signed: bool # only used for numbers 20 | of Command: 21 | tree: CommandTree 22 | CommandTreeKind* = enum 23 | Valid, Error, Discard, 24 | CommandTree* = object 25 | case kind*: CommandTreeKind 26 | of Valid: 27 | name*: string 28 | args*: seq[Form] 29 | of Error, Discard: 30 | message*: string 31 | line*: int 32 | skip*: bool 33 | CommandKind = enum 34 | Play, Instrument, PlayInstrument, Attribute, Length, LengthWithNumerator, 35 | Concurrent, ConcurrentLines, Let, 36 | CommandMetadata = tuple[argc: int, kind: CommandKind] 37 | Commands = Table[string, CommandMetadata] 38 | Context* = object 39 | commands: Commands 40 | stringCommands*: HashSet[string] 41 | variables: Table[string, seq[Form]] 42 | 43 | proc extract*(lines: seq[string]): seq[CommandText] = 44 | for i in 0 ..< lines.len: 45 | let line = lines[i] 46 | if line.len > 1: 47 | if line[0] == '/' and line[1] != '/': # don't add if it is a comment 48 | result.add(CommandText(text: line, line: i)) 49 | 50 | proc toJson(form: Form, preserveNumberSigns: bool = false): JsonNode 51 | 52 | proc instrumentToJson(name: string, args: seq[Form]): JsonNode = 53 | result = JsonNode(kind: JArray) 54 | result.elems.add(JsonNode(kind: JString, str: name[1 ..< name.len])) 55 | for arg in args: 56 | result.elems.add(toJson(arg)) 57 | 58 | proc attributeToJson(name: string, args: seq[Form]): JsonNode = 59 | result = JsonNode(kind: JObject) 60 | assert args.len == 1 61 | result.fields[name[1 ..< name.len]] = toJson(args[0], true) 62 | 63 | proc initCommands(): Table[string, CommandMetadata] = 64 | for inst in constants.instruments: 65 | result["/" & inst] = (argc: -1, kind: PlayInstrument) 66 | result["/length"] = (argc: 1, kind: Attribute) 67 | result["/octave"] = (argc: 1, kind: Attribute) 68 | result["/tempo"] = (argc: 1, kind: Attribute) 69 | result["/"] = (argc: 2, kind: LengthWithNumerator) 70 | result[","] = (argc: 2, kind: Concurrent) 71 | result["/,"] = (argc: 0, kind: ConcurrentLines) 72 | result["/let"] = (argc: -1, kind: Let) 73 | result["/play"] = (argc: -1, kind: Play) 74 | result["/instrument"] = (argc: 1, kind: Instrument) 75 | 76 | proc toStr(form: Form): string = 77 | case form.kind: 78 | of Whitespace: 79 | "" 80 | of Symbol, Operator, Number: 81 | form.name 82 | of Command: 83 | form.tree.name 84 | 85 | const 86 | symbolChars = {'a'..'z', '#'} 87 | operatorChars = {'/', '-', '+'} 88 | operatorSingleChars = {','} # operator chars that can only exist on their own 89 | numberChars = {'0'..'9'} 90 | invalidChars = {'A'..'Z', '~', '`', '!', '@', '$', '%', '^', '&', '*', '(', ')', '{', '}', 91 | '[', ']', '_', '=', ':', ';', '<', '>', '.', '"', '\'', '|', '\\', '?'} 92 | whitespaceChars* = [ 93 | " ", "▀", "▁", "▂", "▃", "▄", "▅", "▆", "▇", "█", "▉", "▊", "▋", "▌", "▍", "▎", "▏", "▐", 94 | "░", "▒", "▓", "▔", "▕", "▖", "▗", "▘", "▙", "▚", "▛", "▜", "▝", "▞", "▟", 95 | ].toHashSet 96 | operatorCommands = ["/,"].toHashSet 97 | commands = initCommands() 98 | stringCommands* = ["/section", "/link", "/name"].toHashSet 99 | stringCommandValidators = { 100 | "/link": 101 | proc (s: string): string = 102 | var foundUrl = false 103 | for word in strutils.split(s, " "): 104 | if webby.parseUrl(word).scheme != "": 105 | foundUrl = true 106 | if not foundUrl: 107 | result = "No URL found. Write it like this: /link hello world https://ansiwave.net" 108 | , 109 | "/name": 110 | proc (s: string): string = 111 | if s.len < 2 or s.len > 20: 112 | return "Your /name must be between 2 and 20 characters" 113 | elif strutils.startsWith(s, "mod") or s in ["admin", "sysop"].toHashSet: 114 | return "Your /name is invalid" 115 | for ch in s: 116 | if ch == ' ': 117 | return "You cannot have a space in your /name" 118 | elif ch notin {'a'..'z', '0'..'9'}: 119 | return "/name can only have numbers and lower-case letters" 120 | if s[0] notin {'a'..'z'}: 121 | return "Names cannot begin with a number" 122 | }.toTable 123 | 124 | proc initContext*(): Context = 125 | result.commands = commands 126 | result.stringCommands = stringCommands 127 | 128 | proc getCommand(meta: var CommandMetadata, name: string): bool = 129 | if name in commands: 130 | meta = commands[name] 131 | else: 132 | try: 133 | discard strutils.parseInt(name[1 ..< name.len]) 134 | meta = (argc: 0, kind: Length) 135 | except: 136 | return false 137 | true 138 | 139 | proc toCommandTree(context: var Context, forms: seq[Form], command: CommandText): CommandTree = 140 | # create a hierarchical tree of commands 141 | proc getNextCommand(context: var Context, head: Form, forms: var seq[Form], topLevel: bool): CommandTree = 142 | if head.kind == Command: 143 | return CommandTree(kind: Error, line: command.line, message: "$1 is not in a valid place".format(head.tree.name)) 144 | result = CommandTree(kind: Valid, line: command.line, name: head.name) 145 | var cmd: CommandMetadata 146 | if getCommand(cmd, head.name): 147 | let (argc, kind) = cmd 148 | if kind == Let: 149 | if not topLevel: 150 | return CommandTree(kind: Error, line: command.line, message: "$1 cannot be placed within another command".format(head.name)) 151 | elif forms.len < 2: 152 | return CommandTree(kind: Error, line: command.line, message: "$1 does not have enough input".format(head.name)) 153 | elif forms[0].kind != Symbol or forms[0].name[0] == '/': 154 | return CommandTree(kind: Error, line: command.line, message: "$1 must have a symbol as its first input".format(head.name)) 155 | else: 156 | let name = "/" & forms[0].name 157 | if name in commands or name in context.variables: 158 | return CommandTree(kind: Error, line: command.line, message: "$1 already was defined".format(name)) 159 | context.variables[name] = forms[1 ..< forms.len] 160 | result.skip = true 161 | var argcFound = 0 162 | while forms.len > 0: 163 | if argc >= 0 and argcFound == argc: 164 | break 165 | let form = forms[0] 166 | forms = forms[1 ..< forms.len] 167 | if form.kind == Symbol and form.name[0] == '/': 168 | let cmd = getNextCommand(context, form, forms, false) 169 | case cmd.kind: 170 | of Valid: 171 | result.args.add(Form(kind: Command, tree: cmd)) 172 | of Error: 173 | return cmd 174 | of Discard: 175 | discard 176 | else: 177 | result.args.add(form) 178 | argcFound.inc 179 | if argcFound < argc: 180 | return CommandTree(kind: Error, line: command.line, message: "$1 expects $2 arguments, but only $3 given".format(head.toStr, argc, argcFound)) 181 | elif head.name in context.variables: 182 | forms = context.variables[head.name] & forms 183 | return CommandTree(kind: Discard, line: command.line, message: "$1 must be placed within another command".format(head.name)) 184 | else: 185 | return CommandTree(kind: Error, line: command.line, message: "Command not found: $1".format(head.name)) 186 | let head = forms[0] 187 | var rest = forms[1 ..< forms.len] 188 | result = getNextCommand(context, head, rest, true) 189 | # error if there is any extra input 190 | if result.kind == Valid and rest.len > 0: 191 | var extraInput = "" 192 | for form in rest: 193 | extraInput &= form.toStr & " " 194 | result = CommandTree(kind: Error, line: command.line, message: "Extra input: $1".format(extraInput)) 195 | 196 | proc parse*(context: var Context, command: CommandText): CommandTree = 197 | var 198 | forms: seq[Form] 199 | form = Form(kind: Whitespace) 200 | proc flush() = 201 | forms.add(form) 202 | form = Form(kind: Whitespace) 203 | for ch in runes(command.text): 204 | let 205 | s = ch.toUTF8 206 | c = s[0] 207 | case form.kind: 208 | of Whitespace: 209 | if c in operatorChars: 210 | form = Form(kind: Operator, name: $c) 211 | elif c in operatorSingleChars: 212 | form = Form(kind: Operator, name: $c) 213 | flush() 214 | elif c in symbolChars or c in invalidChars: 215 | form = Form(kind: Symbol, name: $c) 216 | elif c in numberChars: 217 | form = Form(kind: Number, name: $c) 218 | of Symbol, Operator, Number: 219 | # this is a comment, so ignore everything else 220 | if form.name == "/" and c == '/': 221 | form = Form(kind: Whitespace) 222 | break 223 | elif c in operatorChars: 224 | if form.kind == Operator: 225 | form.name &= $c 226 | else: 227 | flush() 228 | form = Form(kind: Operator, name: $c) 229 | elif c in operatorSingleChars: 230 | flush() 231 | form = Form(kind: Operator, name: $c) 232 | flush() 233 | elif c in symbolChars or c in invalidChars or c in numberChars: 234 | if form.kind == Operator: 235 | flush() 236 | if c in numberChars: 237 | form = Form(kind: Number, name: $c) 238 | else: 239 | form = Form(kind: Symbol, name: $c) 240 | else: 241 | form.name &= $c 242 | else: 243 | flush() 244 | if s in whitespaceChars: 245 | flush() # second flush to add the whitespace 246 | of Command: 247 | discard 248 | flush() 249 | # merge operators with adjacent tokens in some cases 250 | var 251 | newForms: seq[Form] 252 | i = 0 253 | while i < forms.len: 254 | # + and - with whitespace on the left and number on the right should form a single number 255 | if forms[i].kind == Operator and 256 | (forms[i].name == "+" or forms[i].name == "-") and 257 | (i == 0 or forms[i-1].kind == Whitespace) and 258 | (i != forms.len - 1 and forms[i+1].kind == Number): 259 | newForms.add(Form(kind: Number, name: forms[i].name & forms[i+1].name, signed: true)) 260 | i += 2 261 | # + and - with whitespace on the left and symbol on the right should form a command 262 | elif forms[i].kind == Operator and 263 | (forms[i].name == "+" or forms[i].name == "-") and 264 | (i == 0 or forms[i-1].kind == Whitespace) and 265 | (i != forms.len - 1 and forms[i+1].kind == Symbol): 266 | newForms.add(Form(kind: Command, tree: CommandTree(kind: Valid, line: command.line, name: forms[i].name, args: @[forms[i+1]]))) 267 | i += 2 268 | # + and - with a symbol on the left should form a single symbol (including symbol/number on the right if it exists) 269 | elif forms[i].kind == Operator and 270 | (forms[i].name == "+" or forms[i].name == "-") and 271 | (newForms.len > 0 and newForms[newForms.len-1].kind == Symbol): 272 | let lastItem = newForms.pop() 273 | if i != forms.len - 1 and forms[i+1].kind in {Symbol, Number}: 274 | newForms.add(Form(kind: Symbol, name: lastItem.name & forms[i].name & forms[i+1].name)) 275 | i += 2 276 | else: 277 | newForms.add(Form(kind: Symbol, name: lastItem.name & forms[i].name)) 278 | i.inc 279 | else: 280 | newForms.add(forms[i]) 281 | i.inc 282 | forms = newForms 283 | # / with whitespace or comma on the left and symbol/number/operator on the right should form a single symbol 284 | newForms = @[] 285 | i = 0 286 | while i < forms.len: 287 | if forms[i].kind == Operator and 288 | forms[i].name == "/" and 289 | (i == 0 or forms[i-1].kind == Whitespace or (forms[i-1].kind == Operator and forms[i-1].name == ",")) and 290 | (i != forms.len - 1 and forms[i+1].kind in {Symbol, Number, Operator}): 291 | newForms.add(Form(kind: Symbol, name: forms[i].name & forms[i+1].name)) 292 | i += 2 293 | else: 294 | newForms.add(forms[i]) 295 | i.inc 296 | forms = newForms 297 | # remove whitespace 298 | newForms = @[] 299 | i = 0 300 | while i < forms.len: 301 | if forms[i].kind == Whitespace: 302 | i.inc 303 | else: 304 | newForms.add(forms[i]) 305 | i.inc 306 | forms = newForms 307 | # if a string command, exit early 308 | if forms.len >= 1 and forms[0].kind == Symbol and forms[0].name in context.stringCommands: 309 | var text, error = "" 310 | if command.text.len > forms[0].name.len: 311 | let unsanitizedText = command.text[forms[0].name.len+1 ..< command.text.len] 312 | for ch in codes.stripCodes(unsanitizedText.toRunes): 313 | let s = $ch 314 | if s in whitespaceChars: 315 | text &= " " 316 | else: 317 | text &= s 318 | text = strutils.strip(text) 319 | if forms[0].name in stringCommandValidators: 320 | error = stringCommandValidators[forms[0].name](text) 321 | if error != "": 322 | return CommandTree(kind: Error, line: command.line, message: error) 323 | return CommandTree( 324 | kind: Valid, 325 | name: forms[0].name, 326 | args: @[Form(kind: Symbol, name: text)], 327 | line: command.line, 328 | skip: true 329 | ) 330 | # do some error checking 331 | for form in forms: 332 | if form.kind in {Symbol, Number}: 333 | let invalidIdx = strutils.find(form.name, invalidChars) 334 | if invalidIdx >= 0: 335 | return CommandTree(kind: Error, line: command.line, message: "$1 has an invalid character: $2".format(form.name, form.name[invalidIdx])) 336 | if form.kind == Number: 337 | let symbolIdx = strutils.find(form.name, symbolChars) 338 | if symbolIdx >= 0: 339 | return CommandTree(kind: Error, line: command.line, message: "$1 may not contain $2 because it is a number".format(form.name, form.name[symbolIdx])) 340 | # group operators with their operands 341 | newForms = @[] 342 | i = 0 343 | while i < forms.len: 344 | if forms[i].kind == Operator: 345 | if i == 0 or i == forms.len - 1: 346 | return CommandTree(kind: Error, line: command.line, message: "$1 is not in a valid place".format(forms[i].name)) 347 | elif forms[i-1].kind notin {Symbol, Number, Command} or forms[i+1].kind notin {Symbol, Number, Command}: 348 | return CommandTree(kind: Error, line: command.line, message: "$1 must be surrounded by valid operands".format(forms[i].name)) 349 | else: 350 | let lastItem = newForms.pop() 351 | newForms.add(Form(kind: Command, tree: CommandTree(kind: Valid, line: command.line, name: forms[i].name, args: @[lastItem, forms[i+1]]))) 352 | i += 2 353 | else: 354 | newForms.add(forms[i]) 355 | i.inc 356 | forms = newForms 357 | toCommandTree(context, forms, command) 358 | 359 | proc parse*(context: var Context, line: string): CommandTree = 360 | let ret = extract(@[line]) 361 | if ret.len == 1: 362 | parse(context, ret[0]) 363 | else: 364 | CommandTree(kind: Error, message: "Parsing failed") 365 | 366 | proc parseOperatorCommands*(trees: seq[CommandTree]): seq[CommandTree] = 367 | var 368 | i = 0 369 | treesMut = trees 370 | while i < treesMut.len: 371 | var tree = treesMut[i] 372 | if tree.kind == Valid and tree.name in operatorCommands: 373 | var lastNonSkippedLine = i-1 374 | while lastNonSkippedLine >= 0: 375 | if not result[lastNonSkippedLine].skip: 376 | break 377 | lastNonSkippedLine.dec 378 | if i == 0 or i == treesMut.len - 1 or 379 | lastNonSkippedLine == -1 or 380 | result[lastNonSkippedLine].kind == Error or 381 | treesMut[i+1].kind == Error: 382 | result.add(CommandTree(kind: Error, line: tree.line, message: "$1 must have a valid command above and below it".format(tree.name))) 383 | i.inc 384 | else: 385 | var prevLine = result[lastNonSkippedLine] 386 | prevLine.skip = true # skip prev line when playing all lines 387 | var nextLine = treesMut[i+1] 388 | nextLine.skip = true # skip next line when playing all lines 389 | result[lastNonSkippedLine] = prevLine 390 | treesMut[i+1] = nextLine 391 | tree.args.add(Form(kind: Command, tree: prevLine)) 392 | tree.args.add(Form(kind: Command, tree: nextLine)) 393 | result.add(tree) 394 | result.add(nextLine) 395 | i += 2 396 | else: 397 | result.add(tree) 398 | i.inc 399 | 400 | proc toJson(form: Form, preserveNumberSigns: bool = false): JsonNode = 401 | case form.kind: 402 | of Whitespace, Operator: 403 | raise newException(Exception, $form.kind & " cannot be converted to JSON") 404 | of Symbol: 405 | result = JsonNode(kind: JString, str: form.name) 406 | of Number: 407 | # if the number is explicitly signed and we want to preserve that sign, 408 | # pass it to json as a string. 409 | # currently only necessary for relative octaves like /octave +1 410 | if form.signed and preserveNumberSigns: 411 | result = JsonNode(kind: JString, str: form.name) 412 | else: 413 | result = JsonNode(kind: JInt, num: strutils.parseBiggestInt(form.name)) 414 | of Command: 415 | var cmd: CommandMetadata 416 | if not getCommand(cmd, form.tree.name): 417 | raise newException(Exception, "Command not found: " & form.tree.name) 418 | case cmd.kind: 419 | of Play: 420 | result = JsonNode(kind: JArray) 421 | for arg in form.tree.args: 422 | result.elems.add(toJson(arg)) 423 | of Instrument: 424 | if form.tree.args[0].kind != Symbol: 425 | raise newException(Exception, "Instrument names must be symbols") 426 | result = JsonNode(kind: JString, str: form.tree.args[0].name) 427 | of PlayInstrument: 428 | result = instrumentToJson(form.tree.name, form.tree.args) 429 | of Attribute: 430 | result = attributeToJson(form.tree.name, form.tree.args) 431 | of Length: 432 | result = JsonNode(kind: JFloat, fnum: 1 / strutils.parseInt(form.tree.name[1 ..< form.tree.name.len])) 433 | of LengthWithNumerator: 434 | if form.tree.args[0].kind != Number or form.tree.args[1].kind != Number: 435 | raise newException(Exception, "Only numbers can be divided") 436 | result = JsonNode(kind: JFloat, fnum: strutils.parseInt(form.tree.args[0].name) / strutils.parseInt(form.tree.args[1].name)) 437 | of Concurrent: 438 | result = %*[{"mode": "concurrent"}, form.tree.args[0].toJson, form.tree.args[1].toJson] 439 | of ConcurrentLines: 440 | if form.tree.args.len != 2: 441 | raise newException(Exception, "$1 is not in a valid place".format(form.tree.name)) 442 | result = %*[{"mode": "concurrent"}, form.tree.args[0].toJson, form.tree.args[1].toJson] 443 | of Let: 444 | result = JsonNode(kind: JArray) 445 | for arg in form.tree.args[1 ..< form.tree.args.len]: 446 | result.elems.add(toJson(arg)) 447 | 448 | proc toJson*(tree: CommandTree): JsonNode = 449 | toJson(Form(kind: Command, tree: tree)) 450 | 451 | -------------------------------------------------------------------------------- /tests/config.nims: -------------------------------------------------------------------------------- 1 | switch("path", "$projectDir/../src") 2 | -------------------------------------------------------------------------------- /tests/hello.ansiwave: -------------------------------------------------------------------------------- 1 | Hello, world! 2 | 3 | /piano c c# d 4 | /octave 3 // this is a comment 5 | //this is a comment 6 | 7 | these are ignored because the command must start 8 | at the beginning of the line: 9 | /piano c c# /octave 4 d 10 | this is a comment /octave 3 11 | -------------------------------------------------------------------------------- /tests/hulk.ansiwave: -------------------------------------------------------------------------------- 1 |   2 |  Hulk Hogan ▄▄██████████▄▄▄  3 |  WWE Hall of Fame 2005  ▄▄░ █████████████▀▄▄  4 |  ▐█▌ █████████████░░▓█▌  5 |  ▐▓ █████████████▓▓▀▀▓▌  6 |  █░██████████████████▄▀  7 |  █████████████████████▄▌  8 |  ▐█████████▓▓▓▓▓▓████░░▓▌  9 |  █▓▄▄▄▄▄▄▄▄ ▄▄▄▀▀▀▀▀█▄█ ▄  10 |  ▀█▌  ▀██▌ ▐█▀ █░▌  11 |  ▐▌▀▒▄ ▄▄▀░▓▄  ▄▄▀▀▄▓▄▀▌  12 |  ▐▓▀▄▀▀▀▀▀▄▓█▄▀▀▓▀▀▀▄▄░▄ ▄  13 |  ▓▐▓▀▓▄▀▀▓▀▓█▀▀▀▄▓▀▀▓░▓▐▌▄  14 |   ▐█▌▀▀▄░ ▀▀ ░▓▄▀█▓▌▓ ░█ ░▌  15 |  ▐▐▌▐▄▓▓████████▓▄▓▌▓░▄▌░▐▌  16 |  ▐ █▐██▀▄▄▄▄▄▄▄▀██▌▓▓▀▄▌▓ ▀  17 |  ▐▄ ███▐██▄▄▄▄█▌██▓▓█▄▀ █▐ n!  18 |  ▐█▌▓██▐▓▓▓████▌███▀▀▀░▐█▐▌  19 |  ▐█▌ ▀▐▐░░░█▀██▌█▌▀▄▓░▒▐██▌  20 |  ▐▓▌▓ ▓▄▄▀▀▄▐▓▓▓▀░░▓ █▓▓▐▀▀▓  21 |  ▀ ▌▀ ▄ ▓▓▄▄▄▄▄▄▓▓▓ ▓▀▀▄██▄▄▄▄▄  22 |   ▀▄██ ▄ ▐░█░░░ ▓▄█░░░ ▀▄░░░▄▓▓▀▄▄▄▀  23 |  ▄▄▀▄███ ▄░░▐▓▓▓▓▓▓██▓▓▀▀▄▓▓▓██▀▀▄██▄▓▄▄  24 |  ▄▓▓▀▄█░░█▌▐▓▓▓█████▓▓█▓▀▀▄▄████▀▀▄▄▓▓░░██▀▀  25 |  ▄▄░█ ░░░▓▓█ ▀████████▀▀▀▄▄▄████▀▀▄▄██▀█▄▄▄▓▀▓▓▓▄  26 |  ▄▄▓▓█ ░▓▓▓██▓▄▄▀▀▓▓▓▀▀▄▄▄███████▀ ▄▀▄█▄██████▄░░█▀▄▄  27 |  ▄▀▀█▀ ▄▐█████████▄▄▄▄▄██████████▀ ▄▓▓▓▓███████▓▓▓ ▐█▄  28 |  ▀▄░▄▀ ▄██████████████████████████▌▐▓██░░▀▓▓████░▓█▌ ▐█▓▓  29 |   ▓▓▓█▀ ▄█████████████████████████▓█ ▓░█▐█▄░▄ ▀███░▓█▌░██░░▀  30 |   ▐▀██▀░▐██████████████████████████░█ ░█▐ ▀▓▓▄░░▓██▓█▓█▓█▓█░░  31 |   ▄▄█░▓ ▐▓█████████████████████████▐▓▌▐▌▐ ░░▀▓▓▓███▀█▐██░█▓▓▌  32 |  ▄▄██░ ▓ ▓▒████████▀█████████████████░▀ ▓ ▀▓ ▀▓████▌██▓████▒▒▌  33 |   ▄▄███░░░  ░░▌▐▀▀█▀░▀█▀█▀▀█▀█▀█████████▌█  ▓ ▄█ ▐▓▓██▓▄█▓▓█░█░░▌  34 |  ▄▀██▓▓▓▓░ ▌▐█▌▐ ░▄▌▀ ▌ ▀ ▐ ▌▐ ▐ █ █▀ ▐██░░▀▄█░░░▄█████▓░█░▒█▓███  35 |   ▄▓████▀▄█▓▌▓ █▐█ ▄▀ ▐ ▌▐░ ▌░ ▌▐ ░▌▐ ▀ ██▀█ ▓▄▓▓▓▓█████▓░██▓░███░▐  36 |   ▄▀████▌▓██░▓▌▐▄▓▀▀██▄▀█▄█▌▐▄█▐▌█▌▐░▌▐▌▐█░█▌▐██▓▓████████▓░█▓▓███▒▌  37 |   █▀▀▄██▓░▄██ ░ ▐█▌█████████████████████░█▌██ ▓█░░░▓▓█████▓▓▓░▓░█▌░▓▌  38 |  ▄▄██▌▄██▓█▌▓  ▐█████████████████████████░█▌▐███ ░░▌▌████▓█▓█▓█▌░░  39 |   ▄▄███▓▓▓█▓░█▌▓▓ █████████████████████████▌██▌▐██▌▌▓▐ ▌▌████████▐█░█▐  40 |   ▄████▓░░░▀░█▓█▓▓ █████████████████████████▌██▌▐▓█▌░░░ ▌████████▐██░▌  41 |  ▄████▓░ ▄▄▄▀▀░░▀ ███████████████████████▓▓▄▄██ ░██▄ ░░▄█████▓▓▓██▓█▄  42 |  ▀░████▀▀ ▀▄▓▓▄▄ ████████████████████▀▀███████▌▐▀███▓▓██████▓█▌▐█░▄  43 |  ▐▓█▓▀▀▄▄▀▄▄▀▀▓▀██▄▄▄▄▀████████████████▄████▓▓░░███  ▀█████████▌█▓▓█▓▄  44 |  ▀▀ ▄▄▀▓▓█▀░▓▄▄▓█▌▐▓█▌▐███████████████████▀▀▀▀▀▀▀ ░░▓▓████████ ▓ █▓▄  45 |   ▄▐█▄▀▓▓▄▀██▄▀▓▓▄▓█▄▀ ▄██████████████▀▀▀▀▀▄▄▄▄▄████████▓▓█████▌▓░▐▓▄  46 |  ▐▓▄▀▓▄▄▓▓▄▀▀▓▄▀▄▓▄▓██ ▄▄▄▄▄ ▀▀▀▀▀▀▀▀▄▄▄▄▄███████▓▓▓▀▀▄██▓▓▓█▀░█░▌█▄  47 |  ▀▀▄▄▀▀▀▀█▄ ▀█▄▀▀▓ ▓▓▌▐████▐█▌▓░▓█████████▀▀▀▓▓▓▄▄▄███▓▓░░██▓▓▌▓▐▄  48 |  █▄▀▀▀▀░░▀▓▓▄▀▓▓ ░▓▓█▌▐██████▌▐██▓▓▓▓▓▄▄▓▄▄▄▄███▓▓▓▓▓░░░█▀█▄█▓▐█▓  49 |   ▒▀▓███▄▄▄▄▄▓▄▄▓▐▐▐▀ ███████▌▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░▀▀▄█▄▓██  █  50 |   ▀▄▓▓█▓▓█▓▓▀▀█▀█▐▐▌▐████▓▓▓ ▐░░░░░░░░░░░ ▀▀▓▓▄▄█▄▄▓▓███▀ █  51 |     ▀▀▀▀▀▓▓▓░░██ ▓▓▓█░░░░ ▓▓▓▓███ ▀▀▀ ▄▄▄▓▓▓▓██████▓ ▀  52 |  ▄ ▀▀▀▀  ░░░▓▐ ▌░▓░░▒▒▓▓█████▓▓▓▓▓▓▓█████▓▓▀▀▀ ▄  53 |  ▐▄ ▄█ ▀▀▀ ▄▄▄▄▄▀▀▀█▀▀▀▀▀▀▀██▀▀▀▀▀▄▄▄▄░▒▓██  54 |  ▓▌ ▐▓▌ █████████████▄▄▄▄▄▄▄ █░▓██▓▒░█░▒▓██  55 |  ░▌ ▐░ ▀▀▓▓▓▓▓▓▓▓▓▓▓▓▀▀▀▓▓▓ ▄▌ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓  56 |  ▌▌ ▌▌ ▐█ ▒▒▒▒▒▒▒▒▒▒ ▄ ░░░ ▓▌ ░░░░░░░  57 |  ▌▌ ░▌ ▌▓ █▄ ░░░░░░░░░░ ▓ ▒▌ ▄▄▄  58 |  ▐▐ ▓▌ ▐░▐▓▌ ▒▌ ░ ▄█▄ █▌▄█▀ ▄  59 |  ▐▐ ░ ▄░▄█▄ ▐▌▐░ ░▌ ██▀░▓▄█▐░▄ █▄  ▄▄▄▄ ▄▄▄  60 |  ▐░ ██▀▄█▀█▀█▌▐▐ ▀█ ▄▄▄ ▌▐ ▄█░ ▀██▀ ▀▄█ ▐▓▄░░█▄▀▀▀▀  61 |  ▐░ █░ ▀▌▌▐▐▐░ ▄▄▀▄▀▀ ▐░ █▀ █ ▐█▌ ░░ █  62 |  █ ░ ▐▌▐█ ▄██▄▀ ▐█ █ ▐█ ▀  63 |  ▐█ █ ▐█ █ ▐█▌ █▌ █▌  64 |  █▌ █▌ █ ▀ ▄▌ █▌ ▐█  65 |  ▀▀ ▐▌ ▐▌ █▌ █▌  66 |  ▀ █ █▌ ▐█▌  67 |  ▀ ▀  68 | →SAUCE00hulk smash nail blocktronics  69 |  20200204½* ☺☺P C ¶IBM VGA  70 |  71 | 72 | -------------------------------------------------------------------------------- /tests/privkey: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansiwave/wavecore/836ce54862804c521478df8cac7c0b130a993567/tests/privkey -------------------------------------------------------------------------------- /tests/variables.ansiwave: -------------------------------------------------------------------------------- 1 | /let attrs /tempo 74 /octave 3 2 | /let chord1 d-,a-,e,f# 3 | /let chord2 f#,d+ 4 | 5 | /organ /attrs /8 /chord1 a /2 /chord2 -------------------------------------------------------------------------------- /wavecore.nimble: -------------------------------------------------------------------------------- 1 | # Package 2 | 3 | version = "0.9.1" 4 | author = "oakes" 5 | description = "Server and client utils for ANSIWAVE BBS" 6 | license = "Public Domain" 7 | srcDir = "src" 8 | installExt = @["nim", "c", "h"] 9 | bin = @["wavecore"] 10 | 11 | task dev, "Run dev version": 12 | exec "nimble run wavecore --testrun --disable-limbo" 13 | 14 | # Dependencies 15 | 16 | requires "nim >= 1.2.6" 17 | requires "puppy >= 2.1.2" 18 | requires "flatty >= 0.3.4" 19 | requires "paramidi >= 0.6.0" 20 | requires "threading >= 0.1.0" 21 | requires "ansiutils >= 0.2.0" 22 | --------------------------------------------------------------------------------