;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;                                                                 ;;
;; Copyright (C) KolibriOS team 2010-2014. All rights reserved.    ;;
;; Distributed under terms of the GNU General Public License       ;;
;;                                                                 ;;
;;  tftpc.asm - TFTP client for KolibriOS                          ;;
;;                                                                 ;;
;;  Written by hidnplayr@kolibrios.org                             ;;
;;                                                                 ;;
;;          GNU GENERAL PUBLIC LICENSE                             ;;
;;             Version 2, June 1991                                ;;
;;                                                                 ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

format binary as ""

__DEBUG__       = 0
__DEBUG_LEVEL__ = 1

use32
        org     0x0

        db      'MENUET01'
        dd      0x1
        dd      START
        dd      I_END
        dd      IM_END+0x1000
        dd      IM_END+0x1000
        dd      0, 0

include '../../proc32.inc'
include '../../macros.inc'
include '../../libio.inc'
include '../../dll.inc'
include '../../develop/libraries/box_lib/trunk/box_lib.mac'

include '../../network.inc'
include '../../debug-fdo.inc'

TIMEOUT         = 100
buffer_len      = 4096

opcode_rrq      = 1 shl 8
opcode_wrq      = 2 shl 8
opcode_data     = 3 shl 8
opcode_ack      = 4 shl 8
opcode_error    = 5 shl 8

; read/write request packet
;
;  2 bytes     string    1 byte     string   1 byte
;  ------------------------------------------------
; | Opcode |  Filename  |   0  |    Mode    |   0  |
;  ------------------------------------------------

; data packet
;
;  2 bytes     2 bytes      n bytes
;  ----------------------------------
; | Opcode |   Block #  |   Data     |
;  ----------------------------------

; acknowledgement packet
;
;  2 bytes     2 bytes
;  ---------------------
; | Opcode |   Block #  |
;  ---------------------

; error packet
;
;  2 bytes  2 bytes        string    1 byte
;  ----------------------------------------
; | Opcode |  ErrorCode |   ErrMsg   |   0  |
;  ----------------------------------------


START:

        mcall   68, 11

        stdcall dll.Load, @IMPORT
        or      eax, eax
        jnz     exit

home:
        mcall   40, EVM_MOUSE + EVM_MOUSE_FILTER + EVM_REDRAW + EVM_BUTTON + EVM_KEY + EVM_STACK

redraw:
        call    draw_window

mainloop:
        mcall   10
        dec     eax
        jz      redraw
        dec     eax
        jz      key
        dec     eax
        jz      button

        invoke  edit_box_mouse, edit1
        invoke  edit_box_mouse, edit2
        invoke  edit_box_mouse, edit3
        invoke  edit_box_mouse, edit4

        invoke  option_box_mouse, Option_boxs1
        invoke  option_box_mouse, Option_boxs2

        jmp     mainloop

button:
        mcall   17

        cmp     ah, 0x10        ; connect button
        je      start_transfer

        test    ah , ah
        jz      mainloop

exit:
        mcall   -1
key:
        mcall   2

        invoke  edit_box_key, edit1
        invoke  edit_box_key, edit2
        invoke  edit_box_key, edit3
        invoke  edit_box_key, edit4

        jmp     mainloop


