NAME msscom ; File MSSCOM.ASM include mssdef.h ; Copyright (C) 1982, 1997, Trustees of Columbia University in the ; City of New York. The MS-DOS Kermit software may not be, in whole ; or in part, licensed or sold for profit as a software product itself, ; nor may it be included in or distributed with commercial products ; or otherwise distributed by commercial concerns to their clients ; or customers without written permission of the Office of Kermit ; Development and Distribution, Columbia University. This copyright ; notice must not be removed, altered, or obscured. ; ; Edit history ; 12 Jan 1995 version 3.14 ; Last edit ; 12 Jan 1995 ; 8 Dec 1992 version 3.13 ; 6 Sept 1991 version 3.11 ; 2 March 1991 version 3.10 public spack, rpack, spause, bufclr, pakptr, bufrel public makebuf, getbuf, pakdup, chkwind, firstfree, windused public rpacket, windlow, chkparflg, maxbufparas, peekreply public winusedmax, krto, k_sa, k_sd, k_rto, tcp_rto, windgrow public windshrink, cwindow, ssthresh stat_suc equ 0 ; success stat_tmo equ 1 ; timeout stat_chk equ 2 ; checksum mismatch stat_ptl equ 4 ; packet too long stat_int equ 8 ; user interrupt stat_eol equ 10h ; eol char seen stat_echo equ 20h ; echo of sent packet stat_bad equ 80h ; packet is bad (premature EOL) BIOSCLK equ 046ch HI_MAX equ 018h LO_MAX equ 0b0h data segment extrn flags:byte, trans:byte, fsta:word, ssta:word, fmtdsp:byte extrn pktnum:byte, portval:word, denyflg:word, cardet:byte extrn parmsk:byte badpflag db 0 ; flag to say have shown bad parity message spmes db 'Spack: $' rpmes db 'Rpack: $' crlf db cr,lf,'$' msgstl db 'Internal Error: send packet is too long',cr,lf,0,'$' msgheader db '<$' msgtmo db 'Timeout$' msgchk db 'Bad checksum$' msgint db 'Interrupted$' msgptl db 'Pkt too long$' msgbad db 'Early EOL$' msgtail db '>',cr,lf,'$' msgecho db cr,lf,'',cr,lf,'$' msgbadsnd db cr,lf,'',cr,lf,'$' msgbadpare db 'Unexpected Parity from host! Changing Parity to EVEN' db cr,lf,0 msgbadparo db 'Unexpected Parity from host! Changing Parity to ODD' db cr,lf,0 msgbadparm db 'Unexpected Parity from host! Changing Parity to MARK' db cr,lf,0 tmp db 0 spause dw 0 ; # millisec to wait before sending pkt timeval db 0 ; active receive timeout value, seconds prvtyp db 0 ; Type of last packet sent prvlen dw 0 ; bytes sent, for echo suppression chkparflg db 0 ; non-zero to check parity on received pkts chklength db 1 ; active checksum length prevchar db 0 ; previous char from comms line (for ^C exit) SOHchar db 0 ; start of packet char, local copy lentyp db 0 ; packet length type, 3, 0, 1 debflg db 0 ; debug display, send/receive flag timeit db 0 ; arm timeout counter tcp_rto dw 0 ; rto (Bios ticks) from internal TCP/IP stack k_rto dw 0 ; Kermit round trip timeout, Bios ticks k_sa dw 0 ; Kermit smoothed avg round trip time k_sd dw 0 ; Kermit std deviation of round trip time ; sliding windows data structures windlow db 0 ; lower border of window windused db 0 ; number of window slots in use winusedmax db 0 ; max used window slots cwindow db 0,0 ; congestion window, fractions of slot ssthresh db 0 ; congestion threshold slot count thirtytwo db 32 ; divisor for window congestion counting prolog db 10 dup (0) ; prolog: SOH, LEN, SEQ, TYPE, xlen,...,null epilog db 10 dup (0) ; epilog: checksum, eol, handshake + null term rbuf db 128 dup (0) ; static packet buffer for replies even pbufseg dw 0 ; segment of packet buffer memory block maxbufparas dw 0 ; paragraphs available in free memory bufnum dw 0 ; number of buffers available now buflist dw maxwind dup (0) ; pointers to packet structures in pktlist bufuse dw maxwind dup (0) ; in-use flag (0 = not in use) pktlist pktinfo maxwind dup (<>) ; pktinfo structured members (private) rpacket pktinfo ; reply pktinfo even rtemp dw 0 ; address of pktinfo structure for rpack stemp dw 0 ; address of pktinfo structure for spack linecnt db 0 ; debug line width counter colcnt db 0 chksum dw 0 ; running checksum (two char) chrcnt dw 0 ; number of bytes in data field of a packet pktcnt dw 0 ; number of bytes sent/rcvd in this packet status dw 0 ; status of packet receiver (0 = ok) fairflg dw 0 ; fairness flag, for console/port reads rptim db 4 dup (0) ; read packet timeout slots ninefive dw 95 ; for mult/div with long packets bias db ' ' ; ascii bias for checksum calculations prolog_len dw 0 ; length of prolog information, for crc temp dw 0 data ends code1 segment extrn strlen:far, isdev:far, decout:far, dec2di:far assume cs:code1 code1 ends code segment extrn prtchr:near, outchr:near extrn sppos:near, ermsg:near, clearl:near, rppos:near extrn pktcpt:near, pcwait:far, peekcom:far assume cs:code, ds:data, es:nothing prtchr1 proc far ; near-far interface routines for code1 seg call prtchr ret prtchr1 endp foutchr proc far call outchr ret foutchr endp rppos1 proc far call rppos ret rppos1 endp sppos1 proc far call sppos ret sppos1 endp ermsg1 proc far call ermsg ret ermsg1 endp clearl1 proc far call clearl ret clearl1 endp fpktcpt proc far call pktcpt ret fpktcpt endp code ends code1 segment assume cs:code1, ds:data, es:nothing ; Send_Packet ; This routine assembles a packet from the arguments given and sends it ; to the host. ; ; Expects the following: ; SI = pointer to pktinfo structure, as ; [SI].PKTYPE - Packet type letter ; [SI].SEQNUM - Packet sequence number ; [SI].DATLEN - Number of data characters ; [SI].DATADR - DWord Address of data field for packet ; Returns: carry clear if success, carry set if failure. ; Packet construction areas: ; Prolog (8 bytes) Data null Epilog ;+----------------------------------------+---------------+---------------+ ;| SOH,LEN,SEQ,TYPE,Xlen(2-3),Xlen chksum | packet's data | chksum,EOL,HS | ;+----------------------------------------+---------------+---------------+ ; where Xlen is 2 byte (Long) or 3 byte (Extra Long) count of bytes to follow. ; SPACK PROC FAR mov stemp,si ; save pkt pointer mov ah,[si].pktype mov prvtyp,ah ; remember packet type mov pktcnt,0 ; number of bytes sent in this packet mov prolog_len,0 mov ax,spause ; wait spause milliseconds before sending pkt or ax,ax ; zero? jz spac1 ; z = yes call pcwait ; to let other side get ready spac1: mov cl,trans.spad ; get the number of padding chars xor ch,ch jcxz spac4 ; z = none xor al,al xchg al,trans.sdbl ; doubling char, stash and clear it push ax mov al,trans.spadch ; get padding char spac2: call spkout ; send padding char jnc spac3 ; nc = success ret ; failed spac3: loop spac2 pop ax ; recover doubling char xchg trans.sdbl,al spac4: call snddeb ; do debug display (while it's still our turn) mov al,trans.ssoh ; get the start of header char mov prolog,al ; put SOH in the packet mov si,stemp ; address of send pktinfo mov al,[si].seqnum ; SEQ add al,20h ; ascii bias mov prolog+2,al ; store SEQ in packet xor ah,ah mov chksum,ax ; start checksum mov al,prvtyp ; TYPE mov word ptr prolog+3,ax ; store TYPE (and null terminator) add chksum,ax ; add to checksum ; ; packet length type is directly governed here by length of header plus data ; field, [si].datlen, plus chksum: regular <= 94, long <= 9024, else X long. ; mov ax,[si].datlen ; DATA length add ax,2 ; add SEQ, TYPE lengths cmp trans.chklen,'B'-'0' ; B blank-free checksum? jne spac12 ; ne = no add al,2 ; B is a special two byte checksum jmp short spac13 spac12: add al,trans.chklen ; add checksum length at the end spac13: adc ah,0 ; propagate carry, yields overall new length cmp ax,[si].datsize ; too big? jle spac14 ; le = ok push ax ; for carry-on-regardless after error push dx ; tell user an internal error has occurred mov dx,offset msgstl ; packet is too long call ermsg1 ; display message on error line call captdol ; put into packet log pop dx pop ax jmp short spac14 ; carry-on-regardless ; stc ; ret ; return bad spac14: mov lentyp,3 ; assume regular packet cmp ax,94 ; longer than a regular? ja spac15 ; a = use Long add al,20h ; convert length to ascii mov prolog+1,al ; store LEN xor ah,ah add chksum,ax ; add LEN to checksum mov bx,offset prolog+4 ; look at data field jmp spac19 ; do regular ; use Long packets (type 0) spac15: sub ax,2 ; deduct SEQ and TYPE from above = data+chksum mov lentyp,0 ; assume type 0 packet cmp ax,(95*95-1) ; longest type 0 Long packet (9024) jbe spac16 ; be = type 0 mov lentyp,1 ; type 1 packet, Extra Long spac16: mov cl,lentyp ; add new LEN field to checksum add cl,20h ; ascii bias, tochar() xor ch,ch add chksum,cx ; add to running checksum mov prolog+1,cl ; put LEN into packet mov bx,offset prolog+4 mov cx,1 ; a counter xor dx,dx ; high order numerator of length spac17: div ninefive ; divide ax by 95. quo = ax, rem = dx push dx ; push remainder inc cx ; count push depth cmp ax,95 ; quotient >= 95? jae spac17 ; ae = yes, recurse push ax ; push for pop below spac18: pop ax ; get a digit add al,20h ; apply tochar() mov [bx],al ; store in data field add chksum,ax ; accumulate checksum for header inc bx ; point to next data field byte loop spac18 ; get the rest ; mov ax,chksum ; current checksum shl ax,1 ; put two highest bits of al into ah shl ax,1 and ah,3 ; want just those two bits shr al,1 ; put al back in place shr al,1 add al,ah ; add two high bits to earlier checksum and al,03fh ; chop to lower 6 bits (mod 64) add al,20h ; apply tochar() mov [bx],al ; store that in length's header checksum inc bx xor ah,ah add chksum,ax ; add that byte to running checksum ; end of inserting Long pkt info spac19: mov cx,bx ; where we stopped+1 mov bx,offset prolog ; place where prolog section starts sub cx,bx mov prolog_len,cx dec prolog_len jcxz spac22 ; nothing mov prvlen,cx ; count sent bytes for echo suppression sub prvlen,3 ; minus LEN, SEQ, TYPE spac20: mov al,[bx] ; prolog part or al,al ; at the end? jz spac22 ; z = yes inc bx call spkout ; send byte to serial port jnc spac21 ; nc = good send jmp spac28 ; bad send spac21: loop spac20 ; do all prolog parts spac22: push es mov si,stemp ; address of pktinfo les bx,[si].datadr ; select from given data buffer mov dx,[si].datlen ; get the number of data bytes in packet add prvlen,dx ; count bytes sent spac23: or dx,dx ; any data chars remaining? jle spac25 ; le = no, finish up mov al,es:[bx] ; get a data char inc bx ; point to next char spac24: xor ah,ah add chksum,ax ; add the char to the checksum [umd] and chksum,0fffh ; keep only low order 12 bits dec dx ; say sending one character call spkout ; send it jnc spac23 ; nc = success, get more data chars pop es jmp spac28 ; bad send spac25: mov byte ptr es:[bx],0 ; terminator of data field pop es mov bx,offset epilog ; area for epilog mov cx,chksum mov bias,' '+1 ; bias for checksum char (+1 for non-blank) cmp trans.chklen,'B'-'0'; special non-blank checksum? je spac27 ; e = yes dec bias ; use ' ' for regular packets cmp trans.chklen,2 ; what kind of checksum are we using? je spac27 ; e = 2 characters jg spac26 ; g = 3 characters mov al,cl ; 1 char: get the character total mov ch,cl ; save here too (need 'cl' for shift) and al,0C0H ; turn off all but the two high order bits mov cl,6 shr al,cl ; shift them into the low order position mov cl,ch add al,cl ; add it to the old bits and al,3FH ; turn off the two high order bits. (MOD 64) add al,' ' ; add a space so the number is printable mov [bx],al ; put in the packet inc bx ; point to next char jmp short spac30 spac26: mov byte ptr[bx],0 ; null, to determine end of buffer push bx ; don't lose our place push es mov bx,ds ; set up es for crcclc mov es,bx ; es:[bx] is src of data for crcclc mov bx,offset prolog+1 ; first checksummed char, skip SOH mov cx,prolog_len xor dx,dx ; initial CRC value is 0 call crcclc ; calculate the CRC of prolog part, to cx mov dx,cx ; first part of CRC returned in cx mov si,stemp ; address of pktinfo mov cx,[si].datlen les bx,[si].datadr ; address of data call crcclc ; do CRC of data, using current CRC in dx pop es pop bx ; recover place to store more debug info push cx ; save the crc mov ax,cx ; manipulate it here and ax,0F000H ; get 4 highest bits mov cl,4 shr ah,cl ; shift over 4 bits add ah,' ' ; make printable mov [bx],ah ; add to buffer inc bx pop cx ; get back checksum value spac27: push cx ; save it for now and cx,0FC0H ; get bits 6-11 mov ax,cx mov cl,6 shr ax,cl ; shift them bits over add al,bias ; make printable mov [bx],al ; add to buffer inc bx pop cx ; get back the original and cx,003FH ; get bits 0-5 add cl,bias ; make printable mov [bx],cl ; add to buffer inc bx spac30: mov al,trans.seol ; get the EOL the other host wants xor ah,ah mov [bx],ax ; put eol and terminator in buffer inc bx spac32: xor ch,ch mov cl,trans.chklen ; checksum length cmp cl,'B'-'0' ; special non-blank checksum? jne spac32a ; ne = no mov cl,2 ; Blank checksum is a two byte affair spac32a:add prvlen,cx ; bytes sent inc cx ; plus EOL char mov bx,offset epilog; where to find data spac33: mov al,[bx] inc bx call spkout ; send it jc spac28 ; c = failure to send loop spac33 cmp debflg,0 je spac34 mov dx,offset crlf call captdol spac34: mov ax,pktcnt ; number of bytes sent in this packet add fsta.psbyte,ax ; file total bytes sent adc fsta.psbyte+2,0 ; propagate carry to high word add fsta.pspkt,1 ; statistics, count a packet being sent adc fsta.pspkt+2,0 ; ripple carry call chkcon ; check console for user interrupts mov si,stemp ; restore pkt pointer call getbtime ; get Bios time of day to dx:ax mov word ptr [si].sndtime,ax ; low order sent time mov word ptr [si].sndtime+2,dx ; high order clc ; carry clear for success ret ; return successfully spac28: mov dx,offset msgbadsnd ; say sending error in log call captdol mov ax,pktcnt ; number of bytes sent in this packet add fsta.psbyte,ax ; file total bytes sent adc fsta.psbyte+2,0 ; propagate carry to high word add fsta.pspkt,1 ; statistics, count a packet being sent adc fsta.pspkt+2,0 ; ripple carry mov si,stemp ; restore pkt pointer stc ; carry set for failure ret ; bad send SPACK ENDP spkout proc near test cardet,1 ; Carrier detect, was on and is now off? jz spkou4 ; z = no stc ; fail now ret spkou4: cmp al,255 ; possible Telnet IAC char? jne spkou2 ; ne = no cmp flags.comflg,'t' ; internal Telnet? je spkou3 ; e = yes, double the char spkou2: cmp al,trans.sdbl ; double this char? jne spkou1 ; ne = no spkou3: call spkou1 ; do it once here and again via fall through jnc spkou1 ; but again only if no failure ret ; return failure spkou1: push bx ; send char in al out the serial port push cx ; return carry clear if success push dx push es cmp debflg,0 ; recording material? je spkour ; e = no call captchr ; record in debugging log spkour: mov ah,al ; foutchr wants char in ah inc pktcnt ; count number of bytes sent in this packet call foutchr ; serial port transmitter procedure pop es pop dx pop cx pop bx ; carry set by foutchr if failure to send ret spkout endp ; Calculate the CRC of the string whose address is in ES:BX, length CX bytes. ; Returns the CRC in CX. Destroys BX and AX. ; The CRC is based on the SDLC polynomial: x**16 + x**12 + x**5 + 1. ; Original by Edgar Butt 28 Oct 1987 [ebb]. ; Enter with initial CRC in DX (normally 0). crcclc: push dx jcxz crc1 crc0: push cx mov ah,es:[bx] ; get the next char of the string inc bx xor dl,ah ; XOR input with lo order byte of CRC mov ah,dl ; copy it mov cl,4 ; load shift count shl ah,cl ; shift copy xor ah,dl ; XOR to get quotient byte in ah mov dl,dh ; high byte of CRC becomes low byte mov dh,ah ; initialize high byte with quotient xor al,al shr ax,cl ; shift quotient byte xor dl,ah ; XOR (part of) it with CRC shr ax,1 ; shift it again xor dx,ax ; XOR it again to finish up pop cx loop crc0 crc1: mov cx,dx ; return CRC in CX pop dx ret ; Receive_Packet ; This routine waits for a packet arrive from the host. Two Control-C's in a ; row from the comms line will cause a Control-C interruption exit. ; Returns ; SI = pointer to pktinfo structure, as ; [SI].SEQNUM - Packet sequence number ; [SI].DATLEN - Number of data characters ; [SI].DATADR - Address of data field for packet ; Returns AH - packet type (letter code) ; Returns: carry clear if success, carry set if failure. ; Packet construction areas: ; Prolog (8 bytes+2 nulls) null Data null Epilog null ;+----------------------------------------+---------------+---------------+ ;| SOH,LEN,SEQ,TYPE,Xlen(2-3),Xlen chksum | packet's data | chksum,EOL,HS | ;+----------------------------------------+---------------+---------------+ ; where Xlen is 2 byte (Long) or 3 byte (Extra Long) count of bytes to follow. RPACK PROC FAR mov rtemp,si ; save pkt structure address xor ax,ax ; get a zero mov fairflg,ax ; set fairness flag mov badpflag,al ; bad parity flag, clear it mov prevchar,al ; clear previous recv'd char area mov bias,al ; assume not using special B chksum mov [si].pktype,'T' ; assume 'T' type packet (timeout) mov [si].datlen,ax ; init to empty buffer mov ax,word ptr [si].sndtime ; time at which pkt was sent or ax,word ptr [si].sndtime+2 jnz rpack10 ; nz = have a send time call getbtime ; get Bios time of day to dx:ax sub ax,2 ; make two bios ticks ago sbb dx,0 mov word ptr [si].sndtime,ax ; low order sent time mov word ptr [si].sndtime+2,dx ; high order rpack10:push es les bx,[si].datadr ; caller's data buffer mov word ptr es:[bx],ax ; clear storage areas (asciiz) pop es mov word ptr prolog,ax mov prolog_len,ax mov word ptr epilog,ax mov bx,offset prolog mov pktcnt,ax ; number of bytes rcvd in packet mov al,trans.rsoh ; start of packet char mov SOHchar,al ; save, local copy is modified call rcvdeb ; setup debug display mov bx,seg prolog mov es,bx mov bx,offset prolog ; set es:bx for logging jmp rpack0a ; Get here with unexpected char (SOH or before) and echos with printable SOH rpack0: push ax ; save char which got us here mov bx,rtemp ; pktinfo address mov [bx].datlen,0 ; say no data yet mov [bx].seqnum,0ffh ; illegal value mov [bx].pktype,0 ; illegal value xor ax,ax ; get a zero push es les bx,[bx].datadr ; point to data buffer mov word ptr es:[bx],ax ; clear start of that buffer pop es mov bx,seg prolog mov es,bx ; set buffer pointer mov bx,offset prolog mov word ptr prolog,ax ; clear prolog field mov word ptr epilog,ax ; clear epilog field mov pktcnt,ax ; count of chars mov al,trans.rsoh ; start of packet char mov SOHchar,al ; save, local copy is modified mov al,trans.stime ; time to wait for start of packet mov timeval,al ; local timer value, seconds pop ax test status,stat_int ; interrupted? jz rpack0d ; z = no jmp rpack60 ; yes, exit now rpack0d:mov cx,status ; current status mov status,stat_suc ; presume success test cx,stat_echo ; doing echo processing? jnz rpack0h ; nz = yes cmp al,SOHchar ; read SOH? jne rpack0a ; ne = no, get it jmp rpack1 ; go read LEN rpack0h:cmp trans.rsoh,' ' ; printable SOH (special case)? jb rpack0a ; b = no, normal, get normal SOH ; For printable SOH we need to step over possible repetitions used in ; the general data stream. Thus we wait the length of the sent pkt. ; This will hopefully gobble up echos of the last sent packet. mov cx,prvlen ; LEN field of last sent packet jcxz rpack0a ; in case zero by some accident cmp cx,256 ; keep waiting bounded jbe rpack0f ; be = not very large mov cx,256 ; this should be enough bytes to wait rpack0f:push cx call inchr ; get and discard cx bytes pop cx test status,stat_tmo+stat_int ; timeout or user intervention? jz rpack0g ; z = no jmp rpack60 ; timeout or user intervention, quit rpack0g:loop rpack0f ; keep discarding bytes mov status,stat_suc ; assume success mov cl,trans.stime ; time to wait for start of packet mov timeval,cl ; local timer value, seconds call inchr ; this reads the EOP byte echo, maybe jnc rpack0b ; nc = not EOP, consider as SOP ; else discard echoed EOP rpack0a:mov status,stat_suc ; assume success mov cl,trans.stime ; time to wait for start of packet mov timeval,cl ; local timer value, seconds call inchr ; get a character. SOH jnc rpack0b ; nc = got one ; c=failure (eol, timeout, user intervention) test status,stat_eol ; hit eol from prev packet? jnz rpack0 ; nz = yes, restart jmp rpack60 ; timeout or user intervention rpack0b:mov ah,al ; copy the char and ah,7fh ; strip any parity bit, regardless cmp ah,SOHchar ; start of header char? je rpack0c ; e = yes, SOH jmp rpack0 ; ne = no, go until it is rpack0c:xor ah,ah ; clear the terminator byte cmp SOHchar,' ' ; printable SOHchar? jb rpack1 ; b = no (else start crippled mode) mov SOHchar,ah ; yes, set it to null for no matches rpack1: cmp flags.timflg,0 ; are timeouts turned off? je rpack1h ; e = no mov timeval,2 ; reduce local timer value to 2 secs cmp flags.comflg,'t' ; internal Telnet? jne rpack1h ; ne = no mov timeval,20 ; long timeout rpack1h:call inchr ; get a character. LEN jc rpack1a ; failure and al,7fh ; strip any parity bit cmp al,SOHchar ; start of header char? jne rpack1b ; ne = no rpack1a:jmp rpack0 ; yes, start over (common jmp point) rpack1b:mov chksum,ax ; start the checksum sub al,20h ; unchar(LEN) to binary jnc rpack1e ; nc = legal (printable) or status,stat_ptl ; set bad length status jmp rpack40 ; and quit rpack1e:mov si,rtemp mov [si].datlen,ax ; save the data count (byte) call inchr ; get a character. SEQ jc rpack1a ; c = failure and al,7fh ; strip any parity bit cmp al,SOHchar ; SOH? je rpack1a ; e = yes, then go start over add chksum,ax sub al,' ' ; get the real packet number jnc rpack1f ; nc = no overflow or status,stat_ptl ; say bad status jmp rpack40 ; and exit now rpack1f:mov si,rtemp mov [si].seqnum,al ; save the packet number. SEQ call inchr ; get a character. TYPE jc rpack1a ; c = failure and al,7fh ; strip any parity bit cmp al,SOHchar ; SOH? je rpack1a ; e = yes, then go start over mov [si].pktype,al ; save the message type cmp al,prvtyp ; echo of sent packet? jne rpack1g ; ne = no or status,stat_echo ; status is echo processing mov dx,offset msgecho ; say echo in log call captdol jmp rpack0 ; start over rpack1g:add chksum,ax ; add it to the checksum call parchk ; check parity on protocol characters call getlen ; get complicated data length (reg, lp, elp) ; into [si].datlen and kind into byte lentyp. carry set if error jnc rpack1c ; nc = packet is ok so far jmp rpack40 ; failure rpack1c: ; Start of change. ; Now determine block check type for this packet. Here we violate the layered ; nature of the protocol by inspecting the packet type in order to detect when ; the two sides get out of sync. Two heuristics allow us to resync here: ; a. I and S packets always have a type 1 checksum. ; b. A NAK never contains data, so its block check type is len - 1. mov si,rtemp ; pktinfo address mov ch,trans.chklen ; current checksum length cmp ch,'B'-'0' ; special non-blank kind? jne rpk4 ; ne = no mov ch,2 ; yes, it's a special 2-byte flavor rpk4: mov ax,[si].datlen ; length of packet information mov cl,[si].pktype ; packet type byte itself cmp cl,'S' ; "S" packet? jne rpk0 ; ne = no mov ch,1 ; S packets use one byte checksums jmp short rpk3 rpk0: cmp cl,'I' ; I packets are like S packets jne rpk1 mov ch,1 ; I packets use one byte checksums jmp short rpk3 rpk1: cmp cl,'N' ; NAK? jne rpk3 ; ne = no cmp ax,1 ; NAK, get length of data + chklen jb rpk1a ; b = impossible length cmp ax,3 ; longest NAK (3 char checksum) jbe rpk2 ; be = possible rpk1a: or status,stat_ptl ; status = bad length jmp rpack40 ; return on impossible length rpk2: mov trans.chklen,al ; remainder must be checksum type mov ch,al rpk3: sub al,ch ; minus checksum length, for all pkts sbb ah,0 ; propagate borrow mov [si].datlen,ax ; store apparent length of data field mov chklength,ch ; remember for checking below ; End of change. ; For long packets we start the real data (after the extended byte ; count 3 or 4 bytes). sub bx,offset prolog+1 ; compute length of prolog (skip SOH) mov prolog_len,bx mov si,rtemp mov dx,[si].datlen ; length of data field, excl LP header mov chrcnt,dx cmp dx,[si].datsize ; material longer than data buffer? jbe rpk8c ; be = no ; ja rpk8b ; a = yes, give up ; mov dx,trans.rlong ; longest packet we should receive ; sub dl,chklength ; minus checksum length ; sbb dh,0 ; propagate borrow ; cmp dx,chrcnt ; is data too long? ; jae rpk8c ; ae = not too big rpk8b: or status,stat_ptl ; failure status, packet too long jmp rpack40 ; too big, quit now rpk8c: les bx,[si].datadr ; point to offset of data buffer mov word ptr es:[bx],0 ; clear start of that buffer ; get DATA field characters rpack2: cmp chrcnt,0 ; any chars expected? jle rpack3 ; le = no, go do checksum call inchr ; get a character into al. DATA jc rpak2c ; c = Control-C, timeout, eol cmp al,SOHchar ; start of header char? jne rpak2b ; ne = no jmp rpack0 ; yes, then go start over rpak2b: add chksum,ax ; inchr clears AH dec chrcnt ; one less char expected jmp short rpack2 ; get another data character rpak2c: test status,stat_eol ; bare EOL in data part? jz rpak2d ; z = no and status,not stat_eol ; turn off status bit jmp short rpak2b ; and carry on regardless rpak2d: jmp rpack40 ; Control-C, timeout rpack3: and chksum,0fffh ; keep only lower 12 bits of current checksum mov bx,offset epilog ; record debugging in epilog buffer mov ax,seg epilog mov es,ax mov word ptr es:[bx],0 call inchr ; start Checksum bytes jc rpack3b ; failed mov ah,al and ah,7fh ; strip high bit cmp ah,SOHchar ; start of header char? jne rpack3a ; ne = no jmp rpack0 ; yes, then go start over rpack3a:sub al,' ' ; unchar() back to binary mov cx,chksum ; current checksum cmp chklength,2 ; which checksum length is in use? je rpack5 ; e = Two character checksum ja rpack4 ; a = Three char CRC, else one char shl cx,1 ; put two highest digits of al into ah shl cx,1 and ch,3 ; want just those two bits shr cl,1 ; put al back in place shr cl,1 add cl,ch ;add two high bits to earlier checksum and cl,03fh ; chop to lower 6 bits (mod 64) cmp cl,al ; computed vs received checksum byte (binary) je rpack3b ; e = equal, so finish up or status,stat_chk ; say checksum failure rpack3b:jmp rpack40 rpack4: mov tmp,al ; save value from packet here push bx ; three character CRC push es mov bx,seg prolog mov es,bx mov bx,offset prolog+1 ; where data for CRC is, skipping SOH mov cx,prolog_len ; length of prolog field xor dx,dx ; initial CRC is zero call crcclc ; calculate the CRC and put into CX mov dx,cx ; previous CRC mov bx,rtemp mov cx,[bx].datlen ; length of data field les bx,[bx].datadr ; data field segment call crcclc ; final CRC is in CX mov chksum,cx ; save computed checksum pop es pop bx mov ah,ch ; cx = 16 bit binary CRC of rcv'd data and ah,0f0h ; manipulate it here shr ah,1 shr ah,1 ; get 4 highest bits shr ah,1 shr ah,1 ; shift right 4 bits cmp ah,tmp ; is what we got == calculated? je rpack4a ; e = yes or status,stat_chk ; checksum failure rpack4a:call inchr ; get next character of checksum jc rpack40 ; c = failed and al,7fh ; strip high bit cmp al,SOHchar ; SOH? jne rpack4b ; ne = no jmp rpack0 ; start over rpack4b:sub al,' ' ; get back real value ; two character checksum + CRC rpack5: mov ch,al ; save last char here for now mov ax,chksum and ax,0fc0h ; get bits 11..6 mov cl,6 shr ax,cl ; shift bits cmp al,ch ; equal? je rpack5a ; e = yes mov bias,1 ; try adding bias inc al ; try 'B' method of +1 on bias cmp al,ch ; same? je rpack5a ; matched or status,stat_chk ; checksum failure rpack5a:call inchr ; get last character of checksum jc rpack40 ; c = failed and al,7fh ; strip high bit cmp al,SOHchar ; SOH? jne rpack5b ; ne = no jmp rpack0 ; e = yes rpack5b:sub al,' ' ; get back real value mov cx,chksum and cl,3FH ; get bits 0-5 add cl,bias ; try 'B' method of +1 on bias cmp al,cl ; do the last chars match? je rpack40 ; e = yes or status,stat_chk ; say checksum failure rpack40:test status,stat_tmo ; timeout? jz rpack41 ; z = no jmp rpack60 ; nz = yes rpack41:test status,stat_eol ; premature eol? jz rpack42 ; z = no or status,stat_bad ; say bad packet overall jmp short rpack45 ; now try for handshake rpack42:call inchr ; get eol char jnc rpack43 ; nc = got regular character test status,stat_int ; interrupted? jnz rpack60 ; nz = yes rpack43:and status,not stat_tmo ; ignore timeouts on EOL character test status,stat_eol ; eol char? jnz rpack44 ; nz = yes, got the EOL char and al,7fh ; strip high bit cmp al,SOHchar ; SOH already? jne rpack44 ; ne = no jmp rpack0 ; yes, start over rpack44:and status,not stat_eol ; desired eol is not an error ; test for line turn char rpack45:mov bx,portval ; if doing handshaking cmp [bx].hndflg,0 ; doing half duplex handshaking? je rpack60 ; e = no mov ah,[bx].hands ; get desired handshake char mov tmp,ah ; keep handshake char here mov bx,seg epilog ; where to store character mov es,bx mov bx,offset epilog rpack45b:call inchr ; get handshake char jnc rpack46 ; nc = regular character test status,stat_eol ; EOL char? jnz rpack46 ; nz = yes jmp short rpack48 ; timeout or user intervention rpack46:and status,not stat_eol ; ignore unexpected eol status here and al,7fh ; strip high bit cmp al,SOHchar ; SOH already? jne rpack47 ; ne = no jmp rpack0 ; yes, start over rpack47:cmp al,tmp ; compare received char with handshake jne rpack45 ; ne = not handshake, try again til timeout rpack48:and status,not stat_tmo ; ignore timeouts on handshake char ; Perform logging and debugging now rpack60:call chkcon ; check console for user interrupt cmp debflg,0 ; recording packets? je rpack66 ; e = no mov dx,offset crlf call captdol ; end current display line cmp status,stat_suc ; success? je rpack66 ; e = yes mov dx,offset msgheader ; starting "<" call captdol test status,stat_tmo ; timeout? jz rpack61 ; no mov dx,offset msgtmo ; say timeout in log call captdol mov si,rtemp mov ah,'T' ; return packet type in ah mov [si].pktype,ah ; say 'T' type packet (timeout) rpack61:test status,stat_chk ; checksum bad? jz rpack62 ; z = no mov dx,offset msgchk call captdol rpack62:test status,stat_ptl ; packet too long? jz rpack63 ; z = no mov dx,offset msgptl call captdol rpack63:test status,stat_int ; user interruption? jz rpack64 ; z = no mov dx,offset msgint call captdol rpack64:test status,stat_bad ; premature EOL? jz rpack65 ; z = no mov dx,offset msgbad call captdol rpack65:mov dx,offset msgtail ; end of error cause field call captdol rpack66:mov ax,pktcnt ; number of bytes received in packet add fsta.prbyte,ax ; file total received pkt bytes adc fsta.prbyte+2,0 ; propagate carry to high word add fsta.prpkt,1 ; file received packets adc fsta.prpkt+2,0 ; ripple carry mov si,rtemp ; restore pkt pointer mov ah,[si].pktype ; return packet TYPE in ah cmp status,stat_suc ; successful so far? jne rpack72 ; ne = no cmp chkparflg,0 ; do parity checking? je rpack71 ; e = no mov chkparflg,0 ; do only once test badpflag,80h ; get parity error flagging bit jz rpack71 ; z = no parity error mov bx,portval mov cl,badpflag ; get new parity plus flagging bit and cl,7fh ; strip flagging bit mov [bx].parflg,cl ; force new parity rpack71:clc ; carry clear for success ret rpack72:stc ; carry set for failure ret ; failure exit RPACK ENDP ; Get packet if enough bytes for minimal pkt and SOP has been seen. Returns ; carry set if unable, else use Rpack to deliver the packet. Call the same ; as Rpack. peekreply proc far call peekcom ; get comms SOP count jnc peekrp1 ; nc = have some bytes ret ; return carry set for nothing to do peekrp1:cmp cx,6 ; enough for basic NAK? jb peekrp2 ; be = no jmp rpack ; go decode the packet peekrp2:stc ; say nothing to do ret peekreply endp ; Check Console (keyboard). Return carry setif "action" chars: cr for forced ; timeout, Control-E for force out Error packet, Control-C for quit work now. ; Return carry clear on Control-X and Control-Z as these are acted upon by ; higher layers. Consume and ignore anything else. chkcon: call isdev ; is stdin a device and not a disk file? jnc chkco5 ; nc = no, a disk file so do not read here mov dl,0ffh mov ah,dconio ; read console int dos jz chkco5 ; z = nothing there and al,1fh ; make char a control code cmp al,CR ; carriage return? je chkco3 ; e = yes, simulate timeout cmp al,'C'-40h ; Control-C? je chkco1 ; e = yes cmp al,'E'-40h ; Control-E? je chkco1 ; e = yes cmp al,'X'-40h ; Control-X? je chkco4 ; e = yes cmp al,'Z'-40h ; Control-Z? je chkco4 ; record it, take no immmediate action here cmp al,'Q'-40h ; Control-Q? je chkco6 ; e = yes or al,al ; scan code being returned? jnz chkco5 ; nz = no, ignore ascii char mov ah,dconio ; read and discard second byte mov dl,0ffh int dos jmp short chkco5 ; else unknown, ignore chkco1: or al,40h ; make Control-C-E printable mov flags.cxzflg,al ; remember what we saw chkco2: or status,stat_int ; interrupted stc ret ; act now chkco3: or status,stat_tmo ; CR simulates timeout stc ret ; act now chkco4: or al,40h ; make control-X-Z printable mov flags.cxzflg,al ; put into flags clc ; do not act on them here ret chkco5: cmp flags.cxzflg,'C' ; control-C intercepted elsewhere? je chkco2 ; e = yes clc ; else say no immediate action needed ret chkco6: xchg ah,al ; put Control-Q in AH for transmission call spkout ; send it now jmp short chkco5 getlen proc near ; compute packet length for short & long types ; returns length in [si].datlen and ; length type (0, 1, 3) in local byte lentyp ; returns length of data + checksum mov si,rtemp mov ax,[si].datlen ; get LEN byte value and ax,7fh ; clear unused high byte and parity bit cmp al,3 ; regular packet has 3 or larger here jb getln1 ; b = long packet sub [si].datlen,2 ; minus SEQ and TYPE = DATA + CHKSUM mov lentyp,3 ; store assumed length type (3 = regular) clc ; clear carry for success ret getln1: push cx ; counter for number of length bytes mov lentyp,0 ; store assumed length type 0 (long) mov cx,2 ; two base-95 digits or al,al ; is this a type 0 (long packet)? jz getln2 ; z = yes, go find & check length data mov lentyp,1 ; store length type (1 = extra long) inc cx ; three base 95 digits cmp al,1 ; is this a type 1 (extra long packet)? je getln2 ; e = yes, go find & check length data pop cx or status,stat_ptl ; say packet too long (an unknown len code) stc ; set carry bit to say error ret getln2: ; chk header chksum and recover binary length push dx ; save working reg xor ax,ax ; clear length accumulator, low part mov [si].datlen,ax ; clear final length too getln3: xor dx,dx ; ditto, high part mov ax,[si].datlen ; length to date mul ninefive ; multiply accumulation (in ax) by 95 mov [si].datlen,ax ; save results push cx call inchr ; read another serial port char into al pop cx jc getln4 ; c = failure add chksum,ax sub al,20h ; subtract space, apply unchar() mov si,rtemp add [si].datlen,ax ; add to overall length count loop getln3 ; cx preset earlier for type 0 or type 1 mov dx,chksum ; get running checksum shl dx,1 ; get two high order bits into dh shl dx,1 and dh,3 ; want just these two bits shr dl,1 ; put low order part back shr dl,1 add dl,dh ; add low order byte to two high order bits and dl,03fh ; chop to lower 6 bits (mod 64) add dl,20h ; apply tochar() push dx call inchr ; read another serial port char pop dx jc getln4 ; c = failure add chksum,ax cmp dl,al ; our vs their checksum, same? je getln5 ; e = checksums match, success getln4: or status,stat_chk ; checksum failure pop dx ; unsave regs (preserves flags) pop cx stc ; else return carry set for error ret getln5: pop dx ; unsave regs (preserves flags) pop cx clc ; clear carry (say success) ret getlen endp ; Get char from serial port into al, with timeout and console check. ; Return carry set if timeout or console char or EOL seen, ; return carry clear and char in AL for other characters. ; Sets status of stat_eol if EOL seen. ; Fairflg allows occassional reads from console before looking at serial port. inchr proc near mov timeit,0 ; reset timeout flag (do each char separately) push es ; save debug buffer pointer es:bx push bx cmp fairflg,500 ; look at console first every now and then jbe inchr1 ; be = not console's turn yet mov fairflg,0 ; reset fairness flag for next time call chkcon ; check console jnc inchr1 ; nc = nothing to interrupt us pop bx ; clean stack pop es ret ; return failure for interruption inchr1: call prtchr1 ; read a serial port character jc inchr2 ; c = nothing there mov timeit,0 ; say not timing missing byte pop bx ; here with char in al from port pop es ; debug buffer pointer mov ah,al ; copy char to temp place AH and ah,7fh ; strip parity bit from work copy and al,parmsk ; apply 7/8 bit parity mask cmp debflg,0 ; recording material? je inchr1a ; e = no call captchr ; log char in al inchr1a:inc pktcnt ; count received byte cmp al,trans.rign ; char in al to be ignored? je inchr ; e = yes, do so test flags.remflg,dserver ; acting as a server? jz inchr6 ; z = no cmp ah,'C'-40h ; Control-C from comms line? jne inchr6 ; ne = no cmp ah,prevchar ; was previous char also Control-C? jne inchr6 ; ne = no cmp ah,SOHchar ; could this also be an SOH? je inchr6 ; e = yes, do not exit cmp ah,trans.reol ; could this also be an EOL? je inchr6 ; e = yes test denyflg,finflg ; is FIN enabled? jnz inchr6 ; nz = no, ignore server exit cmd mov flags.cxzflg,'C'; set Control-C flag or status,stat_int+stat_eol ; say interrupted and End of Line mov al,ah ; use non-parity version xor ah,ah ; always return with high byte clear mov es:[bx],ax ; store char and null in debugging buffer inc bx stc ; exit failure ret inchr6: mov prevchar,ah ; remember current as previous char cmp ah,trans.reol ; eol char we want? je inchr7 ; e = yes, ret with carry set xor ah,ah ; always return with high byte clear mov es:[bx],ax ; store char and null in buffer inc bx clc ; char is in al ret inchr7: or status,stat_eol ; set status appropriately xor ah,ah ; always return with high byte clear mov es:[bx],ax ; store char and null in buffer inc bx stc ; set carry to say eol seen ret ; and return qualified failure inchr2: push bx mov bx,portval cmp [bx].portrdy,0 ; is port not-ready? pop bx jne inchr2c ; ne = no, port is ready, just no char or status,stat_int ; interrupted ;;;;;;;;; mov flags.cxzflg,'C'; set Control-C flag stc ; return failure (interruption) pop bx pop es ret inchr2c:call chkcon ; check console jnc inchr2a ; nc = nothing to interrupt us pop bx ; clean stack pop es ret ; return failure for interruption inchr2a:cmp flags.timflg,0 ; are timeouts turned off? je inchr1 ; e = yes, just check for more input cmp timeval,0 ; turned off running timeouts on receive? je inchr1 ; e = yes cmp trans.stime,0 ; doing time outs? jne inchr2b ; ne = yes jmp inchr1 ; go check for more input inchr2b:push cx ; save regs push dx cmp timeit,0 ; have we gotten time of day for first fail? jne inchr4 ; ne = yes, just compare times push ax push dx call krto ; get current round trip timeout value call getbtime ; get Bios time to dx:ax add ax,k_rto ; add Bios ticks of timeout interval adc dx,0 mov word ptr rptim,ax mov word ptr rptim+2,dx pop dx pop ax mov timeit,1 ; say have tod of timeout jmp short inchr4d inchr4: call getbtime ; get Bios time cmp dx,word ptr rptim+2 ; high order word ja inchr4c ; a = we are late jb inchr4d ; b = we are early cmp ax,word ptr rptim ; low order word ja inchr4c ; a = we are late jmp inchr4d ; not timed out yet inchr4c:or status,stat_tmo ; say timeout pop dx pop cx pop bx pop es stc ; set carry bit ret ; failure inchr4d:pop dx pop cx jmp inchr1 ; not timed out yet inchr endp ; Packet Debug display routines rcvdeb: test flags.debug,logpkt ; In debug mode? jnz rcvde1 ; nz = yes test flags.capflg,logpkt ; log packets? jnz rcvde1 ; nz = yes ret ; no rcvde1: mov debflg,'R' ; say receiving jmp short deb1 snddeb: test flags.debug,logpkt ; In debug mode? jnz sndde1 ; nz = yes test flags.capflg,logpkt ; log packets? jnz sndde1 ; yes ret ; no sndde1: mov debflg,'S' ; say sending deb1: push di ; Debug. Packet display test flags.debug,logpkt ; is debug active (vs just logging)? jz deb1e ; z = no, just logging cmp fmtdsp,0 ; non-formatted display? je deb1d ; e = yes, skip extra line clearing cmp debflg,'R' ; receiving? je deb1a ; e = yes call sppos1 ; spack: cursor position jmp short deb1b deb1a: call rppos1 ; rpack: cursor position deb1b: mov cx,4 ; clear 4 lines deb1c: push cx call clearl1 ; clear the line mov dx,offset crlf mov ah,prstr ; display int dos pop cx loop deb1c cmp debflg,'R' ; receiving? je deb1d ; e = yes call sppos1 ; reposition cursor for spack: jmp short deb1e deb1d: call rppos1 ; reposition cursor for rpack: deb1e: mov dx,offset spmes ; spack: message cmp debflg,'R' jne deb2 ; ne = sending mov dx,offset rpmes ; rpack: message deb2: mov colcnt,0 ; number of columns used so far mov linecnt,0 ; no lines completed yet call captdol ; record dollar terminated string in Log file pop di ret captdol proc near ; write dollar sign terminated string in dx ; to the capture file (Log file). push ax ; save regs push si push es mov si,dx ; point to start of string cld captdo1:lodsb ; get a byte into al cmp al,'$' ; at the end yet? je captdo3 ; e = yes or al,al ; asciiz? jz captdo3 ; z = yes, this is also the end inc colcnt cmp al,lf ; new line? jne captdo4 ; ne = no inc linecnt ; count displayed lines mov colcnt,0 ; start of new line captdo4:test flags.debug,logpkt ; debug display active? jz captdo2 ; z = no cmp linecnt,4 ; four lines used on screen? jae captdo2 ; ae = yes, omit more lines push ax mov dl,al mov ah,conout int dos ; display char in dl pop ax captdo2:test flags.capflg,logpkt ; logging active? jz captdo1 ; z = no call fpktcpt ; record the char, pktcpt is in msster.asm jmp short captdo1 ; repeat until dollar sign is encountered captdo3:pop es pop si pop ax ret captdol endp captchr proc near ; record char in AL into the Log file test flags.debug,logpkt ; debug display active? jnz captch1 ; nz = yes test flags.capflg,logpkt ; logging active? jnz captch1 ; nz = yes ret captch1:push ax push es test al,80h ; high bit set? jz captch2 ; z = no push ax mov al,'~' call captworker ; record the char pop ax and al,not 80h captch2:cmp al,DEL ; DEL? je captch2a ; e = yes cmp al,' ' ; control char? jae captch4 ; ae = no captch2a:add al,40h ; uncontrollify the char push ax ; save char in dl mov al,5eh ; show caret before control code call captworker ; record the char cmp colcnt,70 ; exhausted line count yet? jb captch3 ; b = not yet mov al,CR call captworker mov al,LF call captworker captch3:pop ax ; recover char in dl captch4:call captworker ; record the char cmp colcnt,70 jb captch5 ; b = not yet mov al,CR call captworker mov al,LF call captworker captch5:pop es pop ax ret captchr endp captworker proc near inc colcnt ; count new character cmp al,LF ; new line? jne captw3 ; ne = no inc linecnt ; count displayed lines mov colcnt,0 ; start of new line captw3: test flags.debug,logpkt ; debug display active? jz captw1 ; z = no cmp linecnt,4 ; four lines used on screen? jae captw1 ; ae = yes, omit more lines push ax mov dl,al mov ah,conout int dos pop ax captw1: test flags.capflg,logpkt ; logging active? jz captw2 ; z = no call fpktcpt ; log to file captw2: ret captworker endp parchk proc near ; check parity of pkt prolog chars cmp chkparflg,0 ; ok to check parity? jne parchk0 ; ne = yes ret parchk0:push ax push bx push cx push dx mov ax,word ptr prolog ; first two prolog chars or ax,word ptr prolog+2 ; next two test ax,8080h ; parity bit set? jz parchk7 ; z = no mov parmsk,7fh ; set parity mask for 7 bits cmp badpflag,0 ; said bad parity once this packet? jne parchk7 ; ne = yes mov cx,4 ; do all four protocol characters xor dx,dx ; dl=even parity cntr, dh=odd parity mov bx,offset prolog parchk1:mov al,[bx] ; get a char inc bx ; point to next char or al,al ; sense parity jpo parchk2 ; po = odd parity inc dl ; count even parity jmp short parchk3 parchk2:inc dh ; count odd parity parchk3:loop parchk1 ; do all four chars cmp dl,4 ; got four even parity chars? jne parchk4 ; ne = no mov badpflag,parevn+80h ; say even parity and flagging bit mov dx,offset msgbadpare ; say using even parity jmp short parchk6 parchk4:cmp dh,4 ; got four odd parity chars? jne parchk5 ; ne = no mov badpflag,parodd+80h ; say odd parity and flagging bit mov dx,offset msgbadparo ; say using odd parity jmp short parchk6 parchk5:mov badpflag,parmrk+80h ; say mark parity and flagging bit mov dx,offset msgbadparm ; say using mark parity parchk6:call ermsg1 call captdol ; write in log file too parchk7:pop dx pop cx pop bx pop ax ret parchk endp ; General packet buffer structure manipulation routines. The packet buffers ; consist of a arrays of words, bufuse and buflist, an array of pktinfo ; structure packet descriptors, and a malloc'd main buffer. ; Each pktinfo member describes a packet by holding the address (seg:offset ; within segment data) of the data field of a packet (datadr), the length of ; that field in bytes (datsize), the number of bytes currently occupying that ; field (datlen), the packet sequence number, an ack-done flag byte, and the ; number of retries of the packet. ; The data field requires a null terminator byte in packet routines rpack and ; spack. Trans.windo buffers are constructed by procedure makebuf. ; Bufuse is an array holding an in-use flag for each pktinfo member; 0 means ; the member is free, otherwise a caller has allocated the member via getbuf. ; Buflist holds the address (offset in segment 'data') of each pktinfo member, ; for rapid list searching. ; ; Packet structures are constructed and initialized by procedure makebuf. ; Other procedures below access the members in various ways. Details of ; buffer construction should remain local to these routines. ; Generally, SI is used to point to a pktinfo member and AL holds a packet ; sequence number (0 - 63 binary). BX and CX are used for some status reports. ; ; bufuse buflist pktlist (group of pktinfo members) ; ------- ------- ------------------------------------------- ; 0 for unused | datadr,datlen,datsize,seqnum,ackdone,numtry | ; pointers to ->+ datadr,datlen,datsize,seqnum,ackdone,numtry | ; 1 for used | datadr,datlen,datsize,seqnum,ackdone,numtry | ; etc ; ; Construct new buffers, cleared, by computing the amount of DOS free memory, ; allocating as much as (window slots * desired packet length). When there ; is not enough memory shorten the packet length to yield the desired number ; of window slots. ; This is called two ways: a protocol initialization stage with one normal ; length packet and a post negotiation stage with a stated length of packet. ; The first state finds the maximum available memory for packet buffers as ; well as allocating one regular packet from it. The second stage always ; follows spar() and that routine needs the maximum memory figure. ; Enter with trans.windo equal to the number of buffer slots and CX equal ; to the length of each buffer (we add one byte for null terminator here). ; Return word maxbufparas as number of paragraphs in free memory before ; allocations are done here. makebuf proc far push ax push bx push cx push dx push si push di mov di,cx ; save real packet length mov ax,pbufseg ; current buffer segment or ax,ax ; segment allocated already? jz makebu1 ; z = no push es mov es,ax ; allocated segment mov ah,freemem ; free it int dos pop es makebu1:mov bx,0ffffh ; allocate biggest chunk of memory mov ah,alloc int dos ; must fail, bx gets number paras free mov maxbufparas,bx ; report max paragraphs available mov al,trans.windo ; number of window slots xor ah,ah or ax,ax ; zero? jnz makebu2 ; nz = no inc ax mov trans.windo,al ; zero means one slot makebu2:add cx,15 ; round, space for null at end of pkt and cx,0fff0h ; and truncate mul cx ; times pkt size per window slot mov cx,4 makebu3:shr dx,1 ; divide double word by 16, to paras rcr ax,1 loop makebu3 ; ax gets paragraphs wanted cmp ax,bx ; wanted versus available jae makebu4 ; ae = want more than available mov bx,ax ; set bx to qty paragraphs wanted makebu4:mov cx,bx ; remember desired paragraphs mov ah,alloc ; allocate a memory block int dos mov pbufseg,ax ; seg of memory area mov ax,bx ; number paragraphs allocated mov cl,trans.windo ; number of window slots xor ch,ch mov bufnum,cx ; number of buffers = window slots cmp cx,1 ; just one window slot? je makebu5 ; e = yes, save division xor dx,dx div cx ; paras per window slot to ax makebu5:mov dx,ax ; keep paragraphs per buffer in dx ; mov di,ax ; paragraphs per buffer ; mov cl,4 ; shl di,cl ; bytes per buffer (9040 max) mov cx,bufnum ; number of buffers wanted mov ax,pbufseg ; seg where buffer starts mov si,offset pktlist ; where pktinfo group starts xor bx,bx ; index (words) makebu6:mov bufuse[bx],0 ; say buffer slot is not used yet mov buflist[bx],si ; pointer to pktinfo member mov word ptr [si].datadr,0 ; offset of data field mov word ptr [si].datadr+2,ax ; segment of data field mov [si].datsize,di ; data buffer size, bytes mov [si].numtry,0 ; clear number tries for this buffer mov [si].ackdone,0 ; not acked yet mov [si].seqnum,0 ; a dummy sequence number add si,size pktinfo ; next pktinfo member add ax,dx ; pointer to next buffer segment add bx,2 ; next buflist slot loop makebu6 ; make another structure member mov windused,0 ; no slots used yet mov winusedmax,0 ; max slots used mov word ptr cwindow,1 ; initial congestion window mov ax,bufnum ; max slots availble mov ssthresh,al ; save as congestion threshold clc ; success pop di pop si pop dx pop cx pop bx pop ax ret makebuf endp ; Allocate a buffer. Return carry clear and SI pointing at fresh pktinfo ; structure, or if failure return carry set and all regs preserved. getbuf proc far push ax push cx push si xor si,si ; index mov cx,bufnum ; number of buffers jcxz getbuf2 ; 0 means none, error mov al,windused ; window slots in use now cmp al,cwindow ; max slots allowed at this time jae getbuf2 ; ae = all in use, sorry getbuf1:cmp bufuse[si],0 ; is this slot in use? je getbuf3 ; e = no, grab it add si,2 ; try next slot loop getbuf1 ; fall through on no free buffers getbuf2:pop si ; get here if all are in use pop cx pop ax stc ; return failure, si preserved ret getbuf3:mov bufuse[si],1 ; mark buffer as being in use inc windused ; one more slot in use mov si,buflist[si] ; address of pktinfo member mov al,pktnum ; next sequence number to be used mov [si].seqnum,al ; use it as sequence number mov [si].datlen,0 ; no data in packet mov [si].numtry,0 ; clear number tries for this buffer mov [si].ackdone,0 ; not acked yet pop cx ; discard originally saved si pop cx pop ax clc ; return success, buffer ptr in si ret getbuf endp ; Release all buffers (marks them as free, releases buffer memory). bufclr proc far push ax push cx push di push es push ds pop es mov cx,maxwind ; max number of buffers xor ax,ax mov di,offset bufuse ; buffer in-use list cld rep stosw ; store zeros to clear the buffers mov windused,0 ; number now used (none) mov ax,pbufseg ; segment of memory mov es,ax ; es = segment affected mov ah,freemem ; free it int dos xor ax,ax mov pbufseg,ax ; say no buffer segment pop es pop di pop cx pop ax ret bufclr endp ; Release buffer whose pktinfo pointer is in SI. ; Return carry clear if success, or carry set if failure. bufrel proc far push bx push cx mov cx,bufnum ; number of buffers xor bx,bx bufrel1:cmp buflist[bx],si ; compare addresses, match? je bufrel2 ; e = yes, found it add bx,2 loop bufrel1 pop cx pop bx stc ; no such buffer ret bufrel2:mov bufuse[bx],0 ; say buffer is no longer in use dec windused ; one less used buffer pop cx pop bx clc ret bufrel endp ; Grow sliding window slot count in accordance with Van Jacobson's ; TCP/IP congestion avoidance paper. ; An ACK has been received for a packet in the window. ; Inc cwindow by 1 if cwindow is below ssthresh, else inc by 1/cwindow. ; Keep fractions of a slot in byte cwindow+1. ; Limit cwindow to max slots in system, bufnum. windgrow proc far push ax push cx xor ah,ah mov al,cwindow ; congestion window, slots cmp al,ssthresh ; above congestion avoidance threshold? jae windgr1 ; ae = yes, grow slowly inc cwindow ; fast opening, add one window slot jmp short windgr2 windgr1:mov al,thirtytwo ; congestion avoidance slow growth xor ah,ah div cwindow ; 32 / cwindow, al=quo, ah=rem add cwindow+1,al ; increment qty of 32nds of slots mov al,cwindow+1 mov cl,5 ; divide cwindow+1 by 32 shr al,cl ; get whole number of window slots jz windgr2 ; z = none inc cwindow ; increment cwindow mov cwindow+1,0 ; clear fraction windgr2:mov al,cwindow ; limit cwindow to max slots (bufnum) cmp al,byte ptr bufnum ; exceeds max number of window slots? jbe windgr3 ; be = no mov al,byte ptr bufnum ; limit windgr3:mov cwindow,al ; max window slots allowed at this time pop cx pop ax ret windgrow endp ; Shrink sliding window slots. ssthresh <- cwindow / 2, cwindow <- 1. windshrink proc far push ax mov al,cwindow ; current congestion window, slots shr al,1 ; divide by two jnz windsh1 ; nz = not too small mov al,1 ; must have at least one slot available windsh1:mov ssthresh,al ; new congestion threshold mov word ptr cwindow,1 ; back to slow start pop ax ret windshrink endp ; Returns in BX the "packet pointer" for the buffer with the same seqnum as ; provided in AL. Returns carry set if no match found. Modifies BX. pakptr proc far push cx push di mov cx,bufnum ; number of buffers xor di,di ; buffer index for tests pakptr1:cmp bufuse[di],0 ; is buffer vacant? je pakptr2 ; e = yes, ignore mov bx,buflist[di] ; bx = address of pktinfo member cmp al,[bx].seqnum ; is this the desired sequence number? je pakptr3 ; e = yes pakptr2:add di,2 ; next buffer index loop pakptr1 ; do next test xor bx,bx ; say no pointer stc ; set carry for failure pop di pop cx ret pakptr3:clc ; success, BX has buffer pointer pop di pop cx ret pakptr endp ; Returns in AH count of packets with a given sequence number supplied in AL ; and returns in BX the packet pointer of the last matching entry. ; Used to detect duplicated packets. pakdup proc far push cx push dx push di mov cx,bufnum ; number of buffers xor di,di ; buffer index for tests xor ah,ah ; number of pkts with seqnum in al mov dx,-1 ; a bad pointer pakdup1:cmp bufuse[di],0 ; is buffer vacant? je pakdup2 ; e = yes, ignore mov bx,buflist[di] ; bx = address of pktinfo member cmp al,[bx].seqnum ; is this the desired sequence number? jne pakdup2 ; ne = no mov dx,bx ; yes, remember last pointer inc ah ; count a found packet pakdup2:add di,2 ; next buffer index loop pakdup1 ; do next test mov bx,dx ; return last matching member's ptr pop di pop dx pop cx or ah,ah ; any found? jz pakdup3 ; z = no clc ; return success ret pakdup3:stc ; return failure ret pakdup endp ; Find sequence number of first free window slot and return it in AL, ; Return carry set and al = windlow if window is full (no free slots). firstfree proc far mov al,windlow ; start looking at windlow mov ah,al add ah,trans.windo and ah,3fh ; ah = 1+top window seq number, mod 64 firstf1:push bx call pakptr ; buffer in use for seqnum in AL? pop bx jc firstf2 ; c = no, seq number in not in use inc al ; next sequence number and al,3fh ; modulo 64 cmp al,ah ; done all yet? jne firstf1 ; ne = no, do more mov al,windlow ; a safety measure stc ; carry set to say no free slots ret firstf2:clc ; success, al has first free seqnum ret firstfree endp ; Check sequence number for lying in the current or previous window or ; outside either window. ; Enter with sequence number of received packet in [si].seqnum. ; Returns: ; carry clear and cx = 0 if [si].seqnum is within the current window, ; carry set and cx = -1 if [si].seqnum is inside previous window, ; carry set and cx = +1 if [si].seqnum is outside any window. chkwind proc far mov ch,[si].seqnum ; current packet sequence number mov cl,trans.windo ; number of window slots sub ch,windlow ; ch = distance from windlow jc chkwin1 ; c = negative result cmp ch,cl ; span greater than # window slots? jb chkwinz ; b = no, in current window sub ch,64 ; distance measured the other way neg ch cmp ch,cl ; more than window size? ja chkwinp ; a = yes, outside any window jmp short chkwinm ; else in previous window ; sequence number less than windlow chkwin1:neg ch ; distance, positive, cl >= ch cmp ch,cl ; more than window size? ja chkwin2 ; a = yes, maybe this window jmp short chkwinm ; no, in previous window chkwin2:sub ch,64 ; distance measured the other way neg ch cmp ch,cl ; greater than window size? jb chkwinz ; b = no, in current window ; else outside any window chkwinp:mov cx,1 ; outside any window stc ; carry set for outside current window ret chkwinz:xor cx,cx ; inside current window clc ; carry clear, inside current window ret chkwinm:mov cx,-1 ; in previous window stc ; carry set for outside current window ret chkwind endp ; Return Bios time of day in dx:ax getbtime proc far push es push bx push cx xor ax,ax mov es,ax getbt1: mov cx,es:[biosclk+0] mov dx,es:[biosclk+2] in al,61h ; pause in al,61h mov ax,es:[biosclk+0] mov bx,es:[biosclk+2] cmp ax,cx jne getbt1 ; ne = time jumped cmp bx,dx jne getbt1 ; ne = time jumped mov ax,[bp+4+0] cwd ; sign extend ax to dx add ax,cx adc dx,bx pop cx ; end critical section pop bx pop es ret getbtime endp ; Compute Kermit round trip timeout, k_rto, in Bios clock ticks. ; Enter with SI pointing to packet structure of sent packet krto proc far cmp flags.comflg,'t' ; internal Telnet? je krto5 ; e = yes, get rto from it push ax push dx call getbtime ; get current Bios time to dx:ax sub ax,word ptr [si].sndtime ; minus time at which pkt was sent sbb dx,word ptr [si].sndtime+2 jc krto4 ; c = negative elapsed time (midnight) ; assume rtt Bios ticks fits into 13 bits (in AX), 450 minutes shl ax,1 shl ax,1 shl ax,1 ; rtt * 8 sub ax,k_sa ; minus 8 * smoothed average rtt mov dx,ax ; dx = 8 * rtt_error sar ax,1 sar ax,1 sar ax,1 ; ax = rtt_error add k_sa,ax ; k_sa += rtt_error or ax,ax ; negative? jge krto1 ; ge = no neg ax ; make error positve krto1: mov dx,k_sd ; 8 * std dev shr dx,1 shr dx,1 ; k_sd >> 2 sub ax,dx ; rtt_error -= (k_sd >> 2) add k_sd,ax ; k_sd += rtt_error mov ax,k_sa shr ax,1 shr ax,1 add ax,k_sd shr ax,1 ; k_rto = ((k_sa >> 2) + k_sd) >> 1 cmp ax,60 * 18 * 3 ; more than 3 * 60 seconds? jle krto2 ; le = no mov ax,60 * 18 * 3 ; clamp at 3 * 60 seconds krto2: cmp ax,9 ; below floor of 9 Bios clock ticks jge krto3 ; ge = no mov ax,9 ; set floor krto3: mov k_rto,ax ; predicted round trip time out, ticks krto4: pop dx pop ax ret krto5: push ax ; Internal TCP/IP stack provides rto mov ax,tcp_rto ; Bios ticks from TCP/IP stack add ax,18 ; bias up by one second mov k_rto,ax pop ax ret krto endp code1 ends end