;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;                                                                 ;;
;; Copyright (C) KolibriOS team 2004-2009. All rights reserved.    ;;
;; Distributed under terms of the GNU General Public License       ;;
;;                                                                 ;;
;;  TCP.INC                                                        ;;
;;                                                                 ;;
;;  Part of the tcp/ip network stack for KolibriOS                 ;;
;;                                                                 ;;
;;    Written by hidnplayr@kolibrios.org                           ;;
;;    Inspired by the TCP code of Mike Hibbit for MenuetOS         ;;
;;                                                                 ;;
;;          GNU GENERAL PUBLIC LICENSE                             ;;
;;             Version 2, June 1991                                ;;
;;                                                                 ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


$Revision$

TCP_RETRIES		equ 5		; Number of times to resend a Packet
TCP_PACKET_TTL		equ 50		; resend if not replied to in 1/100 s
TCP_SOCKET_TTL		equ 10		; # of secs to wait before closing socket
TCP_QUEUE_SIZE		equ 16

TCP_MAX_ACKS		equ 16


struct	TCP_Packet
	.SourcePort		dw ?
	.DestinationPort	dw ?
	.SequenceNumber 	dd ?
	.AckNumber		dd ?
	.DataOffset		db ?	; DataOffset[0-3 bits] and Reserved[4-7]
	.Flags			db ?	; Reserved[0-1 bits]|URG|ACK|PSH|RST|SYN|FIN
	.Window 		dw ?
	.Checksum		dw ?
	.UrgentPointer		dw ?
;        .Options                rb 3
;        .Padding                db ?
	.Data:
ends

struct	tcp_in_queue_entry
	.data_ptr	dd ?
	.data_size	dd ?
	.offset 	dd ?	; TODO: replace this in code by absolute address isntead of relative offset
	.size:
ends

struct	tcp_out_queue_entry
	.data_ptr	dd ?
	.data_size	dd ?
	.ttl		dd ?
	.retries	dd ?
	.owner		dd ?
	.sendproc	dd ?
	.seq_num	dd ?
	.socket 	dd ?
	.size:
ends

align 4
uglobal
	TCP_PACKETS_TX		rd  MAX_IP
	TCP_PACKETS_RX		rd  MAX_IP

	TCP_IN_QUEUE		rd  (tcp_in_queue_entry.size*TCP_QUEUE_SIZE+queue.data)/4
	TCP_OUT_QUEUE		dd  ?, ?
				rd  (tcp_out_queue_entry.size*TCP_QUEUE_SIZE)/4

	TCP_ACKS		dd  ?
	TCP_ACK_LIST		rd  3*TCP_MAX_ACKS
endg

align 4
iglobal
TCPstateHandler:

  dd  stateTCB_LISTEN
  dd  stateTCB_SYN_SENT
  dd  stateTCB_SYN_RECEIVED
  dd  stateTCB_ESTABLISHED
  dd  stateTCB_FIN_WAIT_1
  dd  stateTCB_FIN_WAIT_2
  dd  stateTCB_CLOSE_WAIT
  dd  stateTCB_CLOSING
  dd  stateTCB_LAST_ACK
  dd  stateTCB_TIME_WAIT
  dd  stateTCB_CLOSED

endg


;-----------------------------------------------------------------
;
; TCP_init
;
;  This function resets all TCP variables
;
;  IN:  /
;  OUT: /
;
;-----------------------------------------------------------------
align 4
TCP_init:

	xor	eax, eax
	mov	edi, TCP_PACKETS_TX
	mov	ecx, 2*MAX_IP
	rep	stosd

	init_queue TCP_IN_QUEUE

; tcp_out_queue is a special type of queue:
; The first dword is a counter of total packets queued.
; The remaining bytes are socket 'slots' wich use tcp_out_queue_entry data structure.
; An empty slot is know by the fact that tcp_out_queue_entry.data_ptr (first dword of the slot) is set to 0
; There are TCP_OUT_QUEUE_SIZE number of slots

	xor	eax, eax
	mov	esi, TCP_OUT_QUEUE
	mov	ecx, TCP_QUEUE_SIZE*tcp_out_queue_entry/4+2+2+3*TCP_MAX_ACKS
	rep	stosd

	ret


;-----------------------------------------------------------------
;
;  TCP_decrease_socket_ttls
;
;  IN:  /
;  OUT: /
;
;-----------------------------------------------------------------
align 4
TCP_decrease_socket_ttls:
; scan through all the sockets, decrementing active timers

	mov	ebx, net_sockets

	cmp	[ebx + SOCKET_head.NextPtr], 0
	je	.exit

  .next_socket:
	mov	ebx, [ebx + SOCKET_head.NextPtr]
	or	ebx, ebx
	jz	.exit

	cmp	[ebx + SOCKET_head.Type], IP_PROTO_TCP
	jne	.next_socket

