A Simple NetCat Program - FujiNetWIFI/fujinet-firmware GitHub Wiki

To help get started, we've provided a simple netcat 'nc' like program that echoes received data, and sends keypresses to the specified host.

Atari BASIC

The BASIC version uses the N: handler, and thus is very compact and easy to understand. It is described in detail in this video: https://www.youtube.com/watch?v=LxT6w9JHfKk

100 OPEN #1,12,3,"N1:TCP://BBS.FOZZTEXX.NET/":OPEN #2,4,0,"K:"
101 TRAP 140
110 IF PEEK(764)<>255 THEN GET #2,K:PUT #1,K:XIO 15,#1,12,3,"N:"
120 STATUS #1,A:BW=PEEK(747)*256+PEEK(746):IF BW=0 THEN 110
130 FOR M=1 TO BW:GET #1,C:PUT #16,C:NEXT M:GOTO 110
140 CLOSE #1:? "DISCONNECTED.":END 

You can load a copy of it by loading "D:DUMBTERM.BAS" from the atari-apps.irata.online/Networking/n-handler/n-handler.atr via TNFS.

Modifying:

  • Change the hostname on line 100.
  • You can set the translation on line 100, 3 = CR/LF, 2 = LF, 1 = CR, 0 = NONE. Also change it on line 110.

FastBasic

The latest version of FastBasic supports FujiNet. Learn about the the N commands in the FastBasic Manual. The code below demonstrates opening a connection, getting and putting data, and closing the connection when done. Press [Escape] to end the program.

DIM result(255) BYTE:POKE 65,0
NOPEN 1, 12, 3, "N:TCP://BBS.FOZZTEXX.COM:23" 
REPEAT
  NSTATUS 1: bw = peek(746) ' Check if data is available
  IF bw: NGET 1, &result, bw: BPUT #0, &result, bw : ENDIF ' Get and write data to screen
  IF KEY() : GET k: NPUT 1, &k, 1: ENDIF ' Send keypress
UNTIL k = 27
NCLOSE 1:? "DISCONNECTED.":GET k

C

The C version can be found here: https://github.com/FujiNetWIFI/fujinet-apps/tree/master/netcat/atari/src

It can be compiled with CC65: http://cc65.github.io/

It shows:

Assembler

An assembler version that compiles with the MAD Assembler (aka MADS), can be found here: https://github.com/FujiNetWIFI/fujinet-apps/tree/master/netcat-asm

Assembler source code

; 
; NETCAT - THE Assembly / SIO VERSION - for FujiNet
; by Norman Davie
;
; Assembled using WUSDN / MADS
;
;     
    ORG $6000

; CIO
ICHID   = $0340     ;   Set by OS. Handler Identifier. If not 
                    ;   in use, the value is 255 ($FF), which 
                    ;   is also the initialization value.
ICDNO   = ICHID+1   ;   Set by OS. Device number (eg: D1: D2:).
ICCOM   = ICHID+2   ;   Set by User. Command
ICSTA   = ICHID+3   ;   Set by OS. May or may not be the same 
                    ;   value as CMD_STATUS returned
ICBAL   = ICHID+4   ;   Set by User. Buffer address (low byte)
ICBAH   = ICHID+5   ;   Set by User. buffer address (high byte)
ICPTL   = ICHID+6   ;   Used by BASIC. Address of put byte routine. 
ICPTH   = ICHID+7   ;   Used by BASIC. Address of put byte routine. 
ICBLL   = ICHID+8   ;   buffer length (low byte) in put/get operations
ICBLH   = ICHID+9   ;   buffer length (high byte)
ICAX1   = ICHID+10  ;   auxiliary information.  Used by Open cmd 
                    ;   for READ/WRITE/UPDATE
                    ;   Bit  7   6   5   4   3   2   1   0
                    ;   Use  ....unused....  W   R   D   A
                    ;   W equals write, R equals read, 
                    ;   D equals directory, A equals append.
ICAX2   = ICHID+11  ;   Auxiliary byte two
ICAX3   = ICHID+12  ;   Auxiliary bytes three
ICAX4   = ICHID+13  ;   Auxiliary bytes four
ICAX5   = ICHID+14  ;   Auxiliary bytes five
ICAX6   = ICHID+15  ;   Auxiliary bytes six
;
; BASIC CIO assignments
;      Channel   0    Permanently assigned to the screen editor
;                6    Used for graphics commands
;                7    Used for the Cassette, disk and printer
;
; Only use these channels if you have BASIC running, 
;  otherwise they're all available
;IOCB1   = $10       ; Channel 1
;IOCB2   = $20       ; Channel 2
;IOCB3   = $30       ; Channel 3
;IOCB4   = $40       ; Channel 4
;IOCB5   = $50       ; Channel 5
;
CIOV                	= $E456	; Y will contain status code after call


; SIO 
SIOV	=       $E459	; SIO VECTOR
EOL	=	$9B     ; EOL

DDEVIC	=	$300    ; DEVICE SERIAL BUS ID
DUNIT	=	$301    ; SERIAL UNIT NUMBER
DCOMND	=	$302    ; SIO COMMAND
DSTATS	=	$303    ; SIO DATA DIRECTION
DBUFLO	=	$304    ; BUFFER LOW ADDRESS
DBUFHI	=	$305    ; BUFFER HIGH ADDRESS
DTIMLO	=	$306    ; SIO TIMEOUT
DTIMHI	=	$307
DBYTLO	=	$308    ; BUFFER LENGTH
DBYTHI	=	$309
DAUX1	=	$30A    ; AUXILARY BYTE---PRINTER MODE
DAUX2	=	$30B    ; AUXILARY BYTE---NOT USED  

PACTL 	=	$D302	;
IRQ_ENABLE =    $01     ; PACTL IRQ Enable: If set, enables interrupts from peripheral A. 

VPRCED	=	$0202	; Serial (peripheral) proceed line vector, 
			; initialize to 59314 ($E7B2), which is merely a PLA, RTI instruction sequence. 
			; It is used when an IRQ interrupt occurs due to the serial I/O bus proceed 
			; line which is available for peripheral use. 

CH                  	= $2FC ; keyboard status buffer
LMARGIN			= $52
SOUNDR			= $41
BRKKEY			= $11 ; 0 = break key pressed

;
FUJI_DEVICE_ID		= $71

FUJI_TRANSLATE_CR_LF	= $03
FUJI_TRANSLATE_LF   	= $02
FUJI_TRANSLATE_CR   	= $01
FUJI_TRANSLATE_NONE 	= $00
;
FUJI_CLIENT         	= $00
FUJI_SERVER	    	= $01

FUJI_TIMEOUT		= $001F	; appoximately 30 seconds

;
; OPTIONS FOR ICCOM / XIO
;
; "record" of data, that is, data terminated by an ATASCII EOL ($9B) such as text.
CMD_OPEN       		= $03
CMD_GET_REC    		= $05    ; reads data from the device 
;                   		   until a carriage-return
CMD_GET_CHARS  		= $07    ; get bytes
CMD_PUT_REC    		= $09    ; print until EOL
CMD_PUT_CHARS  		= $0B    ; put bytes 
CMD_CLOSE      		= $0C
CMD_STATUS     		= $0D

; STATUS CODES
; OS STATUS CODES

SUCCES   		= $01	; SUCCESSFUL OPERATION
BRKABT   		= $80	; (128) BREAK KEY ABORT
PRVOPN   		= $81	; (129) IOCB ALREADY OPEN
NONDEV   		= $82 	; (130) NON-EX DEVICE
WRONLY   		= $83 	; (131) IOCB OPENED FOR WRITE ONLY
NVALID   		= $84	; (132) INVALID COMMAND
NOTOPN   		= $85 	; (133) DEVICE OR FILE NOT OPEN
BADIOC   		= $86   ; (134) INVALID IOCB NUMBER
RDONLY   		= $87   ; (135) IOCB OPENED FOR READ ONLY
EOFERR   		= $88   ; (136) END OF FILE
TRNRCD   		= $89   ; (137) TRUNCATED RECORD
TIMOUT   		= $8A   ; (138) DEVICE TIMEOUT
DNACK    		= $8B   ; (139) DEVICE DOES NOT ACK COMMAND
FRMERR   		= $8C   ; (140) SERIAL BUS FRAMING ERROR
CRSROR   		= $8D   ; (141) CURSOR OUT OF RANGE
OVRRUN   		= $8E   ; (142) SERIAL BUS DATA OVERRUN
CHKERR   		= $8F   ; (143) SERIAL BUS CHECKSUM ERROR
DERROR   		= $90   ; (144) DEVICE ERROR (OPERATION INCOMPLETE)
BADMOD   		= $91   ; (145) BAD SCREEN MODE NUMBER
FNCNOT   		= $92   ; (146) FUNCTION NOT IN HANDLER
SCRMEM   		= $93   ; (147) INSUFFICIENT MEMORY FOR SCREEN MODE
;
; OPTIONS FOR OPEN ICAX1
OREAD  	 		= $04	; Read Only
OWRITE   		= $08	; Write Only
OUPDATE  		= $0C	; Read and write
ODIR     		= $02	; Directory
ORDIR    		= $06	; Read Directory
OAPPEND  		= $01	; Append
OWAPPEND 		= $09	; Write Append

SCLEAR_GT		= $10	; Clear Graphics and Text
SKEEP_GT 		= $20	; Keep Graphics and Text
SCLEAR_T 		= $30	; Clear Text

DWRITE			= $80   ; sending   data to SIO device
DREAD			= $40   ; receiving data from the SIO device

