	.include "DS.S"
#-----------------------------------------------
#	S T A T I C   V A R I A B L E S
#-----------------------------------------------
	.data
	.global C.csP
C.csP:	.int	0				# -->CS
	.text
#-----------------------------------------------
#	M A I N   R O U T I N E
#-----------------------------------------------
# takes values from ENV, establishes task for comm nodes and waits for their completion
	ARGS
	DS	prgP				# -->progname
# returns	nothing
	PROLOC
	DL	this, CSL			# CS - top level attr vector
	DL	deP				# -->Debug
	DL	status				# status returned from wait
	DL	pid				# PID returned from wait
	DL	bad				# cummulative subtask rc
	DL	s, 256				# string buf
	EPILOC
#-----------------------------------------------
	.global _start
_start:
	PROLOG
	lea	this(bp), b			# -->CS
	mov	b, C.csP			# save -->CS
	lea	C.debug(b), a			# -->Debug
	mov	a, deP(bp)
# initialize static debug vars
	push	prgP(bp)			# argv[0] -->prgname
	call	D.init				# initialize static debug vars
	push	D.prgNameP
# indetify itself
	DEBID	"client/server demo"
	movl	$1, C.debMaxLev(b)		# default debug level
# initialize top level ctrl values vector
	ShareA					# allocate shared structure
	mov	C.csP, b
	mov	a, C.shP(b)			# save -->shared struct in vector
	movl	$0, S.msgs(a)			# init msg cntr
	movl	$0, S.conns(a)			# init connection cntr
	push	$1				# initial semaphore value
	push	$1				# semaphore shared between processes
	lea	S.counter_sem(a), c
	push	c
#	call	sem_init			# initialize shared counters semaphore
#	cmp	$0, a
#	jz	0f
#	SYSERR	"sem_init of shared counters semaphore"
#0:
	SYS	sem_init
# set default values
	mov	C.csP, b
	movl	$3, C.ttl(b)			# default TTL
	movl	$11000, C.rp0(b)		# default bind port for the 1. node in ring
	movl	$12000, C.mp0(b)		# default bind port for the 1. node in mash
	movl	$0, C.rn(b)			# default # of nodes in ring
	movl	$0, C.mn(b)			# default # of nodes in mash
	movl	$0, C.ssl(b)			# default ssl switch - 0=noSSL
	movl	$77, C.connTh(b)		# conn retries threshold
	mov	$0f, a
	mov	a, C.txtP(b)			# default payload text
	jmp	1f
0:	.ascii "bla bla\0"
1:
# get control values from ENV
# get max debug level
	GETINTENV "DEB"
	mov	C.csP, b
	mov	a, C.debMaxLev(b)
	cmp	$-1, a				# debug level -1 means search subroutines
	jne	0f
	call	D.subr
	jmp	C.ret
0:
# get message payload text
	push	$0f
	call	getenv
	cmp	$0, a
	jz	1f
	mov	C.csP, b
	mov	a, C.txtP(b)
	jmp	1f
0:	.asciz "T"
1:
# get TTL
	GETINTENV "TTL"
	jz	0f
	mov	a, C.ttl(b)
0:
# get # of nodes in each topology
	GETINTENV "I"
	jz	0f
	mov	a, C.rn(b)
	mov	a, C.mn(b)
0:
# get # of nodes in ring
	GETINTENV "RN"
	jz	0f
	mov	a, C.rn(b)
0:
# get # of nodes in mash
	GETINTENV "MN"
	jz	0f
	mov	a, C.mn(b)
0:
	add	C.rn(b), a
	cmp	$3, C.ssl(b)
	jne	1f
	add	a, a				# double # of nodes when both SSL and nonSSL
1:	mov	C.shP(b), c			# -->shared counters
	mov	a, S.act(c)			# save # of active nodes
# get first ring node's bind port#
	GETINTENV "RP0"
	jz	0f
	mov	a, C.rp0(b)
0:
# get first mash node's bind port#
	GETINTENV "MP0"
	jz	0f
	mov	a, C.mp0(b)
0:
# get random() seed
	GETINTENV "RS"
	jz	0f
	push	a
	call	srandom
0:
# get pacing interval (real num in seconds)
	movl	$0, C.pace.tv_sec(b)
	movl	$0, C.pace.tv_nsec(b)
	movl	$0, C.pacing(b)
	push	$0f
	call	getenv
	jmp	1f
0:	.asciz "P"
1:
	test	a, a				# env P set ?
	jz	3f				# no
	push	a
	call	atof				# convert to double
	fstl	(sp)				# tempor save
	mov	C.csP, b
	fisttpl	C.pace.tv_sec(b)		# truncated integral part = secs
	fldl	(sp)
	fisubl	C.pace.tv_sec(b)		# fraction part
	fimull	1f				# * 10^9 = nanosecs
	fistl	C.pace.tv_nsec(b)
	jmp	2f