;        DEBUGF  1, "K :   %x-%x: %x-%x-%x-%u\n", [ebx + SOCKET.PID]:2, [ebx + SOCKET.Number]:2, [ebx + SOCKET.LocalPort]:4, [ebx + SOCKET.RemoteIP], [ebx + SOCKET.RemotePort]:4, [ebx + SOCKET.state]

	cmp	[ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.timer], 0
	jne	.decrement_tcb
	cmp	[ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.wndsizeTimer], 0
	jne	.decrement_wnd
	jmp	.next_socket

  .decrement_tcb:
; decrement it, delete socket if TCB timer = 0 & socket in timewait state
	dec	[ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.timer]
	jnz	.next_socket

	cmp	[ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.state], TCB_TIMED_WAIT
	jne	.next_socket

	push	[ebx + SOCKET_head.PrevPtr]
	stdcall net_socket_free, ebx
	pop	ebx
	jmp	.next_socket

  .decrement_wnd:
	dec	[ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.wndsizeTimer]
	jmp	.next_socket

  .exit:
	ret



;-----------------------------------------------------------------
;
; TCP_send_queued:
;
;  Decreases 'ttl' of tcp packets queued.
;  if 'ttl' reaches 0, resend the packet and decrease 'retries'
;  if 'retries' reaches zero, remove the queued packet
;
;  IN:  /
;  OUT: /
;
;-----------------------------------------------------------------
align 4
TCP_send_queued:

	cmp	[TCP_OUT_QUEUE], 0
	je	.exit

	mov	ebx, TCP_OUT_QUEUE+4
	call	wait_mutex

	mov	eax, TCP_QUEUE_SIZE
	mov	ecx, [TCP_OUT_QUEUE]
	mov	esi, TCP_OUT_QUEUE+8

  .loop:
	cmp	[esi + tcp_out_queue_entry.data_ptr], 0
	jnz	.found_one
	add	esi, tcp_out_queue_entry.size
	loop	.loop
  .exit:
	mov	[TCP_OUT_QUEUE+4], 0
	ret

  .found_one:
	dec	[esi + tcp_out_queue_entry.ttl]
	jz	.send_it
	cmp	[esi + tcp_out_queue_entry.data_ptr], -1
	jz	.is_ack
  .find_next:
	add	esi, tcp_out_queue_entry.size
	dec	eax
	jz	.exit
	test	ecx, ecx
	jnz	.loop
	mov	[TCP_OUT_QUEUE+4], 0
	ret

  .send_it:
	pusha
	mov	ebx, [esi + tcp_out_queue_entry.owner]
	pushd	[esi + tcp_out_queue_entry.data_size]
	pushd	[esi + tcp_out_queue_entry.data_ptr]
	DEBUGF 1,"Now sending TCP packet %x, size: %u, owner: %x, sendproc %x\n", [esp], [esp+4], ebx, [esi + tcp_out_queue_entry.sendproc]
	inc	[TCP_PACKETS_TX]
	call	[esi + tcp_out_queue_entry.sendproc]
	add	esp, 8
	popa

	dec	[esi + tcp_out_queue_entry.retries]
	jz	.remove_it

	mov	[esi + tcp_out_queue_entry.ttl], TCP_PACKET_TTL
	jmp	.find_next

  .remove_it:
	push	[esi + tcp_out_queue_entry.data_ptr]
	mov	[esi + tcp_out_queue_entry.data_ptr], 0
	call	kernel_free
	dec	[TCP_OUT_QUEUE]
	jmp	.find_next

  .is_ack:
	pusha
	mov	eax, [esi + tcp_out_queue_entry.socket]
	mov	ebx, [esi + tcp_out_queue_entry.owner]
	mov	ecx, [esi + tcp_out_queue_entry.size]
	call	TCP_send_ack
	popa
	mov	[esi + tcp_out_queue_entry.data_ptr], 0
	dec	[TCP_OUT_QUEUE]
	jmp	.find_next



;-----------------------------------------------------------------
;
; TCP_handler:
;
;  Called by IPv4_handler,
;  this procedure will inject the tcp data diagrams in the application sockets.
;
;  IN:  Pointer to buffer in [esp]
;       size of buffer in [esp+4]
;       pointer to device struct in ebx
;       TCP Packet size in ecx
;       pointer to TCP Packet in edx
;       SourceAddres (IPv4) in esi
;  OUT: /
;
;-----------------------------------------------------------------
align 4
TCP_handler :

       DEBUGF 1,"TCP_Handler\n"

; TODO: validate checksum