DVSTAT			= $02EA
DVSTAT_BYTES_WAITING_LO = $02EA
DVSTAT_BYTES_WAITING_HI = $02EB
DVSTAT_PROTOCOL		= $02EC ; depends on the protocol
DVSTAT_EXTENDED_ERROR	= $02ED

;
; largest GET/POST length is 2048, but we'll 
; keep it to BUFSIZE characters
BUFSIZE     		= 256	; NOTE: Code assumes a multiple of 256 byte buffer size
;
; ACCEPTABLE COMBINATIONS
;
; SCREEN (E:)
; OWRITE            - Screen Output
; OUPDATE           - Keyboard Input / Screen Write
; OUPDATE + OAPPEND - Keyboard Input / Screen Read and Write
; NOTE:   E: BIT 0 equals one is a forced read (GET command).
;
; KEYBOARD (K:)
; OREAD
;
; PRINTER (P:)
; OWRITE
;
; RS-232 (R:)
; OWRITE            - Block write
; OWRITE  + OAPPEND - Concurrent write
; OUPDATE + OAPPEND - Concurrent read and write
;
; SCREEN (S:)
;                             Clear      Text      Read
;                             Screen     Window    Oper-
;                             after GR.  also      ation
;
; OWRITE                      yes        no        no
; OUPDATE                     yes        no        yes
; SCLEAR_GT + OWRITE          yes        yes       no
; SCLEAR_GR + OUPDATE         yes        yes       yes
; SKEEP_GT  + OWRITE          no         no        no
; SKEEP_GT  + OUPDATE         no         no        yes
; SCLEAR_T  + OWRITE          no         yes       no
; SCLEAR_T  + OUPDATE         no         yes       yes
;
;
; Set up screen and keyboard access
;
START:
	LDA #$00				; make the left margin column 0
	STA LMARGIN
		    
	JSR GET_CHANNEL				; find an available IOCB channel
	CPX #$80
    	BNE GOT_SCREEN_CHANNEL
    	JMP ERROR_HANDLER     			; If X == $80, no CIO available
;
GOT_SCREEN_CHANNEL:

; Remember the channel we're using (Currently in X)

    	STX SCREEN_KEYBOARD_CHANNEL  

; Open Available Channel to the Screen

    	LDA #CMD_OPEN           		; open the device
    	STA ICCOM,X  
           
    	LDA #<SCREEN_KEYBOARD_DEV		; address of the string of the device (E:)
    	STA ICBAL,X
    	LDA #>SCREEN_KEYBOARD_DEV  
    	STA ICBAH,X

    	LDA #OUPDATE            		; put chars to screen get chars from keyboard
    	STA ICAX1,X

    	LDA #$00				; not used, but good practice to store zero here
    	STA ICAX2,X
            	
    	STA ICBLH,X				; 0/0 End of string is indicated by $9B
    	STA ICBLL,X
    	JSR CIOV
	BPL DISPLAY_VERSION 			; Opened device

    	JMP ERROR_HANDLER			; Geesh, we couldn't even display an error message

; Display program name

DISPLAY_VERSION:
    	LDA #<PROG_NAME
    	LDY #>PROG_NAME
    	JSR PRINT_STRING
  	  	
; Ask for the URL 

ASK_QUESTION:
	LDX SCREEN_KEYBOARD_CHANNEL
    	LDA #CMD_PUT_REC       			; Prepare to send to screen
    	STA ICCOM,X
    	
    	LDA #<ASK_SITE         			; Get our string
    	STA ICBAL,X
    	LDA #>ASK_SITE
    	STA ICBAH,X

    	LDA #$00				; EOL ends our string
    	STA ICBLL,X
    	LDA #$FF
    	STA ICBLH,X

    	JSR CIOV               
    	BPL READ_URL            		; start processig the user input
	CMP #$80				; break key was pressed?
	BNE NOT_BREAK1b
	JMP DISCONNECT
NOT_BREAK1b:
    	LDA #<KEYBOARD_FAILURE			; something very strange happened
    	LDY #>KEYBOARD_FAILURE
    	JSR PRINT_STRING
    	JMP ERROR_HANDLER       		
;
READ_URL:

; Get the users response or if he just presses RETURN use the default

    	LDA #CMD_GET_REC       			; Get input from keyboard
    	STA ICCOM,X
    	
    	LDA #<URL_FUJI      			; Where to store the result
    	STA ICBAL,X
    	LDA #>URL_FUJI  	
    	STA ICBAH,X   
    	         
    	LDA #<BUFSIZE          			; Max buffer size
    	STA ICBLL,X
    	LDA #>BUFSIZE
    	STA ICBLH,X
    	
    	JSR CIOV
	BPL PROCESS_URL        			; all is well
	CMP #$80				; is the break key held down?
	BNE NOT_BREAK2
	JMP DISCONNECT
NOT_BREAK2:
    	JMP ERROR_HANDLER      			; An error occurred
;
PROCESS_URL:
    	CLC
    	LDA ICBLH,X 				; returned length of string
    	ADC ICBLL,X 
    	CMP #$01 				; Equals 1 if just pressed return (or unlikely 256 characters)
    	BNE ASK_TRANSLATION
    	
; Copy the default URL

    	LDY #$FF    
COPY_DEFAULT:					; if return was pressed, then copy the default url
    	INY					; which is a zero terminated string
    	LDA URL_DEFAULT,Y 
    	STA URL_FUJI,Y 
    	BNE COPY_DEFAULT
    	
	
; TRANSLATION

ASK_TRANSLATION:

; this string has multiple EOL, so we can't use our print string routine

   	LDX SCREEN_KEYBOARD_CHANNEL
   	LDA #<TRANSLATION
   	STA ICBAL,X
   	LDA #>TRANSLATION
    	STA ICBAH,X
    	LDA #CMD_PUT_CHARS   			; Prepare to send to screen
    	STA ICCOM,X
    	LDA #$00
    	STA ICBLH,X
    	LDA #<(END_TRANS-TRANSLATION)		; the number of characters to output
    	STA ICBLL,X
    	JSR CIOV
    	BPL READ_TRANSLATION           		; All is well
	CMP #$80
	BNE NOT_BREAK1
	JMP DISCONNECT
NOT_BREAK1:
    	LDA #<KEYBOARD_FAILURE
    	LDY #>KEYBOARD_FAILURE
    	JSR PRINT_STRING
    	JMP ERROR_HANDLER       		; Oh oh
;
READ_TRANSLATION:
; read the keyboard
    	LDA #CMD_GET_REC       			; Get input from keyboard
    	STA ICCOM,X
    	
    	LDA #<TEMP_BUFFER      			; Where to store the result
    	STA ICBAL,X
    	LDA #>TEMP_BUFFER  	
    	STA ICBAH,X   
    	         
    	LDA #<BUFSIZE          			; Max buffer size
    	STA ICBLL,X
    	LDA #>BUFSIZE
    	STA ICBLH,X
    	
    	JSR CIOV
	BPL PROCESS_TRANS       			; all is well
	CMP #$80				; is the break key held down?
	BNE NOT_BREAK2b
	JMP DISCONNECT
NOT_BREAK2b:
    	JMP ERROR_HANDLER      			; An error occurred
;
PROCESS_TRANS:
    	LDA TEMP_BUFFER
    	CMP #$9B 				; Equals 1 if just pressed return (or unlikely 256 characters)
    	BNE USER_TRANSLATION
; Copy the default URL
    	LDA #FUJI_TRANSLATE_CR_LF
    	BNE STORE_TRANS
USER_TRANSLATION:    	
    	SEC
    	SBC '0'
STORE_TRANS:    	
    	STA TRANS

ASK_ECHO:
	LDA #<ECHO_INPUT
	LDY #>ECHO_INPUT
	JSR PRINT_STRING

READ_ECHO:
; read the keyboard
    	LDA #CMD_GET_REC       			; Get input from keyboard
    	STA ICCOM,X
    	
    	LDA #<TEMP_BUFFER      			; Where to store the result
    	STA ICBAL,X
    	LDA #>TEMP_BUFFER  	
    	STA ICBAH,X   
    	         
    	LDA #<BUFSIZE          			; Max buffer size
    	STA ICBLL,X
    	LDA #>BUFSIZE
    	STA ICBLH,X
    	
    	JSR CIOV
	BPL PROCESS_ECHO       			; all is well
	CMP #$80				; is the break key held down?
	BNE NOT_BREAK2c
	JMP DISCONNECT
NOT_BREAK2c:
    	JMP ERROR_HANDLER      			; An error occurred
;
PROCESS_ECHO:
	LDA TEMP_BUFFER
	CMP #$9B				; Did the user just press RETURN?
    	BEQ NO_ECHO				; use default
	CMP #'Y'				; How about Yes
	BNE NO_ECHO
    	LDA #$01 				; Equals 1 if just pressed return (or unlikely 256 characters)
    	BNE STORE_ECHO
NO_ECHO:
    	LDA #$00				; Store No echo
STORE_ECHO:
	STA ECHO
	
;
;
; Set up screen and keyboard access
;
OPEN_KEYBOARD:
    	JSR GET_CHANNEL				; we get a keyboard channel so we don't get echo
    	CPX #$80
    	BNE FOUND_KEYBOARD_CHANNEL
    	JMP ERROR_HANDLER  			; If X == $80, no CIO available
;
FOUND_KEYBOARD_CHANNEL:
; Remember the channel we're using (Stored in X)
    	STX KEYBOARD_CHANNEL  
    	