draw_window:
; get system colors
        mcall   48, 3, sc, 40

        mcall   12, 1

        mov     edx, [sc.work]
        or      edx, 0x34000000
        xor     esi, esi
        mov     edi, str_title
        mcall   0, 50 shl 16 + 400, 30 shl 16 + 180

        mov     ebx, 35 shl 16 + 10
        mov     ecx, 0x80000000
        or      ecx, [sc.work_text]
        mov     edx, str_server
        mcall   4
        mov     ebx, 5 shl 16 + 30
        mov     edx, str_source
        mcall
        mov     ebx, 11 shl 16 + 50
        mov     edx, str_destination
        mcall
        mov     ebx, 47 shl 16 + 72
        mov     edx, str_mode
        mcall
        mov     ebx, 160 shl 16 + 72
        mov     edx, str_method
        mcall
        mov     ebx, 270 shl 16 + 72
        mov     edx, str_blocksize
        mcall

        invoke  edit_box_draw, edit1
        invoke  edit_box_draw, edit2
        invoke  edit_box_draw, edit3
        invoke  edit_box_draw, edit4

        invoke  option_box_draw, Option_boxs1
        invoke  option_box_draw, Option_boxs2

        mov     esi, [sc.work_button]
        mcall   8, 210 shl 16 + 170, 105 shl 16 + 16, 0x10

        mcall   38, 10 shl 16 + 380, 130 shl 16 + 130, [sc.work_graph]

        cmp     [errormsg], 0
        je      .no_error

        mov     ecx, 0x80000000
        or      ecx, [sc.work_text]
        mcall   4, 20 shl 16 + 137, , [errormsg]
        mcall   12, 2
        jmp     .draw_btn

  .no_error:
        mov     ecx, 0x80000000
        or      ecx, [sc.work_text]
        mcall   4, 350 shl 16 + 137, , str_kb_s
        mcall   4, 50 shl 16 + 137, , str_complete
        mcall   47, 1 shl 31 + 7 shl 16 + 1, kbps, 305 shl 16 + 137, [sc.work_text]
        mcall   47, 1 shl 31 + 3 shl 16 + 1, done, 25 shl 16 + 137

  .draw_btn:
        cmp     [socketnum], 0
        je      .no_transfer

        mov     ecx, 0x80000000
        or      ecx, [sc.work_button_text]
        mcall   4, 270 shl 16 + 110, , str_stop

        mcall   12, 2
        ret

  .no_transfer:
        mov     ecx, 0x80000000
        or      ecx, [sc.work_button_text]
        mcall   4, 260 shl 16 + 110, , str_transfer

        mcall   12, 2
        ret



start_transfer:

; resolve the hostname
        mov     [errormsg], str_err_resolve

        push    esp     ; reserve stack place

        push    esp     ; fourth parameter
        push    0       ; third parameter
        push    0       ; second parameter
        push    dword SRV     ; first parameter
        call    [getaddrinfo]

        pop     esi

        ; test for error
        test    eax, eax
        jnz     home

        mov     eax, [esi + addrinfo.ai_addr]
        mov     eax, [eax + sockaddr_in.sin_addr]
        mov     [sockaddr.ip], eax

        ; free allocated memory
        push    esi
        call    [freeaddrinfo]

; Open a socket & connect to server
        mov     [errormsg], str_err_socket

        mcall   socket, AF_INET4, SOCK_DGRAM, 0
        cmp     eax, -1
        je      home
        mov     [socketnum], eax

        mcall   connect, [socketnum], sockaddr, sockaddr_len
        cmp     eax, -1
        je      home

; Create the read/write request packet
        mov     word[buffer], opcode_rrq
        cmp     [option_group2], op3
        je      @f
        mov     word[buffer], opcode_wrq
      @@:

; Copy in the remote filename (asciiz)
        xor     al, al
        mov     edi, remote_addr
        mov     ecx, 255
        repnz   scasb
        lea     ecx, [edi - remote_addr - 1]
        mov     esi, remote_addr
        mov     edi, buffer+2
        rep     movsb
        stosb

; Append the data type (asciiz)
        cmp     [option_group1], op1
        je      .ascii
        mov     esi, octet
        movsd
        movsb
        jmp     .send_request

      .ascii:
        mov     esi, netascii
        movsd
        movsd

; Send the request to the server
      .send_request:
        xor     al, al
        stosb
        lea     esi, [edi - buffer]
        xor     edi, edi
        mcall   send, [socketnum], buffer
        cmp     eax, -1
        je      home

; Jump to send/receive code
        cmp     word[buffer], opcode_wrq
        je      tftp_send


tftp_receive:

        mcall   40, EVM_REDRAW + EVM_BUTTON + EVM_STACK
        mov     [last_ack], 0
        mov     [errormsg], 0

        call    draw_window

; Open/create local file
        mov     [file_struct.subfn], 2
        mov     [file_struct.offset], 0
        mov     [file_struct.size], 0
        mcall   70, file_struct

; Truncate it to 0 bytes
        mov     [file_struct.subfn], 4
        mcall   70, file_struct

; Set parameters for writing to file
        mov     [file_struct.subfn], 3
        mov     [file_struct.data], buffer + 4

  .loop:
        mcall   23, TIMEOUT
        dec     eax
        jz      .red
        dec     eax
        dec     eax
        jz      .button

        mcall   recv, [socketnum], buffer, buffer_len, MSG_DONTWAIT     ; receive data
        cmp     eax, -1
        je      .loop

        DEBUGF  1, "Got %u bytes\n", eax
        cmp     word[buffer], opcode_error
        je      tftp_error
        cmp     word[buffer], opcode_data
        jne     .error

