unit EthSockets; {.$DEFINE LOG_UART} uses NMRAnetDefinesShared, EthCommon, dspic_additional_string_library, lib1_enc600_V3_5_b, // lib1_enc600_V3_5_b, definitions_ENC600; const MAX_SEND_DATA_ARRAY = 1024; TIMEOUT_DEFAULT_RETRANSMISSION = 5; TIMEOUT_DEFAULT_TIMEWAIT = 10; RESULT_OPEN_TCP_CONNECTION_OK = 0; RESULT_OPEN_TCP_CONNECTION_ARP_FAILED = 1; RESULT_OPEN_TCP_CONNECTION_NO_SOCKETS = 2; RESULT_OPEN_TCP_CONNECTION_SEND_FAILED = 3; MAX_SOCKET: byte = 4; MAX_DATA_BUFFER_LENGTH = 1024; MAX_TCP_RETRANSMIT_COUNT = 3; // Number of times to try to retransmit the TCP packet waiting for an ACK SOCKET_EXPIRE_TIME_DEFAULT = $000A; // Release the Socket after 10 seconds MAX_TCP_RESEND_ATTEMPTS = 5; // Attempt to resend 5 times before giving up MAX_TCP_RESEND_TIMEOUT = 2; // Resend TCP packet if no response within 2 seconds PROTOCOL_ICMP = 1; PROTOCOL_IGMP = 2; PROTOCOL_TCP = 6; PROTOCOL_UDP = 17; const { TCP_STATE_CLOSED TCP_STATE_LISTEN TCP_STATE_SYN_SENT TCP_STATE_SYN_RECEIVED TCP_STATE_ESTABLISHED TCP_STATE_FIN_WAIT_1 TCP_STATE_FIN_WAIT_2 TCP_STATE_CLOSE_WAIT TCP_STATE_CLOSING TCP_STATE_LAST_ACK TCP_STATE_TIME_WAIT } TCP_STATE_CLOSED = 0; // represents no connection state at all. TCP_STATE_LISTEN = 1; // represents waiting for a connection request from any remote TCP and port. TCP_STATE_SYN_SENT = 2; // represents waiting for a matching connection request after having sent a connection request. TCP_STATE_SYN_RECEIVED = 3; // represents waiting for a confirming connection request acknowledgment after having both received and sent a connection request. TCP_STATE_ESTABLISHED = 4; // represents an open connection, data received can be delivered to the user. The normal state for the data transfer phase of the connection. TCP_STATE_FIN_WAIT_1 = 5; // represents waiting for a connection termination request from the remote TCP, or an acknowledgment of the connection termination request previously sent. TCP_STATE_FIN_WAIT_2 = 6; // represents waiting for a connection termination request from the remote TCP. TCP_STATE_CLOSE_WAIT = 7; // represents waiting for a connection termination request from the local user. TCP_STATE_CLOSING = 8; // represents waiting for a connection termination request acknowledgment from the remote TCP. TCP_STATE_LAST_ACK = 9; // represents waiting for an acknowledgment of the connection termination request previously sent to the remote TCP (which includes an acknowledgment of its connection termination request). TCP_STATE_TIME_WAIT = 10; //-represents waiting for enough time to pass to be sure the remote TCP received the acknowledgment of its connection termination request. TCP_E_OK = 0; TCP_E_CONNECTION_ALREADY_EXISTS = 1; TCP_E_CONNECTION_DOES_NOT_EXIST = 2; TCP_E_CONNECTION_CLOSING = 3; TCP_E_INSUFFICIENT_RESOURCES = 4; SOCKET_STATE_DISCONNECTED = 0; SOCKET_STATE_CONNECT_AS_CLIENT = 1; SOCKET_STATE_CONNECT_AS_SERVER = 2; SOCKET_STATE_CONNECTED = 3; SOCKET_STATE_TEAR_OURSELVES_DOWN = 4; SOCKET_STATE_BEING_TORN_DOWN = 5; SOCKET_STATE_RESET = 6; SOCKET_SUBSTATE_EMPTY = 0; SOCKET_SUBSTATE_CLIENT_CONNECTING_SEND = 1; SOCKET_SUBSTATE_CLIENT_CONNECTING_WAITING_FOR_SYN_ACK = 2; SOCKET_SUBSTATE_CLIENT_CONNECTING_WAITING_FOR_SYN = 3; SOCKET_SUBSTATE_CLIENT_CONNECTING_SENDING_SYN_ACK = 4; SOCKET_SUBSTATE_TEARING_OURSELVES_DOWN_SENDING_FIN = 10; SOCKET_SUBSTATE_TEARING_OURSELVES_DOWN_WAITING_FOR_FIN_ACK = 11; SOCKET_SUBSTATE_TEARING_OURSELVES_DOWN_WAITING_FOR_FIN = 12; SOCKET_SUBSTATE_TEARING_OURSELVES_DOWN_SENDING_FIN_ACK = 13; SOCKET_SUBSTATE_BEING_TORN_DOWN_SENDING_FIN_ACK = 20; SOCKET_SUBSTATE_BEING_TORN_DOWN_WAITING_FOR_ACK = 21; SOCKET_FLAG_RECEIVED_FIN = 0; // Order matters it maps to the TCP Flags SOCKET_FLAG_RECEIVED_SYN = 1; SOCKET_FLAG_RECEIVED_RST = 2; SOCKET_FLAG_RECEIVED_PSH = 3; SOCKET_FLAG_RECEIVED_ACK = 4; SOCKET_FLAG_RECEIVED_URG = 5; SOCKET_FLAG_ALLOCATED = 0; SOCKET_FLAG_CONNECTED = 1; // Bit 1 SOCKET_FLAG_FINISH = 2; // Bit 2, Flagged to finish and disconnect SOCKET_FLAG_IS_SERVER = 3; // If set then the statemachine will trigger Server (Listener) type connections, teardowns else it will be a client // 1 2 3 4 // ----------|----------|----------|----------- // UNA NXT UNA + WND // // 1 - old sequence numbers which have been acknowledged // 2 - sequence numbers of unacknowledged data // 3 - sequence numbers allowed for new data transmission // 4 - future sequence numbers which are not yet allowed type TSendSequenceVariables = record UNA, // send unacknowledged NXT, // send next WND, // send window UP, // send urgent pointer WL1, // segment sequence number used for last window update WL2, // segment acknowledgment number used for last window update ISS: DWord; // initial send sequence number DataArray: array[MAX_SEND_DATA_ARRAY] of Byte; // The WND is the width of the data + end; // 1 2 3 // ----------|----------|---------- // RCV.NXT RCV.NXT // +RCV.WND // 1 - old sequence numbers which have been acknowledged // 2 - sequence numbers allowed for new reception // 3 - future sequence numbers which are not yet allowe TReceiveSequenceVariables = record NXT, // receive next WND, // receive window UP, // receive urgent pointer IRS: DWord; // initial receive sequence number end; // This is the data read out of the packet when it is received. TCurrentSegmentVariables = record SEQ, // segment sequence number ACK: DWord; // segment acknowledgment number LEN, // segment length WND, // segment window UP, // segment urgent pointer PRC: Word; // segment precedence value end; TTransmissionControlBlock = record State: Word; // see TCP_STATE_xxxx constants SendSeq: TSendSequenceVariables; ReceiveSeq: TReceiveSequenceVariables; CurrentSegment: TCurrentSegmentVariables; end; TTCPTransmitter = record Loaded: Boolean; // True if a transmission is ready to occur NextTCPState: Word; // TCP_STATE_xxxx to move to once the transmission is complete TCP_Flags: Byte; // The TCP Flags that need to be sent end; TTCPTimeouts = record UserTimeout, RetransmissionTimeout, TimeWaitTimout: Integer; end; TSocket = record iStateMachine : Word; // See SOCKET_STATE_xxxx constants iSubStateMachine : Word; // See SOCKET_SUBSTATE_xxx constants StateFlags : Word; // See SOCKET_FLAG_xxxx contants TCPReceivedFlags : Word; // See SOCKET_FLAG_RECEIVED contants SourcePort : Word; // Port of the source (us) DestIP : TIPAddress; // Ip of the resource to send the data to DestMAC : TMACAddress; // MAC of the resource to send the data to DestPort : Word; // Port of the resource to send the data to SequenceNumber : DWord; // Tracks the number of Bytes WE have SENT to the Destination (relative to a random start number during SYN) AckReceived : DWord; // The Destinations tracking of the number of Bytes it believes we have sent to it and it has acknowledged receiving SequenceReceived : DWord; // The Destinations tracking of the number of Bytes the Destination has SENT to US (relative to a random start number during SYN) AckNumber : DWord; // Tracks the number of Bytes we have acknowledged with an ACK that the destination has sent us Identification : Word; // ID of the packet to handle fragmentation (if allowed) ExpireTimeCount : Word; // Counter increments every 1s to allow auto destruction of socket after ExpireTimeMax has expired ExpireTimeMax : Word; // Default set to 12 seconds, global set this value with the Set_TCP_TimeOut_Interval function ResendAttempts : Word; // Number of times the Socket has tried to send it payload ResendTimeCount : Word; // counter increments every Nms waiting for a reply (if needed), if the timer times out and the Resend Count is less than MAX_RESEND_TRIES the packet is resetn RAM_ROM : Word; Keep_Alive : Boolean; // [DEFAULT] = True; Keeps the socket alive (until the ExpireTimeMax expires at least) when it is empty. If false then once the data is sent the socket is released. Timeouts : TTCPTimeouts; TCB : TTransmissionControlBlock; Transmitter : TTCPTransmitter; Passive : Boolean; // Is the socket a Listen (passive) or active end; TSocketManager = record TCP_Wait, UDP_Wait : Word; // milliSeconds to wait for replies to TCP or UDP calls Socket : array[MAX_SOCKET] of TSocket; iLastProcessedSocket: Integer; // Last index of the Socket that was processed in the process loop iLastOpenedSocked : Word; // Last Socket index that was opened end; // User functions function TCP_Open_Connection(var DestIP : TIPAddress; DestPort, SourcePort : Word) : Word; function TCP_Close_Connection(var DestIP : TIPAddress; DestPort : Word) : Boolean; function TCP_Find_Connection(var DestIP: TIPAddress; DestPort: Word): Integer; function TCP_Find_Any_Connection(var DestIP: TIPAddress; DestPort: Word): Integer; function TCP_Find_Free_Socket: Integer; procedure TCP_Set_TimeOut_Interval_Global(NewTimeOut : Word); procedure TCP_InitTCB(var TCB: TTransmissionControlBlock); procedure TCP_InitTransmitter(var Transmitter: TTCPTransmitter); procedure TCP_SocketTransmitterLoad(var Socket: TSocket; TCP_Flags: Byte; NextTCPState: Word); function Port_Find(Protocol: Word; Port: Word) : Boolean; function Port_Open_UDP(Port: Word): Boolean; function Port_Open_TCP(Port: Word): Boolean; function Port_Close_UDP(Port: Word): Boolean; function Port_Close_TCP(Port: Word): Boolean; procedure PrintSocket(var Socket: TSocket); // Internally used functions procedure Sockets_Init_Internal; // External functions function ARP_Validate_IP(var ip_arp1 : TIPAddress; var mac_arp : TMACAddress) : Boolean; external; function TCP_Send_Internal(TCP_Flag_T : byte; iSocket: Integer) : Boolean; external; procedure Do_EthernetProc(n : Word); external; procedure DisableInt; external; procedure EnableInt; external; var SocketManager: TSocketManager; TCP_Opened_Ports, UDP_Opened_Ports : array[MAX_SOCKET] of Word; implementation procedure TCP_InitTCB(var TCB: TTransmissionControlBlock); begin TCB.SendSeq.UNA := 0; TCB.SendSeq.NXT := 0; TCB.SendSeq.WND := 0; TCB.SendSeq.UP := 0; TCB.SendSeq.WL1 := 0; TCB.SendSeq.WL2 := 0; TCB.SendSeq.ISS := 0; TCB.ReceiveSeq.NXT := 0; TCB.ReceiveSeq.WND := 0; TCB.ReceiveSeq.UP := 0; TCB.ReceiveSeq.IRS := 0; TCB.CurrentSegment.SEQ := 0; TCB.CurrentSegment.ACK := 0; TCB.CurrentSegment.LEN := 0; TCB.CurrentSegment.WND := 0; TCB.CurrentSegment.UP := 0; TCB.CurrentSegment.PRC := 0; TCB.State := TCP_STATE_CLOSED; end; procedure TCP_InitTransmitter(var Transmitter: TTCPTransmitter); begin Transmitter.Loaded := False; Transmitter.NextTCPState := 0; Transmitter.TCP_Flags := 0; end; procedure InitSocket(iSocket: Integer); begin SocketManager.Socket[iSocket].StateFlags := 0; SocketManager.Socket[iSocket].TCPReceivedFlags := 0; SocketManager.Socket[iSocket].iStateMachine := SOCKET_STATE_DISCONNECTED; SocketManager.Socket[iSocket].iSubStateMachine := SOCKET_SUBSTATE_EMPTY; SocketManager.Socket[iSocket].SourcePort := 0; Mem_Set(@SocketManager.Socket[iSocket].DestIP, 0, 4); Mem_Set(@SocketManager.Socket[iSocket].DestMAC, 0, 6); SocketManager.Socket[iSocket].DestPort := 0; SocketManager.Socket[iSocket].SequenceNumber := 0; SocketManager.Socket[iSocket].SequenceReceived := 0; SocketManager.Socket[iSocket].AckReceived := 0; SocketManager.Socket[iSocket].AckNumber := 0; SocketManager.Socket[iSocket].ExpireTimeCount := 0; // In Seconds SocketManager.Socket[iSocket].ExpireTimeMax := SOCKET_EXPIRE_TIME_DEFAULT; // In Seconds SocketManager.Socket[iSocket].ResendAttempts := 0; SocketManager.Socket[iSocket].ResendTimeCount := 0; SocketManager.Socket[iSocket].RAM_ROM := 0; SocketManager.Socket[iSocket].Keep_Alive := false; SocketManager.Socket[iSocket].Passive := False; SocketManager.Socket[iSocket].Timeouts.UserTimeout := -1; SocketManager.Socket[iSocket].Timeouts.RetransmissionTimeout := TIMEOUT_DEFAULT_RETRANSMISSION; SocketManager.Socket[iSocket].Timeouts.TimeWaitTimout := TIMEOUT_DEFAULT_TIMEWAIT; TCP_InitTransmitter(SocketManager.Socket[iSocket].Transmitter); TCP_InitTCB(SocketManager.Socket[iSocket].TCB); end; procedure PrintSocket(var Socket: TSocket); begin UART1_Write_Text(LF+LF+'....Printing Socket.... '+LF); WordToStr(Socket.iStateMachine, s1); UART1_Write_Text('iStateMachine: ' + s1 + LF); WordToHex(Socket.StateFlags, s1); UART1_Write_Text('StateFlags: 0x' + s1 + LF); WordToHex(Socket.TCPReceivedFlags, s1); UART1_Write_Text('TCPReceivedFlags: 0x' + s1 + LF); WordToStr(Socket.SourcePort, s1); UART1_Write_Text('SourcePort: ' + s1 + LF); Mac2Str(Socket.DestMAC, s1); UART1_Write_Text('DestMac: ' + s1 + LF); ip2Str(Socket.DestIP, s1); UART1_Write_Text('DestIP: ' + s1 + LF); WordToStr(Socket.DestPort, s1); UART1_Write_Text('DestPort: ' + s1 + LF); LongWordToStr(Socket.SequenceNumber, s1); UART1_Write_Text('Seq Number: ' + s1 + LF); LongWordToStr(Socket.AckReceived, s1); UART1_Write_Text('Ack Received: ' + s1 + LF); LongWordToStr(Socket.AckNumber, s1); UART1_Write_Text('Ack Number: ' + s1 + LF); LongWordToStr(Socket.SequenceReceived, s1); UART1_Write_Text('Seq Received: ' + s1 + LF); WordToStr(Socket.Identification, s1); UART1_Write_Text('Identification: ' + s1 + LF); WordToStr(Socket.ExpireTimeCount, s1); UART1_Write_Text('Expire Time Count: ' + s1 + LF); WordToStr(Socket.ExpireTimeMax, s1); UART1_Write_Text('Expire Time Max: ' + s1 + LF); WordToStr(Socket.ResendAttempts, s1); UART1_Write_Text('Resend Attempts: ' + s1 + LF); WordToStr(Socket.ResendTimeCount, s1); UART1_Write_Text('Resend Time Count: ' + s1 + LF); WordToStr(Socket.RAM_ROM, s1); UART1_Write_Text('Ram or Rom: ' + s1 + LF); if Socket.Keep_Alive then UART1_Write_Text('Keep Alive : True' + s1 + LF) else UART1_Write_Text('Keep Alive : False' + s1 + LF); end; procedure Sockets_Init_Internal; var iSocket: Integer; begin Mem_Set(@TCP_Opened_Ports, 0, MAX_SOCKET*2); // clear ports Word format Mem_Set(@UDP_Opened_Ports, 0, MAX_SOCKET*2); // clear ports Word format SocketManager.iLastProcessedSocket := 0; iSocket := 0; while iSocket < MAX_SOCKET do begin InitSocket(iSocket); Inc(iSocket) end end; procedure TCP_SocketTransmitterLoad(var Socket: TSocket; TCP_Flags: Byte; NextTCPState: Word); begin Socket.Transmitter.Loaded := True; Socket.Transmitter.NextTCPState := NextTCPState; Socket.Transmitter.TCP_Flags := TCP_Flags end; //****************************************************************************** // Name : TCP_Open_Connection // Purpose : Start the opening process. The caller must poll the Connection to find // when it actually becomes open //****************************************************************************** function TCP_Open_Connection(var DestIP : TIPAddress; DestPort, SourcePort : Word) : Word; var iSocket : Integer; DestMAC: TMACAddress; begin {$IFDEF LOG_UART}UART1_Write_Text('Opening TCP Connection'+LF);{$ENDIF} Result := RESULT_OPEN_TCP_CONNECTION_OK; if ARP_Validate_IP(DestIP, DestMAC) then // Does the node exist on the network? begin iSocket := TCP_Find_Connection(DestIP, DestPort); // Look for an open socket if iSocket < 0 then // Can't find one, so begin the open process begin iSocket := TCP_Find_Free_Socket; if iSocket >= 0 then begin InitSocket(iSocket); SocketManager.Socket[iSocket].StateFlags.SOCKET_FLAG_ALLOCATED := 1; SocketManager.Socket[iSocket].iStateMachine := SOCKET_STATE_CONNECT_AS_CLIENT; SocketManager.Socket[iSocket].iSubStateMachine := SOCKET_SUBSTATE_EMPTY; SocketManager.Socket[iSocket].SequenceNumber := $AAAA; // This should be Random SocketManager.Socket[iSocket].DestPort := DestPort; SocketManager.Socket[iSocket].SourcePort := SourcePort; Mem_Cpy(@SocketManager.Socket[iSocket].DestIP, @DestIP, 4); Mem_Cpy(@SocketManager.Socket[iSocket].DestMAC, @DestMAC, 6); SocketManager.Socket[iSocket].Keep_Alive := True; end else Result := RESULT_OPEN_TCP_CONNECTION_NO_SOCKETS; // Out of sockets end end else Result := RESULT_OPEN_TCP_CONNECTION_ARP_FAILED; // ARP Failed end; //****************************************************************************** // Name : TCP_Close_Connection // Purpose : Signals the TCP Connection and Socket to finish and close, note the // actual closing happens when it can //****************************************************************************** function TCP_Close_Connection(var DestIP : TIPAddress; DestPort : Word) : Boolean; var iSocket : Integer; begin iSocket := TCP_Find_Any_Connection(DestIP, DestPort); if iSocket >= 0 then begin SocketManager.Socket[iSocket].StateFlags.SOCKET_FLAG_FINISH := 1; Result := True end end; //****************************************************************************** // Name : TCP_Find_Free_Socket // Purpose : Searches for a free socket, returns the index into the Socket Array // or -1 if none can be found //****************************************************************************** function TCP_Find_Free_Socket: Integer; var iSocket: Integer; begin Result := -1; iSocket := 0; while iSocket < MAX_SOCKET do // search for free socket begin if SocketManager.Socket[iSocket].StateFlags.SOCKET_FLAG_ALLOCATED = 0 then begin Result := iSocket; Break; end; Inc(iSocket); end; end; //****************************************************************************** // Name : TCP_Find_Connection // Purpose : Searches for a connected socket (does NOT include sockets in the // process of a SYN volley //****************************************************************************** function TCP_Find_Connection(var DestIP: TIPAddress; DestPort: Word): Integer; var iSocket: Integer; begin Result := -1; iSocket := 0; while iSocket < MAX_SOCKET do // search for opened socket begin if (SocketManager.Socket[iSocket].StateFlags.SOCKET_FLAG_CONNECTED = 1) then // is the socket connected? if (SocketManager.Socket[iSocket].DestPort = DestPort) then // do the destination ports match if (Mem_Cmp(@DestIP, @SocketManager.Socket[iSocket].DestIP, 4) = 0) then // does the destination IP addresses match? begin Result := iSocket; Break; // if connected end; Inc(iSocket); end; end; //****************************************************************************** // Name : TCP_Find_Any_Connection // Purpose : Searches for a socket that is in any state of connection and allocated //****************************************************************************** function TCP_Find_Any_Connection(var DestIP: TIPAddress; DestPort: Word): Integer; var iSocket: Integer; begin Result := -1; iSocket := 0; while iSocket < MAX_SOCKET do // search for opened socket begin if (SocketManager.Socket[iSocket].StateFlags.SOCKET_FLAG_ALLOCATED = 1) then if (SocketManager.Socket[iSocket].DestPort = DestPort) then if (Mem_Cmp(@DestIP, @SocketManager.Socket[iSocket].DestIP, 4) = 0) then begin Result := iSocket; Break; // if connected end; Inc(iSocket); end; end; //****************************************************************************** // Name : TCP_Set_TimeOut_Interval_Global // Purpose : Sets the timeout for all connections //****************************************************************************** procedure TCP_Set_TimeOut_Interval_Global(NewTimeout : Word); var iSocket: Integer; begin iSocket := 0; while iSocket < MAX_SOCKET do begin SocketManager.Socket[iSocket].ExpireTimeMax := NewTimeout; Inc(iSocket) end; end; //****************************************************************************** // Name : Port_Find // Purpose : Searches for a Port that is allocated to a protocol (TCP or UDP) //****************************************************************************** function Port_Find(Protocol: Word; Port: Word) : Boolean; var iPort : Integer; PortPtr : ^Word; begin Result := false; if Port > 0 then begin if Protocol = PROTOCOL_TCP then PortPtr := @TCP_Opened_Ports else PortPtr := @UDP_Opened_Ports; iPort := 0; while iPort < MAX_SOCKET do begin if PortPtr^ = Port then begin Result := true; Exit; end; PortPtr := PortPtr + 1; Inc(iPort); end end end; //****************************************************************************** // Name : Port_Close // Purpose : Closes a TCP or UDP Port //****************************************************************************** function Port_Close(Protocol : byte; Port : Word) : Boolean; var iPort : Integer; PortPtr : ^Word; begin Result := false; if Port > 0 then begin if Protocol = PROTOCOL_TCP then PortPtr := @TCP_Opened_Ports else PortPtr := @UDP_Opened_Ports; iPort := 0; while iPort < MAX_SOCKET do begin if PortPtr^ = Port then begin PortPtr^ := 0; Result := true; Exit; end; PortPtr := PortPtr + 1; Inc(iPort); end; end end; //****************************************************************************** // Name : Port_Open // Purpose : Opens a TCP or UDP Port //****************************************************************************** function Port_Open(Protocol : Word; Port : Word) : Boolean; var iPort: Integer; PortPtr : ^Word; begin Result := False; if Port_Find(Protocol, Port) = 0 then begin if Protocol = PROTOCOL_TCP then PortPtr := @TCP_Opened_Ports else PortPtr := @UDP_Opened_Ports; iPort := 0; while iPort < MAX_SOCKET do // Look for the first empty (Port = 0) slot begin if PortPtr^ = 0 then begin PortPtr^ := Port; Result := true; Exit; end; PortPtr := PortPtr + 1; Inc(iPort); end; end else Result := True end; function Port_Open_UDP(Port : Word): Boolean; begin Result := Port_Open(PROTOCOL_UDP, Port); end; function Port_Open_TCP(Port : Word): Boolean; begin Result := Port_Open(PROTOCOL_TCP, Port); end; function Port_Close_UDP(Port : Word): Boolean; begin Result := Port_Close(PROTOCOL_UDP, Port); end; function Port_Close_TCP(Port : Word): Boolean; begin Result := Port_Close(PROTOCOL_TCP, Port); end; end.