;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;                                                              ;;
;; Copyright (C) KolibriOS team 2009-2012. All rights reserved. ;;
;; Distributed under terms of the GNU General Public License    ;;
;;                                                              ;;
;; Clevermouse & hidnplayr                                      ;;
;;                                                              ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


struct  PPPoE_header

        VersionAndType  db ?
        Code            db ?
        SessionID       dw ?
        Length          dw ?            ; Length of payload, does NOT include the length PPPoE header.

ends

struct  PPPoE_connection

        next            dd ?            ; pointer to next connection
        prev            dd ?            ; pointer to previous connection

        pid             dd ?            ; identifier of base application

        datalen         dd ?            ; length of received data
        recvbuf         rb 1500         ; buffer for received data
        sendbuf         rb 1500         ; buffer for data to send

ends

iglobal
align 4
        PPPoE.head              dd PPPoE.head
        PPPoE.tail              dd PPPoE.head
endg

uglobal
        PPPoE.cur_receiver      dd ?
        PPPoE.cur_receiver_ptr  dd ?
        PPPoE.cur_receiver_len  dd ?
endg


; Allocates internal structure for future PPPoE actions.
align 4
PPPoE_alloc_connection:

; 1. Allocate memory in the kernel area.
        stdcall kernel_alloc, sizeof.PPPoE_connection

; 1a. If memory allocation failed, return NULL.
        test    eax, eax
        jz      .nothing

; 2. Copy PID of caller to the structure.
        mov     edx, [CURRENT_TASK]
        mov     [eax + PPPoE_connection.pid], edx

; 3. Insert the structure to the list of all connections.
        mov     [eax + PPPoE_connection.next], PPPoE.head
        mov     edx, [PPPoE.tail]
        mov     [eax + PPPoE_connection.prev], edx
        mov     [edx + PPPoE_connection.next], eax
        mov     [PPPoE.tail], eax

  .nothing:
        ret


align 4
PPPoE_free_connection:

; 1. Check that the caller is the owner of this connection.
        mov     eax, [CURRENT_TASK]
        cmp     [ebx+PPPoE_connection.pid], eax
        jnz     .nothing

; 2. Delete the structure from the list of all connections.
        mov     eax, [ebx+PPPoE_connection.next]
        mov     edx, [ebx+PPPoE_connection.prev]
        mov     [eax+PPPoE_connection.prev], edx
        mov     [edx+PPPoE_connection.next], eax

; 3. Free the memory.
        stdcall kernel_free, ebx

  .nothing:
        ret


; Send PADI packet

; ebx (ecx in app) = size of buffer for PPPoE offers, must be at least 1514
; ecx (edx in app) = size of tags, 0 means "use default"
; edx (esi in app) = pointer to buffer for PPPoE offers
; esi (edi in app) = pointer to tags, ignored if 'size of tags' == 0
align 4
PPPoE_send_init:

; 1. Check length.
        cmp     ebi, 1514
        jb      .bad

; RFC2516: An entire PADI packet (including the PPPoE header) MUST NOT
; exceed 1484 octets.
; PPPoE header is 6 bytes long, so maximum length of tags is 1478.
        cmp     ecx, 1478
        ja      .bad

; 2. Check that no one listen for offers.
        cmp     [PPPoE.cur_receiver], 0
        jnz     .bad

; 3. Remember PID and data pointer of current listener.
        push    [CURRENT_TASK]
        pop     [PPPoE.cur_receiver]
        mov     [PPPoE.cur_receiver_ptr], edx
        mov     [PPPoE.cur_receiver_len], ebx
        and     dword [edx], 0 ; no offers yet

; 4. Create packet.
        test    ecx, ecx
        jnz     @f
        mov     esi, .default_payload
        mov     ecx, .default_payload_length
       @@:

        mov     edx, [NET_DRV_LIST]     ;;;; FIXME
        lea     eax, [ebx + ETH_DEVICE.mac]     ; Source Address
        mov     edx, ETH_BROADCAST              ; Destination Address
        add     ecx, sizeof.PPPoE_header        ; Data size
        mov     di, ETHER_PPP_DISCOVERY         ; Protocol
        call    ETH_output
        jz      .eth_error

        push    edx eax