; Open Available Channel to the Keyboard
    	LDA #CMD_OPEN           		; open the device
    	STA ICCOM,X        
    	     
    	LDA #<KEYBOARD_DEV      		; address of the string of the device (E)
    	STA ICBAL,X
    	LDA #>KEYBOARD_DEV  
    	STA ICBAH,X
    	
    	LDA #OREAD              		; get chars from keyboard
    	STA ICAX1,X
    	
    	LDA #$00
    	STA ICAX2,X
    	
    	STA ICBLH,X				; Not needed for open
    	STA ICBLL,X
    	
    	JSR CIOV
    	BPL OPEN_KEYBOARD_OK    		; Opened device successfully
    	TYA
    	PHA
    	LDA #<KEYBOARD_FAILURE
    	LDY #>KEYBOARD_FAILURE
    	JSR PRINT_STRING
    	PLA  	

    	JMP ERROR_HANDLER
    	
OPEN_KEYBOARD_OK:


GET_UNIT_NUMBER:
	LDY #1					; Unit number is the 2nd or 3rd character
	LDA URL_FUJI,Y
	CMP #':'				; if we found a : then it must be the 3rd
	BNE GET_NUM
	LDA #$01
	BNE UNIT_NUM
GET_NUM:
	SEC
	SBC #'0'				; we should do some sanity check here, but nah
UNIT_NUM:
	STA UNIT				; store it as a raw number

SET_INTERRUPTS:

; insure interrupts are off before changing vector

	LDA PACTL				; IRQ Enable: If set, enables interrupts from peripheral A. 
	STA OLD_INTERRUPT_STATE
	AND #~IRQ_ENABLE 			; disable the interrupt
	STA PACTL

	LDA VPRCED				; save the old interrupt vector, but jump to it after
	STA OLD_INTERRUPT_HANDLER		; we process it
	LDA VPRCED+1
	STA OLD_INTERRUPT_HANDLER+1
	
	LDA #<INTERRUPT_HANDLER			; put our interrupt handler in place
	STA VPRCED
	LDA #>INTERRUPT_HANDLER
	STA VPRCED+1
	
	LDA #$00			
	STA FUJI_DATA_WAITING			; before we start interrupts, let's tell it we have no data
	
	LDA PACTL				; IRQ Enable 
	ORA #IRQ_ENABLE 			
	STA PACTL

;---------------------------------------------------------------
;---------------------------------------------------------------
; OPEN
;---------------------------------------------------------------
;---------------------------------------------------------------
OPEN_FUJI:

	LDA #<CONNECTING			; show the user where we're connecting to
	LDY #>CONNECTING
	JSR PRINT_STRING
	
	LDA #<URL_FUJI				
	LDY #>URL_FUJI
	JSR PRINT_STRING
	
	
	LDA #FUJI_DEVICE_ID			; Device
	STA DDEVIC
	
	LDA UNIT
	STA DUNIT
	
	LDA #'O'				; Open
	STA DCOMND
	
	LDA #DWRITE				; sending filespec to SIO
	STA DSTATS
	
	LDA #<URL_FUJI				; filespec
	STA DBUFLO
	LDA #>URL_FUJI
	STA DBUFHI
	
	LDA #<FUJI_TIMEOUT			; Approximate the number of seconds to wait
	STA DTIMLO
	LDA #>FUJI_TIMEOUT
	STA DTIMHI
	
	LDA #<BUFSIZE				; the size of the buffer containing the URL
	STA DBYTLO
	LDA #>BUFSIZE
	STA DBYTHI
	
	LDA #OUPDATE				; Read and Write 
	STA DAUX1
	
	LDA #FUJI_TRANSLATE_CR_LF		; translate cr and lf
	STA DAUX2

	JSR SIOV

	LDA DSTATS				; find out if we were successful
	CMP #SUCCES
	BEQ OPEN_FUJI_OK        		; Opened device successfully
; OPEN FAILURE
    	TYA
    	PHA
    	LDA #<FUJI_ERROR
    	LDY #>FUJI_ERROR
    	JSR PRINT_STRING
    	PLA
    	PHA
    	CMP #TIMOUT
    	BNE NOT_TIMEOUT4
    	LDA #<DEVICE_TIMEOUT_FAILURE		; we timed out
    	LDY #>DEVICE_TIMEOUT_FAILURE
    	JSR PRINT_STRING
NOT_TIMEOUT4:    	
    	PLA  	    	
	JSR PRINT_HEX				; print the error code
    	JMP ERROR_HANDLER

OPEN_FUJI_OK:
	LDA #$00				; at this point, no more sound
	STA SOUNDR 				; SILENCE


; MAIN EVENT!
;
CHECK_KEYBOARD:
	LDA BRKKEY				; Doing it this way works
	BNE LOOK_KEYS				; on all versions of roms
	JMP DISCONNECT
	
LOOK_KEYS:	
    	LDA CH					; did the user press a key?
    	CMP #$FF
    	BNE GET_KEY
    	JMP FUJI_STATUS
GET_KEY:

; Get the key but don't display it
    	LDX KEYBOARD_CHANNEL
    	LDA #CMD_GET_CHARS       		; Get character from keyboard
    	STA ICCOM,X
    	
    	LDA #<TEMP_BUFFER       		; Where to store the character
    	STA ICBAL,X
    	LDA #>TEMP_BUFFER
    	STA ICBAH,X   
    	         
    	LDA #$01           	     		; Just one character
    	STA ICBLL,X
    	LDA #$00
    	STA ICBLH,X
    	
    	JSR CIOV
    	BPL SEND_KEY            		; all is well
	CMP #$80
	BNE NOT_BREAK3
	JMP DISCONNECT
NOT_BREAK3:
	LDA #<KEYBOARD_FAILURE			; what the???
	LDY #>KEYBOARD_FAILURE
	JSR PRINT_STRING
    	JMP DISCONNECT          		; assume disconnected

	LDA ECHO
	BEQ SEND_KEY

ECHO_KEYS:

; Print the data
    	LDX SCREEN_KEYBOARD_CHANNEL
    	LDA #CMD_PUT_CHARS        		; Prepare to send to screen
    	STA ICCOM,X
    	LDA #<TEMP_BUFFER       		; Where to store the result
    	STA ICBAL,X
    	LDA #>TEMP_BUFFER
    	STA ICBAH,X
    	LDA #$01  	    			; How many characters to send
    	STA ICBLL,X
    	LDA #$00
    	STA ICBLH,X
    	JSR CIOV               
    	BPL KEY_PRINTED    			; All is well
	CMP #$80
	BNE NOT_BREAK4
	JMP DISCONNECT
NOT_BREAK4:

	TYA
    	PHA
    	LDA #<SCREEN_PUT_FAILURE
    	LDY #>SCREEN_PUT_FAILURE
    	JSR PRINT_STRING
    	PLA  	
    	JMP DISCONNECT      			; Assume disconnect
;
KEY_PRINTED:
    	JMP SEND_KEY
;

;    

;---------------------------------------------------------------
;---------------------------------------------------------------
; WRITE
;---------------------------------------------------------------
;---------------------------------------------------------------
SEND_KEY:
; Send the key to the outside world
  	LDA #FUJI_DEVICE_ID			; Device
	STA DDEVIC
	
	LDA UNIT
	STA DUNIT
	
	LDA #'W'
	STA DCOMND
	
	LDA #DWRITE
	STA DSTATS
	
	LDA #<TEMP_BUFFER
	STA DBUFLO
	LDA #>TEMP_BUFFER
	STA DBUFHI
	
	LDA #<FUJI_TIMEOUT
	STA DTIMLO
	LDA #>FUJI_TIMEOUT
	STA DTIMHI

	LDA #$01 				; one character to send
	STA DBYTLO				; need to send both buffer size and the daux 
	STA DAUX1
	
	LDA #$00
	STA DBYTHI
	STA DAUX2
	
	JSR SIOV

	LDA DSTATS
	CMP #SUCCES
	BEQ FUJI_STATUS				; all is well
    	TYA
    	PHA
    	LDA #<PUT_FUJI_FAILURE
    	LDY #>PUT_FUJI_FAILURE
    	JSR PRINT_STRING
    	PLA
    	PHA
    	CMP #TIMOUT
    	BNE NOT_TIMEOUT3
    	LDA #<DEVICE_TIMEOUT_FAILURE
    	LDY #>DEVICE_TIMEOUT_FAILURE
    	JSR PRINT_STRING
NOT_TIMEOUT3:      	
    	PLA  	
    	JSR PRINT_HEX  

	JMP DISCONNECT          		; assume disconnect

;---------------------------------------------------------------
;---------------------------------------------------------------
; STATUS
;---------------------------------------------------------------
;---------------------------------------------------------------
FUJI_STATUS: 
  	LDA #FUJI_DEVICE_ID			; Device
	STA DDEVIC
	
	LDA UNIT
	STA DUNIT
	
	LDA #'S'
	STA DCOMND
	
	LDA #DREAD				; SIO is going to send us data
	STA DSTATS
	
	LDA #<DVSTAT				; fuji status will return length of buffer here
	STA DBUFLO
	LDA #>DVSTAT
	STA DBUFHI
	
	LDA #<FUJI_TIMEOUT
	STA DTIMLO
	LDA #>FUJI_TIMEOUT
	STA DTIMHI
	
	LDA #$04 				; four bytes
	STA DBYTLO

	LDA #$00
	STA DBYTHI
	
	STA DAUX1
	STA DAUX2
	
	JSR SIOV
	
