;
;    DNS Domain name -> IP lookup
;
;    Compile with FASM for Menuet
;


; If you like, you camd change the DNS server default by changing the
; IP address in the dnsServer string.


; Enabling debugging puts the received response to the
; debug board
DEBUGGING_ENABLED           equ     1
DEBUGGING_DISABLED          equ     0
DEBUGGING_STATE             equ     DEBUGGING_DISABLED


use32
 org	0x0
 db	'MENUET01'    ; header
 dd	0x01	      ; header version
 dd	START	      ; entry point
 dd	I_END	      ; image size
 dd	I_END+0x10000 ; required memory
 dd	I_END+0x10000 ; esp
 dd	0x0 , 0x0     ; I_Param , I_Path

include 'lang.inc'
include '..\..\..\macros.inc'

START:                                      ; start of execution
    mov     eax,40                          ; Report events
    mov     ebx,10000111b                   ; Stack 8 + defaults
    int     0x40

    mov     dword [prompt], p1
    mov     dword [promptlen], p1len - p1   ; 'waiting for command'

red:
    call    draw_window                     ; at first, draw the window

still:
    mov     eax,10                          ; wait here for event
    mcall

    cmp     eax,1                           ; redraw request ?
    jz      red
    cmp     eax,2                           ; key in buffer ?
    jz      key
    cmp     eax,3                           ; button in buffer ?
    jz      button

    jmp     still
key:                            ; Keys are not valid at this part of the
    mov     eax,2               ; loop. Just read it and ignore
    mcall
    jmp     still

button:                         ; button
    mov     eax,17              ; get id
    mcall

    cmp     ah,1                ; button id=1 ?
    jnz     noclose

    ; close socket before exiting
    mov     eax, 53
    mov     ebx, 1
    mov     ecx, [socketNum]
    mcall

    mov     eax,0xffffffff      ; close this program
    mcall

noclose:
    cmp     ah,3                ; Resolve address?
    jnz     noresolve

    mov     dword [prompt], p5
    mov     dword [promptlen], p5len - p5   ; display 'Resolving'
    call    draw_window

    call    translateData       ; Convert domain & DNS IP address

    call    resolveDomain

    jmp     still


noresolve:
    cmp     ah,4
    jz      f1                  ; Enter domain name
    cmp     ah,5
    jz      f2                  ; enter DNS Server IP
    jmp     still


f1:
    mov     [addr],dword query
    mov     [ya],dword 35
    jmp     rk

f2:
    mov     [addr],dword dnsServer
    mov     [ya],dword 35+16

rk:
    mov     ecx,26
    mov     edi,[addr]
    mov     al,' '
    rep     stosb

    call    print_text

    mov     edi,[addr]

f11:
    mov     eax,10
    mcall
    cmp     eax,2
    jz      fbu
    jmp     still

fbu:
    mov     eax,2
    mcall  ; get key
    shr     eax,8
    cmp     eax,8
    jnz     nobs
    cmp     edi,[addr]
    jz      f11
    sub     edi,1
    mov     [edi],byte ' '
    call    print_text
    jmp     f11

nobs:
    cmp     eax,dword 31
    jbe     f11
    cmp     eax,dword 95
    jb      keyok
    sub     eax,32

keyok:
    mov     [edi],al

    call    print_text

    add     edi,1
    mov     esi,[addr]
    add     esi,26
    cmp     esi,edi
    jnz     f11

    jmp  still



print_text:
    mov     eax,13
    mov     ebx,103*65536+26*6
    mov     ecx,[ya]
    shl     ecx,16
    mov     cx,8
    mov     edx,0x224466
    mcall

    mov     eax,4
    mov     ebx,103*65536
    add     ebx,[ya]
    mov     ecx,0xffffff
    mov     edx,[addr]
    mov     esi,26
    mcall

    ret



;***************************************************************************
;   Function
;      translateData
;
;   Description
;      Coverts the domain name and DNS IP address typed in by the user into
;      a format suitable for the IP layer.
;
;    The ename, in query, is converted and stored in dnsMsg
;      The DNS ip, in dnsServer, is converted and stored in dnsIP
;
;***************************************************************************
translateData:

    ; first, get the IP address of the DNS server
    ; Then, build up the request string.

    xor     eax, eax
    mov     dh, 10
    mov     dl, al
    mov     [dnsIP], eax

    mov     esi, dnsServer
    mov     edi, dnsIP

    mov     ecx, 4

