mirror of
https://github.com/minexew/Shrine.git
synced 2026-05-26 05:48:36 +00:00
Hello Shrine
This commit is contained in:
+1
-1
@@ -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);
|
||||
|
||||
|
||||
@@ -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<<PCNET_NUM_RX_LOG2)
|
||||
#define tx_buffer_count (1<<PCNET_NUM_TX_LOG2)
|
||||
|
||||
// Including SrcAddr, DstAddr, EtherType
|
||||
// Where does this even belong? NetFifo defines for now.
|
||||
//#define ETHERNET_FRAME_SIZE 1548
|
||||
|
||||
#define PCNET_DE_SIZE 16
|
||||
|
||||
class CPCNetBufferSetup {
|
||||
U16 mode;
|
||||
U8 rlen;
|
||||
U8 tlen;
|
||||
U8 mac[6];
|
||||
U16 reserved;
|
||||
U8 ladr[8];
|
||||
U32 rxbuf;
|
||||
U32 txbuf;
|
||||
};
|
||||
|
||||
// Card I/O base
|
||||
I64 pcnet_iob = 0;
|
||||
|
||||
U8 my_mac[6];
|
||||
|
||||
// Current Rx/Tx buffer
|
||||
I64 rx_buffer_ptr = 0;
|
||||
I64 tx_buffer_ptr = 0;
|
||||
|
||||
// Rx/Tx descriptor ring buffers, PCNET_DE_SIZE each
|
||||
// _phys are uncached
|
||||
U8* rdes_phys;
|
||||
U8* tdes_phys;
|
||||
U8* rdes;
|
||||
U8* tdes;
|
||||
|
||||
U32 rx_buffers; // physical address of actual receive buffers (< 4 GiB)
|
||||
U32 tx_buffers; // physical address of actual transmit buffers (< 4 GiB)
|
||||
|
||||
static U0 writeRAP32(U32 val) {
|
||||
OutU32(pcnet_iob + PCNET_DW_RAP, val);
|
||||
}
|
||||
|
||||
static U32 readCSR32(U32 csr_no) {
|
||||
writeRAP32(csr_no);
|
||||
return InU32(pcnet_iob + PCNET_DW_RDP);
|
||||
}
|
||||
|
||||
static U0 writeCSR32(U32 csr_no, U32 val) {
|
||||
writeRAP32(csr_no);
|
||||
OutU32(pcnet_iob + PCNET_DW_RDP, val);
|
||||
}
|
||||
|
||||
static U0 PCNetReset() {
|
||||
InU32(pcnet_iob + PCNET_DW_RESET);
|
||||
InU16(pcnet_iob + PCNET_WD_RESET);
|
||||
}
|
||||
|
||||
// does the driver own the particular buffer?
|
||||
static I64 driverOwns(U8 *des, I64 idx)
|
||||
{
|
||||
return (des[PCNET_DE_SIZE * idx + 7] & 0x80) == 0;
|
||||
}
|
||||
|
||||
static U0 PCNetReadEeprom(I64 offset, U8* buffer, I64 count) {
|
||||
while (count) {
|
||||
*buffer = InU32(pcnet_iob + offset);
|
||||
offset++;
|
||||
buffer++;
|
||||
count--;
|
||||
}
|
||||
}
|
||||
|
||||
static I64 PCNetRxPacket(U8** buffer_out, U16* length_out) {
|
||||
I64 index = rx_buffer_ptr;
|
||||
|
||||
// packet length is given by bytes 8 and 9 of the descriptor
|
||||
// (no need to negate it unlike BCNT above)
|
||||
U16* p16 = &rdes[index * PCNET_DE_SIZE + 8];
|
||||
U16 length = *p16;
|
||||
|
||||
// increment rx_buffer_ptr;
|
||||
rx_buffer_ptr = (rx_buffer_ptr + 1) & (rx_buffer_count - 1);
|
||||
|
||||
*buffer_out = rx_buffers + index * ETHERNET_FRAME_SIZE;
|
||||
*length_out = length;
|
||||
return index;
|
||||
}
|
||||
|
||||
static I64 PCNetReleaseRxPacket(I64 index) {
|
||||
rdes[index * PCNET_DE_SIZE + 7] = 0x80;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static I64 PCNetAllocTxPacket(U8** buffer_out, I64 length, I64 flags) {
|
||||
// FIXME: validate length
|
||||
flags = flags;
|
||||
|
||||
if (!driverOwns(tdes, tx_buffer_ptr)) {
|
||||
return PCNET_TXFIFO_FULL;
|
||||
}
|
||||
|
||||
I64 index = tx_buffer_ptr;
|
||||
|
||||
// set the STP bit in the descriptor entry (signals this is the first
|
||||
// frame in a split packet - we only support single frames)
|
||||
tdes[index * PCNET_DE_SIZE + 7] |= 0x2;
|
||||
|
||||
// similarly, set the ENP bit to state this is also the end of a packet
|
||||
tdes[index * PCNET_DE_SIZE + 7] |= 0x1;
|
||||
|
||||
// set the BCNT member to be 0xf000 OR'd with the first 12 bits of the
|
||||
// two's complement of the length of the packet
|
||||
U16 bcnt = (-length);
|
||||
bcnt &= 0xfff;
|
||||
bcnt |= 0xf000;
|
||||
U16* p16 = &tdes[index * PCNET_DE_SIZE + 4];
|
||||
*p16 = bcnt;
|
||||
|
||||
tx_buffer_ptr = (tx_buffer_ptr + 1) & (tx_buffer_count - 1);
|
||||
|
||||
*buffer_out = tx_buffers + index * ETHERNET_FRAME_SIZE;
|
||||
return index;
|
||||
}
|
||||
|
||||
static I64 PCNetFinishTxPacket(I64 index) {
|
||||
// finally, flip the ownership bit back to the card
|
||||
tdes[index * PCNET_DE_SIZE + 7] |= 0x80;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static U0 PCNetInitDE(U32 buf_addr, U8 *des, I64 idx, I64 is_tx) {
|
||||
MemSet(&des[idx * PCNET_DE_SIZE], PCNET_DE_SIZE, 0);
|
||||
|
||||
// first 4 bytes are the physical address of the actual buffer
|
||||
U32* p32 = &des[idx * PCNET_DE_SIZE];
|
||||
*p32 = buf_addr + idx * ETHERNET_FRAME_SIZE;
|
||||
|
||||
// next 2 bytes are 0xf000 OR'd with the first 12 bits of the 2s complement of the length
|
||||
U16 bcnt = (-ETHERNET_FRAME_SIZE);
|
||||
bcnt &= 0x0fff;
|
||||
bcnt |= 0xf000;
|
||||
|
||||
U16* p16 = &des[idx * PCNET_DE_SIZE + 4];
|
||||
*p16 = bcnt;
|
||||
|
||||
// finally, set ownership bit - transmit buffers are owned by us, receive buffers by the card
|
||||
if (!is_tx)
|
||||
des[idx * PCNET_DE_SIZE + 7] = 0x80;
|
||||
}
|
||||
|
||||
static I64 PCNetAllocBuffers() {
|
||||
I64 i;
|
||||
|
||||
I64 rdes_size = PCNET_DE_SIZE * rx_buffer_count;
|
||||
I64 tdes_size = PCNET_DE_SIZE * tx_buffer_count;
|
||||
|
||||
rdes_phys = MAlloc(rdes_size, Fs->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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -30,3 +30,5 @@ LBts(&sys_run_level,RLf_DOC);
|
||||
#include "AMouse"
|
||||
#include "Host"
|
||||
Cd("..");;
|
||||
|
||||
#include "::/Adam/Net/MakeSnailNet"
|
||||
|
||||
+125
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
+538
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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");
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
+920
@@ -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);
|
||||
+243
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
+522
@@ -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";
|
||||
}
|
||||
+98
@@ -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;
|
||||
+463
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
Executable
+11
@@ -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))
|
||||
+12
@@ -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)
|
||||
{
|
||||
|
||||
+3
-1
@@ -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"$
|
||||
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
+2
-2
@@ -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"$.
|
||||
|
||||
@@ -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\");");
|
||||
@@ -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\");");
|
||||
@@ -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\");");
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
'''
|
||||
mfa - minimalist file access for TempleOS
|
||||
|
||||
usage:
|
||||
./mfa.py list <filename> [<local-filename>]
|
||||
./mfa.py put <filename> [<local-filename>]
|
||||
./mfa.py command <command>
|
||||
|
||||
for scripting:
|
||||
./mfa.py <script
|
||||
|
||||
- use tabs to separate arguments
|
||||
- Mfa.HC must be running in a loop
|
||||
|
||||
example script:
|
||||
|
||||
wait 5
|
||||
test
|
||||
put B:/AutoOSInstall.HC.Z AutoOSInstall.HC
|
||||
test
|
||||
command #include "B:/AutoOSInstall"
|
||||
test
|
||||
command OSInstall(FALSE);
|
||||
test
|
||||
|
||||
'''
|
||||
|
||||
import socket
|
||||
import sys
|
||||
import time
|
||||
|
||||
TCP_IP = '127.0.0.1'
|
||||
TCP_PORT = 7770
|
||||
|
||||
CHUNK_SIZE = 8
|
||||
BAUD = 19200
|
||||
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect((TCP_IP, TCP_PORT))
|
||||
|
||||
def read_line():
|
||||
s = b''
|
||||
while True:
|
||||
ch = sock.recv(1)
|
||||
if ch == b'\n':
|
||||
print('<', s.decode())
|
||||
return s.decode()
|
||||
else:
|
||||
s += ch
|
||||
|
||||
def read_bytes(count):
|
||||
s = b''
|
||||
while len(s) < count:
|
||||
s += sock.recv(1)
|
||||
return s
|
||||
|
||||
def send(line):
|
||||
print('>', line)
|
||||
sock.send((line + "\n").encode())
|
||||
|
||||
def test():
|
||||
send('?')
|
||||
assert read_line() == '!'
|
||||
|
||||
def do_command(*argv):
|
||||
cmd = argv[0]
|
||||
|
||||
if cmd == 'put':
|
||||
filename = argv[1]
|
||||
local_filename = argv[2] if len(argv) > 2 else filename
|
||||
|
||||
with open(local_filename, 'rb') as f:
|
||||
data = f.read()
|
||||
size = len(data)
|
||||
|
||||
test()
|
||||
send('P' + filename)
|
||||
send('S' + str(size))
|
||||
|
||||
while len(data):
|
||||
sock.send(data[0:CHUNK_SIZE])
|
||||
data = data[CHUNK_SIZE:]
|
||||
time.sleep(float(CHUNK_SIZE) * 8 / BAUD)
|
||||
|
||||
print('Read', size, 'bytes from', local_filename)
|
||||
elif cmd == 'list':
|
||||
filename = argv[1]
|
||||
local_filename = argv[2] if len(argv) > 2 else filename
|
||||
|
||||
send('L' + filename)
|
||||
|
||||
next_ = read_line()
|
||||
assert next_[0] == 'S'
|
||||
size = int(next_[1:])
|
||||
size_remaining = size
|
||||
|
||||
with open(local_filename, 'wb') as f:
|
||||
while size_remaining:
|
||||
read_now = min(size_remaining, 0x1000)
|
||||
data = read_bytes(read_now)
|
||||
f.write(data)
|
||||
size_remaining -= len(data)
|
||||
|
||||
print('Written', size, 'bytes to', local_filename)
|
||||
elif cmd == 'command':
|
||||
command = argv[1]
|
||||
|
||||
test()
|
||||
send("'" + command)
|
||||
#elif cmd == 'test':
|
||||
# test()
|
||||
elif cmd == 'wait':
|
||||
secs = float(argv[1])
|
||||
time.sleep(secs)
|
||||
else:
|
||||
raise Exception('Command error: ' + argv[0])
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
do_command(*sys.argv[1:])
|
||||
else:
|
||||
try:
|
||||
while True:
|
||||
if sys.version_info[0] < 3:
|
||||
entry = raw_input()
|
||||
else:
|
||||
entry = input()
|
||||
|
||||
if len(entry) and entry[0] != '#':
|
||||
items = entry.split('\t')
|
||||
do_command(*items)
|
||||
except(EOFError):
|
||||
pass
|
||||
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import socket
|
||||
import struct
|
||||
import sys
|
||||
import time
|
||||
|
||||
TCP_IP = '127.0.0.1'
|
||||
TCP_PORT = 7777
|
||||
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect((TCP_IP, TCP_PORT))
|
||||
|
||||
# socket #0 is never used
|
||||
socks = [False]
|
||||
|
||||
CMD_SOCKET = 1
|
||||
CMD_CLOSE = 2
|
||||
CMD_CONNECT_TCP = 3
|
||||
CMD_SEND = 4
|
||||
CMD_RECV = 5
|
||||
CMD_HELLO = 0xAA
|
||||
|
||||
def recvall(sock, count):
|
||||
s = b''
|
||||
|
||||
while len(s) < count:
|
||||
part = sock.recv(count - len(s))
|
||||
if part is None: break
|
||||
s += part
|
||||
|
||||
return s
|
||||
|
||||
while True:
|
||||
cmd = ord(sock.recv(1))
|
||||
#print('%02X' % cmd)
|
||||
if cmd is None: break
|
||||
|
||||
if cmd == CMD_HELLO:
|
||||
print('hello!')
|
||||
sock.send(b'\xAA')
|
||||
elif cmd == CMD_CLOSE:
|
||||
sockfd = ord(sock.recv(1))
|
||||
print('close(%d)' % (sockfd))
|
||||
|
||||
socks[sockfd].close()
|
||||
socks[sockfd] = None
|
||||
sock.send(struct.pack('B', 0))
|
||||
elif cmd == CMD_CONNECT_TCP:
|
||||
sockfd, length = struct.unpack('BB', recvall(sock, 2))
|
||||
hostname = recvall(sock, length).decode()
|
||||
port, = struct.unpack('H', recvall(sock, 2))
|
||||
print('connectTcp(%d, %s, %d)' % (sockfd, hostname, port))
|
||||
|
||||
try:
|
||||
socks[sockfd].connect((hostname, port))
|
||||
rc = 0
|
||||
except socket.error as e:
|
||||
print(e)
|
||||
rc = 0xff
|
||||
|
||||
sock.send(struct.pack('B', rc))
|
||||
elif cmd == CMD_RECV:
|
||||
sockfd, length, flags = struct.unpack('BBB', recvall(sock, 3))
|
||||
#print('recv(%d, %d, %d)' % (sockfd, length, flags))
|
||||
|
||||
try:
|
||||
data = socks[sockfd].recv(length)
|
||||
except socket.error as e:
|
||||
print(e)
|
||||
data = b''
|
||||
|
||||
sock.send(struct.pack('B', len(data)))
|
||||
sock.send(data)
|
||||
elif cmd == CMD_SEND:
|
||||
sockfd, length, flags = struct.unpack('BBB', recvall(sock, 3))
|
||||
data = recvall(sock, length)
|
||||
#print('send(%d, %s, %d)' % (sockfd, data, flags))
|
||||
|
||||
rc = socks[sockfd].send(data)
|
||||
sock.send(struct.pack('B', rc))
|
||||
elif cmd == CMD_SOCKET:
|
||||
af, type = struct.unpack('BB', recvall(sock, 2))
|
||||
id = len(socks)
|
||||
print("socket(%d, %d)" % (af, type))
|
||||
|
||||
socks.append(socket.socket(socket.AF_INET, socket.SOCK_STREAM))
|
||||
sock.send(struct.pack('B', id))
|
||||
Reference in New Issue
Block a user