REM SocketWatch  - pass socket events as pollwords to Wimp apps
REM This is public domain - do with it what you want - no conditions apply
REM knocked up while in sleep deficit mode, so that's your software quality guarantee :-)

REM v0.01 ( 2 Jul 99) First version
REM  0.02 ( 3 Jul 99) ModifyCount added
REM  0.03 ( 3 Jul 99) Added master count
REM  0.04 ( 5 Jul 99) Bug fixed in deregister that was miscounting blocks
REM  0.05 ( 5 Jul 99) Bug fixed not properly checking socket handles
REM  0.06 (31 Jul 99) Renamed to SSHSockWatch to avoid clash with Dickon's version
REM  0.07 (14 Dec 2003) 32-bit compatible version

REM by Theo Markettos - atm26@cam.ac.uk, theomarkettos@letterbox.com
REM http://www-stu.cai.cam.ac.uk/~atm26/

version=0.07

Max_Sockets = 128

EventV=&10
Event_Internet = 19
OS_Module_Claim = 6
OS_Module_Free = 7

WS_SocketCount = 0
WS_SocketMaster = 4
WS_SocketTable = 8

WS_Table_Socket = 0
WS_Table_Pollword = 4
WS_Table_Reason = 8
WS_TableEntrySize = 3*4

ws=12
V_bit = 1<<28
@%="+f4.2"
DIM code% 10000
FOR pass%=0 TO 2 STEP 2
P%=code%
[OPT pass%
.begin
EQUD 0              ;start
EQUD init-begin     ;initialisation
EQUD final-begin    ;finalisation
EQUD 0              ;service call
EQUD title-begin    ;title
EQUD helpstr-begin  ;help string
EQUD command-begin  ;help/command
EQUD &51A80         ;SWI chunk no.
EQUD swicode-begin  ;handler code
EQUD switable-begin ;SWI table
EQUD 0              ;SWI decoding code
EQUD 0              ;MessageTrans
EQUD flags-begin    ;flags word
.title
EQUS "SSHSockWatch"
EQUB 0
ALIGN
.flags
EQUD 1              ; 32-bit compatible
EQUD 0
.helpstr
EQUS "SSHSockWatch"+CHR$9+STR$version+" (14 Dec 2003) by Theo Markettos"
EQUB 0
ALIGN
.switable
EQUS      "SSHSockWatch"
EQUB      0
EQUS      "Register"
EQUB      0
EQUS      "Deregister"
EQUB      0
EQUS      "ModifyCount"
EQUW      0
ALIGN
.init
STMFD     R13!,{R1-R12,R14}
MOV       R0,#OS_Module_Claim
MOV       R3,#Max_Sockets*WS_TableEntrySize
ADD       R3,R3,#WS_SocketTable         ; bytes for header
SWI       "XOS_Module"                  ; claim some workspace
LDMVSFD   R13!,{R1-R12,PC}
STR       R2,[R12]                      ; store in private word
MOV       ws,R2

MOV       R0,#0
STR       R0,[ws,#WS_SocketCount]       ; the count of sockets we're interested in
STR       R0,[ws,#WS_SocketMaster]      ; the master socket pollword

MOV       R0,#0                         ; clear pollword & reason
MVN       R1,#1                         ; invalid socket handle -2
ADD       R2,ws,#WS_SocketTable
MOV       R3,#Max_Sockets
.init_inittable_loop                    ; initialise all slots in table
STR       R1,[R2,#WS_Table_Socket]      ; set socket to invalid handle
STR       R0,[R2,#WS_Table_Pollword]
STR       R0,[R2,#WS_Table_Reason]      ; zero pollword and reason code
ADD       R2,R2,#WS_TableEntrySize
SUBS      R3,R3,#1
BNE       init_inittable_loop

MOV       R0,#EventV
ADR       R1,eventhandler               ; routine to attach to vector
MOV       R2,ws
SWI       "XOS_Claim"                   ; claim EventV
LDMFD     R13!,{R1-R12,PC}
.final
STMFD     R13!,{R1-R12,R14}
LDR       R12,[R12]
MOV       R0,#EventV
ADR       R1,eventhandler               ; routine to remove from vector
MOV       R2,ws                         ; workspace ptr
SWI       "XOS_Release"                 ; release EventV

MOV       R0,#OS_Module_Free
MOV       R2,R12
SWI       "XOS_Module"                  ; free our workspace
LDMFD     R13!,{R1-R12,PC}              ; return whether that worked or not
.swicode
LDR       R12,[R12]                     ;get workspace ptr
CMP       R11,#(EndOfJumpTable-JumpTable)/4
ADDCC     PC,PC,R11,LSL#2               ;dispatch if in range
B         UnknownSWIerror               ; unknown SWI
.JumpTable
B         SWI_register
B         SWI_deregister
B         SWI_modifycount
.EndOfJumpTable
.UnknownSWIerror
ADR       R0,errMesg
ORRS      PC,R14,#1<<28                 ; set V flag + return
.errMesg
EQUD      &1E6                          ; same as system message
EQUS      "Unknown SSHSockWatch operation"
EQUB      0
ALIGN
.error_socketNotClaimed
EQUD      &815120
EQUS      "Socket has not been registered with SSHSockWatch"
EQUB      0
ALIGN
.SWI_register                           ; on entry:
STMFD     R13!,{R3-R12,R14}             ; r0 = socket handle to claim
                                        ; on exit:
                                        ; r0 = pointer to count for this socket
                                        ; r1 = pointer to reason code
                                        ; r2 = pointer to master count for module
ADD       R4,ws,#WS_SocketTable
LDR       R5,[ws,#WS_SocketCount]       ; look through the socket table
CMP       R5,#0                         ; if it's got any entries
BEQ       SWI_register_notfound         ; otherwise just make a new one
.SWI_register_search                    ; for an entry with this socket handle
LDR       R6,[R4,#WS_Table_Socket]
CMP       R6,R0
BEQ       SWI_register_found            ; if we found one, return that slot
ADD       R4,R4,#WS_TableEntrySize      ; check the next slot
SUBS      R5,R5,#1
BNE       SWI_register_search           ; if we didn't get to the end

.SWI_register_notfound
LDR       R5,[ws,#WS_SocketCount]       ; we didn't find it, so
ADD       R5,R5,#1                      ; increment the counter - R4 now points
STR       R5,[ws,#WS_SocketCount]       ; to a vacant slot
STR       R0,[R4,#WS_Table_Socket]      ; so fill in the socket handle

.SWI_register_found                     ; however we got here, R4 point to a vacant slot
                                        ; in the table with socket handle filled in
ADD       R0,R4,#WS_Table_Pollword      ; return the pointers of the values set on
ADD       R1,R4,#WS_Table_Reason        ; an event
MOV       R2,#0
STR       R2,[R0]                       ; zero them just to be safe
STR       R2,[R0]
ADD       R2,ws,#WS_SocketMaster        ; return pointer to master pollword

LDMFD     R13!,{R3-R12,PC}              ; and return

.SWI_deregister                         ; on entry:
STMFD     R13!,{R0-R12,R14}             ; r0 = socket handle to release

ADD       R4,ws,#WS_SocketTable         ; ... this code seems strangely familiar ...
LDR       R5,[ws,#WS_SocketCount]       ; look through the socket table
CMP       R5,#0                         ; if it's there
LDMEQFD   R13!,{R0-R12,PC}              ; otherwise just return

.SWI_deregister_search                  ; for an entry with this socket handle
LDR       R6,[R4,#WS_Table_Socket]
CMP       R6,R0
BEQ       SWI_deregister_found          ; if we found one, return that slot
ADD       R4,R4,#WS_TableEntrySize      ; check the next slot
SUBS      R5,R5,#1
BNE       SWI_deregister_search         ; if we didn't get to the end

LDMFD     R13!,{R0-R12,PC}              ; if we didn't find it, just return

.SWI_deregister_found                   ; we found it - erase its entry (R4 point to it)

MVN       R0,#1                         ; invalid socket id
MOV       R1,#0
STR       R0,[R4,#WS_Table_Socket]      ; set all these to inactive
STR       R1,[R4,#WS_Table_Pollword]
STR       R1,[R4,#WS_Table_Reason]

                                        ; to save time in the event routine,
                                        ; we're now going to rescan the list to see if we
                                        ; can shorten it

ADD       R4,ws,#WS_SocketTable
MOV       R5,#0                         ; keep a count of the current cell
MOV       R6,#0                         ; and the last cell used
.SWI_deregister_compact                 ; for an entry with this socket handle
LDR       R7,[R4,#WS_Table_Socket]
CMN       R7,#2                         ; is it invalid?
ADD       R5,R5,#1                      ; increment the count, then
MOVNE     R6,R5                         ; if so make a note of its count - the count is in no
                                        ; of cells used
ADD       R4,R4,#WS_TableEntrySize      ; check the next slot
CMP       R5,#Max_Sockets               ; are we at the end
BLT       SWI_deregister_compact        ; if not, carry on

STR       R6,[ws,#WS_SocketCount]       ; update the new count

LDMFD     R13!,{R0-R12,PC}              ; and return

.SWI_modifycount                        ; on entry:
STMFD     R13!,{R4-R6,R14}              ; r0 = socket handle to inspect (-1 for master)
                                        ; r1 = new value of count, or -1 to read
                                        ; on exit:
                                        ; r0 preserved
                                        ; r1 = old value of count
                                        ; r2 = current reason code
ADD       R4,ws,#WS_SocketTable
LDR       R5,[ws,#WS_SocketCount]       ; look through the socket table
CMP       R5,#0                         ; if it's got any entries
BEQ       SWI_modifycount_notfound      ; otherwise complain
CMN       R0,#1                         ; are we checking the master socket?
ADDEQ     R4,ws,#WS_SocketMaster-(WS_Table_Pollword) ; produce an offset as if the master
                                        ; was a normal socket block
BEQ       SWI_modifycount_found         ; and treat it as one

.SWI_modifycount_search                 ; for an entry with this socket handle
LDR       R6,[R4,#WS_Table_Socket]
CMP       R6,R0
BEQ       SWI_modifycount_found         ; if we found one, return that slot
ADD       R4,R4,#WS_TableEntrySize      ; check the next slot
SUBS      R5,R5,#1
BNE       SWI_modifycount_search        ; if we didn't get to the end

.SWI_modifycount_notfound               ; complain if we don't know about this handle
ADR       R0,error_socketNotClaimed
LDMFD     R13!,{R4-R6,R14}
ORRS      PC,R14,#V_bit

.SWI_modifycount_found                  ; R4s point to socket entry
LDR       R5,[R4,#WS_Table_Pollword]
CMN       R1,#1                         ; are we just inspecting
STRNE     R1,[R4,#WS_Table_Pollword]    ; if not, write the new value
MOV       R1,R5                         ; and return the old
LDR       R2,[R4,#WS_Table_Reason]      ; and reason code
LDMFD     R13!,{R4-R6,PC}


.eventhandler
CMP       R0,#Event_Internet            ; is it internet event?
MOVNES    PC,R14                        ; if not pass on
STMFD     R13!,{R4-R6,R14}
LDR       R5,[ws,#WS_SocketCount]       ; scan through the sockets we've got registered
ADD       R4,ws,#WS_SocketTable         ; highly inefficient linear search - please improve me
.eventhandler_scan
LDR       R6,[R4,#WS_Table_Socket]
CMP       R6,R2
BEQ       eventhandler_found            ; is this socket ours?
ADD       R4,R4,#WS_TableEntrySize      ; check the next slot
SUBS      R5,R5,#1
BNE       eventhandler_scan             ; if not, ignore it
LDMFD     R13!,{R4-R6,R14}              ; and pass the call on
MOVS      PC,R14

.eventhandler_found                     ; R4 now points to socket entry we've found
LDR       R6,[R4,#WS_Table_Pollword]
ADD       R6,R6,#1                      ; increment the count in the pollword
STR       R6,[R4,#WS_Table_Pollword]
STR       R1,[R4,#WS_Table_Reason]      ; and store the reason code

LDR       R6,[ws,#WS_SocketMaster]
ADD       R6,R6,#1                      ; increment the master count
STR       R6,[ws,#WS_SocketMaster]

LDMFD     R13!,{R4-R6,R14}              ; pass the call on in case any other handlers
MOVS      PC,R14                        ; are interested in this socket
;LDMFD     R13!,{PC}     ; intercept call
.command
EQUS "SocketWatch"
EQUB 0
ALIGN
EQUD 0              ; no code
EQUD 0              ; flags
EQUD 0              ; default syntax
EQUD modulehelp-begin
EQUD 0
.modulehelp
EQUS "The SSHSockWatch module allows Wimp applications to make more efficient use of "
EQUS "network sockets, by allowing them to sleep until they are notified by a pollword "
EQUS "that socket activity has taken place."+CHR$13+CHR$13
EQUS "If that means nothing to you, don't worry about what it does!"+CHR$13+CHR$13
EQUS "Public domain by Theo Markettos - atm26@cam.ac.uk, theomarkettos@letterbox.com"
EQUW 13
ALIGN
]
NEXT pass%
SYS "OS_File",10,"SSHSockW",&FFA,,begin,P%

