GENERAL_LIMIT_MAX_POST_LENGTH = 1024*1024   ; 1MB general limit on post data length. Everything above will be trunkated.

; The types for FCGI_Header.type

FCGI_BEGIN_REQUEST      =  1
FCGI_ABORT_REQUEST      =  2
FCGI_END_REQUEST        =  3
FCGI_PARAMS             =  4
FCGI_STDIN              =  5
FCGI_STDOUT             =  6
FCGI_STDERR             =  7
FCGI_DATA               =  8
FCGI_GET_VALUES         =  9
FCGI_GET_VALUES_RESULT  = 10
FCGI_UNKNOWN_TYPE       = 11
FCGI_MAXTYPE            = FCGI_UNKNOWN_TYPE


struct FCGI_Header
  .version              db ?
  .type                 db ?

  .requestIdB1          db ?
  .requestIdB0          db ?
     label .requestId word at .requestIdB1

  .contentLengthB1      db ?
  .contentLengthB0      db ?
     label .contentLength word at .contentLengthB1

  .paddingLength        db ?
  .reserved             db ?
ends



; Values for role component of FCGI_BeginRequestBody

FCGI_RESPONDER   = 1
FCGI_AUTHORIZER  = 2
FCGI_FILTER      = 3


; Mask for flags component of FCGI_BeginRequestBody

FCGI_KEEP_CONN   = 1


struct FCGI_BeginRequestBody
  .roleB1   db ?
  .roleB0   db ?
     label .role word at .roleB1

  .flags    db ?
  .reserved rb 5
ends


struct FCGI_BeginRequest
  .header FCGI_Header
  .body   FCGI_BeginRequestBody
ends



; Values for protocolStatus component of FCGI_EndRequestBody

FCGI_REQUEST_COMPLETE   =  0
FCGI_CANT_MPX_CONN      =  1
FCGI_OVERLOADED         =  2
FCGI_UNKNOWN_ROLE       =  3



struct FCGI_EndRequestBody
  .appStatusB3          db ?
  .appStatusB2          db ?
  .appStatusB1          db ?
  .appStatusB0          db ?
  .protocolStatus       db ?
  .reserved             rb 3
ends


struct FCGI_EndRequest
  .header FCGI_Header
  .body   FCGI_EndRequestBody
ends



struct FCGI_UnknownTypeBody
  .type         db  ?          ; the unknown type that can't be processed.
  .reserved     rb  7
ends


struct FCGI_UnknownType
  .header  FCGI_Header
  .body    FCGI_UnknownTypeBody
ends





struct FCGI_NameValuePair11
  .nameLengthB0   db ?
  .valueLengthB0  db ?
  .data:
ends



struct FCGI_NameValuePair14
  .nameLengthB0   db ?
  .valueLengthB3  db ?
  .valueLengthB2  db ?
  .valueLengthB1  db ?
  .valueLengthB0  db ?
  .data:
ends


struct FCGI_NameValuePair41
  .nameLengthB3   db ?
  .nameLengthB2   db ?
  .nameLengthB1   db ?
  .nameLengthB0   db ?
  .valueLengthB0  db ?
  .data:
ends


struct FCGI_NameValuePair44
  .nameLengthB3   db ?
  .nameLengthB2   db ?
  .nameLengthB1   db ?
  .nameLengthB0   db ?
  .valueLengthB3  db ?
  .valueLengthB2  db ?
  .valueLengthB1  db ?
  .valueLengthB0  db ?
  .data:
ends


;
; The main FastCGI listening loop.
;
; On accepting connection, starts a new thread in the procedure procServeRequest
; and continues to listen for new connections.
;

proc Listen
.addr TSocketAddressUn
begin

.loop:

        stdcall SocketAccept, [STDIN], 0
        jc      .make_socket

        stdcall ThreadCreate, procServeRequest, eax

        jmp     .loop