0:	.double	0
1:	.int	1000000000			# 10^9
2:
	cmp	$0, C.pace.tv_sec(b)
	jnz	0f
	cmp	$0, C.pace.tv_nsec(b)
	jz	3f
0:	movl	$1, C.pacing(b)
3:
# get ssl switch value and save it as mask
# switch: 0 = noSSL, 1 = SSL, 2 = both
# mask: 01B=noSSL, 10B=SSL, 11B=both
	GETINTENV "SSL"				# returned zero means SSL=0 or SSL by default 0
	inc	a				# change switch to mask
	mov	a, C.ssl(b)
	cmp	$1, a
	je	C.cont				# no SSL
	cmp	$2, a
	je	0f				# only SSL
	mov	C.shP(b), a			# -->shared mem
	shll	$1, S.act(a)			# double # of active nodes when running both SSL and nonSSL
# get SSL CA cert dir path
0:
	push	$0f
	call	getenv
	jmp	1f
0:	.asciz "CAP"
1:
	test	a, a
	jnz	1f
	mov	$0f, a
	jmp	1f
0:	.asciz	"/home/local/etc/ssl/certs/"
1:
	mov	C.csP, b
	mov	a, C.caPathP(b)			# save -->SSL CA certs path
# get SSL dir path
	push	$0f
	call	getenv
	jmp	1f
0:	.asciz "CEP"
1:
	test	a, a
	jz	0f				# ceP not set, try to determine
	mov	C.csP, b
	mov	a, C.cePathP(b)			# save -->SSL dir path
	jmp	1f
# determine home path (needed to locate SSL keys & certificates)
0:
	push	prgP(bp)			# -->first parm - progname w/ path
	call	dirname				# get prog dirname
	push	a
	call	strlen				# dirname length
	lea	1f-0f(a), a			# + suffix length
	sub	a, sp				# allocate space for cePath
	mov	C.csP, b
	mov	sp, C.cePathP(b)		# save -->SSL path
	push	prgP(bp)
	call	dirname				# get home dirname
	push	a				# -->home dirname
	mov	C.csP, b
	push	C.cePathP(b)			# -->SSL path
	call	strcpy				# copy dirname to SSL path
	call	strlen				# end of dirname
	push	$0f				# -->suffix
	mov	C.csP, b
	mov	C.cePathP(b), c
	lea	(c, a), a			# -->end of dirname
	push	a
	call	strcpy				# copy suffix to SSL path
	jmp	1f
0:	.asciz	"/../CS/"
1:
# testing sandbox
	mov	C.csP, b
	pushl	C.debMaxLev(b)
	cmp	$9, C.debMaxLev(b)
	jne	C.cont

	LOG	9, "debug=%u, testing...", 1
	DebugA
	mov	a, deP(bp)
	DEBID	"TEST"
	LOG	9, "progress"

	mov	C.csP, b
	mov	C.shP(b), a
	push	S.act(a)
	LOG	9, "shared act=%u"

	jmp	C.ret
# normal execution
C.cont:
	push	C.debMaxLev(b)
	push	C.ssl(b)
	push	C.pace.tv_nsec(b)
	push	C.pace.tv_sec(b)
	push	C.ttl(b)
	push	C.rn(b)
	push	C.mn(b)
	push	D.prgNameP
	LOG	1, "pgm=%s, mash nodes=%d, ring nodes=%d, ttl=%d, pacing=%ld.%09ld, SSL mask=0x%02x, debug=%u", 8
	testl	$2, C.ssl(b)
	jz	0f
	push	C.caPathP(b)
	push	C.cePathP(b)
	LOG	1, "SSL path=%s, SSL CA path=%s"
0:
# create constellation processes RING/MASH, nonSSL/SSL
	mov	$0, c				# iter counter
	mov	C.ssl(b), d			# SSL mask (01b = noSSL, 10b = SSL, 11b = both)
# iterate on SSL variants
C.iterateOnSsl:
	test	$1, d				# check lowest bit of mask
	jz	C.nextSsslVar			# next SSL variant
	pusha
# create RING
	SYS	fork
	jnz	1f				# parent
	popa
	pushl	$Cn.ring			# RING topology
	push	c				# use iter ctr as SSL switch
	call	Constellation			# create RING constellation
1:	push	a
	LOG	5, "RING started in process %d"
	lea 	4(sp), sp
# create MASH
	SYS	fork
	jnz	1f				# parent
	popa
	pushl	$Cn.mash			# MASH topology
	push	c				# use iter ctr as SSL switch
	call	Constellation			# create MASH constellation
1:	push	a
	LOG	5, "MASH started in process %d"
	lea 	4(sp), sp

	popa
C.nextSsslVar:
	shr	$1, d				# shift to test next SSL bit
	inc	c				# incr ctr
	cmp	$2, c
	jl	C.iterateOnSsl
# wait for constellation processes completion
	LOG	5, "waiting for constellations to terminate"
	movl	$0, bad(bp)			# clear cummulative rc
C.iterateOnWait:
	lea	status(bp), a
	push	a				# -->return status of task
	call	wait
	mov	a, pid(bp)			# save pid
	cmp	$-1, a				# a task ended?
	jne	0f				# yes
	call	__errno_location
	cmp	$10, (a)			# error == ECHILD ?
	je	C.ret				# yes, no other subtasks
	SYSERR	"wait"
0:
#	push	status(bp)			# status of task
#	push	a				# pid
#	LOG	5, "status returned from task %u: 0x%08x"
	mov	status(bp), d
	test	$0x7f, d
	jnz	1f				# task killed, ABEND
	and	$0xff00, d			# task exited, extract rc
	shr	$8, d
	or	d, bad(bp)			# accumulate rc
	push	d				# task rc
	push	pid(bp)				# task pid
	LOG	5, "constellation task %u ended with exit(%d)"
	jmp	C.iterateOnWait			# wait for other tasks
1:	push	d
	push	pid(bp)
	LOG	5, "constellation task %u killed, status=0x%x", 2
2:
# ABEND
	LOG	0, "ABEND, kill all tasks"
	pushl	$15				# SIGTERM
	pushl	$0				# all tasks
#	call	kill
	SYS	kill
	pushl	$1
	call	exit
# normal end
C.ret:	movl	C.csP, b			# -->CS vector
	movl	C.shP(b), b			# -->Share
	pushl	S.conns(b)			# no. of connections made
	pushl	S.msgs(b)			# no. of messages sent
	LOG	1, "END, forwards=%d, connections=%d", 2
	push	bad(bp)
	call	exit
#-----------------------------------------------
#	 C O N S T E L L A T I O N   O P E R A T I O N S
#-----------------------------------------------
	ARGS
	DS	ssl				# ssl switch
	DS	topo				# topology
# returns:	nothing
	PROLOC
	DL	this, ConstellationL		# this Constellation instance
	DL	thisP				# -->this Constellation
	DL	deP				# -->Debug
	DL	last				# last node#
	DL	pid				# pid returned from wait
	DL	stat				# stat returned from wait
	DL	bad				# "some node BAD" exit indicator
	DL	catched				# count of returned node tasks
	DL	killed				# count of killed node tasks
	EPILOC
#-----------------------------------------------
	.global Constellation
Constellation:
	PROLOG
	lea	this(bp), b			# -->this Constellation
	mov	b, thisP(bp)			# save -->this Constellation
# set debid
	lea	Cn.debug(b), a			# -->Debug
	mov	a, deP(bp)			# save -->Debug locally
	mov	C.csP, c			# -->CS
	cmp	$Cn.ring, topo(bp)		# ring topology ?
	je	0f
	mov	C.mn(c), a			# num. of nodes
	mov	a, Cn.nodes(b)
	mov	C.mp0(c), a			# port # of fist node
	mov	a, Cn.first(b)
	movl	$Cn.mash, Cn.topo(b)
	push	$8f
	jmp	1f
0:	mov	C.rn(c), a			# num. of nodes
	mov	a, Cn.nodes(b)
	mov	C.rp0(c), a			# port # of fist node
	mov	a, Cn.first(b)
	movl	$Cn.ring, Cn.topo(b)
	push	$7f
1:	cmp	$0, ssl(bp)			# SSL ?
	jz	2f				# no
	movl	$1, Cn.ssl(b)
	addl	$500, Cn.first(b)		# first SSL port #
	push	$6f
	jmp	9f
2:	movl	$0, Cn.ssl(b)
	push	$5f
	jmp	9f
5:	.ascii	"non\0"
6:	.ascii	"\0"
7:	.ascii	"RING\0"
8:	.ascii	"MASH\0"
9:	DEBID	"%sSSL %s", 2
# check # of nodes
	movL	$0, bad(bp)
	cmp	$1, Cn.nodes(b)			# num of nodes
	jl	Cn.ret			# < 1 ? nothing to do
	jg	0f
	LOG	0, "1 node configuration not implemented yet"
	jmp	Cn.ret
0:	LOG	5, "initializing..."
# determine divisor for random next node choise
	mov     $1, a
        shl     $31, a
        not     a             			# MAX_INT
        xor     d, d
        divl    Cn.nodes(b)
        mov     a, Cn.div(b)			# save divisor (MAX_INT / nodes)