; Find a matching socket for received packet, all following expressions must be valid:
;
; IP Packet TCP Destination Port = local Port
; (IP Packet SA = Remote IP)  OR  (Remote IP = 0)
; (IP Packet TCP Source Port = remote Port)  OR (remote Port = 0)

	mov	ebx, net_sockets

  .socket_loop:
	mov	ebx, [ebx + SOCKET_head.NextPtr]
	or	ebx, ebx
	jz	.dump

	mov	ax, [edx + TCP_Packet.DestinationPort]
	cmp	[ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.LocalPort], ax
	jne	.socket_loop

	mov	eax, [ebx + SOCKET_head.end + IPv4_SOCKET.RemoteIP]
	cmp	eax, esi
	je	@f
	test	eax, eax
	jne	.socket_loop
       @@:

	mov	ax, [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.RemotePort]
	cmp	[edx + TCP_Packet.SourcePort] , ax
	je	.found_socket
	test	ax, ax
	jnz	.socket_loop
  .found_socket:
       DEBUGF 1,"Found valid socket for packet\n"

	inc	[TCP_PACKETS_RX]

	add	ebx, SOCKET_head.lock
	call	wait_mutex
	sub	ebx, SOCKET_head.lock

;-------------------------------
; ebx is pointer to socket
; ecx is size of tcp packet
; edx is pointer to tcp packet

; calculate header length
	movzx	eax, [edx + TCP_Packet.DataOffset]
	and	eax, 11110000b
	shr	eax, 2
       DEBUGF 1,"TCP header size: %u\n", eax
	sub	ecx, eax

;-------------------------------
; ecx is size of tcp data

; as a Packet has been received, update the TCB timer

; If the received Packet has an ACK bit set, remove any Packets in the resend queue that this received Packet acknowledges
	test	[edx + TCP_Packet.Flags], TH_ACK
	jz	.no_ack 				 ; No ACK, so no data yet

; Calculate ACK number, in intel byte order
	mov	edi, [edx + TCP_Packet.AckNumber]
	bswap	edi
	mov	[ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.last_ack_number], edi
	DEBUGF 1,"Setting last_ack_number to %u\n", edi

; Dequeue all acknowledged packets
	cmp	[TCP_OUT_QUEUE], 0		; first, check if any packets are queued at all
	je	.no_ack

	push	ebx
	mov	ebx, TCP_OUT_QUEUE+4
	call	wait_mutex
	pop	ebx

	push	ecx
	DEBUGF 1,"Removing all queued packets with smaller ACK\n"
	mov	ecx, TCP_QUEUE_SIZE
	mov	esi, TCP_OUT_QUEUE+8
  .loop:
	cmp	[esi + tcp_out_queue_entry.data_ptr], 0
	je	.maybe_next

	cmp	[esi + tcp_out_queue_entry.socket], ebx
	jne	.maybe_next

	cmp	[esi + tcp_out_queue_entry.seq_num], edi
	jg	.maybe_next

	DEBUGF 1,"Removing a queued packet\n"

	push	[esi + tcp_out_queue_entry.data_ptr]
	mov	[esi + tcp_out_queue_entry.data_ptr], 0
	dec	[TCP_OUT_QUEUE]
	call	kernel_free

  .maybe_next:
	add	esi, tcp_out_queue_entry.size
	loop	.loop

	mov	[TCP_OUT_QUEUE+4], 0
	pop	ecx

; Now call the correct handler, depending on the socket state
  .no_ack:
	mov	eax, [ebx +  SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.state]

	cmp	eax, TCB_LISTEN
	jb	.dump
	cmp	eax, TCB_CLOSED
	ja	.dump

	call	dword [TCPstateHandler+eax*4-4]

  .dump:
	DEBUGF 1,"Dumping TCP packet\n"
	call	kernel_free
	add	esp, 4 ; pop (balance stack)

	ret



;-----------------------------------------------------------------
;
; TCP_send  (Assumes socket mutex set)
;
; IN: eax = socket pointer
;      bl = flags
;      ecx = number of bytes to send, may be set to 0  (single ACK)
;      esi = pointer to data
;
;-----------------------------------------------------------------
align 4
TCP_send:

	DEBUGF 1,"Creating TCP packet, socket: %x, flags: %x\n",eax, bl

	mov	di , IP_PROTO_TCP
	add	ecx, TCP_Packet.Data

	push	ecx bx eax esi
; Create an IPv4 Packet of the correct size
	mov	ebx, [eax + SOCKET_head.end + IPv4_SOCKET.LocalIP]
	mov	eax, [eax + SOCKET_head.end + IPv4_SOCKET.RemoteIP]

	call	IPv4_create_packet
	cmp	edi, -1
	je	.fail

; If there is any data, copy it first
	pop	esi
	push	edi
	add	edi, TCP_Packet.Data
	sub	ecx, TCP_Packet.Data

	shr	ecx, 1
	jnc	.nb
	movsb