td003:
    lodsb
    sub     al, '0'
    add     dl, al
    lodsb
    cmp     al, '.'
    je      ipNext
    cmp     al, ' '
    je      ipNext
    mov     dh, al
    sub     dh, '0'
    mov     al, 10
    mul     dl
    add     al, dh
    mov     dl, al
    lodsb
    cmp     al, '.'
    je      ipNext
    cmp     al, ' '
    je      ipNext
    mov     dh, al
    sub     dh, '0'
    mov     al, 10
    mul     dl
    add     al, dh
    mov     dl, al
    lodsb

ipNext:
    mov     [edi], dl
    inc     edi
    mov     dl, 0
    loop    td003

    ; Build the request string


    mov     eax, 0x00010100
    mov     [dnsMsg], eax
    mov     eax, 0x00000100
    mov     [dnsMsg+4], eax
    mov     eax, 0x00000000
    mov     [dnsMsg+8], eax

    ; domain name goes in at dnsMsg+12
    mov     esi, dnsMsg + 12        ; location of label length
    mov     edi, dnsMsg + 13        ; label start
    mov     edx, query
    mov     ecx, 12                  ; total string length so far

td002:
    mov     [esi], byte 0
    inc     ecx

td0021:
    mov     al, [edx]
    cmp     al, ' '
    je      td001                   ; we have finished the string translation
    cmp     al, '.'                 ; we have finished the label
    je      td004

    inc     byte [esi]
    inc     ecx
    mov     [edi], al
    inc     edi
    inc     edx
    jmp     td0021

td004:
    mov     esi, edi
    inc     edi
    inc     edx
    jmp     td002



    ; write label len + label text

td001:
    mov     [edi], byte 0
    inc     ecx
    inc     edi
    mov     [edi], dword 0x01000100
    add     ecx, 4

    mov     [dnsMsgLen], ecx

    ret





;***************************************************************************
;   Function
;      resolveDomain
;
;   Description
;       Sends a question to the dns server
;       works out the IP address from the response from the DNS server
;
;***************************************************************************
resolveDomain:
    ; Get a free port number
 mov     ecx, 1000  ; local port starting at 1000
getlp:
 inc     ecx
 push ecx
 mov     eax, 53
 mov     ebx, 9
 mcall
 pop     ecx
 cmp     eax, 0   ; is this local port in use?
 jz  getlp      ; yes - so try next

    ; First, open socket
    mov     eax, 53
    mov     ebx, 0
    mov     edx, 53    ; remote port - dns
    mov     esi, [dnsIP]
    mcall

    mov     [socketNum], eax

    ; write to socket ( request DNS lookup )
    mov     eax, 53
    mov     ebx, 4
    mov     ecx, [socketNum]
    mov     edx, [dnsMsgLen]
    mov     esi, dnsMsg
    mcall

    ; Setup the DNS response buffer

    mov     eax, dnsMsg
    mov     [dnsMsgLen], eax

    ; now, we wait for
    ; UI redraw
    ; UI close
    ; or data from remote

ctr001:
    mov     eax,10                 ; wait here for event
    mcall

    cmp     eax,1                  ; redraw request ?
    je      ctr003
    cmp     eax,2                  ; key in buffer ?
    je      ctr004
    cmp     eax,3                  ; button in buffer ?
    je      ctr005


    ; Any data in the UDP receive buffer?
    mov     eax, 53
    mov     ebx, 2
    mov     ecx, [socketNum]
    mcall

    cmp     eax, 0
    je      ctr001

    ; we have data - this will be the response
ctr002:
    mov     eax, 53
    mov     ebx, 3
    mov     ecx, [socketNum]
    mcall                ; read byte - block (high byte)

    ; Store the data in the response buffer
    mov     eax, [dnsMsgLen]
    mov     [eax], bl
    inc     dword [dnsMsgLen]


if DEBUGGING_STATE = DEBUGGING_ENABLED
    call debug_print_rx_ip
