; _______________________________________________________________________________________
;|                                                                                       |
;| ..::FreshLib::..  Free, open source. Licensed under "BSD 2-clause" license."          |
;|_______________________________________________________________________________________|
;
;  Description: Network library.
;
;  Target OS: Any
;
;  Dependencies:
;
;  Notes:
;_________________________________________________________________________________________

module "Network library"


struct TSocketAddress
  .saFamily  dw ?
  .saAddress rb 14
ends


struct TSocketAddressIn
  .saFamily  dw ?
  .saPort    dw ?
  .saAddress dd ?
  .saZero    rb 8
ends


struct TAddrIn6
  label .addr8 byte
  label .addr16 word
  .addr32 rd 4
ends


struct TSocketAddressIn6
  .saFamily   dw ?
  .saPort     dw ?
  .saFlowInfo dd ?
  .saAddress  TAddrIn6
  .saScopeID  dd ?
ends



struct TSocketAddressUn
  .saFamily dw ?
  .saPath   rb UNIX_PATH_MAX
ends



soReuseAddr = SO_REUSEADDR
soDontRoute = SO_DONTROUTE
soRecvBuffer = SO_RCVBUF
soSendBuffer = SO_SNDBUF
soRecvTimeout = SO_RCVTIMEO     ; timeout in [ms] for all OSes
soSendTimeout = SO_SNDTIMEO     ; timeout in [ms] for all OSes
soLinger      = SO_LINGER       ; timout in [s]



interface SocketCreate, .protocol_family, .socket_type, .protocol

interface SocketClose, .hSocket

interface SocketConnect, .hSocket, .pAddress

interface SocketBind, .hSocket, .pAddress

interface SocketListen, .hSocket, .maxPending

interface SocketAccept, .hSocket, .pAddress

interface SocketSend, .hSocket, .pBuffer, .DataLen, .flags

interface SocketReceive, .hSocket, .pBuffer, .BufferSize, .flags

interface SocketSendTo, .hSocket, .pBuffer, .DataLen, .flags, .pAddressTo

interface SocketReceiveFrom, .hSocket, .pBuffer, .BufferSize, .flags, .pAddressFrom

interface SocketGetOption, .hSocket, .idOption

interface SocketSetOption, .hSocket, .idOption, .Value




include "%TargetOS%/network.asm"



proc SocketSendStr, .hSocket, .hString
begin
        pushad

        stdcall StrLen, [.hString]
        push    eax

        stdcall StrPtr, [.hString]
        stdcall SocketSendAll, [.hSocket], eax     ; remaining arguments from the stack.

        popad
        return
endp




proc SocketSendAll, .hSocket, .pData, .Size
begin
        pushad

;        stdcall GetTimestamp
;        mov     edi, eax

        mov     esi, [.pData]
        mov     ecx, [.Size]
        jecxz   .finish_ok

.loop:
        stdcall SocketSend, [.hSocket], esi, ecx, 0
        jc      .finish_err

;        OutputValue "Send bytes:", eax, 10, -1

        add     esi, eax
        sub     ecx, eax
        jnz     .loop

.finish_ok:

;        stdcall GetTimestamp
;        sub     eax, edi
;
;        OutputValue "SendAll time: ", eax, 10, -1

        clc
        popad
        return

.finish_err:
        mov     [esp+4*regEAX], eax
        popad
        return
endp




struct __s_readln
  .capacity = 1024
  .buffer rb .capacity
  .len    dd ?   ; the count of valid bytes in the buffer.
ends


; Reads a line from the socket and concatenate it to the provided
; string hString.
;
; If there is no data for a whole line, and the timeout expires,
; returns what has been read.
;
; All possible combinations of CR/LF are recognized as a line end.
; After end of line is detected, the remaining data read is returned
; in the buffer and the same pointer should be passed the next time
; the function is call.
;
; Arguments:
;   .hSocket - the socket to be read;
;   .pBuffer - the buffer, returned from the previous call to the procedure.
;              should be NULL on the first call.
;   .timeout - timeout in ms for detection of end-of-transmission.
;
; Returns:
;   eax - the string with the line.
;   edx - pointer to the buffer that to be passed to the procedure next time.
;         can be NULL, if there is no more data to be processed. The buffer is
;         allocated on the first call to the procedure. Should be free with FreeMem
;         when not needed.
;  CF = 1 on socket read error. In this case, ECX returns the error code.
;


proc SocketReadLine, .hSocket, .pBuffer, .timeout
begin
        pushad

        xor     edx, edx
        mov     [esp+4*regEAX], edx

        stdcall SocketSetOption, [.hSocket], soRecvTimeout, [.timeout]

        stdcall StrNew
        mov     ebx, eax

        mov     edi, [.pBuffer]
        test    edi, edi
        jnz     .scan_buffer

        stdcall GetMem, sizeof.__s_readln
        mov     edi, eax
        mov     [edi+__s_readln.len], eax