# allocate "forward" indicator shared by nodes in constellation
	push	$0
	push	$-1
	push	$0x21				# PROT_READ | PROT_WRITE
	push	$0x03				# MAP_SHARED | MAP_ANONYMOUS
	push	$4
	push	$0
#	call	mmap
#	cmp	$-1, a
#	jne	0f
#	SYSERR	"mmap"
#0:	mov	thisP(bp), b
	SYS	mmap
	mov	a, Cn.forwP(b)			# save -->forw
	movl	$1, (a)				# enable forwarding
	push	Cn.nodes(b)
	LOG	1, "%u nodes starting..."
# start processes for all nodes in constellation
	mov	Cn.first(b), d			# first node#
	mov	d, c
	add	Cn.nodes(b), c			# last node + 1
Cn.iterateOnFork:
	pusha
#	call	fork
	SYS	fork
	cmp	$0, a
	jnz	1f				# parent
	popa
	push	d				# node's port#
	push	b				# -->Cnstlln
	call	Node
1:	mov	a, pid(bp)
	popa
	push	pid(bp)				# nodes's pid
	push	d				# node's port#
	LOG	4, "node %u established in process %u", 2
	inc	d
	cmp	d, c				# last node ?
	jg	Cn.iterateOnFork		# no, continue forking
# wait for completion of node processes
	LOG	2, "all nodes established, waiting for them to terminate..."
	movl	$0, bad(bp)			# accumulated return status of node tasks
	movl	$0, killed(bp)			# num. of killed node tasks
	movl	$0, catched(bp)			# num. of returned node tasks
	lea	-12(sp), sp			# prepare space for loop
Cn.iterateOnWait:
	cmp	$0, bad(bp)			# constellation status still OK ?
	je	1f				# yes
	mov	Cn.forwP(b), a			# -->forwarding switch
	movl	$0, (a)				# disable forwarding between nodes
1:
	lea	stat(bp), c
	mov	c, (sp)				# -->return status of task
	call	wait
	mov	thisP(bp), b
	cmp	$-1, a				# normal return from wait?
	jne	0f				# yes
	call	__errno_location
	cmp	$10, (a)			# error == ECHILD ?
	je	Cn.allFinished			# yes, no subtasks
	SYSERR	"wait"
0:
	incl	catched(bp)
	mov	a, pid(bp)			# save subtask's pid
	mov	a, (sp)
	mov	stat(bp), c			# subtask return status
	mov	c, 4(sp)
	test	$0x7f, c			# subtask ended by exit ?
	jnz	2f				# no, killed
	and	$0xff00, c			# extract subtask rc
	jz	1f				# rc = 0
	movl	$1, bad(bp)			# non zero rc, turn on BAD switch
1:	shr	$8, c
	mov	c, 4(sp)			# rc
	mov	a, (sp)				# pid
	LOG	4, "node process %u ended by exit(%d)"
	jmp	Cn.iterateOnWait		# continue waiting for other subtasks
2:	movl	$1, bad(bp)			# subtask killed, turn on BAD switch
	incl	killed(bp)			# counter of killed
	mov	c, 4(sp)			# status
	mov	a, (sp)				# pid
	LOG	4, "node process %u killed, status=0x%x"
	jmp	Cn.iterateOnWait		# continue waiting for other subtasks

# opers of all nodes finished
Cn.allFinished:
	push	killed(bp)
	push	catched(bp)
	mov	thisP(bp), b
	push	Cn.nodes(b)
	cmp	$0, bad(bp)			# all nodes ended OK ?
	je	0f				# yes
	push	$7f
	jmp	9f
0:	push	$8f
	jmp	9f
7:	.ascii	"with ERROR\0"
8:	.ascii	"OK\0"
9:	LOG	1, "ENDED %s, %u spawned, %u catched, %u killed"
Cn.ret:
	push	bad(bp)
	call	exit
#-----------------------------------------------
#	G E T   I N T   V A L U E S   F R O M   E N V
#-----------------------------------------------
	ARGS
	DS	key				# -->env key string
# returns	int value or 0 if not found
	PROLOC
	EPILOC
#-----------------------------------------------
C.getArg:
	PROLOG
	pushl	key(bp)				# -->env key string
	call	getenv				# get value
	test	a, a
	jz	0f				# not found in ENV
	push	a
	call	atoi				# convert to int
0:
	EPILOG_R
#-----------------------------------------------
#	A B N O R M A L   E N D
#-----------------------------------------------
	ARGS
	DS	deP				# -->Debug
	PROLOC
	EPILOC
#-----------------------------------------------
	.globl	C.abend
C.abend:
	PROLOG
	LOG	0, "ABEND"
	push	$15				# SIGTERM
	push	$0				# kill all
#	call	kill
	SYS	kill
	push	$1
	call	exit
#-----------------------------------------------
	.end