end if

    mov     eax, 53
    mov     ebx, 2
    mov     ecx, [socketNum]
    mcall                ; any more data?

    cmp     eax, 0
    jne     ctr002              ; yes, so get it

    ; close socket
    mov     eax, 53
    mov     ebx, 1
    mov     ecx, [socketNum]
    mcall

    mov     [socketNum], dword 0xFFFF

    ; Now parse the message to get the host IP
    ; Man, this is complicated. It's described in
    ; RFC 1035

    ; 1) Validate that we have an answer with > 0 responses
    ; 2) Find the answer record with TYPE 0001 ( host IP )
    ; 3) Finally, copy the IP address to the display
    ; Note: The response is in dnsMsg
    ;       The end of the buffer is pointed to by [dnsMsgLen]

    ; Clear the IP address text
    mov     [hostIP], dword 0

    mov     esi, dnsMsg

    ; Is this a response to my question?
    mov     al, [esi+2]
    and     al, 0x80
    cmp     al, 0x80
    jne     ctr002a

    ; Were there any errors?
    mov     al, [esi+3]
    and     al, 0x0F
    cmp     al, 0x00
    jne     ctr002a

    ; Is there ( at least 1 ) answer?
    mov     ax, [esi+6]
    cmp     ax, 0x00
    je      ctr002a

    ; Header validated. Scan through and get my answer

    add     esi, 12             ; Skip to the question field

    ; Skip through the question field
    call    skipName
    add     esi, 4              ; skip past the questions qtype, qclass

ctr002z:
    ; Now at the answer. There may be several answers,
    ; find the right one ( TYPE = 0x0001 )
    call    skipName
    mov     ax, [esi]
    cmp     ax, 0x0100          ; Is this the IP address answer?
    jne     ctr002c

    ; Yes! Point esi to the first byte of the IP address
    add     esi, 10

    mov     eax, [esi]
    mov     [hostIP], eax
    jmp     ctr002a             ; And exit...


ctr002c:                        ; Skip through the answer, move to the next
    add     esi, 8
    movzx   eax, byte [esi+1]
    mov     ah, [esi]
    add     esi, eax
    add     esi, 2

    ; Have we reached the end of the msg?
    ; This is an error condition, should not happen
    cmp     esi, [dnsMsgLen]
    jl      ctr002z             ; Check next answer
    jmp     ctr002a             ; abort


ctr002a:
    mov     dword [prompt], p4  ; Display IP address
    mov     dword [promptlen], p4len - p4
    call    draw_window

    jmp     ctr001

ctr003:                         ; redraw
    call    draw_window
    jmp     ctr001

ctr004:                         ; key
    mov     eax,2               ; just read it and ignore
    mcall
    jmp     ctr001

ctr005:                         ; button
    mov     eax,17              ; get id
    mcall

    ; close socket
    mov     eax, 53
    mov     ebx, 1
    mov     ecx, [socketNum]
    mcall

    mov     [socketNum], dword 0xFFFF
    mov     [hostIP], dword 0

    mov     dword [prompt], p1
    mov     dword [promptlen], p1len - p1   ; 'waiting for command'

    call    draw_window                     ; at first, draw the window

    ret



;***************************************************************************
;   Function
;      skipName
;
;   Description
;       Increment esi to the first byte past the name field
;       Names may use compressed labels. Normally do.
;       RFC 1035 page 30 gives details
;
;***************************************************************************
skipName:
    mov     al, [esi]
    cmp     al, 0
    je      sn_exit
    and     al, 0xc0
    cmp     al, 0xc0
    je      sn001

    movzx   eax, byte [esi]
    inc     eax
    add     esi, eax
    jmp     skipName

sn001:
    add     esi, 2                          ; A pointer is always at the end
    ret

sn_exit:
    inc     esi
    ret


;   *********************************************
;   *******  WINDOW DEFINITIONS AND DRAW ********
;   *********************************************


