fresco 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/exe/fresco +3 -0
  3. data/lib/fresco/application.rb +12 -0
  4. data/lib/fresco/cli/build.rb +682 -0
  5. data/lib/fresco/cli/dev.rb +17 -0
  6. data/lib/fresco/cli/dev_loop.rb +815 -0
  7. data/lib/fresco/cli/new.rb +120 -0
  8. data/lib/fresco/cli/release.rb +76 -0
  9. data/lib/fresco/cli.rb +56 -0
  10. data/lib/fresco/database_config.rb +34 -0
  11. data/lib/fresco/generators/app/Gemfile.tt +18 -0
  12. data/lib/fresco/generators/app/README.md.tt +32 -0
  13. data/lib/fresco/generators/app/app/action.rb.tt +20 -0
  14. data/lib/fresco/generators/app/app/actions/root_path.rb.tt +5 -0
  15. data/lib/fresco/generators/app/app/views/layouts/application.html.erb +29 -0
  16. data/lib/fresco/generators/app/app/views/root_path.html.erb +8 -0
  17. data/lib/fresco/generators/app/app.rb.tt +15 -0
  18. data/lib/fresco/generators/app/bin/build +2 -0
  19. data/lib/fresco/generators/app/bin/dev +2 -0
  20. data/lib/fresco/generators/app/bin/release +2 -0
  21. data/lib/fresco/generators/app/config/app.rb.tt +26 -0
  22. data/lib/fresco/generators/app/config/database.rb +17 -0
  23. data/lib/fresco/generators/app/config/routes.rb +11 -0
  24. data/lib/fresco/generators/app/db/schema.rb +14 -0
  25. data/lib/fresco/generators/app/public/404.html +87 -0
  26. data/lib/fresco/generators/app/public/500.html +84 -0
  27. data/lib/fresco/migration_builder.rb +55 -0
  28. data/lib/fresco/model_builder.rb +54 -0
  29. data/lib/fresco/paths.rb +20 -0
  30. data/lib/fresco/router.rb +67 -0
  31. data/lib/fresco/runtime/boot.rb +34 -0
  32. data/lib/fresco/runtime/db_postgres.rb +403 -0
  33. data/lib/fresco/runtime/db_sqlite.rb +495 -0
  34. data/lib/fresco/runtime/http.c +456 -0
  35. data/lib/fresco/runtime/postgres.c +339 -0
  36. data/lib/fresco/runtime/runtime.rb +1810 -0
  37. data/lib/fresco/runtime/sqlite.c +220 -0
  38. data/lib/fresco/runtime/welcome.rb +152 -0
  39. data/lib/fresco/schema_builder.rb +71 -0
  40. data/lib/fresco/templates/dispatch.rb.erb +32 -0
  41. data/lib/fresco/templates/layout_dispatch.rb.erb +16 -0
  42. data/lib/fresco/templates/manifest.rb.erb +5 -0
  43. data/lib/fresco/templates/migrations.rb.erb +152 -0
  44. data/lib/fresco/templates/model.rb.erb +223 -0
  45. data/lib/fresco/templates/view.rb.erb +5 -0
  46. data/lib/fresco/version.rb +3 -0
  47. data/lib/fresco.rb +61 -0
  48. metadata +115 -0