CHECK_FOR_FUJI_DATA:

    	LDA FUJI_DATA_WAITING			; set by interrupt handler
    	BNE GET_FUJI_BYTES_WAITING		; something is coming in!
    	JMP CHECK_KEYBOARD			; if zero, no data

GET_FUJI_BYTES_WAITING:
;
; data has shown up!
;
	CLC					; check and see if we have data
	LDA DVSTAT_BYTES_WAITING_LO
	ADC DVSTAT_BYTES_WAITING_HI
	BNE FUJI_DATA_INCOMING			; yes
	JMP CHECK_KEYBOARD			; nope

FUJI_DATA_INCOMING:	

	LDA DVSTAT_BYTES_WAITING_LO		; find out how much data we have
	STA DATA_SIZE_LO
	
	LDA DVSTAT_BYTES_WAITING_HI
	STA DATA_SIZE_HI	

	CMP #>BUFSIZE				; is it more data than our buffer?
	BMI BUFFER_SIZE_OK
	
; if the amount of data coming is
; greater than our buffer, clamp it

	LDA #<BUFSIZE
	STA DATA_SIZE_LO
	LDA #>BUFSIZE
	STA DATA_SIZE_HI

	
BUFFER_SIZE_OK:	


;---------------------------------------------------------------
;---------------------------------------------------------------
; READ
;---------------------------------------------------------------
;---------------------------------------------------------------
GET_FUJI_DATA:

  	LDA #FUJI_DEVICE_ID			; Device
	STA DDEVIC
	
	LDA UNIT
	STA DUNIT
	
	LDA #'R'
	STA DCOMND

	LDA #DREAD
	STA DSTATS
	
	LDA #<TEMP_BUFFER
	STA DBUFLO
	LDA #>TEMP_BUFFER
	STA DBUFHI
	
	LDA #<FUJI_TIMEOUT 
	STA DTIMLO
	LDA #>FUJI_TIMEOUT
	STA DTIMHI
	
	LDA DATA_SIZE_HI
	STA DBYTHI
	STA DAUX2

	LDA DATA_SIZE_LO
	STA DBYTLO
	STA DAUX1

	JSR SIOV

	LDA DSTATS	
	CMP #SUCCES
    	BEQ DATA_HANDLED          		; all is well
	TYA
    	PHA
    	LDA #<FUJI_DATA_FAILURE
    	LDY #>FUJI_DATA_FAILURE
    	JSR PRINT_STRING
    	PLA
    	PHA
    	CMP #TIMOUT
    	BNE NOT_TIMEOUT2
    	LDA #<DEVICE_TIMEOUT_FAILURE
    	LDY #>DEVICE_TIMEOUT_FAILURE
    	JSR PRINT_STRING
NOT_TIMEOUT2:      	
    	PLA  	
    	JMP DISCONNECT          	

DATA_HANDLED:
	LDA #$00
	STA FUJI_DATA_WAITING
	
; re-enable interrupts
	LDA PACTL				; IRQ Enable: If set, enables interrupts from peripheral A. 
	ORA #IRQ_ENABLE 			; If clear, interrupts are disabled.
	STA PACTL
;
PRINT_INCOMING:
; Print the data
    	LDX SCREEN_KEYBOARD_CHANNEL
    	LDA #CMD_PUT_CHARS        		; Prepare to send to screen
    	STA ICCOM,X
    	LDA #<TEMP_BUFFER       		; Where to store the result
    	STA ICBAL,X
    	LDA #>TEMP_BUFFER
    	STA ICBAH,X
    	LDA DATA_SIZE_LO  	    		; How many characters to send
    	STA ICBLL,X
    	LDA DATA_SIZE_HI
    	STA ICBLH,X
    	JSR CIOV               
    	BPL DATA_PRINTED    			; All is well
	CMP #$80
	BNE NOT_BREAK5
	JMP DISCONNECT
NOT_BREAK5:

	TYA
    	PHA
    	LDA #<SCREEN_PUT_FAILURE
    	LDY #>SCREEN_PUT_FAILURE
    	JSR PRINT_STRING
    	PLA  	
	JMP DISCONNECT      			; Assume disconnect
;
DATA_PRINTED:
    	JMP CHECK_KEYBOARD
;
DISCONNECT:
    	LDA #<DISCONNECTED
    	LDY #>DISCONNECTED
    	JSR PRINT_STRING
;
ERROR_HANDLER:

; Wait for RETURN  key from screen *IF* we have the screen and keyboard channel

    	JSR WAIT_FOR_RETURN
;    
CLOSE_ALL:
   
; Close all the channels, but 
; don't close channels that were never opened

    	LDX SCREEN_KEYBOARD_CHANNEL
    	JSR CLOSE_CHANNEL

    	LDX KEYBOARD_CHANNEL
    	JSR CLOSE_CHANNEL
    	
;
;---------------------------------------------------------------
;---------------------------------------------------------------
; restore the old interrupt state
;---------------------------------------------------------------
;---------------------------------------------------------------
	LDA PACTL	 		; IRQ Enable: If set, enables interrupts from peripheral A. 
	AND #~IRQ_ENABLE 		; If clear, interrupts are disabled.
	STA PACTL
	
	LDA OLD_INTERRUPT_HANDLER+1	; Put back the old vector
	STA VPRCED+1
	LDA OLD_INTERRUPT_HANDLER
	STA VPRCED
	
	LDA OLD_INTERRUPT_STATE		; if interrupts were enabled before we started
	AND #IRQ_ENABLE			; restore interrupts
	ORA PACTL
	STA PACTL
	
	
;---------------------------------------------------------------
;---------------------------------------------------------------
; CLOSE
;---------------------------------------------------------------
;---------------------------------------------------------------
;
  	LDA #FUJI_DEVICE_ID		; Device
	STA DDEVIC
	
	LDA UNIT
	STA DUNIT
	
	LDA #'C'
	STA DCOMND
	
	LDA #$00
	STA DSTATS
	STA DBUFLO
	STA DBUFHI
	
	LDA #<FUJI_TIMEOUT
	STA DTIMLO
	LDA #>FUJI_TIMEOUT
	STA DTIMHI
	
	LDA #$00
	STA DBYTHI
	LDA #$01
	STA DBYTLO
	
	LDA #$00
	STA DAUX1
	STA DAUX2

	JSR SIOV
	
	
	LDA #$01			; Let the noise return
	STA SOUNDR 			; NOISE

    	RTS
    	

	 
;====================================
; WAIT_FOR_RETURN
;   If screen_keyboard_channel is open
;   then waits for enter key
; RETURNS
;   Nothing
; OTHER REGISTERS AFFECTED
;   None
;====================================    	
WAIT_FOR_RETURN:    	
; Wait for enter key from screen *IF* we have the screen and keyboard channel
	PHA ; SAVE REGISTERS
	TXA
	PHA
	TYA
	PHA

    	LDX SCREEN_KEYBOARD_CHANNEL
    	CPX #$7F
    	BPL WFE_DONE
    	LDA #<WAITING
    	LDY #>WAITING
    	JSR PRINT_STRING
    
; wait for a key
    	LDX SCREEN_KEYBOARD_CHANNEL
    	LDA #CMD_GET_REC       	; Get input from keyboard
    	STA ICCOM,X
    	LDA #<WAIT_BUFFER       	; Where to store the result
    	STA ICBAL,X
    	LDA #>WAIT_BUFFER
    	STA ICBAH,X            
    	LDA #<BUFSIZE           		; Max buffer size
    	STA ICBLL,X
    	LDA #>BUFSIZE
    	STA ICBLH,X
    	JSR CIOV
    	
    	PLA ; RESTORE REGISTERS
    	TAY
    	PLA
    	TAX
    	PLA
WFE_DONE:
    	RTS
    	

DUMP_SIO:
	LDY #00
DUMP:
	LDA DDEVIC,Y
	JSR PRINT_HEX_NO_EOL
	INY
	CPY #17
	BNE DUMP
	LDA #<EOL_ENDING
	LDY #>EOL_ENDING
	JSR PRINT_STRING
	RTS
	  	    	    	
;====================================
; CLOSE_CHANNEL
;   Close channel if open
;   X = channel to close
;     will be $80 if never opened
; RETURNS
;   Nothing
; OTHER REGISTERS AFFECTED
;   ALL
;====================================
CLOSE_CHANNEL:
    	CPX #$7F
    	BPL ALREADY_CLOSED
; Close the channel
    	LDA #CMD_CLOSE
    	STA ICCOM,X
    	JSR CIOV
ALREADY_CLOSED:
    	RTS
;
;====================================
; GET_CHANNEL
;  Look for an unused channel
; RETURNS
;   X - Available channel * 16
;   if X == $80, no channel found
; OTHER REGISTERS AFFECTED
;   ALL
;====================================
GET_CHANNEL:
   	LDY #$00
CHECK_CHANNEL:
   	TYA      		; Transfer Y to the Accumulator
   	CLC
   	ROL   		; Multiply it by 16
   	ROL
   	ROL
   	ROL
   	TAX      		; Transfer A to the X register
   	LDA ICHID,X
   	CMP #$FF 		; if the Channel ID is FF it's available
   	BEQ AVAILABLE
   	INY
   	CMP #$07
   	BNE CHECK_CHANNEL
   	LDX #$80		; $80 indicates no channel available
AVAILABLE:
   	RTS
