├── .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 | [0m[40;37m [0m
2 | [0m[40m [31mHulk Hogan ▄▄██████████[1;41m▄[22;40m▄▄ [0m
3 | [0m[40m[37m [1;30mWWE Hall of Fame 2005 [22m [31m▄[1;41m▄░ [22;40m█████████████[1;41m▀▄[22;40m▄ [0m
4 | [0m[40m[37m [31m▐[1;41m█▌ [22;40m█████████████[1;41m░░▓█[22;40m▌ [0m
5 | [0m[40m[37m [31m▐[1;41m▓ [22;40m█████████████▓▓▀[1;41m▀▓[22;40m▌ [0m
6 | [0m[40m █[1;41m░[22;40m██████████████████▄▀ [0m
7 | [0m[40m █████████████████████▄▌ [0m
8 | [0m[40m ▐█████████▓▓▓▓▓▓████[1;41m░░▓[22;40m▌ [0m
9 | [0m[40m █▓[41;33m▄[1m▄▄[40m▄[37m▄▄[33m▄[22m▄[31m [1;33m▄[43m▄▄▀▀[40m▀▀▀█[41;37m▄[22;40;31m█[37m [31m▄ [0m
10 | [0m[40m ▀[1;33m█▌[22m [1;33m [22m▀[1m██▌ [22m▐[1m█▀[22m [31m█[1;41m░[22;40m▌ [0m
11 | [0m[40m [33m▐▌▀[1m▒[22m▄[37m [33m▄[1;43m▄▀[22;40m░[1m▓[22m▄[37m [33m ▄[1;43m▄▀[22;40m▀▄[1m▓▄[22;31m▀▌ [0m
12 | [0m[40m [33m▐▓[1;47;31m▀[22;40;33m▄▀▀[1m▀▀▀[22m▄[1;47;31m▓█[22;40;33m▄[1m▀▀[22m▓[1;43m▀[40m▀[22m▀▄[1;43;31m▄[47m░[22;40m▄ [33m▄ [0m
13 | [0m[40m ▓[37m▐[1;47;31m▓[43m▀[47m▓▄[43m▀[22;40;33m▀[1;47;31m▓▀▓[43m█▀[22;40;33m▀[1;43;31m▀[47m▄▓[43m▀[22;40;33m▀[1;47;31m▓[33m░▓[22;40m▐▌[31m▄ [0m
14 | [0m[40m [33m [37m▐[1;43;31m█[22;40;33m▌[1;43;31m▀[22;40;33m▀▄[1;31m░[22m [33m▀▀[37m [33m░▓▄▀[1;43;31m█[47m▓[22;40;33m▌[1;47;31m▓ [33m░[22;40m█[1m [41;31m░[22;40m▌ [0m
15 | [0m[40m [33m▐[1;31m▐[43m▌[22;40;33m▐[1;43m▄[40m▓▓██[37m██████▓[43;33m▄[31m▓[40m▌[47m▓░▄[22;40;33m▌[1;37m░[22;31m▐▌ [0m
16 | [0m[40m [1;33m▐[22m [33m█[1m▐██▀[22m▄[1;31m▄▄▄▄▄[22;33m▄[1m▀█[37m█[43;33m▌[31m▓[47m▓[43m▀[22;40;33m▄▌[1;37m▓[33m [22;31m▀ [0m
17 | [0m[40m [1;37m▐[43;33m▄[40m █[37m██[22;33m▐[1;43;31m██[22;40;33m▄▄▄[1;43;31m▄█[22;40;33m▌[1m█[37m█▓[43;31m▓[47m█[22;40;33m▄[1;47;31m▀[22;40m [1m█[33m▐[22m [31mn! [0m
18 | [0m[40m [1;37m▐[33m█[22m▌[1;37m▓██[31m▐[47m▓▓▓[43m████[22;40;33m▌[1m█[37m██[43;31m▀[22;40;33m▀[1;47;31m▀░[40;33m▐[37m█[33m▐▌ [0m
19 | [0m[40m [43m▐[40m█▌[22m [1m▀▐[22;33m▐[1;47;31m░░░[43m█[47m▀[43m██[22;40;33m▌[1m█▌▀[22m▄[1;47;31m▓░▒[40;33m▐██▌ [0m
20 | [0m[40m [22m▐[1m▓▌▓[22m [33m▓[1;31m▄[22;33m▄▀[1;43;31m▀[47m▄[22;40;33m▐[1;43;31m▓▓[22;40;33m▓▀░░▓[1;43;31m █[47m▓▓[40;33m▐▀▀▓ [0m
21 | [0m[40m [22m▀[37m [1;33m▌▀[22m [1;43;31m▄[47m [22;40;33m▓▓[1;31m▄[22;33m▄▄▄▄▄▓▓▓[1;43;31m ▓▀[22;40;33m▀[37m▄██▄▄[1;33m▄▄[22m▄ [0m
22 | [0m[40m [33m ▀[37m▄██[33m [1;43;31m▄[47m ▐[43m░[22;40;33m█[1;47;31m░[43m░░ ▓▄[22;40;33m█[1;43;31m░░░ [22;40;33m▀[37m▄[1;47;33m░░░▄▓▓[22;40m▀[33m▄▄▄[1m▀ [0m
23 | [0m[40m [22m▄▄▀[37m▄███ [1;43;31m▄[47m░░▐[43m▓▓[47m▓[43m▓▓▓██▓▓▀[22;40;33m▀[37m▄[1;47;33m▓▓▓[40m██[47m▀[22;40m▀[33m▄██[1;43;31m▄▓▄[22;40;33m▄ [0m
24 | [0m[40m ▄▓▓▀[37m▄█[1;47;33m░░[22;40m█▌[33m▐[1;47;31m▓▓▓█[43m██[47m█[43m█[47m▓▓[43m█▓▀[22;40;33m▀[37m▄[1;47;33m▄██[40m██[47m▀[22;40m▀[33m▄[1;43;31m▄▓▓░░[22;40;33m██▀▀ [0m
25 | [0m[40m ▄[1;43;31m▄░[22;40;33m█ [1;47m░░░▓▓[22;40m█[1;33m [43;31m▀███[40m█[43m████▀[40m▀[22;33m▀[37m▄[1;33m▄[47m▄[40m████[47m▀[22;40m▀[33m▄[1;43;31m▄██▀[22;40;33m█[1;43;31m▄▄[22;40;33m▄[1;43;31m▓▀[22;40;33m▓▓▓▄ [0m
26 | [0m[40m ▄[1;43;31m▄▓▓[22;40;33m█[37m [1;47;33m░▓▓▓[40m██[47m▓▄[22;40m▄[33m▀[1;31m▀▓▓▓▀[22;33m▀[37m▄[1;33m▄[47m▄[40m███████[47m▀[22;40m [1;43;31m▄▀▄█▄[40m█[47m██[40m███[43m▄░░[22;40;33m█[1;43;31m▀▄[22;40;33m▄ [0m
27 | [0m[40m [1;43;31m▄[47m▀▀█[43m▀[22;40m [1;47;33m▄▐[40m█████████[47m▄[40m▄▄▄[47m▄[40m██████████[47m▀[22;40m [1;43;31m▄▓▓[47m▓▓[40m███████[47m▓[43m▓▓ ▐█▄[22;40m [0m
28 | [0m[40m [1;47;31m▀[33m▄[31m░▄[43m▀[22;40m [1;47;33m▄█[40;37m██[33m███████████████████████[22m▌[33m▐[1;43;31m▓[22;40;33m█[1;43;31m█[47m░░▀▓▓[40m█[47m███░▓[43m█▌ ▐[40m█[47m▓▓[22;40m [0m
29 | [0m[40m [1;31m ▓[47m▓▓█[43m▀[22;40m [1;47;33m▄[40;37m████[33m█████████████████████[47m▓[40m█[22m [1;43;31m▓░[22;40;33m█[1;43;31m▐[40m█[47m▄[33m░▄[31m ▀███░▓[43m█▌░[40m██[47m░░▀[22;40m [0m
30 | [0m[40m [1;31m [22;33m▐▀[1;47;31m██[43m▀[22;40;33m░[37m▐[1;33m█[37m████[33m█████████████████████[47m░[40m█[22m [1;43;31m░[22;40;33m█▐[1;43;31m ▀▓▓[47m▄░░▓█[40m█[47m▓[43m█▓[22;40;33m█[1;43;31m▓[40m█[47m▓[22;40m█[1;47;33m░░[22;40m [0m
31 | [0m[40m [1;31m [22;33m▄[1;43;31m▄[47m█[43m░[22;40;33m▓[37m [1;47;33m▐▓[40;37m███[33m██████████████████████[47m▐▓[22;40m▌[33m▐▌▐[1;43;31m ░░▀[47m▓▓▓█[40m██[43m▀[22;40;33m█[1;43;31m▐[40m██[47m░[22;40m█[1;47;33m▓▓[40;31m▌ [0m
32 | [0m[40m [22;33m▄[1;43;31m▄[47m██[43m░ [22;40;33m▓[37m [1;47;33m▓▒[40m████████[41m▀[40m█████████████████[47m░▀[22;40m [33m▓ ▀▓[1;43;31m ▀▓[40m████[43m▌[22;40;33m██[1;43;31m▓[40m██[22m██[1;47;33m▒▒[22;40m▌ [0m
33 | [0m[40m [1;31m [22;33m▄[1;43;31m▄[40m███[43m░░░ [22;40m [1;47;33m░░[41m▌▐▀▀[40m█[41m▀░▀█▀█▀▀[40m█[41m▀[40m█[41m▀[40m█[41m█[40m███████[47m▌[22;40m█[1;47;33m [40m [22m▓ ▄█[1;43;31m ▐[47m▓▓█[40m█[43m▓▄[22;40;33m█[1;43;31m▓▓[40m█[47m░[22;40m█[1;47;33m░░[40;31m▌ [0m
34 | [0m[40m [43m▄[47m▀[40m██[43m▓▓▓▓░ [22;40;33m▌[37m▐█[1;41;33m▌▐ ░▄▌▀ ▌ ▀ ▐ ▌▐ ▐ █ [40m█[41m▀ ▐[40m██[47m░░[22;40m▀[33m▄█[1;43;31m░░░▄[40m███[47m█[40m█[43m▓░[22;40;33m█[1;43;31m░▒[40m█[47m▓[22;40m███ [0m
35 | [0m[40m [1;31m [43m▄[47m▓[40m██[43m██[47m▀▄[43m█▓▌[22;40;33m▓[1m [22m█[1;47;33m▐[40m█[41m ▄▀ ▐ ▌▐░ ▌░ ▌▐ ░▌▐ ▀ [40m██[47m▀[22;40m█[31m [33m▓[1;43;31m▄▓▓▓▓[40m████[43m█▓░[22;40;33m██▓[1;43;31m░[40m██[22m█[1;47;33m░[31m▐[22;40m [0m
36 | [0m[40m [1;31m [43m▄[47m▀[40m███[43m█[47m▌▓[43m██░▓[22;40;33m▌[1m▐[47m▄[30m▓[41;33m▀▀█[40m█[41m▄▀[40m█[41m▄[40m█[41m▌▐▄[40m█[41m▐▌[40m█[41m▌▐░▌▐▌▐[40m█[47m░[22;40m█▌[33m▐[1;43;31m██[47m▓▓█[40m█████[43m██▓░[22;40;33m█▓▓[1;31m██[22m█[1;47;33m▒[22;40m▌ [0m
37 | [0m[40m [1;31m [22;33m█▀[1;43;31m▀▄[40m█[47m█▓░▄[43m██ ░[22;40m [1;47;33m▐[40m█[47m▌[40m█████[41m█[40m███████████████[41m░[40m█[47m▌[22;40m██ [1;43;31m▓█[47m░░░▓▓[40m█████[47m▓▓[43m▓░[22;40;33m▓[1;43;31m░[40m█[47m▌[33m░▓[40;31m▌ [0m
38 | [0m[40m [22;33m▄[1;43;31m▄[40m██[47m▌▄█[43m█▓█▌[22;40;33m▓[1;43;31m [22;40m [1;47;33m▐[40m█████████████████████████[47m░[22;40m█▌[33m▐[1;31m█[22m██[1;47;33m [31m░░▌▌[40m███[43m█[47m▓[43m█▓[22;40;33m█[1;43;31m▓[40m█[47m▌[33m░░[22;40m [0m
39 | [0m[40m [1;31m [22;33m▄[43;37m▄[1;40;31m███[43m▓▓▓█▓░█▌[22;40;33m▓▓[37m [1;33m█████████████████████████[47m▌[22;40m██▌[1;31m▐█[22m█[1;47;31m▌[33m▌▓▐[31m ▌▌[40m███[43m████[22;40;33m█[1;43;31m▐[40m█[47m░[22;40m█[1;47;31m▐[22;40m [0m
40 | [0m[40m [1;31m [22;43m▄[1;47;31m█[40m███[43m▓░░░▀░[22;40;33m█[1;43;31m▓[22;40;33m█▓▓[37m [1;33m██████████████████████[37m██[33m█[47m▌[22;40m██▌[33m▐[1;43;31m▓[40m█[47m▌[33m░░░[31m ▌[40m███[43m████[22;40;33m█[1;43;31m▐[47m█[22;40m█[1;47;33m░[40;31m▌ [0m
41 | [0m[40m [43m▄[47m█[40m██[43m█▓░[22;40;33m [1;31m▄▄[22;33m▄▀▀[1;43;31m░░[22;40;33m▀[37m [1;33m███████████████████████[47m▓▓▄▄[22;40m██[1;31m [43m░[40m██[47m▄ ░░▄[40m███[43m██[47m▓▓[43m▓[22;40;33m█[1;31m█[47m▓[22;40m█[1;47;31m▄[22;40m [0m
42 | [0m[40m [1;47;31m▀░[43m████▀[22;40;33m▀[37m [1;43;31m▀[47m▄▓[43m▓▄[22;40;33m▄[37m [1;33m████████████████████[47m▀▀[22;40m███████▌[33m▐[1;43;31m▀[40m███[47m▓▓[40m████[43m██[47m▓[43m█▌▐[40m█[47m░▄[22;40m [0m
43 | [0m[40m ▐[1;47;33m▓[22;40m█[1;47;31m▓[43m▀[22;40;33m▀▄[1;31m▄[47m▀[43m▄[22;40;33m▄▀▀▓[1;43;31m▀[22;40;33m█[1;31m█[22;33m▄[1;31m▄▄[22;33m▄[1m▀████████████████[47m▄[40m████[47m▓▓░░[22;40m███[1;31m [43m ▀[40m████████[43m█▌█▓[22;40;33m▓[1;31m█[47m▓▄[22;40m [0m
44 | [0m[40m [33m▀[37m▀ [33m▄[1;43;31m▄[47m▀▓▓[40m█[43m▀░▓▄[22;40;33m▄▓█[1;31m▌[43m▐[47m▓[43m█[22;40;33m▌[1m▐███████████████████[47m▀▀[40m▀[22m▀▀▀▀ [1;43;31m░░▓▓[40m██████[43m██ ▓ [40m█[47m▓▄[22;40m [0m
45 | [0m[40m [33m ▄▐[1;31m█[22;33m▄▀[1;43;31m▓▓[22;40;33m▄▀[1;31m██[22;33m▄▀[1;43;31m▓▓[22;40;33m▄▓[1;31m█▄▀[22m [1;33m▄██████████████[47m▀▀[40m▀▀▀[22m▄▄[1;31m▄[43m▄▄[40m████████[47m▓▓[40m███[43m██▌[22;40;33m▓[1;43;31m░[40m▐[47m▓▄[22;40m [0m
46 | [0m[40m [33m▐[1;47;31m▓[40m▄[22;33m▀[1;47;31m▓[43m▄[22;40;33m▄[1;43;31m▓[47m▓[22;40;33m▄▀[1;43;31m▀[47m▓[22;40;33m▄▀[1;43;31m▄▓[22;40;33m▄▓[1;31m██[22m [1;33m▄[37m▄▄▄[33m▄[22m [37m▀▀▀[1;33m▀▀▀▀▀[22m▄▄[1;31m▄[43m▄▄[40m███████[47m▓▓▓▀▀▄[40m██[43m▓▓▓[40m█[43m▀░[22;40;33m█[1;43;31m░[22;40;33m▌[1;31m█[47m▄[22;40m [0m
47 | [0m[40m [33m▀[1;43;31m▀[22m▄[40;33m▄[1;31m▀[22;33m▀▀[1;43;31m▀[40m█[43m▄[22;40m [1;43;31m▀[40m█[22;33m▄▀[1;43;31m▀[40m▓[22;33m [1;47;31m▓▓[22;40;33m▌[1m▐████▐█▌[22m▓[1;43;31m░▓[40m██████[47m███▀▀▀▓▓▓▄▄▄[40m███[43m▓▓░░[22;40;33m██[1;43;31m▓▓[22;40;33m▌[1;43;31m▓[40m▐[47m▄[22;40m [0m
48 | [0m[40m [33m█▄▀[1;31m▀▀[22;33m▀░░▀[1;31m▓▓[22;33m▄▀[1;31m▓▓[22m [33m░▓[1;43;31m▓█[22;40;33m▌[1m▐██████▌[31m▐██[47m▓▓▓▓▓▄▄▓▄▄▄▄[40m███[43m▓▓▓▓▓░░░[22;40;33m█▀█[1;43;31m▄█▓[22;40;33m▐[1;31m█[47m▓[22;40m [0m
49 | [0m[40m [33m [1;31m▒▀▓███[43m▄▄[40m▄▄[22;33m▄[1;31m▓[22;33m▄▄▓[1;43;31m▐▐▐▀[40;33m ███████▌[31m▐[43m▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░[22;40;33m▀▀▄█[1;43;31m▄▓██ [40m █ [0m
50 | [0m[40m[22m [33m ▀▄[1;31m▓▓█[47m▓▓[40m█[43m▓▓▀▀[22;40;33m█[1;43;31m▀[22;40;33m█[1;43;31m▐▐[22;40;33m▌[1m▐████[47m▓▓▓[22;40m [33m▐[1;43;31m░░░░░░░░░░░ [22;40;33m▀▀▓▓▄▄█[1;43;31m▄▄▓▓[40m█[43m██▀[22;40;33m [1;31m█ [0m
51 | [0m[40m [22;33m [37m [33m ▀▀[1;31m▀[43m▀▀▓▓▓░░[22;40;33m██[1m [47m▓▓▓[40m█[22m░[1;47;33m░░░[22;40m [33m▓▓▓▓███[1;43;31m ▀▀▀ ▄▄▄▓▓▓▓██████[40m▓[22;33m ▀ [0m
52 | [0m[40m [1;30m▄ [22;33m▀▀▀▀[1m [47m ░░░[22;40m▓▐[1;47;33m [22;40m▌[33m░▓[1;43;31m░░▒▒▓▓█████▓▓▓▓▓▓▓█████[40m▓▓▀▀[22;33m▀ [1;37m▄ [0m
53 | [0m[40m [22m▐[1;47;33m▄ ▄[22;40m█ ▀▀▀[31m ▄▄▄▄▄[33m▀▀▀█[1;43;31m▀▀▀▀▀▀▀[22;40;33m██▀▀▀▀▀[31m▄▄▄▄[1;41;37m░▒▓██[22;40m [0m
54 | [0m[40m [1;47;33m▓[22;40m▌ [1;30m▐[47;33m▓[22;40m▌ [31m█████████████▄▄▄▄▄▄▄[1;37m [22;31m█[1;41m░▓██▓▒░[22;40m█[1;41;37m░▒▓██[22;40m [0m
55 | [0m[40m [1;47;33m░[22;40m▌ ▐[1;47;33m░ [22;40;31m▀▀▓▓▓▓▓▓▓▓▓▓▓▓▀▀▀▓▓▓[37m [1;47;33m▄[22;40m▌ [31m▓▓[1m▓▓▓▓▓[22m▓▓▓[1;37m▓▓▓▓ [0m
56 | [0m[40m [47;30m▌[22;40m▌ [1;47;30m▌[22;40m▌ ▐[1;47;33m█ [22;40;31m▒▒▒▒▒▒▒▒▒▒[37m ▄ [31m░░░[37m [1;47;33m▓[22;40m▌ [31m░░░[1;37m░░░░ [0m
57 | [0m[40m [47;30m▌[22;40m▌ [1;47;33m░[22;40m▌ [1;47;30m▌[33m▓[22;40m █▄ [31m░░░░░░░░░░[37m [1;47;33m▓ ▒[40;30m▌[22m ▄▄[1;30m▄ [0m
58 | [0m[40m [22m▐[1;47;30m▐[22;40m [1;47;33m▓[40;30m▌ [22m▐[1;47;33m░[40;30m▐[47;33m▓[22;40m▌ [1;47;33m▒[22;40m▌ [1;47;33m░[22;40m ▄█[1;47;30m▄[22;40m █[1;30m▌[22m▄█[1;47;30m▀[22;40m [1;30m▄ [0m
59 | [0m[40m [22m▐[1;47;30m▐[22;40m [1;47;33m░[22;40m ▄[1;47;33m░[22;40m▄█[1;30m▄[22m ▐▌▐[1;47;33m░ ░[22;40m▌ ██▀[1;47;33m░▓[40;30m▄[22m█▐[1;47;33m░[22;40m▄ █[1;47;33m▄ [22;40m [1;30m▄[22m▄▄[1;30m▄[22m [1;30m▄[22m▄▄ [0m
60 | [0m[40m ▐[1;47;33m░[22;40m ██▀[1;47;30m▄[22;40m█▀█[1;47;30m▀[22;40m█▌▐[1;47;30m▐[22;40m [1;47;30m▀[22;40m█ [1;30m▄[22m▄▄ [1;47;30m▌▐[22;40m ▄█[1;47;33m░[22;40m ▀██▀ [1;30m▀[47m▄[22;40m█ ▐[1;47;33m▓[30m▄[33m░░[22;40m█[1;47;30m▄[22;40m▀▀▀[1;30m▀ [0m
61 | [0m[40m ▐[47;33m░[22;40m █[1;47;33m░ [22;40m▀[1;47;30m▌[22;40m▌▐[1;47;30m▐[22;40m▐[1;47;33m░[22;40m [1;30m▄[22m▄[1;47;30m▀▄[22;40m▀[1;30m▀[22m ▐[1;47;33m░[22;40m █▀ █ [1;30m▐[22m█▌ [1;47;33m░░[22;40m █ [0m
62 | [0m[40m █ [1;47;33m░ [22;40m▐▌[1;30m▐[22m█ [1;47;30m▄[22;40m██[1;47;30m▄[40m▀ [22m▐█ █ ▐█ [1;30m▀ [0m
63 | [0m[40m [22m▐█ █ [1;30m▐[22m█ █ [1;30m▐[22m█▌ █[1;30m▌ [22m█▌ [0m
64 | [0m[40m █[1;30m▌[22m █[1;30m▌ [22m█ ▀ [1;47;30m▄[22;40m▌ █[1;30m▌ [22m▐█ [0m
65 | [0m[40m [1;30m▀[22m▀ ▐▌ ▐▌ █▌ █▌ [0m
66 | [0m[40m ▀ █ █▌ ▐█[1;30m▌ [0m
67 | [0m[40m [22m▀ ▀ [0m
68 | [0m[40m→SAUCE00hulk smash nail blocktronics [0m
69 | [0m[40m 20200204½* ☺☺P C ¶IBM VGA [0m
70 | [0m
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 |
--------------------------------------------------------------------------------