1// MIT License
2
3// Copyright (c) 2017 Vadim Grigoruk @nesbox // grigoruk@gmail.com
4
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to deal
7// in the Software without restriction, including without limitation the rights
8// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9// copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11
12// The above copyright notice and this permission notice shall be included in all
13// copies or substantial portions of the Software.
14
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21// SOFTWARE.
22
23#include "net.h"
24#include "defines.h"
25
26#include <stdlib.h>
27#include <stdio.h>
28#include <string.h>
29
30#define URL_SIZE 2048
31
32#if defined(__EMSCRIPTEN__)
33
34#include <emscripten/fetch.h>
35
36typedef struct
37{
38 net_get_callback callback;
39 void* calldata;
40} FetchData;
41
42struct tic_net
43{
44 emscripten_fetch_attr_t attr;
45};
46
47static void downloadSucceeded(emscripten_fetch_t *fetch)
48{
49 FetchData* data = (FetchData*)fetch->userData;
50
51 net_get_data getData =
52 {
53 .type = net_get_done,
54 .done =
55 {
56 .size = fetch->numBytes,
57 .data = (u8*)fetch->data,
58 },
59 .calldata = data->calldata,
60 .url = fetch->url,
61 };
62
63 data->callback(&getData);
64
65 free((void*)fetch->data);
66 free(data);
67
68 emscripten_fetch_close(fetch);
69}
70
71static void downloadFailed(emscripten_fetch_t *fetch)
72{
73 FetchData* data = (FetchData*)fetch->userData;
74
75 net_get_data getData =
76 {
77 .type = net_get_error,
78 .error =
79 {
80 .code = fetch->status,
81 },
82 .calldata = data->calldata,
83 .url = fetch->url,
84 };
85
86 data->callback(&getData);
87
88 free(data);
89
90 emscripten_fetch_close(fetch);
91}
92
93static void downloadProgress(emscripten_fetch_t *fetch)
94{
95 FetchData* data = (FetchData*)fetch->userData;
96
97 net_get_data getData =
98 {
99 .type = net_get_progress,
100 .progress =
101 {
102 .size = fetch->dataOffset + fetch->numBytes,
103 .total = fetch->totalBytes,
104 },
105 .calldata = data->calldata,
106 .url = fetch->url,
107 };
108
109 data->callback(&getData);
110}
111
112void tic_net_get(tic_net* net, const char* path, net_get_callback callback, void* calldata)
113{
114 FetchData* data = calloc(1, sizeof(FetchData));
115 *data = (FetchData)
116 {
117 .callback = callback,
118 .calldata = calldata,
119 };
120
121 net->attr.userData = data;
122 emscripten_fetch(&net->attr, path);
123}
124
125void tic_net_start(tic_net *net) {}
126void tic_net_end(tic_net *net) {}
127
128tic_net* tic_net_create(const char* host)
129{
130 tic_net* net = (tic_net*)malloc(sizeof(tic_net));
131
132 emscripten_fetch_attr_init(&net->attr);
133 strcpy(net->attr.requestMethod, "GET");
134 net->attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY;
135 net->attr.onsuccess = downloadSucceeded;
136 net->attr.onerror = downloadFailed;
137 net->attr.onprogress = downloadProgress;
138
139 return net;
140}
141
142void tic_net_close(tic_net* net)
143{
144 free(net);
145}
146
147#elif defined(_3DS)
148
149#include <3ds.h>
150
151struct tic_net
152{
153 LightLock tick_lock;
154 const char* host;
155};
156
157typedef struct {
158 char url[URL_SIZE];
159
160 tic_net *net;
161 httpcContext httpc;
162 net_get_data data;
163 net_get_callback callback;
164
165 void *buffer;
166 s32 size;
167} net_ctx;
168
169// #define DEBUG
170
171static int n3ds_net_setup_context(httpcContext *httpc, const char *url) {
172 if (httpcOpenContext(httpc, HTTPC_METHOD_GET, url, 1)) return -101;
173 if (httpcSetSSLOpt(httpc, SSLCOPT_DisableVerify)) { httpcCloseContext(httpc); return -102; }
174 if (httpcSetKeepAlive(httpc, HTTPC_KEEPALIVE_ENABLED)) { httpcCloseContext(httpc); return -103; }
175 if (httpcAddRequestHeaderField(httpc, "User-Agent", "tic80-n3ds/1.0.0 (httpc)")) { httpcCloseContext(httpc); return -104; }
176 if (httpcAddRequestHeaderField(httpc, "Connection", "Keep-Alive")) { httpcCloseContext(httpc); return -105; }
177 return 0;
178}
179
180#define NET_PAGE_SIZE 4096
181
182static inline bool ctx_resize_buf(net_ctx *ctx, s32 new_size) {
183 if (ctx->buffer == NULL) {
184 ctx->buffer = malloc(new_size);
185 if (ctx->buffer == NULL) {
186 return false;
187 }
188 } else if (ctx->size != new_size) {
189 void *old_buf = ctx->buffer;
190 ctx->buffer = realloc(ctx->buffer, new_size);
191 if (ctx->buffer == NULL) {
192 free(old_buf);
193 return false;
194 }
195 }
196
197 ctx->size = new_size;
198 return true;
199}
200
201#define NET_EXEC_ERROR_CHECK \
202 if (status_code != 200) { \
203 printf("net_httpc: error %d\n", status_code); \
204 if (ctx->callback != NULL) { \
205 ctx->data.type = net_get_error; \
206 ctx->data.error.code = status_code; \
207 if (!ignore_lock) LightLock_Lock(&ctx->net->tick_lock); \
208 ctx->callback(&ctx->data); \
209 if (!ignore_lock) LightLock_Unlock(&ctx->net->tick_lock); \
210 } \
211 httpcCloseContext(&ctx->httpc); \
212 if (ctx->buffer != NULL) { free(ctx->buffer); ctx->size = 0; } \
213 return; \
214 }
215
216static void n3ds_net_execute(net_ctx *ctx, bool ignore_lock) {
217 bool redirecting = true;
218 s32 status_code = -1;
219
220 ctx->data.url = ctx->url;
221 while (redirecting) {
222#ifdef DEBUG
223 printf("url: %s\n", ctx->url);
224#endif
225 redirecting = false;
226
227 status_code = n3ds_net_setup_context(&ctx->httpc, ctx->url);
228 if (status_code < 0) {
229 break;
230 }
231
232 if (httpcBeginRequest(&ctx->httpc)) {
233 status_code = -2;
234 break;
235 }
236
237 if (httpcGetResponseStatusCode(&ctx->httpc, &status_code)) {
238 status_code = -3;
239 break;
240 }
241
242 if ((status_code >= 301 && status_code <= 303) || (status_code >= 307 && status_code <= 308)) {
243 if (httpcGetResponseHeader(&ctx->httpc, "Location", ctx->url, URL_SIZE - 1)) {
244 status_code = -4;
245 break;
246 }
247
248 redirecting = true;
249 httpcCloseContext(&ctx->httpc);
250 }
251 }
252
253 NET_EXEC_ERROR_CHECK;
254
255 s32 state = HTTPC_RESULTCODE_DOWNLOADPENDING;
256 s32 read_size;
257 while (state == HTTPC_RESULTCODE_DOWNLOADPENDING) {
258 s32 old_size = ctx->size;
259 if (!ctx_resize_buf(ctx, ctx->size + NET_PAGE_SIZE)) {
260 httpcCloseContext(&ctx->httpc);
261 status_code = -5;
262 break;
263 }
264 u8 *old_ptr = ((u8*) ctx->buffer) + old_size;
265 state = httpcDownloadData(&ctx->httpc, old_ptr, NET_PAGE_SIZE, &read_size);
266 if (state == HTTPC_RESULTCODE_DOWNLOADPENDING || state == 0) {
267 ctx_resize_buf(ctx, old_size + read_size);
268 if (ctx->callback != NULL) {
269 if (ignore_lock || !LightLock_TryLock(&ctx->net->tick_lock)) {
270 ctx->data.type = net_get_progress;
271 if (!httpcGetDownloadSizeState(&ctx->httpc, &ctx->data.progress.size, &ctx->data.progress.total)) {
272 if (ctx->data.progress.total < ctx->data.progress.size) {
273 ctx->data.progress.total = ctx->data.progress.size;
274 }
275 ctx->callback(&ctx->data);
276 }
277 if (!ignore_lock) LightLock_Unlock(&ctx->net->tick_lock);
278 }
279 }
280 }
281 }
282
283#ifdef DEBUG
284 printf("downloaded: %d bytes\n", ctx->size);
285#endif
286
287 if (status_code == 200 && state != 0) {
288 status_code = -6;
289 }
290 NET_EXEC_ERROR_CHECK;
291
292 if (ctx->callback != NULL) {
293 ctx->data.type = net_get_done;
294 ctx->data.done.data = ctx->buffer;
295 ctx->data.done.size = ctx->size;
296 if (!ignore_lock) LightLock_Lock(&ctx->net->tick_lock);
297 ctx->callback(&ctx->data);
298 if (!ignore_lock) LightLock_Unlock(&ctx->net->tick_lock);
299 }
300 httpcCloseContext(&ctx->httpc);
301}
302
303static void n3ds_net_init(tic_net *net) {
304 httpcInit(0);
305
306 memset(net, 0, sizeof(tic_net));
307 LightLock_Init(&net->tick_lock);
308}
309
310static void n3ds_net_free(tic_net *net) {
311 httpcExit();
312}
313
314static void n3ds_net_get_thread(net_ctx *ctx) {
315 n3ds_net_execute(ctx, false);
316
317 if (ctx->buffer != NULL) {
318 free(ctx->buffer);
319 }
320 free(ctx);
321}
322
323static void n3ds_net_apply_url(net_ctx *ctx, const char *url) {
324 strncpy(ctx->url, ctx->net->host, URL_SIZE - 1);
325 strncat(ctx->url, url, URL_SIZE - 1);
326}
327
328tic_net* tic_net_create(const char* host)
329{
330 tic_net* net = (tic_net*)malloc(sizeof(tic_net));
331
332 n3ds_net_init(net);
333 net->host = host;
334
335 return net;
336}
337
338void tic_net_get(tic_net* net, const char* url, net_get_callback callback, void* calldata)
339{
340 net_ctx ctx =
341 {
342 .net = net,
343 .callback = callback,
344 .data = {.calldata = calldata},
345 };
346
347 n3ds_net_apply_url(&ctx, url);
348
349 s32 priority;
350 svcGetThreadPriority(&priority, CUR_THREAD_HANDLE);
351 threadCreate((ThreadFunc) n3ds_net_get_thread, MOVE(ctx), 16 * 1024, priority - 1, -1, true);
352}
353
354void tic_net_close(tic_net* net)
355{
356 n3ds_net_free(net);
357 free(net);
358}
359
360void tic_net_start(tic_net *net)
361{
362 LightLock_Lock(&net->tick_lock);
363}
364
365void tic_net_end(tic_net *net)
366{
367 LightLock_Unlock(&net->tick_lock);
368}
369
370#elif defined(BAREMETALPI)
371
372tic_net* tic_net_create(const char* host) {return NULL;}
373void tic_net_get(tic_net* net, const char* url, net_get_callback callback, void* calldata) {}
374void tic_net_close(tic_net* net) {}
375void tic_net_start(tic_net *net) {}
376void tic_net_end(tic_net *net) {}
377
378#elif defined(USE_LIBUV)
379
380#include "defines.h"
381
382#include <uv.h>
383#include <http_parser.h>
384
385typedef struct
386{
387 const char* host;
388 const char* path;
389
390 net_get_callback callback;
391 void* calldata;
392
393 uv_tcp_t tcp;
394 http_parser parser;
395
396 struct
397 {
398 u8* data;
399 s32 size;
400 s32 total;
401 } content;
402
403} NetRequest;
404
405struct tic_net
406{
407 const char* host;
408};
409
410static s32 onBody(http_parser* parser, const char *at, size_t length)
411{
412 NetRequest* req = parser->data;
413
414 req->content.data = realloc(req->content.data, req->content.size + length);
415 memcpy(req->content.data + req->content.size, at, length);
416
417 req->content.size += length;
418
419 req->callback(&(net_get_data)
420 {
421 .calldata = req->calldata,
422 .type = net_get_progress,
423 .progress = {req->content.size, req->content.total},
424 .url = req->path
425 });
426
427 return 0;
428}
429
430static void onClose(uv_handle_t* handle)
431{
432 NetRequest* req = handle->data;
433 if (req){
434 FREE(req->content.data);
435 free((void*)req->path);
436 free(req);
437 }
438}
439
440static void freeRequest(NetRequest* req)
441{
442 uv_close((uv_handle_t*)&req->tcp, onClose);
443}
444
445static s32 onMessageComplete(http_parser* parser)
446{
447 NetRequest* req = parser->data;
448
449 if (parser->status_code == HTTP_STATUS_OK)
450 {
451 req->callback(&(net_get_data)
452 {
453 .calldata = req->calldata,
454 .type = net_get_done,
455 .done = { .data = req->content.data, .size = req->content.size },
456 .url = req->path
457 });
458 }
459
460 freeRequest(req);
461
462 return 0;
463}
464
465static s32 onHeadersComplete(http_parser* parser)
466{
467 NetRequest* req = parser->data;
468
469 bool hasBody = parser->flags & F_CHUNKED || (parser->content_length > 0 && parser->content_length != ULLONG_MAX);
470
471 ZEROMEM(req->content);
472 req->content.total = parser->content_length;
473
474 // !TODO: handle HTTP_STATUS_MOVED_PERMANENTLY here
475 if (!hasBody || parser->status_code != HTTP_STATUS_OK)
476 return 1;
477
478 return 0;
479}
480
481static s32 onStatus(http_parser* parser, const char* at, size_t length)
482{
483 return parser->status_code != HTTP_STATUS_OK;
484}
485
486static void onError(NetRequest* req, s32 code)
487{
488 req->callback(&(net_get_data)
489 {
490 .calldata = req->calldata,
491 .type = net_get_error,
492 .error = { .code = code }
493 });
494
495 freeRequest(req);
496}
497
498static void allocBuffer(uv_handle_t *handle, size_t size, uv_buf_t *buf)
499{
500 buf->base = malloc(size);
501 buf->len = size;
502}
503
504static void onResponse(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf)
505{
506 NetRequest* req = stream->data;
507
508 if(nread > 0)
509 {
510 static const http_parser_settings ParserSettings =
511 {
512 .on_status = onStatus,
513 .on_body = onBody,
514 .on_message_complete = onMessageComplete,
515 .on_headers_complete = onHeadersComplete,
516 };
517
518 s32 parsed = http_parser_execute(&req->parser, &ParserSettings, buf->base, nread);
519
520 if(parsed != nread)
521 onError(req, req->parser.status_code);
522
523 free(buf->base);
524 }
525 else onError(req, 0);
526}
527
528static void onHeaderSent(uv_write_t *write, s32 status)
529{
530 NetRequest* req = write->data;
531 http_parser_init(&req->parser, HTTP_RESPONSE);
532 req->parser.data = req;
533
534 uv_stream_t* handle = write->handle;
535 free(write);
536
537 handle->data = req;
538 uv_read_start(handle, allocBuffer, onResponse);
539}
540
541static void onConnect(uv_connect_t *con, s32 status)
542{
543 NetRequest* req = con->data;
544
545 char httpReq[2048];
546 snprintf(httpReq, sizeof httpReq, "GET %s HTTP/1.1\nHost: %s\n\n", req->path, req->host);
547
548 uv_buf_t http = uv_buf_init(httpReq, strlen(httpReq));
549 uv_write(MOVE((uv_write_t){.data = req}), con->handle, &http, 1, onHeaderSent);
550
551 free(con);
552}
553
554static void onResolved(uv_getaddrinfo_t *resolver, s32 status, struct addrinfo *res)
555{
556 NetRequest* req = resolver->data;
557
558 if (res)
559 {
560 uv_tcp_connect(MOVE((uv_connect_t){.data = req}), &req->tcp, res->ai_addr, onConnect);
561 uv_freeaddrinfo(res);
562 }
563 else onError(req, 0);
564
565 free(resolver);
566}
567
568void tic_net_get(tic_net* net, const char* path, net_get_callback callback, void* calldata)
569{
570 uv_loop_t* loop = uv_default_loop();
571
572 NetRequest* req = MOVE((NetRequest)
573 {
574 .host = net->host,
575 .callback = callback,
576 .calldata = calldata,
577 .path = strdup(path),
578 });
579
580 uv_tcp_init(loop, &req->tcp);
581 uv_getaddrinfo(loop, MOVE((uv_getaddrinfo_t){.data = req}), onResolved, net->host, "80", NULL);
582}
583
584void tic_net_start(tic_net *net) {}
585
586void tic_net_end(tic_net *net)
587{
588 uv_run(uv_default_loop(), UV_RUN_NOWAIT);
589}
590
591tic_net* tic_net_create(const char* host)
592{
593 tic_net* net = NEW(tic_net);
594 memset(net, 0, sizeof(tic_net));
595
596 net->host = host;
597
598 static const char Http[] = "http://";
599 if(strstr(host, Http) == host)
600 net->host += sizeof Http - 1;
601
602 return net;
603}
604
605void tic_net_close(tic_net* net)
606{
607 free(net);
608}
609
610#endif
611