.make_socket:

        cmp     [fOwnSocket], 0
        jne     .finish

        stdcall SocketCreate, PF_UNIX, SOCK_STREAM, 0
        jc      .finish

        mov     [STDIN], eax
        mov     [fOwnSocket], 1

        stdcall SocketSetOption, [STDIN], soReuseAddr, TRUE
        stdcall SocketSetOption, [STDIN], soLinger, 5

        mov     [.addr.saFamily], AF_UNIX

        mov     esi, pathMySocket
        mov     ecx, pathMySocket.length + 1
        lea     edi, [.addr.saPath]

        rep movsb

        lea     eax, [.addr]
        stdcall SocketBind, [STDIN], eax
        jc      .finish

        stdcall SocketListen, [STDIN], 1
        jnc     .loop


.finish:
        return
endp


pathMySocket text "./engine.sock"


;create table Log (
;  process_id integer,   -- the unique process id
;  timestamp  integer,
;  event      text       -- what event is logged - start process, end process, start request, end request
;  value      text,      -- details in variable form.
;  runtime    integer
;);
; GOOD report queries:
;
; select process_id, strftime('%d.%m.%Y %H:%M:%S', timestamp, 'unixepoch') as `Time`, E.name, value from log L left join Events E on event = E.id order by timestamp desc;
;
; select strftime('%d.%m.%Y %H:%M:%S', timestamp, 'unixepoch') as `Time`, E.name, value from log L left join Events E on L.event = E.id where L.event = 3 order by L.timestamp, L.rowID desc;
;
;
; -- Gives the 10 slower requests, value in [ms]:
;
; select E.name || " : " || value, cast(L.runtime as float)/1000 from log L left join Events E on L.event = E.id where L.event = 3 order by runtime desc limit 10;
;
;
; -- Shows the process start and end events:
;
; select strftime('%d.%m.%Y %H:%M:%S', L.timestamp, 'unixepoch') as Time, process_id, E.name, L.value, L.runtime from log L left join Events E on (L.event = E.id) where E.name in ("ScriptStart", "ScriptEnd") order by L.rowID;
;
;

sqlLogEvent text "insert into Log (process_id, timestamp, event, value, runtime) values (?1, strftime('%s','now'), ?2, ?3, ?4 )"
sqlCleanLog text "delete from Log where timestamp < strftime('%s','now') - 86400"


logNULL   = 0
logNumber = 1
logText   = 2


proc LogEvent, .event, .log_type, .value, .runtime
.stmt dd ?
begin
        pushad

        cmp     [fLogEvents], 0
        je      .finish

        DebugMsg "Event will be logged!"

        lea     eax, [.stmt]
        cinvoke sqlitePrepare_v2, [hMainDatabase], sqlLogEvent, sqlLogEvent.length, eax, 0

        cinvoke sqliteBindInt, [.stmt], 1, [ProcessID]

        cmp     [.event], 0
        je      .event_ok

        stdcall StrLen, [.event]
        mov     ecx, eax
        stdcall StrPtr, [.event]
        cinvoke sqliteBindText, [.stmt], 2, eax, ecx, SQLITE_STATIC

.event_ok:
        cmp     [.log_type], logNULL
        je      .value_ok

        cmp     [.log_type], logNumber
        je      .value_number

        cmp     [.log_type], logText
        je      .value_text

        jmp     .value_ok

.value_number:
        cinvoke sqliteBindInt, [.stmt], 3, [.value]
        jmp     .value_ok

.value_text:
        stdcall StrLen, [.value]
        mov     ecx, eax
        stdcall StrPtr, [.value]
        cinvoke sqliteBindText, [.stmt], 3, eax, ecx, SQLITE_STATIC

.value_ok:
        cmp     [.runtime], 0
        je      .runtime_ok

        cinvoke sqliteBindInt, [.stmt], 4, [.runtime]

.runtime_ok:
        cinvoke sqliteStep, [.stmt]
        cinvoke sqliteFinalize, [.stmt]

        cinvoke sqliteExec, [hMainDatabase], sqlCleanLog, 0, 0, 0

.finish:
        popad
        return
endp




uglobal
  UniqueID   dd ?
  UniqueLock dd ?
endg



proc GetUniqueID
begin
        xor     eax, eax
        dec     eax

.loop:
        xchg    eax, [UniqueLock]
        test    eax, eax
        jz      .locked

        stdcall Sleep, 1
        jmp     .loop


.locked:
        mov     eax, [UniqueID]
        inc     [UniqueID]

        mov     [UniqueLock], 0
        return
endp







; One connection serving thread procedure.


ffcgiExpectStdIn  = $80000000
ffcgiExpectParams = $40000000


proc procServeRequest, .hSocket

.requestID      dd ?
.requestFlags   dd ?
.requestParams  dd ?
.requestPost    dd ?    ; pointer to TByteStream

.start_time     dd ?

.threadID       dd ?
.thread_start   dd ?

begin
        xor     eax, eax
        mov     [.requestParams], eax
        mov     [.requestPost], eax
        xor     esi, esi

        stdcall GetParam, "log_events", gpInteger
        mov     [fLogEvents], eax

        test    eax, eax
        jz      .main_loop

; only when the logging is ON

        stdcall GetUniqueID
        mov     [.threadID], eax

        stdcall GetFineTimestamp
        mov     [.thread_start], eax

        stdcall LogEvent, "ThreadStart", logNumber, [.threadID], 0


.main_loop:

        call    .FreeAllocations

.pack_loop:
        stdcall FreeMem, esi
        xor     esi, esi

        stdcall FCGI_read_pack, [.hSocket]
        jc      .finish

        mov     esi, eax
        movzx   eax, [esi+FCGI_Header.type]

        cmp     eax, FCGI_BEGIN_REQUEST
        je      .begin_request

        cmp     eax, FCGI_PARAMS
        je      .get_params

        cmp     eax, FCGI_STDIN
        je      .get_stdin

        cmp     eax, FCGI_ABORT_REQUEST
        je      .request_complete

; send back unknown type record.

        xor     edx, edx
        mov     dx, [esi+FCGI_Header.requestId]
        xchg    dl, dh

        stdcall FCGI_send_unknown_type, [.hSocket], edx, eax

        jmp     .pack_loop


; Processing of FCGI_BEGIN_REQUEST

.begin_request:

        xor     eax, eax
        xor     edx, edx
        mov     ax, [esi+FCGI_BeginRequest.header.requestId]
        mov     dx, [esi+FCGI_BeginRequest.body.role]
        xchg    al, ah
        xchg    dl, dh

        cmp     dx, FCGI_RESPONDER
        jne     .unknown_role

        cmp     [.requestID], 0
        jne     .mx_disabled

        mov     [.requestID], eax
        or      [.requestFlags], ffcgiExpectParams

        movzx   ecx, [esi+FCGI_BeginRequest.body.flags]
        or      [.requestFlags], ecx

        stdcall LogEvent, "RequestStart", logNumber, [.threadID], 0

        stdcall GetFineTimestamp
        mov     [.start_time], eax

        jmp     .pack_loop


.unknown_role:

        stdcall FCGI_send_end_request, [.hSocket], eax, FCGI_UNKNOWN_ROLE
        jmp     .pack_loop


.mx_disabled:

        stdcall FCGI_send_end_request, [.hSocket], eax, FCGI_CANT_MPX_CONN
        jmp     .pack_loop


; Processing of FCGI_PARAMS


.get_params:

        xor     eax, eax
        mov     ax, [esi+FCGI_Header.requestId]
        xchg    al, ah

        cmp     eax, [.requestID]
        jne     .mx_disabled

        xor     edx, edx
        mov     dx, [esi+FCGI_Header.contentLength]
        xchg    dl, dh

        test    edx, edx
        jz      .param_received          ; this is the last part of FCGI_PARAMS stream, so go to serve the request.

; add the package to the name/value pairs list.

        lea     edi, [esi+sizeof.FCGI_Header]

        stdcall FCGI_Decode_name_value_pairs, [.requestParams], edi, edx
        mov     [.requestParams], eax

        jmp     .pack_loop


.param_received:
        and     [.requestFlags], not ffcgiExpectParams

        test    [.requestFlags], ffcgiExpectStdIn
        jnz     .pack_loop

        stdcall ValueByName, [.requestParams], "REQUEST_METHOD"
        jc      .serve_request                                          ; not found method

        stdcall StrCompNoCase, eax, txt "POST"
        jnc     .serve_request                          ; it is not the POST request, so no need to wait for more data.

; some post data is expected.

        or      [.requestFlags], ffcgiExpectStdIn
        jmp     .pack_loop



; Processing FCGI_STDIN data stream on the post requests.

.get_stdin:

        xor     eax, eax
        mov     ax, [esi+FCGI_Header.requestId]
        xchg    al, ah

        cmp     eax, [.requestID]
        jne     .mx_disabled

        xor     ecx, ecx
        mov     cx, [esi+FCGI_Header.contentLength]
        xchg    cl, ch

        test    ecx, ecx
        jz      .stdin_received           ; no more packages to wait for FCGI_STDIN

; add the package to the post data byte stream.

        push    esi

        cmp     [.requestPost], 0
        jne     .bytes_ok

        stdcall BytesCreate, 1024
        mov     [.requestPost], eax

.bytes_ok:

        lea     esi, [esi+sizeof.FCGI_Header]

        stdcall BytesGetRoom, [.requestPost], ecx
        mov     [.requestPost], ebx

        rep movsb
        xor     eax, eax
        stosd

        cmp     [ebx+TByteStream.size], GENERAL_LIMIT_MAX_POST_LENGTH
        ja      .stdin_received

        pop     esi
        jmp     .pack_loop


.stdin_received:

        and     [.requestFlags], not ffcgiExpectStdIn

        test    [.requestFlags], ffcgiExpectParams
        jz      .serve_request

        jmp     .pack_loop


; Processing of the request. Here all data is ready, so serve the request!

.serve_request:

        cmp     [fLogEvents], 0
        je      .log_serve_ok

        stdcall ValueByName, [.requestParams], "REQUEST_URI"
        stdcall LogEvent, "RequestServeStart", logText, eax, 0

.log_serve_ok:

        ; SERVE THE REQUEST HERE
        stdcall ServeOneRequest, [.hSocket], [.requestID], [.requestParams], [.requestPost], [.start_time]

        stdcall LogEvent, "RequestServeEnd", logNULL, 0, 0

.request_complete:

        stdcall FCGI_send_end_request, [.hSocket], [.requestID], FCGI_REQUEST_COMPLETE
        jc      .finish

        cmp     [fLogEvents], 0
        je      .log_req_end_ok

        stdcall GetFineTimestamp
        sub     eax, [.start_time]
        stdcall LogEvent, "RequestEnd", logNumber, [.threadID], eax

.log_req_end_ok:

        test    [.requestFlags], FCGI_KEEP_CONN
        jz      .finish

        DebugMsg "Keep connection"

        jmp     .main_loop


.finish:
        DebugMsg "Don't keep connection"

        stdcall FreeMem, esi
        call    .FreeAllocations

        stdcall SocketClose, [.hSocket]

        cmp     [fLogEvents], 0
        je      .log_thread_end_ok

        stdcall GetFineTimestamp
        sub     eax, [.thread_start]
        stdcall LogEvent, "ThreadEnd", logNumber, [.threadID], eax

.log_thread_end_ok:

        stdcall Terminate, 0

;...............................................................


.FreeAllocations:
        xor     eax, eax

        cmp     [.requestParams], eax
        je      .params_ok

        stdcall FreeNameValueArray, [.requestParams]
        mov     [.requestParams], eax

.params_ok:
        cmp     [.requestPost], eax
        je      .post_ok

        stdcall FreeMem, [.requestPost]
        mov     [.requestPost], eax

.post_ok:
        mov     [.requestFlags], eax
        mov     [.requestID], eax

        retn


endp







;_______________________________________________________________________________________________
;
; proc FCGI_output
;
; Outputs data to the FCGI_STDOUT stream. The data can be split among multiply FCGI records
; with maximal size of 65535 bytes.
;
; Arguments:
;
;  .hSocket   - The socket where the data will be streamed.
;  .RequestID - The ID of the request the data belongs to.
;  .pData     - Pointer to the data buffer.
;  .Size      - The size of the data. 0 is valid size and causes the stream to be closed.
;  .final     - boolean flag, specifying whether this is the final block of the stream.
;
; Returns:
;  CF = 0 The transmission completed successfuly.
;  CF = 1 Some error occured.
;
;_______________________________________________________________________________________________

proc FCGI_output, .hSocket, .RequestID, .pData, .Size, .final

.header FCGI_Header
.buffer rb 16

begin
        pushad

        xor     eax, eax
        lea     edi, [.header]
        mov     ecx, ( sizeof.FCGI_Header + 16 ) / 4
        rep stosd


        mov     [.header.version], 1
        mov     [.header.type], FCGI_STDOUT

        mov     eax, [.RequestID]
        mov     [.header.requestIdB1], ah
        mov     [.header.requestIdB0], al

        mov     edx, [.Size]
        mov     esi, [.pData]


.data_loop:
        mov     ecx, $ffff
        cmp     edx, ecx
        cmovb   ecx, edx                        ; ecx = min($ffff, edx)

        lea     ebx, [ecx+7]
        and     ebx, $fffffff8
        sub     ebx, ecx                        ; ebx = padding bytes count.

        mov     [.header.contentLengthB1], ch
        mov     [.header.contentLengthB0], cl
        mov     [.header.paddingLength], bl

        mov     eax, [.final]
        or      eax, ecx
        jz      .end_ok         ; exit without finalizing the stream.

        lea     eax, [.header]
        stdcall SocketSendAll, [.hSocket], eax, sizeof.FCGI_Header
        jc      .finish

        test    edx, edx
        jz      .end_ok

        stdcall SocketSendAll, [.hSocket], esi, ecx
        jc      .finish

        test    ebx, ebx
        jz      .padding_ok

        lea     eax, [.buffer]
        stdcall SocketSendAll, [.hSocket], eax, ebx   ; send 0 as a padding bytes.
        jc      .finish

.padding_ok:
        add     esi, ecx
        sub     edx, ecx
        jmp     .data_loop

.end_ok:
        clc

.finish:
        popad
        return
endp




proc FCGI_send_end_request, .hSocket, .RequestID, .status

.rec    FCGI_EndRequest

begin
        pushad

        xor     eax, eax
        lea     edi, [.rec]
        mov     ecx, sizeof.FCGI_EndRequest / 4
        rep stosd

        mov     [.rec.header.version], 1
        mov     [.rec.header.type], FCGI_END_REQUEST

        mov     eax, [.RequestID]
        mov     [.rec.header.requestIdB1], ah
        mov     [.rec.header.requestIdB0], al

        mov     [.rec.header.contentLengthB0], sizeof.FCGI_EndRequestBody

        mov     al, byte [.status]
        mov     [.rec.body.protocolStatus], al

        lea     eax, [.rec]
        stdcall SocketSendAll, [.hSocket], eax, sizeof.FCGI_EndRequest

        popad
        return
endp




proc FCGI_send_unknown_type, .hSocket, .RequestID, .unknown_type

.rec FCGI_UnknownType

begin
        pushad

        xor     eax, eax
        lea     edi, [.rec]
        mov     ecx, sizeof.FCGI_UnknownType / 4
        rep stosd

        mov     [.rec.header.version], 1
        mov     [.rec.header.type], FCGI_UNKNOWN_TYPE

        mov     eax, [.RequestID]
        mov     [.rec.header.requestIdB1], ah
        mov     [.rec.header.requestIdB0], al

        mov     [.rec.header.contentLengthB0], sizeof.FCGI_UnknownType

        mov     al, byte [.unknown_type]
        mov     [.rec.body.type], al

        lea     eax, [.rec]
        stdcall SocketSendAll, [.hSocket], eax, sizeof.FCGI_UnknownType

        popad
        return
