PAGE 62,132 ; ; TCP SUPPORT FOR IPGATE ; TCPMSS EQU 196 IPDATA SEGMENT PUBLIC 'DATA' EXTRN MYIPADDR:BYTE,IP_Q:WORD,NEWVALUE:WORD NUMBEROFSESSIONS EQU 8 ; ; ; 32 BIT ARITHMETIC MACROS ; ASSUME CS:CODE,DS:IPDATA,ES:IPDATA CR EQU 0DH LF EQU 0AH INC4 MACRO X ADD WORD PTR X,1 ADC WORD PTR X&+2,0 ENDM ADD4 MACRO X,Y ADD WORD PTR X,Y ADC WORD PTR X&+2,0 ENDM ; SUB4 MACRO X,Y SUB WORD PTR X,Y SBB WORD PTR X&+2,0 ENDM ; ; COPY4 COPIES ALLOWING FOR BYTE AND WORD REVERSAL ; COPY4 MACRO X,Y MOV AX,WORD PTR Y MOV DX,WORD PTR Y&+2 XCHG AH,AL XCHG DH,DL MOV WORD PTR X&+2,AX MOV WORD PTR X,DX ENDM ; ; ; COPY2 COPIES ALLOWING FOR BYTE REVERSAL ; COPY2 MACRO X,Y MOV AX,WORD PTR Y XCHG AH,AL MOV WORD PTR X,AX ENDM ; ; CMP4 COMPARES 2 4-BYTE NUMBERS IN MACHINE ORDER ; CMP4 MACRO X,Y LOCAL EXIT MOV AX,WORD PTR Y&+2 CMP WORD PTR X&+2,AX JNE EXIT ; ; HI ORDER THE SAME - COMPARE LOW ORDER ; MOV AX,WORD PTR Y CMP WORD PTR X,AX EXIT: ENDM ; ; CMP4X COMPARES ALLOWING FOR BYTE REVERSAL - 1ST OPERAND IS IN ; MACHINE ORDER, SECOND IN HI-LO ORDER ; CMP4X MACRO X,Y LOCAL EXIT MOV AX,WORD PTR Y XCHG AH,AL CMP WORD PTR X&+2,AX JNE EXIT ; ; HI ORDER THE SAME - COMPARE LOW ORDER ; MOV AX,WORD PTR Y&+2 XCHG AH,AL CMP WORD PTR X,AX EXIT: ENDM ; ; MOV4 MOVES 4 BYTES ; MOV4 MACRO X,Y MOV AX,WORD PTR Y MOV WORD PTR X,AX MOV AX,WORD PTR Y&+2 MOV WORD PTR X&+2,AX ENDM ; ; MOV2 MOVES 2 BYTES ; MOV2 MACRO X,Y MOV AX,WORD PTR Y MOV WORD PTR X,AX ENDM ; ; ; DEVICE CONTROL TABLE FOR TCP/IP SESSIONS ; DCTENTRY STRUC DCTSESSNO DB 0 ; SESSION WITHIN WORKSTATION ; HOSTPORT DB 0 ; CORRESPONDING BPQHOST STREAM SESSIONTYPE DB 0 ; SESSION TYPE INDICATOR - TELNET OR FTP RX_Q DW 0 TX_QUEUE DW 0 ; TX MSGS QUEUED FOR THIS SESSION ACK_QUEUE DW 0 ; FRAMES WAITING ACK ; WATCHDOG DW 0 ; STUCK SESSION DETECTOR ; LOCALADDR DD 0 ; OUR IP ADDR LOCALSOCKET DW 0 ; OUR SOCKET REMOTEADDR DD 0 REMOTESOCKET DW 0 TCPSTATE DB 0 SND_UNA DD 0 SND_NXT DD 0 SND_PTR DD 0 SND_WND DW 0 TCP_ISS DD 0 ; INITIAL SEND SEQ RCV_NXT DD 0 RCV_WND DW 0 TCP_MSS DW 0 ; MAX SEG SIZE (FROM OTHER END) TCP_RETRY DB 0 ; RETRY COUNT TCP_FLAGS DB 0 TCP_WINDOW DW 0 ; RX WINDOW (IE SEND LIMIT) TCP_RCVQ DW 0 ; RX QUEUE TCP_TIMER_START DW 0 TCP_TIMER_COUNT DW 0 LASTSTATE DB 0 ; LAST REPORTED STATE OPENREQUEST DB 0 ; SET IF USER HAS REQUESTED A SESSION CLOSEREQUEST DB 0 ; SET WHEN USER REQUESTS CLOSE WINDOW_CLOSED DB 0 ; FLAG TO REPORT WINDOW OPENING DCTENTRY ENDS ; ; TCP STATE EQUATES ; CLOSED EQU 0 LISTEN EQU 1 SYN_SENT EQU 2 SYN_RECEIVED EQU 3 ESTABLISHED EQU 4 FINWAIT1 EQU 5 FINWAIT2 EQU 6 CLOSE_WAIT EQU 7 CLOSING EQU 8 LAST_ACK EQU 9 TIME_WAIT EQU 10 ; TCP_FLAGS ; FORCE EQU 1 RETRAN EQU 2 ACTIVESESSIONS DW 8 ; NUMBER OF SESSIONS ACTUALLY IN USE HOSTADDR DB 44,131,4,19 ; FOR TESTING DCT DB NUMBEROFSESSIONS*TYPE DCTENTRY DUP (0) REFUSEDCT DB TYPE DCTENTRY DUP (0) ; EXTRA FOR REFUSING INCOMING CALLS STATSFIELD LABEL BYTE ; ; ETHERNET STATS ; TIMEACTIVE DD 0 BAD_CHECKSUMS DD 0 ; CODE RAN MORE THAN 1 TICK NO_NB_SENDS DD 0 ET_COUNT DD 0 ; PACKETS RECEIVED ET_SENT DD 0 ; TOTAL SENT ET_TXTO DD 0 ; TX TIMEOUTS ET_OVERFLOW DD 0 ; PACKETS DISCARDED BY NE1000 DRIVER THROUGH LACK OF STORE ET_NOMEM DD 0 ; DELAYED COS OF MEMORY SHORTAGE ET_TOOBIG DD 0 ; RX PACKET TOO LONG ET_TX_TOOBIG DD 0 ; TX PACKET TOO BIG ARP_REPLIES DD 0 ; ARP REPLIES SENT TXBUFFCOUNT DB 0,0,0,0 MINTXCOUNT DB 0FFH,0,0,0 TEL_MSGS DD 0 ; INPUT MSGS PROCESSED TEL_LAST_MIN DW 0,0 ; MESSGES IN LAST MINUTE ACTIVERXCOUNT DB 0,0,0,0 ; RX ANYS ACTIVE ARPFULLCOUNT DD 0 ; ARP TABLE FULL STATSLEN EQU $-STATSFIELD LRCERROR DD 0 ; FOR ASYNC DRIVER MIN_COUNT DW 0 ; ; ETHERNET SEND/RECEIVE PACKET PROCESSING BUFFERS ; ; PACKET FROM ETHERNET IS COPIED INTO A BUFFER. AS PROCESSING ; PROCEEDS, THE DATA OFFSET AND LENGTH FIELDS ARE ADJUSTED TO REMOVE ; THE VARIOUS HEADERS ; ; SIMILARLY, THE TX MESSAGE IS BUILD UP (BACKWARDS) AS ; HEADERS ARE ADDED ; BUFFSTRUC STRUC BUFFCHAIN DW 0 BUFFLEN DW 0 BUFFPTR DW 0 BUFFDATA DW 594 DUP (0) BUFFSTRUC ENDS EVEN TXFREE_Q DW 0 BUFFER DW 0 ; CURRENT BUFFER (RECEIVE) TCPDATALEN DW 0 ; LENGTH OF DATA FIELD OF CURRENT MSG TCPHDDRLEN DW 0 ; LENGTH OF TCP HEADER OF CURRENT MSG TXBUFFER DW 0 ; CURRENT TX BUFFER ; IPMSG STRUC ; ; FORMAT OF IP HEADER ; ; NOTE THESE FIELDS ARE STORED HI ORDER BYTE FIRST (NOT NORMAL 8086 FORMAT) ; VERLEN DB 0 ; 4 BITS VERSION, 4 BITS LENGTH TOS DB 0 ; TYPE OF SERVICE IPLENGTH DW 0 ; DATAGRAM LENGTH IPID DW 0 ; IDENTIFICATION FRAGWORD DW 0 ; 3 BITS FLAGS, 13 BITS OFFSET IPTTL DB 0 IPPROTOCOL DB 0 ; HIGHER LEVEL PROTOCOL IPCHECKSUM DW 0 ; HEADER CHECKSUM IPSOURCE DD 0 IPDEST DD 0 ; ; OPTIONS AND/OR DATA MAY FOLLOW ; IPMSG ENDS ; NEXTIPIDENT DW 0 ; IDENT PHSUM DW 0 ; PSEUDO-HEADER CHECKSUM (FOR TCP) ; ; ICMP MESSAGE STRUCTURE ; ICMPMSG STRUC ; ; FORMAT OF ICMP HEADER WITHIN AN IP DATAGRAM ; ; NOTE THESE FIELDS ARE STORED HI ORDER BYTE FIRST (NOT NORMAL 8086 FORMAT) ; ICMPTYPE DB 0 ICMPCODE DB 0 ICMPCHECKSUM DW 0 ICMPID DW 0 ; IN ECHO REQ/REPLY ICMPSEQUENCE DW 0 ; ; DATA MAY FOLLOW ; ICMPMSG ENDS ; TEMP DB 6 DUP (0) ; SWAP FIELD FOR IP AND ETHER ADDRESSES ; ; TCP MESSAGE STRUCTURE ; TCPMSG STRUC ; ; FORMAT OF TCP HEADER WITHIN AN IP DATAGRAM ; ; NOTE THESE FIELDS ARE STORED HI ORDER BYTE FIRST (NOT NORMAL 8086 FORMAT) ; SOURCEPORT DW 0 DESTPORT DW 0 SEQNUM DD 0 ACKNUM DD 0 TCPCONTROL DB 0 ; 4 BITS DATA OFFSET 4 RESERVED TCPFLAGS DB 0 ; (2 RESERVED) URG ACK PSH RST SYN FIN WINDOW DW 0 CHECKSUM DW 0 URGPTR DW 0 ; ; OPTIONS AND/OR DATA MAY FOLLOW ; TCPOPTIONS DB 4 DUP (0) TCPMSG ENDS ; ; TCPFLAGS BITS ; FIN EQU 1B SYN EQU 10B RST EQU 100B PSH EQU 1000B ACK EQU 10000B URG EQU 100000B ACKED DW 0 ; BYTES ACKED BY THIS ACK TXCOUNT DW 0 ; DATA BYTES SENT SEQINCR DW 0 ; SEQUENCE SPACE USED (IE TXCOUNT+SYN/FIN) TCPLENGTH DW 0 ; TOTAL LENGTH OF TCP BIT CURRENT_SESSION DW 0 ; CURRENTLY SELECTED SESSION SECTIMER DB 18 ; 18HZ TO 1HZ COUNTER MINTIMER DB 60 ; SEC TO MIN COUNTER ; TEMPSEQ DD 0 ; WORK FIELD FOR SEQUENCE CALCS W10 DW 10 DATASENT DB 0 DATASEG DW 0 SAVEDI DW 0 ; BUFFER ADDRESS SAVE AREA NEXTSOCKET DW 1000 ; TCPSENDBUFFER LABEL BYTE POLLBUFFER DB 300 DUP (0) DATABUFFER DB 514 DUP (0) ; MAX DATA TO/FROM NODE ; CODEACTIVE DB 0 TIMERTICKS DW 0 ; LAST VALUE OF RTC SAVESTACKSEG DW 0 SAVESTACKPTR DW 0 ; LASTTICKS DW 0 SAVEBUFFER DW 0 REFUSED DB 'Connection refused by remote host',cr LREFUSED EQU $-REFUSED TXBUFFS EQU 50 TXBUFLEN EQU 32+6 ; MOST MSGS WILL BE VERY SMALL, BUT ; FTF USES 512 BYTES EVEN TXPOOL DB TXBUFLEN*TXBUFFS DUP (0) ; ; THE FOLLOWING CODE IS NOT REQUIRED ATER INSTALLATION, SO ; OVERLAY WITH TX BUFFER AREA ; PANICBUFFER DB 400 DUP (0) PUBLIC TCPSENDBUFFER IPDATA ENDS CODE SEGMENT PUBLIC 'CODE' PUBLIC PROCESS_TCP_MSG,TCPBG,TCPINIT PUBLIC TCPAPI EXTRN SUMANDSEND:NEAR,IPRELBUFF:NEAR,IPGETBUFF:NEAR EXTRN IPNODE:NEAR,GETVALUE:NEAR SCAN: MOV CX,10 ; DONT LOOK TOO FAR! SCAN1: CMP BYTE PTR [SI],20H JNE SCANRET INC SI LOOP SCAN1 XOR AL,AL ; RETURN Z SCANRET: RET ; TCPAPI: ; ; CALL FROM APPLICATION ; ; AH=FUNCTION CODE AL=SESSION SI=DATA CX=LENGTH ; 'P'=POLL MOV AX,0 RET MOV DX,SI ; SAVE SI PUSH CX MOV SI,OFFSET DCT MOV CX,ACTIVESESSIONS JCXZ NOSESS0 SESSLOOP: CMP DCTSESSNO[SI],AL JE GOTSESS ADD SI,TYPE DCTENTRY LOOP SESSLOOP NOSESS0: ; ; NOT FOUND ; POP CX MOV AX,-1 RET GOTSESS: POP CX CMP AH,'S' JE STATUSMSG ; REQUEST FOR STATUS ; CMP AH,'C' JNE NOTKILL ; ; APPLICATION IS CLOSING - KILL SESSION AND CLEAR ACTIVE ; CMP TCPSTATE[SI],LISTEN JBE DONEIT1 ; ALREADY CLOSED MOV CLOSEREQUEST[SI],1 ; REQUEST CLOSE (WHEN ALL DATA IS GONE) DONEIT1: RET NOTKILL: CMP AH,'A' JNE NOTACTIVE ; ; APPLICATION IS ACTIVE ; RET NOTACTIVE: CMP AH,'P' JNE NOTPOLL RET JMP POLLCODE NOTPOLL: ; ; SEND DATA ; ; CALL GOTAMESSAGE RET STATUSMSG: MOV AL,TXBUFFCOUNT MOV AH,TCPSTATE[SI] RET TCPBG: CALL MAINLOOP CALL TIMERCODE RET ; MAINLOOP: ; ; SEE IF ANY CLOSES ARE NEEDED ; MOV SI,OFFSET DCT MOV CX,ACTIVESESSIONS JCXZ BGLOOPX BGLOOP00: ; ; SEE IF A CLOSE IS PENDING ; PUSH CX CMP CLOSEREQUEST[SI],0 JE NOCLOSE ; ; MAKE SURE TX_Q AND ACK_Q ARE EMPTY ; MOV AX,TX_QUEUE[SI] ; DATA TO SEND OR AX,ACK_QUEUE[SI] ; OR WAITING ACK JNZ NOCLOSE MOV CLOSEREQUEST[SI],0 OR TCP_FLAGS[SI],FORCE ; FORCE A TRANSMISSION MOV TCPSTATE[SI],FINWAIT1 JMP SHORT NEXTSESS NOCLOSE: NEXTSESS: ; ; SEE IF ANYTHING TO SEND ON THIS SESSION ; SENDAGAIN: MOV DATASENT,0 PUSH CX PUSH SI MOV BX,SI CALL TCP_SEND POP SI POP CX ; ; IF MORE TO SEND, AND NOT BUSY, LOOP ROUND AGAIN ; CMP TX_QUEUE[SI],0 JE NOMORETOSEND ; SOMETHING HERE CMP DATASENT,0 JNE SENDAGAIN ; STOPPED AT WINDOW (OR DUE TO STATE) NOMORETOSEND: NOTTELSESS: PUSH SI CALL POLLCODE POP SI PUSH SI CALL GETAMESSAGE ; LOOK FOR DATA FROM NODE POP SI POP CX ADD SI,TYPE DCTENTRY LOOP BGLOOP00_J ; BGLOOPX: RET BGLOOP00_J: JMP BGLOOP00 POLLCODE: ; ; APPLICATION POLL FOR DATA FROM HOST - SET SI BEFORE CALLING ; CMP TCPSTATE[SI],0 ; IDLE JNE POLLCONT CMP LASTSTATE[SI],0 ; ALREADY REPORTED IDLE? JE POLLRET ; YES POLLCONT: CMP TCP_RCVQ[SI],0 JNE GOTDATATOSEND MOV AL,TCPSTATE[SI] CMP AL,LASTSTATE[SI] JE POLLRET ; NO CHANGE ; ; NEED TO SEND SESSION STATE CHANGED MSG TO APPLICATION ; MOV LASTSTATE[SI],AL ; MOV BYTE PTR ES:[DI],'s' ; STATUS MESSAGE ; INC DI ; ; STOSB ; SESSION ; ; MOV AH,'d' MOV CX,0 RET GOTDATATOSEND: ; ; MAKE SURE CONNECTED ; PUSH SI MOV AH,6 MOV AL,HOSTPORT[SI] MOV CX,1 CALL IPNODE POP SI MOV DI,OFFSET DATABUFFER PUSH SI CALL PROCESSRECEIVEDDATA POP SI MOV AH,2 MOV AL,HOSTPORT[SI] MOV SI,OFFSET DATABUFFER CALL IPNODE POLLRET: MOV AX,0 RET TIMERCODE: PUSH ES MOV AX,40H MOV ES,AX MOV AX,ES:6CH SUB AX,TIMERTICKS ; ; AX HAS TICKS SINCE WE LAST RAN THE TIMER ; POP ES CMP AX,18 JAE DOTIMER RET DOTIMER: ADD TIMERTICKS,18 ; ; ONE SECOND ; DEC MINTIMER JNZ NOTMIN ; ; ONE MINUTE ; INC4 TIMEACTIVE MOV MINTIMER,60 MOV AX,MIN_COUNT MOV TEL_LAST_MIN,AX ; FRAMES IN LAST MINUTE MOV MIN_COUNT,0 NOTMIN: CALL TIMER_TICK JMP TIMERCODE ; ; ONE SECOND TIMER ROUTINE ; TIMER_TICK: ; ; CHECK TCP TIMERS ; MOV BX,OFFSET DCT MOV CX,ACTIVESESSIONS JCXZ NOSESSIONS1 TIMERLOOP: CMP TCP_TIMER_COUNT[BX],0 JE TIMEND DEC TCP_TIMER_COUNT[BX] JNZ TIMEND ; ; TIMER HAS EXPIRED ; CMP TCPSTATE[BX],TIME_WAIT ; WAITING TO SHUT DOWN? JNE NORMALTIMEOUT ; NO CALL SETCLOSED ; CLOSE IT JMP SHORT TIMEND NORMALTIMEOUT: CMP TCPSTATE[BX],0 JE TIMEND ; NOTHING DOING ; ; TRANSMISSION TIMEOUT - SET TO RESEND ALL UNACKED STUFF ; DEC TCP_RETRY[BX] JNZ OKTORETRY ; ; TIMED OUT - ZAP SESSION ; CALL SETCLOSED JMP SHORT TIMEND OKTORETRY: OR TCP_FLAGS[BX],RETRAN+FORCE MOV4 SND_PTR[BX],SND_UNA[BX] ; RESET TO SEND AGAIN ; ; PUT ANY UNSENT STUFF ON ACK_Q, THEN MOVE QUEUES ; RETRYCOPYLOOP: LEA SI,TX_QUEUE[BX] CALL Q_REM JZ NOMORETOCOPY LEA SI,ACK_QUEUE[BX] CALL Q_ADD JMP RETRYCOPYLOOP NOMORETOCOPY: MOV AX,ACK_QUEUE[BX] ; MOVE DATA BACK TO TX Q MOV TX_QUEUE[BX],AX MOV ACK_QUEUE[BX],0 ; CLEAR TIMEND: ; ; CHECK STUCK SESSION WATCHDOG ; CMP WATCHDOG[BX],0 JE NOT_WATCH DEC WATCHDOG[BX] JNZ NOT_WATCH CALL SETCLOSED ; ZAP SESSION NOT_WATCH: ADD BX,TYPE DCTENTRY LOOP TIMERLOOP NOSESSIONS1: RET ACTIVATE_SESSION: ; ; SET UP A TCP SESSION ; MOV AX,1024 ; 2000 ; 48 ; 4096 ; WINDOW MOV TCP_WINDOW[SI],AX MOV RCV_WND[SI],AX ; ; SET UP INITIAL SEQENCE CONDITIONS, AND CAUSE SYN TO BE SENT ; MOV AH,0 INT 1AH ; GET CLOCK (IN CX:DX) MOV AX,DX MOV DX,CX MOV WORD PTR TCP_ISS[SI],AX MOV WORD PTR TCP_ISS+2[SI],DX MOV WORD PTR SND_NXT[SI],AX MOV WORD PTR SND_NXT+2[SI],DX MOV WORD PTR SND_PTR[SI],AX MOV WORD PTR SND_PTR+2[SI],DX MOV WORD PTR SND_UNA[SI],AX MOV WORD PTR SND_UNA+2[SI],DX MOV TCP_FLAGS[SI],FORCE MOV OPENREQUEST[SI],1 CMP LOCALSOCKET[SI],0 JNE GOTSOCK MOV AX,NEXTSOCKET MOV LOCALSOCKET[SI],AX INC AX MOV NEXTSOCKET,AX GOTSOCK: RET GETAMESSAGE: ; ; FIRST POLL FOR STATUS CHANGE, THEN FOR DATA ; MOV AH,4 MOV AL,HOSTPORT[SI] CALL IPNODE OR DX,DX JZ NOCHANGE CHANGED: OR CX,CX JNZ CONNECTED ; ; SWITCH SESSION CLOSED - CLOSE TCP SESSION ; MOV AH,5 MOV AL,HOSTPORT[SI] CALL IPNODE ; ACK CHANGE ; ; APPLICATION IS CLOSING - KILL SESSION AND CLEAR ACTIVE ; CMP TCPSTATE[SI],LISTEN JBE ALREADYCLOSED ; ALREADY CLOSED MOV CLOSEREQUEST[SI],1 ; REQUEST CLOSE (WHEN ALL DATA IS GONE) ALREADYCLOSED: RET CONNECTED: MOV AH,5 MOV AL,HOSTPORT[SI] CALL IPNODE ; ACK CHANGE ; ; SET UP TCP SESSION ; ; READ FIRST DATA MESSAGE - SHOULD BE USER TELNET COMMAND ; MOV AH,3 MOV AL,HOSTPORT[SI] PUSH SI MOV DI,OFFSET POLLBUFFER CALL IPNODE POP SI CMP CX,0 JE NOCHANGE ; MOV BX,SI ; SESSION MOV SI,OFFSET POLLBUFFER CMDSKIP: ; ; SKIP OVER COMMAND STRING TO FIRST PARAM ; LODSB CMP AL,20H JNE CMDSKIP LEA DI,REMOTEADDR[BX] MOV CX,4 GETIP1: CALL GETVALUE MOV AL,BYTE PTR NEWVALUE STOSB INC SI ; OVER TERMINATOR LOOP GETIP1 MOV REMOTESOCKET[BX],23 ; DEFAULT TO TELNET CALL SCAN CMP BYTE PTR [SI],20H JE GOTSOCKET CALL GETVALUE MOV AX,NEWVALUE MOV REMOTESOCKET[BX],AX GOTSOCKET: MOV SI,BX MOV LOCALSOCKET[SI],0 ; ASK ACTIVATE TO ALLOCATE OUR SOCKET CALL ACTIVATE_SESSION RET NOCHANGE: MOV AH,3 MOV AL,HOSTPORT[SI] PUSH SI MOV DI,OFFSET POLLBUFFER CALL IPNODE POP SI CMP CX,0 JNE GOTONE ; RET GOTONE: MOV CURRENT_SESSION,SI ; SAVE SESSION MOV SI,OFFSET POLLBUFFER MOV DI,OFFSET DATABUFFER ; ; CONVERT ANY CR TO CRLF ; CRLFLOOP: LODSB STOSB ; CMP AL,0DH ; JNE NOTCR ; ; MOV AL,0AH ; STOSB ; ;NOTCR: LOOP CRLFLOOP MOV CX,DI MOV SI,OFFSET DATABUFFER SUB CX,SI ; ; PROCESS MESSAGE FROM NODE ; IF 0 JCXZ DONEITJMP ; IGNORE NULL INPUT MOV AL,-1[SI] OR AL,AL JNZ CONTROLMSG ; JMP DATAMSG CONTROLMSG: OR AL,20H ; TO LC CMP AL,'c' JE DOCLOSE CMP AL,'l' JE STARTSESSION ; INITIATE LOGIN CMP AL,'s' JE GETSESSIONSTATE DONEITJMP: JMP DONEIT GETSESSIONSTATE: ; ; RETURN SESSION STATE ; MOV AL,TCPSTATE[SI] MOV AH,'S' RET DOCLOSE: MOV SI,CURRENT_SESSION ; SESSION CMP TCPSTATE[SI],LISTEN JBE DONEIT ; ALREADY CLOSED MOV CLOSEREQUEST[SI],1 JMP SHORT DONEIT STARTSESSION: ; ; IF A REMOTE PORT FOLLOWS, USE IT ; MOV DI,CURRENT_SESSION CMP CX,6 JB NOPORT LEA DI,REMOTEADDR[DI] MOVSW MOVSW MOV DI,CURRENT_SESSION LODSW XCHG AH,AL MOV REMOTESOCKET[DI],AX LODSW XCHG AH,AL MOV LOCALSOCKET[DI],AX NOPORT: MOV SI,CURRENT_SESSION CALL ACTIVATE_SESSION JMP SHORT DONEIT DONEIT: RET DATAMSG: ENDIF ; ; DATA MESSAGE - SEND TO HOST ; INC4 TEL_MSGS INC MIN_COUNT ; NUMBER IN LAST MINUTE ; ; SI = DATA, CX=LENGTH ; ; DATA IS COPIED TO A LOCAL BUFFER, SO IT CAN BE SAVED FOR POSSIBLE RETRY ; MOV BX,CURRENT_SESSION CMP TCPSTATE[BX],0 JE DISCARD_DATA ; DONT USE AUTO OPEN ANY MORE ; CMP TCPSTATE[BX],ESTABLISHED JBE OK_TO_QUEUE_DATA ; ; SESSION IS CLOSING - DONT QUEUE ANY MORE DATA TO IT ; ; NOTE THAT THIS CONTRAVENES THE FULL DUPLEX NATURE OF TCP - IF WE ARE ; IN CLOSE_WAIT (IE CLOSE RXED FROM FAR END) WE SHOULD BE ABLE TO ; CARRY ON SENDING. BUT WE DONT PASS THE CLOSE REQUEST TO THE ; APPLICATION - WE JUST REPLY WITH A CLOSE. SO WE MUSTNT QUEUE DATA ; WHEN IN CLOSE_WAIT. ; DISCARD_DATA: RET OK_TO_QUEUE_DATA: MOV SI,OFFSET DATABUFFER ; ; IF MSG EXCEEDS 32 BYTES, IT MUST BE SPLIT OVER MULTIPLE BUFFERS ; CMP TXBUFFCOUNT,20 JB NOBUFFS QLOOP: PUSH SI MOV SI,OFFSET TXFREE_Q CALL Q_REM POP SI JZ NOBUFFS ; SHOULDNT HAPPEN! DEC TXBUFFCOUNT MOV AL,TXBUFFCOUNT ; CURRENT LEVEL CMP AL,MINTXCOUNT JA GETB10 ; NOT LOWER MOV MINTXCOUNT,AL ; NEW LOWEST GETB10: PUSH DI MOV DX,CX CMP CX,32 JBE SENDALL MOV CX,32 SENDALL: SUB DX,CX ; DX = BYTES STILL TO GO MOV 2[DI],CX ; LENGTH TO BUFFER ADD DI,6 ; 6 BYTE HEADER (CHAIN,LEN,PTR) MOV -2[DI],DI ; PUT IN POINTER REP MOVSB POP DI PUSH SI LEA SI,TX_QUEUE[BX] ; QUEUE IT CALL Q_ADD POP SI MOV CX,DX CMP CX,0 JNE QLOOP MOV AX,0 ; SEND OK RET NOBUFFS: MOV AX,-1 ; COULDNT SEND RET Q_ADDF: CLI MOV AX,[SI] ; OLD QUEUE HEADER MOV [DI],AX ; CHAIN TO NEW BUFFER ; MOV [SI],DI ; CHAIN NEW BUFFER TO QUEUE HEADER STI RET ; Q_REM: MOV DI,[SI] CMP DI,0 JZ Q_RET ; QUEUE EMPTY ; MOV AX,[DI] MOV [SI],AX ; SI = HEADER Q_RET: RET ; Q_ADD: MOV WORD PTR [DI],0 ; CLEAR CHAIN POINTER IN NEW BUFFER Q_ADD1: MOV AX,[SI] OR AX,AX JZ Q_ADD5 ; END ; MOV SI,AX JMP Q_ADD1 ; LOOP TILL END FOUND ; Q_ADD5: MOV [SI],DI ; CHAIN NEW BLOCK RET ; ; ; PROCESS DATA FROM TCP HOST ; PROCESSRECEIVEDDATA: ; ; ES:DI = USER'S BUFFER ; MOV CURRENT_SESSION,SI ; SAVE ; PUSH DI LEA SI,TCP_RCVQ[SI] ; RX QUEUE CALL Q_REM MOV SAVEDI,DI MOV SI,BUFFPTR[DI] MOV AX,BUFFLEN[DI] POP DI CMP AX,0 JE BADLEN CMP AX,TCPMSS JBE LENOK MOV AX,TCPMSS LENOK: MOV CX,AX REP MOVSB ; COPY DATA TO USERS BUFFER JMP SHORT SKIPBAD BADLEN: SKIPBAD: MOV SI,CURRENT_SESSION ; RECOVER SESSION ADD RCV_WND[SI],AX ; ADJUST WINDOW CMP RCV_WND[SI],TCPMSS JB WINDOWSTILLCLOSED ; TEST WINDOW_CLOSED[SI],1 JZ WNDNOTCLOSED ; ; WINDOW WAS CLOSED - TELL OTHER END NOW OK ; MOV WINDOW_CLOSED[SI],0 OR TCP_FLAGS[SI],FORCE WINDOWSTILLCLOSED: WNDNOTCLOSED: MOV DI,SAVEDI ; ETH BUFFER ; ; RELEASE THE BUFFER ; PUSH AX ; SAVE LENGTH CALL IPRELBUFF ; RELEASE POP AX MOV CX,AX ; LENGTH MOV AX,0 ; DATA RET ; ; ; CHECKSUM CALCULATION ROUTINE ; DO_CHECKSUM: ; ; CHECKSUM CX WORDS, STARTING AT SI ; XOR DX,DX ; CLEAR CHECKSUM AND CARRY CSUMLOOP: LODSW ADC DX,AX LOOP CSUMLOOP ADC DX,0 ; MAY BE CARRY FLOATING AROUND RET PROCESS_TCP_MSG: ; ; MESSAGE FROM ROUTER ; MOV BUFFER,BX MOV CX,BUFFLEN[BX] ; LENGTH LEFT MOV SI,BUFFPTR[BX] ; DATA LEFT ; ; FIND SESSION - FIRST MATCH ON OUR SOCKET. UNLESS INCOMMING CALL ; (REMOTE SOCKET/ADDR ZERO), ALSO CHECK REMOTE SOCKET/HOST ; PUSH CX ; MOV BX,OFFSET DCT MOV CX,ACTIVESESSIONS JCXZ NOSESSIONS6 GETD00: MOV AX,DESTPORT[SI] XCHG AH,AL ; CMP LOCALSOCKET[BX],AX JE GETD10 ; GOT IT GETD05: ADD BX,TYPE DCTENTRY LOOP GETD00 ; ; NOT FOUND ; NOSESSIONS6: POP CX JMP SHORT SESSIONNOTFOUND GETD10: ; ; PORT MAY NOT BE UNIQUE (IF MULTIPLE FTP, FOR EXAMPLE) ; CMP REMOTESOCKET[BX],0 JNE GETD12 ; ; MUST BE LISTENING ; JMP GOODSESSION GETD12: MOV AX,SOURCEPORT[SI] XCHG AH,AL CMP REMOTESOCKET[BX],AX JNE GETD05 ; WRONG ENTRY - LOOK FOR ANOTHER ; ; ALSO CHECK REMOTE IP ADDRESS - COULD HAVE SESSIONS FROM MORE THAN ; ONE HOST ; CMP4 REMOTEADDR[BX],-8[SI] ; THIS IS RATHER GROTTY! JNE GETD05 ; JMP GOODSESSION ; REMOTE ADDESSES MATCH ; ; SESSION DOES NOT EXIST - SEND RST (UNLESS RST!) ; ZAPSESSION: SESSIONNOTFOUND: REFUSECALL: TEST TCPFLAGS[SI],RST JZ RESET_00 JMP RELEASEBUFFER RESET_00: ; ; IF ACK IS SET, THEN THE OTHER END THINKS THERE IS A VALID SESSION - ; SEND JUST RST, SEQ=SEG.ACK, ACK=0 ; ; IF ACK IS NOT SET (SO SYN SHOULD BE) ACK THE SYN, AND ; SET RST (SEQ=0,ACK=SEG.SEQ+SEG.LEN) ; MOV BX,OFFSET REFUSEDCT ; ; MUST PUT CALLERS ADDR INTO REFUSEDCT ; MOV4 REMOTEADDR[BX],-8[SI] ; THIS IS RATHER GROTTY! CALL GETTCPTXBUFFER ; GET THE BUFFER ; ; DI NOW POINTS TO TCP HEADER IN TX BUFFER ; MOV TCPCONTROL[DI],50H ; 5 WORDS (IN TOP 4 BITS) MOV TCPLENGTH,20 MOV2 SOURCEPORT[DI],DESTPORT[SI] MOV2 DESTPORT[DI],SOURCEPORT[SI] MOV2 SOURCEPORT[DI],DESTPORT[SI] MOV2 DESTPORT[DI],SOURCEPORT[SI] MOV WINDOW[DI],0 MOV URGPTR[DI],0 MOV CHECKSUM[DI],0 ; WILL FILL IN LATER ; TEST TCPFLAGS[SI],ACK JZ ACKNOTSET ; ; ACK BIT IS SET ; MOV TCPFLAGS[DI],RST MOV4 SEQNUM[DI],ACKNUM[SI] MOV WORD PTR ACKNUM[DI],0 MOV WORD PTR ACKNUM+2[DI],0 JMP SHORT RESET_COMMON ACKNOTSET: MOV TCPFLAGS[DI],RST+ACK MOV4 ACKNUM[DI],SEQNUM[SI] ADD BYTE PTR ACKNUM+3[DI],1 ADC BYTE PTR ACKNUM+2[DI],0 ADC BYTE PTR ACKNUM+1[DI],0 ADC BYTE PTR ACKNUM[DI],0 MOV WORD PTR SEQNUM[DI],0 MOV WORD PTR SEQNUM+2[DI],0 RESET_COMMON: CALL TCPS150 ; CHECKSUM AND SEND CHUCKIT: JMP RELEASEBUFFER ; DISCARD GOODSESSION: POP CX ; ; SESSION IN BX IS VALID ; MOV AL,TCPCONTROL[SI] ; TOP 4 BITS ARE HDDR LEN IN WORDS AND AL,11110000B SHR AL,1 SHR AL,1 ; /4 TO GIVE BYTES CBW ; ; AX IS HEADER LENGTH - ADJUST BUFFER POINTERS ; MOV TCPHDDRLEN,AX MOV DI,BUFFER SUB BUFFLEN[DI],AX ; LENGTH LEFT ADD BUFFPTR[DI],AX ; DATA POINTER SUB CX,AX ; CX IS NOW DATA LENGTH MOV TCPDATALEN,CX MOV AL,TCPSTATE[BX] OR AL,AL JNZ NOTCLOSED ; ; WE THINK ITS CLOSED - SEND RESET ; NOSESSION: JMP ZAPSESSION NOTCLOSED: MOV AL,TCPSTATE[BX] CMP AL,SYN_SENT JE SYNSENTSTATE_J CMP AL,LISTEN JE INCOMMINGCALL JMP NOTSYNSENT SYNSENTSTATE_J: JMP SYNSENTSTATE INCOMMINGCALL: ; ; MESSAGE RECEIVED IN LISTEN STATE ; ; MAKE SURE WORKSTATION SESSION EXISTS ; TEST TCPFLAGS[SI],RST JNZ CHUCKIT ; IGNORE RST ; ALSO MAKE SURE MESSAGE IS ACTUALLY A CALL ; TEST TCPFLAGS[SI],ACK JNZ NOSESSION ; ANY ACK IS BAD - SEND RESET TEST TCPFLAGS[SI],SYN JZ CHUCKIT ; NOT A SYN ; ; GET ADDRESSES ; MOV AX,SOURCEPORT[SI] XCHG AH,AL MOV REMOTESOCKET[BX],AX MOV4 REMOTEADDR[BX],-8[SI] ; IP ADDR - NASTY! ; ; SYN ON ITS OWN - A CALL REQUEST ; MOV AX,1024 ; WINDOW MOV TCP_WINDOW[BX],AX MOV RCV_WND[BX],AX COPY4 RCV_NXT[BX],SEQNUM[SI] ; EXTRACT RX ISS INC4 RCV_NXT[BX] ; AND INCREMENT MOV AH,0 INT 1AH ; GET CLOCK (IN CX:DX) MOV AX,DX MOV DX,CX MOV WORD PTR TCP_ISS[BX],AX MOV WORD PTR TCP_ISS+2[BX],DX MOV WORD PTR SND_NXT[BX],AX MOV WORD PTR SND_NXT+2[BX],DX MOV WORD PTR SND_PTR[BX],AX MOV WORD PTR SND_PTR+2[BX],DX MOV WORD PTR SND_UNA[BX],AX MOV WORD PTR SND_UNA+2[BX],DX MOV AH,TCPOPTIONS+2[SI] MOV AL,TCPOPTIONS+3[SI] ; ; GET MSS FROM MESSAGE - HE MAY NOT INCLUDE IT (I THINK!) - IF ; SO SET A REASONABLE DEFAULT (196) ; CMP TCPHDDRLEN,20 JNE GOTMSS1 MOV AX,196 GOTMSS1: MOV TCP_MSS[BX],AX ; OR TCP_FLAGS[BX],FORCE ; FORCE A TRANSMISSION MOV TCPSTATE[BX],SYN_RECEIVED JMP RELEASEBUFFER SYNSENTSTATE: ; ; SYN SEND PROCESSING. THE MOST LIKELY THING IS AN ACK OF OUR SYN, ; BUT A 'CALL COLLISION' IS POSSIBLE (BUT ONLY JUST, AS WE DONT ; EXPECT INCOMING CALLS) ; TEST TCPFLAGS[SI],ACK JNZ GOTACK ; ; NO ACK - AS FAS AS I CAN SEE THE ONLY MESSAGES WITOHUT ACK ARE ; AN INITIAL SYN, OR POSSIBLY A RESET. AS I DONT HAVE SESSIONS WHICH ; ARE BOTH INCOMMING AND OUTGOING, I SHOULD NEVER GET AN INCOMMING ; CALL IN SYN_SENT STATE. IF I DO, CLEAR IT. ; TEST TCPFLAGS[SI],SYN JZ DISCARDIT JMP REFUSECALL ; GOTACK: ; ; IF MSG HAS ACK SET, MAKE SURE ITS IN WINDOW (IT MAY BE FOR AN OLD SESSION) ; ; CHECK ACK ; CMP4X TCP_ISS[BX],ACKNUM[SI] JAE BADACK ; ACK SHOULD BE WITHIN ISS+1, SND.NXT CMP4X SND_NXT[BX],ACKNUM[SI] JAE FIRSTACKOK BADACK: ; ; SET SEQ SO RST IS ACCEPTED ; TEST TCPFLAGS[SI],RST JNZ DISCARDIT ; RST AND BAD SEQ - IGNORE IT COPY4 SND_PTR[BX],ACKNUM[SI] PUSH BX CALL SENDRESET ; KILL IT POP BX MOV TCPSTATE[BX],TIME_WAIT ; WAITING TO SHUT DOWN MOV TCP_TIMER_COUNT[BX],5 ; 5 SECS DISCARDIT: JMP RELEASEBUFFER ; FIRSTACKOK: ; ; ACK IS VALID ; TEST TCPFLAGS[SI],RST JZ NOTRST ; ; ACK + RST USED TO REFUSE CONNECTION ; ; THIS IS A VALID ACK WITH RST, MEANING 'GET LOST' ; MOV SI,OFFSET REFUSED MOV CX,LREFUSED MOV AH,2 MOV AL,HOSTPORT[BX] PUSH BX CALL IPNODE POP BX CALL SETCLOSED JMP RELEASEBUFFER ; SHOULD WE TELL USER?? NOTRST: ; ; IF SYN IS SET, THIS IS A VALID CONNECT ACK ; TEST TCPFLAGS[SI],SYN JNZ CONNECTACK JMP RELEASEBUFFER ; THEY HAVE ACKED OUR SYN, BUT NOT ; SENT THEIR OWN - WHY?? CONNECTACK: ; ; PROCESS INCOMMING SYN ; OR TCP_FLAGS[BX],FORCE ; ALWAYS REPLY COPY4 RCV_NXT[BX],SEQNUM[SI] ; EXTRACT RX ISS INC4 RCV_NXT[BX] ; AND INCREMENT MOV AH,TCPOPTIONS+2[SI] MOV AL,TCPOPTIONS+3[SI] ; ; GET MSS FROM MESSAGE - HE MAY NOT INCLUDE IT (I THINK!) - IF ; SO SET A REASONABLE DEFAULT (196?) ; CMP TCPHDDRLEN,20 JNE GOTMSS MOV AX,196 GOTMSS: MOV TCP_MSS[BX],AX ; CALL DOACKPROCESSING ; PROCESS ACK/WINDOW ; ; THE INCOMING SYN+ACK MAY ALSO HAVE DATA ; CMP TCPDATALEN,0 JNZ PROCESSTCPDATA ; ; MAY HAVE FIN SET (BUT SEEMS VERY UNLIKELY!!) ; TEST TCPFLAGS[SI],FIN JNZ PROCESSTCPDATA ; ; NOTHING ELSE TO DO ; JMP RELEASEBUFFER NOTSYNSENT: ; ; CHECK SEQUENCE ; CMP4X RCV_NXT[BX],SEQNUM[SI] JE DATASEQOK TEST TCPFLAGS[SI],RST JNZ DROPIT ; TEST TCPFLAGS[SI],SYN JZ ACKIT ; OUT OF SEQUENCE - SEND ACK ; ; MAY GET UNEXPECTED SYN AS A RESULT OF RETRY BY FAR END, SO ; IGONRE IT ; ; CALL SETCLOSED ; UNEXPECTED SYN - JUST ZAP IT ; JMP SHORT DROPIT ACKIT: OR TCP_FLAGS[BX],FORCE ; ENSURE ACK IS SENT DROPIT: JMP RELEASEBUFFER DATASEQOK: TEST TCPFLAGS[SI],RST JZ NOTRESETREQ ; ; RST RECEIVED - CLOSE IMMEDIATELY ; CALL SETCLOSED JMP RELEASEBUFFER NOTRESETREQ: TEST TCPFLAGS[SI],ACK JZ NOACKBIT CALL DOACKPROCESSING ; ; ANY STATE CHANGES ARE DONE IN ACKPROCESSING ; NOACKBIT: PROCESSTCPDATA: CMP TCPDATALEN,0 JE NODATAFIELD ; MOV AL,TCPSTATE[BX] CMP AL,ESTABLISHED JE OKTOPROCESS CMP AL,FINWAIT1 JE OKTOPROCESS CMP AL,FINWAIT2 JE OKTOPROCESS ; ; CANT ACCEPT DATA ; JMP RELEASEBUFFER OKTOPROCESS: ; ; MAKE SURE WINDOW IS OPEN - IF NOT, THIS MAY BE A 'WINDOW PROBE' ; MOV AX,TCPDATALEN CMP RCV_WND[BX],AX JAE WINOK ; ; TOO MUCH FOR WINDOW - IGNORE, BUT SEND ACK ; OR TCP_FLAGS[BX],FORCE ; ENSURE ACK IS SENT JMP RELEASEBUFFER WINOK: PUSH SI LEA SI,TCP_RCVQ[BX] MOV DI,BUFFER CALL Q_ADD POP SI MOV AX,TCPDATALEN ADD4 RCV_NXT[BX],AX SUB RCV_WND[BX],AX CMP RCV_WND[BX],TCPMSS JAE WINDOWOK ; ; LESS THAT MSS LEFT, SO OTHER END MAY STOP - SET FLAG TO ; SEND STATUS WHEN WINDOW CLEARS ; MOV WINDOW_CLOSED[BX],1 WINDOWOK: ; ; SET TO SEND ACK ; OR TCP_FLAGS[BX],FORCE ; ENSURE ACK IS SENT ; ; SEE IF FIN ALSO SET ; TEST TCPFLAGS[SI],FIN JNZ DO_FAR_END_CLOSE RET NODATAFIELD: ; ; IF FIN IS SET, OTHER END IS CLOSING ; TEST TCPFLAGS[SI],FIN JZ RELBUF1 CALL DO_FAR_END_CLOSE RELBUF1: JMP RELEASEBUFFER DO_FAR_END_CLOSE: ; ; FIN SET IN MESSAGE. ; ; IF IN ESTABLISHED (OR BELOW), GO TO CLOSE WAIT ; IF IN FINWAIT1, GO TO CLOSING ; IF IN FINWAIT2, GO TO TIMEWAIT ; ; IF IN OTHER CLOSING STATES, JUST LEAVE ALONE - PROBABLY A REPEAT ; OR TCP_FLAGS[BX],FORCE ; ENSURE ACK IS SENT ; INC4 RCV_NXT[BX] ; ACK FIN CMP TCPSTATE[BX],ESTABLISHED JBE SETCLOSEWAIT CMP TCPSTATE[BX],FINWAIT2 JE SETTIMEWAIT CMP TCPSTATE[BX],FINWAIT1 JE SETCLOSING ; HAVE ALREADY SEND FIN SETCLOSEWAIT: ; MOV TCPSTATE[BX],CLOSE_WAIT ; NEED TO SEND FIN RET SETCLOSING: MOV TCPSTATE[BX],CLOSING RET SETTIMEWAIT: MOV TCPSTATE[BX],TIME_WAIT ; WAITING TO SHUT DOWN MOV TCP_TIMER_COUNT[BX],60 RET SETCLOSED: ; ; RELEASE ANY BUFFERS ; PUSH SI LEA SI,TX_QUEUE[BX] CALL Q_REM POP SI JNZ RELEASEIT PUSH SI LEA SI,ACK_QUEUE[BX] CALL Q_REM POP SI JZ NOTHINGTORELEASE RELEASEIT: PUSH SI MOV SI,OFFSET TXFREE_Q CALL Q_ADDF INC TXBUFFCOUNT POP SI JMP SETCLOSED NOTHINGTORELEASE: MOV TCPSTATE[BX],0 MOV TCP_FLAGS[BX],0 MOV LOCALSOCKET[BX],0 MOV WATCHDOG[BX],0 MOV4 LOCALADDR[BX],MYIPADDR MOV LOCALSOCKET[BX],23 MOV4 REMOTEADDR[BX],HOSTADDR ; TESTING MOV REMOTESOCKET[BX],3600 CMP SESSIONTYPE[BX],'I' ; INCOMING -SET LISTEN JNE NOLISTEN MOV TCPSTATE[BX],LISTEN MOV REMOTESOCKET[BX],0 NOLISTEN: ; ; CLOSE ANY ASSOCIATED HOST SESSION ; MOV AH,6 MOV AL,HOSTPORT[BX] MOV CX,2 CALL IPNODE RET RELEASEBUFFER: MOV DI,BUFFER CALL IPRELBUFF RET DOACKPROCESSING: ; ; PROCESS ACK AND WINDOW INFO FROM INCOMING MSG ; ; ; FIRST MAKE SURE ACK IS FOR SOMETHING SENT - IF NOT, IGNORE MSG ; CMP4X SND_NXT[BX],ACKNUM[SI] JAE ACKOK RET ACKOK: ; ; WINDOW PROCESSING IN SPEC SEEMS VERY COMPLICATED - ; WHY NOT JUST SAVE NEW WINDOW?? (MAYBE I'LL FIND OUT..) ; MOV AX,WINDOW[SI] XCHG AH,AL MOV SND_WND[BX],AX ; ; CALCULATE BYTES ACKED ; MOV AX,WORD PTR ACKNUM+2[SI] XCHG AH,AL MOV DX,WORD PTR ACKNUM[SI] XCHG DH,DL SUB AX,WORD PTR SND_UNA[BX] SBB DX,WORD PTR SND_UNA+2[BX] ; ; MAYBE WE SHOULD ONLY USE LOW ORDER WORDS ANYWAY - CANT ACK MORE THAN 64???? ; OR AX,AX JNS NEWACK ; ALLOWS UP TO 32K ACK (I HOPE!) RET NEWACK: ; MOV ACKED,AX ; ; IF IN SYN_SENT OR SYN_RECEIVED, THEN PROCESS ITS ACK ; CMP TCPSTATE[BX],SYN_RECEIVED JE ACKOFSYN CMP TCPSTATE[BX],SYN_SENT JNE NOTACKOFSYN ACKOFSYN: DEC ACKED ; THE SYN IS A 'VIRTUAL' BYTE! ; ; THE SYN IS ACKED - END OF 3 WAY HANDSHAKE - SET ACTIVE ; MOV TCPSTATE[BX],ESTABLISHED ; MOV STATEFLAG[BX],'l' ; TELL USER NOTACKOFSYN: CMP ACKED,0 JNE SOMETHINGACKED JMP NULLACK SOMETHINGACKED: PUSH BX PUSH SI ; SAVE LEA SI,ACK_QUEUE[BX] CALL Q_REM JZ NOTHINGONACKQ ; OH DEAR?? MOV AX,ACKED MOV CX,2[DI] ; BYTES SENT CMP CX,AX JBE FULLYACKED ; ALL THIS BUFFER IS ACKED ; ; I CANT SEE WHY IT SHOULD PARTIALLY ACK A BUFFER, BUT JUST IN CASE... ; ; MUST REMOVE THE ACKED DATA FROM THE BUFFER, IN CASE WE HAVE TO RESEND ; ADD 4[DI],AX SUB 2[DI],AX LEA SI,ACK_QUEUE[BX] CALL Q_ADDF ; PUT BACK JMP NOTHINGTOACK FULLYACKED: SUB ACKED,CX MOV SI,OFFSET TXFREE_Q CALL Q_ADDF ; RETURN TO FREE QUEUE INC TXBUFFCOUNT ; ; SEE IF MORE BUFFERS ARE ACKED ; CMP ACKED,0 JE NOTHINGTOACK ; ALL DONE ; ; LOOP BACK TO DO ANOTHER BUFFER ; POP SI POP BX JMP NOTACKOFSYN NOTHINGONACKQ: ; ; IF IN FIN_WAIT_1/CLOSING/LAST_ACK (ie FIN Sent) THEN PROCESS ITS ACK ; CMP TCPSTATE[BX],FINWAIT1 JNE NOTFIN1 MOV TCPSTATE[BX],FINWAIT2 JMP SHORT ACKOFFIN NOTFIN1: CMP TCPSTATE[BX],CLOSING JNE NOTCLOSING MOV TCPSTATE[BX],TIME_WAIT MOV TCP_TIMER_COUNT[BX],60 JMP SHORT ACKOFFIN NOTCLOSING: CMP TCPSTATE[BX],LAST_ACK JNE NOTACKOFFIN ; FIN NOT OUSTANDING - WOT'S HE ACKING?? ; ; Our FIN IS ACKED - CLOSE CONNECTION ; CALL SETCLOSED ACKOFFIN: DEC ACKED ; THE FIN IS A 'VIRTUAL' BYTE! JZ NOTHINGTOACK ; FINISHED NOTACKOFFIN: ; !!!!! CALL DELAYLOOP NOTHINGTOACK: POP SI POP BX NULLACK: COPY4 SND_UNA[BX],ACKNUM[SI] ; SAVE NEW ACKED VALUE ; ; IF THERE IS NOTHING LEFT TO ACK, STOP TIMER, ELSE RESTART IT ; MOV TCP_TIMER_COUNT[BX],0 ; STOP CMP4 SND_UNA[BX],SND_NXT[BX] JNE NOTALLACKED ; ; ALL WE HAVE SENT HAS BEEN ACKED, SO THERE SHOULD BE NOTHING ON ACK Q ; CMP ACK_QUEUE[BX],0 JE ALLACKED ; ; THIS IS AN INCONSISTANT STATE, SO ALL WE CAN DO IS KILL SESSION ; CALL SETCLOSED RET NOTALLACKED: MOV AX,TCP_TIMER_START[BX] MOV TCP_TIMER_COUNT[BX],AX ; RESTART MOV TCP_RETRY[BX],3 ; RETRY COUNT - LOW - WE HAVE A ; RELIABLE LEVEL 2 ALLACKED: AND TCP_FLAGS[BX],NOT RETRAN ; RET ; ; TCP SEND ROUTINES ; ; INITIATES ALL TCP TRANSMISSIONS ; ; ONLY ACTUALLY SEND SOMETHING IF DATA IS AVAILABLE, OR FORCE IS SET ; ; ; RESET A TCP CONNECTION ; SENDRESET: CALL GETTCPTXBUFFER ; GET THE BUFFER ; ; DI NOW POINTS TO TCP HEADER IN TX BUFFER ; MOV TCPFLAGS[DI],RST MOV TCPCONTROL[DI],50H ; 5 WORDS (IN TOP 4 BITS) MOV TCPLENGTH,20 COPY2 SOURCEPORT[DI],LOCALSOCKET[BX] COPY2 DESTPORT[DI],REMOTESOCKET[BX] COPY4 SEQNUM[DI],SND_PTR[BX] MOV WORD PTR ACKNUM[DI],0 MOV WORD PTR ACKNUM+2[DI],0 MOV WINDOW[DI],0 MOV URGPTR[DI],0 MOV CHECKSUM[DI],0 ; WILL FILL IN LATER ; JMP TCPS150 ; CHECKSUM AND SEND TCP_SEND: ; ; SHOULD ONLY SEND SOMETHING IF FORCED IS SET, OR IF SESSION IS ; ESTABLISHED, AND DATA IS AVAILABLE. NOTE THAT DATA MAY BE ; QUEUED BEFORE SESSION IS ESTABLISHED ; CMP TCPSTATE[BX],ESTABLISHED JAE TCPS20 ; CAN SEND DATA IF AVAILABLE TEST TCP_FLAGS[BX],FORCE JNZ TCPS00 ; SEND HAS BEEN REQUESTED BY SOMETHING RET ; NOTHING DOING TCPS00: ; ; WE ARE IN CLOSED/LISTEN/SYN_SENT/SYN_RECEIVED ; CMP TCPSTATE[BX],LISTEN JA TCPS10 ; ; CLOSED OR LISTEN STATE - MAY NEED TO OPEN (IF CLOSED) ; CMP OPENREQUEST[BX],1 JE TCPS05 ; NEED OPEN ; ; STATE IS CLOSED/LISTEN, AND OPEN HAS NOT BEEN REQUESTED ; - WHY ARE WE HERE?? ; AND TCP_FLAGS[BX],NOT FORCE ; MUST HAVE GOT SET IN ERROR RET TCPS05: ; ; OPEN SESSION - SEND SYN, AND ENTER SYN_SENT STATE ; MOV OPENREQUEST[BX],0 MOV TCPSTATE[BX],SYN_SENT CALL BASICSETUP ; SET UP ADDRESSES, SEQUENCE STUFF, ETC ; ; SYN SENT STATE - SET SYN, MSS - DONT SEND DATA ; MOV TCPFLAGS[DI],SYN ; TCPS08: MOV TXCOUNT,0 ; NO DATA MOV SEQINCR,1 ; THE SYN MOV TCPOPTIONS[DI],2 ; TYPE - MSS MOV TCPOPTIONS+1[DI],4 ; LEN MOV AX,TCPMSS XCHG AH,AL MOV WORD PTR TCPOPTIONS+2[DI],AX ; MSS MOV TCPCONTROL[DI],60H ; 6 WORDS (IN TOP 4 BITS) MOV TCPLENGTH,24 JMP TCPS120 ; NO DATA TCPS10: ; ; SYN_SENT/SYN_RECEIVED ; ; IF IN SYN_SENT WE MUST HAVE TIMED OUT (IF SYN HAD BEEN ACKED ; WE WOULD BE IN ESTABLISHED) - SEND SYN AGAIN ; CMP TCPSTATE[BX],SYN_SENT JE TCPS05 ; SEND IT AGAIN ; ; ; OK, SO WE ARE IN SYN_RECEIVED - AS WE ONLY ACCEPT INCOMING CALLS ; ON LISTENING FTP PORTS THERE IS (OR SHOULD BE) NO CHANCE OF CALL ; COLLISION, SO ALL WE NEED TO DO IS SEND OUR SYN IN REPLY, AND ACK ; THEIR SYN. IT SHOULDNT MATTER WHETHER THIS IS THE FIRST TIME, ; OR A RETRNASMISSION ; CALL BASICSETUP ; SET UP ADDRESSES, SEQUENCE STUFF, ETC MOV TCPFLAGS[DI],SYN+ACK JMP SHORT TCPS08 TCPS20: ; ; ESTABLISHED OR ABOVE - MAY BE DATA AVAILABLE, OR FORCED SET. ; JNE NOTESTABLISHED JMP TCPS60 ; ESTABLISHED - CHECK FOR DATA NOTESTABLISHED: ; ; IN ONE OF THE CLOSEDOWN STATES ; ; WE MAY HAVE DATA QUEUED TO SEND IF CLOSE IS ORIGINATED FROM OTHER ; END. WE SHOULDNT HAVE IF WE ARE INITIATING THE CLOSE. ; ; ; IF IN TIME WAIT, WE SHOULDN'T BE DOING ANYTHING EXCEPT ACKING THEIR ; FIN ; CMP TCPSTATE[BX],TIME_WAIT JNE TCPS25 ; TEST TCP_FLAGS[BX],FORCE JZ TCPS26 ; NOTHING TO SEND JMP TCPS90 TCPS25: ; ; FINWAIT1/2, CLOSE_WAIT, CLOSING, LAST_ACK ; CMP TCPSTATE[BX],CLOSE_WAIT JNE TCPS30 ; ; OTHER END WANTS TO CLOSE - WAIT TILL ALL DATA SENT, THEN SEND FIN ; CMP TX_QUEUE[BX],0 JNE TCPS60 ; SOMETHING HERE ; ; IF ALL OUR DATA IS ACKED, THEN GO TO LAST ACK, AND SEND FIN. ; ; OTHERWISE SEND ACK IF FORCED IS SET ; MOV AX,ACK_QUEUE[BX] OR AX,AX JZ TCPS27 ; OK TO SEND FIN ; CMP4 SND_UNA[BX],SND_NXT[BX] JNE NOTALLACKED1 ; ; ALL WE HAVE SENT HAS BEEN ACKED, SO THERE SHOULD BE NOTHING ON ACK Q ; THIS IS AN INCONSISTANT STATE, SO ALL WE CAN DO IS KILL SESSION ; CALL SETCLOSED RET NOTALLACKED1: TEST TCP_FLAGS[BX],FORCE JZ TCPS26 ; NOTHING TO SEND JMP TCPS90 ; JUST SEND ACK TCPS26: RET ; NOTHING DOING TCPS27: ; ; ENTER LAST ACK, AND SEND FIN ; MOV TCPSTATE[BX],LAST_ACK TCPS28: CALL BASICSETUP ; SET UP ADDRESSES, SEQUENCE STUFF, ETC MOV TXCOUNT,0 ; NO DATA MOV SEQINCR,1 ; THE FIN MOV TCPFLAGS[DI],FIN+ACK+PSH JMP TCPS120 ; NO DATA TCPS30: ; ; FINWAIT1/2, CLOSING, LAST_ACK ; CMP TCPSTATE[BX],LAST_ACK JNE TCPS35 TEST TCP_FLAGS[BX],FORCE JZ TCPS26 ; NOTHING TO SEND JMP TCPS28 ; NEED TO RETRY FIN TCPS35: CMP TCPSTATE[BX],CLOSING JNE TCPS40 ; ; CLOSING MEANS WE HAVE SENT FIN AND RECEIVED A FIN, WITHOUT OUR ; FIN BEING ACKED. IF FORCE IS SET ON ITS OWN, THEN ACK THEIR FIN. ; IF FORCE AND RETRAN ARE SET, THEN ALSO RESEND OUR FIN ; TEST TCP_FLAGS[BX],FORCE JZ TCPS26 ; NOTHING TO SEND TEST TCP_FLAGS[BX],RETRAN JNZ TCPS28 ; SEND FIN AGAIN JMP TCPS90 ; JUST SEND ACK TCPS40: ; ; FINWAIT1/2 ; CMP TCPSTATE[BX],FINWAIT1 JNE TCPS50 ; ; FINWAIT1 MEANS WE HAVE AN UNACKED FIN OUTSTANDING - SEND IT AGAIN ; TEST TCP_FLAGS[BX],FORCE JZ TCPS26 ; NOTHING TO SEND JMP TCPS28 ; SEND FIN AGAIN TCPS50: ; ; FINWAIT2 ; ; WE HAVE SENT OUR FIN, AND HAD IT ACKED. WE ARE WAITING FOR THE OTHER ; END TO SEND A FIN. SHOUDNT HAVE FORCE SET, BUT CHECK IN CASE ; TEST TCP_FLAGS[BX],FORCE JZ TCPS26 ; NOTHING TO SEND TCPS90_J: JMP TCPS90 ; JUST SEND ACK ; TCPS60: ; ; ESTABLISHED OR CLOSE WAIT STATE ; ; GET SOME DATA TO SEND. DONT FORGET SYN AND FIN OCCUPY ; SEQ NUM SPACE, BUT DO NOT APPEAR ON Q ; ; WINDOW IS AN OFFSET FROM SND_UNA, NOT AN ABSOLUTE VALUE! ; ; SO MUST CHECK IF SND_WND+SND_UNA < SND_NXT+(LENGTH TO SEND) MOV4 TEMPSEQ,SND_UNA[BX] MOV AX,SND_WND[BX] ADD4 TEMPSEQ,AX SUB4 TEMPSEQ,32 ; MAX BYTES IN EACH BUFFER CMP4 TEMPSEQ,SND_NXT[BX] JAE TCPS63 ; ; CANT SEND - WINDOW TOO SMALL ; CMP WATCHDOG[BX],0 ; ALREADY RUNNING? JNE TCPS62 ; YES ; ; START STUCK SESSION TIMER ; MOV WATCHDOG[BX],300 ; 5 MIN TCPS62: TEST TCP_FLAGS[BX],FORCE JNZ TCPS90_J ; CAN SEND ACK - BUT NOT DATA ; RET ; CANT SEND NOW TCPS63: CMP TX_QUEUE[BX],0 JNE TCPS63A JMP TCPS80 ; NO DATA ; TCPS63A: MOV TXCOUNT,0 MOV SEQINCR,0 CALL BASICSETUP ; SET UP ADDRESSES, SEQUENCE STUFF, ETC MOV DATASENT,1 ; WILL SEND SOMETHING MOV WATCHDOG[BX],0 ; RESET STUCK SESSION TIMER PUSH DI ; SAVE ADD DI,20 ; TO DATA SENDLOOP: PUSH DI ; SAVE LEA SI,TX_QUEUE[BX] CALL Q_REM PUSH DI LEA SI,ACK_QUEUE[BX] CALL Q_ADD ; SAVE ON ACK Q POP DI ; MCB ; ; GET POINTER FROM BUFFER ; MOV SI,4[DI] MOV CX,2[DI] ADD TXCOUNT,CX ADD SEQINCR,CX ADD TCPLENGTH,CX POP DI ; BUFFER REP MOVSB ; PUT DATA INTO BUFFER XOR AL,AL MOV ES:[DI],AL ; FOR CHECKSUM - IN CASE ODD NO OF BYTES CMP TX_QUEUE[BX],0 JE ENDSEND ; NO MORE DATA ; ; CHECK MSS AND WINDOW ; MOV AX,TCP_MSS[BX] SUB AX,32 ; MAX IN ONE BUFFER CMP TXCOUNT,AX JA ENDSEND ; REACHED MSS MOV4 TEMPSEQ,SND_UNA[BX] MOV AX,SND_WND[BX] ADD4 TEMPSEQ,AX MOV AX,TXCOUNT ADD AX,32 ; WHAT WE MAY SEND SUB4 TEMPSEQ,AX ; AMOUNT WE'VE ALREADY QUEUED CMP4 TEMPSEQ,SND_NXT[BX] JAE SENDLOOP ; OK TO SEND MORE ENDSEND: POP DI JMP SHORT TCPS100 ; TCPS80: ; ; NO DATA TO SEND - IF FORCED, SEND ACK ; TEST TCP_FLAGS[BX],FORCE JNZ TCPS90 RET ; TCPS90: CALL BASICSETUP ; SET UP ADDRESSES, SEQUENCE STUFF, ETC TCPS100: ; ; IF WE ARE SENDING EVERYTHING (WHY SHOULDNT WE BE??) ; SET PUSH ; CMP TX_QUEUE[BX],0 JNE TCPS120 OR TCPFLAGS[DI],PSH TCPS120: ; ; UPDATE SEQUENCE TO ACCOUNT FOR WHAT WEVE SENT ; MOV AX,SEQINCR ; BYTES SENT, INCLUDING SYN/FIN ADD4 SND_PTR[BX],AX MOV4 SND_NXT[BX],SND_PTR[BX] ; ; IF SOMETHING IS BEING SENT, START TIMER ; CMP SEQINCR,0 JE TCPS150 ; NOTHING SENT MOV AX,TCP_TIMER_START[BX] MOV TCP_TIMER_COUNT[BX],AX TEST TCP_FLAGS[BX],RETRAN JNZ TCPS150 ; DONT RESET COUNT IF RETRYING MOV TCP_RETRY[BX],3 ; RETRY COUNT TCPS150: AND TCP_FLAGS[BX],NOT FORCE ; ; BUFFER NOW HAS TCP HEADER IN PLACE - CHECKSUM AND ; DISPLAY IF TRACE ENABLED ; ; IF USING PANIC BUFFER DONT DO SEND - TIMEOUT AND RETRY WILL SORT IT OUT ; CMP TXBUFFER,OFFSET PANICBUFFER JNE OKTOSEND ; RET OKTOSEND: ; ; DO PSEUDO HEADER FIRST ; MOV DX,600H ; PROTOCOL (REVERSED) MOV AX,TCPLENGTH ; TCP LENGTH XCHG AH,AL ADD DX,AX MOV AX,WORD PTR LOCALADDR[BX] ADC DX,AX MOV AX,WORD PTR LOCALADDR+2[BX] ADC DX,AX MOV AX,WORD PTR REMOTEADDR[BX] ADC DX,AX MOV AX,WORD PTR REMOTEADDR+2[BX] ADC DX,AX ADC DX,0 MOV PHSUM,DX PUSH BX MOV BX,TXBUFFER ; HEADER MOV CX,TCPLENGTH ; PUT LENGTH INTO HEADER MOV BUFFLEN[BX],CX ; MOV SI,BUFFPTR[BX] INC CX ; ROUND UP SHR CX,1 ; WORD COUNT CALL DO_CHECKSUM ADD DX,PHSUM ADC DX,0 NOT DX MOV SI,BUFFPTR[BX] MOV CHECKSUM[SI],DX MOV CX,BUFFLEN[BX] ; LENGTH LEFT MOV SI,BUFFPTR[BX] ; DATA LEFT POP BX CALL ADD_IP_HEADER SENDIPFRAME: ; ; FEED TO IPGATE ROUTER CODE ; MOV DI,TXBUFFER MOV SI,OFFSET IP_Q CALL Q_ADD RET BASICSETUP: ; CALL GETTCPTXBUFFER ; GET THE BUFFER ; ; DI NOW POINTS TO TCP HEADER IN TX BUFFER ; MOV TXCOUNT,0 MOV SEQINCR,0 MOV TCPFLAGS[DI],ACK ; ALL BUT SYN_SENT MOV TCPCONTROL[DI],50H ; 5 WORDS (IN TOP 4 BITS) MOV TCPLENGTH,20 ; ; SET UP ADDRESSES, ETC ; COPY2 SOURCEPORT[DI],LOCALSOCKET[BX] COPY2 DESTPORT[DI],REMOTESOCKET[BX] COPY4 SEQNUM[DI],SND_PTR[BX] COPY4 ACKNUM[DI],RCV_NXT[BX] MOV AX,RCV_WND[BX] XCHG AH,AL MOV WINDOW[DI],AX MOV URGPTR[DI],0 MOV CHECKSUM[DI],0 ; WILL FILL IN LATER RET GETTCPTXBUFFER: ; ; GET A BUFFER AND SET UP HEADER INFO ; CALL IPGETBUFF JNZ GOTTXBUFFER ; ; NO BUFFERS ; MOV DI,OFFSET PANICBUFFER ; SET UP MSG BUT THEN DISCARD GOTTXBUFFER: MOV TXBUFFER,DI ; SAVE ; ; SET POINTER TO TCP HEADER POSITION ; MOV BUFFLEN[DI],0 ; CURRENTLY NO DATA LEA AX,BUFFDATA+20+14+20[DI] MOV BUFFPTR[DI],AX ; TCP HEADER POSN ; MOV DI,AX ; RETURN ADDR RET ADD_IP_HEADER: ; ; ADD IP HEADER TO TCP MESSAGE IN TXBUFFER ; PUSH BX MOV BX,TXBUFFER ADD BUFFLEN[BX],20 ; IP HDDR LEN SUB BUFFPTR[BX],20 ; DITTO MOV CX,BUFFLEN[BX] ; GET LENGTH MOV DI,BUFFPTR[BX] POP BX ; ; DI NOW POINTER TO IP BIT ; MOV VERLEN[DI],45H ; VERSION 4, LEN 5 WORDS MOV TOS[DI],0 XCHG CH,CL MOV IPLENGTH[DI],CX INC NEXTIPIDENT COPY2 IPID[DI],NEXTIPIDENT MOV FRAGWORD[DI],0 MOV IPTTL[DI],10 MOV IPPROTOCOL[DI],6 ; TELNET MOV IPCHECKSUM[DI],0 ; WILL CALCULATE LATER MOV4 IPSOURCE[DI],LOCALADDR[BX] MOV4 IPDEST[DI],REMOTEADDR[BX] ; ; HEADER COMPLETE - ADD CHECKSUM AND TRACE IF NECESSARY ; MOV BX,TXBUFFER ; HEADER MOV SI,BUFFPTR[BX] MOV CX,10 ; LENGTH CALL DO_CHECKSUM NOT DX MOV IPCHECKSUM[DI],DX RET ; ; OUTPUT AX IN HEX, FOLLOWED BY A SPACE ; HEXOUTSP: CALL HEXOUT MOV AL,20H CALL PRINTIT RET ; HEXOUT: ; ; OUTPUT AL IN HEX ; PUSH AX sar al,1 sar al,1 sar al,1 sar al,1 call hexout1 pop ax call hexout1 ret hexout1: and al,0fh cmp al,10 jl hexout5 add al,7 hexout5: add al,30h call printit ; ret ; PRINTIT: ; ; SEND BYTE IN AL TO SCREEN (MAY EVENTUALLY ROUTE BACK TO CURRENT ; SESSION TO ALLOW DIAGNOSTICS FROM ANY TERMINAL ; PUSH BX mov AH,14 int 10h POP BX ret ; DISP$: ; ; DISPLAY $ TERMINATED STRING IN DX ; PUSH SI MOV SI,DX DISP$1: LODSB CMP AL,'$' JE DISP$2 CALL PRINTIT JMP DISP$1 DISP$2: POP SI RET EVEN TCPINIT: ; ; GET START TIME ; MOV AX,40H MOV ES,AX MOV AX,ES:6CH MOV TIMERTICKS,AX ; ; SET UP DCT ; MOV BX,OFFSET DCT MOV CX,NUMBEROFSESSIONS+1 MOV AL,1 MOV AH,55 DCTINIT: MOV DCTSESSNO[BX],AL MOV HOSTPORT[BX],AH ; ; UNTIL WE HAVE A CONFIG FILE, SET 4 INCOMING (LISTEN), AND 4 ; OUTGOING (APPLMASK=80H) ; PUSH AX PUSH CX MOV4 LOCALADDR[BX],MYIPADDR MOV LOCALSOCKET[BX],23 MOV4 REMOTEADDR[BX], HOSTADDR ; TESTING MOV REMOTESOCKET[BX],3600 CMP DCTSESSNO[BX],4 JBE INIT_IN ; ; OUTGOING (FROM SWITCH TO IP HOST) SESSIONS - RAISE APPLFLAGS ; MOV SESSIONTYPE[BX],'O' MOV AH,1 MOV AL,HOSTPORT[BX] MOV CL,1 ; COMMAND TO APPL MOV DL,80H ; APPL 8 PUSH BX CALL IPNODE POP BX JMP INIT_REST INIT_IN: MOV SESSIONTYPE[BX],'I' MOV TCPSTATE[BX],LISTEN MOV REMOTESOCKET[BX],0 MOV AH,1 MOV AL,HOSTPORT[BX] MOV CL,0 MOV DL,0 PUSH BX CALL IPNODE POP BX INIT_REST: MOV TCP_TIMER_START[BX],90 ; RETRY TIMER POP CX POP AX INC AL INC AH ADD BX,TYPE DCTENTRY LOOP DCTINIT ; ; AND TX BUFFER POOL ; MOV SI,OFFSET TXFREE_Q MOV DI,OFFSET TXPOOL MOV CX,TXBUFFS TXSETUPLOOP: CALL Q_ADDF INC TXBUFFCOUNT ADD DI,TXBUFLEN LOOP TXSETUPLOOP ; RET CODE ENDS END