@@ -0,0 +1,456 @@
1
+ /*
2
+ * Spinel HTTP shim. Owns sockets, fork/wait, and a fixed-size request
3
+ * buffer. Ruby reads via sphttp_request_buf() with no copy. Surface
4
+ * locked at M1 so later milestones don't have to revisit FFI shape.
5
+ *
6
+ * Lifted from tep's lib/tep/sphttp.c in spirit; trimmed to the symbols
7
+ * the framework's runtime needs.
8
+ */
9
+
10
+ #include <errno.h>
11
+ #include <fcntl.h>
12
+ #include <stdint.h>
13
+ #include <stdio.h>
14
+ #include <stdlib.h>
15
+ #include <string.h>
16
+ #include <time.h>
17
+ #include <sys/socket.h>
18
+ #include <sys/stat.h>
19
+ #include <sys/wait.h>
20
+ #include <netinet/in.h>
21
+ #include <netinet/tcp.h>
22
+ #include <arpa/inet.h>
23
+ #include <unistd.h>
24
+
25
+ #define SPHTTP_REQ_BUF_SIZE 65536
26
+ #define SPHTTP_BODY_BUF_SIZE (1024 * 1024)
27
+
28
+ static char sphttp_req_buf_[SPHTTP_REQ_BUF_SIZE + 1];
29
+ static int sphttp_req_buf_len_ = 0;
30
+ static char sphttp_body_buf_[SPHTTP_BODY_BUF_SIZE + 1];
31
+
32
+ /* Find first occurrence of `needle` (length nl) in haystack[0..hl).
33
+ * Stand-in for memmem(3) — non-portable on a few targets. */
34
+ static const char *find_bytes(const char *h, int hl, const char *needle, int nl) {
35
+ if (nl == 0 || hl < nl) return NULL;
36
+ int last = hl - nl;
37
+ for (int i = 0; i <= last; i++) {
38
+ if (memcmp(h + i, needle, (size_t)nl) == 0) return h + i;
39
+ }
40
+ return NULL;
41
+ }
42
+
43
+ int sphttp_listen(int port, int reuseport) {
44
+ int sfd = socket(AF_INET, SOCK_STREAM, 0);
45
+ if (sfd < 0) return -1;
46
+
47
+ int one = 1;
48
+ setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
49
+ if (reuseport) {
50
+ #ifdef SO_REUSEPORT
51
+ setsockopt(sfd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one));
52
+ #endif
53
+ }
54
+
55
+ struct sockaddr_in addr;
56
+ memset(&addr, 0, sizeof(addr));
57
+ addr.sin_family = AF_INET;
58
+ addr.sin_port = htons((uint16_t)port);
59
+ addr.sin_addr.s_addr = htonl(INADDR_ANY);
60
+
61
+ if (bind(sfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
62
+ close(sfd);
63
+ return -1;
64
+ }
65
+ if (listen(sfd, 128) < 0) {
66
+ close(sfd);
67
+ return -1;
68
+ }
69
+ return sfd;
70
+ }
71
+
72
+ int sphttp_accept(int sfd) {
73
+ return accept(sfd, NULL, NULL);
74
+ }
75
+
76
+ /* Read until end-of-headers (CRLFCRLF). Stops at buffer-full or EOF.
77
+ * Returns bytes read (>0), 0 on clean close, -1 on error. */
78
+ int sphttp_read_request(int cfd) {
79
+ sphttp_req_buf_len_ = 0;
80
+ sphttp_req_buf_[0] = '\0';
81
+
82
+ while (sphttp_req_buf_len_ < SPHTTP_REQ_BUF_SIZE) {
83
+ ssize_t n = recv(cfd,
84
+ sphttp_req_buf_ + sphttp_req_buf_len_,
85
+ (size_t)(SPHTTP_REQ_BUF_SIZE - sphttp_req_buf_len_),
86
+ 0);
87
+ if (n == 0) {
88
+ if (sphttp_req_buf_len_ == 0) return 0;
89
+ break;
90
+ }
91
+ if (n < 0) {
92
+ if (errno == EINTR) continue;
93
+ return -1;
94
+ }
95
+ sphttp_req_buf_len_ += (int)n;
96
+ sphttp_req_buf_[sphttp_req_buf_len_] = '\0';
97
+ if (find_bytes(sphttp_req_buf_, sphttp_req_buf_len_, "\r\n\r\n", 4)) break;
98
+ }
99
+ return sphttp_req_buf_len_;
100
+ }
101
+
102
+ const char *sphttp_request_buf(void) {
103
+ return sphttp_req_buf_;
104
+ }
105
+
106
+ int sphttp_request_len(void) {
107
+ return sphttp_req_buf_len_;
108
+ }
109
+
110
+ /* Bytes already buffered past end-of-headers, useful for figuring out
111
+ * how much body the parser needs to drain from the socket. Returns -1
112
+ * if headers aren't terminated yet. */
113
+ int sphttp_header_end(void) {
114
+ const char *p = find_bytes(sphttp_req_buf_, sphttp_req_buf_len_, "\r\n\r\n", 4);
115
+ if (!p) return -1;
116
+ return (int)(p - sphttp_req_buf_) + 4;
117
+ }
118
+
119
+ /* Append `n` more bytes from the socket to the body buffer. `off` is
120
+ * the offset to start writing at (so callers can carry over any body
121
+ * bytes that came in with the headers). Returns total body length. */
122
+ int sphttp_drain_body(int cfd, int off, int want) {
123
+ if (off < 0 || off > SPHTTP_BODY_BUF_SIZE) return -1;
124
+ if (want < 0) return -1;
125
+ if (off + want > SPHTTP_BODY_BUF_SIZE) return -1;
126
+
127
+ int have = off;
128
+ int target = off + want;
129
+ while (have < target) {
130
+ ssize_t n = recv(cfd, sphttp_body_buf_ + have, (size_t)(target - have), 0);
131
+ if (n == 0) break;
132
+ if (n < 0) {
133
+ if (errno == EINTR) continue;
134
+ return -1;
135
+ }
136
+ have += (int)n;
137
+ }
138
+ sphttp_body_buf_[have] = '\0';
139
+ return have;
140
+ }
141
+
142
+ /* Copy `n` bytes from the request buffer at `src_off` into the body
143
+ * buffer at `dst_off`. Used when the headers buffer already contains
144
+ * the start (or all) of the body. */
145
+ int sphttp_copy_body(int src_off, int dst_off, int n) {
146
+ if (src_off < 0 || n < 0) return -1;
147
+ if (src_off + n > sphttp_req_buf_len_) return -1;
148
+ if (dst_off + n > SPHTTP_BODY_BUF_SIZE) return -1;
149
+ memcpy(sphttp_body_buf_ + dst_off, sphttp_req_buf_ + src_off, (size_t)n);
150
+ sphttp_body_buf_[dst_off + n] = '\0';
151
+ return dst_off + n;
152
+ }
153
+
154
+ const char *sphttp_body_buf(void) {
155
+ return sphttp_body_buf_;
156
+ }
157
+
158
+ int sphttp_write_str(int cfd, const char *s) {
159
+ if (!s) return -1;
160
+ size_t len = strlen(s);
161
+ size_t off = 0;
162
+ while (off < len) {
163
+ ssize_t n = send(cfd, s + off, len - off, 0);
164
+ if (n <= 0) {
165
+ if (n < 0 && errno == EINTR) continue;
166
+ return -1;
167
+ }
168
+ off += (size_t)n;
169
+ }
170
+ return (int)off;
171
+ }
172
+
173
+ /* Open `path`, send the whole file body. Returns bytes sent, or -1 on
174
+ * error (open, fstat, send). Caller is responsible for emitting the
175
+ * status line and headers (incl. Content-Length / Content-Type) first. */
176
+ int sphttp_sendfile(int cfd, const char *path) {
177
+ int fd = open(path, O_RDONLY);
178
+ if (fd < 0) return -1;
179
+
180
+ char buf[16384];
181
+ int total = 0;
182
+ for (;;) {
183
+ ssize_t r = read(fd, buf, sizeof(buf));
184
+ if (r == 0) break;
185
+ if (r < 0) {
186
+ if (errno == EINTR) continue;
187
+ close(fd);
188
+ return -1;
189
+ }
190
+ ssize_t off = 0;
191
+ while (off < r) {
192
+ ssize_t w = send(cfd, buf + off, (size_t)(r - off), 0);
193
+ if (w <= 0) {
194
+ if (w < 0 && errno == EINTR) continue;
195
+ close(fd);
196
+ return -1;
197
+ }
198
+ off += w;
199
+ }
200
+ total += (int)r;
201
+ }
202
+ close(fd);
203
+ return total;
204
+ }
205
+
206
+ /* Returns file size in bytes, or -1 if missing / not a regular file. */
207
+ int sphttp_file_size(const char *path) {
208
+ struct stat st;
209
+ if (stat(path, &st) < 0) return -1;
210
+ if ((st.st_mode & S_IFMT) != S_IFREG) return -1;
211
+ if (st.st_size > 0x7fffffff) return -1;
212
+ return (int)st.st_size;
213
+ }
214
+
215
+ int sphttp_close(int cfd) {
216
+ return close(cfd);
217
+ }
218
+
219
+ int sphttp_fork(void) {
220
+ return (int)fork();
221
+ }
222
+
223
+ int sphttp_wait_any(void) {
224
+ int status;
225
+ return (int)waitpid(-1, &status, 0);
226
+ }
227
+
228
+ int sphttp_getpid(void) {
229
+ return (int)getpid();
230
+ }
231
+
232
+ /* Monotonic-clock pair for request timing. Single static is fine because
233
+ * a worker handles one request at a time; pipelining is out of scope. */
234
+ static struct timespec sphttp_mark_ts_;
235
+
236
+ int sphttp_mark_now(void) {
237
+ clock_gettime(CLOCK_MONOTONIC, &sphttp_mark_ts_);
238
+ return 0;
239
+ }
240
+
241
+ int sphttp_elapsed_micros(void) {
242
+ struct timespec now;
243
+ clock_gettime(CLOCK_MONOTONIC, &now);
244
+ long secs = (long)(now.tv_sec - sphttp_mark_ts_.tv_sec);
245
+ long nsecs = (long)(now.tv_nsec - sphttp_mark_ts_.tv_nsec);
246
+ long total_us = secs * 1000000L + nsecs / 1000L;
247
+ if (total_us < 0) return 0;
248
+ if (total_us > 0x7fffffff) return 0x7fffffff;
249
+ return (int)total_us;
250
+ }
251
+
252
+ /* ---------- SHA-256 + HMAC for the cookie session store ----------
253
+ * Compact public-domain SHA-256 implementation, then HMAC on top.
254
+ * Lifted from tep's lib/tep/sphttp.c — same shim file rather than a
255
+ * new lib/crypto.c, same playbook as the rest of this file. No link
256
+ * against libssl/libcrypto. */
257
+
258
+ static const uint32_t sphttp_sha256_k_[64] = {
259
+ 0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,
260
+ 0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,
261
+ 0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,
262
+ 0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,
263
+ 0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,
264
+ 0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,
265
+ 0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,
266
+ 0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2
267
+ };
268
+
269
+ #define SPHTTP_ROTR(x,n) (((x) >> (n)) | ((x) << (32 - (n))))
270
+ #define SPHTTP_BSIG0(x) (SPHTTP_ROTR(x, 2) ^ SPHTTP_ROTR(x,13) ^ SPHTTP_ROTR(x,22))
271
+ #define SPHTTP_BSIG1(x) (SPHTTP_ROTR(x, 6) ^ SPHTTP_ROTR(x,11) ^ SPHTTP_ROTR(x,25))
272
+ #define SPHTTP_SSIG0(x) (SPHTTP_ROTR(x, 7) ^ SPHTTP_ROTR(x,18) ^ ((x) >> 3))
273
+ #define SPHTTP_SSIG1(x) (SPHTTP_ROTR(x,17) ^ SPHTTP_ROTR(x,19) ^ ((x) >> 10))
274
+ #define SPHTTP_CH(x,y,z) (((x) & (y)) ^ (~(x) & (z)))
275
+ #define SPHTTP_MAJ(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
276
+
277
+ static void sphttp_sha256_block_(uint32_t H[8], const uint8_t b[64]) {
278
+ uint32_t w[64], sa, sb, sc, sd, se, sf, sg, sh, t1, t2;
279
+ int i;
280
+ for (i = 0; i < 16; i++) {
281
+ w[i] = ((uint32_t)b[i*4] << 24) | ((uint32_t)b[i*4+1] << 16) |
282
+ ((uint32_t)b[i*4+2] << 8) | (uint32_t)b[i*4+3];
283
+ }
284
+ for (i = 16; i < 64; i++) {
285
+ w[i] = SPHTTP_SSIG1(w[i-2]) + w[i-7] + SPHTTP_SSIG0(w[i-15]) + w[i-16];
286
+ }
287
+ sa=H[0]; sb=H[1]; sc=H[2]; sd=H[3]; se=H[4]; sf=H[5]; sg=H[6]; sh=H[7];
288
+ for (i = 0; i < 64; i++) {
289
+ t1 = sh + SPHTTP_BSIG1(se) + SPHTTP_CH(se,sf,sg) + sphttp_sha256_k_[i] + w[i];
290
+ t2 = SPHTTP_BSIG0(sa) + SPHTTP_MAJ(sa,sb,sc);
291
+ sh = sg; sg = sf; sf = se; se = sd + t1;
292
+ sd = sc; sc = sb; sb = sa; sa = t1 + t2;
293
+ }
294
+ H[0]+=sa; H[1]+=sb; H[2]+=sc; H[3]+=sd;
295
+ H[4]+=se; H[5]+=sf; H[6]+=sg; H[7]+=sh;
296
+ }
297
+
298
+ static void sphttp_sha256_(const uint8_t *msg, size_t len, uint8_t out[32]) {
299
+ uint32_t H[8] = {
300
+ 0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,
301
+ 0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19
302
+ };
303
+ uint8_t buf[64];
304
+ size_t i, full = len & ~((size_t)63);
305
+ for (i = 0; i < full; i += 64) sphttp_sha256_block_(H, msg + i);
306
+ size_t rem = len - full;
307
+ for (i = 0; i < rem; i++) buf[i] = msg[full + i];
308
+ buf[rem] = 0x80;
309
+ if (rem >= 56) {
310
+ for (i = rem + 1; i < 64; i++) buf[i] = 0;
311
+ sphttp_sha256_block_(H, buf);
312
+ for (i = 0; i < 56; i++) buf[i] = 0;
313
+ } else {
314
+ for (i = rem + 1; i < 56; i++) buf[i] = 0;
315
+ }
316
+ uint64_t bits = (uint64_t)len * 8;
317
+ for (i = 0; i < 8; i++) buf[56 + i] = (uint8_t)(bits >> (56 - 8*i));
318
+ sphttp_sha256_block_(H, buf);
319
+ for (i = 0; i < 8; i++) {
320
+ out[i*4] = (uint8_t)(H[i] >> 24);
321
+ out[i*4+1] = (uint8_t)(H[i] >> 16);
322
+ out[i*4+2] = (uint8_t)(H[i] >> 8);
323
+ out[i*4+3] = (uint8_t)(H[i]);
324
+ }
325
+ }
326
+
327
+ /* HMAC-SHA256(key, msg) -> 32 raw bytes. Streams the (ipad || msg)
328
+ * inner hash without an extra heap alloc. */
329
+ static void sphttp_hmac_sha256_(const uint8_t *key, size_t klen,
330
+ const uint8_t *msg, size_t mlen,
331
+ uint8_t out[32]) {
332
+ uint8_t kpad[64], ipad[64], opad[64], inner[32];
333
+ size_t i;
334
+ if (klen > 64) {
335
+ sphttp_sha256_(key, klen, kpad);
336
+ for (i = 32; i < 64; i++) kpad[i] = 0;
337
+ } else {
338
+ for (i = 0; i < klen; i++) kpad[i] = key[i];
339
+ for (i = klen; i < 64; i++) kpad[i] = 0;
340
+ }
341
+ for (i = 0; i < 64; i++) {
342
+ ipad[i] = kpad[i] ^ 0x36;
343
+ opad[i] = kpad[i] ^ 0x5c;
344
+ }
345
+ /* inner = SHA256(ipad || msg) — stream the two segments. */
346
+ {
347
+ uint32_t H[8] = {
348
+ 0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,
349
+ 0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19
350
+ };
351
+ sphttp_sha256_block_(H, ipad);
352
+ uint8_t buf[64];
353
+ size_t full = mlen & ~((size_t)63);
354
+ for (i = 0; i < full; i += 64) sphttp_sha256_block_(H, msg + i);
355
+ size_t rem = mlen - full;
356
+ for (i = 0; i < rem; i++) buf[i] = msg[full + i];
357
+ buf[rem] = 0x80;
358
+ if (rem >= 56) {
359
+ for (i = rem + 1; i < 64; i++) buf[i] = 0;
360
+ sphttp_sha256_block_(H, buf);
361
+ for (i = 0; i < 56; i++) buf[i] = 0;
362
+ } else {
363
+ for (i = rem + 1; i < 56; i++) buf[i] = 0;
364
+ }
365
+ uint64_t bits = (uint64_t)(64 + mlen) * 8;
366
+ for (i = 0; i < 8; i++) buf[56 + i] = (uint8_t)(bits >> (56 - 8*i));
367
+ sphttp_sha256_block_(H, buf);
368
+ for (i = 0; i < 8; i++) {
369
+ inner[i*4] = (uint8_t)(H[i] >> 24);
370
+ inner[i*4+1] = (uint8_t)(H[i] >> 16);
371
+ inner[i*4+2] = (uint8_t)(H[i] >> 8);
372
+ inner[i*4+3] = (uint8_t)(H[i]);
373
+ }
374
+ }
375
+ /* outer = SHA256(opad || inner) */
376
+ {
377
+ uint32_t H[8] = {
378
+ 0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,
379
+ 0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19
380
+ };
381
+ sphttp_sha256_block_(H, opad);
382
+ uint8_t buf[64];
383
+ for (i = 0; i < 32; i++) buf[i] = inner[i];
384
+ buf[32] = 0x80;
385
+ for (i = 33; i < 56; i++) buf[i] = 0;
386
+ uint64_t bits = (uint64_t)(64 + 32) * 8;
387
+ for (i = 0; i < 8; i++) buf[56 + i] = (uint8_t)(bits >> (56 - 8*i));
388
+ sphttp_sha256_block_(H, buf);
389
+ for (i = 0; i < 8; i++) {
390
+ out[i*4] = (uint8_t)(H[i] >> 24);
391
+ out[i*4+1] = (uint8_t)(H[i] >> 16);
392
+ out[i*4+2] = (uint8_t)(H[i] >> 8);
393
+ out[i*4+3] = (uint8_t)(H[i]);
394
+ }
395
+ }
396
+ }
397
+
398
+ /* Public FFI: HMAC-SHA256 with the result as a 64-char hex string in
399
+ * a static buffer. key and msg are NUL-terminated (Spinel's :str passes
400
+ * no length) — adequate for cookie use, where the secret has no
401
+ * embedded NULs and the message is URL-encoded (also NUL-free). The
402
+ * next call clobbers the buffer. */
403
+ static char sphttp_hmac_hex_buf_[65];
404
+
405
+ const char *sphttp_hmac_sha256_hex(const char *key, const char *msg) {
406
+ uint8_t out[32];
407
+ sphttp_hmac_sha256_((const uint8_t *)key, strlen(key),
408
+ (const uint8_t *)msg, strlen(msg),
409
+ out);
410
+ static const char H[] = "0123456789abcdef";
411
+ int i;
412
+ for (i = 0; i < 32; i++) {
413
+ sphttp_hmac_hex_buf_[i*2] = H[(out[i] >> 4) & 0xf];
414
+ sphttp_hmac_hex_buf_[i*2+1] = H[out[i] & 0xf];
415
+ }
416
+ sphttp_hmac_hex_buf_[64] = '\0';
417
+ return sphttp_hmac_hex_buf_;
418
+ }
419
+
420
+ /* base64url alphabet (RFC 4648 §5) — `+`/`/` replaced by `-`/`_`. No
421
+ * padding. For future JWT signing; JWT's JOSE encoding wants the raw
422
+ * 32-byte HMAC base64url'd directly, never hex. */
423
+ static const char SPHTTP_B64U[65] =
424
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
425
+
426
+ static char sphttp_hmac_b64url_buf_[44];
427
+
428
+ const char *sphttp_hmac_sha256_b64url(const char *key, const char *msg) {
429
+ uint8_t out[32];
430
+ sphttp_hmac_sha256_((const uint8_t *)key, strlen(key),
431
+ (const uint8_t *)msg, strlen(msg),
432
+ out);
433
+ /* 32 bytes -> 43 chars (no padding): 10 full 3-byte groups
434
+ * (30 bytes -> 40 chars) + 1 group of 2 bytes (-> 3 chars). */
435
+ int i, j = 0;
436
+ for (i = 0; i + 3 <= 32; i += 3) {
437
+ uint32_t v = ((uint32_t)out[i] << 16)
438
+ | ((uint32_t)out[i+1] << 8)
439
+ | (uint32_t)out[i+2];
440
+ sphttp_hmac_b64url_buf_[j++] = SPHTTP_B64U[(v >> 18) & 0x3f];
441
+ sphttp_hmac_b64url_buf_[j++] = SPHTTP_B64U[(v >> 12) & 0x3f];
442
+ sphttp_hmac_b64url_buf_[j++] = SPHTTP_B64U[(v >> 6) & 0x3f];
443
+ sphttp_hmac_b64url_buf_[j++] = SPHTTP_B64U[v & 0x3f];
444
+ }
445
+ if (i < 32) {
446
+ uint32_t v = ((uint32_t)out[i] << 16)
447
+ | (i + 1 < 32 ? ((uint32_t)out[i+1] << 8) : 0);
448
+ sphttp_hmac_b64url_buf_[j++] = SPHTTP_B64U[(v >> 18) & 0x3f];
449
+ sphttp_hmac_b64url_buf_[j++] = SPHTTP_B64U[(v >> 12) & 0x3f];
450
+ if (i + 1 < 32) {
451
+ sphttp_hmac_b64url_buf_[j++] = SPHTTP_B64U[(v >> 6) & 0x3f];
452
+ }
453
+ }
454
+ sphttp_hmac_b64url_buf_[j] = '\0';
455
+ return sphttp_hmac_b64url_buf_;
456
+ }