Begin TCP implementation.

Create Binary Search Tree (BST) generic data structure and methods, integrate TCP and UDP bound socket tree structures with BST system.
Fix incorrect variable name for packet data frames across stack codebase.
Split UDP and TCP into multiple files and their own folders for organization.
Update low-level Socket functions to return Bool types, for determining success/failure of state transitions.
Integrate updated Socket function return types into TCP and UDP methods.
Add NetStop() and NetStart() functions in PCNet-II code to easily halt and continue network activity.
Add NetDebug() log output function.
Update networking documentation to reflect changes, remove outdated documentation.
This commit is contained in:
TomAwezome 2021-05-10 20:15:27 -04:00
parent 3df5dd4a9f
commit 05dd1af872
33 changed files with 3392 additions and 282 deletions

View file

@ -51,10 +51,10 @@ I64 ARPSend(U16 operation,
U32 target_ip)
{//method currently assumes send_ and target_ip EndianU16 already...
U8 *ethernet_frame;
U8 *arp_frame;
CARPHeader *header;
I64 de_index = EthernetFrameAllocate(&ethernet_frame,
I64 de_index = EthernetFrameAllocate(&arp_frame,
send_mac_address,
dest_mac_address,
ETHERTYPE_ARP,
@ -63,7 +63,7 @@ I64 ARPSend(U16 operation,
if (de_index < 0)
return de_index; // error state
header = ethernet_frame;
header = arp_frame;
header->hardware_type = EndianU16(HTYPE_ETHERNET);
header->protocol_type = EndianU16(ETHERTYPE_IPV4);

View file

@ -79,13 +79,13 @@ U32 DHCPTransactionBegin()
I64 DHCPDiscoverSend(U32 xid)
{
U8 *ethernet_frame;
U8 *dhcp_frame;
I64 de_index;
CDHCPHeader *dhcp;
CDHCPDiscoverOptions *opts;
de_index = UDPPacketAllocate(&ethernet_frame,
de_index = UDPPacketAllocate(&dhcp_frame,
0x00000000,
68,
0xFFFFFFFF,
@ -97,7 +97,7 @@ I64 DHCPDiscoverSend(U32 xid)
return de_index;
}
dhcp = ethernet_frame;
dhcp = dhcp_frame;
MemSet(dhcp, 0, sizeof(CDHCPHeader));
dhcp->opcode = DHCP_OPCODE_BOOTREQUEST;
@ -113,7 +113,7 @@ I64 DHCPDiscoverSend(U32 xid)
dhcp->gateway_ip = 0;
MemCopy(dhcp->client_hw_addr, EthernetMACGet, MAC_ADDRESS_LENGTH);
opts = ethernet_frame + sizeof(CDHCPHeader);
opts = dhcp_frame + sizeof(CDHCPHeader);
opts->cookie = EndianU32(DHCP_COOKIE);
opts->message_type = DHCP_OPTION_MESSAGETYPE;
@ -133,12 +133,12 @@ I64 DHCPDiscoverSend(U32 xid)
I64 DHCPRequestSend(U32 xid, U32 requested_ip, U32 server_ip)
{
U8 *ethernet_frame;
U8 *dhcp_frame;
I64 de_index;
CDHCPHeader *dhcp;
CDHCPRequestOptions *opts;
de_index = UDPPacketAllocate(&ethernet_frame,
de_index = UDPPacketAllocate(&dhcp_frame,
0x00000000,
68,
0xFFFFFFFF,
@ -149,7 +149,7 @@ I64 DHCPRequestSend(U32 xid, U32 requested_ip, U32 server_ip)
NetErr("DHCP SEND REQUEST: Failed, UDP Packet Allocate error.");
}
dhcp = ethernet_frame;
dhcp = dhcp_frame;
MemSet(dhcp, 0, sizeof(CDHCPHeader));
dhcp->opcode = DHCP_OPCODE_BOOTREQUEST;
@ -165,7 +165,7 @@ I64 DHCPRequestSend(U32 xid, U32 requested_ip, U32 server_ip)
dhcp->gateway_ip = 0;
MemCopy(dhcp->client_hw_addr, EthernetMACGet, MAC_ADDRESS_LENGTH);
opts = ethernet_frame + sizeof(CDHCPHeader);
opts = dhcp_frame + sizeof(CDHCPHeader);
opts->cookie = EndianU32(DHCP_COOKIE);
opts->message_type = DHCP_OPTION_MESSAGETYPE;

View file

@ -146,7 +146,7 @@ U0 DNSQuestionSerialize(U8 *buffer, CDNSQuestion *q)
I64 DNSQuestionSend(U16 id, U16 local_port, CDNSQuestion *q)
{
CIPV4Address *ipv4_addr;
U8 *frame;
U8 *dns_frame;
U16 flags;
CDNSHeader *header;
I64 de_index;
@ -172,7 +172,7 @@ I64 DNSQuestionSend(U16 id, U16 local_port, CDNSQuestion *q)
}
// UDPPacketAllocate currently only accepts IPV4 ...
de_index = UDPPacketAllocate(&frame,
de_index = UDPPacketAllocate(&dns_frame,
IPV4AddressGet,
local_port,
*ipv4_addr,
@ -186,7 +186,7 @@ I64 DNSQuestionSend(U16 id, U16 local_port, CDNSQuestion *q)
flags = DNS_OP_QUERY << 11 | DNS_FLAG_RD;
header = frame;
header = dns_frame;
header->id = EndianU16(id);
header->flags = EndianU16(flags);
@ -195,7 +195,7 @@ I64 DNSQuestionSend(U16 id, U16 local_port, CDNSQuestion *q)
header->ns_count = 0;
header->ar_count = 0;
DNSQuestionSerialize(frame + sizeof(CDNSHeader), q);
DNSQuestionSerialize(dns_frame + sizeof(CDNSHeader), q);
UDPPacketFinish(de_index);
return 0;

View file

@ -29,7 +29,10 @@ PCNet
EthernetFrameFinish (driver alias of PCNetTransmitPacketFinish)
EthernetFrameAllocate
EthernetMACGet
NetStop
NetStart
NetQueue
NetQueueInit
NetQueuePull
@ -89,6 +92,47 @@ ICMP
ICMPHandler
TCP
IsTCPStateSync
TCPGlobalsInit
TCPChecksumPartial
TCPChecksumFinal
TCPPacketAllocate
TCPPacketFinish
TCPSend
TCPSendFlags
TCPSendData
TCPPacketParse
TCPAcknowledgePacket
TCPCheckACKQueue
TCPSocket
TCPSocketBind
TCPSocketClose
TCPSocketConnect
TCPSocketListen
TCPSocketAccept
TCPSocketReceive
TCPSocketSend
TCPHandler
TCPHandleRefuse
TCPHandleSocket
TCPHandleSocketListen
TCPHandleReset
TCPHandleACK
TCPHandleValidSEQ
TCPTreeNodeInit
TCPTreeNodeAdd
TCPTreeNodeParamAdd
TCPTreeNodeParamInit
TCPTreeNodeFind
TCPTreeNodePop
TCPTreeNodeSinglePop
TCPTreeNodeQueueAdd
TCPTreeNodeQueueSocketFind
TCPTreeNodeQueueIPV4Find
TCPTreeNodeQueueSocketSinglePop
UDP
UDPTreeNodeInit
@ -167,5 +211,3 @@ NetHandler
NetHandler
NetHandlerInit
NetConfig

View file

@ -45,10 +45,12 @@ Stack progress: (# done, ~ WIP, . N/A)
~ ICMP (Internet Control Message Protocol)
- needs ICMPSendRequest implemented.
. TCP (Transmission Control Protocol)
~ TCP (Transmission Control Protocol)
- needs much testing, still many bugs/oddities.
- needs reviewing RFC for better compliance.
~ UDP (User Datagram Protocol)
- needs UDPRep function implemented to render bound tree.
# UDP (User Datagram Protocol)
- double check, some FIXMEs.
~ DNS (Domain Name System)
- needs clarifying/rewriting in certain methods.

View file

@ -1,182 +0,0 @@
Sockets... not planning to conform to unix-y socket standards unless absolutely required.
All over socket code is these global vars, lowercase functions.. its a mess.
Shrine does some pretty gnarly stuff to do their NativeSockets.
They have a class, called a CSocket, which is just a bunch of function pointers.
Then, they have another, called CSocketClass, which is a single direction list of
a domain, a type, some padding (...), and a pointer to a CSocket..
I don't even want to get into the CAddrResolver which is a yet another
function pointer to some resolver function.
Then, the socketclass and addrresolver are made into globals!
When they try to find a socket class, they have to loop through all
of the defined socket classes until they find one that matches the
params of domain and type..
Since UDP is regarded apparently as more simple than TCP,
looking at Shrine's UDP code gives a little insight into
what's going on without getting too caught up in high
level intricacies.
When UDP registers their socketclass , they pass in the #defined
type and domain, then pass in the socketclass itself.
All the typical socket functions as defined in shrine,
basically just take in this socketclass and then redirect
execution to the function defined in the socketclass.
At first, I thought "oh, why not just make the args a class!" but
this doesn't address the need to store functions as members, which
is a little niggerlicious still.
So I need a better way to do these socket function calls, without
having to rely on a pointer magic backend. The immediate thought
I have, is to ditch general use socket functions and enforce
application specific function definitions. In a way, that might
kinda end up meaning that a hell of a lot of the other way
of doing things would be stripped.
Maybe, analyze what passed variables are manipulated, and hardcode
functions with higher specificity.
...
/* Zenith Sockets are non-standard.
----------------------
Shrine implementation:
in_addr
sockaddr
sockaddr_in
addrinfo
inet_aton
inet_ntoa
socket
accept
close
bind
connect
listen
recv
recvfrom
send
sendto
setsockopt
getaddrinfo
freeaddrinfo
AddrInfoCopy
gai_strerror
create_connection
RegisterSocketClass
----------------------
Zenith implementation:
----------------------
*/
/* I think a class for a socket is a good idea.
But i do not think that the class should have
function pointers.
A CSocket is literally just a list of function pointers.
In Shrine, for example, the only UDP socket functions
that actually do anything are UDPSocketBind, UDPSocketClose,
UDPSocketRecvFrom, UDPSocketSendTo, and UDPSocketSetSockopt.
The rest just make the compiler happy by saying no_warn on the
args ...
If not function pointers, how would it work?
STREAMS / XTI / TPI lookin sexy rn ngl haha.
Maybe a hybrid of the alternatives and sockets.
One thought is to reserve sockets as some unique class thing,
and have functions that take args that go to a switch statement
to determine which code to next execute.
Perhaps, two Socket related files would make more sense,
one which defines some low-level things, and another that
(like NetHandler) is all-knowing and would discern based
on a switch statement which socket-related functions to run.
If doing this, must make sure that UDP/TCP/etc won't
need to know things only the SocketHandler or whatever we'd
call that would know.
At the root of ShrineSockets, all sockets made in later
files must be put into a RegisterSocketClass call,
which keeps track of which functions go where based on
the searched-for domain and type. So, I'd think a
beginning to an unfuck would be, if it ends up needed,
using a hash table, maybe the key would be (domain << 64 | type)
as a string, assuming the args are still I64.
I fight and I fight.. these bold ideas might be forced
to take place only after I've un-fucked sockets code alone.
Then maybe after massive unfucking/refucking, it can be more
quickly discerened what the better way to go from there is.
*/
//at the end of the day , one distinction MUST be made...
//What IS a socket ? ...
....
---------------------
Thoughts.
Sense in SocketNextState(CSocket *socket, U8 state) ?
Sense in having two socket states, current and requested?
With one, if we try to modify the value, we modify it.
There's no way of conveying or interacting with
higher-defined (TCP, UDP, etc) socket functions.
With a current and requested state, we would be
able to show both what the socket is doing right
now, and what the user/code has requested the
socket to change to. Say, there is a failure
in the higher level code when it sees a socket
and its requested next state: it will process
appropriate higher-level code and then ask for
another Socket State change accordingly or something.

View file

@ -1,43 +0,0 @@
$FG,0$So. We don't have any way to REMOVE or POP treenodes or treequeues.
Goal?
- CUDPTreeNode* UDPTreeNodePop (args?)
- U0 UDPTreeNodeFree(CUDPTreeNode *node)
- CUDPTreeQueue* UDPTreeNodeQueuePop (args?)
- U0 UDPTreeNodeQueueFree(CUDPTreeQueue *queue)
Pop will give you the now-popped-out node pointer.
Free will take that popped node and Free its resources from mem.
To POP a node, different steps necessary.
If node has no left/right, it's simple. Just free it all.
If node has only left or only right, pop it by storing its
pointer for returning, then altering the left/right pointers
for the above tree, and below tree, to stitch it one link closer
If node has both a left and right branch...
Well, i dunno. harder to stitch because conflicting trees.
Hail-mary approach is to take all the branches and respective
branches and just filter back into tree with Add.
It sounds painful.
Is it feasible to just take the Whole sub-Trees of a node,
cut ties between node and tree, then add back in the left,
then right sub trees? If we're getting rid of a central node,
that splits off into higher or lowers... ultimately, if we sever
the original node, either direction is still going to replace
the position of where the original node was, because it will
still be higher/lower than the higher-up-node. Adding back in
nodes would just leave the tree a little funky-lookin.

View file

@ -20,11 +20,11 @@ U0 ICMPReplySend(U32 destination_ip_address,
U8 *payload,
I64 length)
{
U8 *frame;
U8 *icmp_frame;
I64 de_index;
CICMPHeader *header;
de_index = IPV4PacketAllocate(&frame,
de_index = IPV4PacketAllocate(&icmp_frame,
IP_PROTOCOL_ICMP,
IPV4AddressGet,
destination_ip_address,
@ -35,7 +35,7 @@ U0 ICMPReplySend(U32 destination_ip_address,
return;
}
header = frame;
header = icmp_frame;
header->type = ICMP_TYPE_ECHO_REPLY;
header->code = 0; // why is 0 okay?
@ -44,7 +44,7 @@ U0 ICMPReplySend(U32 destination_ip_address,
header->sequence_number = sequence_number;
// TODO: header checksum is awful. Shrine says hack alert.
MemCopy(frame + sizeof(CICMPHeader), payload, length);
MemCopy(icmp_frame + sizeof(CICMPHeader), payload, length);
IPV4PacketFinish(de_index);
}

View file

@ -116,7 +116,7 @@ I64 IPV4AddressMACGet(U32 ip_address, U8 **mac_out)
}
else // "local network"
{
NetLog("GET MAC FOR IP: Attempting ARP Find by IP for address: %d.", ip_address);
NetLog("GET MAC FOR IP: Attempting ARP Find by IP for address: %0X.", ip_address);
entry = ARPCacheFind(ip_address);
if (entry)
@ -167,7 +167,7 @@ I64 IPV4PacketAllocate(U8 **frame_out,
U32 destination_ip_address,
I64 length)
{
U8 *ethernet_frame;
U8 *ipv4_frame;
U8 *destination_mac_address;
I64 error;
I64 de_index;
@ -182,7 +182,7 @@ I64 IPV4PacketAllocate(U8 **frame_out,
return error;
}
de_index = EthernetFrameAllocate(&ethernet_frame,
de_index = EthernetFrameAllocate(&ipv4_frame,
EthernetMACGet,
destination_mac_address,
ETHERTYPE_IPV4,
@ -195,7 +195,7 @@ I64 IPV4PacketAllocate(U8 **frame_out,
internet_header_length = 5;// ... why. need a #define
header = ethernet_frame;
header = ipv4_frame;
header->version_ihl = internet_header_length | 4 << 4;// ? TODO: needs #define
header->dscp_ecn = 0; // a clear define of what this actually means would be good
@ -209,7 +209,7 @@ I64 IPV4PacketAllocate(U8 **frame_out,
header->destination_ip_address = EndianU32(destination_ip_address);
header->header_checksum = IPV4Checksum(header, internet_header_length * 4);//why the 4's...
*frame_out = ethernet_frame + sizeof(CIPV4Header);
*frame_out = ipv4_frame + sizeof(CIPV4Header);
return de_index;
}

View file

@ -14,7 +14,9 @@ U0 IPV4Handler(CEthernetFrame *ethernet_frame)
break;
case IP_PROTOCOL_TCP:
NetWarn("IPV4 HANDLER: TCP. TODO.");
NetWarn("IPV4 HANDLER: TCP.");
TCPHandler(&packet);
// NetWarn("IPV4 HANDLER: TCP. TODO.");
break;
case IP_PROTOCOL_UDP:

View file

@ -56,4 +56,14 @@ U0 NetErr(U8 *format, ...)
Free(buf);
}
U0 NetDebug(U8 *format, ...)
{ // Output text to NetLogTask as Debug.
U8 *buf = StrPrintJoin(NULL, format, argc, argv);
DocBottom(net_log_task->put_doc);
DocPrint(net_log_task->put_doc, "$$BG,YELLOW$$$$DKGRAY$$%s$$BG$$$$FG$$\n", buf);
Free(buf);
}
NetLogInit;

View file

@ -12,7 +12,17 @@
#include "C:/Home/Net/Sockets"
#include "C:/Home/Net/UDP"
#include "C:/Home/Net/Utilities/BST"
#include "C:/Home/Net/UDP/UDP.HH"
#include "C:/Home/Net/UDP/UDPTree"
#include "C:/Home/Net/UDP/UDP.CC"
#include "C:/Home/Net/TCP/TCP.HH"
#include "C:/Home/Net/TCP/TCPTree"
#include "C:/Home/Net/TCP/TCP.CC"
#include "C:/Home/Net/TCP/TCPHandler.CC"
#include "C:/Home/Net/DNS"
#include "C:/Home/Net/DHCP"

View file

@ -744,5 +744,23 @@ U8 *EthernetMACGet()
return pcnet.mac_address;
}
U0 NetStop()
{ // Halt network activity by setting STOP bit on Status CSR.
U32 csr = PCNetCSRRead(PCNET_CSR_CTRLSTATUS);
Bts(&csr, PCNET_CTRL_STOP);
PCNetCSRWrite(PCNET_CSR_CTRLSTATUS, csr);
}
U0 NetStart()
{ // Continue network activity. Setting START bit clears STOP/INIT.
U32 csr = PCNetCSRRead(PCNET_CSR_CTRLSTATUS);
Bts(&csr, PCNET_CTRL_STRT);
PCNetCSRWrite(PCNET_CSR_CTRLSTATUS, csr);
}
PCNetInit;

View file

@ -348,7 +348,7 @@ U0 SocketStateErr(U8 *request, U8 state)
NetErr("Socket attempted %s while in %s state.", request, state_string);
}
U0 SocketAccept(CSocket *socket)
Bool SocketAccept(CSocket *socket)
{
switch (socket->state)
{
@ -356,22 +356,23 @@ U0 SocketAccept(CSocket *socket)
/* Socket expected to stay listening.
At protocol level, a new socket 'connected'
to this one is expected to be made. */
return;
return TRUE;
default:
SocketStateErr("ACCEPT", socket->state);
break;
return FALSE;
}
}
U0 SocketClose(CSocket *socket)
Bool SocketClose(CSocket *socket)
{ /* Sockets attempting close will enter close
request state before destroying the socket at
the protocol level. */
socket->state = SOCKET_STATE_CLOSE_REQ;
return TRUE;
}
U0 SocketBind(CSocket *socket)
Bool SocketBind(CSocket *socket)
{
switch (socket->state)
{
@ -379,15 +380,15 @@ U0 SocketBind(CSocket *socket)
/* Sockets can only be bound
if they are in initial state. */
socket->state = SOCKET_STATE_BIND_REQ;
break;
return TRUE;
default:
SocketStateErr("BIND", socket->state);
break;
return FALSE;
}
}
U0 SocketConnect(CSocket *socket)
Bool SocketConnect(CSocket *socket)
{
switch (socket->state)
{
@ -395,15 +396,15 @@ U0 SocketConnect(CSocket *socket)
/* Sockets can only be connected
if they are in initial state. */
socket->state = SOCKET_STATE_CONNECT_REQ;
break;
return TRUE;
default:
SocketStateErr("CONNECT", socket->state);
break;
return FALSE;
}
}
U0 SocketListen(CSocket *socket)
Bool SocketListen(CSocket *socket)
{
switch (socket->state)
{
@ -411,15 +412,15 @@ U0 SocketListen(CSocket *socket)
/* A socket must be bound to
set it to listening. */
socket->state = SOCKET_STATE_LISTEN_REQ;
break;
return TRUE;
default:
SocketStateErr("LISTEN", socket->state);
break;
return FALSE;
}
}
U0 SocketReceive(CSocket *socket)
Bool SocketReceive(CSocket *socket)
{
switch (socket->state)
{
@ -427,15 +428,15 @@ U0 SocketReceive(CSocket *socket)
case SOCKET_STATE_BOUND:
/* Sockets can only recv when
connected to or bound. */
break;
return TRUE;
default:
SocketStateErr("RECEIVE", socket->state);
break;
return FALSE;
}
}
U0 SocketReceiveFrom(CSocket *socket)
Bool SocketReceiveFrom(CSocket *socket)
{
switch (socket->state)
{
@ -443,30 +444,30 @@ U0 SocketReceiveFrom(CSocket *socket)
case SOCKET_STATE_BOUND:
/* Sockets can only recvfrom when
connected to or bound. */
break;
return TRUE;
default:
SocketStateErr("RECEIVE FROM", socket->state);
break;
return FALSE;
}
}
U0 SocketSend(CSocket *socket)
Bool SocketSend(CSocket *socket)
{
switch (socket->state)
{
case SOCKET_STATE_OPEN:
/* Sockets can only send when
they have been connected to. */
break;
return TRUE;
default:
SocketStateErr("SEND", socket->state);
break;
return FALSE;
}
}
U0 SocketSendTo(CSocket *socket)
Bool SocketSendTo(CSocket *socket)
{
switch (socket->state)
{
@ -478,10 +479,10 @@ U0 SocketSendTo(CSocket *socket)
initial state. Protocol logic
will determine how to change
state based on params. */
break;
return TRUE;
default:
SocketStateErr("SEND TO", socket->state);
break;
return FALSE;
}
}

1325
src/Home/Net/TCP/TCP.CC Executable file

File diff suppressed because it is too large Load diff

136
src/Home/Net/TCP/TCP.HH Executable file
View file

@ -0,0 +1,136 @@
#define TCP_MAX_PORT 65535
#define TCP_SRTT_ALPHA 0.9
#define TCP_RTO_BETA 2
#define TCP_RTO_MIN 0.2
#define TCP_RTO_MAX 10
#define TCP_WINDOW_SIZE 8192
#define TCP_MSS 536 // Max Segment Size default
#define TCP_TIMEOUT 5000
#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_WAIT1 5
#define TCP_STATE_FIN_WAIT2 6
#define TCP_STATE_CLOSE_WAIT 7
#define TCP_STATE_CLOSING 8
#define TCP_STATE_LAST_ACK 9
#define TCP_STATE_TIME_WAIT 10
// TCP header flags. Test with Bt(), e.g. Bt(&flags, TCPf_RST)
#define TCPf_FIN 0
#define TCPf_SYN 1
#define TCPf_RST 2
//#define TCPf_PSH 3 // most stacks don't implement PUSH.
#define TCPf_ACK 4
//#define TCPf_URG 5 // most stacks don't implement URGENT.
#define TCPF_FIN (1 << TCPf_FIN)
#define TCPF_SYN (1 << TCPf_SYN)
#define TCPF_RST (1 << TCPf_RST)
//#define TCPF_PSH (1 << TCPf_PSH) // most stacks don't implement PUSH.
#define TCPF_ACK (1 << TCPf_ACK)
//#define TCPF_URG (1 << TCPf_URG) // most stacks don't implement URGENT.
class CTCPAckQueue:CQueue
{
F64 time_sent;
U32 retries;
U32 start_seq_num;
U32 end_seq_num;
U32 tcp_packet_size; // Size of memory allocation of U8* tcp_packet.
U8 *tcp_packet; // Memory location of entire TCP Header + Data Payload.
};
// stores packet data of incoming connect() requests,
// local accept() will search queue for pending connections,
// and use this data to send out notification connection was accepted.
class CTCPAcceptQueue:CQueue
{
U32 segment_seq_num;
U32 ipv4_address;
U16 port;
};
class CTCPSocket
{
CSocket *socket;
U8 state;
U64 timeout; // timeout for socket calls. Initialized 0: non-blocking.
CSocketAddressStorage source_address; // based on ->family, cast or assign to a var as IPV4/IPV6 CSocketAddress
CSocketAddressStorage destination_address;
CTCPAckQueue *ack_queue; // Sent TCP packets awaiting an ACK collect here to be retransmitted. CQueue head.
U64 accept_queue_limit;
CTCPAcceptQueue *accept_queue;
U32 max_segment_size;
U32 first_unacked_seq; // SEQ number of first unacknowledged octet
U32 next_send_seq_num;
U32 next_recv_seq_num;
F64 srtt; // Smoothed Round-Trip Time
F64 connection_time;
U32 receive_window;// allowed # of unacknowledged incoming octets
U32 send_window; // allowed # of unacknowledged outgoing octets
I64 write_position; // position in RX buffer to write incoming octets
I64 read_position; // position in RX buffer Socket calls have read up to
I64 receive_buffer_size;
U8 *receive_buffer;
};
class CTCPTreeQueue:CQueue
{
CTCPSocket *socket;
};
class CTCPTreeNode:CBST
{
CTCPTreeQueue *queue;
};
class CTCPHeader
{
U16 source_port;
U16 destination_port;
U32 seq_num;
U32 ack_num;
U8 data_offset;
U8 flags;
U16 window_size;
U16 checksum;
U16 urgent_pointer;
};
class CTCPPseudoHeader
{
U32 source_address;
U32 destination_address;
U8 zeros;
U8 protocol;
U16 tcp_length; // length of TCP headers & payload
};
class CTCPGlobals
{
CTCPTreeNode *bound_socket_tree;
U16 next_source_port;
} tcp_globals;

361
src/Home/Net/TCP/TCPHandler.CC Executable file
View file

@ -0,0 +1,361 @@
Bool TCPHandleValidSEQ(CTCPSocket *tcp_socket, CTCPHeader *header, U32 segment_seq_num, I64 length, U8 *data)
{// returns the value of must_ack, used later in TCPHandleSocket. Copies data to receive buffer.
Bool must_ack = FALSE;
I64 write_position;
I64 next_position;
I64 i;
tcp_socket->send_window = header->window_size;
// Shrine doesn't use EndianU16 (ntohs)? are these all being stored network byte order? ...
switch (tcp_socket->state)
{
case TCP_STATE_ESTABLISHED:
case TCP_STATE_FIN_WAIT1:
case TCP_STATE_FIN_WAIT2: // FIN2 check is ommitted in Shrine, yet used in below logic. Adding.
NetDebug("TCP HANDLE VALID SEQ: Updating data in receive buffer.");
// TODO: review while loops, make sure we DO NOT HANG INTERRUPT HANDLER.
write_position = tcp_socket->write_position;
while (length && segment_seq_num != tcp_socket->next_recv_seq_num)
{
segment_seq_num = (segment_seq_num + 1) & 0xFFFFFFFF;
data++;
length--;
}
for (i = 0; i < length; i++)
{
next_position = (write_position + 1) & (tcp_socket->receive_buffer_size - 1);
if (next_position == tcp_socket->read_position)
break; // ...?
tcp_socket->receive_buffer[write_position] = data[i];
write_position = next_position;
}
tcp_socket->write_position = write_position;
tcp_socket->next_recv_seq_num += i;
if (i > 0)
must_ack = TRUE;
if (Bt(&header->flags, TCPf_FIN))
{
must_ack = TRUE;
tcp_socket->next_recv_seq_num++;
switch (tcp_socket->state)
{
case TCP_STATE_ESTABLISHED:
tcp_socket->state = TCP_STATE_CLOSE_WAIT;
break;
case TCP_STATE_FIN_WAIT1:
case TCP_STATE_FIN_WAIT2:
tcp_socket->state = TCP_STATE_TIME_WAIT;
break;
} // review RFC, whether more state checks needed here.
}
break;
default:
break;
}
return must_ack;
}
Bool TCPHandleACK(CTCPSocket *tcp_socket, CIPV4Packet *packet, CTCPHeader *header, U32 segment_seq_num, U32 segment_ack_num, U32 segment_length)
{ // returns the value of must_ack, used later in TCPHandleSocket
I64 ack_relative;
I64 ack_next_relative;
if (Bt(&header->flags, TCPf_ACK))
{
ack_relative = (segment_ack_num - tcp_socket->first_unacked_seq) & 0xFFFFFFFF;
ack_next_relative = (tcp_socket->next_send_seq_num - tcp_socket->first_unacked_seq) & 0xFFFFFFFF;
// Shrine has comments here about RFC poor wording,
// TODO: review RFC and implement more refined approach.
if (ack_relative <= ack_next_relative)
{
TCPAcknowledgePacket(tcp_socket, segment_ack_num);
// "Accept ACK"
tcp_socket->first_unacked_seq = segment_ack_num;
switch (tcp_socket->state)
{
case TCP_STATE_SYN_SENT:
if (!Bt(&header->flags, TCPf_SYN))
break;
// else, fall-through
case TCP_STATE_SYN_RECEIVED:
tcp_socket->state = TCP_STATE_ESTABLISHED;
tcp_socket->srtt = tS - tcp_socket->connection_time;
break;
default:
break;
}
}
else
{
NetWarn("TCP HANDLE SOCKET: Invalid ACK.");
switch (tcp_socket->state)
{
case TCP_STATE_LISTEN:
case TCP_STATE_SYN_SENT:
case TCP_STATE_SYN_RECEIVED:
TCPSend(packet->destination_ip_address,
EndianU16(header->destination_port),
packet->source_ip_address,
EndianU16(header->source_port),
segment_ack_num,
segment_seq_num + segment_length,
TCPF_ACK | TCPF_RST);
break;
default:
if (IsTCPStateSync(tcp_socket))
return TRUE; // must ACK the packet.
break;
}
}
}
return FALSE; // do not need to ACK the packet.
}
Bool TCPHandleReset(CTCPSocket *tcp_socket, CTCPHeader *header, Bool is_seq_valid)
{ // returns whether or not to stop overall TCP procedure.
if (Bt(&header->flags, TCPf_RST))
{
switch (tcp_socket->state)
{
case TCP_STATE_SYN_SENT:
if (tcp_socket->first_unacked_seq == tcp_socket->next_send_seq_num)
{
NetWarn("TCP HANDLE SOCKET: Got RST, socket state SYN_SENT. Connection refused.");
tcp_socket->state = TCP_STATE_CLOSED;
return TRUE;
}
break;
default:
if (is_seq_valid)
{
NetWarn("TCP HANDLE SOCKET: Got RST, connection refused by remote host.");
tcp_socket->state = TCP_STATE_CLOSED;
return TRUE;
}
}
}
return FALSE;
}
U0 TCPHandleSocketListen(CTCPSocket *tcp_socket, CIPV4Packet *packet, CTCPHeader *header, U32 segment_seq_num)
{ // if SYN and socket listening, queue up the connection in the socket's accept queue.
CTCPAcceptQueue *new_connection;
if (Bt(&header->flags, TCPf_SYN) && QueueSize(tcp_socket->accept_queue) < tcp_socket->accept_queue_limit)
{
NetDebug("TCP HANDLE SOCKET LISTEN: Adding new connection to Socket accept queue");
new_connection = CAlloc(sizeof(CTCPAcceptQueue));
new_connection->segment_seq_num = segment_seq_num;
new_connection->ipv4_address = packet->source_ip_address;
new_connection->port = header->source_port;
QueueInsertRev(new_connection, tcp_socket->accept_queue);
}
else
{ // refuse
NetDebug("TCP HANDLE SOCKET LISTEN: Header flags not SYN or Queue full, REFUSING CONNECTION");
TCPSend(packet->destination_ip_address,
EndianU16(header->destination_port),
packet->source_ip_address,
EndianU16(header->source_port),
segment_seq_num + 1,
segment_seq_num + 1,
TCPF_ACK | TCPF_RST);
}
}
U0 TCPHandleSocket(CTCPSocket *tcp_socket, CIPV4Packet *packet, CTCPHeader *header, U8 *data, I64 length)
{
U32 segment_length = length;
U32 segment_seq_num = EndianU32(header->seq_num);
U32 segment_ack_num = EndianU32(header->ack_num);
Bool must_ack = FALSE;
Bool is_seq_valid = FALSE;
I64 seq_relative;
I64 seq_end_relative;
if (Bt(&header->flags, TCPf_FIN))
segment_length++;
if (Bt(&header->flags, TCPf_SYN))
segment_length++;
switch (tcp_socket->state)
{
case TCP_STATE_LISTEN:
NetDebug("TCP HANDLE SOCKET: Running TCP HANDLE SOCKET LISTEN");
TCPHandleSocketListen(tcp_socket, packet, header, segment_seq_num);
return;
case TCP_STATE_CLOSED:
NetErr("TCP HANDLE SOCKET: Received packet but TCP Socket is CLOSED.");
return;
default:
if (Bt(&header->flags, TCPf_SYN))
{
tcp_socket->next_recv_seq_num = ++segment_seq_num;
must_ack = TRUE;
}
if (segment_length == 0 && tcp_socket->receive_window == 0)
{
is_seq_valid = (segment_seq_num == tcp_socket->next_recv_seq_num);
}
else
{
seq_relative = (segment_seq_num - tcp_socket->next_recv_seq_num) & 0xFFFFFFFF;
seq_end_relative = (segment_seq_num + segment_length - 1 - tcp_socket->next_recv_seq_num) & 0xFFFFFFFF;
is_seq_valid = (seq_relative < tcp_socket->receive_window ||
seq_end_relative < tcp_socket->receive_window);
}
if (!is_seq_valid)
NetWarn("TCP HANDLE SOCKET: Invalid SEQ.");
must_ack = TCPHandleACK(tcp_socket, packet, header, segment_seq_num, segment_ack_num, segment_length);
if (TCPHandleReset(tcp_socket, header, is_seq_valid))
return;
// shrine has FIXME check remote addr and port... we already do that.. right ?
// make sure to double check.
if (is_seq_valid)
must_ack = TCPHandleValidSEQ(tcp_socket, header, segment_seq_num, length, data);
if (must_ack)
TCPSendFlags(tcp_socket, TCPF_ACK);
}
}
I64 TCPHandleRefuse(CIPV4Packet *packet, CTCPHeader *header, I64 length)
{
I64 de_index;
U32 ack_num = EndianU32(header->ack_num);
U32 seq_num = EndianU32(header->seq_num);
no_warn length; // TODO: Will reset generation need length ?
ack_num = ++seq_num;
seq_num = 0;
// review RFC Reset-Generation ...
de_index = TCPSend(packet->destination_ip_address,
EndianU16(header->destination_port),
packet->source_ip_address,
EndianU16(header->source_port),
seq_num,
ack_num,
TCPF_RST | TCPF_ACK);
if (de_index < 0)
{
NetErr("TCP Handler Refuse: TCP Send failed.");
return de_index;
}
return 0;
}
I64 TCPHandler(CIPV4Packet *packet)
{
CTCPHeader *header;
U16 destination_port;
U8 *data;
I64 length;
CTCPTreeNode *head = tcp_globals.bound_socket_tree;
CTCPTreeNode *node;
CTCPTreeQueue *queue;
CTCPSocket *tcp_socket;
I64 error = TCPPacketParse(&header, &data, &length, packet);
if (error < 0)
{
NetErr("TCP HANDLER: Packet Parse Error.");
return error;
}
NetDebug("TCP HANDLER: Caught packet with dest port of %0X (B.E.)", header->destination_port);
destination_port = EndianU16(header->destination_port); // B.E. -> L.E.
NetDebug("TCP HANDLER: Caught packet with dest port of %0X (L.E.)", destination_port);
if (head)
{
node = TCPTreeNodeFind(destination_port, head);
if (node)
{
NetDebug("TCP HANDLER: Found node for port, looking for address %0X (L.E.)", packet->source_ip_address);
queue = TCPTreeNodeQueueIPV4Find(packet->source_ip_address, node); // TODO: make sure bit order is correct here!!
if (queue)
{
tcp_socket = queue->socket;
NetLog("TCP HANDLER: Port and Address are in bound tree.");
}
else
{
NetWarn("TCP HANDLER: Found node for port, but address is not in node queue.");
NetWarn(" TCP source ip: 0x%0X.", packet->source_ip_address);
NetWarn("TCP HANDLER: Sending TCP RST ACK packet. Refusing connection.");
TCPHandleRefuse(packet, header, length);
return -1;
}
}
else
{
NetDebug("TCP HANDLER: NODE SEARCH FAILURE: PORT %0X", destination_port);
NetWarn("TCP HANDLER: Node for Port is not in tree.");
NetWarn("TCP HANDLER: Sending TCP RST ACK packet. Refusing connection.");
TCPHandleRefuse(packet, header, length);
return -1;
}
}
else
{
NetWarn("TCP HANDLER: Socket tree is currently empty.");
NetWarn("TCP HANDLER: Sending TCP RST ACK packet. Refusing connection.");
TCPHandleRefuse(packet, header, length);
return -1;
}
// at this point, tcp_socket is set, otherwise has already returned -1.
NetDebug("TCP HANDLER: Running TCP HANDLE SOCKET");
TCPHandleSocket(tcp_socket, packet, header, data, length);
}

156
src/Home/Net/TCP/TCPTree.CC Executable file
View file

@ -0,0 +1,156 @@
/***************************************************
TCP Bound/Connected Socket Tree Functions
***************************************************/
CTCPTreeNode *TCPTreeNodeInit()
{ // init new empty tree/node. Init socket queue head links.
CTCPTreeNode *tree_node = CAlloc(sizeof(CTCPTreeNode));
tree_node->queue = CAlloc(sizeof(CTCPTreeQueue)); // CQueue vs CTCPTreeQueue ?...
QueueInit(tree_node->queue);
return tree_node;
}
U0 TCPTreeNodeAdd(CTCPTreeNode *node, CTCPTreeNode *tree)
{ // using temp and last allows avoiding recursion and non-growing stack issues.
BSTAdd(node, tree);
}
CTCPTreeNode *TCPTreeNodeParamAdd(I64 port, CTCPTreeNode *tree)
{ // add a node using params, return pointer to the node
CTCPTreeNode *result = TCPTreeNodeInit;
result->value = port;
TCPTreeNodeAdd(result, tree);
return result;
}
CTCPTreeNode *TCPTreeNodeParamInit(I64 port)
{
CTCPTreeNode *result = TCPTreeNodeInit;
result->value = port;
return result;
}
CTCPTreeNode *TCPTreeNodeFind(I64 port, CTCPTreeNode *tree)
{
return BSTFind(port, tree);
}
CTCPTreeNode *TCPTreeNodePop(I64 port, CTCPTreeNode *tree)
{ // Pops whole sub-tree, original tree loses whole branch.
return BSTPop(port, tree);
}
CTCPTreeNode *TCPTreeNodeSinglePop(I64 port, CTCPTreeNode *tree)
{ // Pop a tree off, then add back in its sub-trees to main tree.
// Original node sub-tree links are cleared.
return BSTSinglePop(port, tree);
}
U0 TCPTreeNodeQueueAdd(CTCPSocket *socket, CTCPTreeNode *node)
{
CTCPTreeQueue *new_entry = CAlloc(sizeof(CTCPTreeQueue));
new_entry->socket = socket;
QueueInsert(new_entry, node->queue->last);
}
CTCPTreeQueue *TCPTreeNodeQueueSocketFind(CTCPSocket *socket, CTCPTreeNode *node)
{
CTCPTreeQueue *temp_queue;
temp_queue = node->queue->next;
while (temp_queue != node->queue)
{
if (temp_queue->socket == socket)
return temp_queue;
temp_queue = temp_queue->next;
}
return NULL;
}
CTCPTreeQueue *TCPTreeNodeQueueSocketSinglePop(CTCPSocket *socket, CTCPTreeNode *node)
{ // search by socket, pop a single TCPTreeQueue off the node, return popped queue.
CTCPTreeQueue *temp_queue = TCPTreeNodeQueueSocketFind(socket, node);
if (temp_queue)
{
QueueRemove(temp_queue);
}
return temp_queue; // if not found, NULL.
}
CTCPTreeQueue *TCPTreeNodeQueueIPV4Find(U32 address, CTCPTreeNode *node, Bool specific=FALSE)
{ // address should be pulled from an instance of CIPV4Address (TODO... double check what bit order we're in ?)
// use TRUE or FALSE in specific arg to dictate how to handle INADDR_ANY.
CTCPTreeQueue *temp_queue = node->queue->next;
CSocketAddressIPV4 *temp_ip;
while (temp_queue != node->queue)
{
if (temp_queue->socket->destination_address.family == AF_INET)
{
temp_ip = &temp_queue->socket->destination_address;
NetLog("TCPTreeNodeQueueIPV4Find: Comparing: addr, nodequeue addr: %08X, %08X",
address, temp_ip->address.address);
if (temp_ip->address.address == address)
{
NetLog("TCPTreeNodeQueueIPV4Find: Address match: addr, nodequeue addr: %08X, %08X ",
address, temp_ip->address.address);
return temp_queue;
}
}
else
NetErr("TCPTreeNodeQueueIPV4Find: Skipped iteration of a non AF_INET family: %0X", temp_queue->socket->destination_address.family);
temp_queue = temp_queue->next;
}
if (!specific)
{
temp_queue = node->queue->next;
NetDebug("TCPTreeNodeQueueIPV4Find: Exact match not found, looking for an INADDR_ANY address.");
while (temp_queue != node->queue)
{
if (temp_queue->socket->destination_address.family == AF_INET)
{
temp_ip = &temp_queue->socket->destination_address;
NetLog("TCPTreeNodeQueueIPV4Find: Comparing: addr, nodequeue addr: %08X, %08X",
address, temp_ip->address.address);
if (temp_ip->address.address == INADDR_ANY)
{
NetLog("TCPTreeNodeQueueIPV4Find: Address match: addr, nodequeue addr: %08X, %08X ",
address, temp_ip->address.address);
return temp_queue;
}
}
else
NetErr("TCPTreeNodeQueueIPV4Find: Skipped iteration of a non AF_INET family: %0X", temp_queue->socket->destination_address.family);
temp_queue = temp_queue->next;
}
}
return NULL;
}

42
src/Home/Net/Tests/TCPTest0.CC Executable file
View file

@ -0,0 +1,42 @@
CTCPSocket *tcp = TCPSocket(AF_INET);
CTCPSocket *new;
ClassRep(tcp);
CSocketAddressIPV4 *addr = CAlloc(sizeof(CSocketAddressIPV4));
addr->port = EndianU16(0xBEEF);
addr->family = AF_INET;
addr->address.address = INADDR_ANY;
TCPSocketBind(tcp, addr);
TCPSocketListen(tcp, 32);
ClassRep(tcp_globals.bound_socket_tree);
tcp->timeout = TCP_TIMEOUT;
while (TRUE)
{
new = TCPSocketAccept(tcp);
if (new)
{
"\n\nSocket accepted\n\n";
ClassRep(new);
break;
}
else
Sleep(1);
}
"\nTrying to close the accepted socket:\n";
TCPSocketClose(new);
"\nTrying to close the original Listening socket\n";
TCPSocketClose(tcp);
Sleep(10);
NetStop;
//NetStart;

41
src/Home/Net/Tests/TCPTest1.CC Executable file
View file

@ -0,0 +1,41 @@
CTCPSocket *tcp = TCPSocket(AF_INET);
CTCPSocket *new;
ClassRep(tcp);
CSocketAddressIPV4 *addr = CAlloc(sizeof(CSocketAddressIPV4));
addr->port = EndianU16(0xBEEF);
addr->family = AF_INET;
addr->address.address = INADDR_ANY;
TCPSocketBind(tcp, addr);
TCPSocketListen(tcp, 32);
"\nTry TCPSocketClose(tcp);\n";
/*
ClassRep(tcp_globals.bound_socket_tree);
tcp->timeout = TCP_TIMEOUT;
while (TRUE)
{
new = TCPSocketAccept(tcp);
if (new)
{
"\n\nSocket accepted, HOOoOO BOOYYY\n\n";
ClassRep(new);
break;
}
else
Sleep(1);
}
*/
/*
"\nTrying to close the accepted socket:\n";
TCPSocketClose(new);
"\nTrying to close the original Listening socket\n";
TCPSocketClose(tcp);*/

31
src/Home/Net/Tests/TCPTest2.CC Executable file
View file

@ -0,0 +1,31 @@
CTCPSocket *tcp = TCPSocket(AF_INET);
tcp->timeout = TCP_TIMEOUT;
ClassRep(tcp);
CSocketAddressIPV4 *socket_addr = CAlloc(sizeof(CSocketAddressIPV4));
CIPV4Address addr;
PresentationToNetwork(AF_INET, "127.0.0.1", &addr);
socket_addr->port = EndianU16(0xBEEF);
socket_addr->family = AF_INET;
socket_addr->address.address = addr.address;
"\nTrying to connect the socket\n";
if (TCPSocketConnect(tcp, socket_addr) == 0)
"\nSocket connected.\n";
ClassRep(tcp_globals.bound_socket_tree);
Sleep(5000);
"\nTrying to close the socket\n";
TCPSocketClose(tcp);
Sleep(500);
//NetStop;
//NetStart;

43
src/Home/Net/Tests/TCPTest3.CC Executable file
View file

@ -0,0 +1,43 @@
CTCPSocket *tcp = TCPSocket(AF_INET);
U8 *buffer = CAlloc(8);
*buffer = EndianU64(0xDEADBEEFC0DEFADE);
tcp->timeout = TCP_TIMEOUT;
ClassRep(tcp);
CSocketAddressIPV4 *socket_addr = CAlloc(sizeof(CSocketAddressIPV4));
CIPV4Address addr;
PresentationToNetwork(AF_INET, "127.0.0.1", &addr);
socket_addr->port = EndianU16(0xBEEF);
socket_addr->family = AF_INET;
socket_addr->address.address = addr.address;
"\nTrying to connect the socket\n";
if (TCPSocketConnect(tcp, socket_addr) == 0)
"\nSocket connected.\n";
else
"\nFailed to connect.\n";
"\n\nTrying to Send data\n";
if (TCPSocketSend(tcp, buffer, 8) > 0)
"\nData sent\n";
else
"\nData not sent\n";
ClassRep(tcp_globals.bound_socket_tree);
Sleep(5000);
"\n\nTrying to close the socket\n";
TCPSocketClose(tcp);
"\nSocket Closed.";
Sleep(500);
//NetStop;
//NetStart;

77
src/Home/Net/Tests/TCPTest4.CC Executable file
View file

@ -0,0 +1,77 @@
CTCPSocket *tcp = TCPSocket(AF_INET);
CTCPSocket *new;
U8 buffer_size = 16;
U8 *buffer = CAlloc(buffer_size);
CSocketAddressIPV4 *socket_addr = CAlloc(sizeof(CSocketAddressIPV4));
U0 TCPTest()
{
tcp->timeout = TCP_TIMEOUT * 3;
ClassRep(tcp);
socket_addr->port = EndianU16(0xBEEF);
socket_addr->family = AF_INET;
socket_addr->address.address = INADDR_ANY;
"\nTrying to bind socket.\n";
if (TCPSocketBind(tcp, socket_addr) == 0)
"\nSocket bound.\n";
else
"\nFailed to bind socket.\n";
"\nTrying to listen on socket.\n";
if (TCPSocketListen(tcp, 5) == 0)
"\nSocket now listening.\n";
else
"\nFail to listen on socket.\n";
"\nTrying to accept a connection\n";
if ((new = TCPSocketAccept(tcp)) != NULL)
"\nNew socket connected.\n";
else
{
"\nFailed to accept.\n";
return;
}
"\n\nTrying to Receive data\n";
if (TCPSocketReceive(new, buffer, buffer_size) > 0)
"\nData received\n";
else
"\nData not received\n";
"\n\n";
Dm(buffer, buffer_size);
"\n\n";
ClassRep(tcp_globals.bound_socket_tree);
Sleep(5000);
"\n\nTrying to close the accepted socket\n";
if (TCPSocketClose(new) == 0)
"\nSocket Closed.";
else
"\nSocket close failed."
"\n\nTrying to close the listening socket\n";
if (TCPSocketClose(tcp) == 0)
"\nSocket Closed.";
else
"\nSocket close failed.";
Sleep(500);
//NetStop;
//NetStart;
}
TCPTest;

View file

@ -14,6 +14,9 @@ U0 UDPSocketTest()
i1->address.address = 0xF00DBABE;
UDPSocketBind(u0, i0);
ClassRep(udp_globals.bound_socket_tree);
UDPSocketBind(u1, i1);
"Before remove first socket\n";

711
src/Home/Net/UDP/UDP.CC Executable file
View file

@ -0,0 +1,711 @@
/***************************************************
UDP Socket Functions
***************************************************/
U0 UDPGlobalsInit()
{
udp_globals.bound_socket_tree = NULL;
}
I64 UDPPacketAllocate(U8 **frame_out,
U32 source_ip,
U16 source_port,
U32 destination_ip,
U16 destination_port,
I64 length)
{
U8 *udp_frame;
I64 de_index;
CUDPHeader *header;
de_index = IPV4PacketAllocate(&udp_frame,
IP_PROTOCOL_UDP,
source_ip,
destination_ip,
sizeof(CUDPHeader) + length);
if (de_index < 0)
{
NetLog("UDP PACKET ALLOCATE: Ethernet Frame Allocate failed.");
return de_index;
}
header = udp_frame;
header->source_port = EndianU16(source_port);
header->destination_port = EndianU16(destination_port);
header->length = EndianU16(sizeof(CUDPHeader) + length);
header->checksum = 0;
*frame_out = udp_frame + sizeof(CUDPHeader);
return de_index;
}
U0 UDPPacketFinish(I64 de_index)
{ // alias for IPV4PacketFinish, alias for EthernetFrameFinish, alias for driver send packet
IPV4PacketFinish(de_index);
}
I64 UDPPacketParse(U16 *source_port_out,
U16 *destination_port_out,
U8 **data_out,
I64 *length_out,
CIPV4Packet *packet)
{
// check ip protocol? probably redundant
CUDPHeader *header = packet->data;
// TODO: Shrine has FIXME, validate packet length!
// NetDebug("UDP PACKET PARSE: Caught packet, src port: 0x%0X (B.E.)", header->source_port);
// NetDebug("UDP PACKET PARSE: Caught packet, dest port: 0x%0X (B.E.)", header->destination_port);
*source_port_out = EndianU16(header->source_port);
*destination_port_out = EndianU16(header->destination_port);
// NetDebug("UDP PACKET PARSE: Source Port output: 0x%0X (L.E.)", *source_port_out);
// NetDebug("UDP PACKET PARSE: Destination Port Output: 0x%0X (L.E.)", *destination_port_out);
*data_out = packet->data + sizeof(CUDPHeader);
*length_out = packet->length - sizeof(CUDPHeader);
return 0;
}
CUDPSocket *UDPSocket(U16 domain=AF_UNSPEC)
{
U16 type = SOCKET_DATAGRAM;
CUDPSocket *udp_socket = CAlloc(sizeof(CUDPSocket));
udp_socket->socket = Socket(domain, type);
udp_socket->receive_address.family = domain; // INET, INET6, or unspecified
return udp_socket;
}
I64 UDPSocketBind(CUDPSocket *udp_socket, CSocketAddressStorage *address_source)
{
CUDPTreeNode *head = udp_globals.bound_socket_tree;
CUDPTreeNode *temp_node;
CUDPMessageQueue *message_queue;
CSocketAddressIPV4 *ipv4_source;
CSocketAddressIPV4 *ipv4_receive;
CSocketAddressIPV6 *ipv6_source;
CSocketAddressIPV6 *ipv6_receive;
U16 port;
if (!SocketBind(udp_socket->socket))
{
NetErr("UDP SOCKET BIND: Failed, Socket state-machine must be in READY state.");
return -1;
}
if (udp_socket->bound_to)
{
NetErr("UDP SOCKET BIND: UDP Socket currently Bound.");
return -1;
}
switch (address_source->family)
{
case AF_INET:
if (udp_socket->receive_address.family == AF_INET6)
{
NetErr("UDP SOCKET BIND: Incompatible Address type.");
return -1;
}
ipv4_source = address_source;
ipv4_receive = &udp_socket->receive_address;
ipv4_receive->address.address = ipv4_source->address.address; // bind socket to address in parameter.
ipv4_receive->port = ipv4_source->port; // ... consistency would say keep in Big Endian ...
port = EndianU16(ipv4_source->port); // port member should be Big Endian, so now we're going L.E (?)
break;
case AF_INET6:
if (udp_socket->receive_address.family == AF_INET)
{
NetErr("UDP SOCKET BIND: Incompatible Address type.");
return -1;
}
ipv6_source = address_source;
ipv6_receive = &udp_socket->receive_address;
// ...
// ...
port = EndianU16(ipv6_source->port); // port member should be Big Endian, so now we're going L.E (?)
Debug("TODO: IPV6 UDP BIND");
break;
case AF_UNSPEC:
Debug("TODO: AF_UNSPEC UDP BIND -- param family");
break;
}
// at this point, Socket and Address have matching family values
if (head)
{
// look for our port.
temp_node = UDPTreeNodeFind(port, head);
if (temp_node)
{ // if we find we have bound sockets at port, check address before adding to queue
switch (address_source->family)
{
case AF_INET:
// TODO: will any INADDR_ANY sockets bound at the port break this?
if (UDPTreeNodeQueueIPV4Find(ipv4_receive->address.address, temp_node))
{
NetErr("UDP SOCKET BIND: Address already in Bound Socket Tree !");
return -1;
}
else
{ // if no address match, free to add socket to the node queue
UDPTreeNodeQueueAdd(udp_socket, temp_node);
}
break;
case AF_INET6:
Debug("TODO: IPV6 UDP BIND");
break;
case AF_UNSPEC:
Debug("TODO: AF_UNSPEC UDP BIND -- found in bound tree");
break;
}
}
else
{ // if we get no node back from port search, we didn't find it and are free to add a new node.
temp_node = UDPTreeNodeParamAdd(port, head); // add new node with port, return its *.
UDPTreeNodeQueueAdd(udp_socket, temp_node);
}
}
else // if no bound sockets, we init the tree as a new node
{
udp_globals.bound_socket_tree = head = UDPTreeNodeParamInit(port); //... shouuuld be in L.E .. ?
UDPTreeNodeQueueAdd(udp_socket, head); // add the udp socket to the port queue
// maybe more checks to do before this, dunno rn.
}
udp_socket->bound_to = port;
udp_socket->receive_queue = message_queue = CAlloc(sizeof(CUDPMessageQueue));
QueueInit(message_queue); // acts as head. add messages to but don't remove head.
switch (udp_socket->socket->state)
{
case SOCKET_STATE_BIND_REQ: // if BIND request success, set BOUND.
udp_socket->socket->state = SOCKET_STATE_BOUND;
break;
default:
NetErr("UDP SOCKET BIND: Failed, Misconfigured Socket state-machine.");
return -1;
}
return 0;
}
I64 UDPSocketClose(CUDPSocket *udp_socket)
{ // close, pop, and free the socket from the bound tree.
CUDPTreeNode *head = udp_globals.bound_socket_tree;
CUDPTreeNode *node;
CUDPTreeQueue *queue;
CUDPMessageQueue *message;
SocketClose(udp_socket->socket); // TODO: testing on closing a socket while another task is using it
// after low-level socket close, even if protocol level socket fails close, it is now disabled (state is close request)
node = UDPTreeNodeFind(udp_socket->bound_to, head);
if (node)
queue = UDPTreeNodeQueueSocketFind(udp_socket, node);
else
{
Debug("Didn't find node at socket during UDPSocketClose!\n");
return -1;
}
if (queue)
{
UDPTreeNodeQueueSocketSinglePop(udp_socket, node);
if (node->queue == node->queue->next)
{ // if we popped the only queue on the node, remove the node.
if (node == head)
{ // head is the global. if node is the global, change it and add branches.
if (node->left)
{
udp_globals.bound_socket_tree = head = node->left;
if (node->right)
UDPTreeNodeAdd(node->right, head);
}
else if (node->right)
udp_globals.bound_socket_tree = node->right;
else
udp_globals.bound_socket_tree = NULL;
}
else // if node is not the global, just pop it from the tree
UDPTreeNodeSinglePop(node->value, head);
Free(node);
}
Free(udp_socket->socket);
message = udp_socket->receive_queue->next;
while (message != udp_socket->receive_queue)
{
NetWarn("UDP SOCKET CLOSE: Freeing message @ 0x%X", message);
Free(message->data);
QueueRemove(message);
Free(message);
message = udp_socket->receive_queue->next;
}
NetWarn("UDP SOCKET CLOSE: Freeing message queue & socket.");
Free(udp_socket->receive_queue);
Free(udp_socket);
Free(queue);
}
else
{
Debug("Didn't find queue at socket during UDPSocketClose!\n");
return -1;
}
return 0;
}
// UDPSocketConnect (Shrine just has FIXME: 'implement')
// UDPListen (Shrine just has no_warns, not implemented)
I64 UDPSocketReceiveFrom(CUDPSocket *udp_socket, U8 *buffer, I64 len, CSocketAddressStorage *address_out)
{ // ommitted I64 addrlen, flags not implemented
CSocketAddressIPV4 *ipv4_socket_addr;
CSocketAddressIPV6 *ipv6_socket_addr;
CUDPMessageQueue *message;
if (!SocketReceiveFrom(udp_socket->socket))
{
NetErr("UDP SOCKET RECEIVE FROM: Socket state-machine must be in OPEN or BOUND state.");
return -1;
}
if (len < 0)
{
NetErr("UDP SOCKET RECEIVE FROM: Invalid length requested.");
return -1;
}
if (udp_socket->receive_timeout_ms != 0)
udp_socket->receive_max_timeout = counts.jiffies + udp_socket->receive_timeout_ms * JIFFY_FREQ / 1000;
message = udp_socket->receive_queue;
while (message == message->next)
{ // wait for a message to be added to queue. head is non-message.
if (udp_socket->receive_timeout_ms == 0)
return -1; // if no timeout set and didn't see message, bail early
if (counts.jiffies > udp_socket->receive_max_timeout)
{ // Shrine has TODO: 'seterror(EWOULDBLOCK)' investigate this
NetErr("UDP SOCKET RECEIVE FROM: Timed out.");
return -1;
}
Yield;
}
NetLog("UDP SOCKET RECEIVE FROM: Saw message in receive queue.");
message = message->next;
if (address_out)
{
switch (message->from_address.family)
{
case AF_INET:
ipv4_socket_addr = address_out;
MemCopy(ipv4_socket_addr, &message->from_address, sizeof(CSocketAddressIPV4));
break;
case AF_INET6:
ipv6_socket_addr = address_out;
MemCopy(ipv6_socket_addr, &message->from_address, sizeof(CSocketAddressIPV6));
break;
case AF_UNSPEC:
NetWarn("UDP Receive From AF_UNSPEC UDPSocket Address Family\n");
break;
}
}
if (len >= message->data_length - message->received_length)
{
NetLog("UDP SOCKET RECEIVE FROM: Requested length longer than data. Truncating.");
len = message->data_length - message->received_length;
MemCopy(buffer, message->data + message->received_length, len);
NetWarn("UDP SOCKET RECEIVE FROM: Freeing message and removing from queue.");
// all data pulled, release message
QueueRemove(message);
Free(message->data);
Free(message);
}
else
{
NetLog("UDP SOCKET RECEIVE FROM: Requsted length shorter than data at message.");
MemCopy(buffer, message->data + message->received_length, len);
message->received_length += len;
}
return len;
}
I64 UDPSocketSendTo(CUDPSocket *udp_socket, U8 *buffer, I64 len, CSocketAddressStorage *destination_addr)
{
CSocketAddressStorage *dest;
CSocketAddressIPV4 *ipv4_destination;
CSocketAddressIPV6 *ipv6_destination;
U8 *payload_frame;
I64 de_index;
if (!SocketSendTo(udp_socket->socket))
{
NetErr("UDP SOCKET SEND TO: Socket state-machine must be in OPEN, BOUND or READY state.");
return -1;
}
switch (udp_socket->socket->state)
{
case SOCKET_STATE_OPEN: // Socket State machine must
case SOCKET_STATE_BOUND: // be in connected or bound state for send.
dest = &udp_socket->receive_address; // if already bound, ignore param destination
break; // and use stored address as send address.
case SOCKET_STATE_READY: // If socket state is initial, attempt to bind it to destination.
NetLog("UDP SOCKET SEND TO: Socket unbound. Attempting Bind at address parameter.");
UDPSocketBind(udp_socket, destination_addr);
dest = destination_addr;
break;
}
switch (dest->family)
{
case AF_INET:
ipv4_destination = dest;
de_index = UDPPacketAllocate(&payload_frame,
IPV4AddressGet(),
0,
EndianU32(ipv4_destination->address.address),
EndianU16(ipv4_destination->port),
len); // is get address parens redundant?
break;
case AF_INET6:
ipv6_destination = dest;
Debug("TODO: IPV6 Not implemented yet");
break;
case AF_UNSPEC:
Debug("TODO: Error UDP Send To AF_UNSPEC\n");
break;
}
if (de_index < 0)
return -1;
MemCopy(payload_frame, buffer, len); // copies the data in buffer param into the udp payload frame
UDPPacketFinish(de_index);
return 0;
}
// UDPSocketSetOpt ?
I64 UDPHandler(CIPV4Packet *packet)
{ // TODO: Need either two UDP handlers for IPv4/IPv6, or logic changes if IPV6 is desired.
U16 source_port;
U16 destination_port;
U8 *data;
I64 length;
CUDPTreeNode *head = udp_globals.bound_socket_tree;
CUDPTreeNode *node;
CUDPTreeQueue *queue;
CUDPMessageQueue *messages_head;
CUDPMessageQueue *message;
CUDPSocket *udp_socket;
CSocketAddressIPV4 *ipv4_addr;
NetLog("UDP HANDLER: Beginning handling UDP Packet.");
I64 error = UDPPacketParse(&source_port, &destination_port, &data, &length, packet);
// NetDebug("UDP HANDLER: Packet parsed, port to search in bound tree: 0x%0X (L.E...?)", destination_port);
if (error < 0)
{
NetErr("UDP HANDLER: Packet Parse Error.");
return error;
}
if (head)
{
node = UDPTreeNodeFind(destination_port, head);
if (node)
{
queue = UDPTreeNodeQueueIPV4Find(packet->source_ip_address, node); // TODO: make sure bit order is correct here!!
if (queue)
{
udp_socket = queue->socket;
NetLog("UDP HANDLER: Port and Address are in bound tree.");
}
else
{
NetWarn("UDP HANDLER: Found node for port, but address is not in node queue.");
NetWarn(" UDP packet dest ip: 0x%0X.", packet->destination_ip_address);
return -1;
}
}
else
{
NetWarn("UDP HANDLER: Node for Port is not in tree.");
return -1;
}
}
else
{
NetWarn("UDP HANDLER: Socket tree is currently empty.");
return -1;
}
// at this point, udp_socket is set, otherwise has already returned -1.
NetLog("UDP HANDLER: Putting data payload into message queue.");
messages_head = udp_socket->receive_queue;
message = CAlloc(sizeof(CUDPMessageQueue));
QueueInsertRev(message, messages_head);
message->data = CAlloc(length);
MemCopy(message->data, data, length);
message->data_length = length;
ipv4_addr = &message->from_address;
ipv4_addr->family = AF_INET;
ipv4_addr->port = EndianU16(source_port);
ipv4_addr->address.address = EndianU32(packet->source_ip_address);
NetLog("UDP HANDLER: Copying packet source IP (BE) to FROM_ADDRESS of UDP Socket: %08X ", ipv4_addr->address.address);
NetLog("UDP HANDLER: Data payload succesfully placed in message queue.");
return error;
}
// the socket functions just act on the socket state machine.
// NetErr and return fail vals if socket FSM improperly used.
// Careful with Free()'s.
U0 UDPTreeNodeRep(CUDPTreeNode *node)
{
CUDPTreeQueue *queue = node->queue->next;
CUDPSocket *socket;
CSocketAddressIPV4 *ipv4_addr;
CSocketAddressIPV6 *ipv6_addr;
U8 *string;
CUDPMessageQueue *message;
"Port $$YELLOW$$%d$$FG$$ (UDP Node @ $$CYAN$$0x%X$$FG$$):\n", node->value, node;
while (queue != node->queue)
{
socket = queue->socket;
switch (socket->receive_address.family)
{
case AF_UNSPEC:
break;
case AF_INET:
ipv4_addr = &socket->receive_address;
string = MStrPrint("%d.%d.%d.%d",
ipv4_addr->address.address.u8[3],
ipv4_addr->address.address.u8[2],
ipv4_addr->address.address.u8[1],
ipv4_addr->address.address.u8[0]); // todo: kludge, endianness...
" $$BROWN$$%s$$FG$$ (UDP Tree Queue @ $$CYAN$$0x%X$$FG$$):\n", string, queue;
Free(string);
break;
case AF_INET6:
ipv6_addr = &socket->receive_address;
break;
default:
break;
}
" Timeout: %dms\n", socket->receive_timeout_ms;
if (socket->receive_queue) // todo: move socket messages Queue alloc from bind to creation to avoid check?
{
message = socket->receive_queue->next;
while (message != socket->receive_queue)
{
" Queued Message @ $$CYAN$$0x%X$$FG$$:\n", message;
switch (message->from_address.family)
{
case AF_UNSPEC:
string = StrNew("AF_UNSPEC");
break;
case AF_INET:
ipv4_addr = &message->from_address;
string = NetworkToPresentation(ipv4_addr->family, &ipv4_addr->address);
break;
case AF_INET6:
string = StrNew("IPV6");
break;
default:
string = StrNew("INVALID");
break;
}
" From Address: $$BROWN$$%s$$FG$$\n", string;
" Data length: %d\n", message->data_length;
" Received data length: %d\n", message->received_length;
Free(string);
message = message->next;
}
}
queue = queue->next;
}
"\n";
}
U0 UDPRep()
{
CUDPTreeNode *node = udp_globals.bound_socket_tree;
CUDPRepEntry *head;
CUDPRepEntry *entry;
CUDPRepEntry *temp_entry;
"$$LTBLUE$$UDP Report:$$FG$$\n\n";
if (node)
{
head = CAlloc(sizeof(CUDPRepEntry));
QueueInit(head); // no QueueRemove the head
entry = CAlloc(sizeof(CUDPRepEntry));
entry->node = node;
QueueInsert(entry, head);
// perform depth-first-search while Entry Queue has nodes not fully visited.
while (entry != head)
{
if (entry->node->left)
{ // if node has one, add an Entry for the left branch, continue loop.
temp_entry = CAlloc(sizeof(CUDPRepEntry));
temp_entry->node = entry->node->left;
QueueInsertRev(temp_entry, head);
// if left branch, but no right: toss early, now fully traveled.
if (!entry->node->right)
{
QueueRemove(entry);
UDPTreeNodeRep(entry->node);
Free(entry);
}
entry = temp_entry;
}
else if (entry->node->right)
{ // if no left, but right: add right to queue, pop Entry, Rep, set entry to right.
temp_entry = CAlloc(sizeof(CUDPRepEntry));
temp_entry->node = entry->node->right;
QueueInsertRev(temp_entry, head);
QueueRemove(entry);
UDPTreeNodeRep(entry->node);
Free(entry);
entry = temp_entry;
}
else
{ // pop Entry, Rep, if last Entry in Queue has right add it, pop & Rep travelled Entry, entry = right.
QueueRemove(entry);
UDPTreeNodeRep(entry->node);
Free(entry);
if (head->last != head)
{
temp_entry = head->last;
if (temp_entry->node->right)
{
entry = temp_entry;
temp_entry = CAlloc(sizeof(CUDPRepEntry));
temp_entry->node = entry->node->right;
QueueInsertRev(temp_entry, head);
QueueRemove(entry);
UDPTreeNodeRep(entry->node);
Free(entry);
entry = temp_entry;
}
else
{
QueueRemove(temp_entry);
UDPTreeNodeRep(temp_entry->node);
Free(temp_entry);
entry = head->last;
}
}
else
break;
}
}
Free(head);
}
else
"No UDP Sockets currently bound.\n\n";
}
UDPGlobalsInit;

52
src/Home/Net/UDP/UDP.HH Executable file
View file

@ -0,0 +1,52 @@
#define UDP_MAX_PORT 65535
class CUDPMessageQueue:CQueue
{ // each bound socket queues data. recv functions & handler use this.
U8 *data; // contains the UDP payload data.
I64 data_length; // size of payload data.
I64 received_length; // amount of the data received so far.
CSocketAddressStorage from_address; // when UDP Handler sees UDP packet, this is filled with where packet came from.
// recvfrom uses this to fill its address_out parameter.
};
class CUDPSocket
{
CSocket *socket;
CUDPMessageQueue *receive_queue;
CSocketAddressStorage receive_address; // based on ->family, cast or assign to a var as IPV4/IPV6 CSocketAddress
I64 receive_timeout_ms;
I64 receive_max_timeout;
U16 bound_to; // represents the currently bound port
};
class CUDPTreeQueue:CQueue
{
CUDPSocket *socket;
};
class CUDPTreeNode:CBST
{
CUDPTreeQueue *queue;
};
class CUDPRepEntry:CQueue
{
CUDPTreeNode *node;
};
class CUDPHeader
{
U16 source_port;
U16 destination_port;
U16 length;
U16 checksum;
};
class CUDPGlobals
{
CUDPTreeNode *bound_socket_tree;
} udp_globals;

124
src/Home/Net/UDP/UDPTree.CC Executable file
View file

@ -0,0 +1,124 @@
/***************************************************
UDP Bound Socket Tree Functions
***************************************************/
CUDPTreeNode *UDPTreeNodeInit()
{ // init new empty tree/node. Init socket queue head links.
CUDPTreeNode *tree_node = CAlloc(sizeof(CUDPTreeNode));
tree_node->queue = CAlloc(sizeof(CUDPTreeQueue)); // CQueue vs CUDPTreeQueue ?...
QueueInit(tree_node->queue);
return tree_node;
}
U0 UDPTreeNodeAdd(CUDPTreeNode *node, CUDPTreeNode *tree)
{ // using temp and last allows avoiding recursion and non-growing stack issues.
BSTAdd(node, tree);
}
CUDPTreeNode *UDPTreeNodeParamAdd(I64 port, CUDPTreeNode *tree)
{ // add a node using params, return pointer to the node
CUDPTreeNode *result = UDPTreeNodeInit;
result->value = port;
UDPTreeNodeAdd(result, tree);
return result;
}
CUDPTreeNode *UDPTreeNodeParamInit(I64 port)
{
CUDPTreeNode *result = UDPTreeNodeInit;
result->value = port;
return result;
}
CUDPTreeNode *UDPTreeNodeFind(I64 port, CUDPTreeNode *tree)
{
return BSTFind(port, tree);
}
CUDPTreeNode *UDPTreeNodePop(I64 port, CUDPTreeNode *tree)
{ // Pops whole sub-tree, original tree loses whole branch.
return BSTPop(port, tree);
}
CUDPTreeNode *UDPTreeNodeSinglePop(I64 port, CUDPTreeNode *tree)
{ // Pop a tree off, then add back in its sub-trees to main tree.
// Original node sub-tree links are cleared.
return BSTSinglePop(port, tree);
}
U0 UDPTreeNodeQueueAdd(CUDPSocket *socket, CUDPTreeNode *node)
{
CUDPTreeQueue *new_entry = CAlloc(sizeof(CUDPTreeQueue));
new_entry->socket = socket;
QueueInsert(new_entry, node->queue->last);
}
CUDPTreeQueue *UDPTreeNodeQueueSocketFind(CUDPSocket *socket, CUDPTreeNode *node)
{
CUDPTreeQueue *temp_queue;
temp_queue = node->queue->next;
while (temp_queue != node->queue)
{
if (temp_queue->socket == socket)
return temp_queue;
temp_queue = temp_queue->next;
}
return NULL;
}
CUDPTreeQueue *UDPTreeNodeQueueIPV4Find(U32 address, CUDPTreeNode *node)
{ // address should be pulled from an instance of CIPV4Address (TODO... double check what bit order we're in ?)
// TODO: should INADDR_ANY entries be stored and looped, or keep current returning ASAP at INNADDR_ANY ?
CUDPTreeQueue *temp_queue = node->queue->next;
CSocketAddressIPV4 *temp_ip;
while (temp_queue != node->queue)
{
if (temp_queue->socket->receive_address.family == AF_INET)
{
temp_ip = &temp_queue->socket->receive_address;
NetLog("UDPTreeNodeQueueIPV4Find: Comparing: addr, nodequeue addr: %08X, %08X",
address, temp_ip->address.address);
if (temp_ip->address.address == address || temp_ip->address.address == INADDR_ANY)
{
NetLog("UDPTreeNodeQueueIPV4Find: Address match: addr, nodequeue addr: %08X, %08X ",
address, temp_ip->address.address);
return temp_queue;
}
}
temp_queue = temp_queue->next;
}
return NULL;
}
CUDPTreeQueue *UDPTreeNodeQueueSocketSinglePop(CUDPSocket *socket, CUDPTreeNode *node)
{ // search by socket, pop a single UDPTreeQueue off the node, return popped queue.
CUDPTreeQueue *temp_queue = UDPTreeNodeQueueSocketFind(socket, node);
if (temp_queue)
{
QueueRemove(temp_queue);
}
return temp_queue; // if not found, NULL.
}

148
src/Home/Net/Utilities/BST.CC Executable file
View file

@ -0,0 +1,148 @@
/***************************************************
Binary Search Tree (BST) Implementation
***************************************************/
class CBST
{
CBST *left;
CBST *right;
I64 value;
};
CBST *BSTInit()
{
return CAlloc(sizeof(CBST));
}
U0 BSTAdd(CBST *node, CBST *tree)
{ // using temp and last allows avoiding recursion and non-growing stack issues.
CBST *temp_tree = tree;
CBST *last_tree = temp_tree;
while (temp_tree)
{ // loop ends when temp_tree hits a NULL node.
if (node->value < temp_tree->value)
{ // if node smaller, go left
last_tree = temp_tree;
temp_tree = temp_tree->left;
}
else
{ // if node equal or larger, go right
last_tree = temp_tree;
temp_tree = temp_tree->right;
}
}
// once while loop ends, this results in last_tree
// being the resulting tree to store the node inside of.
// recompute the direction and set.
if (node->value < last_tree->value)// if node smaller, go left
last_tree->left = node;
else // if node equal or larger, go right
last_tree->right = node;
}
CBST *BSTParamAdd(I64 value, CBST *tree)
{ // add a node using params, return pointer to the node
CBST *result = BSTInit;
result->value = value;
BSTAdd(result, tree);
return result;
}
CBST *BSTParamInit(I64 value)
{
CBST *result = BSTInit;
result->value = value;
return result;
}
CBST *BSTFind(I64 value, CBST *tree)
{
CBST *temp_tree = tree;
while (temp_tree)
{
if (value < temp_tree->value) // if value smaller, go left
temp_tree = temp_tree->left;
else if (value > temp_tree->value) // if value larger, go right
temp_tree = temp_tree->right;
else // if value equal, match found.
break;
}
return temp_tree; // ! NULL if not found.
}
CBST *BSTPop(I64 value, CBST *tree)
{ // mimics TreeNodeFind. pops whole sub-tree, original tree loses whole branch.
CBST *parent_tree = tree;
CBST *temp_tree = parent_tree;
Bool is_left = FALSE;
Bool is_right = FALSE;
while (temp_tree)
{
if (value < temp_tree->value)
{
parent_tree = temp_tree;
temp_tree = temp_tree->left;
is_right = FALSE;
is_left = TRUE;
}
else if (value > temp_tree->value)
{
parent_tree = temp_tree;
temp_tree = temp_tree->right;
is_right = TRUE;
is_left = FALSE;
}
else // if value equal, match found.
break;
}
if (temp_tree)
{ //if we found it, clear its parents link to the node
if (is_left)
{
parent_tree->left = NULL;
}
else if (is_right)
{
parent_tree->right = NULL;
}
}
return temp_tree; // NULL if not found.
}
CBST *BSTSinglePop(I64 value, CBST *tree)
{ // pop a tree off, then add back in its sub-trees to main tree.
// original node sub-trees are cleared.
CBST *node = BSTPop(value, tree);
CBST *left = node->left;
CBST *right = node->right;
if (node)
{
if (left)
{ // if node has left tree, add the tree
BSTAdd(left, tree);
node->left = NULL;
}
if (right)
{ // if node has right tree, add the tree.
BSTAdd(right, tree);
node->right = NULL;
}
}
return node;
}