1/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
2/* If you are missing that file, acquire a complete release at teeworlds.com. */
3#include "system.h"
4
5#include "lock.h"
6#include "logger.h"
7#include "windows.h"
8
9#include <sys/types.h>
10
11#include <atomic>
12#include <cctype>
13#include <charconv>
14#include <chrono>
15#include <cinttypes>
16#include <cmath>
17#include <cstdarg>
18#include <cstdio>
19#include <cstring>
20#include <iterator> // std::size
21#include <mutex>
22#include <string_view>
23
24#if defined(CONF_WEBSOCKETS)
25#include <engine/shared/websockets.h>
26#endif
27
28#if defined(CONF_FAMILY_UNIX)
29#include <sys/stat.h>
30#include <sys/time.h>
31#include <sys/utsname.h>
32#include <sys/wait.h>
33#include <unistd.h>
34
35#include <csignal>
36#include <locale>
37
38/* unix net includes */
39#include <arpa/inet.h>
40#include <dirent.h>
41#include <netdb.h>
42#include <netinet/in.h>
43#include <pthread.h>
44#include <sys/ioctl.h>
45#include <sys/socket.h>
46
47#include <cerrno>
48
49#if defined(CONF_PLATFORM_MACOS)
50// some lock and pthread functions are already defined in headers
51// included from Carbon.h
52// this prevents having duplicate definitions of those
53#define _lock_set_user_
54#define _task_user_
55
56#include <Carbon/Carbon.h>
57#include <CoreFoundation/CoreFoundation.h>
58#include <mach-o/dyld.h>
59#include <mach/mach_time.h>
60
61#if defined(__MAC_10_10) && __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_10
62#include <pthread/qos.h>
63#endif
64#endif
65
66#elif defined(CONF_FAMILY_WINDOWS)
67#include <io.h>
68#include <objbase.h>
69#include <process.h>
70#include <shellapi.h>
71#include <shlobj.h> // SHGetKnownFolderPath
72#include <shlwapi.h>
73#include <wincrypt.h>
74#include <windows.h>
75#include <winsock2.h>
76#include <ws2tcpip.h>
77
78#include <cerrno>
79#include <cfenv>
80#else
81#error NOT IMPLEMENTED
82#endif
83
84#if defined(CONF_PLATFORM_SOLARIS)
85#include <sys/filio.h>
86#endif
87
88#if defined(CONF_PLATFORM_EMSCRIPTEN)
89#include <emscripten/emscripten.h>
90#endif
91
92static NETSTATS network_stats = {.sent_packets: 0};
93
94#define VLEN 128
95#define PACKETSIZE 1400
96typedef struct
97{
98#ifdef CONF_PLATFORM_LINUX
99 int pos;
100 int size;
101 struct mmsghdr msgs[VLEN];
102 struct iovec iovecs[VLEN];
103 char bufs[VLEN][PACKETSIZE];
104 char sockaddrs[VLEN][128];
105#else
106 char buf[PACKETSIZE];
107#endif
108} NETSOCKET_BUFFER;
109
110void net_buffer_init(NETSOCKET_BUFFER *buffer);
111void net_buffer_reinit(NETSOCKET_BUFFER *buffer);
112void net_buffer_simple(NETSOCKET_BUFFER *buffer, char **buf, int *size);
113
114struct NETSOCKET_INTERNAL
115{
116 int type;
117 int ipv4sock;
118 int ipv6sock;
119 int web_ipv4sock;
120 int web_ipv6sock;
121
122 NETSOCKET_BUFFER buffer;
123};
124static NETSOCKET_INTERNAL invalid_socket = {.type: NETTYPE_INVALID, .ipv4sock: -1, .ipv6sock: -1, .web_ipv4sock: -1, .web_ipv6sock: -1};
125
126IOHANDLE io_open(const char *filename, int flags)
127{
128 dbg_assert(flags == IOFLAG_READ || flags == IOFLAG_WRITE || flags == IOFLAG_APPEND, "flags must be read, write or append");
129#if defined(CONF_FAMILY_WINDOWS)
130 const std::wstring wide_filename = windows_utf8_to_wide(filename);
131 DWORD desired_access;
132 DWORD creation_disposition;
133 const char *open_mode;
134 if((flags & IOFLAG_READ) != 0)
135 {
136 desired_access = FILE_READ_DATA;
137 creation_disposition = OPEN_EXISTING;
138 open_mode = "rb";
139 }
140 else if(flags == IOFLAG_WRITE)
141 {
142 desired_access = FILE_WRITE_DATA;
143 creation_disposition = CREATE_ALWAYS;
144 open_mode = "wb";
145 }
146 else if(flags == IOFLAG_APPEND)
147 {
148 desired_access = FILE_APPEND_DATA;
149 creation_disposition = OPEN_ALWAYS;
150 open_mode = "ab";
151 }
152 else
153 {
154 dbg_assert_failed("logic error");
155 }
156 HANDLE handle = CreateFileW(wide_filename.c_str(), desired_access, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, creation_disposition, FILE_ATTRIBUTE_NORMAL, nullptr);
157 if(handle == INVALID_HANDLE_VALUE)
158 return nullptr;
159 const int file_descriptor = _open_osfhandle((intptr_t)handle, 0);
160 dbg_assert(file_descriptor != -1, "_open_osfhandle failure");
161 FILE *file_stream = _fdopen(file_descriptor, open_mode);
162 dbg_assert(file_stream != nullptr, "_fdopen failure");
163 return file_stream;
164#else
165 const char *open_mode;
166 if((flags & IOFLAG_READ) != 0)
167 {
168 open_mode = "rb";
169 }
170 else if(flags == IOFLAG_WRITE)
171 {
172 open_mode = "wb";
173 }
174 else if(flags == IOFLAG_APPEND)
175 {
176 open_mode = "ab";
177 }
178 else
179 {
180 dbg_assert_failed("Invalid flags: %d", flags);
181 }
182 return fopen(filename: filename, modes: open_mode);
183#endif
184}
185
186unsigned io_read(IOHANDLE io, void *buffer, unsigned size)
187{
188 return fread(ptr: buffer, size: 1, n: size, stream: (FILE *)io);
189}
190
191bool io_read_all(IOHANDLE io, void **result, unsigned *result_len)
192{
193 // Loading files larger than 1 GiB into memory is not supported.
194 constexpr int64_t MAX_FILE_SIZE = (int64_t)1024 * 1024 * 1024;
195
196 int64_t real_len = io_length(io);
197 if(real_len > MAX_FILE_SIZE)
198 {
199 *result = nullptr;
200 *result_len = 0;
201 return false;
202 }
203
204 int64_t len = real_len < 0 ? 1024 : real_len; // use default initial size if we couldn't get the length
205 char *buffer = (char *)malloc(size: len + 1);
206 int64_t read = io_read(io, buffer, size: len + 1); // +1 to check if the file size is larger than expected
207 if(read < len)
208 {
209 buffer = (char *)realloc(ptr: buffer, size: read + 1);
210 len = read;
211 }
212 else if(read > len)
213 {
214 int64_t cap = 2 * read;
215 if(cap > MAX_FILE_SIZE)
216 {
217 free(ptr: buffer);
218 *result = nullptr;
219 *result_len = 0;
220 return false;
221 }
222 len = read;
223 buffer = (char *)realloc(ptr: buffer, size: cap);
224 while((read = io_read(io, buffer: buffer + len, size: cap - len)) != 0)
225 {
226 len += read;
227 if(len == cap)
228 {
229 cap *= 2;
230 if(cap > MAX_FILE_SIZE)
231 {
232 free(ptr: buffer);
233 *result = nullptr;
234 *result_len = 0;
235 return false;
236 }
237 buffer = (char *)realloc(ptr: buffer, size: cap);
238 }
239 }
240 buffer = (char *)realloc(ptr: buffer, size: len + 1);
241 }
242 buffer[len] = 0;
243 *result = buffer;
244 *result_len = len;
245 return true;
246}
247
248char *io_read_all_str(IOHANDLE io)
249{
250 void *buffer;
251 unsigned len;
252 if(!io_read_all(io, result: &buffer, result_len: &len))
253 {
254 return nullptr;
255 }
256 if(mem_has_null(block: buffer, size: len))
257 {
258 free(ptr: buffer);
259 return nullptr;
260 }
261 return (char *)buffer;
262}
263
264int io_skip(IOHANDLE io, int64_t size)
265{
266 return io_seek(io, offset: size, origin: IOSEEK_CUR);
267}
268
269int io_seek(IOHANDLE io, int64_t offset, ESeekOrigin origin)
270{
271 int real_origin;
272 switch(origin)
273 {
274 case IOSEEK_START:
275 real_origin = SEEK_SET;
276 break;
277 case IOSEEK_CUR:
278 real_origin = SEEK_CUR;
279 break;
280 case IOSEEK_END:
281 real_origin = SEEK_END;
282 break;
283 default:
284 dbg_assert_failed("Invalid origin: %d", origin);
285 }
286#if defined(CONF_FAMILY_WINDOWS)
287 return _fseeki64((FILE *)io, offset, real_origin);
288#else
289 return fseeko(stream: (FILE *)io, off: offset, whence: real_origin);
290#endif
291}
292
293int64_t io_tell(IOHANDLE io)
294{
295#if defined(CONF_FAMILY_WINDOWS)
296 return _ftelli64((FILE *)io);
297#else
298 return ftello(stream: (FILE *)io);
299#endif
300}
301
302int64_t io_length(IOHANDLE io)
303{
304 if(io_seek(io, offset: 0, origin: IOSEEK_END) != 0)
305 {
306 return -1;
307 }
308 const int64_t length = io_tell(io);
309 if(io_seek(io, offset: 0, origin: IOSEEK_START) != 0)
310 {
311 return -1;
312 }
313 return length;
314}
315
316unsigned io_write(IOHANDLE io, const void *buffer, unsigned size)
317{
318 return fwrite(ptr: buffer, size: 1, n: size, s: (FILE *)io);
319}
320
321bool io_write_newline(IOHANDLE io)
322{
323#if defined(CONF_FAMILY_WINDOWS)
324 return io_write(io, "\r\n", 2) == 2;
325#else
326 return io_write(io, buffer: "\n", size: 1) == 1;
327#endif
328}
329
330int io_close(IOHANDLE io)
331{
332 return fclose(stream: (FILE *)io) != 0;
333}
334
335int io_flush(IOHANDLE io)
336{
337 return fflush(stream: (FILE *)io);
338}
339
340int io_sync(IOHANDLE io)
341{
342 if(io_flush(io))
343 {
344 return 1;
345 }
346#if defined(CONF_FAMILY_WINDOWS)
347 return FlushFileBuffers((HANDLE)_get_osfhandle(_fileno((FILE *)io))) == FALSE;
348#else
349 return fsync(fd: fileno(stream: (FILE *)io)) != 0;
350#endif
351}
352
353int io_error(IOHANDLE io)
354{
355 return ferror(stream: (FILE *)io);
356}
357
358IOHANDLE io_stdin()
359{
360 return stdin;
361}
362
363IOHANDLE io_stdout()
364{
365 return stdout;
366}
367
368IOHANDLE io_stderr()
369{
370 return stderr;
371}
372
373IOHANDLE io_current_exe()
374{
375 // From https://stackoverflow.com/a/1024937.
376#if defined(CONF_FAMILY_WINDOWS)
377 wchar_t wide_path[IO_MAX_PATH_LENGTH];
378 if(GetModuleFileNameW(nullptr, wide_path, std::size(wide_path)) == 0 || GetLastError() != ERROR_SUCCESS)
379 {
380 return nullptr;
381 }
382 const std::optional<std::string> path = windows_wide_to_utf8(wide_path);
383 return path.has_value() ? io_open(path.value().c_str(), IOFLAG_READ) : nullptr;
384#elif defined(CONF_PLATFORM_MACOS)
385 char path[IO_MAX_PATH_LENGTH];
386 uint32_t path_size = sizeof(path);
387 if(_NSGetExecutablePath(path, &path_size))
388 {
389 return 0;
390 }
391 return io_open(path, IOFLAG_READ);
392#else
393 static const char *NAMES[] = {
394 "/proc/self/exe", // Linux, Android
395 "/proc/curproc/exe", // NetBSD
396 "/proc/curproc/file", // DragonFly
397 };
398 for(auto &name : NAMES)
399 {
400 IOHANDLE result = io_open(filename: name, flags: IOFLAG_READ);
401 if(result)
402 {
403 return result;
404 }
405 }
406 return 0;
407#endif
408}
409
410#define ASYNC_BUFSIZE (8 * 1024)
411#define ASYNC_LOCAL_BUFSIZE (64 * 1024)
412
413struct ASYNCIO
414{
415 CLock lock;
416 IOHANDLE io;
417 SEMAPHORE sphore;
418 void *thread;
419
420 unsigned char *buffer;
421 unsigned int buffer_size;
422 unsigned int read_pos;
423 unsigned int write_pos;
424
425 int error;
426 unsigned char finish;
427 unsigned char refcount;
428};
429
430enum
431{
432 ASYNCIO_RUNNING,
433 ASYNCIO_CLOSE,
434 ASYNCIO_EXIT,
435};
436
437struct BUFFERS
438{
439 unsigned char *buf1;
440 unsigned int len1;
441 unsigned char *buf2;
442 unsigned int len2;
443};
444
445static void buffer_ptrs(ASYNCIO *aio, struct BUFFERS *buffers)
446{
447 mem_zero(block: buffers, size: sizeof(*buffers));
448 if(aio->read_pos < aio->write_pos)
449 {
450 buffers->buf1 = aio->buffer + aio->read_pos;
451 buffers->len1 = aio->write_pos - aio->read_pos;
452 }
453 else if(aio->read_pos > aio->write_pos)
454 {
455 buffers->buf1 = aio->buffer + aio->read_pos;
456 buffers->len1 = aio->buffer_size - aio->read_pos;
457 buffers->buf2 = aio->buffer;
458 buffers->len2 = aio->write_pos;
459 }
460}
461
462static void aio_handle_free_and_unlock(ASYNCIO *aio) RELEASE(aio->lock)
463{
464 int do_free;
465 aio->refcount--;
466
467 do_free = aio->refcount == 0;
468 aio->lock.unlock();
469 if(do_free)
470 {
471 free(ptr: aio->buffer);
472 sphore_destroy(sem: &aio->sphore);
473 delete aio;
474 }
475}
476
477static void aio_thread(void *user)
478{
479 ASYNCIO *aio = (ASYNCIO *)user;
480
481 aio->lock.lock();
482 while(true)
483 {
484 struct BUFFERS buffers;
485 int result_io_error;
486 unsigned char local_buffer[ASYNC_LOCAL_BUFSIZE];
487 unsigned int local_buffer_len = 0;
488
489 if(aio->read_pos == aio->write_pos)
490 {
491 if(aio->finish != ASYNCIO_RUNNING)
492 {
493 if(aio->finish == ASYNCIO_CLOSE)
494 {
495 io_close(io: aio->io);
496 }
497 aio_handle_free_and_unlock(aio);
498 break;
499 }
500 aio->lock.unlock();
501 sphore_wait(sem: &aio->sphore);
502 aio->lock.lock();
503 continue;
504 }
505
506 buffer_ptrs(aio, buffers: &buffers);
507 if(buffers.buf1)
508 {
509 if(buffers.len1 > sizeof(local_buffer) - local_buffer_len)
510 {
511 buffers.len1 = sizeof(local_buffer) - local_buffer_len;
512 }
513 mem_copy(dest: local_buffer + local_buffer_len, source: buffers.buf1, size: buffers.len1);
514 local_buffer_len += buffers.len1;
515 if(buffers.buf2)
516 {
517 if(buffers.len2 > sizeof(local_buffer) - local_buffer_len)
518 {
519 buffers.len2 = sizeof(local_buffer) - local_buffer_len;
520 }
521 mem_copy(dest: local_buffer + local_buffer_len, source: buffers.buf2, size: buffers.len2);
522 local_buffer_len += buffers.len2;
523 }
524 }
525 aio->read_pos = (aio->read_pos + buffers.len1 + buffers.len2) % aio->buffer_size;
526 aio->lock.unlock();
527
528 io_write(io: aio->io, buffer: local_buffer, size: local_buffer_len);
529 io_flush(io: aio->io);
530 result_io_error = io_error(io: aio->io);
531
532 aio->lock.lock();
533 aio->error = result_io_error;
534 }
535}
536
537ASYNCIO *aio_new(IOHANDLE io)
538{
539 ASYNCIO *aio = new ASYNCIO;
540 if(!aio)
541 {
542 return nullptr;
543 }
544 aio->io = io;
545 sphore_init(sem: &aio->sphore);
546 aio->thread = nullptr;
547
548 aio->buffer = (unsigned char *)malloc(ASYNC_BUFSIZE);
549 if(!aio->buffer)
550 {
551 sphore_destroy(sem: &aio->sphore);
552 delete aio;
553 return nullptr;
554 }
555 aio->buffer_size = ASYNC_BUFSIZE;
556 aio->read_pos = 0;
557 aio->write_pos = 0;
558 aio->error = 0;
559 aio->finish = ASYNCIO_RUNNING;
560 aio->refcount = 2;
561
562 aio->thread = thread_init(threadfunc: aio_thread, user: aio, name: "aio");
563 if(!aio->thread)
564 {
565 free(ptr: aio->buffer);
566 sphore_destroy(sem: &aio->sphore);
567 delete aio;
568 return nullptr;
569 }
570 return aio;
571}
572
573static unsigned int buffer_len(ASYNCIO *aio)
574{
575 if(aio->write_pos >= aio->read_pos)
576 {
577 return aio->write_pos - aio->read_pos;
578 }
579 else
580 {
581 return aio->buffer_size + aio->write_pos - aio->read_pos;
582 }
583}
584
585static unsigned int next_buffer_size(unsigned int cur_size, unsigned int need_size)
586{
587 while(cur_size < need_size)
588 {
589 cur_size *= 2;
590 }
591 return cur_size;
592}
593
594void aio_lock(ASYNCIO *aio) ACQUIRE(aio->lock)
595{
596 aio->lock.lock();
597}
598
599void aio_unlock(ASYNCIO *aio) RELEASE(aio->lock)
600{
601 aio->lock.unlock();
602 sphore_signal(sem: &aio->sphore);
603}
604
605void aio_write_unlocked(ASYNCIO *aio, const void *buffer, unsigned size)
606{
607 unsigned int remaining;
608 remaining = aio->buffer_size - buffer_len(aio);
609
610 // Don't allow full queue to distinguish between empty and full queue.
611 if(size < remaining)
612 {
613 unsigned int remaining_contiguous = aio->buffer_size - aio->write_pos;
614 if(size > remaining_contiguous)
615 {
616 mem_copy(dest: aio->buffer + aio->write_pos, source: buffer, size: remaining_contiguous);
617 size -= remaining_contiguous;
618 buffer = ((unsigned char *)buffer) + remaining_contiguous;
619 aio->write_pos = 0;
620 }
621 mem_copy(dest: aio->buffer + aio->write_pos, source: buffer, size);
622 aio->write_pos = (aio->write_pos + size) % aio->buffer_size;
623 }
624 else
625 {
626 // Add 1 so the new buffer isn't completely filled.
627 unsigned int new_written = buffer_len(aio) + size + 1;
628 unsigned int next_size = next_buffer_size(cur_size: aio->buffer_size, need_size: new_written);
629 unsigned int next_len = 0;
630 unsigned char *next_buffer = (unsigned char *)malloc(size: next_size);
631
632 struct BUFFERS buffers;
633 buffer_ptrs(aio, buffers: &buffers);
634 if(buffers.buf1)
635 {
636 mem_copy(dest: next_buffer + next_len, source: buffers.buf1, size: buffers.len1);
637 next_len += buffers.len1;
638 if(buffers.buf2)
639 {
640 mem_copy(dest: next_buffer + next_len, source: buffers.buf2, size: buffers.len2);
641 next_len += buffers.len2;
642 }
643 }
644 mem_copy(dest: next_buffer + next_len, source: buffer, size);
645 next_len += size;
646
647 free(ptr: aio->buffer);
648 aio->buffer = next_buffer;
649 aio->buffer_size = next_size;
650 aio->read_pos = 0;
651 aio->write_pos = next_len;
652 }
653}
654
655void aio_write(ASYNCIO *aio, const void *buffer, unsigned size)
656{
657 aio_lock(aio);
658 aio_write_unlocked(aio, buffer, size);
659 aio_unlock(aio);
660}
661
662void aio_write_newline_unlocked(ASYNCIO *aio)
663{
664#if defined(CONF_FAMILY_WINDOWS)
665 aio_write_unlocked(aio, "\r\n", 2);
666#else
667 aio_write_unlocked(aio, buffer: "\n", size: 1);
668#endif
669}
670
671void aio_write_newline(ASYNCIO *aio)
672{
673 aio_lock(aio);
674 aio_write_newline_unlocked(aio);
675 aio_unlock(aio);
676}
677
678int aio_error(ASYNCIO *aio)
679{
680 CLockScope ls(aio->lock);
681 return aio->error;
682}
683
684void aio_close(ASYNCIO *aio)
685{
686 {
687 CLockScope ls(aio->lock);
688 aio->finish = ASYNCIO_CLOSE;
689 }
690 sphore_signal(sem: &aio->sphore);
691}
692
693void aio_wait(ASYNCIO *aio)
694{
695 void *thread;
696 {
697 CLockScope ls(aio->lock);
698 thread = aio->thread;
699 aio->thread = nullptr;
700 if(aio->finish == ASYNCIO_RUNNING)
701 {
702 aio->finish = ASYNCIO_EXIT;
703 }
704 }
705 sphore_signal(sem: &aio->sphore);
706 thread_wait(thread);
707}
708
709void aio_free(ASYNCIO *aio)
710{
711 aio->lock.lock();
712 if(aio->thread)
713 {
714 thread_detach(thread: aio->thread);
715 aio->thread = nullptr;
716 }
717 aio_handle_free_and_unlock(aio);
718}
719
720struct THREAD_RUN
721{
722 void (*threadfunc)(void *);
723 void *u;
724};
725
726#if defined(CONF_FAMILY_UNIX)
727static void *thread_run(void *user)
728#elif defined(CONF_FAMILY_WINDOWS)
729static unsigned long __stdcall thread_run(void *user)
730#else
731#error not implemented
732#endif
733{
734#if defined(CONF_FAMILY_WINDOWS)
735 CWindowsComLifecycle WindowsComLifecycle(false);
736#endif
737 struct THREAD_RUN *data = (THREAD_RUN *)user;
738 void (*threadfunc)(void *) = data->threadfunc;
739 void *u = data->u;
740 free(ptr: data);
741 threadfunc(u);
742 return 0;
743}
744
745void *thread_init(void (*threadfunc)(void *), void *u, const char *name)
746{
747 struct THREAD_RUN *data = (THREAD_RUN *)malloc(size: sizeof(*data));
748 data->threadfunc = threadfunc;
749 data->u = u;
750#if defined(CONF_FAMILY_UNIX)
751 {
752 pthread_attr_t attr;
753 dbg_assert(pthread_attr_init(&attr) == 0, "pthread_attr_init failure");
754#if defined(CONF_PLATFORM_MACOS) && defined(__MAC_10_10) && __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_10
755 dbg_assert(pthread_attr_set_qos_class_np(&attr, QOS_CLASS_USER_INTERACTIVE, 0) == 0, "pthread_attr_set_qos_class_np failure");
756#endif
757 pthread_t id;
758 dbg_assert(pthread_create(&id, &attr, thread_run, data) == 0, "pthread_create failure");
759#if defined(CONF_PLATFORM_EMSCRIPTEN)
760 // Return control to the browser's main thread to allow the pthread to be started,
761 // otherwise we deadlock when waiting for a thread immediately after starting it.
762 emscripten_sleep(0);
763#endif
764 return (void *)id;
765 }
766#elif defined(CONF_FAMILY_WINDOWS)
767 HANDLE thread = CreateThread(nullptr, 0, thread_run, data, 0, nullptr);
768 dbg_assert(thread != nullptr, "CreateThread failure");
769 HMODULE kernel_base_handle;
770 if(GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN, L"KernelBase.dll", &kernel_base_handle))
771 {
772 // Intentional
773#ifdef __MINGW32__
774#pragma GCC diagnostic push
775#pragma GCC diagnostic ignored "-Wcast-function-type"
776#endif
777 auto set_thread_description_function = reinterpret_cast<HRESULT(WINAPI *)(HANDLE, PCWSTR)>(GetProcAddress(kernel_base_handle, "SetThreadDescription"));
778#ifdef __MINGW32__
779#pragma GCC diagnostic pop
780#endif
781 if(set_thread_description_function)
782 set_thread_description_function(thread, windows_utf8_to_wide(name).c_str());
783 }
784 return thread;
785#else
786#error not implemented
787#endif
788}
789
790void thread_wait(void *thread)
791{
792#if defined(CONF_FAMILY_UNIX)
793 dbg_assert(pthread_join((pthread_t)thread, nullptr) == 0, "pthread_join failure");
794#elif defined(CONF_FAMILY_WINDOWS)
795 dbg_assert(WaitForSingleObject((HANDLE)thread, INFINITE) == WAIT_OBJECT_0, "WaitForSingleObject failure");
796 dbg_assert(CloseHandle(thread), "CloseHandle failure");
797#else
798#error not implemented
799#endif
800}
801
802void thread_yield()
803{
804#if defined(CONF_FAMILY_UNIX)
805 dbg_assert(sched_yield() == 0, "sched_yield failure");
806#elif defined(CONF_FAMILY_WINDOWS)
807 Sleep(0);
808#else
809#error not implemented
810#endif
811}
812
813void thread_detach(void *thread)
814{
815#if defined(CONF_FAMILY_UNIX)
816 dbg_assert(pthread_detach((pthread_t)thread) == 0, "pthread_detach failure");
817#elif defined(CONF_FAMILY_WINDOWS)
818 dbg_assert(CloseHandle(thread), "CloseHandle failure");
819#else
820#error not implemented
821#endif
822}
823
824void thread_init_and_detach(void (*threadfunc)(void *), void *u, const char *name)
825{
826 void *thread = thread_init(threadfunc, u, name);
827 thread_detach(thread);
828}
829
830#if defined(CONF_FAMILY_WINDOWS)
831void sphore_init(SEMAPHORE *sem)
832{
833 *sem = CreateSemaphoreW(nullptr, 0, std::numeric_limits<LONG>::max(), nullptr);
834 dbg_assert(*sem != nullptr, "CreateSemaphoreW failure");
835}
836void sphore_wait(SEMAPHORE *sem)
837{
838 dbg_assert(WaitForSingleObject((HANDLE)*sem, INFINITE) == WAIT_OBJECT_0, "WaitForSingleObject failure");
839}
840void sphore_signal(SEMAPHORE *sem)
841{
842 dbg_assert(ReleaseSemaphore((HANDLE)*sem, 1, nullptr), "ReleaseSemaphore failure");
843}
844void sphore_destroy(SEMAPHORE *sem)
845{
846 dbg_assert(CloseHandle((HANDLE)*sem), "CloseHandle failure");
847}
848#elif defined(CONF_PLATFORM_MACOS)
849void sphore_init(SEMAPHORE *sem)
850{
851 char aBuf[64];
852 str_format(aBuf, sizeof(aBuf), "/%d.%p", pid(), (void *)sem);
853 *sem = sem_open(aBuf, O_CREAT | O_EXCL, S_IRWXU | S_IRWXG, 0);
854 dbg_assert(*sem != SEM_FAILED, "sem_open failure, errno=%d, name='%s'", errno, aBuf);
855}
856void sphore_wait(SEMAPHORE *sem)
857{
858 while(true)
859 {
860 if(sem_wait(*sem) == 0)
861 break;
862 dbg_assert(errno == EINTR, "sem_wait failure");
863 }
864}
865void sphore_signal(SEMAPHORE *sem)
866{
867 dbg_assert(sem_post(*sem) == 0, "sem_post failure");
868}
869void sphore_destroy(SEMAPHORE *sem)
870{
871 dbg_assert(sem_close(*sem) == 0, "sem_close failure");
872 char aBuf[64];
873 str_format(aBuf, sizeof(aBuf), "/%d.%p", pid(), (void *)sem);
874 dbg_assert(sem_unlink(aBuf) == 0, "sem_unlink failure");
875}
876#elif defined(CONF_FAMILY_UNIX)
877void sphore_init(SEMAPHORE *sem)
878{
879 dbg_assert(sem_init(sem, 0, 0) == 0, "sem_init failure");
880}
881void sphore_wait(SEMAPHORE *sem)
882{
883 while(true)
884 {
885 if(sem_wait(sem: sem) == 0)
886 break;
887 dbg_assert(errno == EINTR, "sem_wait failure");
888 }
889}
890void sphore_signal(SEMAPHORE *sem)
891{
892 dbg_assert(sem_post(sem) == 0, "sem_post failure");
893}
894void sphore_destroy(SEMAPHORE *sem)
895{
896 dbg_assert(sem_destroy(sem) == 0, "sem_destroy failure");
897}
898#endif
899
900/* ----- network ----- */
901
902const NETADDR NETADDR_ZEROED = {.type: NETTYPE_INVALID, .ip: {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, .port: 0};
903
904static void netaddr_to_sockaddr_in(const NETADDR *src, sockaddr_in *dest)
905{
906 dbg_assert((src->type & NETTYPE_IPV4) != 0, "Invalid address type '%d' for netaddr_to_sockaddr_in", src->type);
907 mem_zero(block: dest, size: sizeof(*dest));
908 dest->sin_family = AF_INET;
909 dest->sin_port = htons(hostshort: src->port);
910 mem_copy(dest: &dest->sin_addr.s_addr, source: src->ip, size: 4);
911}
912
913static void netaddr_to_sockaddr_in6(const NETADDR *src, sockaddr_in6 *dest)
914{
915 dbg_assert((src->type & NETTYPE_IPV6) != 0, "Invalid address type '%d' for netaddr_to_sockaddr_in6", src->type);
916 mem_zero(block: dest, size: sizeof(*dest));
917 dest->sin6_family = AF_INET6;
918 dest->sin6_port = htons(hostshort: src->port);
919 mem_copy(dest: &dest->sin6_addr.s6_addr, source: src->ip, size: 16);
920}
921
922static void sockaddr_to_netaddr(const sockaddr *src, socklen_t src_len, NETADDR *dst)
923{
924 *dst = NETADDR_ZEROED;
925 if(src->sa_family == AF_INET && src_len >= (socklen_t)sizeof(sockaddr_in))
926 {
927 const sockaddr_in *src_in = (const sockaddr_in *)src;
928 dst->type = NETTYPE_IPV4;
929 dst->port = htons(hostshort: src_in->sin_port);
930 static_assert(sizeof(dst->ip) >= sizeof(src_in->sin_addr.s_addr));
931 mem_copy(dest: dst->ip, source: &src_in->sin_addr.s_addr, size: sizeof(src_in->sin_addr.s_addr));
932 }
933 else if(src->sa_family == AF_INET6 && src_len >= (socklen_t)sizeof(sockaddr_in6))
934 {
935 const sockaddr_in6 *src_in6 = (const sockaddr_in6 *)src;
936 dst->type = NETTYPE_IPV6;
937 dst->port = htons(hostshort: src_in6->sin6_port);
938 static_assert(sizeof(dst->ip) >= sizeof(src_in6->sin6_addr.s6_addr));
939 mem_copy(dest: dst->ip, source: &src_in6->sin6_addr.s6_addr, size: sizeof(src_in6->sin6_addr.s6_addr));
940 }
941 else
942 {
943 log_warn("net", "Cannot convert sockaddr of family %d", src->sa_family);
944 }
945}
946
947int net_addr_comp(const NETADDR *a, const NETADDR *b)
948{
949 int diff = a->type - b->type;
950 if(diff != 0)
951 {
952 return diff;
953 }
954 diff = mem_comp(a: a->ip, b: b->ip, size: sizeof(a->ip));
955 if(diff != 0)
956 {
957 return diff;
958 }
959 return a->port - b->port;
960}
961
962bool NETADDR::operator==(const NETADDR &other) const
963{
964 return net_addr_comp(a: this, b: &other) == 0;
965}
966
967bool NETADDR::operator!=(const NETADDR &other) const
968{
969 return net_addr_comp(a: this, b: &other) != 0;
970}
971
972bool NETADDR::operator<(const NETADDR &other) const
973{
974 return net_addr_comp(a: this, b: &other) < 0;
975}
976
977size_t std::hash<NETADDR>::operator()(const NETADDR &Addr) const noexcept
978{
979 size_t seed = std::hash<unsigned int>{}(Addr.type);
980 seed ^= std::hash<std::string_view>{}(std::string_view(reinterpret_cast<const char *>(Addr.ip), sizeof(Addr.ip))) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
981 seed ^= std::hash<unsigned short>{}(Addr.port) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
982 return seed;
983}
984
985int net_addr_comp_noport(const NETADDR *a, const NETADDR *b)
986{
987 int diff = a->type - b->type;
988 if(diff != 0)
989 {
990 return diff;
991 }
992 return mem_comp(a: a->ip, b: b->ip, size: sizeof(a->ip));
993}
994
995void net_addr_str_v6(const unsigned short ip[8], int port, char *buffer, int buffer_size)
996{
997 int longest_seq_len = 0;
998 int longest_seq_start = -1;
999 int w = 0;
1000 int i;
1001 {
1002 int seq_len = 0;
1003 int seq_start = -1;
1004 // Determine longest sequence of zeros.
1005 for(i = 0; i < 8 + 1; i++)
1006 {
1007 if(seq_start != -1)
1008 {
1009 if(i == 8 || ip[i] != 0)
1010 {
1011 if(longest_seq_len < seq_len)
1012 {
1013 longest_seq_len = seq_len;
1014 longest_seq_start = seq_start;
1015 }
1016 seq_len = 0;
1017 seq_start = -1;
1018 }
1019 else
1020 {
1021 seq_len += 1;
1022 }
1023 }
1024 else
1025 {
1026 if(i != 8 && ip[i] == 0)
1027 {
1028 seq_start = i;
1029 seq_len = 1;
1030 }
1031 }
1032 }
1033 }
1034 if(longest_seq_len <= 1)
1035 {
1036 longest_seq_len = 0;
1037 longest_seq_start = -1;
1038 }
1039 w += str_copy(dst: buffer + w, src: "[", dst_size: buffer_size - w);
1040 for(i = 0; i < 8; i++)
1041 {
1042 if(longest_seq_start <= i && i < longest_seq_start + longest_seq_len)
1043 {
1044 if(i == longest_seq_start)
1045 {
1046 w += str_copy(dst: buffer + w, src: "::", dst_size: buffer_size - w);
1047 }
1048 }
1049 else
1050 {
1051 const char *colon = (i == 0 || i == longest_seq_start + longest_seq_len) ? "" : ":";
1052 w += str_format(buffer: buffer + w, buffer_size: buffer_size - w, format: "%s%x", colon, ip[i]);
1053 }
1054 }
1055 w += str_copy(dst: buffer + w, src: "]", dst_size: buffer_size - w);
1056 if(port >= 0)
1057 {
1058 str_format(buffer: buffer + w, buffer_size: buffer_size - w, format: ":%d", port);
1059 }
1060}
1061
1062void net_addr_str(const NETADDR *addr, char *string, int max_length, bool add_port)
1063{
1064 if((addr->type & (NETTYPE_IPV4 | NETTYPE_WEBSOCKET_IPV4)) != 0)
1065 {
1066 if(add_port)
1067 {
1068 str_format(buffer: string, buffer_size: max_length, format: "%d.%d.%d.%d:%d", addr->ip[0], addr->ip[1], addr->ip[2], addr->ip[3], addr->port);
1069 }
1070 else
1071 {
1072 str_format(buffer: string, buffer_size: max_length, format: "%d.%d.%d.%d", addr->ip[0], addr->ip[1], addr->ip[2], addr->ip[3]);
1073 }
1074 }
1075 else if((addr->type & (NETTYPE_IPV6 | NETTYPE_WEBSOCKET_IPV6)) != 0)
1076 {
1077 unsigned short ip[8];
1078 for(int i = 0; i < 8; i++)
1079 {
1080 ip[i] = (addr->ip[i * 2] << 8) | (addr->ip[i * 2 + 1]);
1081 }
1082 int port = add_port ? addr->port : -1;
1083 net_addr_str_v6(ip, port, buffer: string, buffer_size: max_length);
1084 }
1085 else
1086 {
1087 dbg_assert_failed("unknown NETADDR type %d", addr->type);
1088 }
1089}
1090
1091static int priv_net_extract(const char *hostname, char *host, int max_host, int *port)
1092{
1093 *port = 0;
1094 host[0] = 0;
1095
1096 if(hostname[0] == '[')
1097 {
1098 // ipv6 mode
1099 int i;
1100 for(i = 1; i < max_host && hostname[i] && hostname[i] != ']'; i++)
1101 host[i - 1] = hostname[i];
1102 host[i - 1] = 0;
1103 if(hostname[i] != ']') // malformatted
1104 return -1;
1105
1106 i++;
1107 if(hostname[i] == ':')
1108 *port = str_toint(str: hostname + i + 1);
1109 }
1110 else
1111 {
1112 // generic mode (ipv4, hostname etc)
1113 int i;
1114 for(i = 0; i < max_host - 1 && hostname[i] && hostname[i] != ':'; i++)
1115 host[i] = hostname[i];
1116 host[i] = 0;
1117
1118 if(hostname[i] == ':')
1119 *port = str_toint(str: hostname + i + 1);
1120 }
1121
1122 return 0;
1123}
1124
1125static int net_host_lookup_fallback(const char *hostname, NETADDR *addr, int types, int port)
1126{
1127 if(str_comp_nocase(a: hostname, b: "localhost") == 0)
1128 {
1129 if(types == NETTYPE_IPV4)
1130 {
1131 dbg_assert(net_addr_from_str(addr, "127.0.0.1") == 0, "unreachable");
1132 addr->port = port;
1133 return 0;
1134 }
1135 else if(types == NETTYPE_IPV6)
1136 {
1137 dbg_assert(net_addr_from_str(addr, "[::1]") == 0, "unreachable");
1138 addr->port = port;
1139 return 0;
1140 }
1141 else
1142 {
1143 // TODO: return both IPv4 and IPv6 address
1144 dbg_assert(net_addr_from_str(addr, "127.0.0.1") == 0, "unreachable");
1145 addr->port = port;
1146 return 0;
1147 }
1148 }
1149 return -1;
1150}
1151
1152static int net_host_lookup_impl(const char *hostname, NETADDR *addr, int types)
1153{
1154 char host[256];
1155 int port = 0;
1156 if(priv_net_extract(hostname, host, max_host: sizeof(host), port: &port))
1157 return -1;
1158
1159 log_trace("host_lookup", "host='%s' port='%d' types='%d'", host, port, types);
1160
1161 struct addrinfo hints;
1162 mem_zero(block: &hints, size: sizeof(hints));
1163
1164 if(types == NETTYPE_IPV4)
1165 hints.ai_family = AF_INET;
1166 else if(types == NETTYPE_IPV6)
1167 hints.ai_family = AF_INET6;
1168 else
1169 hints.ai_family = AF_UNSPEC;
1170
1171 struct addrinfo *result = nullptr;
1172 int e = getaddrinfo(name: host, service: nullptr, req: &hints, pai: &result);
1173 if(!result)
1174 {
1175 return net_host_lookup_fallback(hostname, addr, types, port);
1176 }
1177
1178 if(e != 0)
1179 {
1180 freeaddrinfo(ai: result);
1181 return net_host_lookup_fallback(hostname, addr, types, port);
1182 }
1183
1184 sockaddr_to_netaddr(src: result->ai_addr, src_len: result->ai_addrlen, dst: addr);
1185 addr->port = port;
1186 freeaddrinfo(ai: result);
1187 return 0;
1188}
1189
1190int net_host_lookup(const char *hostname, NETADDR *addr, int types)
1191{
1192 const char *ws_hostname = str_startswith(str: hostname, prefix: "ws://");
1193 if(ws_hostname)
1194 {
1195 if((types & (NETTYPE_WEBSOCKET_IPV4 | NETTYPE_WEBSOCKET_IPV6)) == 0)
1196 {
1197 return -1;
1198 }
1199 int result = net_host_lookup_impl(hostname: ws_hostname, addr, types: types & ~(NETTYPE_WEBSOCKET_IPV4 | NETTYPE_WEBSOCKET_IPV6));
1200 if(result == 0)
1201 {
1202 if(addr->type == NETTYPE_IPV4)
1203 {
1204 addr->type = NETTYPE_WEBSOCKET_IPV4;
1205 }
1206 else if(addr->type == NETTYPE_IPV6)
1207 {
1208 addr->type = NETTYPE_WEBSOCKET_IPV6;
1209 }
1210 }
1211 return result;
1212 }
1213 return net_host_lookup_impl(hostname, addr, types: types & ~(NETTYPE_WEBSOCKET_IPV4 | NETTYPE_WEBSOCKET_IPV6));
1214}
1215
1216static int parse_int(int *out, const char **str)
1217{
1218 int i = 0;
1219 *out = 0;
1220 if(!str_isnum(c: **str))
1221 return -1;
1222
1223 i = **str - '0';
1224 (*str)++;
1225
1226 while(true)
1227 {
1228 if(!str_isnum(c: **str))
1229 {
1230 *out = i;
1231 return 0;
1232 }
1233
1234 i = (i * 10) + (**str - '0');
1235 (*str)++;
1236 }
1237
1238 return 0;
1239}
1240
1241static int parse_char(char c, const char **str)
1242{
1243 if(**str != c)
1244 return -1;
1245 (*str)++;
1246 return 0;
1247}
1248
1249static int parse_uint8(unsigned char *out, const char **str)
1250{
1251 int i;
1252 if(parse_int(out: &i, str) != 0)
1253 return -1;
1254 if(i < 0 || i > 0xff)
1255 return -1;
1256 *out = i;
1257 return 0;
1258}
1259
1260static int parse_uint16(unsigned short *out, const char **str)
1261{
1262 int i;
1263 if(parse_int(out: &i, str) != 0)
1264 return -1;
1265 if(i < 0 || i > 0xffff)
1266 return -1;
1267 *out = i;
1268 return 0;
1269}
1270
1271int net_addr_from_url(NETADDR *addr, const char *string, char *host_buf, size_t host_buf_size)
1272{
1273 bool sixup = false;
1274 mem_zero(block: addr, size: sizeof(*addr));
1275 const char *str = str_startswith(str: string, prefix: "tw-0.6+udp://");
1276 if(!str && (str = str_startswith(str: string, prefix: "tw-0.7+udp://")))
1277 {
1278 addr->type |= NETTYPE_TW7;
1279 sixup = true;
1280 }
1281 if(!str)
1282 return 1;
1283
1284 int length = str_length(str);
1285 int start = 0;
1286 int end = length;
1287 for(int i = 0; i < length; i++)
1288 {
1289 if(str[i] == '@')
1290 {
1291 if(start != 0)
1292 {
1293 // Two at signs.
1294 return true;
1295 }
1296 start = i + 1;
1297 }
1298 else if(str[i] == '/' || str[i] == '?' || str[i] == '#')
1299 {
1300 end = i;
1301 break;
1302 }
1303 }
1304
1305 char host[128];
1306 str_truncate(dst: host, dst_size: sizeof(host), src: str + start, truncation_len: end - start);
1307 if(host_buf)
1308 str_copy(dst: host_buf, src: host, dst_size: host_buf_size);
1309
1310 int failure = net_addr_from_str(addr, string: host);
1311 if(failure)
1312 return failure;
1313
1314 if(sixup)
1315 addr->type |= NETTYPE_TW7;
1316
1317 return failure;
1318}
1319
1320bool net_addr_is_local(const NETADDR *addr)
1321{
1322 char addr_str[NETADDR_MAXSTRSIZE];
1323 net_addr_str(addr, string: addr_str, max_length: sizeof(addr_str), add_port: true);
1324
1325 if(addr->ip[0] == 127 || addr->ip[0] == 10 || (addr->ip[0] == 192 && addr->ip[1] == 168) || (addr->ip[0] == 172 && (addr->ip[1] >= 16 && addr->ip[1] <= 31)))
1326 return true;
1327
1328 if(str_startswith(str: addr_str, prefix: "[fe80:") || str_startswith(str: addr_str, prefix: "[::1"))
1329 return true;
1330
1331 return false;
1332}
1333
1334int net_addr_from_str(NETADDR *addr, const char *string)
1335{
1336 const char *str = string;
1337 mem_zero(block: addr, size: sizeof(NETADDR));
1338
1339 if(str[0] == '[')
1340 {
1341 /* ipv6 */
1342 sockaddr_in6 sa6;
1343 char buf[128];
1344 int i;
1345 str++;
1346 for(i = 0; i < 127 && str[i] && str[i] != ']'; i++)
1347 buf[i] = str[i];
1348 buf[i] = 0;
1349 str += i;
1350#if defined(CONF_FAMILY_WINDOWS)
1351 {
1352 int size;
1353 sa6.sin6_family = AF_INET6;
1354 size = (int)sizeof(sa6);
1355 if(WSAStringToAddressA(buf, AF_INET6, nullptr, (sockaddr *)&sa6, &size) != 0)
1356 return -1;
1357 }
1358#else
1359 sa6.sin6_family = AF_INET6;
1360
1361 if(inet_pton(AF_INET6, cp: buf, buf: &sa6.sin6_addr) != 1)
1362 return -1;
1363#endif
1364 sockaddr_to_netaddr(src: (sockaddr *)&sa6, src_len: sizeof(sa6), dst: addr);
1365
1366 if(*str == ']')
1367 {
1368 str++;
1369 if(*str == ':')
1370 {
1371 str++;
1372 if(parse_uint16(out: &addr->port, str: &str))
1373 return -1;
1374 }
1375 else
1376 {
1377 addr->port = 0;
1378 }
1379 }
1380 else
1381 return -1;
1382
1383 return 0;
1384 }
1385 else
1386 {
1387 /* ipv4 */
1388 if(parse_uint8(out: &addr->ip[0], str: &str))
1389 return -1;
1390 if(parse_char(c: '.', str: &str))
1391 return -1;
1392 if(parse_uint8(out: &addr->ip[1], str: &str))
1393 return -1;
1394 if(parse_char(c: '.', str: &str))
1395 return -1;
1396 if(parse_uint8(out: &addr->ip[2], str: &str))
1397 return -1;
1398 if(parse_char(c: '.', str: &str))
1399 return -1;
1400 if(parse_uint8(out: &addr->ip[3], str: &str))
1401 return -1;
1402 if(*str == ':')
1403 {
1404 str++;
1405 if(parse_uint16(out: &addr->port, str: &str))
1406 return -1;
1407 }
1408 if(*str != '\0')
1409 return -1;
1410
1411 addr->type = NETTYPE_IPV4;
1412 }
1413
1414 return 0;
1415}
1416
1417static void priv_net_close_socket(int sock)
1418{
1419#if defined(CONF_FAMILY_WINDOWS)
1420 dbg_assert(closesocket(sock) == 0, "closesocket failure (%s)", net_error_message().c_str());
1421#else
1422 dbg_assert(close(sock) == 0, "close failure (%s)", net_error_message().c_str());
1423#endif
1424}
1425
1426static void priv_net_close_all_sockets(NETSOCKET sock)
1427{
1428 if(sock->ipv4sock >= 0)
1429 {
1430 priv_net_close_socket(sock: sock->ipv4sock);
1431 sock->ipv4sock = -1;
1432 sock->type &= ~NETTYPE_IPV4;
1433 }
1434
1435#if defined(CONF_WEBSOCKETS)
1436 if(sock->web_ipv4sock >= 0)
1437 {
1438 websocket_destroy(socket: sock->web_ipv4sock);
1439 sock->web_ipv4sock = -1;
1440 sock->type &= ~NETTYPE_WEBSOCKET_IPV4;
1441 }
1442#endif
1443
1444 if(sock->ipv6sock >= 0)
1445 {
1446 priv_net_close_socket(sock: sock->ipv6sock);
1447 sock->ipv6sock = -1;
1448 sock->type &= ~NETTYPE_IPV6;
1449 }
1450
1451#if defined(CONF_WEBSOCKETS)
1452 if(sock->web_ipv6sock >= 0)
1453 {
1454 websocket_destroy(socket: sock->web_ipv6sock);
1455 sock->web_ipv6sock = -1;
1456 sock->type &= ~NETTYPE_WEBSOCKET_IPV6;
1457 }
1458#endif
1459
1460 free(ptr: sock);
1461}
1462
1463static int priv_net_create_socket(int domain, int type, const NETADDR *bindaddr)
1464{
1465 int sock = socket(domain: domain, type: type, protocol: 0);
1466 if(sock < 0)
1467 {
1468 log_error("net", "Failed to create socket with domain %d and type %d (%s)", domain, type, net_error_message().c_str());
1469 return -1;
1470 }
1471
1472#if defined(CONF_FAMILY_UNIX)
1473 // On TCP sockets set SO_REUSEADDR to fix port rebind on restart
1474 if(domain == AF_INET && type == SOCK_STREAM)
1475 {
1476 int reuse_addr = 1;
1477 if(setsockopt(fd: sock, SOL_SOCKET, SO_REUSEADDR, optval: (const char *)&reuse_addr, optlen: sizeof(reuse_addr)) != 0)
1478 {
1479 log_error("net", "Setting SO_REUSEADDR failed with domain %d and type %d (%s)", domain, type, net_error_message().c_str());
1480 }
1481 }
1482#elif defined(CONF_FAMILY_WINDOWS)
1483 {
1484 // Ensure exclusive use of address, otherwise it's possible on Windows to bind to the same address and port with another socket.
1485 // See https://learn.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse (last update 06/14/2022)
1486 int exclusive_addr_use = 1;
1487 if(setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (const char *)&exclusive_addr_use, sizeof(exclusive_addr_use)) != 0)
1488 {
1489 log_error("net", "Setting SO_EXCLUSIVEADDRUSE failed with domain %d and type %d (%s)", domain, type, net_error_message().c_str());
1490 }
1491 }
1492#endif
1493
1494 // Set to IPv6-only if that's what we are creating, to ensure that dual-stack does not block the same IPv4 port.
1495#if defined(IPV6_V6ONLY)
1496 if(domain == AF_INET6)
1497 {
1498 int ipv6only = 1;
1499 if(setsockopt(fd: sock, IPPROTO_IPV6, IPV6_V6ONLY, optval: (const char *)&ipv6only, optlen: sizeof(ipv6only)) != 0)
1500 {
1501 log_error("net", "Setting IPV6_V6ONLY failed with domain %d and type %d (%s)", domain, type, net_error_message().c_str());
1502 }
1503 }
1504#endif
1505
1506 sockaddr_storage addr;
1507 socklen_t addr_len;
1508 if(bindaddr->type == NETTYPE_IPV4)
1509 {
1510 netaddr_to_sockaddr_in(src: bindaddr, dest: (sockaddr_in *)&addr);
1511 addr_len = sizeof(sockaddr_in);
1512 }
1513 else if(bindaddr->type == NETTYPE_IPV6)
1514 {
1515 netaddr_to_sockaddr_in6(src: bindaddr, dest: (sockaddr_in6 *)&addr);
1516 addr_len = sizeof(sockaddr_in6);
1517 }
1518 else
1519 {
1520 dbg_assert_failed("socket type invalid: %d", type);
1521 }
1522
1523 if(bind(fd: sock, addr: (sockaddr *)&addr, len: addr_len) != 0)
1524 {
1525 log_error("net", "Failed to bind socket with domain %d and type %d (%s)", domain, type, net_error_message().c_str());
1526 priv_net_close_socket(sock);
1527 return -1;
1528 }
1529
1530 return sock;
1531}
1532
1533int net_socket_type(NETSOCKET sock)
1534{
1535 return sock->type;
1536}
1537
1538NETSOCKET net_udp_create(NETADDR bindaddr)
1539{
1540 NETSOCKET sock = (NETSOCKET_INTERNAL *)malloc(size: sizeof(*sock));
1541 *sock = invalid_socket;
1542
1543 if(bindaddr.type & NETTYPE_IPV4)
1544 {
1545 NETADDR bindaddr_ipv4 = bindaddr;
1546 bindaddr_ipv4.type = NETTYPE_IPV4;
1547 const int socket = priv_net_create_socket(AF_INET, SOCK_DGRAM, bindaddr: &bindaddr_ipv4);
1548 if(socket >= 0)
1549 {
1550 sock->type |= NETTYPE_IPV4;
1551 sock->ipv4sock = socket;
1552
1553 // Set broadcast
1554 {
1555 int broadcast = 1;
1556 if(setsockopt(fd: socket, SOL_SOCKET, SO_BROADCAST, optval: (const char *)&broadcast, optlen: sizeof(broadcast)) != 0)
1557 {
1558 log_error("net", "Setting SO_BROADCAST on IPv4 failed (%s)", net_error_message().c_str());
1559 }
1560 }
1561
1562 // Set DSCP/TOS
1563 {
1564 int iptos = 0x10; // IPTOS_LOWDELAY
1565 if(setsockopt(fd: socket, IPPROTO_IP, IP_TOS, optval: (const char *)&iptos, optlen: sizeof(iptos)) != 0)
1566 {
1567 log_error("net", "Setting IP_TOS on IPv4 failed (%s)", net_error_message().c_str());
1568 }
1569 }
1570 }
1571 }
1572
1573#if defined(CONF_WEBSOCKETS)
1574 if(bindaddr.type & NETTYPE_WEBSOCKET_IPV4)
1575 {
1576 NETADDR bindaddr_websocket_ipv4 = bindaddr;
1577 bindaddr_websocket_ipv4.type = NETTYPE_WEBSOCKET_IPV4;
1578 const int socket = websocket_create(bindaddr: &bindaddr_websocket_ipv4);
1579 if(socket >= 0)
1580 {
1581 sock->type |= NETTYPE_WEBSOCKET_IPV4;
1582 sock->web_ipv4sock = socket;
1583 }
1584 }
1585#endif
1586
1587 if(bindaddr.type & NETTYPE_IPV6)
1588 {
1589 NETADDR bindaddr_ipv6 = bindaddr;
1590 bindaddr_ipv6.type = NETTYPE_IPV6;
1591 const int socket = priv_net_create_socket(AF_INET6, SOCK_DGRAM, bindaddr: &bindaddr_ipv6);
1592 if(socket >= 0)
1593 {
1594 sock->type |= NETTYPE_IPV6;
1595 sock->ipv6sock = socket;
1596
1597 // Set broadcast
1598 {
1599 int broadcast = 1;
1600 if(setsockopt(fd: socket, SOL_SOCKET, SO_BROADCAST, optval: (const char *)&broadcast, optlen: sizeof(broadcast)) != 0)
1601 {
1602 log_error("net", "Setting SO_BROADCAST on IPv6 failed (%s)", net_error_message().c_str());
1603 }
1604 }
1605
1606 // Set DSCP/TOS
1607 // TODO: setting IP_TOS on ipv6 with setsockopt is not supported on Windows, see https://github.com/ddnet/ddnet/issues/7605
1608#if !defined(CONF_FAMILY_WINDOWS)
1609 {
1610 int iptos = 0x10; // IPTOS_LOWDELAY
1611 if(setsockopt(fd: socket, IPPROTO_IP, IP_TOS, optval: (const char *)&iptos, optlen: sizeof(iptos)) != 0)
1612 {
1613 log_error("net", "Setting IP_TOS on IPv6 failed (%s)", net_error_message().c_str());
1614 }
1615 }
1616#endif
1617 }
1618 }
1619
1620#if defined(CONF_WEBSOCKETS)
1621 if(bindaddr.type & NETTYPE_WEBSOCKET_IPV6)
1622 {
1623 NETADDR bindaddr_websocket_ipv6 = bindaddr;
1624 bindaddr_websocket_ipv6.type = NETTYPE_WEBSOCKET_IPV6;
1625 const int socket = websocket_create(bindaddr: &bindaddr_websocket_ipv6);
1626 if(socket >= 0)
1627 {
1628 sock->type |= NETTYPE_WEBSOCKET_IPV6;
1629 sock->web_ipv6sock = socket;
1630 }
1631 }
1632#endif
1633
1634 if(sock->type == NETTYPE_INVALID)
1635 {
1636 free(ptr: sock);
1637 sock = nullptr;
1638 }
1639 else
1640 {
1641 net_set_non_blocking(sock);
1642 net_buffer_init(buffer: &sock->buffer);
1643 }
1644
1645 return sock;
1646}
1647
1648int net_udp_send(NETSOCKET sock, const NETADDR *addr, const void *data, int size)
1649{
1650 int d = -1;
1651
1652 if(addr->type & NETTYPE_IPV4)
1653 {
1654 if(sock->ipv4sock >= 0)
1655 {
1656 sockaddr_in sa;
1657 if(addr->type & NETTYPE_LINK_BROADCAST)
1658 {
1659 mem_zero(block: &sa, size: sizeof(sa));
1660 sa.sin_port = htons(hostshort: addr->port);
1661 sa.sin_family = AF_INET;
1662 sa.sin_addr.s_addr = INADDR_BROADCAST;
1663 }
1664 else
1665 {
1666 netaddr_to_sockaddr_in(src: addr, dest: &sa);
1667 }
1668
1669 d = sendto(fd: sock->ipv4sock, buf: (const char *)data, n: size, flags: 0, addr: (sockaddr *)&sa, addr_len: sizeof(sa));
1670 }
1671 else
1672 {
1673 log_error("net", "Cannot send IPv4 traffic to this socket");
1674 }
1675 }
1676
1677#if defined(CONF_WEBSOCKETS)
1678 if(addr->type & NETTYPE_WEBSOCKET_IPV4)
1679 {
1680 if(sock->web_ipv4sock >= 0)
1681 {
1682 if(addr->type & NETTYPE_LINK_BROADCAST)
1683 {
1684 log_error("net", "Cannot send broadcasts to Websocket IPv4");
1685 }
1686 else
1687 {
1688 d = websocket_send(socket: sock->web_ipv4sock, data: (const unsigned char *)data, size, addr);
1689 }
1690 }
1691 else
1692 {
1693 log_error("net", "Cannot send Websocket IPv4 traffic to this socket");
1694 }
1695 }
1696#endif
1697
1698 if(addr->type & NETTYPE_IPV6)
1699 {
1700 if(sock->ipv6sock >= 0)
1701 {
1702 sockaddr_in6 sa;
1703 if(addr->type & NETTYPE_LINK_BROADCAST)
1704 {
1705 mem_zero(block: &sa, size: sizeof(sa));
1706 sa.sin6_port = htons(hostshort: addr->port);
1707 sa.sin6_family = AF_INET6;
1708 sa.sin6_addr.s6_addr[0] = 0xff; /* multicast */
1709 sa.sin6_addr.s6_addr[1] = 0x02; /* link local scope */
1710 sa.sin6_addr.s6_addr[15] = 1; /* all nodes */
1711 }
1712 else
1713 {
1714 netaddr_to_sockaddr_in6(src: addr, dest: &sa);
1715 }
1716
1717 d = sendto(fd: sock->ipv6sock, buf: (const char *)data, n: size, flags: 0, addr: (sockaddr *)&sa, addr_len: sizeof(sa));
1718 }
1719 else
1720 {
1721 log_error("net", "Cannot send IPv6 traffic to this socket");
1722 }
1723 }
1724
1725#if defined(CONF_WEBSOCKETS)
1726 if(addr->type & NETTYPE_WEBSOCKET_IPV6)
1727 {
1728 if(sock->web_ipv6sock >= 0)
1729 {
1730 if(addr->type & NETTYPE_LINK_BROADCAST)
1731 {
1732 log_error("net", "Cannot send broadcasts to Websocket IPv6");
1733 }
1734 else
1735 {
1736 d = websocket_send(socket: sock->web_ipv6sock, data: (const unsigned char *)data, size, addr);
1737 }
1738 }
1739 else
1740 {
1741 log_error("net", "Cannot send Websocket IPv6 traffic to this socket");
1742 }
1743 }
1744#endif
1745
1746 network_stats.sent_bytes += size;
1747 network_stats.sent_packets++;
1748 return d;
1749}
1750
1751void net_buffer_init(NETSOCKET_BUFFER *buffer)
1752{
1753#if defined(CONF_PLATFORM_LINUX)
1754 buffer->pos = 0;
1755 buffer->size = 0;
1756 mem_zero(block: buffer->msgs, size: sizeof(buffer->msgs));
1757 mem_zero(block: buffer->iovecs, size: sizeof(buffer->iovecs));
1758 mem_zero(block: buffer->sockaddrs, size: sizeof(buffer->sockaddrs));
1759 for(int i = 0; i < VLEN; ++i)
1760 {
1761 buffer->iovecs[i].iov_base = buffer->bufs[i];
1762 buffer->iovecs[i].iov_len = PACKETSIZE;
1763 buffer->msgs[i].msg_hdr.msg_iov = &(buffer->iovecs[i]);
1764 buffer->msgs[i].msg_hdr.msg_iovlen = 1;
1765 buffer->msgs[i].msg_hdr.msg_name = &(buffer->sockaddrs[i]);
1766 buffer->msgs[i].msg_hdr.msg_namelen = sizeof(buffer->sockaddrs[i]);
1767 }
1768#endif
1769}
1770
1771void net_buffer_reinit(NETSOCKET_BUFFER *buffer)
1772{
1773#if defined(CONF_PLATFORM_LINUX)
1774 for(int i = 0; i < VLEN; i++)
1775 {
1776 buffer->msgs[i].msg_hdr.msg_namelen = sizeof(buffer->sockaddrs[i]);
1777 }
1778#endif
1779}
1780
1781void net_buffer_simple(NETSOCKET_BUFFER *buffer, char **buf, int *size)
1782{
1783#if defined(CONF_PLATFORM_LINUX)
1784 *buf = buffer->bufs[0];
1785 *size = sizeof(buffer->bufs[0]);
1786#else
1787 *buf = buffer->buf;
1788 *size = sizeof(buffer->buf);
1789#endif
1790}
1791
1792int net_udp_recv(NETSOCKET sock, NETADDR *addr, unsigned char **data)
1793{
1794 static const auto &&update_stats = [](int bytes) {
1795 network_stats.recv_bytes += bytes;
1796 network_stats.recv_packets++;
1797 };
1798
1799 int bytes = 0;
1800#if defined(CONF_PLATFORM_LINUX)
1801 if(sock->ipv4sock >= 0)
1802 {
1803 if(sock->buffer.pos >= sock->buffer.size)
1804 {
1805 net_buffer_reinit(buffer: &sock->buffer);
1806 sock->buffer.size = recvmmsg(fd: sock->ipv4sock, vmessages: sock->buffer.msgs, VLEN, flags: 0, NULL);
1807 sock->buffer.pos = 0;
1808 }
1809 }
1810
1811 if(sock->ipv6sock >= 0)
1812 {
1813 if(sock->buffer.pos >= sock->buffer.size)
1814 {
1815 net_buffer_reinit(buffer: &sock->buffer);
1816 sock->buffer.size = recvmmsg(fd: sock->ipv6sock, vmessages: sock->buffer.msgs, VLEN, flags: 0, NULL);
1817 sock->buffer.pos = 0;
1818 }
1819 }
1820
1821 if(sock->buffer.pos < sock->buffer.size)
1822 {
1823 sockaddr_to_netaddr(src: (sockaddr *)&(sock->buffer.sockaddrs[sock->buffer.pos]), src_len: sizeof(sock->buffer.sockaddrs[sock->buffer.pos]), dst: addr);
1824 bytes = sock->buffer.msgs[sock->buffer.pos].msg_len;
1825 *data = (unsigned char *)sock->buffer.bufs[sock->buffer.pos];
1826 sock->buffer.pos++;
1827 update_stats(bytes);
1828 return bytes;
1829 }
1830#else
1831 if(sock->ipv4sock >= 0)
1832 {
1833 sockaddr_storage recv_addr;
1834 socklen_t fromlen = sizeof(recv_addr);
1835 bytes = recvfrom(sock->ipv4sock, sock->buffer.buf, sizeof(sock->buffer.buf), 0, (sockaddr *)&recv_addr, &fromlen);
1836 *data = (unsigned char *)sock->buffer.buf;
1837 if(bytes > 0)
1838 {
1839 sockaddr_to_netaddr((sockaddr *)&recv_addr, fromlen, addr);
1840 update_stats(bytes);
1841 return bytes;
1842 }
1843 }
1844
1845 if(sock->ipv6sock >= 0)
1846 {
1847 sockaddr_storage recv_addr;
1848 socklen_t fromlen = sizeof(recv_addr);
1849 bytes = recvfrom(sock->ipv6sock, sock->buffer.buf, sizeof(sock->buffer.buf), 0, (sockaddr *)&recv_addr, &fromlen);
1850 *data = (unsigned char *)sock->buffer.buf;
1851 if(bytes > 0)
1852 {
1853 sockaddr_to_netaddr((sockaddr *)&recv_addr, fromlen, addr);
1854 update_stats(bytes);
1855 return bytes;
1856 }
1857 }
1858#endif
1859
1860#if defined(CONF_WEBSOCKETS)
1861 if(sock->web_ipv4sock >= 0)
1862 {
1863 char *buf;
1864 int size;
1865 net_buffer_simple(buffer: &sock->buffer, buf: &buf, size: &size);
1866 bytes = websocket_recv(socket: sock->web_ipv4sock, data: (unsigned char *)buf, maxsize: size, addr);
1867 *data = (unsigned char *)buf;
1868 if(bytes > 0)
1869 {
1870 update_stats(bytes);
1871 return bytes;
1872 }
1873 }
1874
1875 if(sock->web_ipv6sock >= 0)
1876 {
1877 char *buf;
1878 int size;
1879 net_buffer_simple(buffer: &sock->buffer, buf: &buf, size: &size);
1880 bytes = websocket_recv(socket: sock->web_ipv6sock, data: (unsigned char *)buf, maxsize: size, addr);
1881 *data = (unsigned char *)buf;
1882 if(bytes > 0)
1883 {
1884 update_stats(bytes);
1885 return bytes;
1886 }
1887 }
1888#endif
1889
1890 return bytes < 0 ? -1 : 0;
1891}
1892
1893void net_udp_close(NETSOCKET sock)
1894{
1895 priv_net_close_all_sockets(sock);
1896}
1897
1898NETSOCKET net_tcp_create(NETADDR bindaddr)
1899{
1900 NETSOCKET sock = (NETSOCKET_INTERNAL *)malloc(size: sizeof(*sock));
1901 *sock = invalid_socket;
1902
1903 if(bindaddr.type & NETTYPE_IPV4)
1904 {
1905 NETADDR bindaddr_ipv4 = bindaddr;
1906 bindaddr_ipv4.type = NETTYPE_IPV4;
1907 const int socket4 = priv_net_create_socket(AF_INET, SOCK_STREAM, bindaddr: &bindaddr_ipv4);
1908 if(socket4 >= 0)
1909 {
1910 sock->type |= NETTYPE_IPV4;
1911 sock->ipv4sock = socket4;
1912 }
1913 }
1914
1915 if(bindaddr.type & NETTYPE_IPV6)
1916 {
1917 NETADDR bindaddr_ipv6 = bindaddr;
1918 bindaddr_ipv6.type = NETTYPE_IPV6;
1919 const int socket6 = priv_net_create_socket(AF_INET6, SOCK_STREAM, bindaddr: &bindaddr_ipv6);
1920 if(socket6 >= 0)
1921 {
1922 sock->type |= NETTYPE_IPV6;
1923 sock->ipv6sock = socket6;
1924 }
1925 }
1926
1927 if(sock->type == NETTYPE_INVALID)
1928 {
1929 free(ptr: sock);
1930 sock = nullptr;
1931 }
1932
1933 return sock;
1934}
1935
1936static int net_set_blocking_impl(NETSOCKET sock, bool blocking)
1937{
1938 unsigned long mode = blocking ? 0 : 1;
1939 const char *mode_str = blocking ? "blocking" : "non-blocking";
1940 int sockets[] = {sock->ipv4sock, sock->ipv6sock};
1941 const char *socket_str[] = {"IPv4", "IPv6"};
1942
1943 for(size_t i = 0; i < std::size(sockets); ++i)
1944 {
1945 if(sockets[i] >= 0)
1946 {
1947#if defined(CONF_FAMILY_WINDOWS)
1948 if(ioctlsocket(sockets[i], FIONBIO, (unsigned long *)&mode) != NO_ERROR)
1949 {
1950 log_error("net", "Setting %s mode for %s socket failed (%s)", socket_str[i], mode_str, net_error_message().c_str());
1951 }
1952#else
1953 if(ioctl(fd: sockets[i], FIONBIO, (unsigned long *)&mode) == -1)
1954 {
1955 log_error("net", "Setting %s mode for %s socket failed (%s)", socket_str[i], mode_str, net_error_message().c_str());
1956 }
1957#endif
1958 }
1959 }
1960
1961 return 0;
1962}
1963
1964int net_set_non_blocking(NETSOCKET sock)
1965{
1966 return net_set_blocking_impl(sock, blocking: false);
1967}
1968
1969int net_set_blocking(NETSOCKET sock)
1970{
1971 return net_set_blocking_impl(sock, blocking: true);
1972}
1973
1974int net_tcp_listen(NETSOCKET sock, int backlog)
1975{
1976 int err = -1;
1977 if(sock->ipv4sock >= 0)
1978 {
1979 err = listen(fd: sock->ipv4sock, n: backlog);
1980 }
1981 if(sock->ipv6sock >= 0)
1982 {
1983 err = listen(fd: sock->ipv6sock, n: backlog);
1984 }
1985 return err;
1986}
1987
1988int net_tcp_accept(NETSOCKET sock, NETSOCKET *new_sock, NETADDR *a)
1989{
1990 *new_sock = nullptr;
1991
1992 if(sock->ipv4sock >= 0)
1993 {
1994 sockaddr_storage addr;
1995 socklen_t sockaddr_len = sizeof(addr);
1996
1997 int s = accept(fd: sock->ipv4sock, addr: (sockaddr *)&addr, addr_len: &sockaddr_len);
1998 if(s != -1)
1999 {
2000 sockaddr_to_netaddr(src: (sockaddr *)&addr, src_len: sockaddr_len, dst: a);
2001
2002 *new_sock = (NETSOCKET_INTERNAL *)malloc(size: sizeof(**new_sock));
2003 **new_sock = invalid_socket;
2004 (*new_sock)->type = NETTYPE_IPV4;
2005 (*new_sock)->ipv4sock = s;
2006 return s;
2007 }
2008 }
2009
2010 if(sock->ipv6sock >= 0)
2011 {
2012 sockaddr_storage addr;
2013 socklen_t sockaddr_len = sizeof(addr);
2014
2015 int s = accept(fd: sock->ipv6sock, addr: (sockaddr *)&addr, addr_len: &sockaddr_len);
2016 if(s != -1)
2017 {
2018 *new_sock = (NETSOCKET_INTERNAL *)malloc(size: sizeof(**new_sock));
2019 **new_sock = invalid_socket;
2020 sockaddr_to_netaddr(src: (sockaddr *)&addr, src_len: sockaddr_len, dst: a);
2021 (*new_sock)->type = NETTYPE_IPV6;
2022 (*new_sock)->ipv6sock = s;
2023 return s;
2024 }
2025 }
2026
2027 return -1;
2028}
2029
2030int net_tcp_connect(NETSOCKET sock, const NETADDR *a)
2031{
2032 if(a->type & NETTYPE_IPV4)
2033 {
2034 if(sock->ipv4sock < 0)
2035 return -2;
2036 sockaddr_in addr;
2037 netaddr_to_sockaddr_in(src: a, dest: &addr);
2038 return connect(fd: sock->ipv4sock, addr: (sockaddr *)&addr, len: sizeof(addr));
2039 }
2040
2041 if(a->type & NETTYPE_IPV6)
2042 {
2043 if(sock->ipv6sock < 0)
2044 return -2;
2045 sockaddr_in6 addr;
2046 netaddr_to_sockaddr_in6(src: a, dest: &addr);
2047 return connect(fd: sock->ipv6sock, addr: (sockaddr *)&addr, len: sizeof(addr));
2048 }
2049
2050 return -1;
2051}
2052
2053int net_tcp_connect_non_blocking(NETSOCKET sock, NETADDR bindaddr)
2054{
2055 net_set_non_blocking(sock);
2056 int res = net_tcp_connect(sock, a: &bindaddr);
2057 net_set_blocking(sock);
2058 return res;
2059}
2060
2061int net_tcp_send(NETSOCKET sock, const void *data, int size)
2062{
2063 int bytes = -1;
2064
2065 if(sock->ipv4sock >= 0)
2066 {
2067 bytes = send(fd: sock->ipv4sock, buf: (const char *)data, n: size, flags: 0);
2068 }
2069 if(sock->ipv6sock >= 0)
2070 {
2071 bytes = send(fd: sock->ipv6sock, buf: (const char *)data, n: size, flags: 0);
2072 }
2073
2074 return bytes;
2075}
2076
2077int net_tcp_recv(NETSOCKET sock, void *data, int maxsize)
2078{
2079 int bytes = -1;
2080
2081 if(sock->ipv4sock >= 0)
2082 {
2083 bytes = recv(fd: sock->ipv4sock, buf: (char *)data, n: maxsize, flags: 0);
2084 }
2085 if(sock->ipv6sock >= 0)
2086 {
2087 bytes = recv(fd: sock->ipv6sock, buf: (char *)data, n: maxsize, flags: 0);
2088 }
2089
2090 return bytes;
2091}
2092
2093void net_tcp_close(NETSOCKET sock)
2094{
2095 priv_net_close_all_sockets(sock);
2096}
2097
2098int net_errno()
2099{
2100#if defined(CONF_FAMILY_WINDOWS)
2101 return WSAGetLastError();
2102#else
2103 return errno;
2104#endif
2105}
2106
2107std::string net_error_message()
2108{
2109 const int error = net_errno();
2110#if defined(CONF_FAMILY_WINDOWS)
2111 const std::string message = windows_format_system_message(error);
2112 return std::to_string(error) + " '" + message + "'";
2113#else
2114 return std::to_string(val: error) + " '" + strerror(errnum: error) + "'";
2115#endif
2116}
2117
2118int net_would_block()
2119{
2120#if defined(CONF_FAMILY_WINDOWS)
2121 return net_errno() == WSAEWOULDBLOCK;
2122#else
2123 return net_errno() == EWOULDBLOCK;
2124#endif
2125}
2126
2127void net_init()
2128{
2129#if defined(CONF_FAMILY_WINDOWS)
2130 WSADATA wsa_data;
2131 dbg_assert(WSAStartup(MAKEWORD(1, 1), &wsa_data) == 0, "WSAStartup failure");
2132#endif
2133#if defined(CONF_WEBSOCKETS)
2134 websocket_init();
2135#endif
2136}
2137
2138#if defined(CONF_FAMILY_UNIX)
2139UNIXSOCKET net_unix_create_unnamed()
2140{
2141 return socket(AF_UNIX, SOCK_DGRAM, protocol: 0);
2142}
2143
2144int net_unix_send(UNIXSOCKET sock, UNIXSOCKETADDR *addr, void *data, int size)
2145{
2146 return sendto(fd: sock, buf: data, n: size, flags: 0, addr: (sockaddr *)addr, addr_len: sizeof(*addr));
2147}
2148
2149void net_unix_set_addr(UNIXSOCKETADDR *addr, const char *path)
2150{
2151 mem_zero(block: addr, size: sizeof(*addr));
2152 addr->sun_family = AF_UNIX;
2153 str_copy(dst&: addr->sun_path, src: path);
2154}
2155
2156void net_unix_close(UNIXSOCKET sock)
2157{
2158 close(fd: sock);
2159}
2160#endif
2161
2162#if defined(CONF_FAMILY_WINDOWS)
2163static inline time_t filetime_to_unixtime(LPFILETIME filetime)
2164{
2165 time_t t;
2166 ULARGE_INTEGER li;
2167 li.LowPart = filetime->dwLowDateTime;
2168 li.HighPart = filetime->dwHighDateTime;
2169
2170 li.QuadPart /= 10000000; // 100ns to 1s
2171 li.QuadPart -= 11644473600LL; // Windows epoch is in the past
2172
2173 t = li.QuadPart;
2174 return t == (time_t)li.QuadPart ? t : (time_t)-1;
2175}
2176#endif
2177
2178void fs_listdir(const char *dir, FS_LISTDIR_CALLBACK cb, int type, void *user)
2179{
2180#if defined(CONF_FAMILY_WINDOWS)
2181 char buffer[IO_MAX_PATH_LENGTH];
2182 str_format(buffer, sizeof(buffer), "%s/*", dir);
2183 const std::wstring wide_buffer = windows_utf8_to_wide(buffer);
2184
2185 WIN32_FIND_DATAW finddata;
2186 HANDLE handle = FindFirstFileW(wide_buffer.c_str(), &finddata);
2187 if(handle == INVALID_HANDLE_VALUE)
2188 return;
2189
2190 do
2191 {
2192 const std::optional<std::string> current_entry = windows_wide_to_utf8(finddata.cFileName);
2193 if(!current_entry.has_value())
2194 {
2195 log_error("filesystem", "ERROR: file/folder name containing invalid UTF-16 found in folder '%s'", dir);
2196 continue;
2197 }
2198 if(cb(current_entry.value().c_str(), (finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0, type, user))
2199 break;
2200 } while(FindNextFileW(handle, &finddata));
2201
2202 FindClose(handle);
2203#else
2204 DIR *dir_handle = opendir(name: dir);
2205 if(dir_handle == nullptr)
2206 return;
2207
2208 char buffer[IO_MAX_PATH_LENGTH];
2209 str_format(buffer, buffer_size: sizeof(buffer), format: "%s/", dir);
2210 size_t length = str_length(str: buffer);
2211 while(true)
2212 {
2213 struct dirent *entry = readdir(dirp: dir_handle);
2214 if(entry == nullptr)
2215 break;
2216 if(!str_utf8_check(str: entry->d_name))
2217 {
2218 log_error("filesystem", "ERROR: file/folder name containing invalid UTF-8 found in folder '%s'", dir);
2219 continue;
2220 }
2221 str_copy(dst: buffer + length, src: entry->d_name, dst_size: sizeof(buffer) - length);
2222 if(cb(entry->d_name, fs_is_dir(path: buffer), type, user))
2223 break;
2224 }
2225
2226 closedir(dirp: dir_handle);
2227#endif
2228}
2229
2230void fs_listdir_fileinfo(const char *dir, FS_LISTDIR_CALLBACK_FILEINFO cb, int type, void *user)
2231{
2232#if defined(CONF_FAMILY_WINDOWS)
2233 char buffer[IO_MAX_PATH_LENGTH];
2234 str_format(buffer, sizeof(buffer), "%s/*", dir);
2235 const std::wstring wide_buffer = windows_utf8_to_wide(buffer);
2236
2237 WIN32_FIND_DATAW finddata;
2238 HANDLE handle = FindFirstFileW(wide_buffer.c_str(), &finddata);
2239 if(handle == INVALID_HANDLE_VALUE)
2240 return;
2241
2242 do
2243 {
2244 const std::optional<std::string> current_entry = windows_wide_to_utf8(finddata.cFileName);
2245 if(!current_entry.has_value())
2246 {
2247 log_error("filesystem", "ERROR: file/folder name containing invalid UTF-16 found in folder '%s'", dir);
2248 continue;
2249 }
2250
2251 CFsFileInfo info;
2252 info.m_pName = current_entry.value().c_str();
2253 info.m_TimeCreated = filetime_to_unixtime(&finddata.ftCreationTime);
2254 info.m_TimeModified = filetime_to_unixtime(&finddata.ftLastWriteTime);
2255
2256 if(cb(&info, (finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0, type, user))
2257 break;
2258 } while(FindNextFileW(handle, &finddata));
2259
2260 FindClose(handle);
2261#else
2262 DIR *dir_handle = opendir(name: dir);
2263 if(dir_handle == nullptr)
2264 return;
2265
2266 char buffer[IO_MAX_PATH_LENGTH];
2267 str_format(buffer, buffer_size: sizeof(buffer), format: "%s/", dir);
2268 size_t length = str_length(str: buffer);
2269
2270 while(true)
2271 {
2272 struct dirent *entry = readdir(dirp: dir_handle);
2273 if(entry == nullptr)
2274 break;
2275 if(!str_utf8_check(str: entry->d_name))
2276 {
2277 log_error("filesystem", "ERROR: file/folder name containing invalid UTF-8 found in folder '%s'", dir);
2278 continue;
2279 }
2280 str_copy(dst: buffer + length, src: entry->d_name, dst_size: sizeof(buffer) - length);
2281 time_t created = -1, modified = -1;
2282 fs_file_time(name: buffer, created: &created, modified: &modified);
2283
2284 CFsFileInfo info;
2285 info.m_pName = entry->d_name;
2286 info.m_TimeCreated = created;
2287 info.m_TimeModified = modified;
2288
2289 if(cb(&info, fs_is_dir(path: buffer), type, user))
2290 break;
2291 }
2292
2293 closedir(dirp: dir_handle);
2294#endif
2295}
2296
2297int fs_storage_path(const char *appname, char *path, int max)
2298{
2299#if defined(CONF_FAMILY_WINDOWS)
2300 WCHAR *wide_home = nullptr;
2301 if(SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, /* current user */ nullptr, &wide_home) != S_OK)
2302 {
2303 log_error("filesystem", "ERROR: could not determine location of Roaming/AppData folder");
2304 CoTaskMemFree(wide_home);
2305 path[0] = '\0';
2306 return -1;
2307 }
2308 const std::optional<std::string> home = windows_wide_to_utf8(wide_home);
2309 CoTaskMemFree(wide_home);
2310 if(!home.has_value())
2311 {
2312 log_error("filesystem", "ERROR: path of Roaming/AppData folder contains invalid UTF-16");
2313 path[0] = '\0';
2314 return -1;
2315 }
2316 str_copy(path, home.value().c_str(), max);
2317 fs_normalize_path(path);
2318 str_append(path, "/", max);
2319 str_append(path, appname, max);
2320 return 0;
2321#else
2322 char *home = getenv(name: "HOME");
2323 if(!home)
2324 {
2325 path[0] = '\0';
2326 return -1;
2327 }
2328
2329 if(!str_utf8_check(str: home))
2330 {
2331 log_error("filesystem", "ERROR: the HOME environment variable contains invalid UTF-8");
2332 path[0] = '\0';
2333 return -1;
2334 }
2335
2336#if defined(CONF_PLATFORM_HAIKU)
2337 str_format(path, max, "%s/config/settings/%s", home, appname);
2338#elif defined(CONF_PLATFORM_MACOS)
2339 str_format(path, max, "%s/Library/Application Support/%s", home, appname);
2340#else
2341 if(str_comp(a: appname, b: "Teeworlds") == 0)
2342 {
2343 // fallback for old directory for Teeworlds compatibility
2344 str_format(buffer: path, buffer_size: max, format: "%s/.%s", home, appname);
2345 }
2346 else
2347 {
2348 char *data_home = getenv(name: "XDG_DATA_HOME");
2349 if(data_home)
2350 {
2351 if(!str_utf8_check(str: data_home))
2352 {
2353 log_error("filesystem", "ERROR: the XDG_DATA_HOME environment variable contains invalid UTF-8");
2354 path[0] = '\0';
2355 return -1;
2356 }
2357 str_format(buffer: path, buffer_size: max, format: "%s/%s", data_home, appname);
2358 }
2359 else
2360 str_format(buffer: path, buffer_size: max, format: "%s/.local/share/%s", home, appname);
2361 }
2362 for(int i = str_length(str: path) - str_length(str: appname); path[i]; i++)
2363 path[i] = tolower(c: (unsigned char)path[i]);
2364#endif
2365
2366 return 0;
2367#endif
2368}
2369
2370int fs_makedir_rec_for(const char *path)
2371{
2372 char buffer[IO_MAX_PATH_LENGTH];
2373 str_copy(dst&: buffer, src: path);
2374 for(int index = 1; buffer[index] != '\0'; ++index)
2375 {
2376 // Do not try to create folder for drive letters on Windows,
2377 // as this is not necessary and may fail for system drives.
2378 if((buffer[index] == '/' || buffer[index] == '\\') && buffer[index + 1] != '\0' && buffer[index - 1] != ':')
2379 {
2380 buffer[index] = '\0';
2381 if(fs_makedir(path: buffer) < 0)
2382 {
2383 return -1;
2384 }
2385 buffer[index] = '/';
2386 }
2387 }
2388 return 0;
2389}
2390
2391int fs_is_file(const char *path)
2392{
2393#if defined(CONF_FAMILY_WINDOWS)
2394 const std::wstring wide_path = windows_utf8_to_wide(path);
2395 DWORD attributes = GetFileAttributesW(wide_path.c_str());
2396 return attributes != INVALID_FILE_ATTRIBUTES && !(attributes & FILE_ATTRIBUTE_DIRECTORY) ? 1 : 0;
2397#else
2398 struct stat sb;
2399 if(stat(file: path, buf: &sb) == -1)
2400 return 0;
2401 return S_ISREG(sb.st_mode) ? 1 : 0;
2402#endif
2403}
2404
2405int fs_is_dir(const char *path)
2406{
2407#if defined(CONF_FAMILY_WINDOWS)
2408 const std::wstring wide_path = windows_utf8_to_wide(path);
2409 DWORD attributes = GetFileAttributesW(wide_path.c_str());
2410 return attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_DIRECTORY) ? 1 : 0;
2411#else
2412 struct stat sb;
2413 if(stat(file: path, buf: &sb) == -1)
2414 return 0;
2415 return S_ISDIR(sb.st_mode) ? 1 : 0;
2416#endif
2417}
2418
2419int fs_is_relative_path(const char *path)
2420{
2421#if defined(CONF_FAMILY_WINDOWS)
2422 const std::wstring wide_path = windows_utf8_to_wide(path);
2423 return PathIsRelativeW(wide_path.c_str()) ? 1 : 0;
2424#else
2425 return path[0] == '/' ? 0 : 1; // yes, it's that simple
2426#endif
2427}
2428
2429int fs_chdir(const char *path)
2430{
2431#if defined(CONF_FAMILY_WINDOWS)
2432 const std::wstring wide_path = windows_utf8_to_wide(path);
2433 return SetCurrentDirectoryW(wide_path.c_str()) != 0 ? 0 : 1;
2434#else
2435 return chdir(path: path) ? 1 : 0;
2436#endif
2437}
2438
2439char *fs_getcwd(char *buffer, int buffer_size)
2440{
2441#if defined(CONF_FAMILY_WINDOWS)
2442 const DWORD size_needed = GetCurrentDirectoryW(0, nullptr);
2443 std::wstring wide_current_dir(size_needed, L'0');
2444 dbg_assert(GetCurrentDirectoryW(size_needed, wide_current_dir.data()) == size_needed - 1, "GetCurrentDirectoryW failure");
2445 const std::optional<std::string> current_dir = windows_wide_to_utf8(wide_current_dir.c_str());
2446 if(!current_dir.has_value())
2447 {
2448 buffer[0] = '\0';
2449 return nullptr;
2450 }
2451 str_copy(buffer, current_dir.value().c_str(), buffer_size);
2452 fs_normalize_path(buffer);
2453 return buffer;
2454#else
2455 char *result = getcwd(buf: buffer, size: buffer_size);
2456 if(result == nullptr || !str_utf8_check(str: result))
2457 {
2458 buffer[0] = '\0';
2459 return nullptr;
2460 }
2461 return result;
2462#endif
2463}
2464
2465const char *fs_filename(const char *path)
2466{
2467 for(const char *filename = path + str_length(str: path); filename >= path; --filename)
2468 {
2469 if(filename[0] == '/' || filename[0] == '\\')
2470 return filename + 1;
2471 }
2472 return path;
2473}
2474
2475void fs_split_file_extension(const char *filename, char *name, size_t name_size, char *extension, size_t extension_size)
2476{
2477 dbg_assert(name != nullptr || extension != nullptr, "name or extension parameter required");
2478 dbg_assert(name == nullptr || name_size > 0, "name_size invalid");
2479 dbg_assert(extension == nullptr || extension_size > 0, "extension_size invalid");
2480
2481 const char *last_dot = str_rchr(haystack: filename, needle: '.');
2482 if(last_dot == nullptr || last_dot == filename)
2483 {
2484 if(extension != nullptr)
2485 extension[0] = '\0';
2486 if(name != nullptr)
2487 str_copy(dst: name, src: filename, dst_size: name_size);
2488 }
2489 else
2490 {
2491 if(extension != nullptr)
2492 str_copy(dst: extension, src: last_dot + 1, dst_size: extension_size);
2493 if(name != nullptr)
2494 str_truncate(dst: name, dst_size: name_size, src: filename, truncation_len: last_dot - filename);
2495 }
2496}
2497
2498void fs_normalize_path(char *path)
2499{
2500 for(int i = 0; path[i] != '\0';)
2501 {
2502 if(path[i] == '\\')
2503 {
2504 path[i] = '/';
2505 }
2506 if(i > 0 && path[i] == '/' && path[i + 1] == '\0')
2507 {
2508 path[i] = '\0';
2509 --i;
2510 }
2511 else
2512 {
2513 ++i;
2514 }
2515 }
2516}
2517
2518int fs_parent_dir(char *path)
2519{
2520 char *parent = nullptr;
2521 for(; *path; ++path)
2522 {
2523 if(*path == '/' || *path == '\\')
2524 parent = path;
2525 }
2526
2527 if(parent)
2528 {
2529 *parent = 0;
2530 return 0;
2531 }
2532 return 1;
2533}
2534
2535int fs_remove(const char *filename)
2536{
2537#if defined(CONF_FAMILY_WINDOWS)
2538 if(fs_is_dir(filename))
2539 {
2540 // Not great, but otherwise using this function on a folder would only rename the folder but fail to delete it.
2541 return 1;
2542 }
2543 const std::wstring wide_filename = windows_utf8_to_wide(filename);
2544
2545 unsigned random_num = secure_rand();
2546 std::wstring wide_filename_temp;
2547 do
2548 {
2549 char suffix[64];
2550 str_format(suffix, sizeof(suffix), ".%08X.toberemoved", random_num);
2551 wide_filename_temp = wide_filename + windows_utf8_to_wide(suffix);
2552 ++random_num;
2553 } while(GetFileAttributesW(wide_filename_temp.c_str()) != INVALID_FILE_ATTRIBUTES);
2554
2555 // The DeleteFileW function only marks the file for deletion but the deletion may not take effect immediately, which can
2556 // cause subsequent operations using this filename to fail until all handles are closed. The MoveFileExW function with the
2557 // MOVEFILE_WRITE_THROUGH flag is guaranteed to wait for the file to be moved on disk, so we first rename the file to be
2558 // deleted to a random temporary name and then mark that for deletion, to ensure that the filename is usable immediately.
2559 if(MoveFileExW(wide_filename.c_str(), wide_filename_temp.c_str(), MOVEFILE_WRITE_THROUGH) == 0)
2560 {
2561 const DWORD error = GetLastError();
2562 if(error == ERROR_FILE_NOT_FOUND)
2563 {
2564 return 0; // Success: Renaming failed because the original file did not exist.
2565 }
2566 const std::string filename_temp = windows_wide_to_utf8(wide_filename_temp.c_str()).value_or("(invalid filename)");
2567 log_error("filesystem", "Failed to rename file '%s' to '%s' for removal (%ld '%s')", filename, filename_temp.c_str(), error, windows_format_system_message(error).c_str());
2568 return 1;
2569 }
2570 if(DeleteFileW(wide_filename_temp.c_str()) != 0)
2571 {
2572 return 0; // Success: Marked the renamed file for deletion successfully.
2573 }
2574 const DWORD error = GetLastError();
2575 if(error == ERROR_FILE_NOT_FOUND)
2576 {
2577 return 0; // Success: Another process deleted the renamed file we were about to delete?!
2578 }
2579 const std::string filename_temp = windows_wide_to_utf8(wide_filename_temp.c_str()).value_or("(invalid filename)");
2580 log_error("filesystem", "Failed to remove file '%s' (%ld '%s')", filename_temp.c_str(), error, windows_format_system_message(error).c_str());
2581 // Success: While the temporary could not be deleted, this is also considered success because the original file does not exist anymore.
2582 // Callers of this function expect that the original file does not exist anymore if and only if the function succeeded.
2583 return 0;
2584#else
2585 if(unlink(name: filename) == 0 || errno == ENOENT)
2586 {
2587 return 0;
2588 }
2589 log_error("filesystem", "Failed to remove file '%s' (%d '%s')", filename, errno, strerror(errno));
2590 return 1;
2591#endif
2592}
2593
2594int fs_rename(const char *oldname, const char *newname)
2595{
2596#if defined(CONF_FAMILY_WINDOWS)
2597 const std::wstring wide_oldname = windows_utf8_to_wide(oldname);
2598 const std::wstring wide_newname = windows_utf8_to_wide(newname);
2599 if(MoveFileExW(wide_oldname.c_str(), wide_newname.c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH) != 0)
2600 {
2601 return 0;
2602 }
2603 const DWORD error = GetLastError();
2604 log_error("filesystem", "Failed to rename file '%s' to '%s' (%ld '%s')", oldname, newname, error, windows_format_system_message(error).c_str());
2605 return 1;
2606#else
2607 if(rename(old: oldname, new: newname) == 0)
2608 {
2609 return 0;
2610 }
2611 log_error("filesystem", "Failed to rename file '%s' to '%s' (%d '%s')", oldname, newname, errno, strerror(errno));
2612 return 1;
2613#endif
2614}
2615
2616int fs_file_time(const char *name, time_t *created, time_t *modified)
2617{
2618#if defined(CONF_FAMILY_WINDOWS)
2619 WIN32_FIND_DATAW finddata;
2620 const std::wstring wide_name = windows_utf8_to_wide(name);
2621 HANDLE handle = FindFirstFileW(wide_name.c_str(), &finddata);
2622 if(handle == INVALID_HANDLE_VALUE)
2623 return 1;
2624
2625 *created = filetime_to_unixtime(&finddata.ftCreationTime);
2626 *modified = filetime_to_unixtime(&finddata.ftLastWriteTime);
2627 FindClose(handle);
2628#elif defined(CONF_FAMILY_UNIX)
2629 struct stat sb;
2630 if(stat(file: name, buf: &sb))
2631 return 1;
2632
2633 *created = sb.st_ctime;
2634 *modified = sb.st_mtime;
2635#else
2636#error not implemented
2637#endif
2638
2639 return 0;
2640}
2641
2642void swap_endian(void *data, unsigned elem_size, unsigned num)
2643{
2644 char *src = (char *)data;
2645 char *dst = src + (elem_size - 1);
2646
2647 while(num)
2648 {
2649 unsigned n = elem_size >> 1;
2650 char tmp;
2651 while(n)
2652 {
2653 tmp = *src;
2654 *src = *dst;
2655 *dst = tmp;
2656
2657 src++;
2658 dst--;
2659 n--;
2660 }
2661
2662 src = src + (elem_size >> 1);
2663 dst = src + (elem_size - 1);
2664 num--;
2665 }
2666}
2667
2668int net_socket_read_wait(NETSOCKET sock, std::chrono::nanoseconds nanoseconds)
2669{
2670 const int64_t microseconds = std::chrono::duration_cast<std::chrono::microseconds>(d: nanoseconds).count();
2671 dbg_assert(microseconds >= 0, "Negative wait duration %" PRId64 " not allowed", microseconds);
2672
2673 fd_set readfds;
2674 FD_ZERO(&readfds);
2675
2676 int maxfd = -1;
2677 if(sock->ipv4sock >= 0)
2678 {
2679 FD_SET(sock->ipv4sock, &readfds);
2680 maxfd = sock->ipv4sock;
2681 }
2682 if(sock->ipv6sock >= 0)
2683 {
2684 FD_SET(sock->ipv6sock, &readfds);
2685 maxfd = std::max(a: maxfd, b: sock->ipv6sock);
2686 }
2687#if defined(CONF_WEBSOCKETS)
2688 if(sock->web_ipv4sock >= 0)
2689 {
2690 maxfd = std::max(a: maxfd, b: websocket_fd_set(socket: sock->web_ipv4sock, set: &readfds));
2691 }
2692 if(sock->web_ipv6sock >= 0)
2693 {
2694 maxfd = std::max(a: maxfd, b: websocket_fd_set(socket: sock->web_ipv6sock, set: &readfds));
2695 }
2696#endif
2697 if(maxfd < 0)
2698 {
2699 return 0;
2700 }
2701
2702 struct timeval tv;
2703 tv.tv_sec = microseconds / 1000000;
2704 tv.tv_usec = microseconds % 1000000;
2705 // don't care about writefds and exceptfds
2706 select(nfds: maxfd + 1, readfds: &readfds, writefds: nullptr, exceptfds: nullptr, timeout: &tv);
2707
2708 if(sock->ipv4sock >= 0 && FD_ISSET(sock->ipv4sock, &readfds))
2709 {
2710 return 1;
2711 }
2712 if(sock->ipv6sock >= 0 && FD_ISSET(sock->ipv6sock, &readfds))
2713 {
2714 return 1;
2715 }
2716#if defined(CONF_WEBSOCKETS)
2717 if(sock->web_ipv4sock >= 0 && websocket_fd_get(socket: sock->web_ipv4sock, set: &readfds))
2718 {
2719 return 1;
2720 }
2721 if(sock->web_ipv6sock >= 0 && websocket_fd_get(socket: sock->web_ipv6sock, set: &readfds))
2722 {
2723 return 1;
2724 }
2725#endif
2726 return 0;
2727}
2728
2729int str_format_v(char *buffer, int buffer_size, const char *format, va_list args)
2730{
2731#if defined(CONF_FAMILY_WINDOWS)
2732 _vsprintf_p(buffer, buffer_size, format, args);
2733 buffer[buffer_size - 1] = 0; /* assure null termination */
2734#else
2735 vsnprintf(s: buffer, maxlen: buffer_size, format: format, arg: args);
2736 /* null termination is assured by definition of vsnprintf */
2737#endif
2738 return str_utf8_fix_truncation(str: buffer);
2739}
2740
2741#if !defined(CONF_DEBUG)
2742int str_format_int(char *buffer, size_t buffer_size, int value)
2743{
2744 buffer[0] = '\0'; // Fix false positive clang-analyzer-core.UndefinedBinaryOperatorResult when using result
2745 auto result = std::to_chars(buffer, buffer + buffer_size - 1, value);
2746 result.ptr[0] = '\0';
2747 return result.ptr - buffer;
2748}
2749#endif
2750
2751#undef str_format
2752int str_format(char *buffer, int buffer_size, const char *format, ...)
2753{
2754 va_list args;
2755 va_start(args, format);
2756 int length = str_format_v(buffer, buffer_size, format, args);
2757 va_end(args);
2758 return length;
2759}
2760#if !defined(CONF_DEBUG)
2761#define str_format str_format_opt
2762#endif
2763
2764static int min3(int a, int b, int c)
2765{
2766 int min = a;
2767 if(b < min)
2768 min = b;
2769 if(c < min)
2770 min = c;
2771 return min;
2772}
2773
2774int str_utf8_dist(const char *a, const char *b)
2775{
2776 int buf_len = 2 * (str_length(str: a) + 1 + str_length(str: b) + 1);
2777 int *buf = (int *)calloc(nmemb: buf_len, size: sizeof(*buf));
2778 int result = str_utf8_dist_buffer(a, b, buf, buf_len);
2779 free(ptr: buf);
2780 return result;
2781}
2782
2783static int str_to_utf32_unchecked(const char *str, int **out)
2784{
2785 int out_len = 0;
2786 while((**out = str_utf8_decode(ptr: &str)))
2787 {
2788 (*out)++;
2789 out_len++;
2790 }
2791 return out_len;
2792}
2793
2794int str_utf32_dist_buffer(const int *a, int a_len, const int *b, int b_len, int *buf, int buf_len)
2795{
2796 int i, j;
2797 dbg_assert(buf_len >= (a_len + 1) + (b_len + 1), "buffer too small");
2798 if(a_len > b_len)
2799 {
2800 int tmp1 = a_len;
2801 const int *tmp2 = a;
2802
2803 a_len = b_len;
2804 a = b;
2805
2806 b_len = tmp1;
2807 b = tmp2;
2808 }
2809#define B(i, j) buf[((j) & 1) * (a_len + 1) + (i)]
2810 for(i = 0; i <= a_len; i++)
2811 {
2812 B(i, 0) = i;
2813 }
2814 for(j = 1; j <= b_len; j++)
2815 {
2816 B(0, j) = j;
2817 for(i = 1; i <= a_len; i++)
2818 {
2819 int subst = (a[i - 1] != b[j - 1]);
2820 B(i, j) = min3(
2821 B(i - 1, j) + 1,
2822 B(i, j - 1) + 1,
2823 B(i - 1, j - 1) + subst);
2824 }
2825 }
2826 return B(a_len, b_len);
2827#undef B
2828}
2829
2830int str_utf8_dist_buffer(const char *a_utf8, const char *b_utf8, int *buf, int buf_len)
2831{
2832 int a_utf8_len = str_length(str: a_utf8);
2833 int b_utf8_len = str_length(str: b_utf8);
2834 int *a, *b; // UTF-32
2835 int a_len, b_len; // UTF-32 length
2836 dbg_assert(buf_len >= 2 * (a_utf8_len + 1 + b_utf8_len + 1), "buffer too small");
2837 if(a_utf8_len > b_utf8_len)
2838 {
2839 const char *tmp2 = a_utf8;
2840 a_utf8 = b_utf8;
2841 b_utf8 = tmp2;
2842 }
2843 a = buf;
2844 a_len = str_to_utf32_unchecked(str: a_utf8, out: &buf);
2845 b = buf;
2846 b_len = str_to_utf32_unchecked(str: b_utf8, out: &buf);
2847 return str_utf32_dist_buffer(a, a_len, b, b_len, buf, buf_len: buf_len - b_len - a_len);
2848}
2849
2850void net_stats(NETSTATS *stats_inout)
2851{
2852 *stats_inout = network_stats;
2853}
2854
2855static_assert(sizeof(unsigned) == 4, "unsigned must be 4 bytes in size");
2856static_assert(sizeof(unsigned) == sizeof(int), "unsigned and int must have the same size");
2857
2858unsigned bytes_be_to_uint(const unsigned char *bytes)
2859{
2860 return ((bytes[0] & 0xffu) << 24u) | ((bytes[1] & 0xffu) << 16u) | ((bytes[2] & 0xffu) << 8u) | (bytes[3] & 0xffu);
2861}
2862
2863void uint_to_bytes_be(unsigned char *bytes, unsigned value)
2864{
2865 bytes[0] = (value >> 24u) & 0xffu;
2866 bytes[1] = (value >> 16u) & 0xffu;
2867 bytes[2] = (value >> 8u) & 0xffu;
2868 bytes[3] = value & 0xffu;
2869}
2870
2871int pid()
2872{
2873#if defined(CONF_FAMILY_WINDOWS)
2874 return _getpid();
2875#else
2876 return getpid();
2877#endif
2878}
2879
2880void cmdline_fix(int *argc, const char ***argv)
2881{
2882#if defined(CONF_FAMILY_WINDOWS)
2883 int wide_argc = 0;
2884 WCHAR **wide_argv = CommandLineToArgvW(GetCommandLineW(), &wide_argc);
2885 dbg_assert(wide_argv != NULL, "CommandLineToArgvW failure");
2886 dbg_assert(wide_argc > 0, "Invalid argc value");
2887
2888 int total_size = 0;
2889
2890 for(int i = 0; i < wide_argc; i++)
2891 {
2892 int size = WideCharToMultiByte(CP_UTF8, 0, wide_argv[i], -1, nullptr, 0, nullptr, nullptr);
2893 dbg_assert(size != 0, "WideCharToMultiByte failure");
2894 total_size += size;
2895 }
2896
2897 char **new_argv = (char **)malloc((wide_argc + 1) * sizeof(*new_argv));
2898 new_argv[0] = (char *)malloc(total_size);
2899 mem_zero(new_argv[0], total_size);
2900
2901 int remaining_size = total_size;
2902 for(int i = 0; i < wide_argc; i++)
2903 {
2904 int size = WideCharToMultiByte(CP_UTF8, 0, wide_argv[i], -1, new_argv[i], remaining_size, nullptr, nullptr);
2905 dbg_assert(size != 0, "WideCharToMultiByte failure");
2906
2907 remaining_size -= size;
2908 new_argv[i + 1] = new_argv[i] + size;
2909 }
2910
2911 LocalFree(wide_argv);
2912 new_argv[wide_argc] = nullptr;
2913 *argc = wide_argc;
2914 *argv = (const char **)new_argv;
2915#endif
2916}
2917
2918void cmdline_free(int argc, const char **argv)
2919{
2920#if defined(CONF_FAMILY_WINDOWS)
2921 free((void *)*argv);
2922 free((char **)argv);
2923#endif
2924}
2925
2926#if !defined(CONF_PLATFORM_ANDROID)
2927PROCESS shell_execute(const char *file, EShellExecuteWindowState window_state, const char **arguments, const size_t num_arguments)
2928{
2929 dbg_assert((arguments == nullptr) == (num_arguments == 0), "Invalid number of arguments");
2930#if defined(CONF_FAMILY_WINDOWS)
2931 dbg_assert(str_endswith_nocase(file, ".bat") == nullptr && str_endswith_nocase(file, ".cmd") == nullptr, "Running batch files not allowed");
2932 dbg_assert(str_endswith(file, ".exe") != nullptr || num_arguments == 0, "Arguments only allowed with .exe files");
2933
2934 const std::wstring wide_file = windows_utf8_to_wide(file);
2935 std::wstring wide_arguments = windows_args_to_wide(arguments, num_arguments);
2936
2937 SHELLEXECUTEINFOW info;
2938 mem_zero(&info, sizeof(SHELLEXECUTEINFOW));
2939 info.cbSize = sizeof(SHELLEXECUTEINFOW);
2940 info.lpVerb = L"open";
2941 info.lpFile = wide_file.c_str();
2942 info.lpParameters = num_arguments > 0 ? wide_arguments.c_str() : nullptr;
2943 switch(window_state)
2944 {
2945 case EShellExecuteWindowState::FOREGROUND:
2946 info.nShow = SW_SHOW;
2947 break;
2948 case EShellExecuteWindowState::BACKGROUND:
2949 info.nShow = SW_SHOWMINNOACTIVE;
2950 break;
2951 default:
2952 dbg_assert_failed("Invalid window_state: %d", static_cast<int>(window_state));
2953 }
2954 info.fMask = SEE_MASK_NOCLOSEPROCESS;
2955 // Save and restore the FPU control word because ShellExecute might change it
2956 fenv_t floating_point_environment;
2957 int fegetenv_result = fegetenv(&floating_point_environment);
2958 ShellExecuteExW(&info);
2959 if(fegetenv_result == 0)
2960 fesetenv(&floating_point_environment);
2961 return info.hProcess;
2962#elif defined(CONF_FAMILY_UNIX)
2963 char **argv = (char **)malloc(size: (num_arguments + 2) * sizeof(*argv));
2964 pid_t pid;
2965 argv[0] = (char *)file;
2966 for(size_t i = 0; i < num_arguments; ++i)
2967 {
2968 argv[i + 1] = (char *)arguments[i];
2969 }
2970 argv[num_arguments + 1] = NULL;
2971 pid = fork();
2972 if(pid == -1)
2973 {
2974 free(ptr: argv);
2975 return 0;
2976 }
2977 if(pid == 0)
2978 {
2979 execvp(file: file, argv: argv);
2980 _exit(status: 1);
2981 }
2982 free(ptr: argv);
2983 return pid;
2984#endif
2985}
2986
2987int kill_process(PROCESS process)
2988{
2989#if defined(CONF_FAMILY_WINDOWS)
2990 BOOL success = TerminateProcess(process, 0);
2991 BOOL is_alive = is_process_alive(process);
2992 if(success || !is_alive)
2993 {
2994 CloseHandle(process);
2995 return true;
2996 }
2997 return false;
2998#elif defined(CONF_FAMILY_UNIX)
2999 if(!is_process_alive(process))
3000 return true;
3001 int status;
3002 kill(pid: process, SIGTERM);
3003 return waitpid(pid: process, stat_loc: &status, options: 0) != -1;
3004#endif
3005}
3006
3007bool is_process_alive(PROCESS process)
3008{
3009 if(process == INVALID_PROCESS)
3010 return false;
3011#if defined(CONF_FAMILY_WINDOWS)
3012 DWORD exit_code;
3013 GetExitCodeProcess(process, &exit_code);
3014 return exit_code == STILL_ACTIVE;
3015#else
3016 return waitpid(pid: process, stat_loc: nullptr, WNOHANG) == 0;
3017#endif
3018}
3019
3020int open_link(const char *link)
3021{
3022#if defined(CONF_FAMILY_WINDOWS)
3023 const std::wstring wide_link = windows_utf8_to_wide(link);
3024
3025 SHELLEXECUTEINFOW info;
3026 mem_zero(&info, sizeof(SHELLEXECUTEINFOW));
3027 info.cbSize = sizeof(SHELLEXECUTEINFOW);
3028 info.lpVerb = nullptr; // NULL to use the default verb, as "open" may not be available
3029 info.lpFile = wide_link.c_str();
3030 info.nShow = SW_SHOWNORMAL;
3031 // The SEE_MASK_NOASYNC flag ensures that the ShellExecuteEx function
3032 // finishes its DDE conversation before it returns, so it's not necessary
3033 // to pump messages in the calling thread.
3034 // The SEE_MASK_FLAG_NO_UI flag suppresses error messages that would pop up
3035 // when the link cannot be opened, e.g. when a folder does not exist.
3036 // The SEE_MASK_ASYNCOK flag is not used. It would allow the call to
3037 // ShellExecuteEx to return earlier, but it also prevents us from doing
3038 // our own error handling, as the function would always return TRUE.
3039 info.fMask = SEE_MASK_NOASYNC | SEE_MASK_FLAG_NO_UI;
3040 // Save and restore the FPU control word because ShellExecute might change it
3041 fenv_t floating_point_environment;
3042 int fegetenv_result = fegetenv(&floating_point_environment);
3043 BOOL success = ShellExecuteExW(&info);
3044 if(fegetenv_result == 0)
3045 fesetenv(&floating_point_environment);
3046 return success;
3047#elif defined(CONF_PLATFORM_LINUX)
3048 const int pid = fork();
3049 if(pid == 0)
3050 execlp(file: "xdg-open", arg: "xdg-open", link, nullptr);
3051 return pid > 0;
3052#elif defined(CONF_FAMILY_UNIX)
3053 const int pid = fork();
3054 if(pid == 0)
3055 execlp("open", "open", link, nullptr);
3056 return pid > 0;
3057#endif
3058}
3059
3060int open_file(const char *path)
3061{
3062#if defined(CONF_PLATFORM_MACOS)
3063 return open_link(path);
3064#else
3065 // Create a file link so the path can contain forward and
3066 // backward slashes. But the file link must be absolute.
3067 char buf[512];
3068 char workingDir[IO_MAX_PATH_LENGTH];
3069 if(fs_is_relative_path(path))
3070 {
3071 if(!fs_getcwd(buffer: workingDir, buffer_size: sizeof(workingDir)))
3072 return 0;
3073 str_append(dst&: workingDir, src: "/");
3074 }
3075 else
3076 workingDir[0] = '\0';
3077 str_format(buffer: buf, buffer_size: sizeof(buf), format: "file://%s%s", workingDir, path);
3078 return open_link(link: buf);
3079#endif
3080}
3081#endif // !defined(CONF_PLATFORM_ANDROID)
3082
3083struct SECURE_RANDOM_DATA
3084{
3085 std::once_flag initialized_once_flag;
3086#if defined(CONF_FAMILY_WINDOWS)
3087 HCRYPTPROV provider;
3088#else
3089 IOHANDLE urandom;
3090#endif
3091};
3092
3093static struct SECURE_RANDOM_DATA secure_random_data = {};
3094
3095static void ensure_secure_random_init()
3096{
3097 std::call_once(once&: secure_random_data.initialized_once_flag, f: []() {
3098#if defined(CONF_FAMILY_WINDOWS)
3099 if(!CryptAcquireContext(&secure_random_data.provider, nullptr, nullptr, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
3100 {
3101 const DWORD LastError = GetLastError();
3102 dbg_assert_failed("Failed to initialize secure random: CryptAcquireContext failure (%ld '%s')", LastError, windows_format_system_message(LastError).c_str());
3103 }
3104#else
3105 secure_random_data.urandom = io_open(filename: "/dev/urandom", flags: IOFLAG_READ);
3106 dbg_assert(secure_random_data.urandom != nullptr, "Failed to initialize secure random: failed to open /dev/urandom");
3107#endif
3108 });
3109}
3110
3111void generate_password(char *buffer, unsigned length, const unsigned short *random, unsigned random_length)
3112{
3113 static const char VALUES[] = "ABCDEFGHKLMNPRSTUVWXYZabcdefghjkmnopqt23456789";
3114 static const size_t NUM_VALUES = sizeof(VALUES) - 1; // Disregard the '\0'.
3115 unsigned i;
3116 dbg_assert(length >= random_length * 2 + 1, "too small buffer");
3117 dbg_assert(NUM_VALUES * NUM_VALUES >= 2048, "need at least 2048 possibilities for 2-character sequences");
3118
3119 buffer[random_length * 2] = 0;
3120
3121 for(i = 0; i < random_length; i++)
3122 {
3123 unsigned short random_number = random[i] % 2048;
3124 buffer[2 * i + 0] = VALUES[random_number / NUM_VALUES];
3125 buffer[2 * i + 1] = VALUES[random_number % NUM_VALUES];
3126 }
3127}
3128
3129#define MAX_PASSWORD_LENGTH 128
3130
3131void secure_random_password(char *buffer, unsigned length, unsigned pw_length)
3132{
3133 unsigned short random[MAX_PASSWORD_LENGTH / 2];
3134 // With 6 characters, we get a password entropy of log(2048) * 6/2 = 33bit.
3135 dbg_assert(length >= pw_length + 1, "too small buffer");
3136 dbg_assert(pw_length >= 6, "too small password length");
3137 dbg_assert(pw_length % 2 == 0, "need an even password length");
3138 dbg_assert(pw_length <= MAX_PASSWORD_LENGTH, "too large password length");
3139
3140 secure_random_fill(bytes: random, length: pw_length);
3141
3142 generate_password(buffer, length, random, random_length: pw_length / 2);
3143}
3144
3145#undef MAX_PASSWORD_LENGTH
3146
3147void secure_random_fill(void *bytes, unsigned length)
3148{
3149 ensure_secure_random_init();
3150#if defined(CONF_FAMILY_WINDOWS)
3151 if(!CryptGenRandom(secure_random_data.provider, length, (unsigned char *)bytes))
3152 {
3153 const DWORD LastError = GetLastError();
3154 dbg_assert_failed("CryptGenRandom failure (%ld '%s')", LastError, windows_format_system_message(LastError).c_str());
3155 }
3156#else
3157 dbg_assert(length == io_read(secure_random_data.urandom, bytes, length), "io_read returned with a short read");
3158#endif
3159}
3160
3161int secure_rand()
3162{
3163 unsigned int i;
3164 secure_random_fill(bytes: &i, length: sizeof(i));
3165 return (int)(i % RAND_MAX);
3166}
3167
3168// From https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2.
3169static unsigned int find_next_power_of_two_minus_one(unsigned int n)
3170{
3171 n--;
3172 n |= n >> 1;
3173 n |= n >> 2;
3174 n |= n >> 4;
3175 n |= n >> 4;
3176 n |= n >> 16;
3177 return n;
3178}
3179
3180int secure_rand_below(int below)
3181{
3182 unsigned int mask = find_next_power_of_two_minus_one(n: below);
3183 dbg_assert(below > 0, "below must be positive");
3184 while(true)
3185 {
3186 unsigned int n;
3187 secure_random_fill(bytes: &n, length: sizeof(n));
3188 n &= mask;
3189 if((int)n < below)
3190 {
3191 return n;
3192 }
3193 }
3194}
3195
3196bool os_version_str(char *version, size_t length)
3197{
3198#if defined(CONF_FAMILY_WINDOWS)
3199 const WCHAR *module_path = L"kernel32.dll";
3200 DWORD handle;
3201 DWORD size = GetFileVersionInfoSizeW(module_path, &handle);
3202 if(!size)
3203 {
3204 return false;
3205 }
3206 void *data = malloc(size);
3207 if(!GetFileVersionInfoW(module_path, handle, size, data))
3208 {
3209 free(data);
3210 return false;
3211 }
3212 VS_FIXEDFILEINFO *fileinfo;
3213 UINT unused;
3214 if(!VerQueryValueW(data, L"\\", (void **)&fileinfo, &unused))
3215 {
3216 free(data);
3217 return false;
3218 }
3219 str_format(version, length, "Windows %hu.%hu.%hu.%hu",
3220 HIWORD(fileinfo->dwProductVersionMS),
3221 LOWORD(fileinfo->dwProductVersionMS),
3222 HIWORD(fileinfo->dwProductVersionLS),
3223 LOWORD(fileinfo->dwProductVersionLS));
3224 free(data);
3225 return true;
3226#else
3227 struct utsname u;
3228 if(uname(name: &u))
3229 {
3230 return false;
3231 }
3232 char extra[128];
3233 extra[0] = 0;
3234
3235 do
3236 {
3237 IOHANDLE os_release = io_open(filename: "/etc/os-release", flags: IOFLAG_READ);
3238 char buf[4096];
3239 int read;
3240 int offset;
3241 char *newline;
3242 if(!os_release)
3243 {
3244 break;
3245 }
3246 read = io_read(io: os_release, buffer: buf, size: sizeof(buf) - 1);
3247 io_close(io: os_release);
3248 buf[read] = 0;
3249 if(str_startswith(str: buf, prefix: "PRETTY_NAME="))
3250 {
3251 offset = 0;
3252 }
3253 else
3254 {
3255 const char *found = str_find(haystack: buf, needle: "\nPRETTY_NAME=");
3256 if(!found)
3257 {
3258 break;
3259 }
3260 offset = found - buf + 1;
3261 }
3262 newline = (char *)str_find(haystack: buf + offset, needle: "\n");
3263 if(newline)
3264 {
3265 *newline = 0;
3266 }
3267 str_format(buffer: extra, buffer_size: sizeof(extra), format: "; %s", buf + offset + 12);
3268 } while(false);
3269
3270 str_format(buffer: version, buffer_size: length, format: "%s %s (%s, %s)%s", u.sysname, u.release, u.machine, u.version, extra);
3271 return true;
3272#endif
3273}
3274
3275void os_locale_str(char *locale, size_t length)
3276{
3277#if defined(CONF_FAMILY_WINDOWS)
3278 wchar_t wide_buffer[LOCALE_NAME_MAX_LENGTH];
3279 dbg_assert(GetUserDefaultLocaleName(wide_buffer, std::size(wide_buffer)) > 0, "GetUserDefaultLocaleName failure");
3280
3281 const std::optional<std::string> buffer = windows_wide_to_utf8(wide_buffer);
3282 dbg_assert(buffer.has_value(), "GetUserDefaultLocaleName returned invalid UTF-16");
3283 str_copy(locale, buffer.value().c_str(), length);
3284#elif defined(CONF_PLATFORM_MACOS)
3285 CFLocaleRef locale_ref = CFLocaleCopyCurrent();
3286 CFStringRef locale_identifier_ref = static_cast<CFStringRef>(CFLocaleGetValue(locale_ref, kCFLocaleIdentifier));
3287
3288 // Count number of UTF16 codepoints, +1 for zero-termination.
3289 // Assume maximum possible length for encoding as UTF-8.
3290 CFIndex locale_identifier_size = (UTF8_BYTE_LENGTH * CFStringGetLength(locale_identifier_ref) + 1) * sizeof(char);
3291 char *locale_identifier = (char *)malloc(locale_identifier_size);
3292 dbg_assert(CFStringGetCString(locale_identifier_ref, locale_identifier, locale_identifier_size, kCFStringEncodingUTF8), "CFStringGetCString failure");
3293
3294 str_copy(locale, locale_identifier, length);
3295
3296 free(locale_identifier);
3297 CFRelease(locale_ref);
3298#else
3299 static const char *ENV_VARIABLES[] = {
3300 "LC_ALL",
3301 "LC_MESSAGES",
3302 "LANG",
3303 };
3304
3305 locale[0] = '\0';
3306 for(const char *env_variable : ENV_VARIABLES)
3307 {
3308 const char *env_value = getenv(name: env_variable);
3309 if(env_value)
3310 {
3311 str_copy(dst: locale, src: env_value, dst_size: length);
3312 break;
3313 }
3314 }
3315#endif
3316
3317 // Ensure RFC 3066 format:
3318 // - use hyphens instead of underscores
3319 // - truncate locale string after first non-standard letter
3320 for(int i = 0; i < str_length(str: locale); ++i)
3321 {
3322 if(locale[i] == '_')
3323 {
3324 locale[i] = '-';
3325 }
3326 else if(locale[i] != '-' && !(locale[i] >= 'a' && locale[i] <= 'z') && !(locale[i] >= 'A' && locale[i] <= 'Z') && !(str_isnum(c: locale[i])))
3327 {
3328 locale[i] = '\0';
3329 break;
3330 }
3331 }
3332
3333 // Use default if we could not determine the locale,
3334 // i.e. if only the C or POSIX locale is available.
3335 if(locale[0] == '\0' || str_comp(a: locale, b: "C") == 0 || str_comp(a: locale, b: "POSIX") == 0)
3336 str_copy(dst: locale, src: "en-US", dst_size: length);
3337}
3338