.read_loop:
        stdcall SocketReceive, [.hSocket], edi, __s_readln.capacity, 0
        jc      .socket_error

        mov     [edi+__s_readln.len], eax
        test    eax, eax
        jz      .free_buffer      ; the socket has been closed from the remote side and the buffer is empty.


.scan_buffer:
        xor     ecx, ecx

        mov     eax, edx
        test    eax, eax
        jnz     .eol_ok

.inner_loop:
        mov     al, [edi+ecx]
        inc     ecx

        cmp     al, $0d
        je      .end_of_line
        cmp     al, $0a
        je      .end_of_line

        cmp     ecx, [edi+__s_readln.len]
        jne     .inner_loop

;end of buffer

        inc     ecx
        xor     eax, eax

.end_of_line:
        dec     ecx
        jz      .cat_ok

        stdcall StrCatMem, ebx, edi, ecx

.cat_ok:
        mov     [esp+4*regEAX], ebx

        inc     ecx
        cmp     ecx, [edi+__s_readln.len]
        jb      .eol_ok

        mov     edx, eax
        jmp     .read_loop

.eol_ok:
        xor     al, $0d xor $0a
        cmp     [edi+ecx], al
        jne     .line_ok

        inc     ecx

.line_ok:

; copy the buffer to the beginning.

        lea     esi, [edi+ecx]

        sub     ecx, [edi+__s_readln.len]
        jnz     .copy_buffer

.free_buffer:

        stdcall FreeMem, edi
        xor     edi, edi
        jmp     .finish


.copy_buffer:

        neg     ecx
        mov     [edi+__s_readln.len], ecx

        cmp     esi, edi
        je      .finish

        push    edi
        rep movsb
        pop     edi

.finish:
        clc

.err:
        pushf
        cmp     dword [esp+4*regEAX+4], 0
        jne     .str_ok

        stdcall StrDel, ebx

.str_ok:
        popf
        mov     [esp+4*regEDX], edi
        popad
        return


.socket_error:

        cmp     eax, serrTimeout
        je      .free_buffer

        mov     [esp+4*regECX], eax

        stc
        jmp     .err


endp




proc SocketReadAllLines, .hSocket, .timeout
.pbuffer dd ?
.result  dd ?
.eol     dd ?
begin
        pushad

        stdcall SocketSetOption, [.hSocket], soRecvTimeout, [.timeout]

        stdcall CreateArray, 4
        mov     edx, eax

        stdcall GetMem, 1028
        mov     [.pbuffer], eax

        stdcall StrNew
        mov     [.result], eax

        xor     eax, eax
        mov     [.eol], eax

.read_loop:
        mov     edi, [.pbuffer]
        stdcall SocketReceive, [.hSocket], edi, 1024, 0
        jnc     .no_error

        cmp     eax, EAGAIN
        jne     .error_receive
        jmp     .finalize_ok

.no_error:
        OutputValue "Socket receive:", eax, 10, -1

        test    eax, eax
        jz      .finalize_ok

        mov     esi, edi
        lea     ebx, [edi+eax]  ; the end of the buffer.

        mov     eax, [.eol]
        test    eax, eax
        jnz     .eol_ok

.inner_loop:
        cmp     esi, ebx
        je      .end_of_buffer

        lodsb

        cmp     al, $0d
        je      .end_of_line
        cmp     al, $0a
        je      .end_of_line

        jmp     .inner_loop


.end_of_buffer:
        mov     eax, esi
        sub     eax, edi
        stdcall StrCatMem, [.result], edi, eax

        jmp     .read_loop


.end_of_line:
        push    eax

        lea     eax, [esi-1]
        sub     eax, edi
        stdcall StrCatMem, [.result], edi, eax

        stdcall AddArrayItems, edx, 1
        pushd   [.result]
        popd    [eax]

        stdcall StrNew
        mov     [.result], eax

        pop     eax
        cmp     esi, ebx
        jne     .eol_ok

        mov     [.eol], eax
        jmp     .read_loop


.eol_ok:
        xor     al, $0d xor $0a
        cmp     [esi], al
        jne     .line_ok

        inc     esi

.line_ok:
        xor     eax, eax
        mov     [.eol], eax
        mov     edi, esi        ; move the start of the string.
        jmp     .inner_loop

.finalize_ok:

        clc

.finalize:
        pushf

        stdcall StrLen, [.result]
        test    eax, eax
        jnz     .add_last

        stdcall StrDel, [.result]
        jmp     .finish

.add_last:
        stdcall AddArrayItems, edx, 1
        pushd   [.result]
        popd    [eax]

.finish:
        stdcall FreeMem, [.pbuffer]

        popf
        mov     [esp+4*regEAX], edx

        popad
        return


.error_receive:

        DebugMsg "Error receive"

        stdcall GetErrorString, eax
        mov     ebx, eax

        stdcall FileWriteString, [STDERR], eax
        stdcall StrDel, ebx

        stc
        jmp     .finalize

endp








endmodule