;
;====================================
; PRINT_STRING
;   Displays message on screen
;   A = low byte of address
;   Y = high byte of address
;   assumes succefull open of screen
; RETURNS
;   NOTHING
; OTHER REGISTERS AFFECTED
;   NONE
;====================================
PRINT_STRING:
    	LDX SCREEN_KEYBOARD_CHANNEL
    	STA ICBAL,X
    	TYA
    	STA ICBAH,X
    	LDA #CMD_PUT_REC   	; Prepare to send to screen
    	STA ICCOM,X
    	LDA #$00
    	STA ICBLL,X
    	LDA #$FF
    	STA ICBLH,X
    	JSR CIOV
    	RTS
;
;====================================
; STRING_LENGTH
;   Calculate string length ended by $9B
; RETURNS
;   Y = Length
; OTHER REGISTERS AFFECTED
;   A
;====================================
STRING_LENGTH:
    	LDY #$FF
TEST:
    	INY
    	LDA URL_FUJI,Y
    	CMP #$9B
    	BNE TEST
    	INY	; length is not zero based
    	RTS
;
;====================================
; PRINT_HEX_NO_EOL
;   Displays the A register on screen
;   assumes succefull open of screen
; RETURNS
;   NOTHING
; REGISTERS AFFECTED
;   NONE
;====================================
PRINT_HEX_NO_EOL:
   	PHA		; Save A once
   	PHA          	; Save A again
	PHA		; Third times the charm
	LDA #$20 	; SPACE
	STA HEX_ENDING
	PLA
   	LSR 
   	LSR 
   	LSR 
   	LSR     	
   	CLC
   	CMP #$0A
   	BPL A_F3
   	ADC #'0'
   	JMP STORE_TOP_NIBBLE1
A_F3:
   	ADC #'A'-$0B
STORE_TOP_NIBBLE1:
   	STA HEX_BUFFER
;   
   	PLA		; Get back A
   	AND #$0F
   	CLC
   	CMP #$0A
   	BPL A_F4
   	ADC #'0'
   	JMP STORE_BOT_NIBBLE1
A_F4:
   	ADC #'A'-$0B
STORE_BOT_NIBBLE1:
   	STA HEX_BUFFER+1 
; $9B is already in HEX_BUFFER+2
   	TXA
   	PHA		; Save X

	TYA		; Save Y
	PHA

   	LDX SCREEN_KEYBOARD_CHANNEL
   	LDA #<HEX_BUFFER
   	STA ICBAL,X
   	LDA #>HEX_BUFFER
    	STA ICBAH,X
    	LDA #CMD_PUT_CHARS   	; Prepare to send to screen
    	STA ICCOM,X
    	LDA #$00
    	STA ICBLH,X
    	LDA #$03
    	STA ICBLL,X
    	JSR CIOV

	PLA
	TAY		; Restore Y
	
   	PLA
   	TAX		; Restore X

   	PLA		; Restore A
   	RTS  

;====================================
; PRINT_HEX
;   Displays the A register on screen
;   assumes succefull open of screen
; RETURNS
;   NOTHING
; REGISTERS AFFECTED
;   NONE
;====================================
PRINT_HEX:

   	PHA		; Save A once
   	PHA          	; Save A again
	PHA		; Third times the charm
	LDA #$9B 	; EOL
	STA HEX_ENDING
	PLA
	LSR 
   	LSR 
   	LSR 
   	LSR     	
   	CLC
   	CMP #$0A
   	BPL A_F1
   	ADC #'0'
   	JMP STORE_TOP_NIBBLE
A_F1:
   	ADC #'A'-$0B
STORE_TOP_NIBBLE:
   	STA HEX_BUFFER
;   
   	PLA		; Get back A
   	AND #$0F
   	CLC
   	CMP #$0A
   	BPL A_F2
   	ADC #'0'
   	JMP STORE_BOT_NIBBLE
A_F2:
   	ADC #'A'-$0B
STORE_BOT_NIBBLE:
   	STA HEX_BUFFER+1 
; $9B is already in HEX_BUFFER+2
   	TXA
   	PHA		; Save X
   	TYA
   	PHA		; Save Y
   	LDA #<HEX_BUFFER
   	LDY #>HEX_BUFFER
   	JSR PRINT_STRING
   	PLA
   	TAY		; Restore Y
   	PLA
   	TAX		; Restore X
   	PLA		; Restore A
   	RTS  


;------------------------
;------------------------   
;------------------------
;------------------------   		
INTERRUPT_HANDLER:
	LDA #$01
	STA FUJI_DATA_WAITING
        .BYTE $4C 	; JMP
OLD_INTERRUPT_HANDLER:	; to old vector
	.WORD 0
	
;=================DATA===============
;
ASK_SITE:		.byte 'Enter URL (ENTER for default): ',$9B
TRANSLATION:		.byte 'TRANSLATION',$9B
                        .byte '  0 = NONE, 1=CR, 2=LF, *3=CR/LF?',$9B
                        .byte '  *=Default',$9B
END_TRANS:
ECHO_INPUT:		.byte 'LOCAL ECHO? Y=YES, *NO? *Default', $9B  
 
CONNECTING:		.byte 'Connecting to: ',$9B

DISCONNECTED:		.byte 'Disconnected.',$9B

WAITING:		.byte 'Press RETURN.',$9B

FUJI_OPENED:		.byte 'Fuji successfully opened.',$9B
NO_CHANNEL_ERROR: 	.byte 'No CIO channel available',$9B
FUJI_ERROR:		.byte 'Could not open FujiNet Device.',$9B
STATUS_FAILURE:		.byte 'Status failure.', $9B
KEYBOARD_FAILURE 	.byte 'Keyboard failure', $9B
FUJI_DATA_FAILURE: 	.byte 'Fuji data failure.',$9B
SCREEN_PUT_FAILURE: 	.byte 'Screen Output failure.',$9B
PUT_FUJI_FAILURE:	.byte 'Put fuji failure.',$9B
DEVICE_TIMEOUT_FAILURE: .byte 'Device Timeout.',$9B

KEYBOARD_DEV:		.byte 'K:',$9B
SCREEN_KEYBOARD_DEV: 	.byte 'E:',$9B
FUJI_DEV:		.byte 'N:',$9B

KEYBOARD_CHANNEL: 	 .BYTE $80
SCREEN_KEYBOARD_CHANNEL: .BYTE $80

ECHO			.BYTE $01
TRANS			.BYTE $03
FUJI_DATA_WAITING	.BYTE $00
OLD_INTERRUPT_STATE	.BYTE $00

HEX_BUFFER:		.BYTE $00,$00
HEX_ENDING:		.BYTE $9B
EOL_ENDING		.BYTE $9B

PROG_NAME:		.byte 'NETCAT SIO/ASM Version 2020/11/13 5:00',$9B
URL_DEFAULT:		.byte 'N1:TCP://BBS.FOZZTEXX.NET/',$9B,00

DATA_SIZE_LO:		.BYTE 0
DATA_SIZE_HI:		.BYTE 0

UNIT			.BYTE 0

URL_FUJI:		.DS   BUFSIZE
			
TEMP_BUFFER:		.DS   BUFSIZE
WAIT_BUFFER:		.DS   BUFSIZE 


    run START

ACTION!

https://github.com/FujiNetWIFI/fujinet-apps/tree/master/netcat-action

NIO.ACT - Example Network I/O bindings

;
; #FujiNet Network I/O Library    
; For ACTION!
;
; Author: Thomas Cherryhomes
;  <[email protected]>
;
; These routines call the #Fujinet
; directly from SIO, and thus do
; not need the N: handler (NDEV)
;

MODULE

;
; PROCEED interrupt vars
;
CARD VPRCEDSAVE     ; Save vector for vprced
CARD VPRCED = $0202 ; Proceed vector
BYTE PACTL  = $D302 ; PIA Control for Proceed
BYTE trip           ; Trip FLAG.

;
; DVSTAT (Status)
;
BYTE DVSTAT = $02EA ; PTR TO DVSTAT
BYTE EXTERR = $02ED ; DVSTAT+3

;
; DEVICE CONTROL BLOCK (DCB)
;
BYTE DDEVIC = $0300 ; Device #
BYTE DUNIT  = $0301 ; Unit #
BYTE DCOMND = $0302 ; Command
BYTE DSTATS = $0303 ; <-> and error
CARD DBUF   = $0304 ; buffer
BYTE DTIMLO = $0306 ; timeout secs
BYTE DUNUSE = $0307 ; reserved
CARD DBYT   = $0308 ; pyld byte len
CARD DAUX   = $030A ; daux1/daux2
BYTE DAUX1  = $030A ; daux1
BYTE DAUX2  = $030B ; daux2

;
; Interrupt handler
;
; A9 01    LDA #$01
; 8D XX XX STA trip
; 68       PLA
; 40       RTI
; 
PROC ninterrupt_handler=*()
[$A9$01$8D trip $68$40]

;
; Enable Interrupt handler.
;
PROC nenableproc()

    trip=0
    VPRCEDSAVE=VPRCED
    VPRCED=ninterrupt_handler
    PACTL = PACTL % 1

RETURN

;
; Disable Interrupt handler.
;
PROC ndisableproc()

    trip=0
    VPRCED=VPRCEDSAVE

RETURN

;
; PROC to call SIO Vector (SIOV)
;
PROC siov=$E459()

;
; Get the unit number from devicespec
;
BYTE FUNC ngetunit(BYTE ARRAY ds)
    BYTE unit=1
                   
    IF ds(2)=': THEN
        unit=1
    ELSEIF ds(3)=': THEN
        unit=ds(2)-$30
    ELSE
        unit=1
    FI

RETURN (unit)

;
; Get status of last NIO operation,
; Return in DVSTAT
; 
; @param devicespec N: devicespec
;
PROC nstatus(BYTE ARRAY ds)

  DDEVIC = $71
  DUNIT  = ngetunit(ds)
  DCOMND = 'S     ; STATUS
  DSTATS = $40    ; Payload to Atari
  DBUF   = $02EA  ; status buffer
  DTIMLO = $1F    ; 32 second timeout
  DBYT   = 4      ; 4 byte payload
  DAUX1  = 0      ; R/W
  DAUX2  = 0      ; translation
  siov()   

RETURN

;
; Return error of last NIO operation.
; If SIO error = 144, then a status
; is done, and the extended err is 
; returned.
;
; @param devicespec N: devicespec
; @return error 1=successful
;
BYTE FUNC geterror(BYTE ARRAY ds)
  BYTE errno

  IF DSTATS=144 THEN
    nstatus(ds)
    errno=EXTERR
  ELSE
    errno=DSTATS
  FI

RETURN (errno)

;
; Open the N: device pointed to by
; devicespec.
; 
; @param devicespec N: devicespec
; @param trans - translation mode
;   0=NONE, 1=CR, 2=LF, 3=CR/LF
; @return error, 1=successful.
;
BYTE FUNC nopen(BYTE ARRAY ds, BYTE t)

  DDEVIC = $71
  DUNIT  = ngetunit(ds)
  DCOMND = 'O    ; OPEN
  DSTATS = $80   ; Write to fujinet
  DBUF   = ds    ; send devicespec
  DTIMLO = $1F   ; 32 second timeout
  DBYT   = 256   ; 256 byte payload
  DAUX1  = 12    ; R/W
  DAUX2  = t     ; translation
  siov();  

  IF DSTATS=1 THEN
      nenableproc()
  FI

RETURN (geterror(ds))

;
; Close the N: device pointed to by
; devicespec.
; 
; @param devicespec N: devicespec
; @return error, 1=successful.
;
BYTE FUNC nclose(BYTE ARRAY ds)

  DDEVIC = $71
  DUNIT  = ngetunit(ds)
  DCOMND = 'C    ; CLOSE
  DSTATS = $00   ; No Payload
  DBUF   = 0     ; send devicespec
  DTIMLO = $1F   ; 32 second timeout
  DBYT   = 0     ; No payload
  DAUX1  = 0
  DAUX2  = 0
  siov();  

  ndisableproc()

RETURN (geterror(ds))

;
; Read len bytes from N: device 
; pointed to by devicespec.
;
; @param devicespec N: devicespec
; @param buf The dest buffer
; @param len # of bytes to read
; @return error 1=successful
;
BYTE FUNC nread(BYTE ARRAY ds, BYTE ARRAY buf, CARD len)

  DDEVIC = $71
  DUNIT  = ngetunit(ds)
  DCOMND = 'R    ; READ
  DSTATS = $40   ; Atari<-Payload
  DBUF   = buf   ; send devicespec
  DTIMLO = $1F   ; 32 second timeout
  DBYT   = len   ; No payload
  DAUX   = len
  siov();  

RETURN (geterror(ds))

;
; Write len bytes to N: device 
; pointed to by devicespec.
;
; @param devicespec N: devicespec
; @param buf The src buffer
; @param len # of bytes to read
; @return error 1=successful
;
BYTE FUNC nwrite(BYTE ARRAY ds, BYTE ARRAY buf, CARD len)

  DDEVIC = $71
  DUNIT  = ngetunit(ds)
  DCOMND = 'W    ; WRITE
  DSTATS = $80   ; Payload->FujiNet
  DBUF   = buf   ; send devicespec
  DTIMLO = $1F   ; 32 second timeout
  DBYT   = len   ; No payload
  DAUX   = len
  siov();  

RETURN (geterror(ds))

; Get FujiNet Status
;
; Author: Michael Goroll
;  <[email protected]>
;
PROC fstatus(BYTE ARRAY buf)

DDEVIC = $70
DUNIT  = $01
DCOMND = $E8
DSTATS = $40    ; Payload to Atari
DBUF   = buf    ; status buffer
DTIMLO = $0F    ; 8 second timeout
DBYT   = $88    ; 139 byte payload
DAUX1  = 0      ; R/W
DAUX2  = 0      ; translation
siov()

RETURN

NC.ACT - Main Program

; 
; A simple netcat program
; to show how to do basic network
; input and output.
;
; Author: Thomas Cherryhomes
;  <[email protected]>
;

INCLUDE "D2:SYS.ACT" ; ACTION! Runtime!
INCLUDE "D2:NIO.ACT" ; NETWORK I/O

MODULE

CARD BYTESWAITING=$02EA     ; # of bytes waiting
BYTE KP=$02FC               ; Key pressed?
BYTE ARRAY devicespec(256)  ; DeviceSpec
BYTE trans                  ; translation mode
BYTE localEcho              ; local echo off/on?
BYTE running                ; is program running?
BYTE ARRAY rxbuf(8192)      ; receive buffer

DEFINE KEYBOARD_IOCB="2"
DEFINE TRUE="1"
DEFINE FALSE="0"

;
; Prompt for URL
;
PROC getURL()
       PrintE("NETCAT--ENTER DEVICE SPEC?")
       InputS(devicespec)
       PutE()
       
       PrintE("TRANS--0=NONE, 1=CR, 2=LF, 3=CR/LF?")
       trans=InputB()
       PutE()

       PrintE("LOCAL ECHO--0=NO, 1=YES?")
       localEcho=InputB()
       PutE()
RETURN

;
; Handle nc output
;
PROC ncoutput()
       BYTE ARRAY ch(1)
       BYTE err

       IF KP=$FF THEN
           RETURN
       FI

       ch(0)=GetD(KEYBOARD_IOCB)

       IF localEcho=1 THEN
           Put(ch(0))
       FI

       err=nwrite(devicespec,ch,1)

       IF err<>1 THEN
           Print("Write Error: ")
           PrintB(err)
           running=FALSE
       FI

RETURN

;
; Handle nc input
;
PROC ncinput()
       BYTE err
       CARD I

       IF trip=0 THEN
           RETURN
       FI

       nstatus()

       IF EXTERR=136 THEN
          PrintE("Disconnected.")
          running=FALSE
          RETURN
       FI

       IF BYTESWAITING=0 THEN
          RETURN 
       FI

       IF BYTESWAITING>8192 THEN
          BYTESWAITING=8192
       FI

       ; Do the network read.
       err=nread(devicespec,rxbuf,BYTESWAITING)

       IF err<>1 THEN
           Print("Read Error: ")
           PrintB(err)
           running=FALSE
           RETURN
       FI
       
       ; Drain/display rx buffer
       FOR I=0 TO BYTESWAITING-1
       DO
           Put(rxbuf(I))
       OD

       ; Done, reset interrupt
       trip=0
       PACTL=PACTL%1

RETURN

;
; The main Netcat function
;
PROC nc()
       BYTE err

       err=nopen(deviceSpec,trans)

       IF err<>1 THEN
           Print("Open Error: ")
           PrintB(err)
           RETURN
       FI

       ; flag program as running.
       running=1
               
       ; Open keyboard
       Open(KEYBOARD_IOCB,"K:",4,0)

       WHILE running = TRUE  
         DO
         ncoutput()
         ncinput()
         OD

       PrintE("Bye.")

       ; Clean up
       Close(KEYBOARD_IOCB) ; close kybd
       nclose(deviceSpec)

RETURN

;
; Main entrypoint
;
PROC main()
       getURL()
       nc()
RETURN

Coleco Adam CP/M (MBASIC)

5 GOSUB 64000:REM SETUP ADAMNET FUNCS
10 PRINT "URL: ";:LINE INPUT URL$
20 NET$="O"+CHR$(12)+CHR$(0)+URL$:REM OPEN COMMAND
30 CMD=WRI:GOSUB 64200
100 NET$=STRING$(255,0)
101 K$=INKEY$:IF K$="" THEN GOTO 110
102 NET$="W"+K$:CMD=WRI:GOSUB 64200:GOTO 100
110 CMD=REA:GOSUB 64200
120 IF FNDCBSTATUS(NET)=&H8C THEN GOTO 100
130 POKE VARPTR(NET$),PEEK(FNDCBLENL(NET))
140 PRINT NET$;
150 GOTO 100
63999 END
64000 REM FUJINET ROUTINES
64010 REM ----------------
64011 REM DCB FUNCS
64012 REM ----------------
64020 DCBLEN=21:DCBEGIN=&HFEC4:DCBNUM=&HFEC3
64021 NET$=STRING$(255,0)
64022 STAT=1:WRI=3:REA=4
64030 DEF FN DCB(X)=X*DCBLEN+DCBEGIN
64040 DEF FN DCBSTATUS(X)=PEEK(FNDCB(X))
64050 DEF FN DCBCMD(X)=FNDCB(X)
64060 DEF FN DCBBUFL(X)=FNDCB(X)+1
64061 DEF FN DCBBUFH(X)=FNDCB(X)+2
64070 DEF FN DCBDEVSTATUS(X)=PEEK(FNDCB(X)+20)
64080 DEF FN DCBDEV(X)=PEEK(FNDCB(X)+16)
64081 DEF FN DCBLENL(X)=FNDCB(X)+3
64082 DEF FN DCBLENH(X)=FNDCB(X)+4
64090 DEF FN DCBNUM(X)=PEEK(DCBNUM)
64092 RETURN
64093 REM --------
64100 REM FIND DCB
64101 REM --------
64110 FOR X=0 TO FNDCBNUM(0)-1
64120 IF FNDCBDEV(X)=9 THEN NET=X:RETURN
64130 NEXT X
64140 RETURN
64150 REM --------------------
64200 REM DO FUJINET OPERATION
64210 REM --------------------
64220 GOSUB 64100:REM FIND DCB
64230 POKE FNDCBBUFL(NET),PEEK(VARPTR(NET$)+1)
64240 POKE FNDCBBUFH(NET),PEEK(VARPTR(NET$)+2)
64250 POKE FNDCBLENL(NET),PEEK(VARPTR(NET$))
64260 POKE FNDCBLENH(NET),0
64270 POKE FNDCBCMD(NET),CMD
64280 IF FNDCBSTATUS(NET)<&H80 THEN GOTO 64280
64290 RETURN

Example devicespecs:

N:TCP://BBS.FOZZTEXX.NET/ (trans 3, local echo N)
N:TCP://CAVEMUSH.COM:6116/ (trans 3 local echo Y)
N:TCP://SOUTHERNAMIS.COM/ (trans 0, local echo N)

Coleco ADAM CP/M (Z80 Assembler)

This example is split into two files, the FujiNet I/O functions, which are based on routines in the EOS ROM, and the Netcat program itself.

FUJINET.Z80

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; Global Variables
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	
DEVICE_ID:	DS	1

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;;  IS DEVICE DONE WITH 
;;;  PREVIOUS COMMAND?
;;; 
;;;  ENTRY:
;;;  IY = DCB ADDRESS
;;;
;;;  EXIT:
;;;  ZF: SET IF DEVICE DONE
;;;   A: 3 IF NOT DONE
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

_DEVICE_DONE:
	LD	A,(IY+0)	; Read Status Byte
	OR	A		; Is it 0?
	JR	NZ,_DDON_DONE	; No, so all done.
	LD	A,3		; Yes, A=3 NOT DONE
	OR	A		; Clear ZF
	RET
	
_DDON_DONE:
	XOR	A		; A=0; ZF Set
	RET			; Bye...
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;;  IS DEVICE READY FOR 
;;;  ANOTHER COMMAND?
;;; 
;;;  ENTRY:
;;;  A  = ADAMNET ID
;;;  IY = DCB ADDRESS
;;;
;;;  EXIT:
;;;  ZF: SET IF DEVICE READY
;;;   A: 2 IF NOT READY
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

_DEVICE_READY:
	PUSH	BC
	LD	C,A		; Save Dev in C
	LD	A,(IY+0)	; Get status byte
	CP	0		; Is it zero?
	JR	Z,_DRDY_SUCCESS	; Yes, Exit OK
	BIT	7,(IY+0)	; Is bit 7 set?
	JR	Z,_DRDY_NOT	; No, exit not ready.

_DRDY_SUCCESS:
	XOR	A		; Zero out A
	LD	A,C		; Restore Dev #
	JR	Z,_DRDY_DONE	; ...and done.

_DRDY_NOT:
	INC	A		; Clear ZF
	LD	A,2		; A=2 Device not ready
	
_DRDY_DONE:
	POP	BC		; Restore BC
	RET			; Bye...
	
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;;  FIND DCB
;;; 
;;;  ENTRY:
;;;  A = ADAMNET ID
;;;
;;;  EXIT:
;;;  ZF: SET IF DEVICE EXISTS
;;;  IY: DCB ADDRESS
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

CURRENT_PCB	EQU	0FEC0H	; Address of Processor Control Block (PCB)
DCB_LENGTH	EQU	21	; DCBs are 21 bytes long
	
_FIND_DCB:
	PUSH	BC		; Preserve regs
	PUSH	DE
	LD	C,A		; Save Dev # in C
	LD	IY,CURRENT_PCB
	LD	B,(IY+3)	; B=# of valid DCBs
	XOR	A		; Zero out A
	CP	B		; Any valid DCBs?
	JR	z,_FDCB_DONE	; Nope, we're done.
	LD	DE,4		; Yep, Skip over PCB...
	ADD	IY,DE		; Point to first DCB
	LD	DE,DCB_LENGTH	; Length of each DCB
	LD	A,C		; Put back dev #
	AND	0FH		; We don't care about upper nibble
	
_FDCB_LOOP:
	CP	(IY+16)		; Does this match A?
	JR	Z,_FDCB_SUCCESS	; Yup, exit and set ZF
	ADD	IY,DE		; No, go to next one.
	DJNZ	_FDCB_LOOP	; Keep looking until done
	JR	_FDCB_ERR	; Couldn't find, so error.
	
_FDCB_SUCCESS:
	LD	A,C		; Restore A
	JR	_FDCB_DONE	; And we're done
	
_FDCB_ERR:
	LD	C,9
	LD	DE,FDCBMSG
	CALL	0005H
	LD	A,1		; 1=error
	OR	A		; Clear ZF
	
_FDCB_DONE:
	POP	DE		; Put regs back
	POP	BC
	RET			; Bye...
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;;  REQUEST DEVICE STATUS
;;; 
;;;  ENTRY:
;;;  A = ADAMNET ID
;;;
;;;  EXIT:
;;;  ZF: SET IF DEVICE ACKS
;;;   A: RESULT OF REQUEST
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

CMD_STATUS	EQU	1
	
_REQUEST_STATUS:
	CALL	_FIND_DCB	; Get DCB address in IY
	JR	NZ,_REQS_DONE	; bail if doesn't exist

	LD	(IY+0),CMD_STATUS ; Ask device for status
	
_REQS_WAIT:
	BIT 	7,(IY+0)	; Bit 7 = Completed
	JR	Z,_REQS_WAIT	; Nope? wait some more.
	LD	A,(IY+0)	; Yes, return result
	CP	80H		; Is it OK? Set ZF if yes.
	
_REQS_DONE:
	RET
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;;  GET DEVICE STATUS BYTE
;;;  (BYTE 20 of DCB)
;;;
;;;  THIS IS INTENDED TO BE
;;;  CALLED AFTER
;;;  REQUEST STATUS HAS
;;;  COMPLETED.
;;; 
;;;  ENTRY:
;;;  A = ADAMNET ID
;;;
;;;  EXIT:
;;;  ZF: SET IF DEVICE EXISTS
;;;   A: DEVICE STATUS BYTE
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	
_GET_STATUS:
	PUSH	IY
	CALL	_FIND_DCB	; Get DCB address in IY
	JR	NZ,_GETS_DONE	; bail if doesn't exist

	XOR	A		; Device exists, so set ZF
	LD	A,(IY+20) 	; Get Device status byte in A
	
_GETS_DONE:
	POP	IY
	RET			; Bye
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;;  START WRITE CHAR DEVICE
;;; 
;;;  ENTRY:
;;;  A  = ADAMNET ID
;;;  HL = BUFFER ADDRESS
;;;  BC = BUFFER LENGTH
;;;
;;;  EXIT:
;;;  ZF: SET IF START OK
;;;   A: ERROR CODE IF ZF=0
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

CMD_WRITE	EQU	3
	
_START_WRITE_CHAR_DEV:
	PUSH	IY
	CALL	_FIND_DCB	; Find DCB in IY
	JR	NZ,_SWCHD_DONE	; Not found, bye.

	CALL	_DEVICE_READY	; Is device ready?
	JR	NZ,_SWCHD_DONE	; Nope, bye.

	LD	(IY+3),C	; Set up DCB
	LD	(IY+4),B
	LD	(IY+1),L
	LD	(IY+2),H
	LD	(IY+0),CMD_WRITE ; Command is last!

_SWCHD_DONE:
	POP	IY
	RET
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;;  END WRITE CHAR DEVICE
;;; 
;;;  ENTRY:
;;;  A  = ADAMNET ID
;;;
;;;  EXIT:
;;;  CF: SET IF I/O DONE
;;;  ZF: SET IF END OK
;;;   A: ERROR CODE IF ZF=0
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

_END_WRITE_CHAR_DEV:
	PUSH	IY
	CALL	_FIND_DCB	; Find DCB in IY
	SCF			; Set CF
	JR	NZ,_EWCHD_DONE	; Done if Dev not exist
	CALL	_DEVICE_DONE	; Check if device done
	JR	NZ,_EWCHD_DONE	; Device not done, exit.
	OR	A		; Clear CF
	BIT	7,(IY+0)	; Bit 7 set?
	JR	Z,_EWCHD_DONE	; No, so exit.
	LD	A,(IY+0)	; Get Status Byte
	CP	80H		; Successful?
	SCF			; Set CF

_EWCHD_DONE:
	POP	IY
	RET
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;;  WRITE CHAR DEVICE
;;; 
;;;  ENTRY:
;;;  A  = ADAMNET ID
;;;  HL = BUFFER ADDRESS
;;;  BC = BUFFER LENGTH
;;;
;;;  EXIT:
;;;  ZF: SET IF  OK
;;;   A: ERROR CODE IF ZF=0
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

_WRITE_CHAR_DEV:
	LD	(DEVICE_ID),A
	CALL	_START_WRITE_CHAR_DEV
	RET	NZ

_WCHDEV_WAIT:
	LD	A,(DEVICE_ID)
	CALL	_END_WRITE_CHAR_DEV
	JR	NC,_WCHDEV_WAIT

	RET
	
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;;  START READ CHAR DEVICE
;;; 
;;;  ENTRY:
;;;  A  = ADAMNET ID
;;;  DE = BUFFER ADDRESS
;;;  BC = BUFFER LENGTH
;;;
;;;  EXIT:
;;;  ZF: SET IF START OK
;;;   A: ERROR CODE IF ZF=0
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

CMD_READ	EQU	4
	