.nb:	shr	ecx, 1
	jnc	.nw
	movsw
.nw:	test	ecx, ecx
	jz	.nd
	rep	movsd
.nd:
	pop	edi

; Fill in the TCP header
	pop	esi

; fill in tcp sequence number
	push	[esi + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.SND_NXT]
	pop	[edi + TCP_Packet.SequenceNumber]

; Fill in local and remote ports
	push	dword [esi + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.LocalPort]
	pop	dword [edi + TCP_Packet.SourcePort]

; Acknumber
	push	[esi + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.RCV_NXT]
	pop	[edi + TCP_Packet.AckNumber]

; Fill  in other tcp options
	pop	cx
	mov	[edi + TCP_Packet.Flags], cl
	mov	[edi + TCP_Packet.Window], 0x0005	   ; 1280 bytes
	mov	[edi + TCP_Packet.UrgentPointer], 0
	mov	[edi + TCP_Packet.DataOffset], 0x50
	mov	[edi + TCP_Packet.Checksum], 0

; Get size of total packet back in ecx
	pop	ecx
; Push pointer to and size of total packet (needed for send procedure)
	push	edx eax
; push socket number (for TCP_add_to_queue)
	push	esi

; Now, calculate the checksum
	xchg	cl, ch
	pushw	cx
	xchg	cl, ch
	pushw	IP_PROTO_TCP shl 8
	pushd	[edi-4] ; destination address           ; TODO: fix this, IPv4 packet could have options..
	pushd	[edi-8] ; source address

	xor	edx, edx
	mov	esi, edi
	call	checksum_1
	mov	ecx, 12
	mov	esi, esp
	call	checksum_1
; and store it in TCP header
	call	checksum_2
	mov	[edi + TCP_Packet.Checksum], dx
	add	esp, 10 				   ; remove the pseudoheader from stack

	DEBUGF 1,"Sending TCP Packet to device %x\n", ebx
	mov	edx, [edi + TCP_Packet.SequenceNumber]
	bswap	edx
	mov	esi, [ebx + ETH_DEVICE.transmit]

	pop	cx					   ; get the length from packet, back from pseudoheader
	pop	edi

	cmp	cx, TCP_Packet.Data shl 8		   ; if the packet has no data
	je	.only_one				   ; send it only once

	and	ecx, 0x0000ffff
	xchg	cl, ch
	sub	cx, TCP_Packet.Data

	add_INET (edi + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.SND_NXT)     ; todo: this should only happen when packet was queued successful
	mov	ecx, TCP_RETRIES

	jmp	.go_for_it

  .only_one:
	mov	ecx, 1
  .go_for_it:

	mov	[edi + SOCKET_head.lock], 0
	jmp	TCP_queue				; At last send the packet!

  .fail:
	add	esp, 2+4
	or	eax, -1
	ret


;-----------------------------------------------------------------
;
;  Queue a TCP packet for sending
;
;  IN:  [esp] pointer to buffer
;       [esp + 4] size of buffer
;       ebx = driver struct
;       edx = sequence number of this packet in intel byte order
;       esi = sender proc
;       edi = socket number

;  OUT: /
;
;-----------------------------------------------------------------
align 4
TCP_queue:

	DEBUGF 1,"Adding packet to TCP queue, buffer: %x, size: %u, driver: %x, acknum: %u\n", [esp], [esp+4], ebx, edx

	cmp	[TCP_OUT_QUEUE], TCP_QUEUE_SIZE
	jge	.full

	push	ebx
	mov	ebx, TCP_OUT_QUEUE+4
	call	wait_mutex
	pop	ebx

	mov	ecx, TCP_QUEUE_SIZE
	mov	eax, TCP_OUT_QUEUE+8
  .loop:
	cmp	[eax + tcp_out_queue_entry.data_ptr], 0
	je	.found_it
	add	eax, tcp_out_queue_entry.size
	loop	.loop

	add	esp, 4
  .full:			; silently discard the packet
	DEBUGF 1,"TCP queue is full!\n"
	call	kernel_free
	add	esp, 4

	ret

  .found_it:			; eax points to empty queue entry

	mov	[eax + tcp_out_queue_entry.retries], TCP_RETRIES
	pop	[eax + tcp_out_queue_entry.data_ptr]
	pop	[eax + tcp_out_queue_entry.data_size]
	mov	[eax + tcp_out_queue_entry.ttl], 1			; send immediately
	mov	[eax + tcp_out_queue_entry.owner], ebx
	mov	[eax + tcp_out_queue_entry.sendproc], esi
	mov	[eax + tcp_out_queue_entry.seq_num], edx
	mov	[eax + tcp_out_queue_entry.socket], edi

	inc	[TCP_OUT_QUEUE]

	sub	eax, TCP_OUT_QUEUE+8
	shr	eax, 5
	DEBUGF 1,"Added to queue in pos %u, total queued packets: %u\n", eax, [TCP_OUT_QUEUE+8]

	mov	[TCP_OUT_QUEUE+4], 0

	ret


;-----------------------------------------------------------------
;
;  IN:  ebx = socket
;       ecx = ack number
;
;  OUT: /
;
;-----------------------------------------------------------------
align 4
TCP_queue_ack:

	DEBUGF 1,"Adding ACK to TCP queue, socket: %x, acknum: %u\n", ebx, ecx

	cmp	[TCP_OUT_QUEUE], TCP_QUEUE_SIZE
	jge	.full

	push	ebx ecx
	mov	ebx, TCP_OUT_QUEUE+4
	call	wait_mutex

	mov	ecx, TCP_QUEUE_SIZE
	mov	eax, TCP_OUT_QUEUE+8
  .loop:
	cmp	[eax + tcp_out_queue_entry.data_ptr], 0
	je	.found_it
	add	eax, tcp_out_queue_entry.size
	loop	.loop

	add	esp, 8
  .full:			; silently discard the packet
	DEBUGF 1,"TCP queue is full!\n"
	ret

  .found_it:			; eax points to empty queue entry

	pop	[eax + tcp_out_queue_entry.data_size]			; ACK number
	mov	[eax + tcp_out_queue_entry.data_ptr], -1		; ACK packet
	pop	[eax + tcp_out_queue_entry.socket]
	mov	[eax + tcp_out_queue_entry.retries], 1
	mov	[eax + tcp_out_queue_entry.ttl], 20			; 200 ms

	inc	[TCP_OUT_QUEUE]

	sub	eax, TCP_OUT_QUEUE+8
	shr	eax, 5
	DEBUGF 1,"Added to queue in pos %u, total queued packets: %u\n", eax, [TCP_OUT_QUEUE+8]

	mov	[TCP_OUT_QUEUE+4], 0

	ret


; IN: eax = socket pointer
;     ebx = device structure
;     ecx = ack number

align 4
TCP_send_ack:

	DEBUGF 1,"Creating TCP ACK packet, socket: %x, acknum: %x\n", eax, ecx

	push	ecx eax

	mov	di , IP_PROTO_TCP
	mov	ecx, TCP_Packet.Data
; Create an IPv4 Packet of the correct size
	mov	ebx, [eax + SOCKET_head.end + IPv4_SOCKET.LocalIP]
	mov	eax, [eax + SOCKET_head.end + IPv4_SOCKET.RemoteIP]

	call	IPv4_create_packet
	cmp	edi, -1
	je	.fail

	pop	ecx

; fill in tcp sequence number
	push	[ecx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.SND_NXT]
	pop	[edi + TCP_Packet.SequenceNumber]

; Fill in local and remote ports
	push	dword [ecx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.LocalPort]
	pop	dword [edi + TCP_Packet.SourcePort]

; Acknumber
	pop	[edi + TCP_Packet.AckNumber]

; Fill  in other tcp options
	mov	[edi + TCP_Packet.Flags], TH_ACK
	mov	[edi + TCP_Packet.Window], 0x0005	   ; 1280 bytes
	mov	[edi + TCP_Packet.UrgentPointer], 0
	mov	[edi + TCP_Packet.DataOffset], 0x50
	mov	[edi + TCP_Packet.Checksum], 0

; Push pointer to and size of total packet (needed for send procedure)
	push	edx eax esi

; Now, calculate the checksum
	pushw	TCP_Packet.Data shl 8
	pushw	IP_PROTO_TCP shl 8
	pushd	[edi-4] ; destination address           ; TODO: fix this, IPv4 packet could have options..
	pushd	[edi-8] ; source address

	xor	edx, edx
	mov	ecx, 12
	mov	esi, esp
	call	checksum_1
	call	checksum_2
	mov	[edi + TCP_Packet.Checksum], dx
	add	esp, 12 				; remove the pseudoheader from stack

	pop	eax
	call	eax
	call	kernel_free
	add	esp, 4 ; pop (balance stack)
	ret

  .fail:
	add	esp, 8
	ret




;-----------------------------------------------------------------
;
; Remove all queued TCP packets for a specified socket
;
; IN: eax = socket number
; OUT: /
;
; destoys esi and ecx
;
;-----------------------------------------------------------------

align 4
TCP_remove_socket:

	cmp	[TCP_OUT_QUEUE], 0
	je	.skip

	mov	ebx, TCP_OUT_QUEUE+4
	call	wait_mutex

	mov	eax, TCP_QUEUE_SIZE
	mov	ecx, [TCP_OUT_QUEUE]
	mov	esi, TCP_OUT_QUEUE+8

  .loop:
	cmp	[esi + tcp_out_queue_entry.data_ptr], 0
	jz	.maybenext
	cmp	[esi + tcp_out_queue_entry.socket], eax
	jnz	.maybenext

	push	[esi + tcp_out_queue_entry.data_ptr]
	mov	[esi + tcp_out_queue_entry.data_ptr], 0
	dec	[TCP_OUT_QUEUE]
	call	kernel_free

  .maybenext:
	add	esi, tcp_out_queue_entry.size
	loop	.loop

	mov	[TCP_OUT_QUEUE+4], 0
  .skip:
	ret





;---------- TCB state handlers start here




align 4
stateTCB_LISTEN:

	DEBUGF	1,"TCBStateHandler: Listen\n"

	test	[edx + TCP_Packet.Flags], TH_SYN	; SYN packet? => send syn+ack, open new socket and set connection to established
	jz	.exit
; Exit if backlog queue is full
	mov	ax, [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.backlog_cur]
	cmp	ax, [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.backlog]
	jae	.exit
; Allocate new socket
	push	esi edi
	call	net_socket_alloc
	test	eax, eax
	jz	.fail
; Copy structure from current socket to new, including lock
	lea	esi, [ebx + SOCKET_head.PID]		; yes, PID must also be copied
	lea	edi, [eax + SOCKET_head.PID]
	mov	ecx, ((SOCKET_head.end - SOCKET_head.PID) + IPv4_SOCKET.end + TCP_SOCKET.end + 3)/4
	rep	movsd
	pop	edi esi
; Push pointer to new socket to queue
	movzx	ecx, [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.backlog_cur]
	inc	[ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.backlog_cur]
	mov	[ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.end + ecx*4], eax

	mov	[eax + SOCKET_head.end + IPv4_SOCKET.RemoteIP], esi ; IP source address
	mov	cx, [edx + TCP_Packet.SourcePort]
	mov	[eax + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.RemotePort], cx
	mov	ecx, [edx + TCP_Packet.SequenceNumber]
	mov	[eax + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.IRS], ecx
	mov	[eax + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.RCV_NXT], ecx
	lea	esi, [eax + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.RCV_NXT]
	inc_INET esi ; RCV.NXT
	mov	ecx, [eax + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.ISS]
	mov	[eax + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.SND_NXT], ecx

	mov	[ebx + SOCKET_head.lock], 0

	push	eax
; Now construct the response
	mov	bl, TH_SYN + TH_ACK
	xor	ecx, ecx
	call	TCP_send
	pop	eax

	mov	[eax + SOCKET_head.lock], 0
	mov	[eax +	SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.state], TCB_SYN_RECEIVED
	call	notify_network_event
	ret

  .exit:
	mov	[ebx + SOCKET_head.lock], 0
	ret

  .fail:
	add	esp, 8
	mov	[ebx + SOCKET_head.lock], 0
	ret


align 4
stateTCB_SYN_SENT:

	DEBUGF	1,"TCBStateHandler: Syn_Sent\n"

	; We are awaiting an ACK to our SYN, with a SYM
	; Look at control flags - expecting an ACK

	mov	al, [edx + TCP_Packet.Flags]

	test	al, TH_RST
	jnz	.reset			; jump if RST bit set

	push	[edx + TCP_Packet.SequenceNumber]				     ;;
	pop	[ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.RCV_NXT]	     ;;
	inc_INET (ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.RCV_NXT)      ;;


	push	[edx + TCP_Packet.AckNumber]					    ;;;;;;
	pop	[ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.SND_NXT]	    ;;;;;;

	and	al, TH_SYN + TH_ACK
	jz	.exit			; jump if none of the following is set: RST, SYN, ACK

	test	al, TH_ACK
	jz     .onlysyn 		; jump if only SYN bit is set

	; If we arrived here, SYN and ACK are set

	mov	[ebx +	SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.state], TCB_ESTABLISHED
	pushw	TH_ACK

  .send:	; Send an ACK
	mov	eax, ebx
	pop	bx
	push	eax
	xor	ecx, ecx
	call	TCP_send
	pop	ebx

  .exit:
	mov	[ebx + SOCKET_head.lock], 0
	ret

  .reset:
	; TODO: ....

	; remove all queued TCP packets for this connection !

	mov	[ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.state], TCB_CLOSED
	mov	[ebx + SOCKET_head.lock], 0
	ret

  .onlysyn:
	mov	[ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.state], TCB_SYN_RECEIVED
	pushw	TH_SYN + TH_ACK
	jmp	.send



align 4
stateTCB_SYN_RECEIVED:

	DEBUGF	1,"TCBStateHandler: Syn_received\n"

	test	[edx + TCP_Packet.Flags], TH_RST	; reset connection? => LISTEN
	jz	.check_ack

	push	[ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.OrigRemotePort]
	pop	[ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.RemotePort]
	push	[ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.OrigRemoteIP]
	pop	[ebx + SOCKET_head.end + IPv4_SOCKET.RemoteIP]

	mov	[ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.state], TCB_LISTEN
	jmp	.exit

  .check_ack:
	test	[edx + TCP_Packet.Flags], TH_ACK	; ACK? => connection established!
	jz	.exit

	mov	[ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.state], TCB_ESTABLISHED
	mov	eax, ebx
	call	notify_network_event

  .exit:
	mov	[ebx + SOCKET_head.lock], 0
	ret


if 0


align 4
stateTCB_ESTABLISHED:

	DEBUGF	1,"TCBStateHandler: Established\n"

	mov	eax, [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.RCV_NXT]
	bswap	eax
	DEBUGF	1,"RCV_NXT is set to:%u\n", eax
	bswap	eax
	cmp	eax, [edx + TCP_Packet.SequenceNumber]
	jne	.exit					;;;;;;

; check if we received an ACK
	test	[edx + TCP_Packet.Flags], TH_ACK
	jz	.no_ack

	mov	ax, [edx + TCP_Packet.Window]
	xchg	al, ah
	cmp	ax, 1024
	ja	@f
	mov	[ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.wndsizeTimer], 1
      @@:
  .no_ack:

; Now, see if we received any data
	test	ecx, ecx
	jz	.nodata

; Calculate next sequencenumber
	add_INET (ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.RCV_NXT)

	push	edx
	DEBUGF	1,"Got %u bytes data!\n", ecx
; calculate header length
	movzx	eax, [edx + TCP_Packet.DataOffset]
	and	eax, 11110000b
	shr	eax, 2
	DEBUGF	1,"TCP header size: %u\n", eax
	add	edx, eax	; now edx points to data

	add	esp, 4
	pop	esi		; pointer to buffer
	add	esp, 4

	sub	edx, esi
	mov	edi, edx	; offset
	mov	eax, ebx	; socket ptr

	call	socket_internal_receiver	; Place the data from packet into socket

;        lea     ebx, [eax + SOCKET_head.lock]
;        call    wait_mutex
	mov	ebx, eax
	pop	edx

	test	[edx + TCP_Packet.Flags], TH_FIN + TH_RST
	jz	.ack

  .nodata:
	test	[edx + TCP_Packet.Flags], TH_FIN + TH_RST
	jz	.exit

; Send an ACK to that fin, and enter closewait state
	mov	[ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.state], TCB_CLOSE_WAIT
; Remove all resend entries from the queue
	mov	eax, ebx
	call	TCP_remove_socket

  .ack:
	push	ebx
	mov	ecx, [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.RCV_NXT]
	call	TCP_queue_ack
	pop	ebx

  .exit:
	mov	[ebx + SOCKET_head.lock], 0
	ret


end if


align 4
stateTCB_ESTABLISHED:

	DEBUGF	1,"TCBStateHandler: Established\n"

	mov	eax, [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.RCV_NXT]
	bswap	eax
	DEBUGF	1,"RCV_NXT is set to:%u\n", eax
	bswap	eax
	cmp	eax, [edx + TCP_Packet.SequenceNumber]
	jne	.exit

; Calculate next sequencenumber
	add_INET (ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.RCV_NXT)

	test	[edx + TCP_Packet.Flags], TH_FIN + TH_RST
	jnz	.fin

  .check_ack:
	test	[edx + TCP_Packet.Flags], TH_ACK
	jz	.exit

	DEBUGF	1,"Received ACK\n"
; First, look at the incoming window. If this is less than or equal to 1024,
; Set the socket window timer to 1. This will stop an additional Packets being queued.
; ** I may need to tweak this value, since I do not know how many Packets are already queued
	push	ecx
	mov	cx, [edx + TCP_Packet.Window]
	xchg	cl, ch
	cmp	cx, 1024
	ja	@f
	mov	[ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.wndsizeTimer], 1
      @@:
	pop	ecx

; Now, see if we received any data
	test	ecx, ecx
	jz	.exit

	DEBUGF	1,"Got %u bytes data!\n", ecx
; calculate header length
	movzx	eax, [edx + TCP_Packet.DataOffset]
	and	eax, 11110000b
	shr	eax, 2
       DEBUGF 1,"TCP header size: %u\n", eax
	add	edx, eax	; now edx points to data

	add	esp, 4
	pop	esi		; pointer to buffer
	add	esp, 4

	sub	edx, esi
	mov	edi, edx	; offset
	mov	eax, ebx	; socket ptr

	call	socket_internal_receiver	; Place the data from packet into socket

	lea	ebx, [eax + SOCKET_head.lock]
	call	wait_mutex
	mov	ebx, eax

  .ack:
	mov	eax, ebx
	mov	bl, TH_ACK
	push	eax
	xor	ecx, ecx
	call	TCP_send		    ; send the ack
	pop	ebx
  .exit:
	mov	[ebx + SOCKET_head.lock], 0
	ret

  .fin:        ; we received a FIN or RESET