; 4b. Set ver=1, type=1 (=> first byte 0x11), code=9 (PADI packet), session=0
        mov     dword [edi], (0x09 shl 8) + 0x11

; 4c. Set payload length.
        mov     [edi+4], ch
        mov     [edi+5], cl

; 4e. Copy given tags.
        rep     movsb

; 5. Send packet.
        call    [ebx + NET_DEVICE.transmit]
; 6. Return.
        xor     eax, eax
        ret

  .bad:
        or      eax, -1
        ret

  .default_payload:
; Service-Name tag with zero length
        dw      0x0101, 0x0000
  .default_payload_length = $ - .default_payload


; Stop receiving PADO packets
align 4
PPPoE_stop_offers:

; Only the listener can stop listen.    ;;; TODO: make sure this function is called when process gets terminated
        mov     eax, [CURRENT_TASK]
        cmp     [PPPoE.cur_receiver], eax
        jnz     .bad
        xor     eax, eax
        mov     [PPPoE.cur_receiver_ptr], eax
        mov     [PPPoE.cur_receiver], eax
        ret

  .bad:
        or      eax, -1
        ret

; Send PPPoE data in Discovery stage
align 4
PPPoE_send_discovery:
        ret

; Receive PPPoE data in Discovery stage
align 4
PPPoE_receive_discovery:
        ret



;-----------------------------------------------------------------
;
; PPPoE discovery input
;
; Handler of received Ethernet packet with type = Discovery
;
;
;  IN:  Pointer to buffer in [esp]
;       size of buffer in [esp+4]
;       pointer to device struct in ebx
;       pointer to PPP header in edx
;       size of PPP packet in ecx
;  OUT: /
;
;-----------------------------------------------------------------
align 4
PPPoE_discovery_input:

; 1. Minimum 6 bytes for PPPoE header.
        cmp     ecx, sizeof.PPPoE_header
        jb      .bad

; 1. Ignore packets with ver<>1 and/or type<>1.
        cmp     [edx + PPPoE_header.VersionAndType], 0x11
        jnz     .bad

; 2. Code must be either 7 for Offer,
; or 0x65 for Session-Confirmation, or 0xa7 for Terminate.
; Because only Initiation/Offers are supported, we expect only value 7.
        cmp     [edx + PPPoE_header.Code], 7
        jnz     .bad

; 3. Session ID must be zero for Offers.
        cmp     [edx + PPPoE_header.SessionID], 0
        jnz     .bad

; 4. Payload length
        rol     [edx + PPPoE_header.Length], 8          ; Convert INET byte order to intel

; 5. Ignore packet if nobody is listening.
        cmp     [PPPoE.cur_receiver], 0
        jz      .bad

; 6. Good, now copy the received packet to the buffer of listener.

        ;;; TODO

  .bad:
        DEBUGF 1,'K : PPPoE - dumped\n'
        call    kernel_free
        add     esp, 4                                  ; pop (balance stack)
        ret




;---------------------------------------------------------------------------
;
; PPPoE 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
PPPoE_api:

        movzx   eax, bh
        shl     eax, 2

        and     ebx, 0xff
        cmp     ebx, .number
        ja      .error
        jmp     dword [.table + 4*ebx]

  .table:
        dd      PPPoE_send_init         ; 0
        dd      PPPoE_stop_offers       ; 1
        dd      PPPoE_alloc_connection  ; 3
        dd      PPPoE_free_connection   ; 4
        dd      PPPoE_send_discovery    ; 5
        dd      PPPoE_receive_discovery ; 6
  .number = ($ - .table) / 4 - 1

  .error:
        mov     eax, -1
        ret