; Verify ACK number
        mov     bx, word[buffer + 2]
        rol     bx, 8
        cmp     [last_ack], 0
        je      @f
        cmp     [last_ack], bx
        jne     .packet_got_lost
        inc     bx
      @@:
        mov     [last_ack], bx

; Write to the file
        lea     ecx, [eax - 4]
        mov     [file_struct.size], ecx
        mcall   70, file_struct
        add     [file_struct.offset], ecx

; Send ACK
        mov     word[buffer], opcode_ack
        mcall   send, [socketnum], buffer, 4, 0
        jmp     .loop

  .packet_got_lost:
        ;TODO
        jmp     .loop

  .red:
        call    draw_window
        jmp     .loop

  .button:
        mcall   17
        cmp     ah, 1
        jne     .abort

        mcall   close, [socketnum]
        mcall   -1

  .abort:
        mcall   close, [socketnum]
        xor     eax, eax
        mov     [socketnum], eax
        mov     [errormsg], str_abort
        jmp     home

  .error:
        mcall   close, [socketnum]
        xor     eax, eax
        mov     [socketnum], eax
        mov     [errormsg], str_err_unexp
        jmp     home

  .done:
        mcall   close, [socketnum]
        xor     eax, eax
        mov     [socketnum], eax
        mov     [errormsg], str_success
        jmp     home



tftp_send:

        mcall   40, EVM_REDRAW + EVM_BUTTON + EVM_STACK
        mov     [last_ack], 0
        mov     [errormsg], 0

        call    draw_window

        mov     [file_struct.subfn], 0
        mov     [file_struct.offset], 0
        mov     [file_struct.size], buffer_len
        mov     [file_struct.data], buffer + 4

  .next:
        mov     edi, buffer
        mov     ax, opcode_data
        stosw
        mov     ax, [last_ack]
        stosw

        mcall   70, file_struct
        test    eax, eax
;        jnz     .done
        mov     [size], ebx

  .resend:
        mov     ebx, [size]
        lea     esi, [ebx + 4]
        xor     edi, edi
        mcall   send, [socketnum], buffer

  .loop:
        mcall   23, TIMEOUT
        dec     eax
        jz      .red
        dec     eax
        dec     eax
        jz      .button

        mcall   recv, [socketnum], buffer, buffer_len, MSG_DONTWAIT
        cmp     eax, -1
        je      .loop

        cmp     word[buffer], opcode_error
        je      tftp_error
        cmp     word[buffer], opcode_ack
        jne     .error

        mov     ax, [last_ack]
        cmp     word[buffer+2], ax
        jne     .resend

        mov     eax, [size]
        cmp     eax, buffer_len
        jb      .done
        add     [file_struct.offset], eax

        inc     [last_ack]
        jmp     .next

  .red:
        call    draw_window
        jmp     .loop

  .button:
        mcall   17
        cmp     ah, 1
        jne     .abort

        mcall   close, [socketnum]
        mcall   -1

  .abort:
        mcall   close, [socketnum]
        xor     eax, eax
        mov     [socketnum], eax
        mov     [errormsg], str_abort
        jmp     home

  .error:
        mcall   close, [socketnum]
        xor     eax, eax
        mov     [socketnum], eax
        mov     [errormsg], str_err_unexp
        jmp     home

  .done:
        mcall   close, [socketnum]
        xor     eax, eax
        mov     [socketnum], eax
        mov     [errormsg], str_success
        jmp     home



tftp_error:
        mcall   close, [socketnum]
        xor     eax, eax
        mov     [socketnum], eax

        mov     ax, word[buffer+2]
        xchg    al, ah

        test    ax, ax
        jz      .0
        dec     ax
        jz      .1
        dec     ax
        jz      .2
        dec     ax
        jz      .3
        dec     ax
        jz      .4
        dec     ax
        jz      .5
        dec     ax
        jz      .6
        dec     ax
        jz      .7

  .0:
        mov     [errormsg], str_error.0
        jmp     home
  .1:
        mov     [errormsg], str_error.1
        jmp     redraw
  .2:
        mov     [errormsg], str_error.2
        jmp     home
  .3:
        mov     [errormsg], str_error.3
        jmp     home
  .4:
        mov     [errormsg], str_error.4
        jmp     home
  .5:
        mov     [errormsg], str_error.5
        jmp     home
  .6:
        mov     [errormsg], str_error.6
        jmp     home
  .7:
        mov     [errormsg], str_error.7
        jmp     home

