aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSlendi <slendi@socopon.com>2023-11-19 00:20:32 +0200
committerSlendi <slendi@socopon.com>2023-11-19 00:20:32 +0200
commitbe571d07ff126143753aeba43268ee8b870f0ba1 (patch)
treedadec2cc6a9a243973ec10b8720478215aae669b
-rw-r--r--.gitignore1
-rw-r--r--Init.HC45
-rw-r--r--LICENSE10
-rw-r--r--Lib/AC97.HC228
-rw-r--r--Lib/Debug.HC21
-rw-r--r--Lib/ELF64.HC355
-rw-r--r--Lib/LibC.HC144
-rw-r--r--Lib/LibHolyC.HC18
-rw-r--r--Lib/Misc.HC30
-rw-r--r--Lib/Pci.HC89
-rw-r--r--Lib/Sound.HC24
-rw-r--r--Net/Arp.HC128
-rw-r--r--Net/Dhcp.HC281
-rw-r--r--Net/Dns.HC539
-rw-r--r--Net/Ethernet.HC57
-rw-r--r--Net/IPv4.HC259
-rw-r--r--Net/Icmp.HC101
-rw-r--r--Net/NativeSocket.HC327
-rw-r--r--Net/NetFifo.HC77
-rw-r--r--Net/NetHandler.HC58
-rw-r--r--Net/Netcfg.HC142
-rw-r--r--Net/Socket.HC43
-rw-r--r--Net/Tcp.HC1108
-rw-r--r--Net/Udp.HC247
-rw-r--r--Net/Virtio-net.HC185
-rw-r--r--Net/Virtio.HC77
-rw-r--r--PacketData.HC38
-rw-r--r--README.md7
-rw-r--r--Run.HC3
-rw-r--r--Src/Debug.HC10
-rw-r--r--Src/Doc.HC35
-rw-r--r--Src/Json.HC881
-rw-r--r--Src/MD5.HC141
-rw-r--r--Src/NetUtils.HC39
-rw-r--r--TOL.HC403
-rw-r--r--logo.pngbin0 -> 17968 bytes
36 files changed, 6151 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..dbe9c82
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+.vscode/ \ No newline at end of file
diff --git a/Init.HC b/Init.HC
new file mode 100644
index 0000000..9f60802
--- /dev/null
+++ b/Init.HC
@@ -0,0 +1,45 @@
+CDoc *tmpdoc = DocNew;
+CDoc *fsdoc = Fs->put_doc;
+
+U0 DISABLE_STDOUT() { Fs->put_doc = tmpdoc; }
+U0 ENABLE_STDOUT() { Fs->put_doc = fsdoc; }
+
+I64 aol_socket = NULL;
+
+/* clang-format off */
+
+#include "Src/Debug";
+
+#include "Net/Virtio";
+#include "Net/Virtio-net";
+
+#include "Net/NativeSocket";
+#include "Net/NetFifo";
+#include "Net/Socket";
+
+// Layer 2
+#include "Net/Ethernet";
+
+// Layer 3
+#include "Net/Arp";
+#include "Net/IPv4";
+
+// Layer 4
+#include "Net/Icmp";
+#include "Net/Tcp";
+#include "Net/Udp";
+
+// Layer 7
+#include "Net/Dhcp";
+#include "Net/Dns";
+
+#include "Net/Netcfg";
+#include "Net/NetHandler";
+
+#include "Src/NetUtils";
+#include "TOL";
+
+/* clang-format on */
+
+Netcfg;
+dns_ip = 0x08080808;
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..cde4ac6
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,10 @@
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means.
+
+In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to <http://unlicense.org/>
diff --git a/Lib/AC97.HC b/Lib/AC97.HC
new file mode 100644
index 0000000..9092111
--- /dev/null
+++ b/Lib/AC97.HC
@@ -0,0 +1,228 @@
+#define INT_LAST_VALID_ENTRY 1 << 2
+#define INT_IOC 1 << 3
+#define INT_FIFO_ERR 1 << 4
+
+#define BDL_BUF_SIZE 2044
+#define PCM_BUF_SIZE 2048
+#define MAX_BDLS 32
+
+#define PCM_IN 0
+#define PCM_OUT 1
+#define MIC_IN 2
+
+// Native Audio Mixer registers (all U16)
+
+#define RESET 0x00 // Reset Register
+#define MASTER_VOL 0x02 // Set Master Output Volume
+#define MIC_VOL 0x0E // Set Microphone Volume
+#define PCM_VOL 0x18 // Set Output Volume of PCM patterns
+#define REC_SLC 0x1A // Select Input Device
+#define REC_GAIN 0x1C // Set Input Gain
+#define MIC_GAIN 0x1E // Set Gain of Microphone
+#define EXT_ID 0x28 // Supported extended functions
+#define EXT_CTRL 0x2A // Enabling extended functions
+#define EXT_FRONT_RATE 0x2C // Sample rate of front speaker
+
+// Native Audio Bus Master registers
+
+#define PCM_INPUT_REG_BOX \
+ 0x00 // NABM register box for PCM IN (sizeof NABM register box)
+#define PCM_OUTPUT_REG_BOX \
+ 0x10 // NABM register box for PCM OUT (sizeof NABM register box)
+#define MIC_INPUT_REG_BOX \
+ 0x20 // NABM register box for Microphone (sizeof NABM register box)
+#define GLOBAL_CTL 0x2C // Global Control Register (U32)
+#define GLOBAL_STS 0x30 // Global Status Register (U32)
+
+// NABM register box registers
+
+#define BUFFER_DSC_ADDR 0x00 // Physical Address of Buffer Descriptor List (U32)
+#define CUR_ENTRY_VAL \
+ 0x04 // Number of Actual Processed Buffer Descriptor Entry (U8)
+#define LAST_VALID_ENTRY 0x05 // Number of all Descriptor Entries (U8)
+#define TRANSFER_STS 0x06 // Status of Transferring Data (U16)
+#define CUR_IDX_PROC_SAMPLES \
+ 0x08 // Number of Transferred Samples in Actual Processed Entry (U16)
+#define PRCSD_ENTRY 0x0A // Number of Actual Processed Buffer Entry (U8)
+#define BUFFER_CNT \
+ 0x0B // Most Important Register for controlling Transfers (U8)
+
+class @ac97_bdl_entry {
+ U32 addr;
+ U16 length; // length - 1
+ U16 flags;
+};
+
+class @ac97_bdl {
+ @ac97_bdl_entry entries[32];
+};
+
+class @ac97 {
+ @pci_info pci;
+ @ac97_bdl *bdl[3];
+ U16 nam;
+ U16 nabm;
+};
+
+@ac97 AC97;
+
+#define AUDIO_MAX_STREAMS 16
+#define AUDIO_OUTPUT_BUFFER_SIZE 1024
+#define AUDIO_STREAM_FIFO_SIZE 1048576 * 16
+#define AUDIO_STREAM_TYPE_INPUT 0
+#define AUDIO_STREAM_TYPE_OUTPUT 1
+
+class @audio_stream {
+ CFifoI64 *data;
+};
+
+class @audio {
+ @audio_stream output[AUDIO_MAX_STREAMS];
+ I64 output_frames[AUDIO_MAX_STREAMS];
+};
+
+@audio Audio;
+
+U0 @audio_init() {
+ I64 i = 0;
+ for (i = 0; i < AUDIO_MAX_STREAMS; i++)
+ Audio.output[i].data = FifoI64New(AUDIO_STREAM_FIFO_SIZE);
+}
+
+@audio_init;
+
+I64 @audio_get_available_output_stream() {
+ I64 stream = 0;
+ while (FifoI64Cnt(Audio.output[stream].data))
+ stream++;
+ if (stream > AUDIO_MAX_STREAMS - 1)
+ return -1;
+ return stream;
+}
+
+I64 @audio_play_sfx(U32 *data, I64 length) {
+ I64 i;
+ I64 stream = @audio_get_available_output_stream;
+ if (stream < 0)
+ return stream;
+ for (i = 0; i < length; i++)
+ FifoI64Ins(Audio.output[stream].data, data[i]);
+ return stream;
+}
+
+U0 @ac97_mix_output(U32 *buf, I64 length = NULL) {
+ I64 i;
+ I64 j;
+ I64 acc_sample_L = 0;
+ I64 acc_sample_R = 0;
+ I64 acc_streams = 0;
+ U32 sample;
+
+ if (!length)
+ length = AUDIO_OUTPUT_BUFFER_SIZE;
+ for (i = 0; i < length / 4; i++) {
+ acc_sample_L = 0;
+ acc_sample_R = 0;
+ acc_streams = 0;
+
+ for (j = 0; j < AUDIO_MAX_STREAMS; j++) {
+ if (FifoI64Cnt(Audio.output[j].data)) {
+ FifoI64Rem(Audio.output[j].data, &sample);
+ Audio.output_frames[j]++;
+ acc_streams++;
+ acc_sample_L += sample.u16[0];
+ acc_sample_R += sample.u16[1];
+ }
+ }
+
+ buf[i].i16[0] = ToI64(acc_sample_L / Sqrt(acc_streams));
+ buf[i].i16[1] = ToI64(acc_sample_R / Sqrt(acc_streams));
+ }
+}
+
+U0 @ac97_fill_buffer() {
+ I64 idx = InU8(AC97.nabm + PCM_OUTPUT_REG_BOX + LAST_VALID_ENTRY);
+ U32 *buf = AC97.bdl[PCM_OUT]->entries[idx].addr;
+ @ac97_mix_output(buf, BDL_BUF_SIZE);
+ OutU8(AC97.nabm + PCM_OUTPUT_REG_BOX + LAST_VALID_ENTRY, ++idx);
+}
+
+U0 @ac97_process_audio() {
+ U16 status = InU16(AC97.nabm + PCM_OUTPUT_REG_BOX + TRANSFER_STS);
+ if (status & INT_IOC) {
+ @ac97_fill_buffer;
+ OutU16(AC97.nabm + PCM_OUTPUT_REG_BOX + TRANSFER_STS, 0x1C);
+ }
+}
+
+I64 @ac97_init() {
+ I64 i;
+ I64 j;
+ // Scan for device
+ j = PCIClassFind(0x040100, 0);
+ if (j < 0) {
+ device_not_found:
+ AdamLog("\n[AC'97] Device not found\n");
+ return -1;
+ }
+ @get_pci_info(j, &AC97.pci);
+
+ if (AC97.pci.vendor_id != 0x8086 || AC97.pci.device_id != 0x2415)
+ goto device_not_found;
+
+ AC97.nam = AC97.pci.bar[0] & 0xFFFFFF00;
+ AC97.nabm = AC97.pci.bar[1] & 0xFFFFFF00;
+
+ // Enable port IO, disable MMIO
+ PCIWriteU8(j.u8[2], j.u8[1], j.u8[0], 0x4, 5);
+
+ OutU32(AC97.nabm + GLOBAL_CTL, 0x03);
+ OutU16(AC97.nam + RESET, 0xFFFF);
+
+ // Set PCM Output to Max volume
+ OutU16(AC97.nam + PCM_VOL, 0x0000);
+
+ // Allocate Buffer Descriptor Lists
+ AC97.bdl[PCM_IN] = CAllocAligned(sizeof(@ac97_bdl), 4096, Fs->code_heap);
+ AC97.bdl[PCM_OUT] = CAllocAligned(sizeof(@ac97_bdl), 4096, Fs->code_heap);
+ AC97.bdl[MIC_IN] = CAllocAligned(sizeof(@ac97_bdl), 4096, Fs->code_heap);
+
+ for (i = 0; i < MAX_BDLS; i++) {
+ AC97.bdl[PCM_OUT]->entries[i].addr =
+ CAllocAligned(PCM_BUF_SIZE, 4096, Fs->code_heap);
+ AC97.bdl[PCM_OUT]->entries[i].length = BDL_BUF_SIZE / 2;
+ AC97.bdl[PCM_OUT]->entries[i].flags = 1 << 15;
+ }
+
+ // Set addresses of Buffer Descriptor Lists
+ // OutU32(AC97.nabm + PCM_INPUT_REG_BOX + BUFFER_DSC_ADDR, AC97.bdl[PCM_IN]);
+ OutU32(AC97.nabm + PCM_OUTPUT_REG_BOX + BUFFER_DSC_ADDR, AC97.bdl[PCM_OUT]);
+ // OutU32(AC97.nabm + MIC_INPUT_REG_BOX + BUFFER_DSC_ADDR, AC97.bdl[MIC_IN]);
+
+ // Set Master Volume
+ OutU16(AC97.nam + MASTER_VOL, 0x0F0F);
+
+ // Stop playing sound
+ OutU8(AC97.nabm + PCM_OUTPUT_REG_BOX + BUFFER_CNT, 0);
+
+ // Fill one buffers
+ @ac97_fill_buffer;
+
+ // Enable interrupt handler
+ //@pci_register_int_handler(&@ac97_int_handler);
+
+ // Start playing sound
+ OutU8(AC97.nabm + PCM_OUTPUT_REG_BOX + BUFFER_CNT, 1);
+
+ return 0;
+}
+
+U0 @ac97_task() {
+ while (1) {
+ @ac97_process_audio;
+ Sleep(1);
+ }
+}
+
+@ac97_init;
+Spawn(&@ac97_task, , "AC97 Task", 1); \ No newline at end of file
diff --git a/Lib/Debug.HC b/Lib/Debug.HC
new file mode 100644
index 0000000..0ce16c8
--- /dev/null
+++ b/Lib/Debug.HC
@@ -0,0 +1,21 @@
+class Debug {
+ Bool enabled;
+ I64 bookmark;
+ I64 counter;
+};
+
+Debug debug;
+debug.bookmark = 0;
+debug.counter = 0;
+debug.enabled = FALSE;
+
+U0 debug_print(U8 *fmt, ...) {
+ if (!debug.enabled || debug.counter < debug.bookmark) {
+ debug.counter++;
+ return;
+ }
+ U8 *buf = StrPrintJoin(NULL, fmt, argc, argv);
+ "[%05d] %s", debug.counter, buf;
+ Free(buf);
+ debug.counter++;
+}
diff --git a/Lib/ELF64.HC b/Lib/ELF64.HC
new file mode 100644
index 0000000..63109a7
--- /dev/null
+++ b/Lib/ELF64.HC
@@ -0,0 +1,355 @@
+#define EI_NIDENT 16
+#define EM_X86_64 0x3E
+#define ET_EXEC 2
+#define ET_DYN 3
+
+#define STT_OBJECT 1
+#define STT_FUNC 2
+
+class Elf64_Ehdr {
+ U8 e_ident[EI_NIDENT]; /* Magic number and other info */
+ U16 e_type; /* Object file type */
+ U16 e_machine; /* Architecture */
+ U32 e_version; /* Object file version */
+ U64 e_entry; /* Entry point virtual address */
+ U64 e_phoff; /* Program header table file offset */
+ U64 e_shoff; /* Section header table file offset */
+ U32 e_flags; /* Processor-specific flags */
+ U16 e_ehsize; /* ELF header size in bytes */
+ U16 e_phentsize; /* Program header table entry size */
+ U16 e_phnum; /* Program header table entry count */
+ U16 e_shentsize; /* Section header table entry size */
+ U16 e_shnum; /* Section header table entry count */
+ U16 e_shstrndx; /* Section header string table index */
+};
+
+class Elf64_Shdr {
+ U32 sh_name; /* Section name (string tbl index) */
+ U32 sh_type; /* Section type */
+ U64 sh_flags; /* Section flags */
+ U64 sh_addr; /* Section virtual addr at execution */
+ U64 sh_offset; /* Section file offset */
+ U64 sh_size; /* Section size in bytes */
+ U32 sh_link; /* Link to another section */
+ U32 sh_info; /* Additional section information */
+ U64 sh_addralign; /* Section alignment */
+ U64 sh_entsize; /* Entry size if section holds table */
+};
+
+class Elf64_Sym {
+ U32 st_name; /* Symbol name (string tbl index) */
+ U8 st_info; /* Symbol type and binding */
+ U8 st_other; /* Symbol visibility */
+ U16 st_shndx; /* Section index */
+ U64 st_value; /* Symbol value */
+ U64 st_size; /* Symbol size */
+};
+
+class PLT_entry {
+ U8 pad[0x10];
+};
+
+class RELA_entry {
+ U64 r_offset;
+ U64 r_info;
+ I64 r_addend;
+};
+
+class Elf {
+ union {
+ U8 *u8;
+ Elf64_Ehdr *ehdr;
+ } I64 size;
+ U8 *dynstr;
+ Elf64_Sym *dynsym;
+ PLT_entry *plt;
+ RELA_entry *rela_dyn;
+ RELA_entry *rela_plt;
+ Elf64_Sym *strtab;
+ Elf64_Sym *symtab;
+ I64 rela_dyn_size;
+ I64 rela_plt_size;
+ I64 strtab_size;
+ I64 symtab_size;
+};
+
+U0 (*_start)();
+
+U0 unimplemented_symbol() {
+ I32 s = 0xDEADF00D;
+ "Unimplemented symbol: %s\n", s;
+ while (1)
+ Sleep(1);
+}
+
+Bool is_valid_elf(Elf *elf) {
+ Bool res = TRUE;
+ if (MemCmp(elf->u8 + 1, "ELF", 3)) {
+ debug_print("Invalid signature (not ELF).\n");
+ res = FALSE;
+ }
+ if (elf->ehdr->e_type != ET_EXEC && elf->ehdr->e_type != ET_DYN) {
+ debug_print("Invalid object file type.\n");
+ res = FALSE;
+ }
+ if (elf->ehdr->e_machine != EM_X86_64) {
+ debug_print("Invalid architecture.\n");
+ res = FALSE;
+ }
+ return res;
+}
+
+U0 find_value(U8 *value) {
+ U64 addr = 0x0;
+ while (addr < 0x2000000) {
+ if (!MemCmp(addr, value, StrLen(value))) {
+ "found at 0x%08x\n", addr;
+ return;
+ }
+ addr++;
+ }
+ "not found\n";
+}
+
+U0 process_elf_section_header_table(Elf *elf) {
+ Elf64_Shdr *shdr = elf->u8 + elf->ehdr->e_shoff;
+ Elf64_Shdr *shdr_shstrtab = shdr + elf->ehdr->e_shstrndx;
+ U8 *shstrtab = elf->u8 + shdr_shstrtab->sh_offset;
+ I64 i = 0;
+ while (i < elf->ehdr->e_shnum) {
+ if (!StrCmp(shstrtab + shdr->sh_name, ".symtab")) {
+ debug_print("found symtab at 0x%08x, size = %d\n", shdr->sh_offset,
+ shdr->sh_size);
+ elf->symtab = elf->u8 + shdr->sh_offset;
+ elf->symtab_size = shdr->sh_size;
+ }
+ if (!StrCmp(shstrtab + shdr->sh_name, ".strtab")) {
+ debug_print("found strtab at 0x%08x, size = %d\n", shdr->sh_offset,
+ shdr->sh_size);
+ elf->strtab = elf->u8 + shdr->sh_offset;
+ elf->strtab_size = shdr->sh_size;
+ }
+ if (shdr->sh_addr) {
+ MemCpy(shdr->sh_addr, elf->u8 + shdr->sh_offset, shdr->sh_size);
+ if (!StrCmp(shstrtab + shdr->sh_name, ".dynstr"))
+ elf->dynstr = shdr->sh_addr;
+ if (!StrCmp(shstrtab + shdr->sh_name, ".dynsym"))
+ elf->dynsym = shdr->sh_addr;
+ if (!StrCmp(shstrtab + shdr->sh_name, ".plt"))
+ elf->plt = shdr->sh_addr;
+ if (!StrCmp(shstrtab + shdr->sh_name, ".rela.dyn")) {
+ elf->rela_dyn = shdr->sh_addr;
+ elf->rela_dyn_size = shdr->sh_size / shdr->sh_entsize;
+ }
+ if (!StrCmp(shstrtab + shdr->sh_name, ".rela.plt")) {
+ elf->rela_plt = shdr->sh_addr;
+ elf->rela_plt_size = shdr->sh_size / shdr->sh_entsize;
+ }
+ debug_print(
+ "MemCpy section '%s' to physical address 0x%06x, size = %d bytes\n",
+ shstrtab + shdr->sh_name, shdr->sh_addr, shdr->sh_size);
+ if (!StrCmp(shstrtab + shdr->sh_name, ".bss")) {
+ MemSet(shdr->sh_addr, NULL, shdr->sh_size);
+ debug_print("MemSet section '%s' at physical address 0x%06x to NULL, "
+ "size = %d bytes\n",
+ shstrtab + shdr->sh_name, shdr->sh_addr, shdr->sh_size);
+ }
+ }
+ shdr++;
+ i++;
+ }
+}
+
+U0 process_elf_rela_dyn_entries(Elf *elf) {
+ I64 i;
+ U8 *entry_name;
+ RELA_entry *rela_dyn = elf->rela_dyn;
+ for (i = 0; i < elf->rela_dyn_size; i++) {
+ entry_name = elf->dynstr + elf->dynsym[(rela_dyn->r_info >> 32)].st_name;
+ debug_print("rela_dyn->r_offset = %08x\n", rela_dyn->r_offset);
+ debug_print("entry name = '%s'\n", entry_name);
+ if (!StrCmp(entry_name, "__libc_start_main")) {
+ *(rela_dyn->r_offset)(U64 *) = &_main;
+ debug_print("Set value for .rela.dyn entry '%s' to: &_main\n",
+ entry_name);
+ }
+ if (!StrCmp(entry_name, "stdin")) {
+ *(rela_dyn->r_offset)(U64 *) = 0;
+ debug_print("Set value for .rela.dyn entry '%s' to: %d\n", entry_name, 0);
+ }
+ if (!StrCmp(entry_name, "stdout")) {
+ *(rela_dyn->r_offset)(U64 *) = 1;
+ debug_print("Set value for .rela.dyn entry '%s' to: %d\n", entry_name, 1);
+ }
+ if (!StrCmp(entry_name, "stderr")) {
+ *(rela_dyn->r_offset)(U64 *) = 2;
+ debug_print("Set value for .rela.dyn entry '%s' to: %d\n", entry_name, 2);
+ }
+ rela_dyn++;
+ }
+}
+
+CHashClass *get_symbol_hash_entry(U8 *entry_name) {
+ I64 i;
+ CHashSrcSym *sym;
+ CHashTable *tbl = Fs->hash_table;
+ while (tbl) {
+ for (i = 0; i < tbl->mask; i++) {
+ sym = tbl->body[i];
+ while (sym) {
+ if (sym->type == HTT_CLASS)
+ if (!StrCmp(sym->str, entry_name))
+ return sym;
+ sym = sym->next;
+ }
+ }
+ tbl = tbl->next;
+ }
+ return NULL;
+}
+
+U0 process_elf_debug_symbols(Elf *elf) {
+ I64 i = 0;
+ I64 entry_bind;
+ U64 entry_name;
+ I64 entry_type;
+ CHashFun *hf;
+ CHashGlblVar *hgv;
+ Elf64_Sym *symtab = elf->symtab;
+ entry_name = elf->strtab;
+ entry_name += symtab->st_name;
+ while (i < 476) {
+ entry_bind = symtab->st_info >> 4;
+ entry_type = symtab->st_info & 0xf;
+ switch (entry_type) {
+ case STT_OBJECT:
+ hgv = CAlloc(sizeof(CHashGlblVar));
+ hgv->str = entry_name;
+ hgv->type = HTT_GLBL_VAR;
+ hgv->data_addr = symtab->st_value;
+ hgv->size = symtab->st_size;
+ // TempleOS reboots if I nest a switch table here, for some reason, so we
+ // have to do this dumb shit instead...
+ hgv->var_class = get_symbol_hash_entry("U32");
+ if (hgv->size == 1)
+ hgv->var_class = get_symbol_hash_entry("U8");
+ if (hgv->size == 2)
+ hgv->var_class = get_symbol_hash_entry("U16");
+ HashAdd(hgv, Fs->hash_table);
+ debug_print(
+ "Add global variable '%s' to hash table, addr = 0x%08x, size = %d\n",
+ entry_name, symtab->st_value, symtab->st_size);
+ break;
+ case STT_FUNC:
+ hf = CAlloc(sizeof(CHashFun));
+ hf->str = entry_name;
+ hf->type = HTT_FUN;
+ hf->exe_addr = symtab->st_value;
+ hf->size = symtab->st_size;
+ HashAdd(hf, Fs->hash_table);
+ debug_print("Add function '%s' to hash table, addr = 0x%08x, size = %d\n",
+ entry_name, symtab->st_value, symtab->st_size);
+ break;
+ }
+ symtab++;
+ i++;
+ entry_name = elf->strtab;
+ entry_name += symtab->st_name;
+ }
+}
+
+U64 get_symbol_address(U8 *entry_name) {
+ I64 i;
+ CHashSrcSym *sym;
+ CHashTable *tbl = Fs->hash_table;
+ while (tbl) {
+ for (i = 0; i < tbl->mask; i++) {
+ sym = tbl->body[i];
+ while (sym) {
+ if (sym->type == HTT_GLBL_VAR)
+ if (!StrCmp(sym->str, entry_name))
+ return sym(CHashGlblVar *)->data_addr;
+ if (sym->type == HTT_FUN)
+ if (!StrCmp(sym->str, entry_name))
+ return sym(CHashFun *)->exe_addr;
+ sym = sym->next;
+ }
+ }
+ tbl = tbl->next;
+ }
+ return NULL;
+}
+
+U0 process_debug_patches(Elf *elf) { no_warn elf; }
+
+U0 process_elf_rela_plt_entries(Elf *elf) {
+ I64 i;
+ U32 handler;
+ U32 *patch;
+ U8 *entry_name;
+ Bool symbol_exists;
+ PLT_entry *plt = elf->plt;
+ RELA_entry *rela_plt = elf->rela_plt;
+ plt++;
+ for (i = 0; i < elf->rela_plt_size; i++) {
+ symbol_exists = FALSE;
+ entry_name = elf->dynstr + elf->dynsym[(rela_plt->r_info >> 32)].st_name;
+ handler = MAlloc(sizeof(unimplemented_symbol), Fs->code_heap);
+ MemCpy(handler, &unimplemented_symbol, sizeof(unimplemented_symbol));
+ patch = handler + 0x0A;
+ *patch = entry_name;
+ PatchJmpRel32(plt, handler);
+ PatchCallRel32(handler + 0x16, &PrintErr);
+ PatchCallRel32(handler + 0x21, &_exit);
+ if (!StrCmp(entry_name, "__libc_start_main")) {
+ symbol_exists = TRUE;
+ PatchJmpRel32(plt, &_main);
+ debug_print("Set value for .rela.plt entry '%s' to &_main\n", entry_name);
+ }
+ if (get_symbol_address(entry_name)) {
+ symbol_exists = TRUE;
+ PatchJmpRel32(plt, get_symbol_address(entry_name));
+ debug_print("Set value for .rela.plt entry '%s' to &%s\n", entry_name,
+ entry_name);
+ }
+ if (!symbol_exists)
+ debug_print(
+ "Set value for .rela.plt entry '%s' to &unimplemented_symbol\n",
+ entry_name);
+ rela_plt++;
+ plt++;
+ }
+}
+
+U0 load_elf(...) {
+ if (argc < 1) {
+ PrintErr("Not enough arguments.\n");
+ return;
+ }
+ if (!FileFind(argv[0])) {
+ PrintErr("File not found: %s\n", argv[0]);
+ return;
+ }
+
+ Elf elf;
+ elf.u8 = FileRead(argv[0], &elf.size);
+ debug_print("Load file '%s', size = %d bytes\n", argv[0], elf.size);
+
+ if (!is_valid_elf(&elf)) {
+ PrintErr("File is not a valid ELF x86-64 executable.\n");
+ return;
+ }
+
+ process_elf_section_header_table(&elf);
+ process_elf_rela_dyn_entries(&elf);
+ process_elf_rela_plt_entries(&elf);
+ process_elf_debug_symbols(&elf);
+
+ _start = elf.ehdr->e_entry;
+ elf_argc = argc;
+ elf_argv = argv;
+
+ process_debug_patches(&elf);
+
+ //_start();
+} \ No newline at end of file
diff --git a/Lib/LibC.HC b/Lib/LibC.HC
new file mode 100644
index 0000000..3a59758
--- /dev/null
+++ b/Lib/LibC.HC
@@ -0,0 +1,144 @@
+#define PUSH_SYSV_REGS \
+ asm {PUSH RCX PUSH RDX PUSH RBX PUSH RBP PUSH RSI PUSH RDI PUSH R8 PUSH R9 PUSH \
+ R10 PUSH R11 PUSH R12 PUSH R13 PUSH R14 PUSH R15}
+#define POP_SYSV_REGS \
+ asm {POP R15 POP R14 POP R13 POP R12 POP R11 POP R10 POP R9 POP R8 POP RDI POP \
+ RSI POP RBP POP RBX POP RDX POP RCX}
+#define MOV_ANS_RAX asm {MOV [&ans], RAX}
+#define MOV_P0_RDI asm {MOV [&p0], RDI}
+#define MOV_P1_RSI asm {MOV [&p1], RSI}
+#define MOV_P2_RDX asm {MOV [&p2], RDX}
+#define MOV_P3_RCX asm {MOV [&p3], RCX}
+#define MOV_P4_R8 asm {MOV [&p4], R8}
+#define MOV_P5_R9 asm {MOV [&p5], R9}
+#define GET_SYSV_ARGS \
+ MOV_P0_RDI MOV_P1_RSI MOV_P2_RDX MOV_P3_RCX MOV_P4_R8 MOV_P5_R9
+
+I64 p0, p1, p2, p3, p4, p5;
+I64 elf_argc;
+U8 **elf_argv;
+
+#define stdin 0
+#define stdout 1
+#define stderr 2
+
+asm {
+_ELF_CALL::
+ PUSH RBP
+ MOV RBP,RSP
+ MOV RAX,U64 SF_ARG1[RBP]
+ MOV RDI,U64 SF_ARG2[RBP]
+ MOV RSI,U64 SF_ARG3[RBP]
+ TEST RAX,RAX
+ JZ @@05
+ CALL RAX
+@@05: POP RBP
+ RET1 8
+}
+
+U0 _main() {
+ MOV_P0_RDI
+ CallInd(_ELF_CALL, p0, elf_argc, elf_argv);
+ MOV_ANS_RAX
+ throw('end', TRUE);
+}
+
+U0 _exit() {
+ MOV_ANS_RAX
+ throw('end', TRUE);
+}
+
+U0 free() {
+ PUSH_SYSV_REGS
+ GET_SYSV_ARGS
+ debug_print("called free(0x%08x)\n", p0);
+ Free(p0);
+ POP_SYSV_REGS
+}
+
+U64 @malloc(I64 size) {
+ U64 ptr = NULL;
+ ptr = MAlloc(p0);
+ debug_print("malloc(%d), result: 0x%08x\n", size, ptr);
+ return ptr;
+}
+
+U0 malloc() {
+ PUSH_SYSV_REGS
+ GET_SYSV_ARGS
+ debug_print("called: malloc(%d)\n", p0);
+ @malloc(p0);
+ POP_SYSV_REGS
+}
+
+U0 memcpy() {
+ PUSH_SYSV_REGS
+ GET_SYSV_ARGS
+ debug_print("called: memcpy(0x%08x, 0x%08x, %d)\n", p0, p1, p2);
+ MemCpy(p0, p1, p2);
+ POP_SYSV_REGS
+}
+
+U8 *@memmove(U8 *dest, U8 *src, I64 n) {
+ I64 i;
+ U8 *from = src;
+ U8 *to = dest;
+ if (from == to || n == 0)
+ return dest;
+ if (to > from && to - from < n) {
+ /* to overlaps with from */
+ /* <from......> */
+ /* <to........> */
+ /* copy in reverse, to avoid overwriting from */
+ for (i = n - 1; i >= 0; i--)
+ to[i] = from[i];
+ return dest;
+ }
+ if (from > to && from - to < n) {
+ /* to overlaps with from */
+ /* <from......> */
+ /* <to........> */
+ /* copy forwards, to avoid overwriting from */
+ for (i = 0; i < n; i++)
+ to[i] = from[i];
+ return dest;
+ }
+ MemCpy(dest, src, n);
+ return dest;
+}
+
+U0 memmove() {
+ PUSH_SYSV_REGS
+ GET_SYSV_ARGS
+ debug_print("called: memmove(0x%08x, 0x%08x, %d)\n", p0, p1, p2);
+ @memmove(p0, p1, p2);
+ POP_SYSV_REGS
+}
+
+U0 memset() {
+ PUSH_SYSV_REGS
+ GET_SYSV_ARGS
+ debug_print("called: memset(0x%08x, %d, %d)\n", p0, p1, p2);
+ MemSet(p0, p1, p2);
+ POP_SYSV_REGS
+}
+
+U8 *@realloc(U8 *ptr, I64 size) {
+ U8 *new;
+ if (!ptr) {
+ new = MAlloc(size);
+ } else {
+ new = MAlloc(size);
+ MemCpy(new, ptr, size);
+ Free(ptr);
+ }
+ return new;
+}
+
+U0 realloc() {
+ PUSH_SYSV_REGS
+ GET_SYSV_ARGS
+ debug_print("called: realloc(0x%08x, %d)\n", p0, p1);
+ @realloc(p0, p1);
+ POP_SYSV_REGS
+}
diff --git a/Lib/LibHolyC.HC b/Lib/LibHolyC.HC
new file mode 100644
index 0000000..3233768
--- /dev/null
+++ b/Lib/LibHolyC.HC
@@ -0,0 +1,18 @@
+asm {
+GCC_FUNC_ADDR::
+ DU64 0;
+HOLYC_ARG1::
+ DU64 0;
+HOLYC_ARG2::
+ DU64 0;
+HOLYC_ARG3::
+ DU64 0;
+HOLYC_ARG4::
+ DU64 0;
+HOLYC_ARG5::
+ DU64 0;
+HOLYC_ARG6::
+ DU64 0;
+HOLYC_RES::
+ DU64 0;
+}
diff --git a/Lib/Misc.HC b/Lib/Misc.HC
new file mode 100644
index 0000000..ce0b83a
--- /dev/null
+++ b/Lib/Misc.HC
@@ -0,0 +1,30 @@
+I64 Cond(Bool cond, I64 true, I64 false) {
+ if (cond)
+ return true;
+ return false;
+}
+
+U0 PatchCallRel32(U32 from, U32 to) {
+ *(from(U8 *)) = 0xE8;
+ *((from + 1)(I32 *)) = to - from - 5;
+}
+
+U0 PatchJmpRel32(U32 from, U32 to) {
+ *(from(U8 *)) = 0xE9;
+ *((from + 1)(I32 *)) = to - from - 5;
+}
+
+U0 EnableSSE() {
+ asm
+ {
+ MOV_EAX_CR0
+ AND AX, 0xFFFB // clear coprocessor emulation CR0.EM
+ OR AX, 0x2 // set coprocessor monitoring CR0.MP
+ MOV_CR0_EAX
+ MOV_EAX_CR4
+ OR AX, 3 << 9 // set CR4.OSFXSR and CR4.OSXMMEXCPT at the same time
+ MOV_CR4_EAX
+ }
+}
+
+EnableSSE;
diff --git a/Lib/Pci.HC b/Lib/Pci.HC
new file mode 100644
index 0000000..1309068
--- /dev/null
+++ b/Lib/Pci.HC
@@ -0,0 +1,89 @@
+#define PCI_INTH_MAX 16
+
+U64 @pci_int_handler[PCI_INTH_MAX];
+
+class @pci_info {
+ U16 vendor_id;
+ U16 device_id;
+ U16 command;
+ U16 status;
+ U32 _class;
+ U32 bar[6];
+ U32 cap_pointer;
+};
+
+class @pci_cap {
+ U8 cap_vndr; /* Generic PCI field: PCI_CAP_ID_VNDR */
+ U8 cap_next; /* Generic PCI field: next ptr. */
+ U8 cap_len; /* Generic PCI field: capability length */
+ U8 cfg_type; /* Identifies the structure. */
+ U8 bar; /* Where to find it. */
+ U8 padding[3]; /* Pad to full dword. */
+ U32 offset; /* Offset within bar. */
+ U32 length; /* Length of the structure, in bytes. */
+};
+
+U0 @get_pci_info(I64 i, @pci_info *pci) {
+ I64 j;
+ pci->vendor_id = PCIReadU32(i.u8[2], i.u8[1], i.u8[0], 0x0) & 0xFFFF;
+ pci->device_id = PCIReadU32(i.u8[2], i.u8[1], i.u8[0], 0x0) >> 16;
+ pci->command = PCIReadU32(i.u8[2], i.u8[1], i.u8[0], 0x4) & 0xFFFF;
+ pci->status = PCIReadU32(i.u8[2], i.u8[1], i.u8[0], 0x4) >> 16;
+ pci->_class = PCIReadU32(i.u8[2], i.u8[1], i.u8[0], 0x8) >> 24;
+ for (j = 0; j < 6; j++)
+ pci->bar[j] = PCIReadU32(i.u8[2], i.u8[1], i.u8[0], 0x10 + (0x04 * j));
+}
+
+U0 @get_pci_cap(I64 i, @pci_cap *cap, I64 idx) {
+ I64 base = 0x40 + (idx * 16);
+ U32 u32;
+ u32 = PCIReadU32(i.u8[2], i.u8[1], i.u8[0], base);
+ cap->cap_vndr = u32.u8[0];
+ cap->cap_next = u32.u8[1];
+ cap->cap_len = u32.u8[2];
+ cap->cfg_type = u32.u8[3];
+ u32 = PCIReadU32(i.u8[2], i.u8[1], i.u8[0], base + 0x04);
+ cap->bar = u32.u8[0];
+ cap->offset = PCIReadU32(i.u8[2], i.u8[1], i.u8[0], base + 0x08);
+ cap->length = PCIReadU32(i.u8[2], i.u8[1], i.u8[0], base + 0x0c);
+}
+
+U0 @pci_reroute_interrupts(I64 base, I64 cpu) {
+ I64 i;
+ U8 *da = dev.uncached_alias + IOAPIC_REG;
+ U32 *_d = dev.uncached_alias + IOAPIC_DATA;
+
+ for (i = 0; i < 4; i++) {
+ *da = IOREDTAB + i * 2 + 1;
+ *_d = dev.mp_apic_ids[cpu] << 24;
+ *da = IOREDTAB + i * 2;
+ *_d = 0x4000 + base + i;
+ }
+}
+
+I64 @pci_register_int_handler(U64 handler) {
+ if (!handler)
+ return -1;
+ I64 i = 0;
+ while (@pci_int_handler[i])
+ i++;
+ if (i > PCI_INTH_MAX - 1)
+ return -1;
+ @pci_int_handler[i] = handler;
+ return 0;
+}
+
+interrupt U0 @pci_interrupt_handler() {
+ I64 i;
+ for (i = 0; i < PCI_INTH_MAX; i++)
+ if (@pci_int_handler[i])
+ Call(@pci_int_handler[i]);
+ *(dev.uncached_alias + LAPIC_EOI)(U32 *) = 0;
+}
+
+MemSet(&@pci_int_handler, NULL, sizeof(U64) * PCI_INTH_MAX);
+// IntEntrySet(0x40, &@pci_interrupt_handler, IDTET_IRQ);
+// IntEntrySet(0x41, &@pci_interrupt_handler, IDTET_IRQ);
+// IntEntrySet(0x42, &@pci_interrupt_handler, IDTET_IRQ);
+// IntEntrySet(0x43, &@pci_interrupt_handler, IDTET_IRQ);
+//@pci_reroute_interrupts(0x40, 0); \ No newline at end of file
diff --git a/Lib/Sound.HC b/Lib/Sound.HC
new file mode 100644
index 0000000..3391749
--- /dev/null
+++ b/Lib/Sound.HC
@@ -0,0 +1,24 @@
+class CSound {
+ U8 signature[8];
+ U32 *data;
+ I64 len;
+};
+
+class @sound {
+ CSound *(*Load)(U8 * filename);
+ U64 func_addr[16];
+};
+
+@sound Sound;
+
+CSound *@sound_load(U8 *filename) {
+ if (!filename || !FileFind(filename)) {
+ PrintErr("Sound file not found.\n");
+ return NULL;
+ }
+ CSound *sound = CAlloc(sizeof(CSound));
+ StrCpy(&sound->signature, "CSound");
+ return sound;
+}
+
+Sound.Load = &@sound_load; \ No newline at end of file
diff --git a/Net/Arp.HC b/Net/Arp.HC
new file mode 100644
index 0000000..83a5086
--- /dev/null
+++ b/Net/Arp.HC
@@ -0,0 +1,128 @@
+// Not a Network Layer protocol, but it is encapsulated in L2 frames, which
+// makes it L3 for our purposes
+
+#define ARP_REQUEST 0x01
+#define ARP_REPLY 0x02
+
+class CArpHeader {
+ U16 htype;
+ U16 ptype;
+ U8 hlen;
+ U8 plen;
+ U16 oper;
+ U8 sha[6];
+ U32 spa;
+ U8 tha[6];
+ U32 tpa;
+};
+
+class CArpCacheEntry {
+ CArpCacheEntry *next;
+ U32 ip;
+ U8 mac[6];
+};
+
+// Stored in network order
+static U32 arp_my_ipv4_n = 0;
+
+// TODO: use a Hash table
+static CArpCacheEntry *arp_cache = NULL;
+
+// IPs are in network order
+I64 ArpSend(U16 oper, U8 *dest_mac, U8 *sender_mac, U32 sender_ip_n,
+ U8 *target_mac, U32 target_ip_n) {
+ U8 *frame;
+
+ I64 index = EthernetFrameAlloc(&frame, sender_mac, dest_mac, ETHERTYPE_ARP,
+ sizeof(CArpHeader), 0);
+
+ if (index < 0)
+ return index;
+
+ CArpHeader *hdr = frame;
+ hdr->htype = htons(1);
+ hdr->ptype = htons(ETHERTYPE_IPV4);
+ hdr->hlen = 6;
+ hdr->plen = 4;
+ hdr->oper = htons(oper);
+ MemCpy(hdr->sha, sender_mac, 6);
+ hdr->spa = sender_ip_n;
+ MemCpy(hdr->tha, target_mac, 6);
+ hdr->tpa = target_ip_n;
+
+ return EthernetFrameFinish(index);
+}
+
+U0 ArpSetIPv4Address(U32 addr) {
+ arp_my_ipv4_n = htonl(addr);
+
+ // Broadcast our new address
+ ArpSend(ARP_REPLY, eth_broadcast, EthernetGetAddress(), arp_my_ipv4_n,
+ eth_null, arp_my_ipv4_n);
+}
+
+CArpCacheEntry *ArpCacheFindByIP(U32 ip) {
+ CArpCacheEntry *e = arp_cache;
+
+ while (e) {
+ if (e->ip == ip)
+ return e;
+ e = e->next;
+ }
+
+ return e;
+}
+
+CArpCacheEntry *ArpCachePut(U32 ip, U8 *mac) {
+ CArpCacheEntry *e = ArpCacheFindByIP(ip);
+
+ if (!e) {
+ //"ARP: add entry for %08X\n", ip;
+ e = MAlloc(sizeof(CArpCacheEntry));
+ e->next = arp_cache;
+ e->ip = ip;
+ MemCpy(e->mac, mac, 6);
+ arp_cache = e;
+ }
+ // FIXME: else replace!
+
+ return e;
+}
+
+I64 ArpHandler(CEthFrame *eth_frame) {
+ if (eth_frame->ethertype != ETHERTYPE_ARP)
+ return -1;
+
+ // FIXME[obecebo]: this blocks responding to ARP_REQUEST? [2019/08/05]
+ if (eth_frame->length < sizeof(CArpHeader))
+ return -1;
+
+ CArpHeader *hdr = eth_frame->data;
+ U16 oper = ntohs(hdr->oper);
+
+ //"ARP: htype %d, ptype %d, hlen %d, plen %d, oper %d\n",
+ // ntohs(hdr->htype), ntohs(hdr->ptype), hdr->hlen, hdr->plen, oper;
+ //" spa %08X, tpa %08X\n", ntohl(hdr->spa), ntohl(hdr->tpa);
+
+ if (ntohs(hdr->htype) != 1 || ntohs(hdr->ptype) != ETHERTYPE_IPV4 ||
+ hdr->hlen != 6 || hdr->plen != 4)
+ return -1;
+
+ if (oper == ARP_REQUEST) {
+ // Not too sure about this line, but it seems necessary in WiFi networks,
+ // because the wireless device won't hear our Ethernet broadcast when we
+ // Request
+ // ArpCachePut(ntohl(hdr->spa), hdr->sha);
+
+ if (hdr->tpa == arp_my_ipv4_n) {
+ ArpSend(ARP_REPLY, hdr->sha, EthernetGetAddress(), arp_my_ipv4_n,
+ hdr->sha, hdr->spa);
+ }
+ } else if (oper == ARP_REPLY) {
+ ArpCachePut(ntohl(hdr->spa), hdr->sha);
+ }
+
+ return 0;
+}
+
+RegisterL3Protocol(ETHERTYPE_ARP, &ArpHandler);
diff --git a/Net/Dhcp.HC b/Net/Dhcp.HC
new file mode 100644
index 0000000..46af956
--- /dev/null
+++ b/Net/Dhcp.HC
@@ -0,0 +1,281 @@
+
+#define BOOTREQUEST 0x01
+#define BOOTREPLY 0x02
+
+#define HTYPE_ETHERNET 0x01
+
+#define HLEN_ETHERNET 6
+
+#define DHCP_OPTION_SUBNET_MASK 1
+#define DHCP_OPTION_ROUTER 3
+#define DHCP_OPTION_DNS 6
+#define DHCP_OPTION_DOMAIN_NAME 15
+#define DHCP_OPTION_REQUESTED_IP 50
+#define DHCP_OPTION_MSGTYPE 53
+#define DHCP_OPTION_SERVER_ID 54
+#define DHCP_OPTION_PARAMLIST 55
+
+#define DHCP_COOKIE 0x63825363
+#define DHCP_MSGTYPE_DISCOVER 0x01
+#define DHCP_MSGTYPE_OFFER 0x02
+#define DHCP_MSGTYPE_REQUEST 0x03
+#define DHCP_MSGTYPE_ACK 0x05
+
+class CDhcpHeader {
+ U8 op;
+ U8 htype;
+ U8 hlen;
+ U8 hops;
+ U32 xid;
+ U16 secs;
+ U16 flags;
+ U32 ciaddr;
+ U32 yiaddr;
+ U32 siaddr;
+ U32 giaddr;
+ U8 chaddr[16];
+ U8 sname[64];
+ U8 file[128];
+};
+
+class CDhcpDiscoverOptions {
+ U32 cookie;
+ // DHCP Message Type
+ U8 dmt_type;
+ U8 dmt_length;
+ U8 dmt;
+ // DHCP Parameter Request List
+ U8 prl_type;
+ U8 prl_length;
+ U8 prl[4];
+
+ U8 end;
+};
+
+class CDhcpRequestOptions {
+ U32 cookie;
+ // DHCP Message Type
+ U8 dmt_type;
+ U8 dmt_length;
+ U8 dmt;
+ // DHCP Requested IP
+ U8 requested_ip_type;
+ U8 requested_ip_length;
+ U32 requested_ip;
+ // DHCP Server Identifier
+ U8 server_id_type;
+ U8 server_id_length;
+ U32 server_id;
+
+ U8 end;
+};
+
+U32 DhcpBeginTransaction() { return RandU32(); }
+
+I64 DhcpSendDiscover(U32 xid) {
+ U8 *frame;
+ I64 index =
+ UdpPacketAlloc(&frame, 0x00000000, 68, 0xffffffff, 67,
+ sizeof(CDhcpHeader) + sizeof(CDhcpDiscoverOptions));
+
+ if (index < 0)
+ return index;
+
+ CDhcpHeader *dhcp = frame;
+ MemSet(dhcp, 0, sizeof(CDhcpHeader));
+ dhcp->op = BOOTREQUEST;
+ dhcp->htype = HTYPE_ETHERNET;
+ dhcp->hlen = HLEN_ETHERNET;
+ dhcp->hops = 0;
+ dhcp->xid = htonl(xid);
+ dhcp->secs = 0;
+ dhcp->flags = htons(0x8000);
+ dhcp->ciaddr = 0;
+ dhcp->yiaddr = 0;
+ dhcp->siaddr = 0;
+ dhcp->giaddr = 0;
+ MemCpy(dhcp->chaddr, EthernetGetAddress(), 6);
+
+ CDhcpDiscoverOptions *opts = frame + sizeof(CDhcpHeader);
+ opts->cookie = htonl(DHCP_COOKIE);
+ opts->dmt_type = DHCP_OPTION_MSGTYPE;
+ opts->dmt_length = 1;
+ opts->dmt = DHCP_MSGTYPE_DISCOVER;
+ opts->prl_type = DHCP_OPTION_PARAMLIST;
+ opts->prl_length = 4;
+ opts->prl[0] = DHCP_OPTION_SUBNET_MASK;
+ opts->prl[1] = DHCP_OPTION_ROUTER;
+ opts->prl[2] = DHCP_OPTION_DNS;
+ opts->prl[3] = DHCP_OPTION_DOMAIN_NAME;
+ opts->end = 0xff;
+
+ return UdpPacketFinish(index);
+}
+
+I64 DhcpSendRequest(U32 xid, U32 requested_ip, U32 siaddr) {
+ U8 *frame;
+ I64 index = UdpPacketAlloc(&frame, 0x00000000, 68, 0xffffffff, 67,
+ sizeof(CDhcpHeader) + sizeof(CDhcpRequestOptions));
+
+ if (index < 0)
+ return index;
+
+ CDhcpHeader *dhcp = frame;
+ MemSet(dhcp, 0, sizeof(CDhcpHeader));
+ dhcp->op = BOOTREQUEST;
+ dhcp->htype = HTYPE_ETHERNET;
+ dhcp->hlen = HLEN_ETHERNET;
+ dhcp->hops = 0;
+ dhcp->xid = htonl(xid);
+ dhcp->secs = 0;
+ dhcp->flags = htons(0x0000);
+ dhcp->ciaddr = 0;
+ dhcp->yiaddr = 0;
+ dhcp->siaddr = htonl(siaddr);
+ dhcp->giaddr = 0;
+ MemCpy(dhcp->chaddr, EthernetGetAddress(), 6);
+
+ CDhcpRequestOptions *opts = frame + sizeof(CDhcpHeader);
+ opts->cookie = htonl(DHCP_COOKIE);
+ opts->dmt_type = DHCP_OPTION_MSGTYPE;
+ opts->dmt_length = 1;
+ opts->dmt = DHCP_MSGTYPE_REQUEST;
+ opts->requested_ip_type = DHCP_OPTION_REQUESTED_IP;
+ opts->requested_ip_length = 4;
+ opts->requested_ip = htonl(requested_ip);
+ opts->server_id_type = DHCP_OPTION_SERVER_ID;
+ opts->server_id_length = 4;
+ opts->server_id = htonl(siaddr);
+ opts->end = 0xff;
+
+ return UdpPacketFinish(index);
+}
+
+I64 DhcpParseBegin(U8 **data_inout, I64 *length_inout, CDhcpHeader **hdr_out) {
+ U8 *data = *data_inout;
+ I64 length = *length_inout;
+
+ if (length < sizeof(CDhcpHeader) + 4) {
+ //"DhcpParseBegin: too short\n";
+ return -1;
+ }
+
+ U32 *p_cookie = data + sizeof(CDhcpHeader);
+
+ if (ntohl(*p_cookie) != DHCP_COOKIE) {
+ //"DhcpParseBegin: cookie %08Xh != %08Xh\n", ntohl(*p_cookie), DHCP_COOKIE;
+ return -1;
+ }
+
+ *hdr_out = data;
+ *data_inout = data + (sizeof(CDhcpHeader) + 4);
+ *length_inout = length - (sizeof(CDhcpHeader) + 4);
+ return 0;
+}
+
+I64 DhcpParseOption(U8 **data_inout, I64 *length_inout, U8 *type_out,
+ U8 *value_length_out, U8 **value_out) {
+ U8 *data = *data_inout;
+ I64 length = *length_inout;
+
+ if (length < 2 || length < 2 + data[1]) {
+ //"DhcpParseOption: too short\n";
+ return -1;
+ }
+
+ if (data[0] == 0xff)
+ return 0;
+
+ *type_out = data[0];
+ *value_length_out = data[1];
+ *value_out = data + 2;
+
+ *data_inout = data + (2 + *value_length_out);
+ *length_inout = length - (2 + *value_length_out);
+ return data[0];
+}
+
+I64 DhcpParseOffer(U32 xid, U8 *data, I64 length, U32 *yiaddr_out,
+ U32 *dns_ip_out, U32 *router_ip_out, U32 *subnet_mask_out) {
+ CDhcpHeader *hdr;
+ I64 error = DhcpParseBegin(&data, &length, &hdr);
+ if (error < 0)
+ return error;
+
+ if (ntohl(hdr->xid) != xid)
+ return -1;
+
+ Bool have_type = FALSE;
+ Bool have_dns = FALSE;
+ Bool have_router = FALSE;
+ Bool have_subnet = FALSE;
+
+ while (length) {
+ U8 type, value_length;
+ U8 *value;
+
+ error = DhcpParseOption(&data, &length, &type, &value_length, &value);
+ //"%d, %02Xh, %d, %02Xh...\n", error, type, value_length, value[0];
+ if (error < 0)
+ return error;
+ if (error == 0)
+ break;
+
+ if (type == DHCP_OPTION_MSGTYPE && value_length == 1 &&
+ value[0] == DHCP_MSGTYPE_OFFER)
+ have_type = TRUE;
+
+ if (type == DHCP_OPTION_DNS && value_length == 4) {
+ *dns_ip_out = ntohl(*(value(U32 *)));
+ have_dns = TRUE;
+ }
+
+ if (type == DHCP_OPTION_ROUTER && value_length == 4) {
+ *router_ip_out = ntohl(*(value(U32 *)));
+ have_router = TRUE;
+ }
+
+ if (type == DHCP_OPTION_SUBNET_MASK && value_length == 4) {
+ *subnet_mask_out = ntohl(*(value(U32 *)));
+ have_subnet = TRUE;
+ }
+ }
+
+ //"DhcpParseOffer: end %d %d %d %d\n", have_type, have_dns, have_subnet,
+ // have_router;
+
+ // VirtualBox host network doesn't provide DNS or ROUTER, so this has to do
+ if (have_type && have_subnet) {
+ *yiaddr_out = ntohl(hdr->yiaddr);
+ return 0;
+ } else
+ return -1;
+}
+
+I64 DhcpParseAck(U32 xid, U8 *data, I64 length) {
+ CDhcpHeader *hdr;
+ I64 error = DhcpParseBegin(&data, &length, &hdr);
+ if (error < 0)
+ return error;
+
+ if (ntohl(hdr->xid) != xid)
+ return -1;
+
+ while (length) {
+ U8 type, value_length;
+ U8 *value;
+
+ error = DhcpParseOption(&data, &length, &type, &value_length, &value);
+ //"%d, %02Xh, %d, %02Xh...\n", error, type, value_length, value[0];
+ if (error < 0)
+ return error;
+ if (error == 0)
+ break;
+
+ if (type == DHCP_OPTION_MSGTYPE && value_length == 1 &&
+ value[0] == DHCP_MSGTYPE_ACK)
+ return 0;
+ }
+
+ return -1;
+}
diff --git a/Net/Dns.HC b/Net/Dns.HC
new file mode 100644
index 0000000..b8003f1
--- /dev/null
+++ b/Net/Dns.HC
@@ -0,0 +1,539 @@
+#define DNS_RCODE_NO_ERROR 0
+#define DNS_RCODE_FORMAT_ERROR 1
+#define DNS_RCODE_SERVER_FAILURE 2
+#define DNS_RCODE_NAME_ERROR 3
+#define DNS_RCODE_NOT_IMPLEMENTED 5
+#define DNS_RCODE_REFUSED 6
+
+#define DNS_FLAG_RA 0x0080
+#define DNS_FLAG_RD 0x0100
+#define DNS_FLAG_TC 0x0200
+#define DNS_FLAG_AA 0x0400
+
+#define DNS_OP_QUERY 0
+#define DNS_OP_IQUERY 1
+#define DNS_OP_STATUS 2
+
+#define DNS_FLAG_QR 0x8000
+
+// http://www.freesoft.org/CIE/RFC/1035/14.htm
+#define DNS_TYPE_A 1
+#define DNS_TYPE_NS 2
+#define DNS_TYPE_CNAME 5
+#define DNS_TYPE_PTR 12
+#define DNS_TYPE_MX 15
+#define DNS_TYPE_TXT 16
+
+// http://www.freesoft.org/CIE/RFC/1035/16.htm
+#define DNS_CLASS_IN 1
+
+#define DNS_TIMEOUT 5000
+#define DNS_MAX_RETRIES 3
+
+class CDnsCacheEntry {
+ CDnsCacheEntry *next;
+ U8 *hostname;
+ addrinfo info;
+ // TODO: honor TTL
+};
+
+class CDnsHeader {
+ U16 id;
+ U16 flags;
+ U16 qdcount;
+ U16 ancount;
+ U16 nscount;
+ U16 arcount;
+};
+
+class CDnsDomainName {
+ U8 **labels;
+ I64 num_labels;
+}
+
+class CDnsQuestion {
+ CDnsQuestion *next;
+
+ CDnsDomainName qname;
+ U16 qtype;
+ U16 qclass;
+};
+
+class CDnsRR {
+ CDnsRR *next;
+
+ CDnsDomainName name;
+ U16 type;
+ U16 class_;
+ U32 ttl;
+ U16 rdlength;
+ U8 *rdata;
+};
+
+// TODO: use a Hash table
+static CDnsCacheEntry *dns_cache = NULL;
+
+static U32 dns_ip = 0;
+
+static CDnsCacheEntry *DnsCacheFind(U8 *hostname) {
+ CDnsCacheEntry *e = dns_cache;
+
+ while (e) {
+ if (!StrCmp(e->hostname, hostname))
+ return e;
+
+ e = e->next;
+ }
+
+ return e;
+}
+
+static CDnsCacheEntry *DnsCachePut(U8 *hostname, addrinfo *info) {
+ CDnsCacheEntry *e = DnsCacheFind(hostname);
+
+ if (!e) {
+ e = MAlloc(sizeof(CDnsCacheEntry));
+ e->next = dns_cache;
+ e->hostname = StrNew(hostname);
+ AddrInfoCopy(&e->info, info);
+
+ dns_cache = e;
+ }
+
+ return e;
+}
+
+static I64 DnsCalcQuestionSize(CDnsQuestion *question) {
+ I64 size = 0;
+ I64 i;
+ for (i = 0; i < question->qname.num_labels; i++) {
+ size += 1 + StrLen(question->qname.labels[i]);
+ }
+ return size + 1 + 4;
+}
+
+static U0 DnsSerializeQuestion(U8 *buf, CDnsQuestion *question) {
+ I64 i;
+
+ for (i = 0; i < question->qname.num_labels; i++) {
+ U8 *label = question->qname.labels[i];
+ *(buf++) = StrLen(label);
+
+ while (*label)
+ *(buf++) = *(label++);
+ }
+
+ *(buf++) = 0;
+ *(buf++) = (question->qtype >> 8);
+ *(buf++) = (question->qtype & 0xff);
+ *(buf++) = (question->qclass >> 8);
+ *(buf++) = (question->qclass & 0xff);
+}
+
+static I64 DnsSendQuestion(U16 id, U16 local_port, CDnsQuestion *question) {
+ if (!dns_ip)
+ return -1;
+
+ U8 *frame;
+ I64 index =
+ UdpPacketAlloc(&frame, IPv4GetAddress(), local_port, dns_ip, 53,
+ sizeof(CDnsHeader) + DnsCalcQuestionSize(question));
+
+ if (index < 0)
+ return index;
+
+ U16 flags = (DNS_OP_QUERY << 11) | DNS_FLAG_RD;
+
+ CDnsHeader *hdr = frame;
+ hdr->id = htons(id);
+ hdr->flags = htons(flags);
+ hdr->qdcount = htons(1);
+ hdr->ancount = 0;
+ hdr->nscount = 0;
+ hdr->arcount = 0;
+
+ DnsSerializeQuestion(frame + sizeof(CDnsHeader), question);
+
+ return UdpPacketFinish(index);
+}
+
+static I64 DnsParseDomainName(U8 *packet_data, I64 packet_length,
+ U8 **data_inout, I64 *length_inout,
+ CDnsDomainName *name_out) {
+ U8 *data = *data_inout;
+ I64 length = *length_inout;
+ Bool jump_taken = FALSE;
+
+ if (length < 1) {
+ //"DnsParseDomainName: EOF\n";
+ return -1;
+ }
+
+ name_out->labels = MAlloc(16 * sizeof(U8 *));
+ name_out->num_labels = 0;
+
+ U8 *name_buf = MAlloc(256);
+ name_out->labels[0] = name_buf;
+
+ while (length) {
+ I64 label_len = *(data++);
+ length--;
+
+ if (label_len == 0) {
+ break;
+ } else if (label_len >= 192) {
+ label_len &= 0x3f;
+
+ if (!jump_taken) {
+ *data_inout = data + 1;
+ *length_inout = length - 1;
+ jump_taken = TRUE;
+ }
+
+ //"jmp %d\n", ((label_len << 8) | *data);
+
+ data = packet_data + ((label_len << 8) | *data);
+ length = packet_data + packet_length - data;
+ } else {
+ if (length < label_len)
+ return -1;
+
+ MemCpy(name_buf, data, label_len);
+ data += label_len;
+ length -= label_len;
+
+ name_buf[label_len] = 0;
+ //"%d bytes => %s\n", label_len, name_buf;
+ name_out->labels[name_out->num_labels++] = name_buf;
+
+ name_buf += label_len + 1;
+ }
+ }
+
+ if (!jump_taken) {
+ *data_inout = data;
+ *length_inout = length;
+ }
+
+ return 0;
+}
+
+static I64 DnsParseQuestion(U8 *packet_data, I64 packet_length, U8 **data_inout,
+ I64 *length_inout, CDnsQuestion *question_out) {
+ I64 error = DnsParseDomainName(packet_data, packet_length, data_inout,
+ length_inout, &question_out->qname);
+
+ if (error < 0)
+ return error;
+
+ U8 *data = *data_inout;
+ I64 length = *length_inout;
+
+ if (length < 4)
+ return -1;
+
+ question_out->next = NULL;
+ question_out->qtype = (data[1] << 8) | data[0];
+ question_out->qclass = (data[3] << 8) | data[2];
+
+ //"DnsParseQuestion: qtype %d, qclass %d\n", ntohs(question_out->qtype),
+ // ntohs(question_out->qclass);
+
+ *data_inout = data + 4;
+ *length_inout = length - 4;
+ return 0;
+}
+
+static I64 DnsParseRR(U8 *packet_data, I64 packet_length, U8 **data_inout,
+ I64 *length_inout, CDnsRR *rr_out) {
+ I64 error = DnsParseDomainName(packet_data, packet_length, data_inout,
+ length_inout, &rr_out->name);
+
+ if (error < 0)
+ return error;
+
+ U8 *data = *data_inout;
+ I64 length = *length_inout;
+
+ if (length < 10)
+ return -1;
+
+ rr_out->next = NULL;
+ MemCpy(&rr_out->type, data, 10);
+
+ I64 record_length = 10 + ntohs(rr_out->rdlength);
+
+ if (length < record_length)
+ return -1;
+
+ rr_out->rdata = data + 10;
+
+ //"DnsParseRR: type %d, class %d\n, ttl %d, rdlength %d\n",
+ // ntohs(rr_out->type), ntohs(rr_out->class_), ntohl(rr_out->ttl),
+ // ntohs(rr_out->rdlength);
+
+ *data_inout = data + record_length;
+ *length_inout = length - record_length;
+ return 0;
+}
+
+static I64 DnsParseResponse(U16 id, U8 *data, I64 length, CDnsHeader **hdr_out,
+ CDnsQuestion **questions_out,
+ CDnsRR **answers_out) {
+ U8 *packet_data = data;
+ I64 packet_length = length;
+
+ if (length < sizeof(CDnsHeader)) {
+ //"DnsParseResponse: too short\n";
+ return -1;
+ }
+
+ CDnsHeader *hdr = data;
+ data += sizeof(CDnsHeader);
+
+ if (id != 0 && ntohs(hdr->id) != id) {
+ //"DnsParseResponse: id %04Xh != %04Xh\n", ntohs(hdr->id), id;
+ return -1;
+ }
+
+ I64 i;
+
+ for (i = 0; i < htons(hdr->qdcount); i++) {
+ CDnsQuestion *question = MAlloc(sizeof(CDnsQuestion));
+ if (DnsParseQuestion(packet_data, packet_length, &data, &length, question) <
+ 0)
+ return -1;
+
+ question->next = *questions_out;
+ *questions_out = question;
+ }
+
+ for (i = 0; i < htons(hdr->ancount); i++) {
+ CDnsRR *answer = MAlloc(sizeof(CDnsRR));
+ if (DnsParseRR(packet_data, packet_length, &data, &length, answer) < 0)
+ return -1;
+
+ answer->next = *answers_out;
+ *answers_out = answer;
+ }
+
+ *hdr_out = hdr;
+ return 0;
+}
+
+static U0 DnsBuildQuestion(CDnsQuestion *question, U8 *name) {
+ question->next = NULL;
+ question->qname.labels = MAlloc(16 * sizeof(U8 *));
+ question->qname.labels[0] = 0;
+ question->qname.num_labels = 0;
+ question->qtype = DNS_TYPE_A;
+ question->qclass = DNS_CLASS_IN;
+
+ U8 *copy = StrNew(name);
+
+ while (*copy) {
+ question->qname.labels[question->qname.num_labels++] = copy;
+ U8 *dot = StrFirstOcc(copy, ".");
+
+ if (dot) {
+ *dot = 0;
+ copy = dot + 1;
+ } else
+ break;
+ }
+}
+
+static U0 DnsFreeQuestion(CDnsQuestion *question) {
+ Free(question->qname.labels[0]);
+}
+
+static U0 DnsFreeRR(CDnsRR *rr) { Free(rr->name.labels[0]); }
+
+static U0 DnsFreeQuestionChain(CDnsQuestion *questions) {
+ while (questions) {
+ CDnsQuestion *next = questions->next;
+ DnsFreeQuestion(questions);
+ Free(questions);
+ questions = next;
+ }
+}
+
+static U0 DnsFreeRRChain(CDnsRR *rrs) {
+ while (rrs) {
+ CDnsQuestion *next = rrs->next;
+ DnsFreeRR(rrs);
+ Free(rrs);
+ rrs = next;
+ }
+}
+
+static I64 DnsRunQuery(I64 sock, U8 *name, U16 port, addrinfo **res_out) {
+ I64 retries = 0;
+ I64 timeout = DNS_TIMEOUT;
+
+ if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO_MS, &timeout, sizeof(timeout)) <
+ 0) {
+ "$FG,6$DnsRunQuery: setsockopt failed\n$FG$";
+ }
+
+ U16 local_port = RandU16();
+
+ sockaddr_in addr;
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(local_port);
+ addr.sin_addr.s_addr = INADDR_ANY;
+
+ if (bind(sock, &addr, sizeof(addr)) < 0) {
+ "$FG,4$DnsRunQuery: failed to bind\n$FG$";
+ return -1;
+ }
+
+ U8 buffer[2048];
+
+ I64 count;
+ sockaddr_in addr_in;
+
+ U16 id = RandU16();
+ I64 error = 0;
+
+ CDnsQuestion question;
+ DnsBuildQuestion(&question, name);
+
+ while (1) {
+ error = DnsSendQuestion(id, local_port, &question);
+ if (error < 0)
+ return error;
+
+ count =
+ recvfrom(sock, buffer, sizeof(buffer), 0, &addr_in, sizeof(addr_in));
+
+ if (count > 0) {
+ //"Try parse response\n";
+ CDnsHeader *hdr = NULL;
+ CDnsQuestion *questions = NULL;
+ CDnsRR *answers = NULL;
+
+ error = DnsParseResponse(id, buffer, count, &hdr, &questions, &answers);
+
+ if (error >= 0) {
+ Bool have = FALSE;
+
+ // Look for a suitable A-record in the answer
+ CDnsRR *answer = answers;
+ while (answer) {
+ // TODO: if there are multiple acceptable answers,
+ // we should pick one at random -- not just the first one
+ if (htons(answer->type) == DNS_TYPE_A &&
+ htons(answer->class_) == DNS_CLASS_IN &&
+ htons(answer->rdlength) == 4) {
+ addrinfo *res = MAlloc(sizeof(addrinfo));
+ res->ai_flags = 0;
+ res->ai_family = AF_INET;
+ res->ai_socktype = 0;
+ res->ai_protocol = 0;
+ res->ai_addrlen = sizeof(sockaddr_in);
+ res->ai_addr = MAlloc(sizeof(sockaddr_in));
+ res->ai_canonname = NULL;
+ res->ai_next = NULL;
+
+ sockaddr_in *sa = res->ai_addr;
+ sa->sin_family = AF_INET;
+ sa->sin_port = port;
+ MemCpy(&sa->sin_addr.s_addr, answers->rdata, 4);
+
+ DnsCachePut(name, res);
+ *res_out = res;
+ have = TRUE;
+ break;
+ }
+
+ answer = answer->next;
+ }
+
+ DnsFreeQuestionChain(questions);
+ DnsFreeRRChain(answers);
+
+ if (have)
+ break;
+
+ // At this point we could try iterative resolution,
+ // but all end-user DNS servers would have tried that already
+
+ "$FG,6$DnsParseResponse: no suitable answer in reply\n$FG$";
+ error = -1;
+ } else {
+ "$FG,6$DnsParseResponse: error %d\n$FG$", error;
+ }
+ }
+
+ if (++retries == DNS_MAX_RETRIES) {
+ "$FG,4$DnsRunQuery: max retries reached\n$FG$";
+ error = -1;
+ break;
+ }
+ }
+
+ DnsFreeQuestion(&question);
+ return error;
+}
+
+I64 DnsGetaddrinfo(U8 *node, U8 *service, addrinfo *hints, addrinfo **res) {
+ no_warn service;
+ no_warn hints;
+
+ CDnsCacheEntry *cached = DnsCacheFind(node);
+
+ if (cached) {
+ *res = MAlloc(sizeof(addrinfo));
+ AddrInfoCopy(*res, &cached->info);
+ (*res)->ai_flags |= AI_CACHED;
+ return 0;
+ }
+
+ I64 sock = socket(AF_INET, SOCK_DGRAM);
+ I64 error = 0;
+
+ if (sock >= 0) {
+ // TODO: service should be parsed as int, specifying port number
+ error = DnsRunQuery(sock, node, 0, res);
+
+ close(sock);
+ } else
+ error = -1;
+
+ return error;
+}
+
+U0 DnsSetResolverIPv4(U32 ip) { dns_ip = ip; }
+
+public
+U0 Host(U8 *hostname) {
+ addrinfo *res = NULL;
+ I64 error = getaddrinfo(hostname, NULL, NULL, &res);
+
+ if (error < 0) {
+ "$FG,4$getaddrinfo: error %d\n", error;
+ } else {
+ addrinfo *curr = res;
+ while (curr) {
+ U8 buffer[INET_ADDRSTRLEN];
+ "flags %04Xh, family %d, socktype %d, proto %d, addrlen %d, addr %s\n",
+ curr->ai_flags, curr->ai_family, curr->ai_socktype, curr->ai_protocol,
+ curr->ai_addrlen,
+ inet_ntop(AF_INET, &(curr->ai_addr(sockaddr_in *))->sin_addr, buffer,
+ sizeof(buffer));
+ curr = curr->ai_next;
+ }
+ }
+
+ freeaddrinfo(res);
+}
+
+U0 DnsInit() {
+ static CAddrResolver dns_addr_resolver;
+ dns_addr_resolver.getaddrinfo = &DnsGetaddrinfo;
+
+ socket_addr_resolver = &dns_addr_resolver;
+}
+
+DnsInit;
diff --git a/Net/Ethernet.HC b/Net/Ethernet.HC
new file mode 100644
index 0000000..475607e
--- /dev/null
+++ b/Net/Ethernet.HC
@@ -0,0 +1,57 @@
+class CEthFrame {
+ U8 source_addr[6];
+ U8 padding[2];
+ U8 dest_addr[6];
+ U16 ethertype;
+
+ U8 *data;
+ I64 length;
+};
+
+class CL3Protocol {
+ CL3Protocol *next;
+
+ U16 ethertype;
+ U8 padding[6];
+
+ I64 (*handler)(CEthFrame *frame);
+};
+
+static CL3Protocol *l3_protocols = NULL;
+
+U8 eth_null[6] = {0, 0, 0, 0, 0, 0};
+U8 eth_broadcast[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+
+I64 EthernetFrameParse(CEthFrame *frame_out, U8 *frame, U16 length) {
+ // FIXME: check length
+ // TODO: MemCpy has high overhead, get rid of it
+ MemCpy(frame_out->dest_addr, frame, 6);
+ MemCpy(frame_out->source_addr, frame + 6, 6);
+ frame_out->ethertype = frame[13] | (frame[12] << 8);
+
+ /*"Rx dst: %02X:%02X:%02X:%02X:%02X:%02X\n",
+ frame_out->dest_addr[0], frame_out->dest_addr[1],
+ frame_out->dest_addr[2], frame_out->dest_addr[3], frame_out->dest_addr[4],
+ frame_out->dest_addr[5];
+
+ "Rx src: %02X:%02X:%02X:%02X:%02X:%02X\n",
+ frame_out->source_addr[0], frame_out->source_addr[1],
+ frame_out->source_addr[2], frame_out->source_addr[3],
+ frame_out->source_addr[4], frame_out->source_addr[5];
+
+ "Rx ethertype: %02X\n", frame_out->ethertype;*/
+
+ frame_out->data = frame + 14;
+ frame_out->length = length - 14 - 4; // ??
+ return 0;
+}
+
+U0 RegisterL3Protocol(U16 ethertype, I64 (*handler)(CEthFrame *frame)) {
+ CL3Protocol *p = MAlloc(sizeof(CL3Protocol));
+
+ p->next = l3_protocols;
+ p->ethertype = ethertype;
+ p->handler = handler;
+
+ l3_protocols = p;
+}
diff --git a/Net/IPv4.HC b/Net/IPv4.HC
new file mode 100644
index 0000000..490197f
--- /dev/null
+++ b/Net/IPv4.HC
@@ -0,0 +1,259 @@
+#define IP_PROTO_ICMP 0x01
+#define IP_PROTO_TCP 0x06
+#define IP_PROTO_UDP 0x11
+
+#define IPV4_EADDR_INVALID (-200001)
+#define IPV4_EHOST_UNREACHABLE (-200002)
+
+#define IPV4_TTL 64
+
+class CIPv4Packet {
+ CEthFrame *l2_frame;
+
+ U32 source_ip;
+ U32 dest_ip;
+ U8 proto;
+ U8 padding[7];
+
+ U8 *data;
+ I64 length;
+ I64 ttl;
+};
+
+class CIPv4Header {
+ U8 version_ihl;
+ U8 dscp_ecn;
+ U16 total_length;
+ U16 ident;
+ U16 flags_fragoff;
+ U8 ttl;
+ U8 proto;
+ U16 header_checksum;
+ U32 source_ip;
+ U32 dest_ip;
+};
+
+class CL4Protocol {
+ CL4Protocol *next;
+
+ U8 proto;
+ U8 padding[7];
+
+ U0 (*handler)(CIPv4Packet *packet);
+};
+
+// *_n = stored in network order
+static U32 my_ip = 0;
+static U32 my_ip_n = 0;
+
+static U32 ipv4_router_addr = 0;
+static U32 ipv4_subnet_mask = 0;
+
+static CL4Protocol *l4_protocols = NULL;
+
+// http://stackoverflow.com/q/26774761/2524350
+static U16 IPv4Checksum(U8 *header, I64 length) {
+ I64 nleft = length;
+ U16 *w = header;
+ I64 sum = 0;
+
+ while (nleft > 1) {
+ sum += *(w++);
+ nleft -= 2;
+ }
+
+ // mop up an odd byte, if necessary
+ if (nleft == 1) {
+ sum += ((*w) & 0x00ff);
+ }
+
+ // add back carry outs from top 16 bits to low 16 bits
+ sum = (sum >> 16) + (sum & 0xffff); // add hi 16 to low 16
+ sum += (sum >> 16); // add carry
+ return (~sum) & 0xffff;
+}
+
+static I64 GetEthernetAddressForIP(U32 ip, U8 **mac_out) {
+ // invalid
+ if (ip == 0) {
+ return IPV4_EADDR_INVALID;
+ }
+ // broadcast
+ else if (ip == 0xffffffff) {
+ *mac_out = eth_broadcast;
+ return 0;
+ }
+ // outside this subnet; needs routing
+ else if ((ip & ipv4_subnet_mask) != (my_ip & ipv4_subnet_mask)) {
+ // no gateway
+ if (ipv4_router_addr == 0) {
+ return IPV4_EADDR_INVALID;
+ }
+
+ // FIXME: infinite loop if mis-configured
+
+ return GetEthernetAddressForIP(ipv4_router_addr, mac_out);
+ }
+ // local network
+ else {
+ // FIXME: this can stall NetHandlerTask, we might need a flag to bail early
+
+ CArpCacheEntry *e = ArpCacheFindByIP(ip);
+
+ if (e) {
+ *mac_out = e->mac;
+ return 0;
+ }
+
+ //"Not in cache, requesting\n";
+
+ // Up to 4 retries, 500 ms each
+ I64 retries = 4;
+
+ while (retries) {
+ ArpSend(ARP_REQUEST, eth_broadcast, EthernetGetAddress(), my_ip_n,
+ eth_null, htonl(ip));
+
+ I64 try_ = 0;
+
+ for (try_ = 0; try_ < 50; try_++) {
+ Sleep(10);
+
+ e = ArpCacheFindByIP(ip);
+ if (e)
+ break;
+ }
+
+ if (e) {
+ *mac_out = e->mac;
+ return 0;
+ }
+
+ retries--;
+ }
+
+ in_addr in;
+ in.s_addr = htonl(ip);
+ U8 buffer[INET_ADDRSTRLEN];
+ "$FG,6$IPv4: Failed to resolve address %s\n$FG$",
+ inet_ntop(AF_INET, &in.s_addr, buffer, sizeof(buffer));
+ return IPV4_EHOST_UNREACHABLE;
+ }
+}
+
+I64 IPv4PacketAlloc(U8 **frame_out, U8 proto, U32 source_ip, U32 dest_ip,
+ I64 length) {
+ U8 *frame;
+ U8 *dest_mac;
+
+ I64 error = GetEthernetAddressForIP(dest_ip, &dest_mac);
+
+ if (error < 0)
+ return error;
+
+ I64 index =
+ EthernetFrameAlloc(&frame, EthernetGetAddress(), dest_mac, ETHERTYPE_IPV4,
+ sizeof(CIPv4Header) + length, 0);
+
+ if (index < 0)
+ return index;
+
+ I64 internet_header_length = 5;
+
+ CIPv4Header *hdr = frame;
+ hdr->version_ihl = internet_header_length | (4 << 4);
+ hdr->dscp_ecn = 0;
+ hdr->total_length = htons(internet_header_length * 4 + length);
+ hdr->ident = 0;
+ hdr->flags_fragoff = 0;
+ hdr->ttl = IPV4_TTL;
+ hdr->proto = proto;
+ hdr->header_checksum = 0;
+ hdr->source_ip = htonl(source_ip);
+ hdr->dest_ip = htonl(dest_ip);
+
+ hdr->header_checksum = IPv4Checksum(hdr, internet_header_length * 4);
+
+ *frame_out = frame + sizeof(CIPv4Header);
+ return index;
+}
+
+I64 IPv4PacketFinish(I64 index) { return EthernetFrameFinish(index); }
+
+U32 IPv4GetAddress() { return my_ip; }
+
+U0 IPv4SetAddress(U32 addr) {
+ my_ip = addr;
+ my_ip_n = htonl(addr);
+
+ ArpSetIPv4Address(addr);
+}
+
+U0 IPv4SetSubnet(U32 router_addr, U32 subnet_mask) {
+ ipv4_router_addr = router_addr;
+ ipv4_subnet_mask = subnet_mask;
+}
+
+I64 IPv4ParsePacket(CIPv4Packet *packet_out, CEthFrame *eth_frame) {
+ if (eth_frame->ethertype != ETHERTYPE_IPV4)
+ return -1;
+
+ // FIXME: check eth_frame->length etc.
+
+ CIPv4Header *hdr = eth_frame->data;
+ I64 header_length = (hdr->version_ihl & 0x0f) * 4;
+ //"IPv4: hdr %d, proto %02X, source %08X, dest %08X, len %d\n",
+ // header_length, hdr->proto, ntohl(hdr->source_ip), ntohl(hdr->dest_ip),
+ // eth_frame->length - header_length;
+
+ U16 total_length = ntohs(hdr->total_length);
+
+ packet_out->l2_frame = eth_frame;
+ packet_out->source_ip = ntohl(hdr->source_ip);
+ packet_out->dest_ip = ntohl(hdr->dest_ip);
+ packet_out->proto = hdr->proto;
+
+ packet_out->data = eth_frame->data + header_length;
+ packet_out->length = total_length - header_length;
+ packet_out->ttl = hdr->ttl;
+
+ return 0;
+}
+
+U0 RegisterL4Protocol(U8 proto, I64 (*handler)(CIPv4Packet *frame)) {
+ CL4Protocol *p = MAlloc(sizeof(CL4Protocol));
+
+ p->next = l4_protocols;
+ p->proto = proto;
+ p->handler = handler;
+
+ l4_protocols = p;
+}
+
+I64 IPv4Handler(CEthFrame *eth_frame) {
+ CIPv4Packet packet;
+
+ I64 error = IPv4ParsePacket(&packet, eth_frame);
+
+ if (error < 0)
+ return error;
+
+ // This seems necessary to receive connections under VBox NAT,
+ // but is also pretty slow, so should be optimized to use a better
+ // struct than linked list.
+ ArpCachePut(packet.source_ip, eth_frame->source_addr);
+
+ CL4Protocol *l4 = l4_protocols;
+
+ while (l4) {
+ if (l4->proto == packet.proto) {
+ l4->handler(&packet);
+ break;
+ }
+ l4 = l4->next;
+ }
+
+ return error;
+}
+
+RegisterL3Protocol(ETHERTYPE_IPV4, &IPv4Handler);
diff --git a/Net/Icmp.HC b/Net/Icmp.HC
new file mode 100644
index 0000000..0deab1f
--- /dev/null
+++ b/Net/Icmp.HC
@@ -0,0 +1,101 @@
+#define ICMP_TYPE_ECHO_REPLY 0
+#define ICMP_TYPE_ECHO_REQUEST 8
+
+class CIcmpHeader {
+ U8 type;
+ U8 code;
+ U16 checksum;
+ U16 identifier;
+ U16 seq_number;
+};
+
+U64 *icmp_reply = CAlloc(sizeof(U64) * 65536);
+
+U16 IcmpComputeChecksum(U8 *buf, I64 size) {
+ I64 i;
+ U64 sum = 0;
+
+ for (i = 0; i < size; i += 2) {
+ sum += *buf(U16 *);
+ buf += 2;
+ }
+ if (size - i > 0) {
+ sum += *buf;
+ }
+
+ while ((sum >> 16) != 0) {
+ sum = (sum & 0xFFFF) + (sum >> 16);
+ }
+
+ return ~sum(U16);
+}
+
+I64 IcmpSendReply(U32 dest_ip, U16 identifier, U16 seq_number,
+ U16 request_checksum, U8 *payload, I64 length) {
+ U8 *frame;
+ I64 index = IPv4PacketAlloc(&frame, IP_PROTO_ICMP, IPv4GetAddress(), dest_ip,
+ sizeof(CIcmpHeader) + length);
+
+ if (index < 0)
+ return index;
+
+ CIcmpHeader *hdr = frame;
+ hdr->type = ICMP_TYPE_ECHO_REPLY;
+ hdr->code = 0;
+ hdr->checksum = htons(ntohs(request_checksum) + 0x0800); // hack alert!
+ hdr->identifier = identifier;
+ hdr->seq_number = seq_number;
+
+ MemCpy(frame + sizeof(CIcmpHeader), payload, length);
+ return IPv4PacketFinish(index);
+}
+
+I64 IcmpSendRequest(U32 dest_ip, U16 identifier, U16 seq_number,
+ U16 request_checksum, U8 *payload, I64 length) {
+ no_warn request_checksum;
+ U8 *frame;
+ I64 index = IPv4PacketAlloc(&frame, IP_PROTO_ICMP, IPv4GetAddress(), dest_ip,
+ sizeof(CIcmpHeader) + length);
+
+ if (index < 0)
+ return index;
+
+ CIcmpHeader *hdr = frame;
+ hdr->type = ICMP_TYPE_ECHO_REQUEST;
+ hdr->code = 0;
+ hdr->checksum = 0;
+ hdr->identifier = identifier;
+ hdr->seq_number = seq_number;
+
+ hdr->checksum = IcmpComputeChecksum(hdr, sizeof(CIcmpHeader));
+
+ MemCpy(frame + sizeof(CIcmpHeader), payload, length);
+ return IPv4PacketFinish(index);
+}
+
+I64 IcmpHandler(CIPv4Packet *packet) {
+ if (packet->proto != IP_PROTO_ICMP)
+ return -1;
+
+ if (packet->length < sizeof(CIcmpHeader))
+ return -1;
+
+ CIcmpHeader *hdr = packet->data;
+
+ if (hdr->type == ICMP_TYPE_ECHO_REPLY && hdr->code == 0) {
+ icmp_reply[hdr->identifier] = packet;
+ }
+
+ if (hdr->type == ICMP_TYPE_ECHO_REQUEST && hdr->code == 0) {
+ // This also makes sure that we don't stall NetHandlerTask
+ ArpCachePut(packet->source_ip, packet->l2_frame->source_addr);
+
+ IcmpSendReply(packet->source_ip, hdr->identifier, hdr->seq_number,
+ hdr->checksum, packet->data + sizeof(CIcmpHeader),
+ packet->length - sizeof(CIcmpHeader));
+ }
+
+ return 0;
+}
+
+RegisterL4Protocol(IP_PROTO_ICMP, &IcmpHandler);
diff --git a/Net/NativeSocket.HC b/Net/NativeSocket.HC
new file mode 100644
index 0000000..f2517b2
--- /dev/null
+++ b/Net/NativeSocket.HC
@@ -0,0 +1,327 @@
+#define SOCK_STREAM 1
+#define SOCK_DGRAM 2
+#define SOCK_RAW 3
+
+#define AF_UNSPEC 0
+#define AF_INET 2
+#define AF_INET6 10
+
+#define INADDR_ANY 0
+
+#define INET_ADDRSTRLEN 16
+
+#define NS_INADDRSZ 4
+
+#define SOL_SOCKET 1
+
+// optval = I64*
+#define SO_RCVTIMEO_MS 1
+
+#define AI_CACHED 0x8000
+
+class in_addr {
+ U32 s_addr;
+};
+
+class sockaddr {
+ U16 sa_family;
+ U8 sa_data[14];
+};
+
+class sockaddr_in {
+ I16 sin_family;
+ U16 sin_port;
+ in_addr sin_addr;
+ U8 sin_zero[8];
+};
+
+class addrinfo {
+ I32 ai_flags;
+ I32 ai_family;
+ I32 ai_socktype;
+ I32 ai_protocol;
+ I64 ai_addrlen;
+ sockaddr *ai_addr;
+ U8 *ai_canonname;
+ addrinfo *ai_next;
+};
+
+I64 inet_pton(I64 af, U8 *src, U8 *dst) {
+ I64 saw_digit, octets, ch;
+ U8 tmp[NS_INADDRSZ], *tp;
+
+ if (af != AF_INET) {
+ return -1;
+ }
+
+ saw_digit = 0;
+ octets = 0;
+ *(tp = tmp) = 0;
+ while (*src) {
+ ch = *src++;
+ if (ch >= '0' && ch <= '9') {
+ U64 new = *tp * 10 + (ch - '0');
+ if (saw_digit && *tp == 0)
+ return 0;
+ if (new > 255)
+ return 0;
+ *tp = new;
+ if (!saw_digit) {
+ if (++octets > 4)
+ return 0;
+ saw_digit = 1;
+ }
+ } else if (ch == '.' && saw_digit) {
+ if (octets == 4)
+ return 0;
+ *++tp = 0;
+ saw_digit = 0;
+ } else
+ return 0;
+ }
+ if (octets < 4)
+ return 0;
+ MemCpy(dst, tmp, NS_INADDRSZ);
+ return 1;
+}
+
+U8 *inet_ntop(I64 af, U8 *src, U8 *dst, I64 size) {
+ if (af == AF_INET && size >= INET_ADDRSTRLEN) {
+ StrPrint(dst, "%d.%d.%d.%d", src[0], src[1], src[2], src[3]);
+ return dst;
+ } else {
+ return 0;
+ }
+}
+
+class CSocket {
+ I64 (*accept)(CSocket *s, sockaddr *src_addr, I64 addrlen);
+ I64 (*bind)(CSocket *s, sockaddr *addr, I64 addrlen);
+ I64 (*close)(CSocket *s);
+ I64 (*connect)(CSocket *s, sockaddr *addr, I64 addrlen);
+ I64 (*listen)(CSocket *s, I64 backlog);
+ I64(*recvfrom)
+ (CSocket *s, U8 *buf, I64 len, I64 flags, sockaddr *src_addr, I64 addrlen);
+ I64(*sendto)
+ (CSocket *s, U8 *buf, I64 len, I64 flags, sockaddr *dest_addr, I64 addrlen);
+ I64 (*setsockopt)(CSocket *s, I64 level, I64 optname, U8 *optval, I64 optlen);
+};
+
+class CSocketClass {
+ CSocketClass *next;
+
+ U16 domain;
+ U16 type;
+ U8 padding[4];
+
+ CSocket *(*socket)(U16 domain, U16 type);
+};
+
+class CAddrResolver {
+ // TODO: allow different resolvers for different socket domains
+
+ I64 (*getaddrinfo)(U8 *node, U8 *service, addrinfo *hints, addrinfo **res);
+};
+
+static CSocketClass *socket_classes = NULL;
+static CAddrResolver *socket_addr_resolver = NULL;
+
+static CSocketClass *FindSocketClass(U16 domain, U16 type) {
+ CSocketClass *cls = socket_classes;
+
+ while (cls) {
+ if (cls->domain == domain && cls->type == type)
+ return cls;
+
+ cls = cls->next;
+ }
+
+ return NULL;
+}
+
+I64 SocketInit() { return 0; }
+
+I64 socket(I64 domain, I64 type) {
+ CSocketClass *cls = FindSocketClass(domain, type);
+
+ if (cls)
+ return cls->socket(domain, type)(I64);
+ else
+ return -1;
+}
+
+I64 accept(I64 sockfd, sockaddr *addr, I64 addrlen) {
+ CSocket *sock = sockfd(CSocket *);
+ if (sockfd > 0)
+ return sock->accept(sock, addr, addrlen);
+ else
+ return -1;
+}
+
+I64 close(I64 sockfd) {
+ CSocket *sock = sockfd(CSocket *);
+ if (sockfd > 0)
+ return sock->close(sock);
+ else
+ return -1;
+}
+
+I64 bind(I64 sockfd, sockaddr *addr, I64 addrlen) {
+ CSocket *sock = sockfd(CSocket *);
+ if (sockfd > 0)
+ return sock->bind(sock, addr, addrlen);
+ else
+ return -1;
+}
+
+I64 connect(I64 sockfd, sockaddr *addr, I64 addrlen) {
+ CSocket *sock = sockfd(CSocket *);
+ if (sockfd > 0)
+ return sock->connect(sock, addr, addrlen);
+ else
+ return -1;
+}
+
+I64 listen(I64 sockfd, I64 backlog) {
+ CSocket *sock = sockfd(CSocket *);
+ if (sockfd > 0)
+ return sock->listen(sock, backlog);
+ else
+ return -1;
+}
+
+I64 recv(I64 sockfd, U8 *buf, I64 len, I64 flags) {
+ CSocket *sock = sockfd(CSocket *);
+ if (sockfd > 0)
+ return sock->recvfrom(sock, buf, len, flags, NULL, 0);
+ else
+ return -1;
+}
+
+I64 recvfrom(I64 sockfd, U8 *buf, I64 len, I64 flags, sockaddr *src_addr,
+ I64 addrlen) {
+ CSocket *sock = sockfd(CSocket *);
+ if (sockfd > 0)
+ return sock->recvfrom(sock, buf, len, flags, src_addr, addrlen);
+ else
+ return -1;
+}
+
+I64 send(I64 sockfd, U8 *buf, I64 len, I64 flags) {
+ CSocket *sock = sockfd(CSocket *);
+ if (sockfd > 0)
+ return sock->sendto(sock, buf, len, flags, NULL, 0);
+ else
+ return -1;
+}
+
+I64 sendto(I64 sockfd, U8 *buf, I64 len, I64 flags, sockaddr *dest_addr,
+ I64 addrlen) {
+ CSocket *sock = sockfd(CSocket *);
+ if (sockfd > 0)
+ return sock->sendto(sock, buf, len, flags, dest_addr, addrlen);
+ else
+ return -1;
+}
+
+I64 setsockopt(I64 sockfd, I64 level, I64 optname, U8 *optval, I64 optlen) {
+ CSocket *sock = sockfd(CSocket *);
+ if (sockfd > 0)
+ return sock->setsockopt(sock, level, optname, optval, optlen);
+ else
+ return -1;
+}
+
+I64 getaddrinfo(U8 *node, U8 *service, addrinfo *hints, addrinfo **res) {
+ if (socket_addr_resolver)
+ return socket_addr_resolver->getaddrinfo(node, service, hints, res);
+ else
+ return -1;
+}
+
+U0 freeaddrinfo(addrinfo *res) {
+ while (res) {
+ addrinfo *next = res->ai_next;
+ Free(res->ai_addr);
+ Free(res->ai_canonname);
+ Free(res);
+ res = next;
+ }
+}
+
+U0 AddrInfoCopy(addrinfo *ai_out, addrinfo *ai_in) {
+ MemCpy(ai_out, ai_in, sizeof(addrinfo));
+
+ if (ai_in->ai_addr) {
+ ai_out->ai_addr = MAlloc(ai_in->ai_addrlen);
+ MemCpy(ai_out->ai_addr, ai_in->ai_addr, ai_in->ai_addrlen);
+ }
+
+ if (ai_in->ai_canonname) {
+ ai_out->ai_canonname = StrNew(ai_in->ai_canonname);
+ }
+}
+
+U8 *gai_strerror(I64 errcode) {
+ no_warn errcode;
+ return "Unspecified error";
+}
+
+// Inspired by
+// https://docs.python.org/3.7/library/socket.html#socket.create_connection
+I64 create_connection(U8 *hostname, U16 port) {
+ sockaddr_in addr;
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(port);
+ addr.sin_addr.s_addr = 0;
+
+ addrinfo *res;
+ I64 error = getaddrinfo(hostname, NULL, NULL, &res);
+
+ if (error < 0) {
+ "$FG,4$getaddrinfo: error %d\n$FG$", error;
+ } else {
+ addrinfo *curr = res;
+
+ while (curr) {
+ if (curr->ai_family == AF_INET &&
+ (curr->ai_socktype == 0 || curr->ai_socktype == SOCK_STREAM)) {
+ addr.sin_addr.s_addr = (curr->ai_addr(sockaddr_in *))->sin_addr.s_addr;
+ freeaddrinfo(res);
+
+ I64 sockfd = socket(AF_INET, SOCK_STREAM);
+
+ if (sockfd < 0)
+ return sockfd;
+
+ error = connect(sockfd, &addr, sizeof(addr));
+
+ if (error < 0) {
+ close(sockfd);
+ return error;
+ }
+
+ return sockfd;
+ }
+
+ curr = curr->ai_next;
+ }
+
+ "$FG,4$create_connection: no suitable address\n$FG$";
+ }
+
+ freeaddrinfo(res);
+ return -1;
+}
+
+U0 RegisterSocketClass(U16 domain, U16 type,
+ CSocket *(*socket)(U16 domain, U16 type)) {
+ CSocketClass *cls = MAlloc(sizeof(CSocketClass));
+
+ cls->next = socket_classes;
+ cls->domain = domain;
+ cls->type = type;
+ cls->socket = socket;
+
+ socket_classes = cls;
+}
diff --git a/Net/NetFifo.HC b/Net/NetFifo.HC
new file mode 100644
index 0000000..7d331da
--- /dev/null
+++ b/Net/NetFifo.HC
@@ -0,0 +1,77 @@
+// Warning: terrible code ahead. this still needs a lot of work
+
+// In the future we'll probably have 2 FIFOs (pending frames & empty buffers)
+// TODO: check if FIFO implementation is suitable for high throughput
+
+#define NET_FIFO_DEPTH 1024
+
+#define ETHERNET_FRAME_SIZE 1548
+
+#define ETHERTYPE_IPV4 0x0800
+#define ETHERTYPE_ARP 0x0806
+
+class CNetFifoEntry {
+ I64 length;
+ U8 frame[ETHERNET_FRAME_SIZE];
+};
+
+static CFifoI64 *netfifo;
+
+static CNetFifoEntry *entries;
+static I64 next_entry = 0;
+
+CTask *netfifo_handler_task = NULL;
+
+// TODO: asm optimization? or perhaps use EndianU*?
+// These don't belong here in the first place,
+// but it's convenient for Ethernet drivers
+// We'll probably split it off along with ETHERTYPE_* constants
+
+U16 htons(U16 h) { return ((h >> 8) | (h << 8)) & 0xffff; }
+
+U16 ntohs(U16 h) { return ((h >> 8) | (h << 8)) & 0xffff; }
+
+U32 htonl(U32 h) {
+ return ((h >> 24) | ((h & 0x00ff0000) >> 8) | ((h & 0x0000ff00) << 8) |
+ (h << 24)) &
+ 0xffffffff;
+}
+
+U32 ntohl(U32 h) {
+ return ((h >> 24) | ((h & 0x00ff0000) >> 8) | ((h & 0x0000ff00) << 8) |
+ (h << 24)) &
+ 0xffffffff;
+}
+
+CNetFifoEntry *NetFifoPull() {
+ CNetFifoEntry *entry;
+
+ if (FifoI64Rem(netfifo, &entry))
+ return entry;
+ else
+ return NULL;
+}
+
+I64 NetFifoPushCopy(U8 *data, I64 length) {
+ CNetFifoEntry *entry = &entries[next_entry];
+ next_entry = (next_entry + 1) & (NET_FIFO_DEPTH - 1);
+
+ entry->length = length;
+ MemCpy(entry->frame, data, length);
+
+ if (!FifoI64Ins(netfifo, entry))
+ return -1;
+
+ // Wake up Handler Task
+ if (netfifo_handler_task)
+ LBtr(&netfifo_handler_task->task_flags, TASKf_IDLE);
+
+ return 0;
+}
+
+U0 NetFifoInit() {
+ netfifo = FifoI64New(NET_FIFO_DEPTH);
+ entries = MAlloc(NET_FIFO_DEPTH * sizeof(CNetFifoEntry));
+}
+
+NetFifoInit;
diff --git a/Net/NetHandler.HC b/Net/NetHandler.HC
new file mode 100644
index 0000000..a7c7575
--- /dev/null
+++ b/Net/NetHandler.HC
@@ -0,0 +1,58 @@
+U0 @virtio_net_handle_net_fifo_entry(CNetFifoEntry *e) {
+ CEthFrame l2_frame;
+
+ if (EthernetFrameParse(&l2_frame, e->frame, e->length) < 0)
+ return;
+
+ CL3Protocol *l3 = l3_protocols;
+
+ while (l3) {
+ if (l3->ethertype == l2_frame.ethertype) {
+ l3->handler(&l2_frame);
+ break;
+ }
+ l3 = l3->next;
+ }
+}
+
+U0 @virtio_net_handler_task() {
+ I64 idx_used, idx_rec;
+ I64 i, j;
+ @virtio_used_item *item;
+ U8 *buffer;
+ I64 length;
+ while (1) {
+ idx_rec = VirtioNet.rq_index;
+ idx_used = VirtioNet.rq->used.index;
+
+ if (idx_used < idx_rec) {
+ idx_used += 0x10000;
+ }
+
+ if (idx_rec != idx_used && idx_used) {
+
+ j = 0;
+ for (i = idx_rec; i < idx_used; i++) {
+ item = VirtioNet.rq->used.ring;
+ buffer = VirtioNet.rq->buffers[item[i % 256].index + 1];
+ length = item[i % 256].length;
+ NetFifoPushCopy(buffer, length - 10);
+ j++;
+ VirtioNet.rx_packets++;
+ VirtioNet.rx_bytes += length - 10;
+ }
+ VirtioNet.rq_index = idx_used % 0x10000;
+ VirtioNet.rq->available.index += j;
+ OutU16(VirtioNet.port + VIRTIO_PCI_QUEUE_NOTIFY, 0);
+ }
+ CNetFifoEntry *e = NetFifoPull;
+ if (e) {
+ @virtio_net_handle_net_fifo_entry(e);
+ }
+ Busy(200);
+ }
+}
+
+Spawn(&@virtio_net_handler_task, NULL, "NetHandlerTask", 2);
+
+"[OK] NetHandler \n"; \ No newline at end of file
diff --git a/Net/Netcfg.HC b/Net/Netcfg.HC
new file mode 100644
index 0000000..9c43c68
--- /dev/null
+++ b/Net/Netcfg.HC
@@ -0,0 +1,142 @@
+
+#define CLIENT_START 0
+#define CLIENT_DISCOVER 1
+#define CLIENT_REQUEST 2
+#define CLIENT_REQUEST_ACCEPTED 3
+
+#define DHCP_TIMEOUT 3000
+#define MAX_RETRIES 3
+
+I64 DhcpConfigureInner(I64 sock, U32 *yiaddr_out, U32 *dns_ip_out,
+ U32 *router_ip_out, U32 *subnet_mask_out) {
+ I64 state = CLIENT_START;
+ I64 retries = 0;
+
+ I64 timeout = DHCP_TIMEOUT;
+
+ if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO_MS, &timeout, sizeof(timeout)) <
+ 0) {
+ "$FG,6$DhcpConfigure: setsockopt failed\n$FG$";
+ }
+
+ sockaddr_in addr;
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(68);
+ addr.sin_addr.s_addr = INADDR_ANY;
+
+ if (bind(sock, &addr, sizeof(addr)) < 0) {
+ "$FG,4$DhcpConfigure: failed to bind\n$FG$";
+ return -1;
+ }
+
+ U32 xid = DhcpBeginTransaction();
+
+ I64 error = 0;
+
+ U32 dhcp_addr;
+ U8 buffer[2048];
+
+ I64 count;
+ sockaddr_in addr_in;
+
+ while (state != CLIENT_REQUEST_ACCEPTED) {
+ if (state == CLIENT_START) {
+ state = CLIENT_DISCOVER;
+ retries = 0;
+ } else if (state == CLIENT_DISCOVER) {
+ error = DhcpSendDiscover(xid);
+ if (error < 0)
+ return error;
+
+ count =
+ recvfrom(sock, buffer, sizeof(buffer), 0, &addr_in, sizeof(addr_in));
+
+ if (count > 0) {
+ //"Try parse Offer\n";
+ error = DhcpParseOffer(xid, buffer, count, yiaddr_out, dns_ip_out,
+ router_ip_out, subnet_mask_out);
+
+ if (error < 0) {
+ "$FG,6$DhcpParseOffer1: error %d\n$FG$", error;
+ }
+ }
+
+ if (count > 0 && error >= 0) {
+ dhcp_addr = ntohl(addr_in.sin_addr.s_addr);
+ //"DHCP Offer from %08X: YIAddr %08X,\n\tDNS %08X, Router %08X, Subnet
+ //%08X\n",
+ // dhcp_addr, *yiaddr_out, dns_ip, router_ip, subnet_mask;
+
+ state = CLIENT_REQUEST;
+ retries = 0;
+ } else if (++retries == MAX_RETRIES) {
+ "$FG,4$DhcpConfigure: max retries for DISCOVER\n$FG$";
+ return -1;
+ }
+ } else if (state == CLIENT_REQUEST) {
+ error = DhcpSendRequest(xid, *yiaddr_out, dhcp_addr);
+ if (error < 0)
+ return error;
+
+ count =
+ recvfrom(sock, buffer, sizeof(buffer), 0, &addr_in, sizeof(addr_in));
+
+ if (count > 0) {
+ //"Try parse Ack\n";
+ error = DhcpParseAck(xid, buffer, count);
+
+ if (error < 0) {
+ "$FG,6$DhcpParseOffer: error %d\n$FG$", error;
+ }
+ }
+
+ if (count > 0 && error >= 0) {
+ dhcp_addr = ntohl(addr_in.sin_addr.s_addr);
+ //"DHCP Ack from %08X\n", dhcp_addr;
+
+ state = CLIENT_REQUEST_ACCEPTED;
+ } else if (++retries == MAX_RETRIES) {
+ "$FG,4$DhcpConfigure: max retries for REQUEST\n$FG$";
+ return -1;
+ }
+ }
+ }
+
+ return state;
+}
+
+I64 DhcpConfigure() {
+ I64 sock = socket(AF_INET, SOCK_DGRAM);
+
+ if (sock < 0)
+ return -1;
+
+ U32 yiaddr, dns_ip, router_ip, subnet_mask;
+ I64 state =
+ DhcpConfigureInner(sock, &yiaddr, &dns_ip, &router_ip, &subnet_mask);
+
+ close(sock);
+
+ if (state == CLIENT_REQUEST_ACCEPTED) {
+ in_addr in;
+ in.s_addr = htonl(yiaddr);
+ U8 buffer[INET_ADDRSTRLEN];
+ "$FG,2$Obtained IP address %s\n$FG$",
+ inet_ntop(AF_INET, &in.s_addr, buffer, sizeof(buffer));
+ IPv4SetAddress(yiaddr);
+ IPv4SetSubnet(router_ip, subnet_mask);
+ DnsSetResolverIPv4(dns_ip);
+ return 0;
+ } else
+ return -1;
+}
+
+U0 Netcfg() {
+ SocketInit();
+
+ "$FG,7$Netcfg: Configuring network...\n$FG$";
+
+ I64 error = DhcpConfigure();
+ if (error < 0)
+ "$FG,4$DhcpConfigure: error %d\n$FG$", error;
+}
diff --git a/Net/Socket.HC b/Net/Socket.HC
new file mode 100644
index 0000000..aa23a05
--- /dev/null
+++ b/Net/Socket.HC
@@ -0,0 +1,43 @@
+#exe {
+if (SNAILNET_NATIVE_DRIVER == NULL) {
+ StreamPrint("#include \"::/Adam/Net/SnailLib\"");
+}
+}
+
+// Higher-level, utility functions
+
+I64 recvLine(I64 sock, U8 *buffer, I64 size, I64 flags) {
+ I64 got = 0;
+ while (got + 1 < size) {
+ if (!recv(sock, buffer + got, 1, flags))
+ return -1;
+
+ if (buffer[got] == '\n')
+ break;
+ else if (buffer[got] != '\r')
+ got++;
+ }
+ // FIXME: safe but incorrect behavior on overflow
+ buffer[got] = 0;
+ return got;
+}
+
+I64 sendall(I64 sockfd, U8 *buf, I64 len, I64 flags) {
+ I64 total = 0;
+
+ while (len) {
+ I64 sent = send(sockfd, buf, len, flags);
+ if (sent > 0) {
+ buf += sent;
+ total += sent;
+ len -= sent;
+ } else
+ break;
+ }
+
+ return total;
+}
+
+I64 sendString(I64 sockfd, U8 *str, I64 flags) {
+ return sendall(sockfd, str, StrLen(str), flags);
+}
diff --git a/Net/Tcp.HC b/Net/Tcp.HC
new file mode 100644
index 0000000..9378f6f
--- /dev/null
+++ b/Net/Tcp.HC
@@ -0,0 +1,1108 @@
+// https://tools.ietf.org/html/rfc793
+
+// See https://en.wikipedia.org/wiki/File:Tcp_state_diagram_fixed_new.svg
+#define TCP_STATE_CLOSED 0
+#define TCP_STATE_LISTEN 1
+#define TCP_STATE_SYN_SENT 2
+#define TCP_STATE_SYN_RECEIVED 3
+#define TCP_STATE_ESTABLISHED 4
+#define TCP_STATE_FIN_WAIT_1 5
+#define TCP_STATE_FIN_WAIT_2 6
+#define TCP_STATE_CLOSE_WAIT 7
+#define TCP_STATE_CLOSING 8
+#define TCP_STATE_LAST_ACK 9
+#define TCP_STATE_TIME_WAIT 10
+
+#define TCP_CONNECT_TIMEOUT 10000
+
+//#define TCP_DEFAULT_MSS 536
+
+#define TCP_DEFAULT_MSS 1500
+
+#define TCP_WINDOW_SIZE 65536
+
+#define TCP_FLAG_FIN 0x01
+#define TCP_FLAG_SYN 0x02
+#define TCP_FLAG_RST 0x04
+#define TCP_FLAG_PSH 0x08
+#define TCP_FLAG_ACK 0x10
+#define TCP_FLAG_URG 0x20
+
+#define TCP_SRTT_ALPHA 0.9
+#define TCP_RTO_MIN 0.2
+#define TCP_RTO_MAX 10000
+#define TCP_RTO_BETA 2
+
+class CTcpHeader {
+ U16 source_port;
+ U16 dest_port;
+ U32 seq;
+ U32 ack;
+ U8 data_offset;
+ U8 flags;
+ U16 window_size;
+ U16 checksum;
+ U16 urgent_pointer;
+};
+
+class CTcpSendBufHeader {
+ CTcpSendBufHeader *next;
+
+ F64 time_sent;
+ U32 length;
+ U32 retries;
+ U32 seq_start;
+ U32 seq_end;
+};
+
+class CTcpSocket {
+ CSocket sock;
+
+ I64 state;
+
+ U32 local_addr;
+ U16 local_port;
+
+ U32 remote_addr;
+ U32 remote_port;
+
+ U32 snd_una; // seq number of first unacknowledged octet
+ U32 snd_nxt; // seq number of next octet to send
+ U32 snd_wnd; // allowed number of unacknowledged outgoing octets
+ U32 mss; // maximum segment size
+
+ U32 rcv_nxt; // seq number of next octet to receive
+ U32 rcv_wnd; // allowed number of unacknowledged incoming octets
+
+ F64 conntime;
+ F64 srtt;
+
+ I64 recv_buf_size;
+ U8 *recv_buf;
+ I64 recv_buf_read_pos;
+ I64 recv_buf_write_pos;
+
+ CTcpSocket *backlog_next;
+ CTcpSocket *backlog_first;
+ CTcpSocket *backlog_last;
+ I64 backlog_remaining;
+
+ CTcpSendBufHeader *send_buf_first;
+ CTcpSendBufHeader *send_buf_last;
+
+ // I64 rcvtimeo_ms;
+ // I64 recv_maxtime;
+};
+
+class CTcpPseudoHeader {
+ U32 source_addr;
+ U32 dest_addr;
+ U8 zeros;
+ U8 protocol;
+ U16 tcp_length;
+};
+
+class CTcpSocketListItem {
+ CTcpSocketListItem *prev;
+ CTcpSocketListItem *next;
+ CTcpSocket *sock;
+};
+
+static CTcpSocketListItem **tcp_socket_list;
+
+static CTcpSocket *GetTcpSocketFromList(CIPv4Packet *packet, CTcpHeader *hdr) {
+ CTcpSocketListItem *item = tcp_socket_list[ntohs(hdr->dest_port)]->next;
+ while (item) {
+ if (item->sock->remote_addr == packet->source_ip &&
+ item->sock->remote_port == ntohs(hdr->source_port)) {
+ return item->sock;
+ }
+ item = item->next;
+ }
+ return NULL;
+}
+
+U0 AddTcpSocketToList(CTcpSocket *s) {
+ CTcpSocketListItem *prev = tcp_socket_list[s->local_port];
+ CTcpSocketListItem *new = CAlloc(sizeof(CTcpSocketListItem));
+ while (prev->next) {
+ prev = prev->next;
+ }
+ new->prev = prev;
+ new->sock = s;
+ prev->next = new;
+}
+
+CTcpSocket *RemoveTcpSocketFromList(CTcpSocket *s) {
+ CTcpSocketListItem *prev = NULL;
+ CTcpSocketListItem *next = NULL;
+ CTcpSocketListItem *item = tcp_socket_list[s->local_port]->next;
+ while (item) {
+ if (item->sock == s) {
+ prev = item->prev;
+ next = item->next;
+ if (prev) {
+ prev->next = next;
+ }
+ if (next) {
+ next->prev = prev;
+ }
+ return s;
+ }
+ item = item->next;
+ }
+ return NULL;
+}
+
+// TODO: this takes up half a meg, change it to a binary tree or something
+static CTcpSocket **tcp_bound_sockets;
+
+static U16 tcp_next_source_port = RandU16();
+
+static Bool TcpIsSynchronizedState(I64 state) {
+ return state == TCP_STATE_ESTABLISHED || state == TCP_STATE_FIN_WAIT_1 ||
+ state == TCP_STATE_FIN_WAIT_2 || state == TCP_STATE_CLOSE_WAIT ||
+ state == TCP_STATE_CLOSING || state == TCP_STATE_LAST_ACK ||
+ state == TCP_STATE_TIME_WAIT;
+}
+
+static U16 TcpPartialChecksum(U32 sum, U8 *header, I64 length) {
+ I64 nleft = length;
+ U16 *w = header;
+
+ while (nleft > 1) {
+ sum += *(w++);
+ nleft -= 2;
+ }
+
+ return sum;
+}
+
+static U16 TcpFinalChecksum(U32 sum, U8 *header, I64 length) {
+ I64 nleft = length;
+ U16 *w = header;
+
+ while (nleft > 1) {
+ sum += *(w++);
+ nleft -= 2;
+ }
+
+ // mop up an odd byte, if necessary
+ if (nleft == 1) {
+ sum += ((*w) & 0x00ff);
+ }
+
+ // add back carry outs from top 16 bits to low 16 bits
+ sum = (sum >> 16) + (sum & 0xffff); // add hi 16 to low 16
+ sum += (sum >> 16); // add carry
+ return (~sum) & 0xffff;
+}
+
+I64 TcpPacketAlloc(U8 **frame_out, U32 source_ip, U16 source_port, U32 dest_ip,
+ U16 dest_port, U32 seq, U32 ack, U8 flags, I64 length) {
+ U8 *frame;
+ I64 index = IPv4PacketAlloc(&frame, IP_PROTO_TCP, source_ip, dest_ip,
+ sizeof(CTcpHeader) + length);
+
+ if (index < 0)
+ return index;
+
+ CTcpHeader *hdr = frame;
+ hdr->source_port = htons(source_port);
+ hdr->dest_port = htons(dest_port);
+ hdr->seq = htonl(seq);
+ hdr->ack = htonl(ack);
+ hdr->data_offset = (sizeof(CTcpHeader) / 4) << 4;
+ hdr->flags = flags;
+ hdr->window_size = htons(TCP_WINDOW_SIZE / 2); // FIXME
+ hdr->checksum = 0;
+ hdr->urgent_pointer = 0;
+
+ *frame_out = frame + sizeof(CTcpHeader);
+ return index;
+}
+
+I64 TcpPacketFinish(I64 index, U32 source_ip, U32 dest_ip, U8 *frame,
+ I64 length, CTcpSendBufHeader **send_buf_out) {
+ CTcpHeader *hdr = frame - sizeof(CTcpHeader);
+
+ CTcpPseudoHeader pseudo;
+ pseudo.source_addr = htonl(source_ip);
+ pseudo.dest_addr = htonl(dest_ip);
+ pseudo.zeros = 0;
+ pseudo.protocol = IP_PROTO_TCP;
+ pseudo.tcp_length = htons(sizeof(CTcpHeader) + length);
+
+ U32 sum = TcpPartialChecksum(0, &pseudo, sizeof(CTcpPseudoHeader));
+ hdr->checksum = TcpFinalChecksum(sum, hdr, sizeof(CTcpHeader) + length);
+
+ if (send_buf_out) {
+ CTcpSendBufHeader *sb =
+ MAlloc(sizeof(CTcpSendBufHeader) + sizeof(CTcpHeader) + length);
+ sb->next = NULL;
+ sb->time_sent = tS;
+ sb->length = sizeof(CTcpHeader) + length;
+ sb->retries = 0;
+ sb->seq_start = ntohl(hdr->seq);
+ sb->seq_end = 0; // NEEDS TO BE SET UPSTREAM
+
+ MemCpy((sb(U8 *)) + sizeof(CTcpSendBufHeader), frame,
+ sizeof(CTcpHeader) + length);
+ *send_buf_out = sb;
+ }
+
+ return IPv4PacketFinish(index);
+}
+
+// Send a TCP frame with flags and/or data
+I64 TcpSend(U32 local_addr, U16 local_port, U32 remote_addr, U16 remote_port,
+ U32 seq, U32 ack, U8 flags) {
+ U8 *frame;
+ I64 index = TcpPacketAlloc(&frame, local_addr, local_port, remote_addr,
+ remote_port, seq, ack, flags, 0);
+
+ if (index < 0)
+ return index;
+
+ return TcpPacketFinish(index, local_addr, remote_addr, frame, 0, NULL);
+}
+
+// Send a TCP frame with flags only, no data
+I64 TcpSend2(CTcpSocket *s, U8 flags) {
+ U8 *frame;
+ I64 index =
+ TcpPacketAlloc(&frame, s->local_addr, s->local_port, s->remote_addr,
+ s->remote_port, s->snd_nxt, s->rcv_nxt, flags, 0);
+
+ if (index < 0)
+ return index;
+
+ if (flags & TCP_FLAG_SYN)
+ s->snd_nxt++;
+
+ if (flags & TCP_FLAG_FIN)
+ s->snd_nxt++;
+
+ //"Sent #%d, to %08X, err = %d\n", s->seq, s->remote_addr, error;
+ if (flags & (TCP_FLAG_SYN | TCP_FLAG_FIN)) {
+ CTcpSendBufHeader *sb;
+ TcpPacketFinish(index, s->local_addr, s->remote_addr, frame, 0, &sb);
+ sb->seq_end = s->snd_nxt;
+
+ // Append to SendBuf chain
+ if (s->send_buf_first)
+ s->send_buf_last->next = sb;
+ else
+ s->send_buf_first = sb;
+
+ s->send_buf_last = sb;
+ } else {
+ return TcpPacketFinish(index, s->local_addr, s->remote_addr, frame, 0,
+ NULL);
+ }
+}
+
+// Send a TCP frame with flags and data
+I64 TcpSendData2(CTcpSocket *s, U8 flags, U8 *data, I64 length) {
+ U8 *frame;
+ I64 index =
+ TcpPacketAlloc(&frame, s->local_addr, s->local_port, s->remote_addr,
+ s->remote_port, s->snd_nxt, s->rcv_nxt, flags, length);
+
+ if (index < 0)
+ return index;
+
+ if (length)
+ MemCpy(frame, data, length);
+
+ if (flags & TCP_FLAG_SYN)
+ s->snd_nxt++;
+
+ s->snd_nxt += length;
+
+ if (flags & TCP_FLAG_FIN)
+ s->snd_nxt++;
+
+ //"Sent #%d, to %08X, err = %d\n", s->seq, s->remote_addr, error;
+
+ CTcpSendBufHeader *sb;
+ TcpPacketFinish(index, s->local_addr, s->remote_addr, frame, length, &sb);
+ sb->seq_end = s->snd_nxt;
+
+ // Append to SendBuf chain
+ if (s->send_buf_first)
+ s->send_buf_last->next = sb;
+ else
+ s->send_buf_first = sb;
+
+ s->send_buf_last = sb;
+}
+
+I64 TcpParsePacket(CTcpHeader **header_out, U8 **data_out, I64 *length_out,
+ CIPv4Packet *packet) {
+ if (packet->proto != IP_PROTO_TCP)
+ return -1;
+
+ // FIXME: validate packet->length
+ // FIXME: checksum
+
+ CTcpHeader *hdr = packet->data;
+ I64 header_length = (hdr->data_offset >> 4) * 4;
+
+ //"TCP: in hdr %d, flags %02Xh, seq %d, ack %d, len %d, chksum %d\n",
+ // header_length, hdr->flags, ntohl(hdr->seq), ntohl(hdr->ack),
+ // packet->length - header_length, ntohs(hdr->checksum);
+
+ *header_out = hdr;
+ *data_out = packet->data + header_length;
+ *length_out = packet->length - header_length;
+ return 0;
+}
+
+/*
+class CTcpSendBufHeader {
+ CTcpSendBufHeader* next;
+
+ F64 time_sent;
+ U32 length;
+ U32 retries;
+ U32 seq_start;
+ U32 seq_end;
+};
+*/
+
+static U0 TcpSocketAckSendBufs(CTcpSocket *s, U32 seg_ack) {
+ F64 time = tS;
+
+ while (s->send_buf_first) {
+ CTcpSendBufHeader *sb = s->send_buf_first;
+
+ // There's no notion of smaller/greater than in modular arithemtic,
+ // we can only check if a number lies within some range.
+ // Here we check that
+ // sb->seq_end <= seg_ack <= s->snd_nxt
+ // because that will work for all meaningful ACKs.
+ I64 seg_ack_rel = (seg_ack - sb->seq_end) & 0xffffffff;
+ I64 snd_nxt_rel = (s->snd_nxt - sb->seq_end) & 0xffffffff;
+
+ if (seg_ack_rel <= snd_nxt_rel) {
+ // Update smoothed RTT
+ F64 rtt = time - sb->time_sent;
+ s->srtt = (s->srtt * TCP_SRTT_ALPHA) + ((1.0 - TCP_SRTT_ALPHA) * rtt);
+ //"ACK'd %d->%d (RTT %f ms)", sb->seq_start, sb->seq_end, rtt * 1000;
+
+ // Remove SendBuf from chain
+ s->send_buf_first = sb->next;
+
+ if (s->send_buf_first == NULL)
+ s->send_buf_last = NULL;
+
+ Free(sb);
+ } else
+ break;
+ }
+}
+
+// Check unacknowledged outgoing packets and retransmit if needed
+static U0 TcpSocketCheckSendBufs(CTcpSocket *s) {
+ F64 time = tS;
+
+ F64 rto = TCP_RTO_BETA * s->srtt;
+
+ if (rto < TCP_RTO_MIN)
+ rto = TCP_RTO_MIN;
+ if (rto > TCP_RTO_MAX)
+ rto = TCP_RTO_MAX;
+
+ while (s->send_buf_first) {
+ CTcpSendBufHeader *sb = s->send_buf_first;
+
+ if (time > sb->time_sent + rto) {
+ break;
+
+ // Retransmit
+ "Retransmit %d->%d (%f ms)!\n", sb->seq_start, sb->seq_end,
+ (time - sb->time_sent) * 1000;
+
+ U8 *frame;
+ I64 index = IPv4PacketAlloc(&frame, IP_PROTO_TCP, s->local_addr,
+ s->remote_addr, sb->length);
+
+ if (index < 0) {
+ return; // retry later I guess
+ }
+
+ MemCpy(frame, (sb(U8 *)) + sizeof(CTcpSendBufHeader), sb->length);
+ IPv4PacketFinish(index);
+
+ sb->time_sent = tS;
+
+ // Move to the end of the chain
+ s->send_buf_first = sb->next;
+ sb->next = NULL;
+
+ if (s->send_buf_first)
+ s->send_buf_last->next = sb;
+ else
+ s->send_buf_first = sb;
+
+ s->send_buf_last = sb;
+ } else
+ break;
+ }
+}
+
+I64 TcpSocketAccept(CTcpSocket *s, sockaddr *addr, I64 addrlen) {
+ if (s->state != TCP_STATE_LISTEN)
+ return -1;
+
+ while (1) {
+ // TODO: Thread safe?
+ if (s->backlog_first) {
+ CTcpSocket *new_socket = s->backlog_first;
+ // "Retr %p\n", new_socket;
+
+ s->backlog_first = s->backlog_first->backlog_next;
+ if (!s->backlog_first)
+ s->backlog_last = NULL;
+
+ s->backlog_remaining++;
+
+ // TODO: this should be done in a way that doesn't block on accept()
+ I64 maxtime = cnts.jiffies + TCP_CONNECT_TIMEOUT * JIFFY_FREQ / 1000;
+
+ while (cnts.jiffies < maxtime) {
+ if (new_socket->state == TCP_STATE_ESTABLISHED ||
+ new_socket->state == TCP_STATE_CLOSED)
+ break;
+ else
+ Yield;
+ }
+
+ if (new_socket->state != TCP_STATE_ESTABLISHED) {
+ close(new_socket);
+ return -1;
+ }
+
+ return new_socket;
+ } else
+ Yield;
+ }
+
+ no_warn addr; // FIXME
+ no_warn addrlen;
+ return -1;
+}
+
+I64 TcpSocketBind(CTcpSocket *s, sockaddr *addr, I64 addrlen) {
+ if (addrlen < sizeof(sockaddr_in))
+ return -1;
+
+ if (s->state != TCP_STATE_CLOSED)
+ return -1;
+
+ sockaddr_in *addr_in = addr;
+
+ U16 local_port = ntohs(addr_in->sin_port);
+
+ // TODO: address & stuff
+ if (tcp_bound_sockets[local_port] != NULL)
+ return -1;
+
+ tcp_bound_sockets[local_port] = s;
+
+ s->local_addr = IPv4GetAddress();
+ s->local_port = local_port;
+
+ return 0;
+}
+
+I64 TcpSocketClose(CTcpSocket *s) {
+ /* https://tools.ietf.org/html/rfc793#section-3.5
+ Case 1: Local user initiates the close
+
+ In this case, a FIN segment can be constructed and placed on the
+ outgoing segment queue. No further SENDs from the user will be
+ accepted by the TCP, and it enters the FIN-WAIT-1 state. RECEIVEs
+ are allowed in this state. All segments preceding and including FIN
+ will be retransmitted until acknowledged. When the other TCP has
+ both acknowledged the FIN and sent a FIN of its own, the first TCP
+ can ACK this FIN. Note that a TCP receiving a FIN will ACK but not
+ send its own FIN until its user has CLOSED the connection also.
+ */
+
+ // Send FIN & wait for acknowledge
+ if (s->state == TCP_STATE_ESTABLISHED) {
+ while (TcpSend2(s, TCP_FLAG_FIN | TCP_FLAG_ACK) < 0) {
+ TcpSocketCheckSendBufs(s);
+ Yield;
+ }
+
+ s->state = TCP_STATE_FIN_WAIT_1;
+ // "FIN-WAIT-1\n";
+
+ // Block until all outgoing data including our FIN have been acknowledged
+ // (una == nxt)
+ //
+ // TODO: what other states are permissible here?
+ // TODO: this can block for ever if our receive buffer fills up, but the
+ // other side
+ // insists on pushing more data before closing the connection
+ while ((s->state == TCP_STATE_FIN_WAIT_1) && s->snd_una != s->snd_nxt) {
+ TcpSocketCheckSendBufs(s);
+ Yield;
+ }
+
+ if (s->state == TCP_STATE_FIN_WAIT_1) {
+ s->state = TCP_STATE_FIN_WAIT_2;
+ // "FIN-WAIT-2 (%d/%d)\n", s->snd_una, s->snd_nxt;
+ }
+
+ // Now we should wait for the other side's FIN and acknowledge it
+ // TODO: time-out
+ while (s->state == TCP_STATE_FIN_WAIT_2) {
+ Yield;
+ }
+ } else if (s->state == TCP_STATE_CLOSE_WAIT) {
+ while (TcpSend2(s, TCP_FLAG_FIN | TCP_FLAG_ACK) < 0) {
+ TcpSocketCheckSendBufs(s);
+ Yield;
+ }
+
+ if (s->state == TCP_STATE_CLOSE_WAIT) {
+ s->state = TCP_STATE_LAST_ACK;
+ // "LAST-ACK (%d/%d)\n", s->snd_una, s->snd_nxt;
+ }
+
+ // Block until all outgoing data including our FIN have been acknowledged
+ // (una == nxt)
+ while (s->state == TCP_STATE_LAST_ACK && s->snd_una != s->snd_nxt) {
+ TcpSocketCheckSendBufs(s);
+ Yield;
+ }
+ }
+
+ // Still connected? RST it!
+ if (TcpIsSynchronizedState(s->state)) {
+ TcpSend2(s, TCP_FLAG_RST);
+ }
+
+ // Free backlog
+ CTcpSocket *backlog = s->backlog_first;
+ CTcpSocket *backlog2;
+
+ while (backlog) {
+ backlog2 = backlog->backlog_next;
+ close(backlog);
+ backlog = backlog2;
+ }
+
+ if (s->local_port)
+ if (!RemoveTcpSocketFromList(s))
+ tcp_bound_sockets[s->local_port] = NULL;
+
+ Free(s->recv_buf);
+ Free(s);
+ return 0;
+}
+
+I64 TcpSocketConnect(CTcpSocket *s, sockaddr *addr, I64 addrlen) {
+ if (addrlen < sizeof(sockaddr_in))
+ return -1;
+
+ if (s->state != TCP_STATE_CLOSED)
+ return -1;
+
+ sockaddr_in *addr_in = addr;
+
+ U16 local_port = 0x8000 + (tcp_next_source_port & 0x7fff);
+ tcp_next_source_port++;
+
+ // TODO: address & stuff
+ if (tcp_bound_sockets[local_port] != NULL)
+ return -1;
+
+ tcp_bound_sockets[local_port] = s;
+
+ s->local_addr = IPv4GetAddress();
+ s->local_port = local_port;
+ s->remote_addr = ntohl(addr_in->sin_addr.s_addr);
+ s->remote_port = ntohs(addr_in->sin_port);
+
+ s->snd_una = 0;
+ s->snd_nxt = 0;
+ s->snd_wnd = 0;
+ s->mss = TCP_DEFAULT_MSS;
+
+ s->rcv_nxt = 0;
+ s->rcv_wnd = TCP_WINDOW_SIZE;
+
+ s->conntime = tS;
+
+ TcpSend2(s, TCP_FLAG_SYN);
+ s->state = TCP_STATE_SYN_SENT;
+
+ // TODO: TcpSetTimeout
+ I64 maxtime = cnts.jiffies + TCP_CONNECT_TIMEOUT * JIFFY_FREQ / 1000;
+
+ while (cnts.jiffies < maxtime) {
+ if (s->state == TCP_STATE_ESTABLISHED || s->state == TCP_STATE_CLOSED)
+ break;
+ else
+ Yield;
+ }
+
+ if (s->state != TCP_STATE_ESTABLISHED)
+ return -1;
+
+ return 0;
+}
+
+I64 TcpSocketListen(CTcpSocket *s, I64 backlog) {
+ if (s->state != TCP_STATE_CLOSED)
+ return -1;
+
+ // Enter listen state. If a SYN packet arrives, it will be processed by
+ // TcpHandler, which opens the connection and puts the new socket into the
+ // listening socket's accept backlog.
+ s->state = TCP_STATE_LISTEN;
+ s->backlog_remaining = backlog;
+
+ return 0;
+}
+
+I64 TcpSocketRecvfrom(CTcpSocket *s, U8 *buf, I64 len, I64 flags,
+ sockaddr *src_addr, I64 addrlen) {
+ no_warn flags;
+ no_warn src_addr; // FIXME
+ no_warn addrlen;
+ //"TcpSocketRecvfrom\n";
+ // If we are ready to receive data, but there is none currently, block until
+ // we receive is some.
+ // TODO: checking for FIN-WAIT-1 here is not so useful, since it only exists
+ // while we are in Close()
+ while (
+ (s->state == TCP_STATE_ESTABLISHED || s->state == TCP_STATE_FIN_WAIT_1) &&
+ s->recv_buf_read_pos == s->recv_buf_write_pos) {
+ TcpSocketCheckSendBufs(s);
+ Yield;
+ }
+
+ // TODO: this works for now, but we should be still able to receive data
+ // in connection-closing states
+ if (((s->state != TCP_STATE_ESTABLISHED ||
+ s->state == TCP_STATE_FIN_WAIT_1) &&
+ s->recv_buf_read_pos == s->recv_buf_write_pos) ||
+ len == 0)
+ return 0;
+
+ I64 read_pos = s->recv_buf_read_pos;
+ I64 write_pos = s->recv_buf_write_pos;
+
+ // I64 avail = (write_pos - read_pos) & (s->recv_buf_size);
+ I64 read_total = 0;
+ I64 step;
+
+ if (write_pos < read_pos) {
+ // We can read up to the end of the buffer
+ step = s->recv_buf_size - read_pos;
+
+ if (step > len)
+ step = len;
+
+ //"Read %d from %d..end\n", step, read_pos;
+ MemCpy(buf, s->recv_buf + read_pos, step);
+ buf += step;
+ len -= step;
+ read_pos = (read_pos + step) & (s->recv_buf_size - 1);
+ read_total += step;
+
+ // at this point, (len == 0 || read_pos == 0) must be true
+ }
+
+ if (len) {
+ step = write_pos - read_pos;
+
+ if (step > len)
+ step = len;
+
+ //"Read %d from start+%d..\n", step, read_pos;
+ MemCpy(buf, s->recv_buf + read_pos, step);
+ buf += step;
+ len -= step;
+ read_pos += step;
+ read_total += step;
+ }
+
+ s->recv_buf_read_pos = read_pos;
+ return read_total;
+}
+
+// This function blocks until at least some data is sent.
+// Then it returns if the transmission window or outgoing buffers are full.
+I64 TcpSocketSendto(CTcpSocket *s, U8 *buf, I64 len, I64 flags,
+ sockaddr_in *dest_addr, I64 addrlen) {
+ no_warn dest_addr; // TODO: should be validated instead, no?
+ no_warn addrlen;
+ no_warn flags;
+
+ I64 sent_total = 0;
+
+ while (
+ (s->state == TCP_STATE_ESTABLISHED || s->state == TCP_STATE_CLOSE_WAIT) &&
+ len) {
+ I64 can_send = (s->snd_una + s->snd_wnd - s->snd_nxt) & 0xffffffff;
+
+ // TODO: Keep trying
+ // Must be tied to a timeout; see RFC793/Managing-the-Window
+ // if (s->snd_wnd == 0)
+ // can_send = 1;
+
+ if (can_send == 0) {
+ if (sent_total > 0)
+ break;
+ else {
+ // Check unacknowledged outgoing packets, re-transmit as needed
+ TcpSocketCheckSendBufs(s);
+ Yield;
+ }
+ } else {
+ if (can_send > len)
+ can_send = len;
+
+ if (can_send > s->mss)
+ can_send = s->mss;
+
+ if (TcpSendData2(s, TCP_FLAG_ACK, buf, can_send) < 0) {
+ // No out-buffers available! Handle in the same way as full window:
+ // stall until some of the outdoing data is acknowledged.
+ if (sent_total > 0)
+ break;
+ else {
+ // Check unacknowledged outgoing packets, re-transmit as needed
+ TcpSocketCheckSendBufs(s);
+ Yield;
+ }
+ } else {
+ buf += can_send;
+ len -= can_send;
+ }
+ }
+ }
+
+ return sent_total;
+}
+
+I64 TcpSocketSetsockopt(CTcpSocket *s, I64 level, I64 optname, U8 *optval,
+ I64 optlen) {
+ /*if (level == SOL_SOCKET && optname == SO_RCVTIMEO_MS && optlen == 8) {
+ s->rcvtimeo_ms = *(optval(I64*));
+ return 0;
+ }*/
+
+ no_warn s;
+ no_warn level;
+ no_warn optname;
+ no_warn optval;
+ no_warn optlen;
+
+ return -1;
+}
+
+CTcpSocket *TcpSocket(U16 domain, U16 type) {
+ if (domain != AF_INET || type != SOCK_STREAM)
+ return NULL;
+
+ CTcpSocket *s = MAlloc(sizeof(CTcpSocket));
+ s->sock.accept = &TcpSocketAccept;
+ s->sock.bind = &TcpSocketBind;
+ s->sock.close = &TcpSocketClose;
+ s->sock.connect = &TcpSocketConnect;
+ s->sock.listen = &TcpSocketListen;
+ s->sock.recvfrom = &TcpSocketRecvfrom;
+ s->sock.sendto = &TcpSocketSendto;
+ s->sock.setsockopt = &TcpSocketSetsockopt;
+
+ s->state = TCP_STATE_CLOSED;
+
+ s->send_buf_first = NULL;
+ s->send_buf_last = NULL;
+
+ s->recv_buf_size = TCP_WINDOW_SIZE;
+ s->recv_buf = MAlloc(s->recv_buf_size);
+ s->recv_buf_read_pos = 0;
+ s->recv_buf_write_pos = 0;
+
+ s->backlog_next = NULL;
+ s->backlog_first = NULL;
+ s->backlog_last = NULL;
+ s->backlog_remaining = 0;
+
+ /*s->rcvtimeo_ms = 0;
+ s->recv_maxtime = 0;
+
+ s->recv_buf = NULL;
+ s->recv_len = 0;
+ s->recv_addr.sin_family = AF_INET;
+ s->bound_to = 0;*/
+ return s;
+}
+
+U0 TcpSocketHandle(CTcpSocket *s, CIPv4Packet *packet, CTcpHeader *hdr,
+ U8 *data, I64 length) {
+ U32 seg_len = length;
+
+ if (hdr->flags & TCP_FLAG_FIN)
+ seg_len++;
+ if (hdr->flags & TCP_FLAG_SYN)
+ seg_len++;
+
+ U32 seg_seq = ntohl(hdr->seq);
+
+ if (s->state == TCP_STATE_LISTEN) {
+ // A new connection is being opened.
+
+ if ((hdr->flags & TCP_FLAG_SYN) && s->backlog_remaining > 0) {
+ //"SYN in from %08X:%d => %08X:%d.\n", packet->source_ip,
+ // ntohs(hdr->source_port),
+ // packet->dest_ip, ntohs(hdr->dest_port);
+ CTcpSocket *new_socket = TcpSocket(AF_INET, SOCK_STREAM);
+
+ new_socket->local_addr = IPv4GetAddress();
+ new_socket->local_port = s->local_port;
+ new_socket->remote_addr = packet->source_ip;
+ new_socket->remote_port = ntohs(hdr->source_port);
+
+ new_socket->snd_una = 0;
+ new_socket->snd_nxt = 0;
+ new_socket->snd_wnd = 0;
+ new_socket->mss = TCP_DEFAULT_MSS;
+
+ new_socket->rcv_nxt = ++seg_seq;
+ new_socket->rcv_wnd = TCP_WINDOW_SIZE;
+
+ new_socket->conntime = tS;
+
+ TcpSend2(new_socket, TCP_FLAG_SYN | TCP_FLAG_ACK);
+ new_socket->state = TCP_STATE_SYN_RECEIVED;
+
+ AddTcpSocketToList(new_socket);
+
+ if (s->backlog_last)
+ s->backlog_last->backlog_next = new_socket;
+ else
+ s->backlog_first = new_socket;
+
+ s->backlog_last = new_socket;
+ s->backlog_remaining--;
+ } else {
+ //"REJ %08X:%d (as %08X:%d)\n", packet->source_ip,
+ // ntohs(hdr->source_port),
+ // packet->dest_ip, ntohs(hdr->dest_port);
+ TcpSend(packet->dest_ip, ntohs(hdr->dest_port), packet->source_ip,
+ ntohs(hdr->source_port), seg_seq + 1, seg_seq + 1,
+ TCP_FLAG_ACK | TCP_FLAG_RST);
+ }
+
+ return;
+ }
+
+ if (s->state == TCP_STATE_CLOSED)
+ return;
+
+ Bool must_ack = FALSE;
+
+ // Process SYN
+ if (hdr->flags & TCP_FLAG_SYN) {
+ s->rcv_nxt = ++seg_seq;
+ //"Reset ACK to %d\n", s->ack;
+
+ must_ack = TRUE;
+ }
+
+ // Validate SEQ
+ Bool valid_seq;
+
+ if (seg_len == 0 && s->rcv_wnd == 0) {
+ valid_seq = (seg_seq == s->rcv_nxt);
+ } else {
+ // At least one of these must be true:
+ // RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND
+ // RCV.NXT =< SEG.SEQ+SEG.LEN-1 < RCV.NXT+RCV.WND
+ I64 rel_seq = ((seg_seq - s->rcv_nxt) & 0xffffffff);
+ I64 rel_seq_end = ((seg_seq + seg_len - 1 - s->rcv_nxt) & 0xffffffff);
+
+ if (rel_seq < s->rcv_wnd || rel_seq_end < s->rcv_wnd)
+ valid_seq = TRUE;
+ else
+ valid_seq = FALSE;
+ }
+
+ if (!valid_seq)
+ "SEQ error: seg_seq %d, seg_len %d, rcv_nxt %d, rcv_wnd %d\n", seg_seq,
+ seg_len, s->rcv_nxt, s->rcv_wnd;
+
+ // Process ACK
+ if (hdr->flags & TCP_FLAG_ACK) {
+ U32 seg_ack = ntohl(hdr->ack);
+ // ACK is acceptable iff SND.UNA < SEG.ACK =< SND.NXT
+
+ I64 rel_ack = ((seg_ack - s->snd_una) & 0xffffffff);
+ I64 rel_nxt = ((s->snd_nxt - s->snd_una) & 0xffffffff);
+
+ // RFC 793 is poorly worded in this regard, unacceptable ACK
+ // is not the opposite of an acceptible (= new) ACK!
+ // TODO: Instead of zero, we should compare rel_ack to some
+ // NEGATIVE_CONSTANT, so that we don't unnecessarily try to correct every
+ // slightly delayed ACK
+ if (/*0 < rel_ack &&*/ rel_ack <= rel_nxt) {
+ TcpSocketAckSendBufs(s, seg_ack);
+
+ // Accept ACK
+ s->snd_una = seg_ack;
+
+ if (s->state == TCP_STATE_SYN_SENT && (hdr->flags & TCP_FLAG_SYN)) {
+ s->state = TCP_STATE_ESTABLISHED;
+ s->srtt = tS - s->conntime;
+ //"Initial RTT: %f ms", s->srtt * 1000;
+ } else if (s->state == TCP_STATE_SYN_RECEIVED) {
+ //"Connection established.\n";
+ s->state = TCP_STATE_ESTABLISHED;
+ s->srtt = tS - s->conntime;
+ //"Initial RTT: %f ms", s->srtt * 1000;
+ }
+ } else {
+ // Unacceptable ACK
+ "Bad ACK; state %d, seg_ack %d, snd_nxt %d\n", s->state, seg_ack,
+ s->snd_nxt;
+
+ if (s->state == TCP_STATE_LISTEN || s->state == TCP_STATE_SYN_SENT ||
+ s->state == TCP_STATE_SYN_RECEIVED) {
+ // Reset
+ TcpSend(packet->dest_ip, ntohs(hdr->dest_port), packet->source_ip,
+ ntohs(hdr->source_port), seg_ack, seg_seq + seg_len,
+ TCP_FLAG_ACK | TCP_FLAG_RST);
+ } else if (TcpIsSynchronizedState(s->state)) {
+ // Send a 'corrective' ACK
+ must_ack = TRUE;
+ }
+ }
+ }
+
+ // Process RST
+ if (hdr->flags & TCP_FLAG_RST) {
+ if ((s->state == TCP_STATE_SYN_SENT)) {
+ // If acknowledged
+ if (s->snd_una == s->snd_nxt) {
+ "Connection refused\n";
+ s->state = TCP_STATE_CLOSED;
+ return;
+ }
+ } else {
+ if (valid_seq) {
+ "Connection reset by peer\n";
+ s->state = TCP_STATE_CLOSED;
+ return;
+ }
+ }
+
+ "Spurious RST\n";
+ }
+
+ // FIXME check remote addr & port
+
+ // Process data
+ if (valid_seq) {
+ s->snd_wnd = hdr->window_size;
+
+ if (s->state == TCP_STATE_ESTABLISHED || s->state == TCP_STATE_FIN_WAIT_1) {
+ I64 write_pos = s->recv_buf_write_pos;
+ //"%d in @ %d", length, write_pos;
+
+ // Skip retransmitted bytes
+ while (length && seg_seq != s->rcv_nxt) {
+ seg_seq = (seg_seq + 1) & 0xffffffff;
+ data++;
+ length--;
+ }
+
+ // ugh!
+ I64 i = 0;
+ for (i = 0; i < length; i++) {
+ I64 next_pos = (write_pos + 1) & (s->recv_buf_size - 1);
+
+ if (next_pos == s->recv_buf_read_pos)
+ break;
+
+ s->recv_buf[write_pos] = data[i];
+ write_pos = next_pos;
+ }
+
+ s->recv_buf_write_pos = write_pos;
+ s->rcv_nxt += i;
+ //"; %d saved\n", i;
+
+ if (i > 0)
+ must_ack = TRUE;
+
+ if (hdr->flags & TCP_FLAG_FIN) {
+ must_ack = TRUE;
+ s->rcv_nxt++;
+
+ if (s->state == TCP_STATE_ESTABLISHED) {
+ s->state = TCP_STATE_CLOSE_WAIT;
+ } else if (s->state == TCP_STATE_FIN_WAIT_1 ||
+ s->state == TCP_STATE_FIN_WAIT_2) {
+ s->state = TCP_STATE_TIME_WAIT;
+ }
+ // else { ?? }
+ }
+ }
+ }
+
+ if (must_ack) {
+ TcpSend2(s, TCP_FLAG_ACK);
+ }
+}
+
+I64 TcpHandler(CIPv4Packet *packet) {
+ CTcpHeader *hdr;
+ U8 *data;
+ I64 length;
+
+ I64 error = TcpParsePacket(&hdr, &data, &length, packet);
+
+ if (error < 0)
+ return error;
+
+ U16 dest_port = ntohs(hdr->dest_port);
+ //"%u => %p\n", dest_port, tcp_bound_sockets[dest_port];
+
+ CTcpSocket *s = GetTcpSocketFromList(packet, hdr);
+ if (!s)
+ s = tcp_bound_sockets[dest_port];
+
+ // FIXME: should also check that bound address is INADDR_ANY,
+ // OR packet dest IP matches bound address
+ if (s != NULL) {
+ TcpSocketHandle(s, packet, hdr, data, length);
+ } else {
+ // TODO: Send RST as per RFC793/Reset-Generation
+ }
+
+ return error;
+}
+
+U0 TcpInit() {
+ I64 i;
+ tcp_bound_sockets = MAlloc(65536 * sizeof(CTcpSocket *));
+ MemSet(tcp_bound_sockets, 0, 65536 * sizeof(CTcpSocket *));
+ tcp_socket_list = MAlloc(65536 * sizeof(CTcpSocketListItem *));
+ for (i = 0; i < 65536; i++) {
+ tcp_socket_list[i] = CAlloc(sizeof(CTcpSocketListItem));
+ }
+}
+
+TcpInit;
+RegisterL4Protocol(IP_PROTO_TCP, &TcpHandler);
+RegisterSocketClass(AF_INET, SOCK_STREAM, &TcpSocket);
diff --git a/Net/Udp.HC b/Net/Udp.HC
new file mode 100644
index 0000000..74e19f8
--- /dev/null
+++ b/Net/Udp.HC
@@ -0,0 +1,247 @@
+class CUdpHeader {
+ U16 source_port;
+ U16 dest_port;
+ U16 length;
+ U16 checksum;
+};
+
+class CUdpSocket {
+ CSocket sock;
+
+ I64 rcvtimeo_ms;
+ I64 recv_maxtime;
+
+ U8 *recv_buf;
+ I64 recv_len;
+
+ sockaddr_in recv_addr;
+ U16 bound_to;
+};
+
+// TODO: this takes up half a meg, change it to a binary tree or something
+static CUdpSocket **udp_bound_sockets;
+
+I64 UdpPacketAlloc(U8 **frame_out, U32 source_ip, U16 source_port, U32 dest_ip,
+ U16 dest_port, I64 length) {
+ U8 *frame;
+ I64 index = IPv4PacketAlloc(&frame, IP_PROTO_UDP, source_ip, dest_ip,
+ sizeof(CUdpHeader) + length);
+
+ if (index < 0)
+ return index;
+
+ CUdpHeader *hdr = frame;
+ hdr->source_port = htons(source_port);
+ hdr->dest_port = htons(dest_port);
+ hdr->length = htons(sizeof(CUdpHeader) + length);
+ hdr->checksum = 0;
+
+ *frame_out = frame + sizeof(CUdpHeader);
+ return index;
+}
+
+I64 UdpPacketFinish(I64 index) { return IPv4PacketFinish(index); }
+
+I64 UdpParsePacket(U16 *source_port_out, U16 *dest_port_out, U8 **data_out,
+ I64 *length_out, CIPv4Packet *packet) {
+ if (packet->proto != IP_PROTO_UDP)
+ return -1;
+
+ CUdpHeader *hdr = packet->data;
+ //"UDP: from %d, to %d, len %d, chksum %d\n",
+ // ntohs(hdr->source_port), ntohs(hdr->dest_port), ntohs(hdr->length),
+ // ntohs(hdr->checksum);
+
+ // FIXME: validate packet->length
+
+ *source_port_out = ntohs(hdr->source_port);
+ *dest_port_out = ntohs(hdr->dest_port);
+ // ntohs(hdr->length)
+ // ntohs(hdr->checksum)
+
+ *data_out = packet->data + sizeof(CUdpHeader);
+ *length_out = packet->length - sizeof(CUdpHeader);
+
+ return 0;
+}
+
+I64 UdpSocketAccept(CUdpSocket *s, sockaddr *addr, I64 addrlen) {
+ no_warn s;
+ no_warn addr;
+ no_warn addrlen;
+ return -1;
+}
+
+I64 UdpSocketBind(CUdpSocket *s, sockaddr *addr, I64 addrlen) {
+ if (addrlen < sizeof(sockaddr_in))
+ return -1;
+
+ if (s->bound_to)
+ return -1;
+
+ sockaddr_in *addr_in = addr;
+ U16 port = ntohs(addr_in->sin_port);
+
+ // TODO: address & stuff
+ if (udp_bound_sockets[port] != NULL)
+ return -1;
+
+ udp_bound_sockets[port] = s;
+ s->bound_to = port;
+ return 0;
+}
+
+I64 UdpSocketClose(CUdpSocket *s) {
+ if (s->bound_to)
+ udp_bound_sockets[s->bound_to] = NULL;
+
+ Free(s);
+ return 0;
+}
+
+I64 UdpSocketConnect(CUdpSocket *s, sockaddr *addr, I64 addrlen) {
+ // FIXME: implement
+ no_warn s;
+ no_warn addr;
+ no_warn addrlen;
+ return -1;
+}
+
+I64 UdpSocketListen(CUdpSocket *s, I64 backlog) {
+ no_warn s;
+ no_warn backlog;
+ return -1;
+}
+
+I64 UdpSocketRecvfrom(CUdpSocket *s, U8 *buf, I64 len, I64 flags,
+ sockaddr *src_addr, I64 addrlen) {
+ no_warn flags;
+
+ s->recv_buf = buf;
+ s->recv_len = len;
+
+ if (s->rcvtimeo_ms != 0)
+ s->recv_maxtime = cnts.jiffies + s->rcvtimeo_ms * JIFFY_FREQ / 1000;
+
+ while (s->recv_buf != NULL) {
+ // Check for timeout
+ if (s->rcvtimeo_ms != 0 && cnts.jiffies > s->recv_maxtime) {
+ // TODO: seterror(EWOULDBLOCK)
+ s->recv_len = -1;
+ break;
+ }
+
+ Yield;
+ }
+
+ // TODO: addrlen
+ if (src_addr) {
+ // wtf? can't copy structs with '='?
+ MemCpy((src_addr(sockaddr_in *)), &s->recv_addr, addrlen);
+ }
+
+ return s->recv_len;
+}
+
+I64 UdpSocketSendto(CSocket *s, U8 *buf, I64 len, I64 flags,
+ sockaddr_in *dest_addr, I64 addrlen) {
+ no_warn s;
+ no_warn flags;
+
+ if (addrlen < sizeof(sockaddr_in))
+ return -1;
+
+ U8 *frame;
+
+ I64 index = UdpPacketAlloc(&frame, IPv4GetAddress(), 0,
+ ntohl(dest_addr->sin_addr.s_addr),
+ ntohs(dest_addr->sin_port), len);
+
+ if (index < 0)
+ return -1;
+
+ MemCpy(frame, buf, len);
+ return UdpPacketFinish(index);
+}
+
+I64 UdpSocketSetsockopt(CUdpSocket *s, I64 level, I64 optname, U8 *optval,
+ I64 optlen) {
+ if (level == SOL_SOCKET && optname == SO_RCVTIMEO_MS && optlen == 8) {
+ s->rcvtimeo_ms = *(optval(I64 *));
+ return 0;
+ }
+
+ return -1;
+}
+
+CUdpSocket *UdpSocket(U16 domain, U16 type) {
+ if (domain != AF_INET || type != SOCK_DGRAM)
+ return NULL;
+
+ CUdpSocket *s = MAlloc(sizeof(CUdpSocket));
+ s->sock.accept = &UdpSocketAccept;
+ s->sock.bind = &UdpSocketBind;
+ s->sock.close = &UdpSocketClose;
+ s->sock.connect = &UdpSocketConnect;
+ s->sock.listen = &UdpSocketListen;
+ s->sock.recvfrom = &UdpSocketRecvfrom;
+ s->sock.sendto = &UdpSocketSendto;
+ s->sock.setsockopt = &UdpSocketSetsockopt;
+
+ s->rcvtimeo_ms = 0;
+ s->recv_maxtime = 0;
+
+ s->recv_buf = NULL;
+ s->recv_len = 0;
+ s->recv_addr.sin_family = AF_INET;
+ s->bound_to = 0;
+ return s;
+}
+
+I64 UdpHandler(CIPv4Packet *packet) {
+ U16 source_port;
+ U16 dest_port;
+ U8 *data;
+ I64 length;
+
+ I64 error = UdpParsePacket(&source_port, &dest_port, &data, &length, packet);
+
+ if (error < 0)
+ return error;
+
+ //"%u => %p\n", dest_port, udp_bound_sockets[dest_port];
+
+ CUdpSocket *s = udp_bound_sockets[dest_port];
+
+ // FIXME: should also check that bound address is INADDR_ANY,
+ // OR packet dest IP matches bound address
+ if (s != NULL) {
+ if (s->recv_buf) {
+ I64 num_recv = s->recv_len;
+
+ if (num_recv > length)
+ num_recv = length;
+
+ MemCpy(s->recv_buf, data, num_recv);
+
+ // signal that we received something
+ s->recv_buf = NULL;
+ s->recv_len = num_recv;
+
+ // TODO: we keep converting n>h>n, fuck that
+ s->recv_addr.sin_port = htons(source_port);
+ s->recv_addr.sin_addr.s_addr = htonl(packet->source_ip);
+ }
+ }
+
+ return error;
+}
+
+U0 UdpInit() {
+ udp_bound_sockets = MAlloc(65536 * sizeof(CUdpSocket *));
+ MemSet(udp_bound_sockets, 0, 65536 * sizeof(CUdpSocket *));
+}
+
+UdpInit;
+RegisterL4Protocol(IP_PROTO_UDP, &UdpHandler);
+RegisterSocketClass(AF_INET, SOCK_DGRAM, &UdpSocket);
diff --git a/Net/Virtio-net.HC b/Net/Virtio-net.HC
new file mode 100644
index 0000000..31066c8
--- /dev/null
+++ b/Net/Virtio-net.HC
@@ -0,0 +1,185 @@
+/* NOTE: This driver uses SnailNet naming conventions for required Ethernet
+ function calls. */
+
+extern U16 htons(U16 h);
+extern I64 NetFifoPushCopy(U8 *data, I64 length);
+#define ETHERNET_FRAME_SIZE 1548
+U8 *SNAILNET_NATIVE_DRIVER = "Virtio-net";
+
+// Current Rx/Tx buffer
+I64 rx_buffer_ptr = 0;
+I64 tx_buffer_ptr = 0;
+
+I64 rx_buffer_count = 256;
+I64 tx_buffer_count = 256;
+
+U64 rx_buffers = MAlloc(ETHERNET_FRAME_SIZE * 256);
+U64 tx_buffers = MAlloc(ETHERNET_FRAME_SIZE * 256);
+
+class @virtio_net {
+ U16 port;
+ U8 mac[6];
+ @virtio_queue *rq;
+ @virtio_queue *sq;
+ I64 rq_size;
+ I64 rq_index;
+ I64 sq_size;
+ I64 sq_index;
+ I64 rx_packets;
+ I64 rx_bytes;
+ I64 tx_packets;
+ I64 tx_bytes;
+};
+
+class @virtio_net_header {
+ U8 flags;
+ U8 gso_type;
+ U16 header_length;
+ U16 gso_size;
+ U16 checksum_start;
+ U16 checksum_offset;
+};
+
+@virtio_net VirtioNet;
+MemSet(&VirtioNet, 0, sizeof(@virtio_net));
+
+@virtio_net_header *def_pkt_hdr = CAlloc(sizeof(@virtio_net_header));
+
+static I64 @virtio_net_alloc_tx_packet(U8 **buffer_out, I64 length, I64 flags) {
+ // FIXME: validate length
+ flags = flags;
+ I64 sq_idx = VirtioNet.sq->available.index % 256;
+ I64 sq_idx2 = sq_idx % 128;
+ I64 index = tx_buffer_ptr;
+ tx_buffer_ptr = (tx_buffer_ptr + 1) & (tx_buffer_count - 1);
+ *buffer_out = tx_buffers + index * ETHERNET_FRAME_SIZE;
+
+ VirtioNet.sq->buffers[sq_idx2 * 2].address = def_pkt_hdr;
+ VirtioNet.sq->buffers[sq_idx2 * 2].length = sizeof(@virtio_net_header);
+ VirtioNet.sq->buffers[sq_idx2 * 2].flags = VRING_DESC_F_NEXT;
+ VirtioNet.sq->buffers[sq_idx2 * 2].next = (sq_idx2 * 2) + 1;
+ VirtioNet.sq->buffers[(sq_idx2 * 2) + 1].address = *buffer_out;
+ VirtioNet.sq->buffers[(sq_idx2 * 2) + 1].length = length;
+ VirtioNet.sq->buffers[(sq_idx2 * 2) + 1].flags = NULL;
+ VirtioNet.sq->buffers[(sq_idx2 * 2) + 1].next = 0;
+ VirtioNet.sq->available.ring[sq_idx] = sq_idx2 * 2;
+
+ VirtioNet.sq->available.index++;
+
+ VirtioNet.tx_packets++;
+ VirtioNet.tx_bytes += length;
+
+ return index;
+}
+
+static I64 @virtio_net_finish_tx_packet(I64) {
+ OutU16(VirtioNet.port + VIRTIO_PCI_QUEUE_NOTIFY, 1);
+ return 0;
+}
+
+U8 *loopback_frame = MAlloc(ETHERNET_FRAME_SIZE);
+I64 loopback_length = 0;
+
+I64 EthernetFrameAlloc(U8 **buffer_out, U8 *src_addr, U8 *dst_addr,
+ U16 ethertype, I64 length, I64 flags) {
+
+ U8 *frame;
+
+ // APAD_XMT doesn't seem to work in VirtualBox, so we have to pad the frame
+ // ourselves
+ if (length < 46)
+ length = 46;
+
+ I64 index;
+
+ if (!MemCmp(dst_addr, &VirtioNet.mac, 6)) {
+ frame = loopback_frame;
+ loopback_length = length;
+ index = I64_MAX;
+ } else {
+ index = @virtio_net_alloc_tx_packet(&frame, 14 + length, flags);
+ if (index < 0)
+ return index;
+ }
+
+ MemCpy(frame + 0, dst_addr, 6);
+ MemCpy(frame + 6, src_addr, 6);
+ frame[12] = (ethertype >> 8);
+ frame[13] = (ethertype & 0xff);
+
+ *buffer_out = frame + 14;
+ return index;
+}
+
+I64 EthernetFrameFinish(I64 index) {
+ if (index == I64_MAX && loopback_frame && loopback_length) {
+ NetFifoPushCopy(loopback_frame, loopback_length);
+ loopback_length = 0;
+ return 0;
+ }
+ return @virtio_net_finish_tx_packet(index);
+}
+
+U8 *EthernetGetAddress() { return &VirtioNet.mac; }
+
+I64 @virtio_net_init() {
+ I64 i, j;
+
+ // Scan for device
+ j = PCIClassFind(0x020000, 0);
+ if (j < 0) {
+ "\nVirtio-net device not found.\n";
+ return -1;
+ }
+ VirtioNet.port = PCIReadU32(j.u8[2], j.u8[1], j.u8[0], 0x10) & 0xFFFFFFFC;
+ for (i = 0; i < 6; i++) {
+ VirtioNet.mac[i] = InU8(VirtioNet.port + VIRTIO_PCI_CONFIG + i);
+ }
+
+ // Reset Device
+ OutU8(VirtioNet.port + VIRTIO_PCI_STATUS, 0);
+
+ // Found Driver
+ OutU8(VirtioNet.port + VIRTIO_PCI_STATUS,
+ InU8(VirtioNet.port + VIRTIO_PCI_STATUS) | VIRTIO_CONFIG_S_ACKNOWLEDGE |
+ VIRTIO_CONFIG_S_DRIVER);
+
+ // Set up receive queue
+ OutU16(VirtioNet.port + VIRTIO_PCI_QUEUE_SEL, 0);
+ VirtioNet.rq_size = InU16(VirtioNet.port + VIRTIO_PCI_QUEUE_SIZE); // 256
+ VirtioNet.rq = CAllocAligned(sizeof(@virtio_queue), 4096, Fs->code_heap);
+ OutU32(VirtioNet.port + VIRTIO_PCI_QUEUE_PFN, VirtioNet.rq / 4096);
+
+ // Set up send queue
+ OutU16(VirtioNet.port + VIRTIO_PCI_QUEUE_SEL, 1);
+ VirtioNet.sq_size = InU16(VirtioNet.port + VIRTIO_PCI_QUEUE_SIZE); // 256
+ VirtioNet.sq = CAllocAligned(sizeof(@virtio_queue), 4096, Fs->code_heap);
+ OutU32(VirtioNet.port + VIRTIO_PCI_QUEUE_PFN, VirtioNet.sq / 4096);
+
+ for (i = 0; i < 128; i++) {
+ VirtioNet.rq->buffers[i * 2].address = CAlloc(sizeof(@virtio_net_header));
+ VirtioNet.rq->buffers[i * 2].length = sizeof(@virtio_net_header);
+ VirtioNet.rq->buffers[i * 2].flags = VRING_DESC_F_NEXT | VRING_DESC_F_WRITE;
+ VirtioNet.rq->buffers[i * 2].next = (i * 2) + 1;
+ VirtioNet.rq->buffers[(i * 2) + 1].address = CAlloc(ETHERNET_FRAME_SIZE);
+ VirtioNet.rq->buffers[(i * 2) + 1].length = ETHERNET_FRAME_SIZE;
+ VirtioNet.rq->buffers[(i * 2) + 1].flags = VRING_DESC_F_WRITE;
+ VirtioNet.rq->buffers[(i * 2) + 1].next = 0;
+ VirtioNet.rq->available.ring[i] = i * 2;
+ VirtioNet.rq->available.ring[i + 128] = i * 2;
+ }
+ VirtioNet.rq->available.index = 1;
+
+ // Init OK
+ OutU8(VirtioNet.port + VIRTIO_PCI_STATUS,
+ InU8(VirtioNet.port + VIRTIO_PCI_STATUS) | VIRTIO_CONFIG_S_DRIVER_OK);
+ OutU16(VirtioNet.port + VIRTIO_PCI_QUEUE_NOTIFY, 0);
+ "[Virtio-net] device detected, MAC address "
+ "%02x:%02x:%02x:%02x:%02x:%02x\n",
+ VirtioNet.mac[0], VirtioNet.mac[1], VirtioNet.mac[2], VirtioNet.mac[3],
+ VirtioNet.mac[4], VirtioNet.mac[5];
+}
+
+@virtio_net_init;
+
+"[OK] virtio-net \n"; \ No newline at end of file
diff --git a/Net/Virtio.HC b/Net/Virtio.HC
new file mode 100644
index 0000000..862bd3a
--- /dev/null
+++ b/Net/Virtio.HC
@@ -0,0 +1,77 @@
+//
+// PCI virtio I/O registers.
+//
+
+#define VIRTIO_PCI_HOST_FEATURES 0 // Features supported by the host
+#define VIRTIO_PCI_GUEST_FEATURES 4 // Features activated by the guest
+#define VIRTIO_PCI_QUEUE_PFN 8 // PFN for the currently selected queue
+#define VIRTIO_PCI_QUEUE_SIZE 12 // Queue size for the currently selected queue
+#define VIRTIO_PCI_QUEUE_SEL 14 // Queue selector
+#define VIRTIO_PCI_QUEUE_NOTIFY 16 // Queue notifier
+#define VIRTIO_PCI_STATUS 18 // Device status register
+#define VIRTIO_PCI_ISR 19 // Interrupt status register
+#define VIRTIO_PCI_CONFIG 20 // Configuration data block
+
+//
+// PCI virtio status register bits
+//
+
+#define VIRTIO_CONFIG_S_ACKNOWLEDGE 1
+#define VIRTIO_CONFIG_S_DRIVER 2
+#define VIRTIO_CONFIG_S_DRIVER_OK 4
+#define VIRTIO_CONFIG_S_FAILED 0x80
+
+//
+// Ring descriptor flags
+//
+
+#define VRING_DESC_F_NEXT 1 // Buffer continues via the next field
+#define VRING_DESC_F_WRITE 2 // Buffer is write-only (otherwise read-only)
+#define VRING_DESC_F_INDIRECT 4 // Buffer contains a list of buffer descriptors
+
+class @virtio_queue_buf {
+ U64 address;
+ U32 length;
+ U16 flags;
+ U16 next;
+};
+class @virtio_avail {
+ U16 flags;
+ U16 index;
+ U16 ring[256];
+ U16 int_index;
+};
+class @virtio_used_item {
+ U32 index;
+ U32 length;
+};
+class @virtio_used {
+ U16 flags;
+ U16 index;
+ @virtio_used_item ring[256];
+ U16 int_index;
+};
+class @virtio_queue {
+ @virtio_queue_buf buffers[256];
+ @virtio_avail available;
+ U8 padding[3578];
+ @virtio_used used;
+};
+
+class @virtio_avail_buf {
+ U32 index;
+ U64 address;
+ U32 length;
+};
+
+class @virtio_buf_info {
+ U8 *buffer;
+ U64 size;
+ U8 flags;
+
+ // If the user wants to keep same buffer as passed in this struct, use "true".
+ // otherwise, the supplied buffer will be copied in the queues' buffer
+ Bool copy;
+};
+
+"[OK] virtio \n"; \ No newline at end of file
diff --git a/PacketData.HC b/PacketData.HC
new file mode 100644
index 0000000..c536388
--- /dev/null
+++ b/PacketData.HC
@@ -0,0 +1,38 @@
+U8 @aol_packet_init[58] = {
+ 0x5a, 0x06, 0x36, 0x00, 0x34, 0x7f, 0x7f, 0xa3, 0x03, 0x6e, 0x5f, 0x00,
+ 0x10, 0x00, 0x00, 0x00, 0x05, 0x0f, 0x00, 0x00, 0x1c, 0x98, 0x0b, 0x3a,
+ 0xc3, 0xb6, 0x10, 0xc0, 0x03, 0x20, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00,
+ 0x00, 0x00, 0x01, 0x80, 0x07, 0x38, 0x04, 0xff, 0xff, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0d};
+
+U8 @aol_packet_sign_in_as_guest[66] = {
+ 0x5a, 0xf1, 0x02, 0x00, 0x3c, 0x10, 0x7f, 0xa0, 0x44, 0x64, 0x00,
+ 0x16, 0x00, 0x01, 0x00, 0x01, 0x0a, 0x04, 0x00, 0x00, 0x00, 0x01,
+ 0x01, 0x0b, 0x04, 0x00, 0x00, 0x00, 0x02, 0x03, 0x01, 0x0a, 0x47,
+ 0x75, 0x65, 0x73, 0x74, 0x20, 0x20, 0x20, 0x20, 0x20, 0x01, 0x1d,
+ 0x00, 0x01, 0x1d, 0x00, 0x01, 0x0a, 0x04, 0x00, 0x00, 0x00, 0x02,
+ 0x03, 0x01, 0x01, 0x20, 0x01, 0x1d, 0x00, 0x00, 0x02, 0x00, 0x0d};
+
+U8 @aol_packet_sc[26] = {0x5a, 0x35, 0x9e, 0x00, 0x14, 0x11, 0x12, 0xa0, 0x53,
+ 0x43, 0x00, 0x14, 0x00, 0x01, 0x00, 0x03, 0x01, 0x04,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x0d};
+
+U8 @aol_packet_join_chat_room[36] = {
+ 0x5a, 0xa4, 0x10, 0x00, 0x1e, 0x13, 0x15, 0xa0, 0x63, 0x51, 0x00, 0x1d,
+ 0x00, 0x01, 0x00, 0x01, 0x07, 0x04, 0x00, 0x00, 0x00, 0x03, 0x03, 0x01,
+ 0x07, 0x57, 0x65, 0x6c, 0x63, 0x6f, 0x6d, 0x65, 0x00, 0x02, 0x00, 0x0d};
+
+U8 @aol_packet_send_chat_message[45] = {
+ 0x5a, 0x23, 0xe6, 0x00, 0x27, 0x18, 0x23, 0xa0, 0x41, 0x61, 0x00, 0x3b,
+ 0x00, 0x01, 0x00, 0x01, 0x07, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x01, 0x0a,
+ 0x04, 0x00, 0x00, 0x01, 0x02, 0x03, 0x01, 0x09, 0x74, 0x65, 0x73, 0x74,
+ 0x20, 0x31, 0x32, 0x33, 0x34, 0x00, 0x02, 0x00, 0x0d};
+
+U8 @aol_packet_send_im[73] = {
+ 0x5a, 0xb5, 0x8a, 0x00, 0x43, 0x14, 0x19, 0xa0, 0x69, 0x53, 0x00,
+ 0x2e, 0x00, 0x01, 0x00, 0x01, 0x07, 0x04, 0x00, 0x00, 0x00, 0x07,
+ 0x01, 0x0a, 0x04, 0x00, 0x00, 0x00, 0x01, 0x03, 0x01, 0x08, 0x47,
+ 0x75, 0x65, 0x73, 0x74, 0x46, 0x49, 0x58, 0x01, 0x1d, 0x00, 0x01,
+ 0x0a, 0x04, 0x00, 0x00, 0x00, 0x02, 0x03, 0x01, 0x0d, 0x74, 0x65,
+ 0x73, 0x74, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x21,
+ 0x01, 0x1d, 0x00, 0x00, 0x02, 0x00, 0x0d};
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..38979f2
--- /dev/null
+++ b/README.md
@@ -0,0 +1,7 @@
+# TOL
+
+AOL 3.0 Client for TempleOS
+
+![TOL](https://git.checksum.fail/alec/TOL/raw/branch/master/logo.png? "TOL")
+
+This is a minimal AOL 3.0 Client designed to be compatible with the Re-AOL reverse engineering project.
diff --git a/Run.HC b/Run.HC
new file mode 100644
index 0000000..8ad30d4
--- /dev/null
+++ b/Run.HC
@@ -0,0 +1,3 @@
+Adam("StrPrint(Fs->cur_dir, \"/\");\n");
+Adam("Fs->cur_dv = Let2Drv('T');\n");
+AdamFile("Init");
diff --git a/Src/Debug.HC b/Src/Debug.HC
new file mode 100644
index 0000000..099fe9c
--- /dev/null
+++ b/Src/Debug.HC
@@ -0,0 +1,10 @@
+U0 @debug(U8 *fmt, ...) {
+ U8 *buf = StrPrintJoin(NULL, fmt, argc, argv);
+ PutS(buf);
+ Free(buf);
+}
+
+U0 @vbox_debug_print(U8 *s) {
+ while (*s)
+ OutU8(0x504, *s++);
+} \ No newline at end of file
diff --git a/Src/Doc.HC b/Src/Doc.HC
new file mode 100644
index 0000000..52457b6
--- /dev/null
+++ b/Src/Doc.HC
@@ -0,0 +1,35 @@
+U0 @task_doc_push(U64 desc) {
+ CDoc *push_doc = DocNew;
+ push_doc->desc = desc;
+ push_doc->parent_doc = Fs->display_doc;
+ Fs->display_doc = push_doc;
+ Fs->put_doc = push_doc;
+}
+
+U0 @task_doc_pop() {
+ CDoc *pop_doc = Fs->display_doc;
+ Fs->display_doc = pop_doc->parent_doc;
+ Fs->put_doc = pop_doc->parent_doc;
+ DocDel(pop_doc);
+}
+
+U8 @popup_get_str_task(U8 *prompt) {
+ DocClear;
+ "\n";
+ "\dFG,0\d %s\dFD\d", prompt;
+ return GetStr;
+}
+
+U8 @popup_get_str(U8 *title, U8 *prompt) {
+ CTask *p = User("Fs->user_data = @popup_get_str_task(0x%08x);\n", prompt);
+ while (!p->user_data) {
+ StrCpy(p->task_title, title);
+ p->win_top = 20;
+ p->win_bottom = p->win_top + 2;
+ p->put_doc->desc = NULL;
+ Sleep(1);
+ }
+ U8 *res = StrNew(p->user_data);
+ Kill(p);
+ return res;
+} \ No newline at end of file
diff --git a/Src/Json.HC b/Src/Json.HC
new file mode 100644
index 0000000..e44783f
--- /dev/null
+++ b/Src/Json.HC
@@ -0,0 +1,881 @@
+#define JSON_SAME -1
+#define JSON_UNDEFINED 0
+#define JSON_OBJECT 1
+#define JSON_ARRAY 2
+#define JSON_STRING 3
+#define JSON_NUMBER 4
+#define JSON_BOOLEAN 5
+#define JSON_NULL 6
+#define JSON_HTML 7
+
+#define JSON_STATE_OBJECT_OR_ARRAY 0
+
+#define JSON_STATE_OBJECT 100
+#define JSON_STATE_OBJECT_KEY 101
+#define JSON_STATE_OBJECT_SEPARATOR 102
+#define JSON_STATE_OBJECT_TYPE 103
+#define JSON_STATE_OBJECT_NEXT 104
+
+#define JSON_STATE_OBJECT_OBJECT 105
+#define JSON_STATE_OBJECT_ARRAY 106
+#define JSON_STATE_OBJECT_STRING 107
+#define JSON_STATE_OBJECT_NUMBER 108
+#define JSON_STATE_OBJECT_BOOLEAN 109
+#define JSON_STATE_OBJECT_NULL 110
+
+#define JSON_STATE_ARRAY 200
+#define JSON_STATE_ARRAY_TYPE 201
+#define JSON_STATE_ARRAY_NEXT 202
+
+#define JSON_STATE_ARRAY_OBJECT 203
+#define JSON_STATE_ARRAY_ARRAY 204
+#define JSON_STATE_ARRAY_STRING 205
+#define JSON_STATE_ARRAY_NUMBER 206
+#define JSON_STATE_ARRAY_BOOLEAN 207
+#define JSON_STATE_ARRAY_NULL 208
+
+#define JSON_PARSER_FIFO_SIZE 16384
+#define JSON_STRINGIFY_BUF_SIZE 1048576
+
+I64 T(Bool _condition, I64 _true, I64 _false) {
+ if (_condition)
+ return _true;
+ return _false;
+}
+
+class @json_element {
+ @json_element *prev;
+ @json_element *next;
+ I64 type;
+};
+
+class @json_key : @json_element {
+ U8 *name;
+ U64 value;
+};
+
+class @json_item : @json_element {
+ U64 value;
+};
+
+class @json_object : @json_element {
+ I64 length;
+ @json_key *keys;
+};
+
+class @json_array : @json_element {
+ I64 length;
+ @json_item *items;
+};
+
+class @json_parser {
+ U8 *stream;
+ U8 token;
+ CFifoU8 *consumed;
+ I64 pos;
+ I64 state;
+ Bool debug;
+};
+
+#define JsonArray @json_array
+#define JsonElement @json_element
+#define JsonItem @json_item
+#define JsonKey @json_key
+#define JsonObject @json_object
+
+U0 @json_debug_parser_state(@json_parser *parser) {
+ switch (parser->state) {
+ case JSON_STATE_OBJECT:
+ "JSON_STATE_OBJECT\n";
+ break;
+ case JSON_STATE_OBJECT_KEY:
+ "JSON_STATE_OBJECT_KEY\n";
+ break;
+ case JSON_STATE_OBJECT_SEPARATOR:
+ "JSON_STATE_OBJECT_SEPARATOR\n";
+ break;
+ case JSON_STATE_OBJECT_TYPE:
+ "JSON_STATE_OBJECT_TYPE\n";
+ break;
+ case JSON_STATE_OBJECT_NEXT:
+ "JSON_STATE_OBJECT_NEXT\n";
+ break;
+ case JSON_STATE_OBJECT_STRING:
+ "JSON_STATE_OBJECT_STRING\n";
+ break;
+ case JSON_STATE_OBJECT_NUMBER:
+ "JSON_STATE_OBJECT_NUMBER\n";
+ break;
+ case JSON_STATE_ARRAY:
+ "JSON_STATE_ARRAY\n";
+ break;
+ case JSON_STATE_ARRAY_TYPE:
+ "JSON_STATE_ARRAY_TYPE\n";
+ break;
+ case JSON_STATE_ARRAY_NEXT:
+ "JSON_STATE_ARRAY_NEXT\n";
+ break;
+ case JSON_STATE_ARRAY_STRING:
+ "JSON_STATE_ARRAY_STRING\n";
+ break;
+ case JSON_STATE_ARRAY_NUMBER:
+ "JSON_STATE_ARRAY_NUMBER\n";
+ break;
+ }
+}
+
+U8 *@json_string_from_fifo(CFifoU8 *f) {
+ U8 ch;
+ I64 i = 0;
+ U8 *str = CAlloc(FifoU8Cnt(f) + 1);
+ while (FifoU8Cnt(f)) {
+ FifoU8Rem(f, &ch);
+ str[i] = ch;
+ i++;
+ }
+ FifoU8Flush(f);
+ return str;
+}
+
+U0 @json_insert_key(@json_object *obj, @json_key *key) {
+ if (!obj)
+ return;
+ if (!obj->keys) {
+ obj->keys = key;
+ obj->length++;
+ return;
+ }
+ @json_key *k = obj->keys;
+ while (k->next)
+ k = k->next;
+ k->next = key;
+ key->prev = k;
+ obj->length++;
+}
+
+U0 @json_insert_item(@json_array *arr, @json_item *item) {
+ if (!arr)
+ return;
+ if (!arr->items) {
+ arr->items = item;
+ arr->length++;
+ return;
+ }
+ @json_item *i = arr->items;
+ while (i->next)
+ i = i->next;
+ i->next = item;
+ item->prev = i;
+ arr->length++;
+}
+
+extern @json_element *@json_parse_object_or_array(@json_parser *parser);
+
+U0 @json_parse_object(@json_parser *parser, @json_object *obj) {
+ @json_key *key = NULL;
+ while (1) {
+ switch (parser->stream[parser->pos]) {
+ case '}':
+ switch (parser->state) {
+ case JSON_STATE_OBJECT_KEY:
+ case JSON_STATE_OBJECT_STRING:
+ FifoU8Ins(parser->consumed, parser->stream[parser->pos]);
+ break;
+ case JSON_STATE_OBJECT_NUMBER:
+ key->value = @json_string_from_fifo(parser->consumed);
+ key->value = Str2F64(key->value);
+ @json_insert_key(obj, key);
+ return;
+ break;
+ case JSON_STATE_OBJECT_BOOLEAN:
+ key->value = @json_string_from_fifo(parser->consumed);
+ if (StrCmp("true", key->value) && StrCmp("false", key->value)) {
+ PrintErr("@json_parse_object: Illegal boolean value at position %d",
+ parser->pos);
+ while (1)
+ Sleep(1);
+ }
+ if (!StrCmp("true", key->value))
+ key->value = TRUE;
+ else
+ key->value = FALSE;
+ @json_insert_key(obj, key);
+ return;
+ break;
+ case JSON_STATE_OBJECT_NULL:
+ key->value = @json_string_from_fifo(parser->consumed);
+ if (StrCmp("null", key->value)) {
+ PrintErr("@json_parse_object: Illegal null value at position %d",
+ parser->pos);
+ while (1)
+ Sleep(1);
+ }
+ key->value = NULL;
+ @json_insert_key(obj, key);
+ return;
+ break;
+ case JSON_STATE_OBJECT:
+ case JSON_STATE_OBJECT_NEXT:
+ return;
+ break;
+ }
+ break;
+ case ',':
+ switch (parser->state) {
+ case JSON_STATE_OBJECT_KEY:
+ case JSON_STATE_OBJECT_STRING:
+ FifoU8Ins(parser->consumed, parser->stream[parser->pos]);
+ break;
+ case JSON_STATE_OBJECT_NUMBER:
+ key->value = @json_string_from_fifo(parser->consumed);
+ key->value = Str2F64(key->value);
+ @json_insert_key(obj, key);
+ parser->state = JSON_STATE_OBJECT;
+ break;
+ case JSON_STATE_OBJECT_BOOLEAN:
+ key->value = @json_string_from_fifo(parser->consumed);
+ if (StrCmp("true", key->value) && StrCmp("false", key->value)) {
+ PrintErr("@json_parse_object: Illegal boolean value at position %d",
+ parser->pos);
+ while (1)
+ Sleep(1);
+ }
+ if (!StrCmp("true", key->value))
+ key->value = TRUE;
+ else
+ key->value = FALSE;
+ @json_insert_key(obj, key);
+ parser->state = JSON_STATE_OBJECT;
+ break;
+ case JSON_STATE_OBJECT_NULL:
+ key->value = @json_string_from_fifo(parser->consumed);
+ if (StrCmp("null", key->value)) {
+ PrintErr("@json_parse_object: Illegal null value at position %d",
+ parser->pos);
+ while (1)
+ Sleep(1);
+ }
+ key->value = NULL;
+ @json_insert_key(obj, key);
+ parser->state = JSON_STATE_OBJECT;
+ break;
+ case JSON_STATE_OBJECT_NEXT:
+ parser->state = JSON_STATE_OBJECT;
+ break;
+ }
+ break;
+ case ':':
+ switch (parser->state) {
+ case JSON_STATE_OBJECT_KEY:
+ case JSON_STATE_OBJECT_STRING:
+ FifoU8Ins(parser->consumed, parser->stream[parser->pos]);
+ break;
+ case JSON_STATE_OBJECT_SEPARATOR:
+ parser->state = JSON_STATE_OBJECT_TYPE;
+ break;
+ }
+ break;
+ case '[':
+ switch (parser->state) {
+ case JSON_STATE_OBJECT_KEY:
+ case JSON_STATE_OBJECT_STRING:
+ FifoU8Ins(parser->consumed, parser->stream[parser->pos]);
+ break;
+ case JSON_STATE_OBJECT_TYPE:
+ key->type = JSON_ARRAY;
+ key->value = @json_parse_object_or_array(parser);
+ @json_insert_key(obj, key);
+ parser->state = JSON_STATE_OBJECT_NEXT;
+ break;
+ }
+ break;
+ case '{':
+ switch (parser->state) {
+ case JSON_STATE_OBJECT_KEY:
+ case JSON_STATE_OBJECT_STRING:
+ FifoU8Ins(parser->consumed, parser->stream[parser->pos]);
+ break;
+ case JSON_STATE_OBJECT_TYPE:
+ key->type = JSON_OBJECT;
+ key->value = @json_parse_object_or_array(parser);
+ @json_insert_key(obj, key);
+ parser->state = JSON_STATE_OBJECT_NEXT;
+ break;
+ }
+ break;
+ case '"':
+ switch (parser->state) {
+ case JSON_STATE_OBJECT_STRING:
+ key->value = @json_string_from_fifo(parser->consumed);
+ @json_insert_key(obj, key);
+ parser->state = JSON_STATE_OBJECT_NEXT;
+ break;
+ case JSON_STATE_OBJECT_TYPE:
+ key->type = JSON_STRING;
+ parser->state = JSON_STATE_OBJECT_STRING;
+ break;
+ case JSON_STATE_OBJECT_KEY:
+ key->name = @json_string_from_fifo(parser->consumed);
+ parser->state = JSON_STATE_OBJECT_SEPARATOR;
+ break;
+ case JSON_STATE_OBJECT:
+ key = CAlloc(sizeof(@json_key));
+ parser->state = JSON_STATE_OBJECT_KEY;
+ break;
+ }
+ break;
+ case '-':
+ case '0' ... '9':
+ case '.':
+ switch (parser->state) {
+ case JSON_STATE_OBJECT_KEY:
+ case JSON_STATE_OBJECT_STRING:
+ case JSON_STATE_OBJECT_NUMBER:
+ FifoU8Ins(parser->consumed, parser->stream[parser->pos]);
+ break;
+ case JSON_STATE_OBJECT_TYPE:
+ key->type = JSON_NUMBER;
+ parser->state = JSON_STATE_OBJECT_NUMBER;
+ FifoU8Ins(parser->consumed, parser->stream[parser->pos]);
+ break;
+ }
+ break;
+ case 't':
+ case 'f':
+ switch (parser->state) {
+ case JSON_STATE_OBJECT_KEY:
+ case JSON_STATE_OBJECT_STRING:
+ FifoU8Ins(parser->consumed, parser->stream[parser->pos]);
+ break;
+ case JSON_STATE_OBJECT_TYPE:
+ key->type = JSON_BOOLEAN;
+ parser->state = JSON_STATE_OBJECT_BOOLEAN;
+ FifoU8Ins(parser->consumed, parser->stream[parser->pos]);
+ break;
+ }
+ break;
+ case 'n':
+ switch (parser->state) {
+ case JSON_STATE_OBJECT_KEY:
+ case JSON_STATE_OBJECT_STRING:
+ FifoU8Ins(parser->consumed, parser->stream[parser->pos]);
+ break;
+ case JSON_STATE_OBJECT_TYPE:
+ key->type = JSON_NULL;
+ parser->state = JSON_STATE_OBJECT_NULL;
+ FifoU8Ins(parser->consumed, parser->stream[parser->pos]);
+ break;
+ }
+ break;
+ default:
+ switch (parser->state) {
+ case JSON_STATE_OBJECT_KEY:
+ case JSON_STATE_OBJECT_STRING:
+ case JSON_STATE_OBJECT_BOOLEAN:
+ case JSON_STATE_OBJECT_NULL:
+ FifoU8Ins(parser->consumed, parser->stream[parser->pos]);
+ break;
+ }
+ break;
+ }
+ if (parser->debug) {
+ @json_debug_parser_state(parser);
+ "Object: %08X, Pos: %d, Token: %c\n", obj, parser->pos,
+ parser->stream[parser->pos];
+ Sleep(50);
+ }
+ parser->pos++;
+ }
+}
+
+U0 @json_parse_array(@json_parser *parser, @json_array *arr) {
+ @json_item *item = NULL;
+ while (1) {
+ if (parser->state == JSON_STATE_ARRAY) {
+ switch (parser->stream[parser->pos]) {
+ case 0:
+ PrintErr("@json_parse_array: Malformed array");
+ while (1)
+ Sleep(1);
+ break;
+ case ']':
+ return;
+ break;
+ }
+ item = CAlloc(sizeof(@json_item));
+ parser->state = JSON_STATE_ARRAY_TYPE;
+ }
+ switch (parser->stream[parser->pos]) {
+ case ']':
+ switch (parser->state) {
+ case JSON_STATE_ARRAY_STRING:
+ FifoU8Ins(parser->consumed, parser->stream[parser->pos]);
+ break;
+ case JSON_STATE_ARRAY_NUMBER:
+ item->value = @json_string_from_fifo(parser->consumed);
+ item->value = Str2F64(item->value);
+ @json_insert_item(arr, item);
+ return;
+ break;
+ case JSON_STATE_ARRAY_BOOLEAN:
+ item->value = @json_string_from_fifo(parser->consumed);
+ if (StrCmp("true", item->value) && StrCmp("false", item->value)) {
+ PrintErr("@json_parse_array: Illegal boolean value at position %d",
+ parser->pos);
+ while (1)
+ Sleep(1);
+ }
+ if (!StrCmp("true", item->value))
+ item->value = TRUE;
+ else
+ item->value = FALSE;
+ @json_insert_item(arr, item);
+ break;
+ case JSON_STATE_ARRAY_NULL:
+ item->value = @json_string_from_fifo(parser->consumed);
+ if (StrCmp("null", item->value)) {
+ PrintErr("@json_parse_array: Illegal null value at position %d",
+ parser->pos);
+ while (1)
+ Sleep(1);
+ }
+ item->value = NULL;
+ @json_insert_item(arr, item);
+ break;
+ case JSON_STATE_ARRAY:
+ case JSON_STATE_ARRAY_NEXT:
+ return;
+ break;
+ }
+ break;
+ case ',':
+ switch (parser->state) {
+ case JSON_STATE_ARRAY_STRING:
+ FifoU8Ins(parser->consumed, parser->stream[parser->pos]);
+ break;
+ case JSON_STATE_ARRAY_NUMBER:
+ item->value = @json_string_from_fifo(parser->consumed);
+ item->value = Str2F64(item->value);
+ @json_insert_item(arr, item);
+ parser->state = JSON_STATE_ARRAY;
+ break;
+ case JSON_STATE_ARRAY_BOOLEAN:
+ item->value = @json_string_from_fifo(parser->consumed);
+ if (StrCmp("true", item->value) && StrCmp("false", item->value)) {
+ PrintErr("@json_parse_array: Illegal boolean value at position %d",
+ parser->pos);
+ while (1)
+ Sleep(1);
+ }
+ if (!StrCmp("true", item->value))
+ item->value = TRUE;
+ else
+ item->value = FALSE;
+ @json_insert_item(arr, item);
+ parser->state = JSON_STATE_ARRAY;
+ break;
+ case JSON_STATE_ARRAY_NULL:
+ item->value = @json_string_from_fifo(parser->consumed);
+ if (StrCmp("null", item->value)) {
+ PrintErr("@json_parse_array: Illegal null value at position %d",
+ parser->pos);
+ while (1)
+ Sleep(1);
+ }
+ item->value = NULL;
+ @json_insert_item(arr, item);
+ parser->state = JSON_STATE_ARRAY;
+ break;
+ case JSON_STATE_ARRAY_NEXT:
+ parser->state = JSON_STATE_ARRAY;
+ break;
+ }
+ break;
+ case '[':
+ switch (parser->state) {
+ case JSON_STATE_ARRAY_STRING:
+ FifoU8Ins(parser->consumed, parser->stream[parser->pos]);
+ break;
+ case JSON_STATE_ARRAY_TYPE:
+ item->type = JSON_ARRAY;
+ item->value = @json_parse_object_or_array(parser);
+ @json_insert_item(arr, item);
+ parser->state = JSON_STATE_ARRAY_NEXT;
+ break;
+ }
+ break;
+ case '{':
+ switch (parser->state) {
+ case JSON_STATE_ARRAY_STRING:
+ FifoU8Ins(parser->consumed, parser->stream[parser->pos]);
+ break;
+ case JSON_STATE_ARRAY_TYPE:
+ item->type = JSON_OBJECT;
+ item->value = @json_parse_object_or_array(parser);
+ @json_insert_item(arr, item);
+ parser->state = JSON_STATE_ARRAY_NEXT;
+ break;
+ }
+ break;
+ case '"':
+ switch (parser->state) {
+ case JSON_STATE_ARRAY_STRING:
+ item->value = @json_string_from_fifo(parser->consumed);
+ @json_insert_item(arr, item);
+ parser->state = JSON_STATE_ARRAY_NEXT;
+ break;
+ case JSON_STATE_ARRAY_TYPE:
+ item->type = JSON_STRING;
+ parser->state = JSON_STATE_ARRAY_STRING;
+ break;
+ }
+ break;
+ case '-':
+ case '0' ... '9':
+ case '.':
+ switch (parser->state) {
+ case JSON_STATE_ARRAY_STRING:
+ case JSON_STATE_ARRAY_NUMBER:
+ FifoU8Ins(parser->consumed, parser->stream[parser->pos]);
+ break;
+ case JSON_STATE_ARRAY_TYPE:
+ item->type = JSON_NUMBER;
+ parser->state = JSON_STATE_ARRAY_NUMBER;
+ FifoU8Ins(parser->consumed, parser->stream[parser->pos]);
+ break;
+ }
+ break;
+ case 't':
+ case 'f':
+ switch (parser->state) {
+ case JSON_STATE_ARRAY_STRING:
+ FifoU8Ins(parser->consumed, parser->stream[parser->pos]);
+ break;
+ case JSON_STATE_ARRAY_TYPE:
+ item->type = JSON_BOOLEAN;
+ parser->state = JSON_STATE_ARRAY_BOOLEAN;
+ FifoU8Ins(parser->consumed, parser->stream[parser->pos]);
+ break;
+ }
+ break;
+ case 'n':
+ switch (parser->state) {
+ case JSON_STATE_ARRAY_STRING:
+ FifoU8Ins(parser->consumed, parser->stream[parser->pos]);
+ break;
+ case JSON_STATE_OBJECT_TYPE:
+ item->type = JSON_NULL;
+ parser->state = JSON_STATE_ARRAY_NULL;
+ FifoU8Ins(parser->consumed, parser->stream[parser->pos]);
+ break;
+ }
+ break;
+ default:
+ switch (parser->state) {
+ case JSON_STATE_ARRAY_STRING:
+ case JSON_STATE_ARRAY_BOOLEAN:
+ case JSON_STATE_ARRAY_NULL:
+ FifoU8Ins(parser->consumed, parser->stream[parser->pos]);
+ break;
+ }
+ break;
+ }
+ if (parser->debug) {
+ @json_debug_parser_state(parser);
+ "Array: %08X, Pos: %d, Token: %c\n", arr, parser->pos,
+ parser->stream[parser->pos];
+ Sleep(50);
+ }
+ parser->pos++;
+ }
+}
+
+@json_element *@json_parse_object_or_array(@json_parser *parser) {
+ @json_element *el = CAlloc(sizeof(@json_element) * 2);
+ while (1) {
+ switch (parser->stream[parser->pos]) {
+ case 0:
+ return el;
+ break;
+ case ' ':
+ case '\r':
+ case '\n':
+ case '\t':
+ break;
+ case '{':
+ el->type = JSON_OBJECT;
+ parser->pos++;
+ parser->state = JSON_STATE_OBJECT;
+ @json_parse_object(parser, el);
+ return el;
+ break;
+ case '[':
+ el->type = JSON_ARRAY;
+ parser->pos++;
+ parser->state = JSON_STATE_ARRAY;
+ @json_parse_array(parser, el);
+ return el;
+ break;
+ default:
+ PrintErr("@json_parse_object_or_array: Invalid token");
+ while (1) {
+ Sleep(1);
+ };
+ break;
+ }
+ parser->pos++;
+ Sleep(1);
+ }
+}
+
+@json_element *@json_parse(U8 *str) {
+ @json_parser *parser = CAlloc(sizeof(@json_parser));
+ parser->consumed = FifoU8New(JSON_PARSER_FIFO_SIZE);
+ // parser->debug = TRUE;
+ parser->stream = str;
+ @json_element *root = @json_parse_object_or_array(parser);
+ FifoU8Flush(parser->consumed);
+ FifoU8Del(parser->consumed);
+ Free(parser);
+ return root;
+}
+
+U0 @json_stringify_append_char(U8 *str, U8 char) {
+ I64 len = StrLen(str);
+ str[len] = char;
+ str[len + 1] = NULL;
+}
+
+U0 @json_stringify_append_str(U8 *str, U8 *str2) {
+ while (*str2) {
+ @json_stringify_append_char(str, *str2);
+ str2++;
+ }
+}
+
+U0 @json_stringify_append_number(U8 *str, F64 num) {
+ U8 buf[16];
+ StrPrint(buf, "%.7f", num);
+ I64 i = StrLen(buf) - 1;
+ while (buf[i] == '0') {
+ buf[i] = NULL;
+ i--;
+ }
+ i = StrLen(buf) - 1;
+ if (buf[i] == '.')
+ buf[i] = NULL;
+ @json_stringify_append_str(str, buf);
+}
+
+extern U0 @json_stringify_object_or_array(U8 *str, @json_element *el);
+
+U0 @json_stringify_object(U8 *str, @json_object *obj) {
+ @json_stringify_append_char(str, '{');
+ @json_key *key = obj->keys;
+ while (key) {
+ @json_stringify_append_char(str, '"');
+ @json_stringify_append_str(str, key->name);
+ @json_stringify_append_char(str, '"');
+ @json_stringify_append_char(str, ':');
+ switch (key->type) {
+ case JSON_OBJECT:
+ case JSON_ARRAY:
+ @json_stringify_object_or_array(str, key->value);
+ break;
+ case JSON_STRING:
+ @json_stringify_append_char(str, '"');
+ @json_stringify_append_str(str, key->value);
+ @json_stringify_append_char(str, '"');
+ break;
+ case JSON_NUMBER:
+ @json_stringify_append_number(str, key->value);
+ break;
+ case JSON_BOOLEAN:
+ @json_stringify_append_str(str, T(key->value, "true", "false"));
+ break;
+ case JSON_NULL:
+ @json_stringify_append_str(str, "null");
+ break;
+ default:
+ PrintErr("@json_stringify_object: Invalid element type");
+ while (1) {
+ Sleep(1);
+ };
+ }
+ if (key->next)
+ @json_stringify_append_char(str, ',');
+ key = key->next;
+ }
+ @json_stringify_append_char(str, '}');
+}
+
+U0 @json_stringify_array(U8 *str, @json_array *arr) {
+ @json_stringify_append_char(str, '[');
+ @json_item *item = arr->items;
+ while (item) {
+ switch (item->type) {
+ case JSON_OBJECT:
+ case JSON_ARRAY:
+ @json_stringify_object_or_array(str, item->value);
+ break;
+ case JSON_STRING:
+ @json_stringify_append_char(str, '"');
+ @json_stringify_append_str(str, item->value);
+ @json_stringify_append_char(str, '"');
+ break;
+ case JSON_NUMBER:
+ @json_stringify_append_number(str, item->value);
+ break;
+ case JSON_BOOLEAN:
+ @json_stringify_append_str(str, T(item->value, "true", "false"));
+ break;
+ case JSON_NULL:
+ @json_stringify_append_str(str, "null");
+ break;
+ default:
+ PrintErr("@json_stringify_array: Invalid element type");
+ while (1) {
+ Sleep(1);
+ };
+ }
+ if (item->next)
+ @json_stringify_append_char(str, ',');
+ item = item->next;
+ }
+ @json_stringify_append_char(str, ']');
+}
+
+U0 @json_stringify_object_or_array(U8 *str, @json_element *el) {
+ while (el) {
+ switch (el->type) {
+ case JSON_OBJECT:
+ @json_stringify_object(str, el);
+ break;
+ case JSON_ARRAY:
+ @json_stringify_array(str, el);
+ break;
+ default:
+ PrintErr("@json_stringify_object_or_array: Invalid element type");
+ while (1) {
+ Sleep(1);
+ };
+ break;
+ }
+ el = el->next;
+ }
+}
+
+U8 *@json_stringify(@json_element *el, I64 buf_size = JSON_STRINGIFY_BUF_SIZE) {
+ U8 *str = CAlloc(buf_size);
+ @json_stringify_object_or_array(str, el);
+ return str;
+}
+
+U64 @json_get(@json_object *obj, U8 *key, Bool return_key = FALSE) {
+ if (!obj || !key)
+ return NULL;
+ if (!obj->keys || obj->type != JSON_OBJECT)
+ return NULL;
+ @json_key *iter_key = obj->keys;
+ while (iter_key) {
+ if (!StrCmp(iter_key->name, key))
+ if (return_key)
+ return iter_key;
+ else
+ return iter_key->value;
+ iter_key = iter_key->next;
+ }
+ return NULL;
+}
+
+U0 @json_set(@json_object *obj, U8 *key, U64 value, I64 type = JSON_SAME) {
+ if (!obj || !key || !type)
+ return;
+ if (obj->type != JSON_OBJECT)
+ return;
+ @json_key *iter_key = obj->keys;
+ while (iter_key) {
+ if (!StrCmp(iter_key->name, key)) {
+ if (type != JSON_SAME)
+ iter_key->type = type;
+ iter_key->value = value;
+ return;
+ }
+ iter_key = iter_key->next;
+ }
+ @json_key *new_key = CAlloc(sizeof(@json_key));
+ new_key->name = StrNew(key);
+ new_key->type = type;
+ new_key->value = value;
+ @json_insert_key(obj, new_key);
+}
+
+@json_object *@json_create_object() {
+ @json_object *obj = CAlloc(sizeof(@json_object));
+ obj->type = JSON_OBJECT;
+ return obj;
+}
+
+@json_array *@json_create_array() {
+ @json_array *arr = CAlloc(sizeof(@json_array));
+ arr->type = JSON_ARRAY;
+ arr->items = CAlloc(sizeof(@json_item));
+ return arr;
+}
+
+U0 @json_append_item(@json_array *arr, @json_item *append_item) {
+ if (!arr || !append_item)
+ return;
+ if (arr->type != JSON_ARRAY)
+ return;
+ @json_item *item = arr->items;
+ while (item->next) {
+ item = item->next;
+ }
+ item->next = append_item;
+ append_item->prev = item;
+ arr->length++;
+}
+
+U64 @json_array_index(@json_array *arr, I64 index, Bool return_item = FALSE) {
+ if (!arr)
+ return NULL;
+ if (arr->type != JSON_ARRAY)
+ return NULL;
+ if (index > arr->length - 1)
+ return NULL;
+ @json_item *item = arr->items->next;
+ if (!item)
+ return NULL;
+ I64 i;
+ for (i = 0; i < index; i++)
+ item = item->next;
+ if (return_item)
+ return item;
+ else
+ return item->value;
+}
+
+class @json {
+ U0 (*AppendItem)(@json_array * arr, @json_item * append_item);
+ U64 (*ArrayIndex)(@json_array * arr, I64 index, Bool return_item = FALSE);
+ @json_object *(*CreateObject)();
+ @json_array *(*CreateArray)();
+ @json_element *(*Parse)(U8 * str);
+ U64(*Get)
+ (@json_object * obj, U8 * key, Bool return_key = FALSE);
+ U0 (*Set)(@json_object * obj, U8 * key, U64 value, I64 type);
+ U8 *(*Stringify)(@json_element * el, I64 buf_size = JSON_STRINGIFY_BUF_SIZE);
+};
+
+@json Json;
+Json.AppendItem = &@json_append_item;
+Json.ArrayIndex = &@json_array_index;
+Json.CreateArray = &@json_create_array;
+Json.CreateObject = &@json_create_object;
+Json.Get = &@json_get;
+Json.Parse = &@json_parse;
+Json.Set = &@json_set;
+Json.Stringify = &@json_stringify;
+
+"[OK] json \n"; \ No newline at end of file
diff --git a/Src/MD5.HC b/Src/MD5.HC
new file mode 100644
index 0000000..02fb5d6
--- /dev/null
+++ b/Src/MD5.HC
@@ -0,0 +1,141 @@
+/*
+ * Simple MD5 implementation
+ *
+ * https://gist.github.com/creationix/4710780
+ */
+
+U32 md5_r[64] = {7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
+ 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
+ 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
+ 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21};
+// Use binary integer part of the sines of integers (in radians) as constants//
+// Initialize variables:
+U32 md5_k[64] = {
+ 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a,
+ 0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
+ 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340,
+ 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
+ 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8,
+ 0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
+ 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa,
+ 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
+ 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92,
+ 0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
+ 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391};
+
+// leftrotate function
+U32 LEFTROTATE(U32 x, U32 c) { return (((x) << (c)) | ((x) >> (32 - (c)))); }
+
+U0 md5(U8 *initial_msg, U32 initial_len, U32 *md5_h) {
+
+ // These vars will contain the hash
+ U32 md5_h0, md5_h1, md5_h2, md5_h3;
+
+ // Message (to prepare)
+ U8 *msg = NULL;
+
+ // Note: All variables are unsigned 32 bit and wrap modulo 2^32 when
+ // calculating
+
+ // r specifies the per-round shift amounts
+
+ md5_h0 = 0x67452301;
+ md5_h1 = 0xefcdab89;
+ md5_h2 = 0x98badcfe;
+ md5_h3 = 0x10325476;
+
+ // Pre-processing: adding a single 1 bit
+ // append "1" bit to message
+ /* Notice: the input bytes are considered as bits strings,
+ where the first bit is the most significant bit of the byte.[37] */
+
+ // Pre-processing: padding with zeros
+ // append "0" bit until message length in bit ≡ 448 (mod 512)
+ // append length mod (2 pow 64) to message
+
+ U32 new_len;
+ for (new_len = initial_len * 8 + 1; new_len % 512 != 448; new_len++)
+ ;
+ new_len /= 8;
+
+ msg = CAlloc(new_len + 64); // also appends "0" bits
+ // (we alloc also 64 extra bytes...)
+ MemCpy(msg, initial_msg, initial_len);
+ msg[initial_len] = 128; // write the "1" bit
+
+ U32 bits_len = 8 * initial_len; // note, we append the len
+ MemCpy(msg + new_len, &bits_len, 4); // in bits at the end of the buffer
+
+ // Process the message in successive 512-bit chunks:
+ // for each 512-bit chunk of message:
+ U32 offset;
+ for (offset = 0; offset < new_len; offset += (512 / 8)) {
+
+ // break chunk into sixteen 32-bit words w[j], 0 ≤ j ≤ 15
+ U32 *w = (msg + offset)(U32 *);
+
+ // Initialize hash value for this chunk:
+ U32 a = md5_h0;
+ U32 b = md5_h1;
+ U32 c = md5_h2;
+ U32 d = md5_h3;
+
+ // Main loop:
+ U32 i;
+ for (i = 0; i < 64; i++) {
+
+ U32 f, g;
+
+ if (i < 16) {
+ f = (b & c) | ((~b) & d);
+ g = i;
+ } else if (i < 32) {
+ f = (d & b) | ((~d) & c);
+ g = (5 * i + 1) % 16;
+ } else if (i < 48) {
+ f = b ^ c ^ d;
+ g = (3 * i + 5) % 16;
+ } else {
+ f = c ^ (b | (~d));
+ g = (7 * i) % 16;
+ }
+
+ U32 temp = d;
+ d = c;
+ c = b;
+ // printf("rotateLeft(%x + %x + %x + %x, %d)\n", a, f, k[i], w[g], r[i]);
+ b = b + LEFTROTATE((a + f + md5_k[i] + w[g]), md5_r[i]);
+ a = temp;
+ }
+
+ // Add this chunk's hash to result so far:
+
+ md5_h0 += a;
+ md5_h1 += b;
+ md5_h2 += c;
+ md5_h3 += d;
+ }
+
+ md5_h[0] = md5_h0;
+ md5_h[1] = md5_h1;
+ md5_h[2] = md5_h2;
+ md5_h[3] = md5_h3;
+
+ // cleanup
+ Free(msg);
+}
+
+U8 *md5_string(U8 *buf, I64 size) {
+ U32 md5_h[4];
+ md5(buf, size, &md5_h[0]);
+ U8 *str = CAlloc(33);
+ StrPrint(str + StrLen(str), "%02x%02x%02x%02x", md5_h[0].u8[0],
+ md5_h[0].u8[1], md5_h[0].u8[2], md5_h[0].u8[3]);
+ StrPrint(str + StrLen(str), "%02x%02x%02x%02x", md5_h[1].u8[0],
+ md5_h[1].u8[1], md5_h[1].u8[2], md5_h[1].u8[3]);
+ StrPrint(str + StrLen(str), "%02x%02x%02x%02x", md5_h[2].u8[0],
+ md5_h[2].u8[1], md5_h[2].u8[2], md5_h[2].u8[3]);
+ StrPrint(str + StrLen(str), "%02x%02x%02x%02x", md5_h[3].u8[0],
+ md5_h[3].u8[1], md5_h[3].u8[2], md5_h[3].u8[3]);
+ return str;
+}
diff --git a/Src/NetUtils.HC b/Src/NetUtils.HC
new file mode 100644
index 0000000..ff32c2c
--- /dev/null
+++ b/Src/NetUtils.HC
@@ -0,0 +1,39 @@
+I64 @net_resolve_ipv4_address(U8 *_str, U32 *addr) {
+ U8 *str = StrNew(_str);
+ *addr = NULL;
+ I64 err = NULL;
+ addrinfo *res = NULL;
+ if (!inet_pton(AF_INET, str, addr)) {
+ err = getaddrinfo(str, NULL, NULL, &res);
+ if (!err) {
+ *addr = (res->ai_addr(sockaddr_in *))->sin_addr.s_addr;
+ }
+ if (res) {
+ freeaddrinfo(res);
+ }
+ Free(str);
+ return err;
+ }
+ Free(str);
+ return 0;
+}
+
+U32 @net_get_ipv4_dns_resolver() { return dns_ip; }
+
+U32 inet_addr(U8 *str) {
+ U32 res = NULL;
+ @net_resolve_ipv4_address(str, &res);
+ return res;
+}
+
+class @net {
+ I64 (*ResolveIPv4Address)(U8 * _str, U32 * addr);
+ U32 (*GetIPv4DNSResolver)();
+};
+
+@net Net;
+
+Net.GetIPv4DNSResolver = &@net_get_ipv4_dns_resolver;
+Net.ResolveIPv4Address = &@net_resolve_ipv4_address;
+
+"[OK] netutils \n"; \ No newline at end of file
diff --git a/TOL.HC b/TOL.HC
new file mode 100644
index 0000000..958668f
--- /dev/null
+++ b/TOL.HC
@@ -0,0 +1,403 @@
+#include "PacketData";
+
+#define AOL_HOST "reaol.org"
+#define AOL_PORT 5190
+
+class AolPacketHeader {
+ U8 start_of_frame;
+ U16 checksum;
+ U16 length;
+ U8 tx_seq;
+ U8 rx_seq;
+ U8 type;
+};
+
+class AolDataPacket : AolPacketHeader {
+ U8 token[2];
+ U8 data;
+};
+
+U8 *aol_recv_buf = CAlloc(1048576 * 4);
+U8 *aol_send_buf = CAlloc(1048576 * 4);
+
+U0 @tol_send_init(I64 s) {
+ I64 len = 58;
+ U8 *buf = @aol_packet_init;
+ buf[1] = '*';
+ buf[2] = '*';
+ send(s, buf, len, 0);
+}
+
+U0 @tol_sign_in_as_guest(I64 s) {
+ I64 len = 66;
+ U8 *buf = @aol_packet_sign_in_as_guest;
+ buf[1] = '*';
+ buf[2] = '*';
+ send(s, buf, len, 0);
+}
+
+U0 @tol_send_sc(I64 s) {
+ I64 len = 26;
+ U8 *buf = @aol_packet_sc;
+ buf[1] = '*';
+ buf[2] = '*';
+ send(s, buf, len, 0);
+}
+
+U0 @tol_join_room(I64 s, U8 *name) {
+ I64 len = 36;
+ U8 *buf = CAlloc(1024);
+ U8 *tbuf = @aol_packet_join_chat_room;
+ MemCpy(buf, tbuf, len);
+
+ AolPacketHeader *pkt = buf;
+
+ buf[1] = '*';
+ buf[2] = '*';
+
+ buf[0x18] = StrLen(name);
+ MemCpy(buf + 0x19, name, StrLen(name));
+ buf[0x19 + StrLen(name) + 0] = 0x00;
+ buf[0x19 + StrLen(name) + 1] = 0x02;
+ buf[0x19 + StrLen(name) + 2] = 0x00;
+ buf[0x19 + StrLen(name) + 3] = 0x0d;
+
+ pkt->length = 0x19 + StrLen(name) + 4;
+
+ send(s, buf, pkt->length, 0);
+ Free(buf);
+}
+
+Bool @wait_for_ack(I64 s) {
+ AolPacketHeader *pkt = aol_recv_buf;
+ I64 len = recv(s, aol_recv_buf, 1024, 0);
+ if (pkt->type == 0x24) {
+ return FALSE;
+ }
+ return TRUE;
+}
+
+U8 *@terminate_buffer(U8 *s) {
+ U8 *res = s;
+ while (*s != 0x0d)
+ s++;
+ *s = NULL;
+ return res;
+}
+
+U8 *@skip_null_padding(U8 *s) {
+ while (!(*s))
+ s++;
+ return s;
+}
+
+U0 @send_msg(I64 s, U8 *my_msg) {
+
+ I64 len = 45;
+ U8 *buf = CAlloc(1024);
+ U8 *tbuf = @aol_packet_send_chat_message;
+ MemCpy(buf, tbuf, len);
+
+ AolPacketHeader *pkt = buf;
+
+ buf[1] = '*';
+ buf[2] = '*';
+
+ buf[0x1f] = StrLen(my_msg);
+ MemCpy(buf + 0x20, my_msg, StrLen(my_msg));
+ buf[0x20 + StrLen(my_msg) + 0] = 0x00;
+ buf[0x20 + StrLen(my_msg) + 1] = 0x02;
+ buf[0x20 + StrLen(my_msg) + 2] = 0x00;
+ buf[0x20 + StrLen(my_msg) + 3] = 0x0d;
+
+ pkt->length = 0x20 + StrLen(my_msg) + 4;
+
+ send(s, buf, pkt->length, 0);
+ Free(buf);
+}
+
+U0 @send_im(I64 s, U8 *name, U8 *my_msg) {
+
+ I64 len = 73;
+ U8 *buf = CAlloc(1024);
+ U8 *tbuf = @aol_packet_send_im;
+ MemCpy(buf, tbuf, len);
+
+ AolPacketHeader *pkt = buf;
+
+ buf[1] = '*';
+ buf[2] = '*';
+
+ buf[0x1f] = StrLen(name);
+ MemCpy(buf + 0x20, name, StrLen(name));
+
+ buf[0x20 + StrLen(name) + 00] = 0x01;
+ buf[0x20 + StrLen(name) + 01] = 0x1d;
+ buf[0x20 + StrLen(name) + 02] = 0x00;
+ buf[0x20 + StrLen(name) + 03] = 0x01;
+ buf[0x20 + StrLen(name) + 04] = 0x0a;
+ buf[0x20 + StrLen(name) + 05] = 0x04;
+ buf[0x20 + StrLen(name) + 06] = 0x00;
+ buf[0x20 + StrLen(name) + 07] = 0x00;
+ buf[0x20 + StrLen(name) + 08] = 0x00;
+ buf[0x20 + StrLen(name) + 09] = 0x02;
+ buf[0x20 + StrLen(name) + 10] = 0x03;
+ buf[0x20 + StrLen(name) + 11] = 0x01;
+
+ buf[0x20 + StrLen(name) + 12] = StrLen(my_msg);
+ MemCpy(buf + 0x20 + StrLen(name) + 13, my_msg, StrLen(my_msg));
+
+ buf[0x20 + StrLen(name) + 13 + StrLen(my_msg) + 00] = 0x01;
+ buf[0x20 + StrLen(name) + 13 + StrLen(my_msg) + 01] = 0x1d;
+ buf[0x20 + StrLen(name) + 13 + StrLen(my_msg) + 02] = 0x00;
+ buf[0x20 + StrLen(name) + 13 + StrLen(my_msg) + 03] = 0x00;
+ buf[0x20 + StrLen(name) + 13 + StrLen(my_msg) + 04] = 0x02;
+ buf[0x20 + StrLen(name) + 13 + StrLen(my_msg) + 05] = 0x00;
+ buf[0x20 + StrLen(name) + 13 + StrLen(my_msg) + 06] = 0x0d;
+
+ pkt->length = 0x20 + StrLen(name) + 13 + StrLen(my_msg) + 07;
+
+ send(s, buf, pkt->length, 0);
+ Free(buf);
+}
+
+U0 @tol_send_im_loop(U8 *name) {
+ U8 *msg;
+ while (1) {
+ DocClear;
+ msg = GetStr;
+ if (StrLen(msg))
+ @send_im(aol_socket, name, msg);
+ DocPrint(Fs->parent_task->put_doc, "\dFG,RED\d%s: \dFG,BLACK\d%s\n\dFD\d",
+ "Me", msg);
+ Play("st4C#F#sB");
+ }
+}
+
+U0 @tol_init_im_window(U8 *name) {
+ // Set window x, y, w, h
+
+ I64 ww = 36;
+ I64 hh = 15;
+
+ Fs->win_left = (TEXT_COLS / 2) - (ww / 2);
+ Fs->win_right = Fs->win_left + ww;
+ Fs->win_top = 12;
+ Fs->win_bottom = Fs->win_top + hh;
+
+ // Set receive window details
+ U8 buf[512];
+ StrPrint(buf, "Instant Message from: %s", name);
+ StrCpy(Fs->task_title, buf);
+ Fs->put_doc->desc = 'Msg';
+
+ // Clear document
+ DocClear(Fs->put_doc);
+
+ // Create Task for send window
+ StrPrint(buf, "@tol_send_im_loop(\"%s\");\n", name);
+ CTask *send_task = User(buf);
+ send_task->parent_task = Fs;
+ Sleep(200);
+
+ // Set send window details
+ send_task->put_doc->desc = '';
+ send_task->border_doc = NULL;
+
+ // Loop: send window follows recevive window
+ while (1) {
+ send_task->win_left = Fs->win_left;
+ send_task->win_right = Fs->win_right;
+ send_task->win_top = Fs->win_bottom + 3;
+ send_task->win_bottom = send_task->win_top + 6;
+ Sleep(1);
+ }
+}
+
+U8 im_marker[5] = {0x0a, 0x01, 0x03, 0x01, 0x14};
+U8 dialog_marker[6] = {0x00, 0x01, 0x01, 0x00, 0x01, 0x00};
+U8 dialog_text_marker[4] = {0x01, 0x0a, 0x01, 0x14};
+
+U0 @display_im(U8 *name, U8 *msg) {
+
+ U8 buf[512];
+ CDoc *doc = NULL;
+
+ // Check if we already have a window open to display messages.
+ PUSHFD
+ CLI
+
+ CCPU *c = &cpu_structs[0];
+ CTask *task = c->seth_task;
+ CTask *task1;
+ task1 = task->next_child_task;
+ while (task1 !=
+ (&task->next_child_task)(U8 *)-offset(CTask.next_sibling_task)) {
+ if (!StrICmp(task1->user_data, name)) {
+ doc = task1->put_doc;
+ WinToTop(task1);
+ goto im_window_exists;
+ }
+ task1 = task1->next_sibling_task;
+ }
+
+im_window_exists:
+ POPFD
+
+ // If not, create one.
+
+ if (!doc) {
+ StrPrint(buf, "@tol_init_im_window(\"%s\");\n", name);
+ CTask *new_im_task = User(buf);
+ Sleep(500);
+ new_im_task->user_data = StrNew(name);
+ doc = new_im_task->put_doc;
+ }
+
+ DocPrint(doc, "\dFG,BLUE\d%s: \dFG,BLACK\d%s\n\dFD\d", name, msg);
+ Play("st4BF#sC#");
+}
+
+U0 @handle_atom_stream(I64 s) {
+
+ I64 i;
+ I64 j;
+ I64 k;
+
+ U8 ch;
+
+ if (!MemCmp(aol_recv_buf + 12, dialog_marker, 6)) {
+
+ // Received a Dialog box message
+ U8 *title = aol_recv_buf + 0x14;
+ *(StrFirstOcc(title, "\x10"))(U8 *) = NULL;
+
+ for (i = title + StrLen(title) - aol_recv_buf; i < 1024 - 5; i++) {
+
+ if (!MemCmp(aol_recv_buf + i, dialog_text_marker, 4)) {
+ U8 *dialog_msg = CAlloc(1024);
+ StrPrint(dialog_msg, "%s\n\n", title);
+ I64 dialog_msg_length = *(aol_recv_buf + i + 0x4)(U8 *);
+
+ // TODO: Parse whatever subset of HTML that AOL uses for its dialog
+ // boxes
+
+ k = 0;
+ for (j = 0; j < dialog_msg_length; j++) {
+ ch = aol_recv_buf[i + 0x5 + j];
+ if (ch == '<')
+ k = 1;
+ if (ch == '>')
+ k = 2;
+ if (!k)
+ dialog_msg[StrLen(dialog_msg)] = ch;
+ if (k > 1)
+ k = 0;
+ }
+
+ PopUpOk(dialog_msg);
+ Free(dialog_msg);
+
+ return;
+ }
+ }
+ }
+
+ for (i = 0; i < 1024 - 5; i++) {
+ if (!MemCmp(aol_recv_buf + i, im_marker, 5)) {
+
+ // Received an Instant Message, copy into temp buffer
+ U8 *raw_im = CAlloc(1024);
+ I64 raw_im_length = *(aol_recv_buf + i + 0x5)(U8 *);
+ MemCpy(raw_im, aol_recv_buf + i + 0x6, raw_im_length);
+
+ // Translate '\n'
+ for (j = 0; j < StrLen(raw_im); j++)
+ if (raw_im[j] == 0x7f)
+ raw_im[j] = '\n';
+
+ // Separate sender name and message
+ U8 *msg = StrFirstOcc(raw_im, ": ") + 3;
+ U8 *name = raw_im;
+ *(StrFirstOcc(raw_im, ": "))(U8 *) = NULL;
+
+ @display_im(name, msg);
+
+ Free(raw_im);
+
+ return;
+ }
+ }
+
+ /*
+ if (!MemCmp(aol_recv_buf + 0x44, "i2", 2)) {
+ }
+ */
+}
+
+U0 @event_handler(I64 s) {
+
+ AolDataPacket *pkt = aol_recv_buf;
+ U8 *name;
+ U8 *msg;
+
+ MemSet(aol_recv_buf, NULL, 1024);
+ I64 len = recv(s, aol_recv_buf, 1024, 0);
+
+ if (!MemCmp(pkt->token, "AT", 2)) {
+ @handle_atom_stream(s);
+ };
+}
+
+I64 TOL() {
+
+ I64 err;
+
+ sockaddr_in addr;
+ I64 s = socket(AF_INET, SOCK_STREAM);
+ if (s == -1) {
+ PrintErr("Can't create socket.\n");
+ return -1;
+ }
+
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = inet_addr(AOL_HOST);
+ addr.sin_port = htons(AOL_PORT);
+
+ err = connect(s, &addr, sizeof(addr));
+ if (err == -1) {
+ PrintErr("Can't connect to AOL server.\n");
+ return -1;
+ }
+
+ "socket @ 0x%08x\n", s;
+ aol_socket = s;
+
+ "Connecting to Re-AOL ... \n";
+
+ @tol_send_init(s);
+ err = @wait_for_ack(s);
+ if (err) {
+ PrintErr("Malformed result; expected ACK\n");
+ return -1;
+ }
+ @tol_sign_in_as_guest(s);
+ @tol_send_sc(s);
+
+ "Signed in as Guest\n";
+
+ Sleep(2000);
+
+ U8 *room_name = "TempleOS";
+ @tol_join_room(s, room_name);
+
+ "Joined chat room '%s'\n", room_name;
+ "\n";
+
+ while (1) {
+ @event_handler(s);
+ Sleep(1);
+ }
+
+ return 0;
+} \ No newline at end of file
diff --git a/logo.png b/logo.png
new file mode 100644
index 0000000..71298e6
--- /dev/null
+++ b/logo.png
Binary files differ