+// --- Server
+tVFS_Node *TCP_Server_Init(tInterface *Interface)
+{
+ tTCPListener *srv;
+
+ srv = malloc( sizeof(tTCPListener) );
+
+ if( srv == NULL ) {
+ Log_Warning("TCP", "malloc failed for listener (%i) bytes", sizeof(tTCPListener));
+ return NULL;
+ }
+
+ srv->Interface = Interface;
+ srv->Port = 0;
+ srv->NextID = 0;
+ srv->Connections = NULL;
+ srv->ConnectionsTail = NULL;
+ srv->NewConnections = NULL;
+ srv->Next = NULL;
+ srv->Node.Flags = VFS_FFLAG_DIRECTORY;
+ srv->Node.Size = -1;
+ srv->Node.ImplPtr = srv;
+ srv->Node.NumACLs = 1;
+ srv->Node.ACLs = &gVFS_ACL_EveryoneRW;
+ srv->Node.ReadDir = TCP_Server_ReadDir;
+ srv->Node.FindDir = TCP_Server_FindDir;
+ srv->Node.IOCtl = TCP_Server_IOCtl;
+ srv->Node.Close = TCP_Server_Close;
+
+ SHORTLOCK(&glTCP_Listeners);
+ srv->Next = gTCP_Listeners;
+ gTCP_Listeners = srv;
+ SHORTREL(&glTCP_Listeners);
+
+ return &srv->Node;
+}
+
+/**
+ * \brief Wait for a new connection and return the connection ID
+ * \note Blocks until a new connection is made
+ * \param Node Server node
+ * \param Pos Position (ignored)
+ */
+char *TCP_Server_ReadDir(tVFS_Node *Node, int Pos)
+{
+ tTCPListener *srv = Node->ImplPtr;
+ tTCPConnection *conn;
+ char *ret;
+
+ ENTER("pNode iPos", Node, Pos);
+
+ Log_Log("TCP", "Thread %i waiting for a connection", Threads_GetTID());
+ for(;;)
+ {
+ SHORTLOCK( &srv->lConnections );
+ if( srv->NewConnections != NULL ) break;
+ SHORTREL( &srv->lConnections );
+ Threads_Yield(); // TODO: Sleep until poked
+ continue;
+ }
+
+
+ // Increment the new list (the current connection is still on the
+ // normal list)
+ conn = srv->NewConnections;
+ srv->NewConnections = conn->Next;
+
+ SHORTREL( &srv->lConnections );
+
+ LOG("conn = %p", conn);
+ LOG("srv->Connections = %p", srv->Connections);
+ LOG("srv->NewConnections = %p", srv->NewConnections);
+ LOG("srv->ConnectionsTail = %p", srv->ConnectionsTail);
+
+ ret = malloc(9);
+ itoa(ret, conn->Node.ImplInt, 16, 8, '0');
+ Log_Log("TCP", "Thread %i got '%s'", Threads_GetTID(), ret);
+ LEAVE('s', ret);
+ return ret;
+}
+
+/**
+ * \brief Gets a client connection node
+ * \param Node Server node
+ * \param Name Hexadecimal ID of the node
+ */
+tVFS_Node *TCP_Server_FindDir(tVFS_Node *Node, const char *Name)
+{
+ tTCPConnection *conn;
+ tTCPListener *srv = Node->ImplPtr;
+ char tmp[9];
+ int id = atoi(Name);
+
+ ENTER("pNode sName", Node, Name);
+
+ // Sanity Check
+ itoa(tmp, id, 16, 8, '0');
+ if(strcmp(tmp, Name) != 0) {
+ LOG("'%s' != '%s' (%08x)", Name, tmp, id);
+ LEAVE('n');
+ return NULL;
+ }
+
+ Log_Debug("TCP", "srv->Connections = %p", srv->Connections);
+ Log_Debug("TCP", "srv->NewConnections = %p", srv->NewConnections);
+ Log_Debug("TCP", "srv->ConnectionsTail = %p", srv->ConnectionsTail);
+
+ // Search
+ SHORTLOCK( &srv->lConnections );
+ for(conn = srv->Connections;
+ conn;
+ conn = conn->Next)
+ {
+ LOG("conn->Node.ImplInt = %i", conn->Node.ImplInt);
+ if(conn->Node.ImplInt == id) break;
+ }
+ SHORTREL( &srv->lConnections );
+
+ // If not found, ret NULL
+ if(!conn) {
+ LOG("Connection %i not found", id);
+ LEAVE('n');
+ return NULL;
+ }
+
+ // Return node
+ LEAVE('p', &conn->Node);
+ return &conn->Node;
+}
+
+/**
+ * \brief Handle IOCtl calls
+ */
+int TCP_Server_IOCtl(tVFS_Node *Node, int ID, void *Data)
+{
+ tTCPListener *srv = Node->ImplPtr;
+
+ switch(ID)
+ {
+ case 4: // Get/Set Port
+ if(!Data) // Get Port
+ return srv->Port;
+
+ if(srv->Port) // Wait, you can't CHANGE the port
+ return -1;
+
+ if(!CheckMem(Data, sizeof(Uint16))) // Sanity check
+ return -1;
+
+ // Permissions check
+ if(Threads_GetUID() != 0
+ && *(Uint16*)Data != 0
+ && *(Uint16*)Data < 1024)
+ return -1;
+
+ // TODO: Check if a port is in use
+
+ // Set Port
+ srv->Port = *(Uint16*)Data;
+ if(srv->Port == 0) // Allocate a random port
+ srv->Port = TCP_GetUnusedPort();
+ else // Else, mark this as used
+ TCP_AllocatePort(srv->Port);
+
+ Log_Log("TCP", "Server %p listening on port %i", srv, srv->Port);
+
+ return srv->Port;
+ }
+ return 0;
+}
+
+void TCP_Server_Close(tVFS_Node *Node)
+{
+ free(Node->ImplPtr);
+}
+
+// --- Client
+/**
+ * \brief Create a client node
+ */
+tVFS_Node *TCP_Client_Init(tInterface *Interface)
+{
+ tTCPConnection *conn = malloc( sizeof(tTCPConnection) );
+
+ conn->State = TCP_ST_CLOSED;
+ conn->Interface = Interface;
+ conn->LocalPort = -1;
+ conn->RemotePort = -1;
+ memset( &conn->RemoteIP, 0, sizeof(conn->RemoteIP) );
+
+ conn->Node.ImplPtr = conn;
+ conn->Node.NumACLs = 1;
+ conn->Node.ACLs = &gVFS_ACL_EveryoneRW;
+ conn->Node.Read = TCP_Client_Read;
+ conn->Node.Write = TCP_Client_Write;
+ conn->Node.IOCtl = TCP_Client_IOCtl;
+ conn->Node.Close = TCP_Client_Close;
+
+ conn->RecievedBuffer = RingBuffer_Create( TCP_RECIEVE_BUFFER_SIZE );
+
+ SHORTLOCK(&glTCP_OutbountCons);
+ conn->Next = gTCP_OutbountCons;
+ gTCP_OutbountCons = conn;
+ SHORTREL(&glTCP_OutbountCons);
+
+ return &conn->Node;
+}
+
+/**
+ * \brief Wait for a packet and return it
+ * \note If \a Length is smaller than the size of the packet, the rest
+ * of the packet's data will be discarded.
+ */
+Uint64 TCP_Client_Read(tVFS_Node *Node, Uint64 Offset, Uint64 Length, void *Buffer)
+{
+ tTCPConnection *conn = Node->ImplPtr;
+ size_t len;
+
+ ENTER("pNode XOffset XLength pBuffer", Node, Offset, Length, Buffer);
+ LOG("conn = %p {State:%i}", conn, conn->State);
+
+ // Check if connection is open
+ while( conn->State == TCP_ST_SYN_RCVD || conn->State == TCP_ST_SYN_SENT )
+ Threads_Yield();
+
+ if( conn->State != TCP_ST_OPEN )
+ {
+ Mutex_Acquire( &conn->lRecievedPackets );
+ len = RingBuffer_Read( Buffer, conn->RecievedBuffer, Length );
+ Mutex_Release( &conn->lRecievedPackets );
+
+ if( len == 0 ) {
+ VFS_MarkAvaliable(Node, 0);
+ LEAVE('i', -1);
+ return -1;
+ }
+
+ LEAVE('i', len);
+ return len;
+ }
+
+ // Wait
+ VFS_SelectNode(Node, VFS_SELECT_READ, NULL, "TCP_Client_Read");
+
+ // Lock list and read
+ Mutex_Acquire( &conn->lRecievedPackets );
+
+ // Attempt to read all `Length` bytes
+ len = RingBuffer_Read( Buffer, conn->RecievedBuffer, Length );
+
+ if( len == 0 || conn->RecievedBuffer->Length == 0 ) {
+ LOG("Marking as none avaliable (len = %i)", len);
+ VFS_MarkAvaliable(Node, 0);
+ }
+
+ // Release the lock (we don't need it any more)
+ Mutex_Release( &conn->lRecievedPackets );
+
+ LEAVE('i', len);
+ return len;
+}
+
+/**
+ * \brief Send a data packet on a connection
+ */
+void TCP_INT_SendDataPacket(tTCPConnection *Connection, size_t Length, void *Data)
+{
+ char buf[sizeof(tTCPHeader)+Length];
+ tTCPHeader *packet = (void*)buf;
+
+ packet->SourcePort = htons(Connection->LocalPort);
+ packet->DestPort = htons(Connection->RemotePort);
+ packet->DataOffset = (sizeof(tTCPHeader)/4)*16;
+ packet->WindowSize = TCP_WINDOW_SIZE;
+
+ packet->AcknowlegementNumber = htonl(Connection->NextSequenceRcv);
+ packet->SequenceNumber = htonl(Connection->NextSequenceSend);
+ packet->Flags = TCP_FLAG_PSH|TCP_FLAG_ACK; // Hey, ACK if you can!
+
+ memcpy(packet->Options, Data, Length);
+
+ Log_Debug("TCP", "Send sequence 0x%08x", Connection->NextSequenceSend);
+ Debug_HexDump("[TCP ] TCP_INT_SendDataPacket: Data = ",
+ Data, Length);
+
+ TCP_SendPacket( Connection, sizeof(tTCPHeader)+Length, packet );
+
+ Connection->NextSequenceSend += Length;
+}
+
+/**
+ * \brief Send some bytes on a connection
+ */
+Uint64 TCP_Client_Write(tVFS_Node *Node, Uint64 Offset, Uint64 Length, void *Buffer)
+{
+ tTCPConnection *conn = Node->ImplPtr;
+ size_t rem = Length;
+
+ ENTER("pNode XOffset XLength pBuffer", Node, Offset, Length, Buffer);
+
+ // Check if connection is open
+ while( conn->State == TCP_ST_SYN_RCVD || conn->State == TCP_ST_SYN_SENT )
+ Threads_Yield();
+
+ if( conn->State != TCP_ST_OPEN ) {
+ VFS_MarkError(Node, 1);
+ LEAVE('i', -1);
+ return -1;
+ }
+
+ while( rem > TCP_MAX_PACKET_SIZE )
+ {
+ TCP_INT_SendDataPacket(conn, TCP_MAX_PACKET_SIZE, Buffer);
+ Buffer += TCP_MAX_PACKET_SIZE;
+ }
+
+ TCP_INT_SendDataPacket(conn, rem, Buffer);
+
+ LEAVE('i', Length);
+ return Length;
+}
+
+/**
+ * \brief Open a connection to another host using TCP
+ * \param Conn Connection structure
+ */
+void TCP_StartConnection(tTCPConnection *Conn)
+{
+ tTCPHeader hdr = {0};
+
+ Conn->State = TCP_ST_SYN_SENT;
+
+ hdr.SourcePort = htons(Conn->LocalPort);
+ hdr.DestPort = htons(Conn->RemotePort);
+ Conn->NextSequenceSend = rand();
+ hdr.SequenceNumber = htonl(Conn->NextSequenceSend);
+ hdr.DataOffset = (sizeof(tTCPHeader)/4) << 4;
+ hdr.Flags = TCP_FLAG_SYN;
+ hdr.WindowSize = htons(TCP_WINDOW_SIZE); // Max
+ hdr.Checksum = 0; // TODO
+
+ TCP_SendPacket( Conn, sizeof(tTCPHeader), &hdr );
+
+ Conn->NextSequenceSend ++;
+ Conn->State = TCP_ST_SYN_SENT;
+ return ;
+}
+
+/**
+ * \brief Control a client socket
+ */
+int TCP_Client_IOCtl(tVFS_Node *Node, int ID, void *Data)
+{
+ tTCPConnection *conn = Node->ImplPtr;
+
+ ENTER("pNode iID pData", Node, ID, Data);
+
+ switch(ID)
+ {
+ case 4: // Get/Set local port
+ if(!Data)
+ LEAVE_RET('i', conn->LocalPort);
+ if(conn->State != TCP_ST_CLOSED)
+ LEAVE_RET('i', -1);
+ if(!CheckMem(Data, sizeof(Uint16)))
+ LEAVE_RET('i', -1);
+
+ if(Threads_GetUID() != 0 && *(Uint16*)Data < 1024)
+ LEAVE_RET('i', -1);
+
+ conn->LocalPort = *(Uint16*)Data;
+ LEAVE_RET('i', conn->LocalPort);
+
+ case 5: // Get/Set remote port
+ if(!Data) LEAVE_RET('i', conn->RemotePort);
+ if(conn->State != TCP_ST_CLOSED) LEAVE_RET('i', -1);
+ if(!CheckMem(Data, sizeof(Uint16))) LEAVE_RET('i', -1);
+ conn->RemotePort = *(Uint16*)Data;
+ LEAVE_RET('i', conn->RemotePort);
+
+ case 6: // Set Remote IP
+ if( conn->State != TCP_ST_CLOSED )
+ LEAVE_RET('i', -1);
+ if( conn->Interface->Type == 4 )
+ {
+ if(!CheckMem(Data, sizeof(tIPv4))) LEAVE_RET('i', -1);
+ conn->RemoteIP.v4 = *(tIPv4*)Data;
+ }
+ else if( conn->Interface->Type == 6 )
+ {
+ if(!CheckMem(Data, sizeof(tIPv6))) LEAVE_RET('i', -1);
+ conn->RemoteIP.v6 = *(tIPv6*)Data;
+ }
+ LEAVE_RET('i', 0);
+
+ case 7: // Connect
+ if(conn->LocalPort == 0xFFFF)
+ conn->LocalPort = TCP_GetUnusedPort();
+ if(conn->RemotePort == -1)
+ LEAVE_RET('i', 0);
+
+ TCP_StartConnection(conn);
+ LEAVE_RET('i', 1);
+
+ // Get recieve buffer length
+ case 8:
+ LEAVE_RET('i', conn->RecievedBuffer->Length);
+ }
+
+ return 0;
+}
+
+void TCP_Client_Close(tVFS_Node *Node)
+{
+ tTCPConnection *conn = Node->ImplPtr;
+ tTCPHeader packet;
+
+ ENTER("pNode", Node);
+
+ if( conn->State == TCP_ST_CLOSE_WAIT || conn->State == TCP_ST_OPEN )
+ {
+ packet.SourcePort = htons(conn->LocalPort);
+ packet.DestPort = htons(conn->RemotePort);
+ packet.DataOffset = (sizeof(tTCPHeader)/4)*16;
+ packet.WindowSize = TCP_WINDOW_SIZE;
+
+ packet.AcknowlegementNumber = 0;
+ packet.SequenceNumber = htonl(conn->NextSequenceSend);
+ packet.Flags = TCP_FLAG_FIN|TCP_FLAG_ACK;
+
+ TCP_SendPacket( conn, sizeof(tTCPHeader), &packet );
+ }
+
+ switch( conn->State )
+ {
+ case TCP_ST_CLOSE_WAIT:
+ conn->State = TCP_ST_CLOSED;
+ break;
+ case TCP_ST_OPEN:
+ conn->State = TCP_ST_FIN_WAIT1;
+ while( conn->State == TCP_ST_FIN_WAIT1 ) Threads_Yield();
+ break;
+ default:
+ Log_Warning("TCP", "Unhandled connection state in TCP_Client_Close");
+ break;
+ }
+
+ free(conn);
+
+ LEAVE('-');
+}
+
+/**
+ * \brief Checks if a value is between two others (after taking into account wrapping)
+ */
+int WrapBetween(Uint32 Lower, Uint32 Value, Uint32 Higher, Uint32 MaxValue)
+{
+ if( MaxValue < 0xFFFFFFFF )
+ {
+ Lower %= MaxValue + 1;
+ Value %= MaxValue + 1;
+ Higher %= MaxValue + 1;
+ }
+
+ // Simple Case, no wrap ?
+ // Lower Value Higher
+ // | ... + ... + ... + ... |
+
+ if( Lower < Higher ) {
+ return Lower < Value && Value < Higher;
+ }
+ // Higher has wrapped below lower
+
+ // Value > Lower ?
+ // Higher Lower Value
+ // | ... + ... + ... + ... |
+ if( Value > Lower ) {
+ return 1;
+ }
+
+ // Value < Higher ?
+ // Value Higher Lower
+ // | ... + ... + ... + ... |
+ if( Value < Higher ) {
+ return 1;
+ }
+
+ return 0;
+}