;-------------------------
; DATA

socketnum       dd 0
kbps            dd 0
done            dd 0
errormsg        dd str_welcome

sockaddr:
                dw AF_INET4
                dw 0x4500       ; 69
  .ip           dd ?
sockaddr_len = $ - sockaddr

file_struct:
  .subfn        dd ?
  .offset       dd ?
                dd 0
  .size         dd ?
  .data         dd ?
                db 0
  .filename     dd local_addr

align 16
@IMPORT:

library box_lib         , 'box_lib.obj'         ,\
        network         , 'network.obj'

import  box_lib                                 ,\
        edit_box_draw   , 'edit_box'            ,\
        edit_box_key    , 'edit_box_key'        ,\
        edit_box_mouse  , 'edit_box_mouse'      ,\
        version_ed      , 'version_ed'          ,\
        init_checkbox   , 'init_checkbox2'      ,\
        check_box_draw  , 'check_box_draw2'     ,\
        check_box_mouse , 'check_box_mouse2'    ,\
        version_ch      , 'version_ch2'         ,\
        option_box_draw , 'option_box_draw'     ,\
        option_box_mouse, 'option_box_mouse'    ,\
        version_op      , 'version_op'

import  network                                 ,\
        inet_ntoa       , 'inet_ntoa'           ,\
        getaddrinfo     , 'getaddrinfo'         ,\
        freeaddrinfo    , 'freeaddrinfo'


edit1 edit_box 300, 80, 5, 0xffffff, 0x6f9480, 0, 0, 0, 255, SRV, mouse_dd, ed_focus, 13, 13
edit2 edit_box 300, 80, 25, 0xffffff, 0x6f9480, 0, 0, 0, 255, remote_addr, mouse_dd, 0, 4, 4
edit3 edit_box 300, 80, 45, 0xffffff, 0x6f9480, 0, 0, 0, 255, local_addr, mouse_dd, 0, 12, 12
edit4 edit_box 40, 340, 68, 0xffffff, 0x6f9480, 0, 0, 0, 5, BLK, mouse_dd, ed_figure_only, 3, 3

op1 option_box option_group1, 80, 68, 6, 12, 0xffffff, 0, 0, netascii, octet-netascii
op2 option_box option_group1, 80, 85, 6, 12, 0xFFFFFF, 0, 0, octet, get-octet

op3 option_box option_group2, 210, 68, 6, 12, 0xffffff, 0, 0, get, put-get
op4 option_box option_group2, 210, 85, 6, 12, 0xFFFFFF, 0, 0, put, BLK-put

option_group1   dd op1
option_group2   dd op3
Option_boxs1    dd op1, op2, 0
Option_boxs2    dd op3, op4, 0

str_title       db 'TFTP client', 0
str_server      db 'Server:', 0
str_source      db 'Remote file:', 0
str_destination db 'Local file:', 0
str_mode        db 'Mode:', 0
str_method      db 'Method:', 0
str_blocksize   db 'Blocksize:', 0
str_kb_s        db 'kB/s', 0
str_complete    db '% complete', 0
str_transfer    db 'Transfer', 0
str_stop        db 'Stop', 0

str_error:
.0              db 'Error.', 0                      ; not further defined error
.1              db 'File not found.', 0
.2              db 'Access violation.', 0
.3              db 'Disk full or allocation exceeded.', 0
.4              db 'Illegal TFTP operation.', 0
.5              db 'Unknown transfer ID.', 0
.6              db 'File already exists.', 0
.7              db 'No such user.', 0

str_welcome     db 'Welcome.', 0
str_err_resolve db 'Unable to resolve server address.', 0
str_err_socket  db 'Socket error.', 0
str_err_unexp   db 'Unexpected command from server.', 0
str_success     db 'Operation completed successfully.', 0
str_abort       db 'Operation aborted by user.', 0

netascii        db 'NetASCII'
octet           db 'Octet'
get             db 'GET'
put             db 'PUT'

BLK             db "512", 0, 0, 0

SRV             db "192.168.1.115", 0
                rb (SRV + 256 - $)

remote_addr     db "file", 0
                rb (remote_addr + 256 - $)

local_addr      db "/tmp0/1/file", 0
                rb (local_addr + 256 - $)

include_debug_strings

I_END:

last_ack        dw ?
size            dd ?
mouse_dd        dd ?

sc              system_colors

buffer          rb buffer_len

IM_END: