HeavyThing - rwasa/worker.inc

Jeff Marrison

	; ------------------------------------------------------------------------
	; HeavyThing x86_64 assembly language library and showcase programs
	; Copyright © 2015-2018 2 Ton Digital 
	; Homepage: https://2ton.com.au/
	; Author: Jeff Marrison <jeff@2ton.com.au>
	;       
	; This file is part of the HeavyThing library.
	;       
	; HeavyThing is free software: you can redistribute it and/or modify
	; it under the terms of the GNU General Public License, or
	; (at your option) any later version.
	;       
	; HeavyThing is distributed in the hope that it will be useful, 
	; but WITHOUT ANY WARRANTY; without even the implied warranty of
	; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
	; GNU General Public License for more details.
	;       
	; You should have received a copy of the GNU General Public License along
	; with the HeavyThing library. If not, see <http://www.gnu.org/licenses/>.
	; ------------------------------------------------------------------------
	;       
	; worker.inc: child process goods for rwasa
	;

globals
{
	; keep an epoll object so we can talk with the master process
	masterlink	dq	0

	; a working buffer so we don't have to continually stack allocate or heap allocate log activities
	logbuffer	dq	0
}


; possible message types that go over the master/worker comms link
linkmessage_ocsp = 0
linkmessage_log = 1
linkmessage_tlsupdate = 2


falign
workerthread:
	prolog	workerthread
	push	rbx
	mov	rbx, rdi		; our fd
if profiling
	call	profiler$reset
end if
	; reinit our states
	call	epoll$init
	call	rng$init

	; reset our syslog pid so they make sense
	mov	eax, syscall_getpid
	syscall
	mov	[syslog_pid], rax

	; create our logbuffer
	call	buffer$new
	mov	[logbuffer], rax
	; setup our masterlink
	mov	rdi, masterlink$vtable
	xor	esi, esi
	call	epoll$new
	mov	rdi, rbx
	mov	rsi, rax
	mov	rbx, rax
	mov	[masterlink], rax
	call	epoll$established
	; setup an epoll object for our comms back to our master
if defined children_write_their_own_logs
	cmp	dword [cpucount], 1
	je	.skip_hooks
end if
	; if cpucount > 1:
	; hook logwriting so that it goes back to the master
	mov	qword [webservercfg$log_hook], worker_loghook
	; hook tls session cache so that it also propagates to our siblings
	cmp	dword [cpucount], 1
	je	.notlshook
	mov	qword [tls$sessioncache_hook], worker_tlscache
.notlshook:
	; we need to go through our webservercfg objects and recreate our timers
	mov	rdi, [configs]
	mov	rsi, .newconfigtimer
	call	list$foreach

	pop	rbx

if profiling
	; make sure cpu count is 1 and we are not backgrounded
	mov	rdi, .profiler_err1
	cmp	dword [background], 0
	jne	.profiler_errmsg
	mov	rdi, .profiler_err2
	cmp	dword [cpucount], 1
	jne	.profiler_errmsg
	
	; otherwise, go ahead and fire up a tui_profiler
	call	tui_profiler$new
	mov	rdi, rax
	call	tui_terminal$new
end if

	call	epoll$run
	epilog
falign
.newconfigtimer:
	mov	rsi, rdi
	mov	edi, 1500
	call	epoll$timer_new
	ret
if profiling
cleartext .profiler_err1, 'ERROR: Profiling TUI component requires foreground mode.'
cleartext .profiler_err2, 'ERROR: Profiling TUI component requires cpu count to be 1.'
calign
.profiler_errmsg:
	call	string$to_stdoutln
	call	epoll$run
	epilog
end if
calign
.skip_hooks:
	; when we called epoll$init for the second time, we lost our configuration timer
	; so we have to create a new one, similar to how the multiprocess version does it
	; in the master thread.
	call	io$new
	mov	qword [rax], logwriter$vtable
	mov	edi, 1500
	mov	rsi, rax
	call	epoll$timer_new
	pop	rbx

if profiling
	; make sure cpu count is 1 and we are not backgrounded
	mov	rdi, .profiler_err1
	cmp	dword [background], 0
	jne	.profiler_errmsg
	mov	rdi, .profiler_err2
	cmp	dword [cpucount], 1
	jne	.profiler_errmsg
	
	; otherwise, go ahead and fire up a tui_profiler
	call	tui_profiler$new
	mov	rdi, rax
	call	tui_terminal$new
end if


	call	epoll$run
	epilog

	; we are called from the webservercfg layer for all webservercfg objects
	; rdi == webservercfg object, rsi == preformatted log string, edx == 0 == normal, 1 == error
falign
worker_loghook:
	prolog	worker_loghook
	; compose and send a masterlink message
	push	rbx r12 r13
	mov	rbx, rdi
	mov	r12, rsi
	mov	r13, rdx

	mov	rdi, [logbuffer]
	call	buffer$reset
	mov	rdi, [logbuffer]
	mov	esi, linkmessage_log
	call	buffer$append_dword	; [buf+0] == linkmessage type
	mov	rsi, [r12]
	mov	rdi, [logbuffer]
if string_bits = 32
	shl	rsi, 2
else
	shl	rsi, 1
end if
	add	rsi, 28			; +4 type, +4 totallength, +4 logtype, +webservercfg object 8, +stringlength 8 preface
	call	buffer$append_dword	; [buf+4] == linkmessage length (total, including preface)
	mov	rdi, [logbuffer]
	mov	rsi, rbx
	call	buffer$append_qword	; [buf+8] == webservercfg object pointer
	mov	rdi, [logbuffer]
	mov	esi, r13d
	call	buffer$append_dword	; [buf+16] == logtype
	mov	rdi, [logbuffer]
	mov	rsi, r12
	mov	rdx, [r12]
if string_bits = 32
	shl	rdx, 2
else
	shl	rdx, 1
end if
	add	rdx, 8
	call	buffer$append
	mov	r8, [logbuffer]
	mov	rdi, [masterlink]
	mov	rsi, [r8+buffer_itself_ofs]
	mov	rdx, [r8+buffer_length_ofs]
	mov	rcx, [rdi]
	call	qword [rcx+io_vsend]

	pop	r13 r12 rbx
	epilog


	; we are called directly from the tls layer anytime something gets added to our sessioncache
	; rdi == string sessionid, rsi == 72 bytes of possibly encrypted tls state goods
falign
worker_tlscache:
	prolog	worker_tlscache
	; compose and send a masterlink message
	sub	rsp, 128
	mov	dword [rsp], linkmessage_tlsupdate
	mov	dword [rsp+4], 40 + 64
	mov	rcx, rdi
	lea	rdi, [rsp+8]
	push	rsi
	mov	rsi, rcx
	mov	edx, 32
	call	memcpy
	pop	rsi
	lea	rdi, [rsp+40]
	mov	edx, 64
	call	memcpy
	mov	rdi, [masterlink]
	mov	rsi, rsp
	mov	edx, 40 + 64
	mov	rcx, [rdi]
	call	qword [rcx+io_vsend]
	add	rsp, 128
	epilog


dalign
masterlink$vtable:
	dq	epoll$destroy, epoll$clone, io$connected, epoll$send, masterlink$receive, io$error, io$timeout


falign
masterlink$receive:
	prolog	masterlink$receive
	; first 4 bytes is our message type, next 4 bytes is the length (which includes the 8 byte preface)
	push	rbx r12 r13 r14 r15
	mov	rbx, rdi
	mov	r12, [rdi+epoll_inbuf_ofs]
calign
.outer:
	mov	rdx, [r12+buffer_length_ofs]
	mov	rcx, [r12+buffer_itself_ofs]
	cmp	rdx, 8
	jb	.needmore
	mov	eax, [rcx+4]
	; sanity only
	cmp	rdx, rax
	jb	.needmore
	cmp	dword [rcx], linkmessage_ocsp
	je	.ocsp
	cmp	dword [rcx], linkmessage_tlsupdate
	jne	.insanity
	; otherwise, we are sitting on a TLS session cache update item
	; which is sessionid at [rcx+8], 64 byte state info at [rcx+40]
	lea	r13, [rcx+8]
	lea	r14, [rcx+40]
	mov	rdi, r13
	mov	rsi, r14
	push	qword [tls$sessioncache_hook]
	mov	qword [tls$sessioncache_hook], 0
	call	tls$sessioncache_set
	pop	rax
	mov	[tls$sessioncache_hook], rax
	; consume and continue
	mov	rcx, [r12+buffer_itself_ofs]
	mov	rdi, r12
	mov	esi, [rcx+4]
	call	buffer$consume
	jmp	.outer
calign
.ocsp:
	lea	r13, [rcx+8]	; our subjectcn string
	; this is a bit inefficient, because we have to walk our certificates every time we get these
	; but a) there aren't many certificates (normally), and b) this doesn't happen very often
	; so we really don't mind

	; so we know that ALL of our certificates live in the tls$pem_byptr map, so we'll walk it by
	; hand searching for our matching subjectcn
	mov	rdi, [tls$pem_byptr]
	mov	r14, [rdi+_avlofs_next]	; first one in the list
calign
.ocsp_search_outer:
	mov	rdi, [r14+_avlofs_key]	; the X509 object
	cmp	qword [rdi+X509_certificates_ofs], 0
	je	.ocsp_search_outer_next
	mov	rdi, [rdi+X509_certificates_ofs]
	mov	r15, [rdi+_list_first_ofs]
	test	r15, r15
	jz	.ocsp_search_outer_next
calign
.ocsp_search_inner:
	mov	rdi, [r15+_list_valueofs]
	mov	rsi, r13
	mov	rdi, [rdi+X509cert_subjectcn_ofs]
	call	string$equals
	test	eax, eax
	jnz	.ocsp_search_found
	mov	r15, [r15+_list_nextofs]
	test	r15, r15
	jnz	.ocsp_search_inner
calign
.ocsp_search_outer_next:
	mov	r14, [r14+_avlofs_next]
	test	r14, r14
	jnz	.ocsp_search_outer
	; if we made it to here, we didn't find it
	mov	rcx, [r12+buffer_itself_ofs]
	mov	rdi, r12
	mov	esi, [rcx+4]
	call	buffer$consume
	jmp	.outer
calign
.ocsp_search_found:
	mov	r14, [r15+_list_valueofs]		; the X509cert object
	cmp	qword [r14+X509cert_ocspresponse_ofs], 0
	jne	.ocsp_nonewbuffer
	call	buffer$new
	mov	[r14+X509cert_ocspresponse_ofs], rax
calign
.ocsp_nonewbuffer:
	mov	rdi, [r14+X509cert_ocspresponse_ofs]
	call	buffer$reset
	mov	rcx, [r13]
	mov	rdi, [r14+X509cert_ocspresponse_ofs]
	mov	rsi, [r12+buffer_itself_ofs]
	mov	edx, [rsi+4]
if string_bits = 32
	shl	rcx, 2
else
	shl	rcx, 1
end if
	sub	edx, 16
	add	rsi, 16
	add	rsi, rcx
	sub	edx, ecx
	call	buffer$append
	; consume and continue
	mov	rcx, [r12+buffer_itself_ofs]
	mov	rdi, r12
	mov	esi, [rcx+4]
	call	buffer$consume
	jmp	.outer
calign
.insanity:
	; should NOT happen
	mov	rdi, r12
	call	buffer$reset
	pop	r15 r14 r13 r12 rbx
	xor	eax, eax
	epilog
calign
.needmore:
	xor	eax, eax
	pop	r15 r14 r13 r12 rbx
	epilog