draw_window:
    mov     eax,12                    ; function 12:tell os about windowdraw
    mov     ebx,1                     ; 1, start of draw
    mcall
                                      ; DRAW WINDOW
    mov     eax,0                     ; function 0 : define and draw window
    mov     ebx,100*65536+300         ; [x start] *65536 + [x size]
    mov     ecx,100*65536+140         ; [y start] *65536 + [y size]
    mov     edx,0x13224466            ; color of work area RRGGBB
    mov     edi,title                 ; WINDOW LABEL;
    mcall
                                      
    mov     eax,8                     ; Resolve
    mov     ebx,20*65536+190
    mov     ecx,79*65536+15
    mov     edx,3
    mov     esi,0x557799
    mcall

    ;mov     eax,8
    mov     ebx,270*65536+10
    mov     ecx,34*65536+10
    inc     edx
    mcall

    ;mov     eax,8
    mov     ebx,270*65536+10
    mov     ecx,50*65536+10
    inc     edx
    mcall

    ; Copy the file name to the screen buffer
    ; file name is same length as IP address, to
    ; make the math easier later.
    cld
    mov     esi,query
    mov     edi,text+13
    mov     ecx,26
    rep     movsb


    ; copy the IP address to the screen buffer
    mov     esi,dnsServer
    mov     edi,text+40+13
    mov     ecx,26
    rep     movsb

    ; copy the prompt to the screen buffer
    mov     esi,[prompt]
    mov     edi,text+200
    mov     ecx,[promptlen]
    rep     movsb

    ; Re-draw the screen text
    cld
    mov     eax,4
    mov     ebx,25*65536+35           ; draw info text with function 4
    mov     ecx,0xffffff
    mov     edx,text
    mov     esi,40
newline:
    mcall
    add     ebx,16
    add     edx,40
    cmp     [edx],byte 'x'
    jnz     newline


    ; Write the host IP, if we have one
    mov     eax, [hostIP]
    cmp     eax, 0
    je      dw001

    ; We have an IP address... display it
    mov     edi,hostIP
    mov     edx,97*65536+115
    mov     esi,0x00ffffff
    mov     ebx,3*65536

ipdisplay:
    mov     eax,47
    movzx   ecx,byte [edi]
    mcall
    add     edx,6*4*65536
    inc     edi
    cmp     edi,hostIP+4
    jb      ipdisplay

dw001:
    mov     eax,12                    ; function 12:tell os about windowdraw
    mov     ebx,2                     ; 2, end of draw
    mcall

    ret


if DEBUGGING_STATE = DEBUGGING_ENABLED
;****************************************************************************
;    Function
;       debug_print_string
;
;   Description
;       prints a string to the debug board
;
;       esi holds ptr to msg to display
;
;       Nothing preserved; I'm assuming a pusha/popa is done before calling
;
;****************************************************************************
debug_print_string:
    mov     cl, [esi]
    cmp     cl, 0
    jnz     dps_001
    ret

dps_001:
    mov     eax,63
    mov     ebx, 1
    push    esi
    mcall

    inc   word [ind]
    mov  ax, [ind]
    and ax, 0x1f
    cmp  ax, 0
    jne  ds1

    mov   cl, 13
    mov     eax,63
    mov     ebx, 1
    mcall
    mov   cl, 10
    mov     eax,63
    mov     ebx, 1
    mcall


ds1:
    pop     esi
    inc     esi
    jmp     debug_print_string


ind: dw 0
; This is used for translating hex to ASCII for display or output
hexchars db '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'
IP_STR              db  'xx',0


debug_print_rx_ip:
    pusha
    mov     edi, IP_STR

    xor     eax, eax
    mov     al, bl
    shr     al, 4
    mov     ah, [eax + hexchars]
    mov     [edi], ah
    inc     edi

    xor     eax, eax
    mov     al, bl
    and     al, 0x0f
    mov     ah, [eax + hexchars]
    mov     [edi], ah
    mov     esi, IP_STR

    call    debug_print_string
    popa
    ret
end if


; DATA AREA

addr            dd  0x0
ya              dd  0x0

text:
    db 'Host name  : xxxxxxxxxxxxxxx            '
    db 'DNS server : xxx.xxx.xxx.xxx            '
    db '                                        '
    db '     RESOLVE ADDRESS                    '
    db '                                        '
    db '                                        '
    db 'x <- END MARKER, DONT DELETE            '


title    db   'DNS Client',0


prompt: dd 0
promptlen: dd 0


p1:             db 'Waiting for Command        '
p1len:

p4:             db 'IP Address:    .   .   .   '
p4len:

p5:             db 'Resolving...               '
p5len:


dnsServer       db  '194.145.128.1              ' ; iolfree.ie DNS
query           db  'WWW.MENUETOS.NET           '

hostIP:         dd 0
dnsIP:          dd 0
dnsMsgLen:      dd 0
socketNum:      dd 0xFFFF
dnsMsg:
I_END:
