;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;                                                                 ;;
;; Copyright (C) KolibriOS team 2004-2009. All rights reserved.    ;;
;; Distributed under terms of the GNU General Public License       ;;
;;                                                                 ;;
;;  ARP.INC                                                        ;;
;;                                                                 ;;
;;  Part of the tcp/ip network stack for KolibriOS                 ;;
;;                                                                 ;;
;;  Based on the work of [Johnny_B] and [smb]                      ;;
;;                                                                 ;;
;;    Written by hidnplayr@kolibrios.org                           ;;
;;                                                                 ;;
;;          GNU GENERAL PUBLIC LICENSE                             ;;
;;             Version 2, June 1991                                ;;
;;                                                                 ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


$Revision$


ARP_NO_ENTRY		equ  0
ARP_VALID_MAPPING	equ  1
ARP_AWAITING_RESPONSE	equ  2
ARP_RESPONSE_TIMEOUT	equ  3

ARP_REQUEST_TTL 	= 20 ; in seconds
ARP_ENTRY_TTL		= 600 ; in seconds

ETHER_ARP		equ  0x0608

ARP_REQ_OPCODE		equ  0x0100  ; request
ARP_REP_OPCODE		equ  0x0200  ; reply

ARP_TABLE_SIZE		equ  20      ; Size of table

struct ARP_ENTRY
       .IP		dd  ?
       .MAC		dp  ?
       .Status		dw  ?
       .TTL		dw  ?  ; in seconds
       .size:
ends

struct ARP_Packet
       .HardwareType	dw  ?
       .ProtocolType	dw  ?
       .HardwareSize	db  ?
       .ProtocolSize	db  ?
       .Opcode		dw  ?
       .SenderMAC	dp  ?
       .SenderIP	dd  ?
       .TargetMAC	dp  ?
       .TargetIP	dd  ?
ends


; The TTL field is decremented every second, and is deleted when it
; reaches 0. It is refreshed every time a packet is received
; If the TTL field is 0xFFFF it is a static entry and is never deleted
; The status field can be the following values:
; 0x0000  entry not used
; 0x0001  entry holds a valid mapping
; 0x0002  entry contains an IP address, awaiting ARP response
; 0x0003  No response received to ARP request.
; The last status value is provided to allow the network layer to delete
; a packet that is queued awaiting an ARP response

align 4
uglobal

	NumARP		dd ?

	ARPTable	rb ARP_ENTRY.size * ARP_TABLE_SIZE

	ARP_PACKETS_TX	rd  MAX_NET_DEVICES
	ARP_PACKETS_RX	rd  MAX_NET_DEVICES


endg



;-----------------------------------------------------------------
;
; ARP_init
;
;  This function resets all ARP variables
;
;  IN:  /
;  OUT: /
;
;-----------------------------------------------------------------
align 4
ARP_init:

	xor	eax, eax

	mov	[NumARP], eax

	mov	edi, ARP_PACKETS_TX
	mov	ecx, 2*MAX_NET_DEVICES
	rep	stosd

	ret


;-----------------------------------------------------------------
;
; ARP_IP_to_MAC
;
;  This function resets all ARP variables
;
;  IN: eax = IPv4 address
;  OUT: eax = -1 on error, else eax = first two bytes of mac
;                   ( high 16 bits are zero)
;       ebx = last four bytes of mac                                  ; TODO: special eax value for 'request send'
;
;-----------------------------------------------------------------
align 4
ARP_IP_to_MAC:

	DEBUGF 1,"ARP_IP_to_MAC\n"

    ; first, check destination IP to see if it is on 'this' network.
    ; The test is:
    ; if ( destIP & subnet_mask == stack_ip & subnet_mask )
    ;   destination is local
    ; else
    ;  destination is remote, so pass to gateway

	xor	edx, edx ; TODO: find device num in edx

	mov	ebx, [IP_LIST+edx]
	and	ebx, [SUBNET_LIST+edx]

	mov	ecx, eax
	and	ecx, [SUBNET_LIST+edx]

	cmp	ecx, ebx
	je	.local

	mov	eax, [GATEWAY_LIST+edx]
	DEBUGF 1,"requested IP is not on subnet, using gateway\n"

  .local:
   ; try to find it on the list
	mov	ecx, [NumARP]
	test	ecx, ecx
	jz	.not_in_list
	mov	esi, ARPTable + ARP_ENTRY.IP
  .scan_loop:
	cmp	[esi], eax
	je	.found_it
	add	esi, ARP_ENTRY.size
	loop	.scan_loop
  .not_in_list:

	DEBUGF 1,"IP not found on list, preparing for ARP request\n"

   ; if not, reserve an entry in list and send an ARP request packet

	push	eax

	pushw	ARP_REQUEST_TTL
	pushw	ARP_AWAITING_RESPONSE
	pushd	0
	pushw	0
	pushd	eax
	call	ARP_add_entry
	cmp	eax, -1
	je	.full

	; <Some dirty test code>

	; This piece of code waits for an ARP reply

	mov	ebx, eax
	pop	eax
	push	ebx
	call	ARP_create_request

	push	[timer_ticks]
	add	dword[esp], 100*ARP_REQUEST_TTL
	DEBUGF 1,"Waiting for ARP reply, time: %x, entry:%u\n",[timer_ticks], [esp + 4]
   .dirty_loop:

	call	change_task	; The ARP reply hasnt been received yet, tell the processor to do some other stuff first

	mov	eax, [esp + 4]
	imul	eax, ARP_ENTRY.size
	add	eax, ARPTable
	cmp	[eax + ARP_ENTRY.Status], ARP_VALID_MAPPING
	je	.gogogo

	mov	eax, [esp]	; Check if the reply hasnt timed-out yet
	cmp	[timer_ticks], eax
	jl	.dirty_loop

	; </Some dirty test code>
	or	eax, -1
	add	esp, 8
	ret

  .found_it:
	DEBUGF 1,"found MAC in ARPTable\n"
	movzx  eax, word [esi+ARP_ENTRY.MAC]
	mov    ebx, dword[esi+ARP_ENTRY.MAC+2]
	ret

  .full:
	add	esp, 4
	mov	eax, -1
	ret

  .gogogo:
	DEBUGF 1,"got ARP reply, time: %x\n",[timer_ticks]
	mov    ebx, dword[eax+ARP_ENTRY.MAC+2]
	movzx  eax, word [eax+ARP_ENTRY.MAC]
	add    esp, 8
	ret


;---------------------------------------------------------------------------
;
; ARP_create_request
;
; IN:  ip in eax
;
; OUT: /
;
;---------------------------------------------------------------------------
align 4
ARP_create_request:

	DEBUGF 1,"Create ARP Packet\n"

	call	IPv4_dest_to_dev

	push	eax						; DestIP
	mov	eax, [IP_LIST+4*edi]				; senderIP
	push	eax

	mov	edi, [ETH_DRV_LIST + 4*edi]
	lea	eax, [edi + ETH_DEVICE.mac]
	mov	ebx, ETH_BROADCAST
	mov	ecx, 60 ; minimum packet size
	mov	edx, edi ;;;
	mov	di , ETHER_ARP
	call	ETH_create_packet
	cmp	edi, -1
	je	.exit

	mov	ecx, eax

	mov	[edi + ARP_Packet.HardwareType], 0x0100 ;Ethernet
	mov	[edi + ARP_Packet.ProtocolType], 0x0008 ;IP
	mov	[edi + ARP_Packet.HardwareSize], 6   ;MAC-addr length
	mov	[edi + ARP_Packet.ProtocolSize], 4   ;IP-addr length
	mov	[edi + ARP_Packet.Opcode], ARP_REQ_OPCODE      ;Request

	add	edi, ARP_Packet.SenderMAC			; sendermac
	lea	esi, [ebx + ETH_DEVICE.mac]			;
	movsw							;
	movsd							;
	pop	eax						;
	stosd							;
	mov	eax, -1 					; destmac
	stosd							;
	stosw							;
	pop	eax
	stosd							;

	DEBUGF 1,"ARP Packet for device %x created successfully\n", ebx

	push	edx ecx
	jmp	ETH_sender

  .exit:
	add	esp, 8
	DEBUGF 1,"Create ARP Packet - failed\n"
	mov	eax, -1
	ret



;---------------------------------------------------------------------------
;
; ARP_decrease_entry_ttls
;
; IN: /
; OUT: /
;
;---------------------------------------------------------------------------
align 4
ARP_decrease_entry_ttls:

	mov	ecx, [NumARP]
	test	ecx, ecx
	jz	.exit

	mov	ebx, ARPTable

.timer_loop:

	cmp	[ebx + ARP_ENTRY.TTL], 0xFFFF
	je	.timer_loop_end  ;if TTL==0xFFFF then it's static entry

	cmp	[ebx + ARP_ENTRY.TTL], 0
	jnz	.timer_loop_end_with_dec  ;if TTL!=0

	; Ok, TTL is 0
	;if Status==AWAITING_RESPONSE and TTL==0
	;then we have to change it to ARP_RESPONSE_TIMEOUT
	cmp	[ebx + ARP_ENTRY.Status], ARP_AWAITING_RESPONSE
	jne	@f

	mov	[ebx + ARP_ENTRY.Status], ARP_RESPONSE_TIMEOUT
	mov	[ebx + ARP_ENTRY.TTL], word 0x000A   ;10 sec
	jmp	.timer_loop_end

  @@:
	;if TTL==0 and Status==VALID_MAPPING, we have to delete it
	;if TTL==0 and Status==RESPONSE_TIMEOUT, delete too
	mov	esi, [NumARP]
	sub	esi, ecx	  ;esi=index of entry, will be deleted

	push	ebx ecx
	call	ARP_del_entry
	pop	ecx ebx

	jmp	.timer_loop_end


.timer_loop_end_with_dec:

	dec	[ebx + ARP_ENTRY.TTL]  ;decrease TTL

.timer_loop_end:

	add	ebx, ARP_ENTRY.size
	loop	.timer_loop

.exit:

	ret

;-----------------------------------------------------------------
;
; ARP_add_entry (or update)
;
; IN: arp entry in stack: esp     .IP
;                         esp+4   .MAC
;                         esp+10  .Status
;                         esp+12  .TTL
;                         esp+14
;
; OUT: eax = entry #, -1 on error
;
;-----------------------------------------------------------------   ; TODO: use a mutex
align 4
ARP_add_entry:

	DEBUGF 1,"ARP add entry: "

	mov	ecx, [NumARP]
	test	ecx, ecx
	jz	.add

	mov	eax, dword[esp + 4 + ARP_ENTRY.MAC]
	mov	bx , word[esp + 4 + ARP_ENTRY.MAC + 4]
	mov	esi, ARPTable

.loop:
	cmp	dword [esi + ARP_ENTRY.MAC], eax
	jne	.maybe_next
	cmp	word [esi + ARP_ENTRY.MAC + 4], bx
	jne	.maybe_next

	cmp	dword[esi + ARP_ENTRY.TTL], 0xFFFF ; static entry
	jne	.notstatic
	cmp	dword[esp + 4 + ARP_ENTRY.TTL], 0xFFFF
	jne	.error
.notstatic:

	mov	ebx, [NumARP]
	xchg	ebx, ecx
	sub	ecx, ebx
	jmp	.add

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

	mov	ecx, [NumARP]
	cmp	ecx, ARP_TABLE_SIZE
	jge	.error

.add:
	push	ecx
	imul	ecx, ARP_ENTRY.size
	lea	edi, [ecx + ARPTable]
	lea	esi, [esp + 8]
	mov	ecx, ARP_ENTRY.size/2
	repz	movsw

	inc	[NumARP]
	pop	eax
	DEBUGF 1,"New entry created: %u\n", eax
.exit:
	DEBUGF 1,"Exiting\n"
	ret	ARP_ENTRY.size

.error:

	DEBUGF 1,"error! \n"

	mov	eax, -1
	jmp	.exit


;-----------------------------------------------------------------
;
; ARP_del_entry
;
; IN: entry # in esi
; OUT: /
;
;-----------------------------------------------------------------
align 4
ARP_del_entry:

	DEBUGF 1,"ARP del entry %u, total entrys: %u\n", esi, [NumARP]

	cmp	esi, [NumARP]
	jge	.error

	DEBUGF 1,"deleting the entry..\n"

	imul	esi, ARP_ENTRY.size

	mov	ecx, (ARP_TABLE_SIZE - 1) * ARP_ENTRY.size
	sub	ecx, esi

	lea	edi, [ebx + esi]	    ;edi=ptr to entry that should be deleted
	lea	esi, [edi + ARP_ENTRY.size] ;esi=ptr to next entry

	shr	ecx,1	   ;ecx/2 => ARP_ENTRY_SIZE MUST BE EVEN NUMBER!
	rep	movsw

	dec	[NumARP] ;decrease arp-entries counter
.error:
	ret




;-----------------------------------------------------------------
;
; ARP_Handler:
;
;  This function handles ARP protocol over ethernet
;  (other protocols may follow in the future)
;
;  IN:  Pointer to buffer in [esp]
;       size of buffer in [esp+4]
;       packet size (without ethernet header) in ecx
;  OUT: /
;
;-----------------------------------------------------------------
align 4
ARP_handler:

	DEBUGF	1,"ARP_Handler - start\n"
	cmp	ecx, 28
	jl	.exit

	cmp	word [edx + ARP_Packet.Opcode], ARP_REP_OPCODE	; Is this a reply packet?
	jne	.maybe_request

	DEBUGF	1,"ARP_Handler - it's a reply packet from %u.%u.%u.%u\n",\
	[edx + ARP_Packet.SenderIP]:1,[edx + ARP_Packet.SenderIP+1]:1,[edx + ARP_Packet.SenderIP+2]:1,[edx + ARP_Packet.SenderIP+3]:1,

	mov	ecx, [NumARP]
	test	ecx, ecx
	jz	.exit

	mov	eax, [edx + ARP_Packet.SenderIP]
	mov	esi, ARPTable+ARP_ENTRY.IP

  .loop:
	cmp	[esi], eax
	je	.gotit
	add	esi, ARP_ENTRY.size
	loop	.loop

	jmp	.exit

  .gotit:

	DEBUGF	1,"ARP_Handler - found matching entry\n"

	cmp	[esi+ARP_ENTRY.Status], 0x0300	 ;if it is a static entry, dont touch it
	je	.exit

	DEBUGF	1,"ARP_Handler - updating entry\n"

	mov	[esi+ARP_ENTRY.Status], ARP_VALID_MAPPING
	mov	[esi+ARP_ENTRY.TTL], ARP_ENTRY_TTL

	mov	eax, dword [edx + ARP_Packet.SenderMAC]
	mov	dword [esi+ARP_ENTRY.MAC], eax
	mov	ax , word [edx + ARP_Packet.SenderMAC + 4]
	mov	word [esi+ARP_ENTRY.MAC+4], ax

	jmp	.exit


;------


  .maybe_request:
	cmp	word [edx + ARP_Packet.Opcode], ARP_REQ_OPCODE	; Is this a request packet?
	jne	.exit

	call	ETH_struc2dev
	DEBUGF	1,"ARP Request packet through device: %u\n", edi
	inc	[ARP_PACKETS_RX+4*edi]
	cmp	edi, -1
	jz	.exit

	mov	eax, edi
	shl	eax, 2
	add	eax, IP_LIST
	mov	eax, [eax]
	cmp	eax, [edx + ARP_Packet.TargetIP]		; Is it looking for my IP address?
	jnz	.exit
	push	eax
	push	edi

; OK, it is a request for one of our MAC addresses. Build the frame and send it
; We can reuse the buffer.  (faster then using ARP_create_packet)

	cld
	lea	esi, [edx + ARP_Packet.SenderMAC]
	lea	edi, [edx + ARP_Packet.TargetMAC]
	movsd							; Move Sender Mac to Dest MAC
	movsw							;
	movsd							; Move sender IP to Dest IP

	pop	esi
	mov	esi, [ETH_DRV_LIST + 4*esi]
	lea	esi, [esi + ETH_DEVICE.mac]
	lea	edi, [edx + ARP_Packet.SenderMAC]
	movsd							; Copy MAC address from in MAC_LIST
	movsw							;
	pop	eax
	stosd							; Write our IP

	mov	word [edx + ARP_Packet.Opcode], ARP_REP_OPCODE

; Now, Fill in ETHERNET header

	mov	edi, [esp]
	lea	esi, [edx + ARP_Packet.TargetMAC]
	movsd
	movsw
	lea	esi, [edx + ARP_Packet.SenderMAC]
	movsd
	movsw
;        mov     ax , ETHER_ARP
;        stosw

	DEBUGF	1,"ARP_Handler - Sending reply \n"

	jmp	ETH_sender					; And send it!

     .exit:
	call	kernel_free
	add	esp, 4						; pop (balance stack)

	DEBUGF 1,"ARP_Handler - exiting\n"
	ret




;-----------------------------------------------------------------
;
; ARP_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
ARP_API:

	movzx	eax, bh
	shl	eax, 2

	test	bl, bl
	jz	.packets_tx	; 0
	dec	bl
	jz	.packets_rx	; 1
	dec	bl
	jz	.entries	; 2
	dec	bl
	jz	.read		; 3
	dec	bl
	jz	.write		; 4
	dec	bl
	jz	.remove 	; 5
	dec	bl

.error:
	mov	eax, -1
	ret

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

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

.entries:
	mov	eax, [NumARP]
	ret

.read:
	cmp	ecx, [NumARP]
	jge	.error
	; edi = pointer to buffer
	; ecx = # entry
	imul	ecx, ARP_ENTRY.size
	add	ecx, ARPTable
	mov	esi, ecx
	mov	ecx, ARP_ENTRY.size/2
	rep	movsw

	xor	eax, eax
	ret

.write:
	; esi = pointer to buffer
	sub	esp, ARP_ENTRY.size
	mov	edi, esp
	mov	ecx, ARP_ENTRY.size/2
	rep	movsw
	call	ARP_add_entry	     ;out: eax = entry number, -1 on error
	ret

.remove:
	; ecx = # entry
	mov	esi, ecx
	call	ARP_del_entry
	ret