_START_READ_CHAR_DEV:
	PUSH	IY
	CALL	_FIND_DCB	; Find DCB in IY
	JR	NZ,_SRCHD_DONE	; Not found, bye.

	CALL	_DEVICE_READY	; Is device ready?
	JR	NZ,_SRCHD_DONE	; Nope, bye.

	LD	(IY+3),C	; Set up DCB
	LD	(IY+4),B
	LD	(IY+1),E
	LD	(IY+2),D
	LD	(IY+0),CMD_READ ; Command is last!

_SRCHD_DONE:
	POP	IY
	RET
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;;  END READ CHAR DEVICE
;;; 
;;;  ENTRY:
;;;  A  = ADAMNET ID
;;;
;;;  EXIT:
;;;  CF: SET IF I/O DONE
;;;  ZF: SET IF END OK
;;;   A: ERROR CODE IF ZF=0
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

_END_READ_CHAR_DEV:
	PUSH	IY
	CALL	_FIND_DCB	; Find DCB in IY
	SCF			; Set CF
	JR	NZ,_ERCHD_DONE	; Done if Dev not exist
	CALL	_DEVICE_DONE	; Check if device done
	JR	NZ,_ERCHD_DONE	; Device not done, exit.
	OR	A		; Clear CF
	BIT	7,(IY+0)	; Bit 7 set?
	JR	Z,_ERCHD_DONE	; No, so exit.
	LD	A,(IY+0)	; Get Status Byte
	CP	80H		; Successful?
	SCF			; Set CF

_ERCHD_DONE:
	POP	IY
	RET
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;;  READ CHAR DEVICE
;;; 
;;;  ENTRY:
;;;  A  = ADAMNET ID
;;;  HL = BUFFER ADDRESS
;;;  BC = BUFFER LENGTH
;;;
;;;  EXIT:
;;;  ZF: SET IF  OK
;;;   A: ERROR CODE IF ZF=0
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

_READ_CHAR_DEV:
	LD	(DEVICE_ID),A
	CALL	_START_READ_CHAR_DEV
	RET	NZ

_RCHDEV_WAIT:
	LD	A,(DEVICE_ID)
	CALL	_END_READ_CHAR_DEV
	JR	NC,_RCHDEV_WAIT

	RET

;;
;; Debug msgs
;;
FDCBMSG:	DB	'COULD NOT FIND DCB.',0DH,0AH,24H

netcat.z80

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; NETCAT
;;;
;;; Simple Terminal
;;;
;;; @author Thom Cherryhomes
;;; @email thom dot cherryhomes at gmail dot com
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;;
;;; Constants
;;;
NETDEV		EQU	9	; Adamnet device ID
RESP_SIZE	EQU	1024	; 1K resp buff

;;;
;;; BDOS functions
;;;
C_WRITE		EQU	2
C_RAWIO		EQU	6
WRITESTR	EQU	9
READSTR		EQU	10
C_STAT		EQU	11

;;;
;;; N: Mode values
;;;
READWRITE	EQU	12
NOTRANS		EQU	0

;;;
;;; Addresses
;;;
WARMST		EQU	0000H
BDOS		EQU	0005H
TPA		EQU	0100H
	
	ORG	TPA

	JP	START

	INCLUDE	FUJINET.Z80

;;;
;;; Display banner and URL prompt.
;;;
START:
	LD	C,WRITESTR
	LD	DE,BANNER
	CALL	BDOS

;;;
;;; Input URL from user
;;;
GETURL:
	LD	C,READSTR
	LD	DE,COPEN+2
	CALL	BDOS
	
;;;
;;; Attempt connection
;;; See if this can be done
;;; in a less awkward manner.
;;;
CONNECT:
	LD	IY,COPEN
	LD	C,(IY+3)	; Grab len be4 we obliterate
	LD	B,0		; not more than 256b
	INC	BC
	INC	BC
	INC	BC
	LD	(IY+1),'O'	; Move open command up.
	LD	(IY+2),0CH
	LD	(IY+3),00H
	LD	HL,COPEN+1	; Finally, set buffer to open cmd
	LD	A,NETDEV	; Network device
	CALL	_WRITE_CHAR_DEV ; Do it.

;;;
;;; Wait for connection,
;;; first request status.
;;; 
CONNECT_WAIT:
	LD	A,NETDEV
	CALL	_REQUEST_STATUS
	JR	Z,CWCHECK
	CP	09BH		; Timeout?
	JR	Z,CONNECT_WAIT

;;;
;;; Then see if node status has
;;; bit 2 set.
CWCHECK:
	LD	A,NETDEV
	CALL	_GET_STATUS
	BIT	1,A
	JR	NZ,CONNECTED

;;;
;;; Connected bit not set yet,
;;; So print a dot and go back.
;;;
DODOT:	LD	C,9
	LD	DE,DOT
	CALL	BDOS
	JP	CONNECT_WAIT

;;;
;;; Print connected msg!
;;;
CONNECTED:
	LD	C,9
	LD	DE,CNMSG
	CALL	BDOS

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; Netcat main loop BEG
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

MAIN:	LD	A,8
	LD	HL,SCTR		; Reset status counter.
	LD	(HL),A
	
STAT:	LD	A,(SCTR)        ; Check status counter.
	CP	0
	JR	NZ,SEND		; Go to send if not time.
	
STATRC:	LD	A,8
	LD	HL,SCTR		; otherwise reset counter
	LD	(HL),A		;

STATR:	LD	A,NETDEV	; set net device. 
	CALL	_REQUEST_STATUS ; send status request
	CP	80H		; Status OK?
	JR	NZ,STATR	; try again.

	LD	A,NETDEV	; Otherwise...
	CALL	_GET_STATUS	; Get the network status byte
	BIT	1,A		; Get connected bit status
	JR	Z,DISCONNECTED	; Disconnected?
	
SEND:	LD	C,6		; No. Set up console input
	LD	E,0FFH          ; Get char without echo.
	CALL	BDOS            ; and do it...
	CP	0		; No char waiting?
	JR	Z,RECV		; Correct, go to recv.
	LD	(CWRITC),A	; otherwise put char in cmd
	LD	A,NETDEV	; To Net device
	LD	HL,CWRIT	; Buffer in CWRIT
	LD	BC,2		; 'W' + One char
	CALL	_WRITE_CHAR_DEV ; Do it, ignore errs
	
RECV:	LD	A,NETDEV	; From net device
	LD	DE,RESP		; Point to resp buff
	LD	BC,RESP_SIZE	; buff len
	CALL	_READ_CHAR_DEV  ; Do it.

RECV_OK:
	LD	A,NETDEV
	CALL	_FIND_DCB	; Get DCB in IY
	LD	A,(IY+0)	; Check status
	CP	128		; is it ACK?
	JR	NZ,STAT		; Nope, go back.

RECV_LEN:
	LD	E,(IY+3)	; Load pointers from DCB
	LD	D,(IY+4)
	LD	L,(IY+1)	
	LD	H,(IY+2)
	
RECV_PRINT:
	PUSH	DE
	PUSH	HL
	LD	C,C_WRITE	; Console write
	LD	E,(HL)		; Next char in buf
	CALL	BDOS		; Do it.
	POP	HL
	POP	DE
	INC	HL		; Increment buff ptr.
	DEC	DE		; Decrement len
	ld	a,d
	or	e
	JR	NZ,RECV_PRINT	; Not done yet.

UPDATE_SCTR:
	LD	HL,SCTR		; Decrement status counter
	LD	A,(HL)
	DEC	A
	LD	(HL),A
	JP	STAT		; Back to stat.
	
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; Netcat main loop END
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;;
;;; Host disconnected.
;;; Print msg, close connection.
;;; and head back to CPM.
DISCONNECTED:
	LD	C,WRITESTR	; We disconnected.
	LD	DE,DISCMSG	; Show the disconnected string
	CALL	BDOS		; Do it.
	LD	A,NETDEV	; To Net device
	LD	HL,CCLOS	; Buffer ptr to CLOSE command
	LD	BC,1		; only one char.
	CALL	_WRITE_CHAR_DEV ; Do it
				; ... fall thru to BYE.
				
;;;
;;; Exit to CP/M
;;;
BYE:	RST	0

;;;
;;; Fujinet Commands
;;;
COPEN:	DB	'O'		; FujiNet N: OPEN
	DB	0CH		; reused as str len. changed to 0C
	DB	50H		; 80 chars
	DS	80

CWRIT:	DB	'W'		; FujiNet N: WRITE
CWRITC:	DS	1		; Char to send

CCLOS:	DB	'C'		; Fujinet N: CLOSE

;;;
;;; WRITESTR strings.
;;;
BANNER:
	DB	'NETCAT!',0DH,0AH,0D,0AH
	DB	'ENTER URL:',0DH,0AH,24H

TOMSG:
	DB	'TIMEOUT WAITING FOR STATUS.',0DH,0AH,24H

DOT:
	DB	'.',24H

CNMSG:	DB	0DH,0AH,'CONNECTED!',0DH,0AH,24H

DISCMSG:
	DB	0DH,0AH,'DISCONNECTED.',0DH,0AH,24H

;;;
;;; 1024 BYTE RESPONSE BUFFER
;;; ALL FUJINET PROGS NEED THIS
;;; AND RECEIVE SIZE IS ALWAYS
;;; 1024 BYTES.
;;;
RESP:	DS	1024

;;; A STATUS COUNTER, COMPARED AND
;;; CHECKED EVERY TIME ZF IS SET
SCTR:   DS      1

Any not here?

Do you have an equivalent in other languages to put here, add it!