endp




proc FCGI_read_pack, .hSocket
.header FCGI_Header
.ptr    dd ?
begin
        pushad

        lea     eax, [.header]
        stdcall SocketReceive, [.hSocket], eax, sizeof.FCGI_Header, 0
        jc      .error
        test    eax, eax
        jz      .error

        mov     al, [.header.contentLengthB0]
        mov     ah, [.header.contentLengthB1]

        movzx   ebx, ax
        movzx   eax, [.header.paddingLength]
        add     ebx, eax

        lea     ecx, [ebx+sizeof.FCGI_Header]

        stdcall GetMem, ecx
        mov             edi, eax
        mov     [.ptr], eax

        lea     esi, [.header]
        mov     ecx, sizeof.FCGI_Header/4
        rep movsd

        test    ebx, ebx
        jz      .finish

.read_data:
        stdcall SocketReceive, [.hSocket], edi, ebx, 0
        jc      .error2
        test    eax, eax
        jz      .error2

        add     edi, eax
        sub     ebx, eax
        jnz     .read_data


.finish:
        popad
        mov     eax, [.ptr]
        clc
        return

.error2:

        stdcall FreeMem, [.ptr]

.error:

        stc
        popad
        return
endp







proc FCGI_Decode_name_value_pairs, .pArray, .pData, .size
.name  dd ?
.value dd ?
begin
        pushad


        mov     esi, [.pData]
        mov     edx, [.pArray]
        test    edx, edx
        jnz     .array_ok

        stdcall CreateArray, 8
        mov     edx, eax

.array_ok:


.loop:
        call    .get_length              ; name length
        mov     ecx, eax

        call    .get_length              ; value length
        push    eax

        stdcall StrNew
        mov     [.name], eax

        stdcall StrSetCapacity, eax, ecx
        mov     [eax+string.len], ecx
        sub     [.size], ecx

        mov     edi, eax
        rep movsb
        xor     eax, eax
        stosd

        pop     ecx
        stdcall StrNew
        mov     [.value], eax

        stdcall StrSetCapacity, eax, ecx
        mov     [eax+string.len], ecx
        sub     [.size], ecx

        mov     edi, eax
        rep movsb
        xor     eax, eax
        stosd

        stdcall AddArrayItems, edx, 1

        pushd   [.value] [.name]
        popd    [eax] [eax+4]

        cmp     [.size], 0
        jg      .loop


        mov     [esp+4*regEAX], edx
        popad
        return


.get_length:

        test    byte [esi], $80
        jz      .one_byte

; four byte length
        mov     eax, [esi]
        and     al, $7f

        bswap   eax
        add     esi, 4
        sub     [.size], 4
        retn

.one_byte:
        movzx   eax, byte [esi]
        inc     esi
        dec     [.size]
        retn

endp




proc FreeNameValueArray, .pArray
begin
        pushad

        mov     esi, [.pArray]
        test    esi, esi
        jz      .finish

        mov     ecx, [esi+TArray.count]

.loop:
        dec     ecx
        js      .free_array

        stdcall StrDel, [esi+TArray.array+8*ecx]
        stdcall StrDel, [esi+TArray.array+8*ecx+4]
        jmp     .loop

.free_array:
        stdcall FreeMem, [.pArray]

.finish:
        popad
        return
endp








proc ValueByName, .pArray, .name
begin
        pushad

        mov     esi, [.pArray]
        test    esi, esi
        jz      .not_found

        xor     ecx, ecx

.loop:
        cmp     ecx, [esi+TArray.count]
        jae     .not_found

        stdcall StrCompNoCase, [esi+TArray.array+8*ecx], [.name]
        jc      .found

        inc     ecx
        jmp     .loop

.not_found:
        stc
        popad
        return

.found:
        mov     eax, [esi+TArray.array+8*ecx+4] ; the value
        mov     [esp+4*regEAX], eax
        popad
        clc
        return
endp






