From e45d1535dbc2b4c637b128ebf0f98f582bb568e6 Mon Sep 17 00:00:00 2001 From: minexew Date: Fri, 28 Feb 2020 18:51:14 +0100 Subject: [PATCH] Hello Shrine --- Adam/ADefine.HC | 2 +- Adam/HwSupp/PCNet.HC | 410 ++++++++++++++ Adam/HwSupp/Pci.HC | 119 +++++ Adam/MakeAdam.HC | 2 + Adam/Net/Arp.HC | 125 +++++ Adam/Net/Dhcp.HC | 275 ++++++++++ Adam/Net/Dns.HC | 538 +++++++++++++++++++ Adam/Net/Ethernet.HC | 55 ++ Adam/Net/Http.HC | 197 +++++++ Adam/Net/IPv4.HC | 252 +++++++++ Adam/Net/Icmp.HC | 52 ++ Adam/Net/MakeSnailNet.HC | 45 ++ Adam/Net/NativeSocket.HC | 264 +++++++++ Adam/Net/NetFifo.HC | 79 +++ Adam/Net/NetHandlerTask.HC | 38 ++ Adam/Net/Netcfg.HC | 139 +++++ Adam/Net/SnailLib.HC | 148 +++++ Adam/Net/Socket.HC | 29 + Adam/Net/Tcp.HC | 920 ++++++++++++++++++++++++++++++++ Adam/Net/Udp.HC | 243 +++++++++ Adam/Net/Url.HC | 91 ++++ Adam/Net/UrlParse.HC | 73 +++ Apps/Lsh.HC | 522 ++++++++++++++++++ Apps/Mfa.HC | 98 ++++ Apps/Pkg.HC | 463 ++++++++++++++++ Apps/Wget.HC | 25 + Demo/Network/TcpEchoClient.HC | 49 ++ Demo/Network/TcpEchoServer.HC | 54 ++ Demo/Network/UdpListen.HC | 50 ++ Demo/Network/UdpSend.HC | 32 ++ Demo/Network/tcp-echo-client.py | 15 + Demo/Network/tcp-echo-server.py | 22 + Demo/Network/udp-listen.py | 14 + Demo/Network/udp-send.py | 11 + Doc/Comm.HC | 12 + Doc/Start.DD | 4 +- Kernel/KGlbls.HC | 2 +- Misc/DoDistro.HC | 4 +- Misc/PalConEmu.HC | 8 + Misc/PalMonokai.HC | 8 + Misc/PalUbuntu.HC | 8 + Once.HC | 6 + mfa.py | 134 +++++ snail.py | 90 ++++ 44 files changed, 5722 insertions(+), 5 deletions(-) create mode 100644 Adam/HwSupp/PCNet.HC create mode 100644 Adam/HwSupp/Pci.HC create mode 100644 Adam/Net/Arp.HC create mode 100644 Adam/Net/Dhcp.HC create mode 100644 Adam/Net/Dns.HC create mode 100644 Adam/Net/Ethernet.HC create mode 100644 Adam/Net/Http.HC create mode 100644 Adam/Net/IPv4.HC create mode 100644 Adam/Net/Icmp.HC create mode 100644 Adam/Net/MakeSnailNet.HC create mode 100644 Adam/Net/NativeSocket.HC create mode 100644 Adam/Net/NetFifo.HC create mode 100644 Adam/Net/NetHandlerTask.HC create mode 100644 Adam/Net/Netcfg.HC create mode 100644 Adam/Net/SnailLib.HC create mode 100644 Adam/Net/Socket.HC create mode 100644 Adam/Net/Tcp.HC create mode 100644 Adam/Net/Udp.HC create mode 100644 Adam/Net/Url.HC create mode 100644 Adam/Net/UrlParse.HC create mode 100644 Apps/Lsh.HC create mode 100644 Apps/Mfa.HC create mode 100644 Apps/Pkg.HC create mode 100644 Apps/Wget.HC create mode 100644 Demo/Network/TcpEchoClient.HC create mode 100644 Demo/Network/TcpEchoServer.HC create mode 100644 Demo/Network/UdpListen.HC create mode 100644 Demo/Network/UdpSend.HC create mode 100644 Demo/Network/tcp-echo-client.py create mode 100644 Demo/Network/tcp-echo-server.py create mode 100644 Demo/Network/udp-listen.py create mode 100755 Demo/Network/udp-send.py create mode 100644 Misc/PalConEmu.HC create mode 100644 Misc/PalMonokai.HC create mode 100644 Misc/PalUbuntu.HC create mode 100755 mfa.py create mode 100755 snail.py diff --git a/Adam/ADefine.HC b/Adam/ADefine.HC index 311be97..3a47cbf 100644 --- a/Adam/ADefine.HC +++ b/Adam/ADefine.HC @@ -4,7 +4,7 @@ U0 LoadDocDefines() { CBinFile *bfh=mem_boot_base-sizeof(CBinFile); - DefinePrint("DD_OS_NAME_VERSION","TempleOS V%0.2f",sys_os_version); + DefinePrint("DD_OS_NAME_VERSION","Shrine %0.2f",sys_os_version); DefinePrint("DD_TEMPLEOS_AGE","%0.1f", (Now-Str2Date("8/1/2003"))/ToF64(1<<32)/CDATE_YEAR_DAYS); diff --git a/Adam/HwSupp/PCNet.HC b/Adam/HwSupp/PCNet.HC new file mode 100644 index 0000000..46f5475 --- /dev/null +++ b/Adam/HwSupp/PCNet.HC @@ -0,0 +1,410 @@ +// vim: set ft=c: + +#include "::/Adam/HwSupp/Pci" + +// Significantly based on http://wiki.osdev.org/AMD_PCNET + +#define PCNET_DEVICE_ID 0x2000 +#define PCNET_VENDOR_ID 0x1022 + +#define PCNET_COMMAND_IOEN (1<<0) +#define PCNET_COMMAND_MEMEN (1<<1) +#define PCNET_COMMAND_BMEN (1<<2) +#define PCNET_COMMAND_SCYCEN (1<<3) +#define PCNET_COMMAND_MWIEN (1<<4) +#define PCNET_COMMAND_VGASNOOP (1<<5) +#define PCNET_COMMAND_PERREN (1<<6) +#define PCNET_COMMAND_ADSTEP (1<<7) +#define PCNET_COMMAND_SERREN (1<<8) +#define PCNET_COMMAND_FBTBEN (1<<9) + +#define PCNET_STATUS_FBTBC (1<<7) +#define PCNET_STATUS_DATAPERR (1<<8) +#define PCNET_STATUS_DEVSEL_BIT 9 +#define PCNET_STATUS_STABORT (1<<11) +#define PCNET_STATUS_RTABORT (1<<12) +#define PCNET_STATUS_RMABORT (1<<13) +#define PCNET_STATUS_SERR (1<<14) +#define PCNET_STATUS_PERR (1<<15) + +#define PCNET_WD_RESET 0x14 + +#define PCNET_DW_RDP 0x10 +#define PCNET_DW_RAP 0x14 +#define PCNET_DW_RESET 0x18 + +#define PCNET_CSR0_INIT (1<<0) +#define PCNET_CSR0_STRT (1<<1) +#define PCNET_CSR0_STOP (1<<2) +#define PCNET_CSR0_IENA (1<<6) +#define PCNET_CSR0_IDON (1<<8) +#define PCNET_CSR0_TINT (1<<9) +#define PCNET_CSR0_RINT (1<<10) + +#define PCNET_CSR3_BSWP (1<<2) +#define PCNET_CSR3_IDONM (1<<8) +#define PCNET_CSR3_TINTM (1<<9) +#define PCNET_CSR3_RINTM (1<<10) + +#define PCNET_CSR4_TXSTRT (1<<3) +#define PCNET_CSR4_ASTRP_RCV (1<<10) +#define PCNET_CSR4_APAD_XMT (1<<11) + +#define PCNET_TXFIFO_FULL (-1) + +// TODO: this should be configurable +#define PCNET_NUM_RX_LOG2 5 +#define PCNET_NUM_TX_LOG2 3 + +#define rx_buffer_count (1<code_heap); + tdes_phys = MAlloc(tdes_size, Fs->code_heap); + + if (rdes_phys + rdes_size > 0x100000000 || tdes_phys + tdes_size > 0x100000000) { + "$FG,4$PCNetAllocBuffers: rdes_phys=%08Xh tdes_phys=%08Xh\n$FG$", rdes_phys, tdes_phys; + return -1; + } + + rdes = rdes_phys + dev.uncached_alias; + tdes = tdes_phys + dev.uncached_alias; + + I64 rx_buffers_size = ETHERNET_FRAME_SIZE * rx_buffer_count; + I64 tx_buffers_size = ETHERNET_FRAME_SIZE * tx_buffer_count; + + // TODO: shouldn't these be uncached as well? + rx_buffers = MAlloc(rx_buffers_size, Fs->code_heap); + tx_buffers = MAlloc(tx_buffers_size, Fs->code_heap); + + if (rx_buffers + rx_buffers_size > 0x100000000 || tx_buffers + tx_buffers_size > 0x100000000) { + "$FG,4$PCNetAllocBuffers: rx_buffers=%08Xh tx_buffers=%08Xh\n$FG$", rx_buffers, tx_buffers; + return -1; + } + + for (i = 0; i < rx_buffer_count; i++) + PCNetInitDE(rx_buffers, rdes, i, FALSE); + + for (i = 0; i < tx_buffer_count; i++) + PCNetInitDE(tx_buffers, tdes, i, TRUE); + + //"rdes: %08Xh\ttdes: %08X\n", rdes, tdes; + //"rbuf: %08Xh\ttbuf: %08X\n", rx_buffers, tx_buffers; + + return 0; +} + +interrupt U0 PCNetIrq() { + U32 csr0 = readCSR32(0); + + while (driverOwns(rdes, rx_buffer_ptr)) { + //if (csr0 & PCNET_CSR0_RINT) { + //"Int reason %08X\n", csr0; + + U8* buffer; + U16 length; + I64 index = PCNetRxPacket(&buffer, &length); + + if (index >= 0) { + //"[%d] Rx %d B\n", index, length; + NetFifoPushCopy(buffer, length); + PCNetReleaseRxPacket(index); + } + + writeCSR32(0, csr0 | PCNET_CSR0_RINT); + } + + *(dev.uncached_alias + LAPIC_EOI)(U32*) = 0; +} + +static U0 PCNetInit(I64 bus, I64 dev_, I64 fun) { + CPciDevInfo info; + + PciGetDevInfo(&info, bus, dev_, fun); + //PciDumpInfo(&info); + + if (info.vendor_id != PCNET_VENDOR_ID || info.device_id != PCNET_DEVICE_ID) + throw; + + U16 config = PCNET_COMMAND_IOEN | PCNET_COMMAND_BMEN; + PCIWriteU16(bus, dev_, fun, PCI_REG_COMMAND, config); + + config = PCIReadU16(bus, dev_, fun, PCI_REG_COMMAND); + + pcnet_iob = (PCIReadU32(bus, dev_, fun, PCI_REG_BAR0) & ~(0x0000001f)); + //U32 membase = (PCIReadU32(bus, dev_, fun, PCI_REG_BAR1) & ~(0x0000001f)); + //"PCNet iobase: %016Xh\n", pcnet_iob; + //"PCNet membase: %08Xh\n", membase; + + // Reset the card to get into defined state + PCNetReset(); + Sleep(1); + + // Enter 32-bit mode + OutU32(pcnet_iob + PCNET_DW_RDP, 0); + + // SWSTYLE + U32 csr58 = readCSR32(58); + csr58 &= 0xfff0; + csr58 |= 2; + writeCSR32(58, csr58); + + PCNetReadEeprom(0, my_mac, 6); + + if (PCNetAllocBuffers() < 0) + return; + + U8* setup = MAlloc(sizeof(CPCNetBufferSetup), Fs->code_heap); + CPCNetBufferSetup* u_setup = setup + dev.uncached_alias; + + u_setup->mode = 0; // see CSR15 in spec + u_setup->rlen = (PCNET_NUM_RX_LOG2 << 4); + u_setup->tlen = (PCNET_NUM_TX_LOG2 << 4); + MemCpy(u_setup->mac, my_mac, 6); + "PCNet MAC: %02X:%02X:%02X:%02X:%02X:%02X\n", + u_setup->mac[0], u_setup->mac[1], u_setup->mac[2], u_setup->mac[3], u_setup->mac[4], u_setup->mac[5]; + u_setup->reserved = 0; + MemSet(u_setup->ladr, 0, 8); + u_setup->rxbuf = rdes_phys; + u_setup->txbuf = tdes_phys; + + U32 p_setup = setup; + writeCSR32(1, p_setup & 0xffff); + writeCSR32(2, p_setup >> 16); + + U32 csr3 = readCSR32(3); + csr3 &= ~PCNET_CSR3_BSWP; // disable big-endian + csr3 &= ~PCNET_CSR3_RINTM; // enable Rx interruot + csr3 |= PCNET_CSR3_IDONM; // mask-out Init Done Interrupt + csr3 |= PCNET_CSR3_TINTM; // mask-out Tx interrupt + writeCSR32(3, csr3); + + U32 csr4 = readCSR32(4); + csr4 |= PCNET_CSR4_APAD_XMT; // auto pad transmit + writeCSR32(4, csr4); + + // Upload configuration + writeCSR32(0, readCSR32(0) | PCNET_CSR0_INIT | PCNET_CSR0_IENA); + + while (!(readCSR32(0) & PCNET_CSR0_IDON)) { + Yield; + } + + // Exit config mode + U32 csr0 = readCSR32(0); + csr0 &= ~(PCNET_CSR0_INIT | PCNET_CSR0_STOP); + csr0 |= PCNET_CSR0_STRT; + writeCSR32(0, csr0); + + // Init interrupt + //IntEntrySet(info.interrupt_line, &PCNetIrq, IDTET_IRQ); + IntEntrySet(0x40, &PCNetIrq, IDTET_IRQ); + IntEntrySet(0x41, &PCNetIrq, IDTET_IRQ); + IntEntrySet(0x42, &PCNetIrq, IDTET_IRQ); + IntEntrySet(0x43, &PCNetIrq, IDTET_IRQ); + PciRerouteInterrupts(0x40); + + Sleep(100); + + Free(setup); +} + +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 = PCNetAllocTxPacket(&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) { + return PCNetFinishTxPacket(index); +} + +U8* EthernetGetAddress() { + return my_mac; +} + +I64 EthernetInit() { + I64 b, d, f; + + if (pcnet_iob != 0) + return 0; + + if (PciFindByID(PCNET_VENDOR_ID, PCNET_DEVICE_ID, &b, &d, &f)) { + //"PCNet @ %d:%d:%d\n", b, d, f; + PCNetInit(b, d, f); + return 0; + } + + return -1; +} diff --git a/Adam/HwSupp/Pci.HC b/Adam/HwSupp/Pci.HC new file mode 100644 index 0000000..5f44dff --- /dev/null +++ b/Adam/HwSupp/Pci.HC @@ -0,0 +1,119 @@ +// vim: set ft=c: + +#define PCI_REG_VENDOR_ID 0x00 +#define PCI_REG_DEVICE_ID 0x02 +#define PCI_REG_COMMAND 0x04 +#define PCI_REG_STATUS 0x06 +#define PCI_REG_REVISION_ID 0x08 +#define PCI_REG_PROG_IF 0x09 +#define PCI_REG_SUBCLASS 0x0a +#define PCI_REG_CLASS 0x0b +#define PCI_REG_CACHE_LINE_SIZE 0x0c +#define PCI_REG_LATENCY_TIMER 0x0d +#define PCI_REG_HEADER_TYPE 0x0e +#define PCI_REG_BIST 0x0f +#define PCI_REG_BAR0 0x10 +#define PCI_REG_BAR1 0x14 +#define PCI_REG_BAR2 0x18 +#define PCI_REG_BAR3 0x1c +#define PCI_REG_BAR4 0x20 +#define PCI_REG_BAR5 0x24 + +#define PCI_REG_INTERRUPT_LINE 0x3C + +class CPciDevInfo { + U16 vendor_id, device_id; + U16 command, status; + U8 class_, subclass, prog_if, revision_id; + U8 cache_line_size, latency_timer, header_type, bist; + U32 bar[6]; + + // ...a lot of trash comes here... + + U8 interrupt_line; +}; + +U0 PciDumpInfo(CPciDevInfo* info) { + I64 i; + + "vendor_id=%04Xh\tdevice_id=%04Xh\n", info->vendor_id, info->device_id; + "command=%04Xh\tstatus=%04Xh\n", info->command, info->status; + "revision_id=%02Xh\tprog_if=%02Xh\n", info->revision_id, info->prog_if; + "subclass=%02Xh\tclass_=%02Xh\n", info->subclass, info->class_; + "cache_line_size=%02Xh\tlatency_timer=%02Xh\n", info->cache_line_size, info->latency_timer; + "header_type=%02Xh\tbist=%02Xh\n", info->header_type, info->bist; + + for (i = 0; i < 6; i++) + "BAR[%d]=%08X\n", i, info->bar[i]; + + "interrupt_line=%02Xh\n", info->interrupt_line; +} + +Bool PciFindByID(U16 vendor_id, U16 device_id, I64* bus_out, I64* dev_out, I64* fun_out) { + I64 vendor, b, d, f, timeout = 32 * 8 * 2; + + if (dev.pci_head.next != &dev.pci_head) + return FALSE; + + for (b = 0; b < sys_pci_busses; b++) { + for (d = 0; d < 32; d++) { + for (f = 0; f < 8; f++) { + vendor = PCIReadU16(b, d, f, PCI_REG_VENDOR_ID); + + if (vendor != 0xFFFF) { + if (vendor == vendor_id && PCIReadU16(b, d, f, PCI_REG_DEVICE_ID) == device_id) { + *bus_out = b; + *dev_out = d; + *fun_out = f; + return TRUE; + } + timeout = 32 * 8 * 2; + } + else if (sys_pci_busses == 256 && --timeout <= 0) { + break; + } + } + } + } + + return FALSE; +} + +U0 PciGetDevInfo(CPciDevInfo* info_out, I64 bus, I64 dev, I64 fun) { + // TODO: do a bunch of PCIReadU32 in a loop instead + info_out->vendor_id = PCIReadU16(bus, dev, fun, PCI_REG_VENDOR_ID); + info_out->device_id = PCIReadU16(bus, dev, fun, PCI_REG_DEVICE_ID); + info_out->command = PCIReadU16(bus, dev, fun, PCI_REG_COMMAND); + info_out->status = PCIReadU16(bus, dev, fun, PCI_REG_STATUS); + info_out->revision_id = PCIReadU8(bus, dev, fun, PCI_REG_REVISION_ID); + info_out->prog_if = PCIReadU8(bus, dev, fun, PCI_REG_PROG_IF); + info_out->subclass = PCIReadU8(bus, dev, fun, PCI_REG_SUBCLASS); + info_out->class_ = PCIReadU8(bus, dev, fun, PCI_REG_CLASS); + info_out->cache_line_size = PCIReadU8(bus, dev, fun, PCI_REG_CACHE_LINE_SIZE); + info_out->latency_timer = PCIReadU8(bus, dev, fun, PCI_REG_LATENCY_TIMER); + info_out->header_type = PCIReadU8(bus, dev, fun, PCI_REG_HEADER_TYPE); + info_out->bist = PCIReadU8(bus, dev, fun, PCI_REG_BIST); + info_out->bar[0] = PCIReadU32(bus, dev, fun, PCI_REG_BAR0); + info_out->bar[1] = PCIReadU32(bus, dev, fun, PCI_REG_BAR1); + info_out->bar[2] = PCIReadU32(bus, dev, fun, PCI_REG_BAR2); + info_out->bar[3] = PCIReadU32(bus, dev, fun, PCI_REG_BAR3); + info_out->bar[4] = PCIReadU32(bus, dev, fun, PCI_REG_BAR4); + info_out->bar[5] = PCIReadU32(bus, dev, fun, PCI_REG_BAR5); + + info_out->interrupt_line = PCIReadU8(bus, dev, fun, PCI_REG_INTERRUPT_LINE); +} + +#define INT_DEST_CPU 0 + +U0 PciRerouteInterrupts(I64 base) { + 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[INT_DEST_CPU] << 24; + *da = IOREDTAB + i * 2; + *_d = 0x4000 + base + i; + } +} diff --git a/Adam/MakeAdam.HC b/Adam/MakeAdam.HC index 2d5ca21..72713c9 100644 --- a/Adam/MakeAdam.HC +++ b/Adam/MakeAdam.HC @@ -30,3 +30,5 @@ LBts(&sys_run_level,RLf_DOC); #include "AMouse" #include "Host" Cd("..");; + +#include "::/Adam/Net/MakeSnailNet" diff --git a/Adam/Net/Arp.HC b/Adam/Net/Arp.HC new file mode 100644 index 0000000..7a82096 --- /dev/null +++ b/Adam/Net/Arp.HC @@ -0,0 +1,125 @@ +// vim: set ft=c: + +// 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; + + 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/Adam/Net/Dhcp.HC b/Adam/Net/Dhcp.HC new file mode 100644 index 0000000..88fad40 --- /dev/null +++ b/Adam/Net/Dhcp.HC @@ -0,0 +1,275 @@ +// vim: set ft=c: + +#include "::/Adam/Net/Socket" + +#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; + + if (have_type && have_dns && have_subnet && have_router) { + *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/Adam/Net/Dns.HC b/Adam/Net/Dns.HC new file mode 100644 index 0000000..44a2bb6 --- /dev/null +++ b/Adam/Net/Dns.HC @@ -0,0 +1,538 @@ +// vim: set ft=c: + +#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) { + "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_ntoa((curr->ai_addr(sockaddr_in*))->sin_addr); + 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/Adam/Net/Ethernet.HC b/Adam/Net/Ethernet.HC new file mode 100644 index 0000000..3f355c2 --- /dev/null +++ b/Adam/Net/Ethernet.HC @@ -0,0 +1,55 @@ +// vim: set ft=c: + +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/Adam/Net/Http.HC b/Adam/Net/Http.HC new file mode 100644 index 0000000..7acc92f --- /dev/null +++ b/Adam/Net/Http.HC @@ -0,0 +1,197 @@ +// vim: set ft=cpp: + +#include "::/Adam/Net/Socket" +#include "::/Adam/Net/UrlParse" + +#define HTTP_ECONNECT (-101) +#define HTTP_EPROTOCOL (-102) +#define HTTP_EREQUEST (-103) +#define HTTP_EREDIRECT (-104) +#define HTTP_EEOF (-105) +#define HTTP_ECONTENTLENGTH (-106) + +#define HTTP_MAX_REDIRECTS 5 +#define HTTP_USER_AGENT "SnailNet ($TX+CX,"TempleOS",D="DD_OS_NAME_VERSION"$)" + +/** + * @param len_out (required) requires the content length, or -1 if unspecified + * @return socket (>= 0) on success, error code on failure + */ +I64 HttpOpenGet(U8* host, U16 port = 0, U8* path, I64* len_out, + I64 allowed_redirects = HTTP_MAX_REDIRECTS) { + U8 line[256]; + I64 error = 0; + + if (!port) + port = 80; + + // Should this be done here though? + if (*path == 0) + path = "/"; + + //"Connect(%s:%d)\n", host, port; + I64 sock = create_connection(host, port); + //"create_connection: %d\n", sock; + if (sock >= 0) { + StrPrint(line, "GET %s HTTP/1.0\r\n", path); + sendString(sock, line, 0); + StrPrint(line, "Host: %s\r\n", host); + sendString(sock, line, 0); + sendString(sock, "User-Agent: " HTTP_USER_AGENT "\r\n", 0); + sendString(sock, "\r\n", 0); + + Bool haveHTTP = FALSE; + U8* location = NULL; + + I64 code = -1; + + *len_out = -1; + + while (1) { + error = recvLine(sock, line, sizeof(line), 0); + + if (error < 0) { + break; + } + else if (error == 0) { + if (!haveHTTP) + error = HTTP_EPROTOCOL; + break; + } + + U8* delim; + + //"%s\n", line; + if (!haveHTTP) { + delim = StrFirstOcc(line, " "); + if (delim && StrNCmp(line, "HTTP/", 5) == 0) { + code = Str2I64(delim + 1); + + if (code >= 200 && code <= 399) { + haveHTTP = TRUE; + } + else { + error = HTTP_EREQUEST; + break; + } + } + else { + error = HTTP_EREQUEST; + break; + } + } + else { + delim = StrFirstOcc(line, ":"); + + if (!delim) { + error = HTTP_EPROTOCOL; + break; + } + + *delim = 0; + + do { delim++; } + while (*delim == ' '); + + //"%s=%s\n", line, delim; + if (!StrCmp(line, "Content-Length")) { + StrScan(delim, "%d", len_out); + } + else if (!StrCmp(line, "Location")) { + // This will leak on malformed response + location = StrNew(delim); + } + } + } + + // HTTP Code 3xx -- Redirection + if (!error && code >= 300 && code <= 399) { + if (allowed_redirects > 0) { + CUrl curl; + UrlInit(&curl); + + if (UrlParse(location, &curl)) { + if (!StrCmp(curl.protocol, "http")) { + close(sock); + sock = HttpOpenGet(curl.host, curl.port, curl.path, len_out, allowed_redirects - 1); + + if (sock < 0) + error = sock; + } + else + error = HTTP_EPROTOCOL; + } + else + error = HTTP_EREDIRECT; + + UrlFree(&curl); + } + else { + error = HTTP_EREDIRECT; + } + } + + Free(location); + } + else + error = HTTP_ECONNECT; + + if (error) { + close(sock); + return error; + } + else { + return sock; + } +} + +I64 HttpGet(U8* host, U16 port = 0, U8* path, U8** data_out = NULL, I64* len_out = NULL) { + I64 error = 0; + I64 len = 0; + I64 sock = HttpOpenGet(host, port, path, &len); + + if (sock > 0) { + if (len >= 0) { + U8* data = MAlloc(1 + len); + I64 total = 0; + + while (total < len) { + I64 step = len - total; + + if (step > 1024) + step = 1024; + + I64 got = recv(sock, data + total, step, 0); + + if (got <= 0) { + error = HTTP_EEOF; + break; + } + + total += got; + } + + if (error) { + Free(data); + } + else { + if (data_out) { + data[total] = 0; + *data_out = data; + } + + if (len_out) + *len_out = len; + } + } + else + // We currently don't handle dynamic-length HTTP responses + error = HTTP_ECONTENTLENGTH; + + close(sock); + } + else + error = sock; + + return error; +} diff --git a/Adam/Net/IPv4.HC b/Adam/Net/IPv4.HC new file mode 100644 index 0000000..e467a4e --- /dev/null +++ b/Adam/Net/IPv4.HC @@ -0,0 +1,252 @@ +// vim: set ft=c: + +#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; +}; + +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)) { + // 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); + "$FG,6$IPv4: Failed to resolve address %s\n$FG$", inet_ntoa(in); + 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; + + 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/Adam/Net/Icmp.HC b/Adam/Net/Icmp.HC new file mode 100644 index 0000000..84075d3 --- /dev/null +++ b/Adam/Net/Icmp.HC @@ -0,0 +1,52 @@ +// vim: set ft=c: + +#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; +}; + +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 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_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/Adam/Net/MakeSnailNet.HC b/Adam/Net/MakeSnailNet.HC new file mode 100644 index 0000000..50dad43 --- /dev/null +++ b/Adam/Net/MakeSnailNet.HC @@ -0,0 +1,45 @@ +// vim: set ft=c: + +#exe { + #include "::/Adam/HwSupp/Pci" + + U8* native_driver = NULL; + I64 b, d, f; + + if (PciFindByID(0x1022, 0x2000, &b, &d, &f)) + native_driver = "PCNet"; + + // If we're using the native stack, load it system-wide + if (native_driver != NULL) { + StreamPrint("U8* SNAILNET_NATIVE_DRIVER = \"%s\";\n", native_driver); + + // Hardware support + StreamPrint("#include \"::/Adam/Net/NetFifo\""); + StreamPrint("#include \"::/Adam/HwSupp/%s\"", native_driver); + + // Contains a lot of common definitions, probably should be cleaned up + StreamPrint("#include \"::/Adam/Net/NativeSocket\""); + + // L2 + StreamPrint("#include \"::/Adam/Net/Ethernet\""); + + // L3 + StreamPrint("#include \"::/Adam/Net/Arp\""); + StreamPrint("#include \"::/Adam/Net/IPv4\""); + + // L4 + StreamPrint("#include \"::/Adam/Net/Icmp\""); + StreamPrint("#include \"::/Adam/Net/Tcp\""); + StreamPrint("#include \"::/Adam/Net/Udp\""); + + // L7 + StreamPrint("#include \"::/Adam/Net/Dns\""); + + // Handler Task + StreamPrint("#include \"::/Adam/Net/NetHandlerTask\""); + + StreamPrint("#include \"::/Adam/Net/Netcfg\""); + } + else + StreamPrint("U8* SNAILNET_NATIVE_DRIVER = NULL;\n", native_driver); +} diff --git a/Adam/Net/NativeSocket.HC b/Adam/Net/NativeSocket.HC new file mode 100644 index 0000000..1024e7a --- /dev/null +++ b/Adam/Net/NativeSocket.HC @@ -0,0 +1,264 @@ +// vim: set ft=c: + +#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 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_aton(U8* cp, in_addr* inp) { + // FIXME: error handling + I64 a, b, c, d; + StrScan(cp, "%d.%d.%d.%d", &a, &b, &c, &d); + inp->s_addr = (a | (b << 8) | (c << 16) | (d << 24)); + return 0; +} + +U8* inet_ntoa(in_addr in) { + static U8 buffer[16]; + StrPrint(buffer, "%d.%d.%d.%d", in.s_addr & 0xff, (in.s_addr >> 8) & 0xff, + (in.s_addr >> 16) & 0xff, (in.s_addr >> 24) & 0xff); + return buffer; +} + +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/Adam/Net/NetFifo.HC b/Adam/Net/NetFifo.HC new file mode 100644 index 0000000..fffa507 --- /dev/null +++ b/Adam/Net/NetFifo.HC @@ -0,0 +1,79 @@ +// vim: set ft=c: + +// 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/Adam/Net/NetHandlerTask.HC b/Adam/Net/NetHandlerTask.HC new file mode 100644 index 0000000..d1d5228 --- /dev/null +++ b/Adam/Net/NetHandlerTask.HC @@ -0,0 +1,38 @@ +// vim: set ft=c: + +U0 HandleNetFifoEntry(CNetFifoEntry* e) { + CEthFrame l2_frame; + + if (EthernetFrameParse(&l2_frame, e->frame, e->length) < 0) + return; + + //"NetFifoEntry %04X\n", l2_frame.ethertype; + + CL3Protocol* l3 = l3_protocols; + + while (l3) { + if (l3->ethertype == l2_frame.ethertype) { + l3->handler(&l2_frame); + break; + } + l3 = l3->next; + } +} + +U0 NetHandlerTask(I64) { + EthernetInit(); + + while (1) { + CNetFifoEntry* e = NetFifoPull(); + + if (e) { + HandleNetFifoEntry(e); + } + else { + LBts(&Fs->task_flags, TASKf_IDLE); + Yield; + } + } +} + +netfifo_handler_task = Spawn(&NetHandlerTask, NULL, "NetHandler"); diff --git a/Adam/Net/Netcfg.HC b/Adam/Net/Netcfg.HC new file mode 100644 index 0000000..dcaac1f --- /dev/null +++ b/Adam/Net/Netcfg.HC @@ -0,0 +1,139 @@ +// vim: set ft=c: + +#include "::/Adam/Net/Dhcp" + +#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$DhcpParseOffer: 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); + "$FG,2$Obtained IP address %s\n$FG$", inet_ntoa(in); + 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/Adam/Net/SnailLib.HC b/Adam/Net/SnailLib.HC new file mode 100644 index 0000000..2fc0579 --- /dev/null +++ b/Adam/Net/SnailLib.HC @@ -0,0 +1,148 @@ +// vim: set ft=cpp: + +#include "::/Doc/Comm" + +#define CMD_SOCKET 1 +#define CMD_CLOSE 2 +#define CMD_CONNECT_TCP 3 +#define CMD_SEND 4 +#define CMD_RECV 5 +#define CMD_HELLO 0xAA + +#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 SNAIL_COM 3 +#define SNAIL_TIMEOUT 500 +#define SNAIL_FRAME_SIZE 112 + +static CComm* snail_comm; + +static U8 ReadByte() { + U8 chr; + while (1) { + if (FifoU8Rem(snail_comm->RX_fifo, &chr)) + return chr; + else + Yield; + } +} + +static I8 ReadI8() { + I8 chr; + while (1) { + if (FifoU8Rem(snail_comm->RX_fifo, &chr)) + return chr; + else + Yield; + } +} + +static U0 ReadBlock(U8* buf, I64 count) { + while (count) { + if (FifoU8Rem(snail_comm->RX_fifo, buf)) { + buf++; + count--; + } + else Yield; + } +} + +I64 SocketInit() { + U8 chr; + + snail_comm = CommInit8n1(SNAIL_COM, 115200); + while (FifoU8Rem(snail_comm->RX_fifo, &chr)) {} + + CommPutChar(SNAIL_COM, CMD_HELLO); + + I64 max_time = cnts.jiffies + SNAIL_TIMEOUT * JIFFY_FREQ / 1000; + + do { + if (FifoU8Rem(snail_comm->RX_fifo, &chr)) { + if (chr == CMD_HELLO) { + return 0; + } + else { + "$FG,6$Failed to initialize Snail -- wrong hello 0x%02X\n", chr; + "Are you using the right version of snail.py?\n$FG$"; + throw; + } + return chr; + } + else + Yield; + } + while (cnts.jiffies < max_time); + + "$FG,6$Failed to initialize Snail -- make sure COM%d " + "is properly configured & snail.py is running!\n$FG$", SNAIL_COM; + throw; +} + +I64 socket(I64 domain, I64 type) { + CommPutChar(SNAIL_COM, CMD_SOCKET); + CommPutChar(SNAIL_COM, domain); + CommPutChar(SNAIL_COM, type); + return ReadI8(); +} + +I64 close(I64 sockfd) { + CommPutChar(SNAIL_COM, CMD_CLOSE); + CommPutChar(SNAIL_COM, sockfd); + return ReadI8(); +} + +I64 create_connection(U8* addr, U16 port) { + I64 sockfd = socket(AF_INET, SOCK_STREAM); + + if (sockfd < 0) + return sockfd; + + CommPutChar(SNAIL_COM, CMD_CONNECT_TCP); + CommPutChar(SNAIL_COM, sockfd); + CommPutChar(SNAIL_COM, StrLen(addr)); + CommPutS(SNAIL_COM, addr); + CommPutChar(SNAIL_COM, port & 0xff); + CommPutChar(SNAIL_COM, port >> 8); + + I64 error = ReadI8(); + if (error < 0) { + close(sockfd); + return error; + } + + return sockfd; +} + +I64 recv(I64 sockfd, U8* buf, I64 len, I64 flags) { + // This will be problematic for UDP + if (len > SNAIL_FRAME_SIZE) + len = SNAIL_FRAME_SIZE; + + CommPutChar(SNAIL_COM, CMD_RECV); + CommPutChar(SNAIL_COM, sockfd); + CommPutChar(SNAIL_COM, len); + CommPutChar(SNAIL_COM, flags); + I64 got = ReadI8(); + + if (got > 0) + ReadBlock(buf, got); + + return got; +} + +I64 send(I64 sockfd, U8* buf, I64 len, I64 flags) { + // FIXME: use frames + CommPutChar(SNAIL_COM, CMD_SEND); + CommPutChar(SNAIL_COM, sockfd); + CommPutChar(SNAIL_COM, len); + CommPutChar(SNAIL_COM, flags); + CommPutBlk(SNAIL_COM, buf, len); + return ReadI8(); +} diff --git a/Adam/Net/Socket.HC b/Adam/Net/Socket.HC new file mode 100644 index 0000000..7e5b818 --- /dev/null +++ b/Adam/Net/Socket.HC @@ -0,0 +1,29 @@ +// vim: set ft=c: + +#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 sendString(I64 sockfd, U8* str, I64 flags) { + return send(sockfd, str, StrLen(str), flags); +} diff --git a/Adam/Net/Tcp.HC b/Adam/Net/Tcp.HC new file mode 100644 index 0000000..4027a49 --- /dev/null +++ b/Adam/Net/Tcp.HC @@ -0,0 +1,920 @@ +// vim: set ft=c: + +// 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_WINDOW_SIZE 8192 + +#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 10 +#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; +}; + +// 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); +} + +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); +} + +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; + // FIXME: If the packet is SYN or FIN, we also need to queue for retransmit! + return TcpPacketFinish(index, s->local_addr, s->remote_addr, frame, 0, NULL); +} + +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; + } +} + +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) { + // 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) { + 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) + 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"; + while (s->state == TCP_STATE_ESTABLISHED && 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->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; +} + +I64 TcpSocketSendto(CTcpSocket* s, U8* buf, I64 len, I64 flags, sockaddr_in* dest_addr, I64 addrlen) { + no_warn dest_addr; + no_warn addrlen; + no_warn flags; + + I64 sent_total = 0; + + while (s->state == TCP_STATE_ESTABLISHED && 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 { + TcpSocketCheckSendBufs(s); + Yield; + } + } + else { + if (can_send > len) + can_send = len; + + if (can_send > s->mss) + can_send = s->mss; + + TcpSendData2(s, TCP_FLAG_ACK, buf, can_send); + 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; + + // FIXME FIXME FIXME FIXME + tcp_bound_sockets[new_socket->local_port] = 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) { + 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) { + s->rcv_nxt++; + s->state = TCP_STATE_CLOSE_WAIT; + must_ack = TRUE; + } + } + } + + 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 = 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() { + tcp_bound_sockets = MAlloc(65536 * sizeof(CTcpSocket*)); + MemSet(tcp_bound_sockets, 0, 65536 * sizeof(CTcpSocket*)); +} + +TcpInit; +RegisterL4Protocol(IP_PROTO_TCP, &TcpHandler); +RegisterSocketClass(AF_INET, SOCK_STREAM, &TcpSocket); diff --git a/Adam/Net/Udp.HC b/Adam/Net/Udp.HC new file mode 100644 index 0000000..ca33fd2 --- /dev/null +++ b/Adam/Net/Udp.HC @@ -0,0 +1,243 @@ +// vim: set ft=c: + +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/Adam/Net/Url.HC b/Adam/Net/Url.HC new file mode 100644 index 0000000..2b95237 --- /dev/null +++ b/Adam/Net/Url.HC @@ -0,0 +1,91 @@ +// vim: set ft=cpp: + +#include "::/Adam/Net/Http" + +I64 UrlGet(U8* url, U8** data_out = NULL, I64* size_out = NULL) { + CUrl curl; + UrlInit(&curl); + + I64 error = 0; + + if (UrlParse(url, &curl)) { + if (!StrCmp(curl.protocol, "http")) + error = HttpGet(curl.host, curl.port, curl.path, data_out, size_out); + else + error = URL_EPROTOCOL; + } + else + error = URL_EPARSE; + + UrlFree(&curl); + return error; +} + +I64 UrlGetWithProgress(U8* url, U8** data_out, I64* size_out) { + CUrl curl; + UrlInit(&curl); + + I64 error = 0; + I64 size = 0; + + if (UrlParse(url, &curl)) { + if (!StrCmp(curl.protocol, "http")) { + I64 sock = HttpOpenGet(curl.host, curl.port, curl.path, &size); + + if (sock > 0) { + if (size >= 0) { + U8* data = MAlloc(1 + size); + I64 total = 0; + I64 progress = 0; + "[$FG,3$"; + + while (total < size) { + I64 step = size - total; + + if (step > 1024) + step = 1024; + + I64 got = recv(sock, data + total, step, 0); + + if (got <= 0) { + error = HTTP_EEOF; + break; + } + + total += got; + + I64 new_progress = (20 * total + size - 1) / size; + while (progress < new_progress) { + '' 0xfe; + progress++; + } + } + } + else + error = HTTP_ECONTENTLENGTH; + + close(sock); + + if (error) { + "$FG,4$x\n$FG$"; + Free(data); + } + else { + "$FG$]\n"; + data[total] = 0; + *data_out = data; + *size_out = total; + } + } + else + error = sock; + } + else + error = URL_EPROTOCOL; + } + else + error = URL_EPARSE; + + UrlFree(&curl); + return error; +} diff --git a/Adam/Net/UrlParse.HC b/Adam/Net/UrlParse.HC new file mode 100644 index 0000000..f47be84 --- /dev/null +++ b/Adam/Net/UrlParse.HC @@ -0,0 +1,73 @@ +// vim: set ft=cpp: + +#define URL_EPARSE (-201) +#define URL_EPROTOCOL (-202) + +class CUrl { + U8* protocol; + U8* host; + U16 port; + U8* path; +}; + +U0 UrlInit(CUrl* url) { + url->protocol = 0; + url->host = 0; + url->port = 0; + url->path = 0; +} + +U0 UrlFree(CUrl* url) { + Free(url->protocol); + Free(url->host); + Free(url->path); + UrlInit(url); +} + +Bool UrlParse(U8* url, CUrl* url_out) { + U8* colon = StrFirstOcc(url, ":"); + U8* protosep = StrFind("//", url); + + if (colon && colon < protosep) { + I64 len = colon - url; + url_out->protocol = MAlloc(len + 1); + MemCpy(url_out->protocol, url, len); + url_out->protocol[len] = 0; + + url = colon + 1; + while (*url == '/') + url++; + } + else { + url_out->protocol = StrNew("http"); + } + + I64 pos = 0; + + while (url[pos]) { + if (url[pos] == ':' || url[pos] == '/') { + url_out->host = MAlloc(pos + 1); + MemCpy(url_out->host, url, pos); + url_out->host[pos] = 0; + + if (url[pos] == ':') { + I64 port = 0; + U8* end = 0; + port = Str2I64(url + pos + 1, 10, &end); + + url_out->port = port; + url_out->path = StrNew(end); + } + else { + url_out->path = StrNew(url + pos); + } + + return TRUE; + } + + pos++; + } + + url_out->host = StrNew(url); + return TRUE; +} diff --git a/Apps/Lsh.HC b/Apps/Lsh.HC new file mode 100644 index 0000000..3b59d10 --- /dev/null +++ b/Apps/Lsh.HC @@ -0,0 +1,522 @@ +// vim: set ft=cpp: + +#define LAMBDA_BITMAP 0x006336363C181B0E +#define LAMBDA "\xFB" +#define LAMBDA_CHARCODE 0xFB + +#define AUTOCOMPLETE_MAX_RESULTS 15 + +// ------------------------------------- +// History + +class histent { + histent* next; + U8 text[0]; +}; + +histent* history; + +Bool LoadHistory() { + history = NULL; + return TRUE; +} + +U0 HistoryPush(U8* line) { + if (!line[0]) + return; + + if (history && !StrCmp(history->text, line)) + return; + + I64 len = StrLen(line); + histent* ent = MAlloc(sizeof(histent) + len + 1); + StrCpy(ent->text, line); + ent->next = history; + history = ent; +} + +// ------------------------------------- +// Line input + +U8* ReadLine(U8* prompt, I64 (*autocomplete)(U8* buffer) = NULL) { + static U8 buf[256]; + I64 len = 0; + histent* hist = history; + + buf[0] = 0; + +show_prompt: + + DocBottom; + "" prompt; + "" buf; + + while (len < 255) { + I64 scan; + I64 ch = GetKey(&scan, FALSE, FALSE); + + if (ch == CH_BACKSPACE) { + if (len > 0) { + '' CH_BACKSPACE; + --len; + } + } + else if (ch == CH_ESC) { + len = 0; + buf[len] = 0; + + EdLineDel(DocPut); + goto show_prompt; + } + else if (ch == CH_SHIFT_ESC) { + return 0; + } + else if (ch == '\t' && autocomplete) { + buf[len] = 0; + + // Completing path or last argument? + U8* start = StrLastOcc(buf, " "); + + if (start) + start++; + else + start = buf; + + // Find matching results + I64 results = autocomplete(start); + len = StrLen(buf); + + // If multiple results were printed, + // we need to wait for the WinMgr (or whoever) + // to catch up. UGH! + if (results > 1) + Sleep(200); + + EdLineDel(DocPut); + goto show_prompt; + } + else if (ch == 0 && scan.u8[0] == SC_CURSOR_UP) { + if (hist) { + StrCpy(buf, hist->text); + len = StrLen(buf); + hist = hist->next; + + EdLineDel(DocPut); + goto show_prompt; + } + } + else if (ch) { + '' ch; + + if (ch == '\n') + break; + + buf[len++] = ch; + } + } + buf[len] = 0; + return buf; +} + +// ------------------------------------- +// Parse + +I64 Tokenize(U8* str, U8** tokens) { + I64 count = 0; + Bool started = FALSE; + + while (*str) { + if (*str == ' ') { + if (started) { + *str = 0; + started = FALSE; + } + } + else if (!started) { + tokens[count] = str; + count++; + started = TRUE; + } + + str++; + } + + return count; +} + +U0 TransformCommand(U8* command, U8* exec_buf) { + Bool upperize = TRUE; + I64 pos = 0; + + for (; *command; command++) { + if (*command == '-') + upperize = TRUE; + else if (upperize) { + exec_buf[pos++] = ToUpper(*command); + upperize = FALSE; + } + else + exec_buf[pos++] = *command; + } + + exec_buf[pos] = 0; +} + +// ------------------------------------- +// Autocomplete + +class CHashTableIter { + CHashTable* ht; + I64 i; + CHash* curr; + I64 recursive; +}; + +U0 HashTableIterBegin(CHashTableIter* it, CHashTable* ht, I64 recursive) { + it->ht = ht; + it->i = 0; + it->curr = NULL; + it->recursive = recursive; +} + +CHash* HashTableIterNext(CHashTableIter* it) { + // End of current bucket? + while (!it->curr) { + // End of hash table? + while (it->i >= it->ht->mask) { + // If recursive search is enabled, + // jump to the next table in chain + if (it->recursive) { + if (!it->ht->next) + return NULL; + + it->ht = it->ht->next; + it->i = 0; + } + else + return NULL; + } + + it->curr = it->ht->body[it->i]; + it->i++; + } + + CHash* ret = it->curr; + it->curr = it->curr->next; + return ret; +} + +class CAutocompleteIter { + U8* query; + I64 length; + CDirEntry* entries; + CDirEntry* de; + CHashTableIter hti; +}; + +class CAutocompleteResult { + // Exactly one of these will be set + CDirEntry* de; + CHashFun* fun; +}; + +U0 AutocompleteIterRewind(CAutocompleteIter* it) { + it->de = it->entries; + HashTableIterBegin(&it->hti, Fs->hash_table, TRUE); +} + +U0 AutocompleteIterBegin(CAutocompleteIter* it, U8* query) { + it->query = query; + it->length = StrLen(query); + + U8* mask = MStrPrint("%s*", query); + try { + it->entries = FilesFind(mask); + } + catch { + it->entries = NULL; + } + Free(mask); + + AutocompleteIterRewind(it); +} + +I64 AutocompleteIterNext(CAutocompleteIter* it, CAutocompleteResult* out) { + // Go through all file matches first + while (it->de) { + if (it->de->name[0] != '.') { + // Return the DE, iteration will resume at the next one + out->de = it->de; + out->fun = NULL; + it->de = it->de->next; + return TRUE; + } + + it->de = it->de->next; + } + + // Go through all hashtable matches + CHash* next; + while ((next = HashTableIterNext(&it->hti))) { + // Function? + if ((next->type & HTT_FUN) != 0 + && !StrNICmp(next->str, it->query, it->length)) { + out->de = NULL; + out->fun = next(CHashFun*); + return TRUE; + } + } + + return FALSE; +} + +U0 AutocompleteIterEnd(CAutocompleteIter* it) { + DirTreeDel(it->entries); +} + +U0 AutocompleteSetResult(U8* buffer, U8* str, I64 length) { + // Completing path or last argument? + U8* start = StrLastOcc(buffer, "/"); + + if (start) + start++; + else + start = buffer; + + MemCpy(start, str, length); + start[length] = 0; +} + +I64 StrCommonSubset(U8* a, U8* b) { + I64 len = 0; + while (*a && *b == *a) { + a++; + b++; + len++; + } + return len; +} + +// No matches -> return 0 +// 1 match -> return 1, set *p_match to alloced +// multiple matches -> print matches, return count +I64 Autocomplete(U8* buffer) { + // This is somewhat complicated, because we want + // to avoid any unnecessary allocations. + + CAutocompleteIter it; + CAutocompleteResult first, next; + + AutocompleteIterBegin(&it, buffer); + + if (!AutocompleteIterNext(&it, &first)) { + // No results. + return 0; + } + + I64 count; + U8* str; + + if (!AutocompleteIterNext(&it, &next)) { + // Single result. + + if (first.de) + str = first.de->name; + else if (first.fun) + str = first.fun->str; + + AutocompleteSetResult(buffer, str, StrLen(str)); + + count = 1; + } + else { + U8* common_base = NULL; + I64 common_length = 0; + + AutocompleteIterRewind(&it); + + count = 0; + "\n"; + + while (count < AUTOCOMPLETE_MAX_RESULTS + && AutocompleteIterNext(&it, &next)) { + if (next.de) { + str = next.de->name; + "$FG,4$%s\n", str; + } + else if (next.fun) { + str = next.fun->str; + "$FG,3$%s\n", str; + } + + if (!common_base) { + common_base = str; + common_length = StrLen(common_base); + } + else { + I64 new_common = StrCommonSubset(common_base, str); + if (common_length > new_common) + common_length = new_common; + } + + count++; + } + + if (AutocompleteIterNext(&it, &next)) + "$FG,6$Too many results, display truncated\n$FG$"; + else if (common_length > StrLen(buffer)) + AutocompleteSetResult(buffer, common_base, common_length); + } + + AutocompleteIterEnd(&it); + return count; +} + +// ------------------------------------- +// Shell + +Bool skip_intro = FALSE; + +U8* DirCurShort() { + U8* dir = DirCur(); + // FIXME!! + if (!StrCmp(dir, "C:/Home")) { + Free(dir); + return StrNew("~"); + } + else + return dir; +} + +CHashFun* FindFunction(U8* name) { + CHash* result = HashFind(name, Fs->hash_table, HTT_FUN); + + if (result && (result->type & HTT_FUN) != 0) + return result(CHashFun *); + else + return NULL; +} + +Bool IsPath(U8* str) { + for (; *str; str++) { + if (*str == '/') + return TRUE; + } + + return FALSE; +} + +U8* Prompt() { + // TODO: Avoid malloc if we can rely on MAX_PATH + static U8 buf[200]; + U8* dir = DirCurShort(); + + //StrPrint(buf, "$FG,5$" LAMBDA " $FG,8$%s $FG,0$", dir); + StrPrint(buf, "$FG,8$%s $FG,5$" LAMBDA " $FG,0$", dir); + Free(dir); + return buf; +} + +U0 PatchFont() { + U64* font = sys_font_std; + font[LAMBDA_CHARCODE] = LAMBDA_BITMAP; +} + +// ------------------------------------- +// Main + +U0 Intro() { + "\n" + "$FG,1$- #include files by absolute or relative path\n" + " $FG,7$" LAMBDA "$FG,0$ ./Lsh $FG,7$=> #include \"Lsh\"\n" + "\n" + "$FG,1$- Call functions\n" + " $FG,7$" LAMBDA "$FG,0$ cd .. $FG,7$=> Cd(\"..\");\n" + " $FG,7$" LAMBDA "$FG,0$ dir $FG,7$=> Dir;\n" + " $FG,7$" LAMBDA "$FG,0$ ed Program.HC $FG,7$=> Ed(\"Program.HC\");\n" + " $FG,7$" LAMBDA "$FG,0$ file-mgr $FG,7$=> FileMgr;\n" + "\n" + "$FG,1$- Execute code directly\n" + " $FG,7$" LAMBDA "$FG,0$ 'DskChg('B');\n" + "\n" + "$FG,1$- $FG,0$Esc$FG,1$ deletes line\n" + "$FG,1$- $FG,0$Tab$FG,1$ auto-completes paths\n" + "$FG,1$- $FG,0$Shift-Esc$FG,1$ quits\n" + "$FG,1$- $FG,0$Up Arrow$FG,1$ recalls previous commands\n" + "\n"; +} + +U0 ParseAndExecute(U8* line) { + if (line[0] == '#') + return; + + if (line[0] == '\'') { + ExePutS(line + 1); + return; + } + + U8* tokens[10]; + I64 count = Tokenize(line, tokens); + + if (count) { + if (IsPath(tokens[0])) { + "$FG$"; + ExePrint("#include \"%s\";", tokens[0]); + } + else { + U8 exec_buf[200]; + + TransformCommand(tokens[0], exec_buf); + CHashFun* fun = FindFunction(exec_buf); + + if (!fun) { + "%s: $FG,4$function not found$FG$\n", exec_buf; + return; + } + + if (count > 1) { + CatPrint(exec_buf, "("); + I64 have; + for (have = 1; have < count; have++) { + if (have > 1) + CatPrint(exec_buf, ","); + + CatPrint(exec_buf, "\"%s\"", tokens[have]); + } + CatPrint(exec_buf, ")"); + } + CatPrint(exec_buf, ";"); + "$FG,7$%s\n$FG$", exec_buf; + ExePutS(exec_buf); + } + } +} + +U0 Lsh() { + PatchFont(); + LoadHistory(); + + if (!skip_intro) { + "$FG,8$Welcome to $FG,5$Lambda Shell$FG,8$!\n"; + "Type $FG,0$intro$FG,8$ for a quick introduction.\n\n"; + } + + while (1) { + U8* p = Prompt(); + U8* line = ReadLine(p, &Autocomplete); + + if (!line || !StrCmp(line, "exit")) + break; + + HistoryPush(line); + + try { + ParseAndExecute(line); + } + catch { + PutExcept(); + } + } + + "$FG$\n"; +} diff --git a/Apps/Mfa.HC b/Apps/Mfa.HC new file mode 100644 index 0000000..e3fb0c5 --- /dev/null +++ b/Apps/Mfa.HC @@ -0,0 +1,98 @@ +// vim: set ft=cpp: + +#include "::/Doc/Comm" + +#define MFA_COM 1 + +static CComm* comm; +static U8 in_buf[256]; + +U8* ReadStr() { + I64 len = 0; + while (1) { + if (FifoU8Rem(comm->RX_fifo, in_buf + len)) { + if (in_buf[len] == '\n') + break; + len++; + } + else Yield; + } + in_buf[len] = 0; + "%s\n", in_buf; + return in_buf; +} + +U0 ReadBlk(U8* buf, I64 count) { + while (count) { + if (FifoU8Rem(comm->RX_fifo, buf)) { + buf++; + count--; + } + else Yield; + } +} + +U0 Mfa() { + U8 command; + + comm = CommInit8n1(MFA_COM, 115200); + while (FifoU8Rem(comm->RX_fifo, &command)) {} + + "$FG,5$minimalist file access\n" + "\n" + "$FG,8$- configure your VM's COM1 as follows:\n" + "$FG,0$ TCP, server, port 7770\n" + "$FG,8$- use $FG,5$mfa.py$FG,8$ to send commands & files\n" + "\n" + "awaiting commands. press Esc to quit\n"; + + while (1) { + next: + I64 key; + + if (ScanKey(&key) && (key == CH_ESC || key == CH_SHIFT_ESC || key == 'q')) + break; + + if (!FifoU8Rem(comm->RX_fifo, &command)) { + Sleep(50); + goto next; + } + + '' command; + U8* line = ReadStr(); + I64 size; + + if (command == 'L') { + U8* file = FileRead(line, &size); + + CommPrint(MFA_COM, "S%d\n", size); + CommPutBlk(MFA_COM, file, size); + Free(file); + + "Sent %d\n", size; + } + else if (command == 'P') { + U8 filename[255]; + StrCpy(filename, line); + U8* next = ReadStr(); + StrScan(next, "S%d", &size); + + U8* file_buf = MAlloc(size); + ReadBlk(file_buf, size); + FileWrite(filename, file_buf, size); + Free(file_buf); + + "Wrote %d\n", size; + } + else if (command == '\'') { + ExePutS(line); + } + else if (command == '?') { + CommPutS(MFA_COM, "!\n"); + } + } + + "$FG$"; +} + +Mfa; diff --git a/Apps/Pkg.HC b/Apps/Pkg.HC new file mode 100644 index 0000000..56ef206 --- /dev/null +++ b/Apps/Pkg.HC @@ -0,0 +1,463 @@ +// vim: set ft=cpp: + +#include "::/Adam/Net/Url" + +#define PKG_EURL (-20001) +#define PKG_EMOUNT (-20002) +#define PKG_EMANIFEST (-20003) +#define PKG_EVERSION (-20004) +#define PKG_EOSVERSION (-20005) +#define PKG_EUNSUITABLE (-20006) + +#define PKG_VERSION 11 + +static U8* PKG_BASE_URL = "http://shrine.lanceward.net/packages"; +static U8* PKG_LOCAL_REPO = "::/Misc/Packages"; +static U8* PKG_TMP_DIR = "::/Tmp/PkgTmp"; + +class CPkgInfo { + U8* package_name; + I32 pkgmin; + I32 release; + I32 osmin; + I32 osmax; + I64 size; + U8* version; + U8* installdir; + U8* iso_c; + U8* post_install_doc; +}; + +// TODO: Is there a built-in for this? +static U8* StripDir(U8* file_path) { + U8* slash = StrLastOcc(file_path, "/"); + + if (slash) + return slash + 1; + else + return file_path; +} + +U0 PkgInfoInit(CPkgInfo* pinf) { + pinf->package_name = 0; + + pinf->pkgmin = 0x7fffffff; + pinf->release = 0; + pinf->osmin = 0; + pinf->osmax = 0x7fffffff; + pinf->size = 0; + + pinf->version = 0; + pinf->installdir = 0; + pinf->iso_c = 0; + pinf->post_install_doc = 0; +} + +U0 PkgInfoFree(CPkgInfo* pinf) { + Free(pinf->package_name); + Free(pinf->version); + Free(pinf->installdir); + Free(pinf->iso_c); + Free(pinf->post_install_doc); + PkgInfoInit(pinf); +} + +// Returns 0 or error code +I64 PkgParseManifest(CPkgInfo* pinf, U8* manifest) { + U8* key = manifest; + + while (*key) { + //"?%s", key; + U8* end = StrFirstOcc(key, "\n"); + if (end) { + *end = 0; + end++; + } + else + end = key + StrLen(key); + + U8* value = StrFirstOcc(key, "\t"); + + if (!value) + return PKG_EMANIFEST; + + *value = 0; + value++; + + //"%s=%s;\n", key, value; + + if (0) {} + else if (!StrCmp(key, "name")) { Free(pinf->package_name); pinf->package_name = StrNew(value); } + else if (!StrCmp(key, "pkgmin")) { pinf->pkgmin = Str2I64(value); } + else if (!StrCmp(key, "release")) { pinf->release = Str2I64(value); } + else if (!StrCmp(key, "osmin")) { pinf->osmin = Str2I64(value); } + else if (!StrCmp(key, "osmax")) { pinf->osmax = Str2I64(value); } + else if (!StrCmp(key, "size")) { pinf->size = Str2I64(value); } + else if (!StrCmp(key, "version")) { Free(pinf->version); pinf->version = StrNew(value); } + else if (!StrCmp(key, "installdir")) { Free(pinf->installdir); pinf->installdir = StrNew(value); } + else if (!StrCmp(key, "iso.c")) { Free(pinf->iso_c); pinf->iso_c = StrNew(value); } + else if (!StrCmp(key, "post-install-doc")) { Free(pinf->post_install_doc); pinf->post_install_doc = StrNew(value); } + else { /* unrecognized keys are simply ignored */ } + + key = end; + } + + return 0; +} + +I64 PkgWriteManifest(CPkgInfo* pinf, U8* path) { + // TODO: implement + + no_warn pinf; + FileWrite(path, "", 0); + return 0; +} + +// Downloads a package info from the repository. +// Returns 0 or error code +I64 PkgFetchManifest(CPkgInfo* pinf, U8* package_name) { + // Old packages didn't have to specify a name, so we'll keep this for now + pinf->package_name = StrNew(package_name); + + U8* url = MStrPrint("%s/%s", PKG_BASE_URL, package_name); + + U8* manifest = 0; + I64 size = 0; + I64 error = UrlGet(url, &manifest, &size); + + if (error == 0) + error = PkgParseManifest(pinf, manifest); + + Free(manifest); + Free(url); + return error; +} + +// Get the URL of the package's ISO.C download. +// Returns NULL if N/A, otherwise must be Free()d. +U8* PkgAllocISOCUrl(CPkgInfo* pinf) { + if (!pinf->iso_c) + return NULL; + + // A bit hacky, but will probably always work + if (StrFind("//", pinf->iso_c)) + return StrNew(pinf->iso_c); + else + return MStrPrint("%s/%s", PKG_BASE_URL, pinf->iso_c); +} + +// Check if the package metadata makes it viable for installation. +// You still need to do PkgCheckCompatibility, dependency resolution, +// and check for a suitable installable format. +Bool PkgIsInstallable(CPkgInfo* pinf) { + return pinf->package_name != NULL && pinf->version != NULL && pinf->installdir != NULL; +} + +// Check if the package is compatible with this OS & Pkg version +I64 PkgCheckCompatibility(CPkgInfo* pinf) { + if (pinf->pkgmin > PKG_VERSION) { + "$FG,6$This package requires a more recent version of $FG,5$Pkg\n"; + "$FG$Please update.\n"; + return PKG_EVERSION; + } + + I64 osver = ToI64(sys_os_version * 100); + + if (osver < pinf->osmin) { + "$FG,6$This package requires a more recent system version.\n"; + "$FG$Please update. (need %d, have %d)\n", pinf->osmin, osver; + return PKG_EOSVERSION; + } + + if (osver > pinf->osmax) { + "$FG,6$This package is not compatible with your system version.\n"; + "$FG$Last supported version is %d, you have %d.\n", pinf->osmax, osver; + return PKG_EOSVERSION; + } + + return 0; +} + +I64 PkgRegister(CPkgInfo* pinf) { + // TODO: this is very preliminary + + if (pinf->package_name == NULL) + return PKG_EUNSUITABLE; + + U8* path = MStrPrint("%s/%s", PKG_LOCAL_REPO, pinf->package_name); + + PkgWriteManifest(pinf, path); + + return 0; +} + +// Install a package, using the provided ISO.C file. +// This will also register the package as installed. +I64 PkgInstallISOC(CPkgInfo* pinf, U8* iso_c) { + if (pinf->package_name == NULL || pinf->installdir == NULL) + return PKG_EUNSUITABLE; + + I64 error = 0; + "Installing %s\n$FG,7$", pinf->package_name; + + I64 letter = MountFile(iso_c); + if (letter) { + U8 src_path[8]; + StrPrint(src_path, "%c:/", letter); + + // StrLen check is a temporary hack to not complain about MkDir("::/"); + if (StrLen(pinf->installdir) > 3) + DirMk(pinf->installdir); + + CopyTree(src_path, pinf->installdir); + + // Register package as installed + error = PkgRegister(pinf); + + // Display post-install doc + if (pinf->post_install_doc) { + Ed(pinf->post_install_doc); + } + } + else + error = PKG_EMOUNT; + + Unmount(letter); + "$FG$"; + return error; +} + +// Verify, download & install a single package +// All dependencies must have been installed at this point. +I64 PkgDownloadAndInstall(CPkgInfo* pinf) { + I64 error = PkgCheckCompatibility(pinf); + if (error) { return error; } + + U8* iso_c_url = PkgAllocISOCUrl(pinf); + + if (iso_c_url) { + U8* iso_data = 0; + I64 iso_size = 0; + "Downloading %s...\n", pinf->package_name; + + error = UrlGetWithProgress(iso_c_url, &iso_data, &iso_size); + + if (error == 0) { + U8* tmppath = "::/Tmp/Package.ISO.C"; + FileWrite(tmppath, iso_data, iso_size); + error = PkgInstallISOC(pinf, tmppath); + } + + Free(iso_data); + Free(iso_c_url); + } + else { + "$FG,6$No suitable download address. Package broken?\n"; + error = PKG_EUNSUITABLE; + } + + return error; +} + +// Expected max length: 5 ("1023k") +static U8* FormatSize(I64 size) { + static U8 buf[16]; + if (size > 0x40000000) + StrPrint(buf, "%dG", (size + 0x3fffffff) / 0x40000000); + else if (size > 0x100000) + StrPrint(buf, "%dM", (size + 0xfffff) / 0x100000); + else if (size > 0x400) + StrPrint(buf, "%dk", (size + 0x3ff) / 0x400); + else + StrPrint(buf, "%d", size); + return buf; +} + +// Install a package using a local manifest file +public I64 PkgInstallFromFile(U8* manifest_path) { + DirMk(PKG_LOCAL_REPO); + + CPkgInfo pinf; + PkgInfoInit(&pinf); + + // Parse manifest + I64 manifest_size; + U8* manifest_file = FileRead(manifest_path, &manifest_size); + + // This relies on FileRead returning a 0-terminated buffer. + // As of v502, this happens for all file systems + I64 error = PkgParseManifest(&pinf, manifest_file); + + if (error == 0) { + error = PkgCheckCompatibility(&pinf); + + if (!error) { + if (pinf.iso_c) { + PkgInstallISOC(&pinf, pinf.iso_c); + } + else { + "$FG,6$No suitable installable file. Package broken?\n"; + error = PKG_EUNSUITABLE; + } + } + else { + "$FG,4$PkgCheckCompatibility error: %d\n$FG$", error; + } + } + else { + "$FG,4$PkgParseManifest error: %d\n$FG$", error; + } + + PkgInfoFree(&pinf); + return error; +} + +// Install a package from the repository +public I64 PkgInstall(U8* package_name) { + SocketInit(); + DirMk(PKG_LOCAL_REPO); + + CPkgInfo pinf; + + PkgInfoInit(&pinf); + I64 error = PkgFetchManifest(&pinf, package_name); + + if (error == 0) { + if (PkgIsInstallable(&pinf)) { + "$FG,8$ Package Ver \n" + "$FG,8$ Dir Size \n" + "$FG,8$============================\n" + "$FG,2$+ %-20s %-6s\n", package_name, pinf.version; + "$FG,2$ %-20s %-6s\n", pinf.installdir, FormatSize(pinf.size); + "\n" + "$FG$Is this ok? (y/n) "; + I64 ok = GetKey(NULL, TRUE); + "\n"; + + // TODO: verify all packages before we start downloading + + if (ok == 'y') { + error = PkgDownloadAndInstall(&pinf); + + if (error == 0) { + "$FG,2$Installed 1 package(s)\n"; + } + else { + "$FG,4$PkgDownloadAndInstall error: %d\n$FG$", error; + } + } + } + else { + "$FG,4$PkgInstall: %s is not installable\n$FG$", package_name; + error = PKG_EUNSUITABLE; + } + } + else { + "$FG,4$PkgFetchManifest error: %d\n$FG$", error; + } + + PkgInfoFree(&pinf); + + return error; +} + +// List packages available in the repository +public I64 PkgList() { + SocketInit(); + + U8* url = MStrPrint("%s/packages.list", PKG_BASE_URL); + + U8* list = 0; + I64 size = 0; + I64 error = UrlGet(url, &list, &size); + + if (error == 0) { + "$FG,2$%s\n", list; + + /*U8* entry = list; + + while (*entry) { + U8* end = StrFirstOcc(entry, "\n"); + if (end) { + *end = 0; + end++; + } + else + end = value + StrLen(value); + + "$FG,2$%s\n", entry; + + entry = end; + }*/ + } + else { + "$FG,4$UrlGet error: %d\n$FG$", error; + } + + Free(list); + Free(url); + return error; +} + +// Build a package from directory contents +public I64 PkgMakeFromDir(U8* manifest_path, U8* src_dir) { + CPkgInfo pinf; + PkgInfoInit(&pinf); + + // Parse manifest + I64 manifest_size; + U8* manifest_file = FileRead(manifest_path, &manifest_size); + + // This relies on FileRead returning a 0-terminated buffer. + // As of v502, this happens for all file systems + I64 error = PkgParseManifest(&pinf, manifest_file); + + if (error == 0) { + // Build RedSea ISO + if (pinf.iso_c) { + U8* iso_path = pinf.iso_c; + + // RedSeaISO doesn't return a proper error code + RedSeaISO(iso_path, src_dir); + + // TODO: update & save manifest + /*CDirEntry* de; + if (FileFind(iso_path, &de)) { + pinf.size = de.size; + + // Save updated manifest + PkgWriteManifest(&pinf, manifest_path); + + Free(de->full_name); + } + else { + "$FG,6$Something went wrong, can't stat %s.\n", iso_path; + error = PKG_EMOUNT; + }*/ + } + else { + "$FG,6$No output file defined.\n"; + error = PKG_EUNSUITABLE; + } + } + else { + "$FG,4$PkgParseManifest error: %d\n$FG$", error; + } + + PkgInfoFree(&pinf); + return error; +} + +// Build a package using a single file +I64 PkgMakeFromFile(U8* manifest_path, U8* file_path) { + DelTree(PKG_TMP_DIR); + DirMk(PKG_TMP_DIR); + + U8* tmppath = MStrPrint("%s/%s", PKG_TMP_DIR, StripDir(file_path)); + Copy(file_path, tmppath); + + I64 error = PkgMakeFromDir(manifest_path, PKG_TMP_DIR); + + Free(tmppath); + return error; +} diff --git a/Apps/Wget.HC b/Apps/Wget.HC new file mode 100644 index 0000000..328421e --- /dev/null +++ b/Apps/Wget.HC @@ -0,0 +1,25 @@ +// vim: set ft=cpp: + +#include "::/Adam/Net/Url" + +I64 Wget(U8* url, U8* saveas = NULL) { + SocketInit(); + + U8* data; + I64 len; + I64 error = UrlGet(url, &data, &len); + + if (!error) { + if (saveas) { + FileWrite(saveas, data, len); + } + else { + "%s\n", data; + } + } + else { + "$FG,4$Wget: error %d\n$FG$", error; + } + + return error; +} diff --git a/Demo/Network/TcpEchoClient.HC b/Demo/Network/TcpEchoClient.HC new file mode 100644 index 0000000..c00208f --- /dev/null +++ b/Demo/Network/TcpEchoClient.HC @@ -0,0 +1,49 @@ +// vim: set ft=c: + +#include "::/Adam/Net/Socket" + +#define PORT 8000 + +I64 TcpEchoClient(U8* dest_address) { + SocketInit(); + + I64 sock = socket(AF_INET, SOCK_STREAM); + + if (sock < 0) + return -1; + + sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(PORT); + addr.sin_addr.s_addr = 0; + inet_aton(dest_address, &addr.sin_addr); + + I64 error = connect(sock, &addr, sizeof(addr)); + + if (error < 0) { + "$FG,6$connect: error %d\n$FG$", error; + return -1; + } + + U8* message = "hello, world!\n\n"; + I64 count = send(sock, message, StrLen(message), 0); + + if (count < 0) { + "$FG,6$send: error %d\n$FG$", count; + return -1; + } + + U8 buffer[2048 + 1]; + count = recv(sock, buffer, sizeof(buffer) - 1, 0); + + if (count <= 0) { + "$FG,6$recv: error %d\n$FG$", count; + } + else { + buffer[count] = 0; + "$FG,8$Received %d bytes:\n$FG$%s\n", count, buffer; + } + + close(sock); + return 0; +} diff --git a/Demo/Network/TcpEchoServer.HC b/Demo/Network/TcpEchoServer.HC new file mode 100644 index 0000000..081cc82 --- /dev/null +++ b/Demo/Network/TcpEchoServer.HC @@ -0,0 +1,54 @@ +// vim: set ft=c: + +#include "::/Adam/Net/Socket" + +#define PORT 8000 + +I64 TcpEchoServer() { + SocketInit(); + + I64 sock = socket(AF_INET, SOCK_STREAM); + + if (sock < 0) + return -1; + + sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(PORT); + addr.sin_addr.s_addr = INADDR_ANY; + + if (bind(sock, &addr, sizeof(addr)) < 0) { + close(sock); + "$FG,4$TcpEchoServer: failed to bind to port %d\n$FG$", PORT; + return -1; + } + + I64 error = listen(sock, 1); + + if (error < 0) { + "$FG,6$listen: error %d\n$FG$", error; + return -1; + } + + "$FG,2$Listening on port %d\n$FG$", PORT; + + I64 client = accept(sock, 0, 0); + + U8 buffer[2048 + 1]; + I64 count = recv(client, buffer, sizeof(buffer) - 1, 0); + + if (count <= 0) { + "$FG,6$recv: error %d\n$FG$", count; + } + else { + buffer[count] = 0; + "$FG,8$Received %d bytes:\n$FG$%s\n", count, buffer; + } + + send(client, buffer, count, 0); + + close(client); + + close(sock); + return 0; +} diff --git a/Demo/Network/UdpListen.HC b/Demo/Network/UdpListen.HC new file mode 100644 index 0000000..bf5043f --- /dev/null +++ b/Demo/Network/UdpListen.HC @@ -0,0 +1,50 @@ +// vim: set ft=c: + +// udp-send.py 192.168.1.10 15051 "Hello World!" + +#include "::/Adam/Net/Socket" + +#define PORT 15051 + +I64 UdpListen() { + SocketInit(); + + I64 sock = socket(AF_INET, SOCK_DGRAM); + + if (sock < 0) + return -1; + + sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(PORT); + addr.sin_addr.s_addr = INADDR_ANY; + + if (bind(sock, &addr, sizeof(addr)) < 0) { + close(sock); + "$FG,4$UdpListen: failed to bind to port %d\n$FG$", PORT; + return -1; + } + + "$FG,2$Listening on port %d\n$FG$", PORT; + + while (1) { + U8 buffer[2048 + 1]; + I64 count = recvfrom(sock, buffer, sizeof(buffer) - 1, 0, &addr, sizeof(addr)); + + if (count == 0) + break; + + if (count < 0) { + "$FG,6$recvfrom: error %d\n$FG$", count; + } + else { + buffer[count] = 0; + + "$FG,8$Received %d bytes from %s:%d:\n$FG$%s\n", count, + inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), buffer; + } + } + + close(sock); + return 0; +} diff --git a/Demo/Network/UdpSend.HC b/Demo/Network/UdpSend.HC new file mode 100644 index 0000000..13fe795 --- /dev/null +++ b/Demo/Network/UdpSend.HC @@ -0,0 +1,32 @@ +// vim: set ft=c: + +// udp-listen.py 15051 + +#include "::/Adam/Net/Socket" + +#define PORT 15051 + +I64 UdpSend(U8* dest_address) { + SocketInit(); + + I64 sock = socket(AF_INET, SOCK_DGRAM); + + if (sock < 0) + return -1; + + sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(PORT); + addr.sin_addr.s_addr = 0; + inet_aton(dest_address, &addr.sin_addr); + + U8* message = "hello, world!"; + I64 count = sendto(sock, message, StrLen(message), 0, &addr, sizeof(addr)); + + if (count < 0) { + "$FG,6$sendto: error %d\n$FG$", count; + } + + close(sock); + return 0; +} diff --git a/Demo/Network/tcp-echo-client.py b/Demo/Network/tcp-echo-client.py new file mode 100644 index 0000000..1efd9d1 --- /dev/null +++ b/Demo/Network/tcp-echo-client.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 + +import socket +import sys + +HOST = sys.argv[1] +PORT = int(sys.argv[2]) +MESSAGE = sys.argv[3] + +with socket.create_connection((HOST, PORT)) as s: + s.sendall(MESSAGE.encode()) + data = s.recv(1024) + + if data: + print('Received', data.decode()) diff --git a/Demo/Network/tcp-echo-server.py b/Demo/Network/tcp-echo-server.py new file mode 100644 index 0000000..7a30ca5 --- /dev/null +++ b/Demo/Network/tcp-echo-server.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 + +import socket +import sys + +HOST = "0.0.0.0" +PORT = int(sys.argv[1]) + +with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind((HOST, PORT)) + s.listen(1) + + conn, addr = s.accept() + with conn: + print('Connected by', addr) + while True: + data = conn.recv(1024) + + if not data: + break + + conn.sendall(data.decode().upper().encode()) diff --git a/Demo/Network/udp-listen.py b/Demo/Network/udp-listen.py new file mode 100644 index 0000000..f0ab341 --- /dev/null +++ b/Demo/Network/udp-listen.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 + +import socket +import sys + +HOST = "0.0.0.0" +PORT = int(sys.argv[1]) + +sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +sock.bind((HOST, PORT)) + +while True: + data, addr = sock.recvfrom(2048) + print("received message:", data.decode()) diff --git a/Demo/Network/udp-send.py b/Demo/Network/udp-send.py new file mode 100755 index 0000000..be71f32 --- /dev/null +++ b/Demo/Network/udp-send.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python3 + +import socket +import sys + +HOST = sys.argv[1] +PORT = int(sys.argv[2]) +MESSAGE = sys.argv[3] + +sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +sock.sendto(MESSAGE.encode(), (HOST, PORT)) diff --git a/Doc/Comm.HC b/Doc/Comm.HC index e37e045..d6626f7 100644 --- a/Doc/Comm.HC +++ b/Doc/Comm.HC @@ -100,6 +100,7 @@ public CComm *CommInit8n1(I64 port,I64 baud) return c; } +/* public U0 CommPutChar(I64 port,U8 b) { CComm *c=&comm_ports[port]; @@ -110,6 +111,17 @@ public U0 CommPutChar(I64 port,U8 b) POPFD Sleep(10); //!!! Remove this line!!! Linux echo_socket is too slow. } +*/ + +public U0 CommPutChar(I64 port,U8 b) +{ + I64 base=comm_ports[port].base; + while (!(InU8(base+UART_LSR) & 0x20)) + Yield; + OutU8(base+UART_THR,b); + while (!(InU8(base+UART_LSR) & 0x20)) + Yield; +} U0 CommPutS(I64 port,U8 *st) { diff --git a/Doc/Start.DD b/Doc/Start.DD index 0af030b..7bce4e4 100644 --- a/Doc/Start.DD +++ b/Doc/Start.DD @@ -1,4 +1,6 @@ -$WW+H,1$$FG,5$$TX+CX,"TempleOS V5.03",D="DD_OS_NAME_VERSION"$$FG$ + +$WW+H,1$$FG,3$$TX+CX,"Shrine",D="DD_OS_NAME_VERSION"$$FG$ +$WW+H,1$$FG,5$$TX+CX,"based on TempleOS by Terry A. Davis"$$FG$ $TX+CX,"Public Domain Operating System"$ diff --git a/Kernel/KGlbls.HC b/Kernel/KGlbls.HC index e6a1a63..0b92644 100644 --- a/Kernel/KGlbls.HC +++ b/Kernel/KGlbls.HC @@ -13,7 +13,7 @@ U8 *rev_bits_table, //Table with U8 bits revd *set_bits_table; //Table with count of set bits in a U8 CDate local_time_offset; F64 *pow10_I64, - sys_os_version=5.030; + sys_os_version=5.050; CAutoCompleteDictGlbls acd; CAutoCompleteGlbls ac; diff --git a/Misc/DoDistro.HC b/Misc/DoDistro.HC index 09d36ad..8f6a106 100644 --- a/Misc/DoDistro.HC +++ b/Misc/DoDistro.HC @@ -31,7 +31,7 @@ U0 MakeMyISO(U8 *_out_iso_filename) CopyTree("/Misc", "/Distro/Misc"); //To save space, optionally delete dictionary. - //Del("/Distro/Adam/AutoComplete/ACDefs.DATA"); + Del("/Distro/Adam/AutoComplete/ACDefs.DATA"); CopyTree("/Downloads","/Distro/Downloads"); //You can leave this out. DirMk("/Distro/Tmp"); DirMk("/Distro/Tmp/ScrnShots"); @@ -44,6 +44,6 @@ U0 MakeMyISO(U8 *_out_iso_filename) Free(out_iso_filename); } -MakeMyISO("/Tmp/MyDistro.ISO.C"); +MakeMyISO("/Tmp/ShrineDist.ISO.C"); // Study my account examples $LK,"Cfg Strs",A="FL:::/Demo/AcctExample/TOS/TOSCfg.HC,1"$, $LK,"Update Funs",A="FL:::/Demo/AcctExample/TOS/TOSDistro.HC,1"$. diff --git a/Misc/PalConEmu.HC b/Misc/PalConEmu.HC new file mode 100644 index 0000000..57ab846 --- /dev/null +++ b/Misc/PalConEmu.HC @@ -0,0 +1,8 @@ +CBGR48 gr_palette_conemu[COLORS_NUM]={ +0x00002b2b3636,0x070736364242,0x000080808080,0x31318282a4a4, +0xcbcb4b4b1616,0x9c9c3636b6b6,0x858599990000,0xeeeee8e8d5d5, +0x9393a1a1a1a1,0x26268b8bd2d2,0x4f4fb6b63636,0x2a2aa1a19898, +0xdcdc32322f2f,0xd3d336368282,0xb5b589890000,0xfdfdf6f6e3e3}; + +GrPaletteSet(gr_palette_conemu); +Adam("DefineLoad(\"USER_PALETTE\", \"ConEmu\");"); diff --git a/Misc/PalMonokai.HC b/Misc/PalMonokai.HC new file mode 100644 index 0000000..801f1c6 --- /dev/null +++ b/Misc/PalMonokai.HC @@ -0,0 +1,8 @@ +CBGR48 gr_palette_monokai[COLORS_NUM]={ +0x272728282222,0x010154549e9e,0x7474aaaa0404,0x1a1a8383a6a6, +0xa7a703033434,0x898956569c9c,0xb6b6b6b64949,0xcacacacacaca, +0x7c7c7c7c7c7c,0x03038383f5f5,0x8d8dd0d00606,0x5858c2c2e5e5, +0xf3f304044b4b,0xa8a87d7db8b8,0xcccccccc8181,0xffffffffffff}; + +GrPaletteSet(gr_palette_monokai); +Adam("DefineLoad(\"USER_PALETTE\", \"Monokai\");"); diff --git a/Misc/PalUbuntu.HC b/Misc/PalUbuntu.HC new file mode 100644 index 0000000..89f3dc8 --- /dev/null +++ b/Misc/PalUbuntu.HC @@ -0,0 +1,8 @@ +CBGR48 gr_palette_ubuntu[COLORS_NUM]={ +0x2e2e34343636,0x34346565a4a4,0x4e4e9a9a0606,0x060698989a9a, +0xcccc00000000,0x757550507b7b,0xc4c4a0a00000,0xd3d3d7d7cfcf, +0x555557575353,0x72729f9fcfcf,0x8a8ae2e23434,0x3434e2e2e2e2, +0xefef29292929,0xadad7f7fa8a8,0xfcfce9e94f4f,0xeeeeeeeeecec}; + +GrPaletteSet(gr_palette_ubuntu); +Adam("DefineLoad(\"USER_PALETTE\", \"Ubuntu\");"); diff --git a/Once.HC b/Once.HC index d8b4d0d..6820c80 100644 --- a/Once.HC +++ b/Once.HC @@ -1,3 +1,7 @@ +#include "::/Apps/Lsh"; +#include "::/Apps/Pkg"; +#include "::/Misc/PalUbuntu.HC" + //Place this file in /Home and change //anything you want. @@ -52,4 +56,6 @@ U0 Tmp() } } +#exe { if (SNAILNET_NATIVE_DRIVER != NULL) StreamPrint("Netcfg;\n"); } Tmp; +Lsh; diff --git a/mfa.py b/mfa.py new file mode 100755 index 0000000..bc6cee6 --- /dev/null +++ b/mfa.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 + +''' +mfa - minimalist file access for TempleOS + +usage: + ./mfa.py list [] + ./mfa.py put [] + ./mfa.py command + +for scripting: + ./mfa.py