emailler/apps/telnet65.s
2019-05-10 17:47:06 +02:00

607 lines
14 KiB
ArmAsm

; minimal telnet implementation (dumb terminal emulation only)
.include "../inc/common.inc"
.include "../inc/commonprint.inc"
.include "../inc/net.inc"
.include "../inc/error.inc"
.export start
.import abort_key
.importzp abort_key_default
.importzp abort_key_disable
.importzp eth_init_default
.import get_filtered_input
.import get_key
.import get_key_if_available
.import exit_to_basic
.import filter_dns
.import filter_number
.import dns_hostname_is_dotted_quad
.import dns_ip
.import dns_resolve
.import dns_set_hostname
.import ip65_process
.import native_to_ascii
.import parse_integer
.import print_cr
.import tcp_callback
.import tcp_close
.import tcp_connect
.import tcp_connect_ip
.import tcp_inbound_data_ptr
.import tcp_inbound_data_length
.import tcp_send
.import tcp_send_data_len
.import tcp_send_keep_alive
.import timer_read
.import vt100_init_terminal
.import vt100_exit_terminal
.import vt100_process_inbound_char
.import vt100_process_outbound_char
.importzp vt100_screen_cols
.importzp vt100_screen_rows
.export telnet_close
.export telnet_send_char
.export telnet_send_string
buffer_ptr = sreg
; keep LD65 happy
.segment "INIT"
.segment "ONCE"
.segment "STARTUP"
jmp start
eth_init_value:
.byte eth_init_default
.code
start:
jsr vt100_init_terminal
; initialize stack
ldax #welcome_1
jsr print_vt100
ldax #welcome_2
jsr print_vt100
ldax #initializing
jsr print_ascii_as_native
lda eth_init_value
jsr ip65_init
bcc :+
ldax #device_not_found
jsr print_ascii_as_native
jmp error_exit
: ldax #eth_name
jsr print_ascii_as_native
jsr print_cr
; get IP addr
ldax #obtaining
jsr print_ascii_as_native
jsr dhcp_init
bcc :+
jsr print_error
jmp error_exit
: ldax #cfg_ip
jsr print_dotted_quad
jsr print_cr
telnet_main_entry:
; enter host name
ldax #remote_host
jsr print_ascii_as_native
ldy #40 ; max chars
ldax #filter_dns
jsr get_filtered_input
bcc :+
jmp exit
; set host name
: stax buffer_ptr
ldy #$ff
: iny
lda (buffer_ptr),y
jsr native_to_ascii
cmp #'a'
bcs :+
cmp #'A'
bcc :+
clc
adc #'a'-'A'
: sta (buffer_ptr),y
tax ; set Z flag
bne :--
ldax buffer_ptr
jsr dns_set_hostname
bcc :+
jsr print_error
jmp telnet_main_entry
; resolve host name
: lda dns_hostname_is_dotted_quad
bne :++
ldax #resolving
jsr print_ascii_as_native
jsr dns_resolve
bcc :+
jsr print_error
jmp telnet_main_entry
: ldax #dns_ip
jsr print_dotted_quad
; enter port
: ldax #remote_port
jsr print_ascii_as_native
ldy #5 ; max chars
ldax #filter_number
jsr get_filtered_input
bcs :+ ; empty -> default
jsr parse_integer
bcc :++
jmp telnet_main_entry
: ldax #23 ; default
: stax telnet_port
; connect
ldax #connecting
jsr print_ascii_as_native
ldax #dns_ip
jsr print_dotted_quad
lda #' '
jsr print_a
ldax #telnet_callback
stax tcp_callback
ldx #3
: lda dns_ip,x
sta tcp_connect_ip,x
dex
bpl :-
ldax telnet_port
jsr tcp_connect
bcc :+
jsr print_error
jmp telnet_main_entry
; connected
: ldax #ok
jsr print_ascii_as_native
lda #0
sta connection_close_requested
sta connection_closed
sta data_received
sta iac_response_buffer_length
lda #abort_key_disable
sta abort_key
ldax #on_connect
jsr print_vt100
@main_polling_loop:
jsr timer_read
txa ; 1/1000 * 256 = ~ 1/4 seconds
adc #$20 ; 32 x 1/4 = ~ 8 seconds
sta telnet_timeout
@check_timeout:
lda data_received
bne :+
jsr timer_read
cpx telnet_timeout
bne :+
jsr tcp_send_keep_alive
jmp @main_polling_loop
: lda #0
sta data_received
jsr ip65_process
lda connection_close_requested
beq :+
jsr tcp_close
jmp :++
: lda connection_closed
beq :++
: lda #abort_key_default
sta abort_key
ldax #on_disconnect
jsr print_vt100
ldax #disconnected
jsr print_ascii_as_native
jmp telnet_main_entry
: lda iac_response_buffer_length
beq :+
ldx #0
stax tcp_send_data_len
stx iac_response_buffer_length
ldax #iac_response_buffer
jsr tcp_send
: jsr get_key_if_available
bcc @check_timeout
ldx #0
stx tcp_send_data_len
stx tcp_send_data_len+1
tay
jsr vt100_process_outbound_char
jmp @main_polling_loop
print_vt100:
stax buffer_ptr
lda #0
sta buffer_offset
: ldy buffer_offset
lda (buffer_ptr),y
bne :+
rts
: tay
jsr vt100_process_inbound_char
inc buffer_offset
jmp :--
print_error:
lda ip65_error
cmp #IP65_ERROR_ABORTED_BY_USER
bne :+
ldax #abort
jmp print_ascii_as_native
: cmp #IP65_ERROR_TIMEOUT_ON_RECEIVE
bne :+
ldax #timeout
jmp print_ascii_as_native
: ldax #error_prefix
jsr print_ascii_as_native
lda ip65_error
jsr print_hex
jmp print_cr
error_exit:
ldax #press_a_key_to_continue
jsr print_ascii_as_native
jsr get_key
exit:
jsr vt100_exit_terminal
jmp exit_to_basic
; vt100 callback - will be executed when the user requests to close the connection
telnet_close:
lda #1
sta connection_close_requested
rts
; vt100 callback - will be executed when sending a char string
telnet_send_string:
stx buffer_ptr
sty buffer_ptr+1
ldy #0
: lda (buffer_ptr),y
beq send_char
sta scratch_buffer,y
inc tcp_send_data_len
iny
bne :-
jmp send_char
; vt100 callback - will be executed when sending a single char
telnet_send_char:
ldy tcp_send_data_len
sta scratch_buffer,y
inc tcp_send_data_len
send_char:
ldax #scratch_buffer
jsr tcp_send
bcs :+
rts
: lda ip65_error
cmp #IP65_ERROR_CONNECTION_CLOSED
bne :+
lda #1
sta connection_closed
rts
: ldax #send_error
jsr print_ascii_as_native
jmp print_error
; tcp callback - will be executed whenever data arrives on the TCP connection
telnet_callback:
lda #1
ldx tcp_inbound_data_length+1
cpx #$ff
bne :+
sta connection_closed
rts
: sta data_received
lda tcp_inbound_data_length
stax buffer_length
ldax tcp_inbound_data_ptr
stax buffer_ptr
@next_byte:
ldy #0
lda (buffer_ptr),y
tax
; if we get here, we are in ASCII 'char at a time' mode,
; so look for (and process) Telnet style IAC bytes
lda telnet_state
cmp #telnet_state_got_command
bne :+
jmp @waiting_for_option
: cmp #telnet_state_got_iac
beq @waiting_for_command
cmp #telnet_state_got_suboption
beq @waiting_for_suboption_end
; we must be in 'normal' mode
txa
cmp #255
beq :+
jmp @not_iac
: lda #telnet_state_got_iac
sta telnet_state
jmp @byte_processed
@waiting_for_suboption_end:
txa
ldx iac_suboption_buffer_length
sta iac_suboption_buffer,x
inc iac_suboption_buffer_length
cmp #$f0 ; SE - suboption end
bne @exit_suboption
lda #telnet_state_normal
sta telnet_state
lda iac_suboption_buffer
cmp #$18
bne @not_terminal_type
ldx #0
: lda terminal_type_response,x
ldy iac_response_buffer_length
inc iac_response_buffer_length
sta iac_response_buffer,y
inx
txa
cmp #terminal_type_response_length
bne :-
@not_terminal_type:
@exit_suboption:
jmp @byte_processed
@waiting_for_command:
txa
sta telnet_command
cmp #$fa ; SB - suboption begin
beq @suboption
cmp #$fb ; WILL
beq @option
cmp #$fc ; WONT
beq @option
cmp #$fd ; DO
beq @option
cmp #$fe ; DONT
beq @option
; we got a command we don't understand - just ignore it
lda #telnet_state_normal
sta telnet_state
jmp @byte_processed
@suboption:
lda #telnet_state_got_suboption
sta telnet_state
lda #0
sta iac_suboption_buffer_length
jmp @byte_processed
@option:
lda #telnet_state_got_command
sta telnet_state
jmp @byte_processed
@waiting_for_option:
; we have now got IAC, <command>, <option>
txa
sta telnet_option
lda telnet_command
cmp #$fb
beq @iac_will
cmp #$fc
beq @iac_wont
cmp #$fe
beq @iac_dont
; if we get here, then it's a "do"
lda telnet_option
cmp #$18 ; terminal type
beq @do_terminaltype
cmp #$1f
beq @do_naws
; if we get here, then it's a "do" command we don't honour
@iac_dont:
lda #$fc ; WONT
@add_iac_response:
ldx iac_response_buffer_length
sta iac_response_buffer+1,x
lda #$ff
sta iac_response_buffer,x
lda telnet_option
sta iac_response_buffer+2,x
inc iac_response_buffer_length
inc iac_response_buffer_length
inc iac_response_buffer_length
@after_set_iac_response:
lda #telnet_state_normal
sta telnet_state
jmp @byte_processed
@iac_will:
lda telnet_option
cmp #$01 ; ECHO
beq @will_echo
cmp #$03 ; DO SUPPRESS GA
beq @will_suppress_ga
@iac_wont:
lda #$fe ; DONT
jmp @add_iac_response
@will_echo:
lda #$fd ; DO
jmp @add_iac_response
@will_suppress_ga:
lda #$fd ; DO
jmp @add_iac_response
@do_naws:
ldx #0
: lda naws_response,x
ldy iac_response_buffer_length
inc iac_response_buffer_length
sta iac_response_buffer,y
inx
txa
cmp #naws_response_length
bne :-
jmp @after_set_iac_response
@do_terminaltype:
lda #$fb ; WILL
jmp @add_iac_response
@not_iac:
txa
tay
jsr vt100_process_inbound_char
@byte_processed:
inc buffer_ptr
bne :+
inc buffer_ptr+1
: lda buffer_length+1
beq :++
lda buffer_length
bne :+
dec buffer_length+1
: dec buffer_length
jmp @next_byte
: dec buffer_length
beq :+
jmp @next_byte
: rts
.rodata
initializing: .byte 10,"Initializing ",0
obtaining: .byte "Obtaining IP address ",0
resolving: .byte 10,"Resolving to address ",0
connecting: .byte 10,"Connecting to ",0
ok: .byte "Ok",10,10,0
device_not_found: .byte "- Device not found",10,0
abort: .byte "- User abort",10,0
timeout: .byte "- Timeout",10,0
error_prefix: .byte "- Error $",0
remote_host: .byte 10,"Hostname (leave blank to quit)",10,"? ",0
remote_port: .byte 10,10,"Port Num (leave blank for default)",10,"? ",0
disconnected: .byte 10,"Disconnected",10,0
send_error: .byte "Sending ",0
welcome_1: .byte 27,")0"
.byte 14,"lqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqk"
.byte 15,13,10
.byte 14,"x x"
.byte 15,13,10
.byte 14,"x",15," ",27,"[1m","Telnet65 v1.3",27,"[0m"," based on: ",14,"x"
.byte 15,13,10
.byte 14,"x x"
.byte 15,13,10,0
welcome_2: .byte 14,"x",15," - IP65 (github.com/cc65/ip65) ",14,"x"
.byte 15,13,10
.byte 14,"x",15," - CaTer (www.opppf.de/Cater) ",14,"x"
.byte 15,13,10
.byte 14,"x x"
.byte 15,13,10
.byte 14,"mqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqj"
.byte 15,13,10
.byte 27,")A"
.byte 27,"[?25l",0
on_connect: .byte 27,"[?25h",0
on_disconnect: .byte 27,"[?25l",27,"[0m",27,"(A",15,0
; initial_telnet_options:
; .byte $ff,$fb,$1F ; IAC WILL NAWS
; .byte $ff,$fb,$18 ; IAC WILL TERMINAL TYPE
; initial_telnet_options_length = *-initial_telnet_options
terminal_type_response:
.byte $ff ; IAC
.byte $fa ; SB
.byte $18 ; TERMINAL TYPE
.byte $0 ; IS
.byte "vt100" ; what we pretend to be
.byte $ff ; IAC
.byte $f0 ; SE
terminal_type_response_length = *-terminal_type_response
naws_response:
.byte $ff,$fb,$1f ; IAC WILL NAWS
.byte $ff ; IAC
.byte $fa ; SB
.byte $1f ; NAWS
.byte $00 ; width (high byte)
.byte vt100_screen_cols ; width (low byte)
.byte $00 ; height (high byte)
.byte vt100_screen_rows ; height (low byte)
.byte $ff ; IAC
.byte $f0 ; SE
naws_response_length = *-naws_response
.bss
; variables
telnet_port: .res 2 ; port number to connect to
telnet_timeout: .res 1
connection_close_requested: .res 1
connection_closed: .res 1
data_received: .res 1
buffer_offset: .res 1
telnet_command: .res 1
telnet_option: .res 1
telnet_state_normal = 0
telnet_state_got_iac = 1
telnet_state_got_command = 2
telnet_state_got_suboption = 3
buffer_length: .res 2
telnet_state: .res 1
iac_response_buffer: .res 64
iac_response_buffer_length: .res 1
scratch_buffer : .res 40
iac_suboption_buffer: .res 64
iac_suboption_buffer_length: .res 1
; -- LICENSE FOR telnet.s --
; The contents of this file are subject to the Mozilla Public License
; Version 1.1 (the "License"); you may not use this file except in
; compliance with the License. You may obtain a copy of the License at
; http://www.mozilla.org/MPL/
;
; Software distributed under the License is distributed on an "AS IS"
; basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
; License for the specific language governing rights and limitations
; under the License.
;
; The Original Code is ip65.
;
; The Initial Developer of the Original Code is Jonno Downes,
; jonno@jamtronix.com.
; Portions created by the Initial Developer are Copyright (C) 2009
; Jonno Downes. All Rights Reserved.
; -- LICENSE END --