; Remove all resend entries from the queue
	mov	ecx, TCP_QUEUE_SIZE
	mov	esi, TCP_OUT_QUEUE+4

  .removeloop:
	cmp	[esi + tcp_out_queue_entry.data_ptr], 0
	je	.maybe_next

	; TODO: check if the packets belong to the same tcp connection !

       DEBUGF 1,"Removing a queued packet\n"

	push	[esi + tcp_out_queue_entry.data_ptr]
	mov	[esi + tcp_out_queue_entry.data_ptr], 0
	dec	[TCP_OUT_QUEUE]
	call	kernel_free

  .maybe_next:
	add	esi, tcp_out_queue_entry.size
	loop	.removeloop

; Send an ACK to that fin, and enter closewait state
	mov	[ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.state], TCB_CLOSING
	jmp	.check_ack


align 4
stateTCB_FIN_WAIT_1:

	DEBUGF	1,"TCBStateHandler: Fin_wait_1\n"

	; We can either receive an ACK of a fin, or a fin
	mov	al, [edx + TCP_Packet.Flags]
	and	al, TH_FIN + TH_ACK

	cmp	al, TH_ACK
	jne	@f

	; It was an ACK
	mov	[ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.state], TCB_FIN_WAIT_2
	jmp	.exit

    @@: mov	[ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.state], TCB_CLOSING
	cmp	al, TH_FIN
	je	@f
	mov	[ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.state], TCB_TIMED_WAIT

    @@:
	; Send an ACK
	mov	eax, ebx
	mov	bl, TH_ACK
	push	eax
	xor	ecx, ecx
	call	TCP_send
	pop	ebx

  .exit:
	mov	[ebx + SOCKET_head.lock], 0
	ret



align 4
stateTCB_FIN_WAIT_2:

	DEBUGF	1,"TCBStateHandler: Fin_wait_2\n"

	test	[edx + TCP_Packet.Flags], TH_FIN
	jz	.exit

	; Change state, as we have a fin
	mov	[ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.state], TCB_TIMED_WAIT

	; Send an ACK
	mov	eax, ebx
	mov	bl, TH_ACK
	push	eax
	xor	ecx, ecx
	call	TCP_send
	pop	ebx

  .exit:
	mov	[ebx + SOCKET_head.lock], 0
	ret



align 4
stateTCB_CLOSE_WAIT:

	DEBUGF	1,"TCBStateHandler: close_wait\n"
	; Intentionally left empty
	; socket_close_tcp handles this

	mov	[ebx + SOCKET_head.lock], 0
	ret



align 4
stateTCB_CLOSING:

	DEBUGF	1,"TCBStateHandler: closingn\n"

	; We can either receive an ACK of a fin, or a fin
	test	[edx + TCP_Packet.Flags], TH_ACK
	jz	.exit

	mov	[ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.state], TCB_TIMED_WAIT

  .exit:

	mov	[ebx + SOCKET_head.lock], 0
	ret


align 4
stateTCB_LAST_ACK:

	DEBUGF	1,"TCBStateHandler: last_ackn\n"

	; Look at control flags - expecting an ACK
	test	[edx + TCP_Packet.Flags], TH_ACK
	jz	.exit

	mov	[ebx + SOCKET_head.lock], 0

	; delete the socket
	stdcall net_socket_free, ebx

  .exit:
	ret


align 4
stateTCB_TIME_WAIT:

	DEBUGF	1,"TCBStateHandler: time_wait\n"

	mov	[ebx + SOCKET_head.lock], 0

	ret


align 4
stateTCB_CLOSED:

	DEBUGF	1,"TCBStateHandler: closed\n"

	mov	[ebx + SOCKET_head.lock], 0

	ret



;---------------------------------------------------------------------------
;
; TCP_API
;
; This function is called by system function 75
;
; IN:  subfunction number in bl
;      device number in bh
;      ecx, edx, .. depends on subfunction
;
; OUT:
;
;---------------------------------------------------------------------------
align 4
TCP_API:

	movzx	eax, bh
	shl	eax, 2

	test	bl, bl
	jz	.packets_tx	; 0
	dec	bl
	jz	.packets_rx	; 1

.error:
	mov	eax, -1
	ret

.packets_tx:
	add	eax, TCP_PACKETS_TX
	mov	eax, [eax]
	ret

.packets_rx:
	add	eax, TCP_PACKETS_RX
	mov	eax, [eax]
	ret
