mirror of
https://github.com/bobbimanners/emailler.git
synced 2024-08-06 16:28:54 +00:00
git-svn-id: http://svn.code.sf.net/p/netboot65/code@21 93682198-c243-4bdb-bd91-e943c89aac3b
This commit is contained in:
parent
cb09f0e4fd
commit
830d271979
@ -15,6 +15,8 @@ IP65LIB=../ip65/ip65.lib
|
|||||||
C64NETLIB=../drivers/c64net.lib
|
C64NETLIB=../drivers/c64net.lib
|
||||||
APPLE2NETLIB=../drivers/apple2net.lib
|
APPLE2NETLIB=../drivers/apple2net.lib
|
||||||
|
|
||||||
|
BOOTA2.BIN=../../server/boot/BOOTA2.BIN
|
||||||
|
|
||||||
INCFILES=\
|
INCFILES=\
|
||||||
../inc/common.i\
|
../inc/common.i\
|
||||||
../inc/commonprint.i\
|
../inc/commonprint.i\
|
||||||
@ -23,6 +25,7 @@ INCFILES=\
|
|||||||
all: \
|
all: \
|
||||||
rrnetboot.bin \
|
rrnetboot.bin \
|
||||||
utherboot.dsk \
|
utherboot.dsk \
|
||||||
|
$(BOOTA2.BIN) \
|
||||||
|
|
||||||
rrnetboot.bin: rrnetboot.o $(IP65LIB) $(C64NETLIB) $(INCFILES) ../cfg/rrbin.cfg
|
rrnetboot.bin: rrnetboot.o $(IP65LIB) $(C64NETLIB) $(INCFILES) ../cfg/rrbin.cfg
|
||||||
$(LD) -m rrnetboot.map -C ../cfg/rrbin.cfg -o rrnetboot.bin $(AFLAGS) $< $(IP65LIB) $(C64NETLIB)
|
$(LD) -m rrnetboot.map -C ../cfg/rrbin.cfg -o rrnetboot.bin $(AFLAGS) $< $(IP65LIB) $(C64NETLIB)
|
||||||
@ -35,6 +38,9 @@ utherboot.dsk: utherboot.bin
|
|||||||
dsktool.rb --init dos33 utherboot.dsk -a utherboot.bin -t B
|
dsktool.rb --init dos33 utherboot.dsk -a utherboot.bin -t B
|
||||||
dsktool.rb utherboot.dsk -a hello -t A
|
dsktool.rb utherboot.dsk -a hello -t A
|
||||||
|
|
||||||
|
$(BOOTA2.BIN): bootmenu.o $(IP65LIB) $(APPLE2NETLIB) $(INCFILES) ../cfg/a2language_card.cfg
|
||||||
|
$(LD) -m bootmenu.map -C ../cfg/a2language_card.cfg -o $(BOOTA2.BIN) $(AFLAGS) $< $(IP65LIB) $(APPLE2NETLIB)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f *.o
|
rm -f *.o
|
||||||
rm -f rrnetboot.bin rrnetboot.map
|
rm -f rrnetboot.bin rrnetboot.map
|
||||||
|
141
client/clients/bootmenu.s
Normal file
141
client/clients/bootmenu.s
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
;#############
|
||||||
|
;
|
||||||
|
; This program looks for a TNDP server on the network, presents a catalog of volumes on that server, and allows a volume to be attached
|
||||||
|
;
|
||||||
|
; jonno@jamtronix.com - January 2009
|
||||||
|
;
|
||||||
|
|
||||||
|
.include "../inc/common.i"
|
||||||
|
.include "../inc/commonprint.i"
|
||||||
|
.include "../inc/net.i"
|
||||||
|
.import cls
|
||||||
|
|
||||||
|
.import copymem
|
||||||
|
.importzp copy_src
|
||||||
|
.importzp copy_dest
|
||||||
|
|
||||||
|
.import __STARTUP_LOAD__
|
||||||
|
.import __STARTUP_SIZE__
|
||||||
|
.import __BSS_LOAD__
|
||||||
|
.import __DATA_LOAD__
|
||||||
|
.import __DATA_RUN__
|
||||||
|
.import __DATA_SIZE__
|
||||||
|
.import __RODATA_LOAD__
|
||||||
|
.import __RODATA_RUN__
|
||||||
|
.import __RODATA_SIZE__
|
||||||
|
.import __CODE_LOAD__
|
||||||
|
.import __CODE_RUN__
|
||||||
|
.import __CODE_SIZE__
|
||||||
|
|
||||||
|
.segment "PAGE3"
|
||||||
|
|
||||||
|
disable_language_card: .res 3
|
||||||
|
bin_file_jmp: .res 3
|
||||||
|
|
||||||
|
; ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
.segment "EXEHDR"
|
||||||
|
|
||||||
|
.addr __STARTUP_LOAD__ ; Start address
|
||||||
|
.word __STARTUP_SIZE__+__CODE_SIZE__+__RODATA_SIZE__+__DATA_SIZE__+4 ; Size
|
||||||
|
|
||||||
|
; ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
.segment "STARTUP"
|
||||||
|
|
||||||
|
|
||||||
|
lda $c089 ;enable language : card read ROM, write RAM, BANK 1
|
||||||
|
|
||||||
|
;copy the monitor rom on to the language card
|
||||||
|
ldax #$f800
|
||||||
|
stax copy_src
|
||||||
|
stax copy_dest
|
||||||
|
ldax #$0800
|
||||||
|
jsr startup_copymem
|
||||||
|
|
||||||
|
|
||||||
|
lda $c08b ;enable language : card read RAM, write RAM, BANK 1
|
||||||
|
lda $c08b ;this soft switch needs to be read twice
|
||||||
|
|
||||||
|
|
||||||
|
;relocate the CODE segment
|
||||||
|
ldax #__CODE_LOAD__
|
||||||
|
stax copy_src
|
||||||
|
ldax #__CODE_RUN__
|
||||||
|
stax copy_dest
|
||||||
|
ldax #__CODE_SIZE__
|
||||||
|
jsr startup_copymem
|
||||||
|
|
||||||
|
|
||||||
|
;relocate the RODATA segment
|
||||||
|
ldax #__RODATA_LOAD__
|
||||||
|
stax copy_src
|
||||||
|
ldax #__RODATA_RUN__
|
||||||
|
stax copy_dest
|
||||||
|
ldax #__RODATA_SIZE__
|
||||||
|
jsr startup_copymem
|
||||||
|
|
||||||
|
;relocate the DATA segment
|
||||||
|
ldax #__DATA_LOAD__
|
||||||
|
stax copy_src
|
||||||
|
ldax #__DATA_RUN__
|
||||||
|
stax copy_dest
|
||||||
|
ldax #__DATA_SIZE__
|
||||||
|
jsr startup_copymem
|
||||||
|
|
||||||
|
jmp init
|
||||||
|
|
||||||
|
; copy memory
|
||||||
|
; set copy_src and copy_dest, length in A/X
|
||||||
|
|
||||||
|
|
||||||
|
end: .res 1
|
||||||
|
|
||||||
|
startup_copymem:
|
||||||
|
sta end
|
||||||
|
ldy #0
|
||||||
|
|
||||||
|
cpx #0
|
||||||
|
beq @tail
|
||||||
|
|
||||||
|
: lda (copy_src),y
|
||||||
|
sta (copy_dest),y
|
||||||
|
iny
|
||||||
|
bne :-
|
||||||
|
inc copy_src+1 ;next page
|
||||||
|
inc copy_dest+1 ;next page
|
||||||
|
dex
|
||||||
|
bne :-
|
||||||
|
|
||||||
|
@tail:
|
||||||
|
lda end
|
||||||
|
beq @done
|
||||||
|
|
||||||
|
: lda (copy_src),y
|
||||||
|
sta (copy_dest),y
|
||||||
|
iny
|
||||||
|
cpy end
|
||||||
|
bne :-
|
||||||
|
|
||||||
|
@done:
|
||||||
|
rts
|
||||||
|
|
||||||
|
.code
|
||||||
|
|
||||||
|
|
||||||
|
init:
|
||||||
|
|
||||||
|
jsr cls
|
||||||
|
|
||||||
|
ldax #startup_msg
|
||||||
|
jsr print
|
||||||
|
jsr print_cr
|
||||||
|
|
||||||
|
; init_ip_via_dhcp
|
||||||
|
; bcs bad_boot
|
||||||
|
jsr print_ip_config
|
||||||
|
|
||||||
|
.rodata
|
||||||
|
startup_msg: .byte "NETBOOT65 MENU FOR APPLE 2 V0.1",0
|
||||||
|
|
@ -2,7 +2,7 @@ TRIVIAL NETWORK DISK PROTOCOL
|
|||||||
|
|
||||||
* "CAPABILITIES REQUEST" is the only message type that should be broadcast. Everything else should be unicast
|
* "CAPABILITIES REQUEST" is the only message type that should be broadcast. Everything else should be unicast
|
||||||
|
|
||||||
All fields are in network byte order (lo/hi)
|
All fields are in network byte order (hi/lo)
|
||||||
|
|
||||||
FOR ALL MESSAGES
|
FOR ALL MESSAGES
|
||||||
Offset meaning
|
Offset meaning
|
||||||
@ -45,8 +45,8 @@ For READ/WRITE:
|
|||||||
$08..$09 2 byte track #
|
$08..$09 2 byte track #
|
||||||
$0A..$0B 2 byte sector #
|
$0A..$0B 2 byte sector #
|
||||||
$0C..$0D 2 byte sector length (in bytes)
|
$0C..$0D 2 byte sector length (in bytes)
|
||||||
$0E..$4B volume name (null padded 61 bytes)
|
$0E..$4B volume name (null padded 55 bytes)
|
||||||
$4C null byte (i.e. filename can be 61 bytes long, MUST be at least 1 null at the 62nd byte)
|
$4C null byte (i.e. filename can be 55 bytes long, MUST be at least 1 null at the 56th byte)
|
||||||
FOR WRITE
|
FOR WRITE
|
||||||
$4d.. sector data
|
$4d.. sector data
|
||||||
|
|
||||||
@ -79,11 +79,11 @@ The list of supported architectures should end with an entry for architecture ID
|
|||||||
file system ID $00 (any) which should have a count of all volumes available for all architectures on this server.
|
file system ID $00 (any) which should have a count of all volumes available for all architectures on this server.
|
||||||
|
|
||||||
FOR VOLUME CATALOG
|
FOR VOLUME CATALOG
|
||||||
$06..$07 Total Catalogue Entries
|
$08..$09 Total Catalogue Entries
|
||||||
$08..$09 Catalogue Offset (i.e. how many entries are there in the catalogue before the first entry in this response)
|
$0A..$0B Catalogue Offset (i.e. how many entries are there in the catalogue before the first entry in this response)
|
||||||
$0a Number of Entries in this response (maximum of 10)
|
$0C Number of Entries in this response (maximum of 10)
|
||||||
|
|
||||||
$0b.. the volume catalog entries.
|
$0D.. the volume catalog entries.
|
||||||
for each entry
|
for each entry
|
||||||
|
|
||||||
$00..$3c volume name (up to 56 chars, null padded)
|
$00..$3c volume name (up to 56 chars, null padded)
|
||||||
@ -91,8 +91,8 @@ $37 null byte
|
|||||||
$38 system architecture ID
|
$38 system architecture ID
|
||||||
$39 file system ID
|
$39 file system ID
|
||||||
$3A..$3B number of tracks
|
$3A..$3B number of tracks
|
||||||
$3C..$3D number of sectors
|
$3C..$3D sector length (in bytes)
|
||||||
$3E..$3F sector length (in bytes)
|
$3E..$3F ??
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ class Hash
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
module TNDP
|
module TNDP
|
||||||
|
|
||||||
OPCODES={
|
OPCODES={
|
||||||
@ -31,11 +32,13 @@ module TNDP
|
|||||||
0xFF =>"ERROR RESPONSE",
|
0xFF =>"ERROR RESPONSE",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MAX_CATALOG_ENTRIES_PER_MESSAGE=8
|
||||||
|
MAX_VOLUME_NAME_LENGTH=57
|
||||||
SYSTEM_ARCHITECTURES={
|
SYSTEM_ARCHITECTURES={
|
||||||
:any=>0x00,
|
:any=>0x00,
|
||||||
:c64=>0x64,
|
:c64=>0x64,
|
||||||
:apple2=>0xA2,
|
:apple2=>0xA2,
|
||||||
:other=>0xFF,
|
:unknown=>0xFF,
|
||||||
}
|
}
|
||||||
|
|
||||||
FILESYSTEMS={
|
FILESYSTEMS={
|
||||||
@ -44,7 +47,8 @@ FILESYSTEMS={
|
|||||||
:apple_dos_33=>0x02,
|
:apple_dos_33=>0x02,
|
||||||
:prodos=>0x03,
|
:prodos=>0x03,
|
||||||
:cpm=>0x04,
|
:cpm=>0x04,
|
||||||
:cbm_dos=>0x05
|
:cbm_dos=>0x05,
|
||||||
|
:unknown=>0xFF,
|
||||||
}
|
}
|
||||||
|
|
||||||
class ErrorCodes
|
class ErrorCodes
|
||||||
@ -75,6 +79,25 @@ FILESYSTEMS={
|
|||||||
class InvalidOpcode < FormatError
|
class InvalidOpcode < FormatError
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def TNDP.hex_dump(buffer)
|
||||||
|
s=""
|
||||||
|
(0..(buffer.length/16)).each {|line_number|
|
||||||
|
lhs=""
|
||||||
|
rhs=""
|
||||||
|
start_byte=line_number*16
|
||||||
|
line=buffer[start_byte,16]
|
||||||
|
if line.length>0 then
|
||||||
|
line.each_byte {|byte|
|
||||||
|
lhs+= sprintf("%02X ", byte)
|
||||||
|
rhs+=byte.chr.sub(/[\x00-\x1f]/,'.')
|
||||||
|
}
|
||||||
|
lhs+=" "*(16-line.length)*3
|
||||||
|
s+=sprintf("%02X\t%s %s\n",start_byte,lhs,rhs)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
s
|
||||||
|
end
|
||||||
|
|
||||||
def TNDP.coalesce(a,b)
|
def TNDP.coalesce(a,b)
|
||||||
if a.nil? then
|
if a.nil? then
|
||||||
b
|
b
|
||||||
@ -123,30 +146,13 @@ FILESYSTEMS={
|
|||||||
TNDP MESSAGE
|
TNDP MESSAGE
|
||||||
SIGNATURE: #{signature}
|
SIGNATURE: #{signature}
|
||||||
VERSION: #{version_id}
|
VERSION: #{version_id}
|
||||||
TRANSACTION ID: 0x#{"%04x"%transaction_id}
|
TRANSACTION ID: $#{"%04x"%transaction_id}
|
||||||
OPCODE: 0x#{"%02X"%opcode} [#{OPCODES[opcode].nil? ? "UNKNOWN" : OPCODES[opcode]}]"
|
OPCODE: $#{"%02X"%opcode} [#{OPCODES[opcode].nil? ? "UNKNOWN" : OPCODES[opcode]}]"
|
||||||
end
|
end
|
||||||
|
|
||||||
def hex_dump
|
def hex_dump
|
||||||
buffer=raw_bytes
|
TNDP.hex_dump(raw_bytes)
|
||||||
s=""
|
|
||||||
(0..(buffer.length/16)).each {|line_number|
|
|
||||||
lhs=""
|
|
||||||
rhs=""
|
|
||||||
start_byte=line_number*16
|
|
||||||
line=buffer[start_byte,16]
|
|
||||||
if line.length>0 then
|
|
||||||
line.each_byte {|byte|
|
|
||||||
lhs+= sprintf("%02X ", byte)
|
|
||||||
rhs+=byte.chr.sub(/[\x00-\x1f]/,'.')
|
|
||||||
}
|
|
||||||
lhs+=" "*(16-line.length)*3
|
|
||||||
s+=sprintf("%02X\t%s %s\n",start_byte,lhs,rhs)
|
|
||||||
end
|
|
||||||
}
|
|
||||||
s
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize(args={})
|
def initialize(args={})
|
||||||
# puts "args:"
|
# puts "args:"
|
||||||
# args.keys.each do |key|
|
# args.keys.each do |key|
|
||||||
@ -168,13 +174,6 @@ private
|
|||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
class RequestMessage < BaseMessage
|
|
||||||
attr_reader :opcode
|
|
||||||
end
|
|
||||||
|
|
||||||
class ResponseMessage < BaseMessage
|
|
||||||
end
|
|
||||||
|
|
||||||
class CapabilitiesRequestMessage < BaseMessage
|
class CapabilitiesRequestMessage < BaseMessage
|
||||||
OPCODE=0x00
|
OPCODE=0x00
|
||||||
def initialize(args={})
|
def initialize(args={})
|
||||||
@ -190,24 +189,23 @@ private
|
|||||||
args[:opcode]=OPCODE
|
args[:opcode]=OPCODE
|
||||||
@application_name=TNDP.coalesce(args[:application_name],"netboot 65")
|
@application_name=TNDP.coalesce(args[:application_name],"netboot 65")
|
||||||
@highest_supported_version_id=TNDP.coalesce(args[:highest_supported_version_id],VERSION_ID)
|
@highest_supported_version_id=TNDP.coalesce(args[:highest_supported_version_id],VERSION_ID)
|
||||||
@supported_architectures=TNDP.coalesce(args[:supported_architectures],[[TNDP::SYSTEM_ARCHITECTURES[:any],0]])
|
@supported_architectures=TNDP.coalesce(args[:supported_architectures],[[:any,0]])
|
||||||
super(args)
|
super(args)
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_s
|
def to_s
|
||||||
s=""
|
s=""
|
||||||
supported_architectures.each do |sfs|
|
supported_architectures.each do |entry|
|
||||||
system_architecture_id=sfs[0]
|
system_architecture_id=TNDP::SYSTEM_ARCHITECTURES[entry[0]]
|
||||||
system_architecture_name=TNDP.coalesce(TNDP::SYSTEM_ARCHITECTURES.key_by_value(system_architecture_id),:unknown)
|
count=entry[1]
|
||||||
count=sfs[1]
|
s<<"\n%s [0x%02X] - 0x%04X" % [entry[0],system_architecture_id,count]
|
||||||
s<<"\n%s [0x%02X] - 0x%04X" % [system_architecture_name,system_architecture_id,count]
|
|
||||||
end
|
end
|
||||||
super+s
|
super+s
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_buffer
|
def to_buffer
|
||||||
supported_architectures_buffer=[supported_architectures.length].pack("C")
|
supported_architectures_buffer=[supported_architectures.length].pack("C")
|
||||||
supported_architectures.each{|a| supported_architectures_buffer+=a.pack("CC")}
|
supported_architectures.each{|a| supported_architectures_buffer+=[TNDP::SYSTEM_ARCHITECTURES[a[0]],a[1]].pack("CC")}
|
||||||
super+[highest_supported_version_id,application_name].pack("CZ20")+supported_architectures_buffer
|
super+[highest_supported_version_id,application_name].pack("CZ20")+supported_architectures_buffer
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -216,8 +214,9 @@ private
|
|||||||
supported_architectures={}
|
supported_architectures={}
|
||||||
supported_architectures_length.times do |i|
|
supported_architectures_length.times do |i|
|
||||||
system_architecture_id=buffer[(i*2)+0x1E]
|
system_architecture_id=buffer[(i*2)+0x1E]
|
||||||
|
system_architecture=TNDP.coalesce(TNDP::SYSTEM_ARCHITECTURES.key_by_value(system_architecture_id),:unknown)
|
||||||
count=buffer[(i*2)+0x1F]
|
count=buffer[(i*2)+0x1F]
|
||||||
supported_architectures[system_architecture_id]=count
|
supported_architectures[system_architecture]=count
|
||||||
end
|
end
|
||||||
self.new({:signature=>signature,:version=>version_id,:transaction_id=>transaction_id,
|
self.new({:signature=>signature,:version=>version_id,:transaction_id=>transaction_id,
|
||||||
:opcode=>opcode,:application_name=>application_name,:highest_supported_version_id=>highest_supported_version_id,:supported_architectures=>supported_architectures})
|
:opcode=>opcode,:application_name=>application_name,:highest_supported_version_id=>highest_supported_version_id,:supported_architectures=>supported_architectures})
|
||||||
@ -226,19 +225,137 @@ private
|
|||||||
end
|
end
|
||||||
|
|
||||||
class VolumeCatalogRequestMessage < BaseMessage
|
class VolumeCatalogRequestMessage < BaseMessage
|
||||||
|
attr_reader :system_architecture, :file_system, :catalog_offset
|
||||||
OPCODE=0x01
|
OPCODE=0x01
|
||||||
def initialize(args={})
|
def initialize(args={})
|
||||||
args[:opcode]=OPCODE
|
args[:opcode]=OPCODE
|
||||||
|
@system_architecture=TNDP.coalesce(TNDP::SYSTEM_ARCHITECTURES.key_by_value(TNDP::SYSTEM_ARCHITECTURES[args[:system_architecture]]),:any)
|
||||||
|
@file_system=TNDP.coalesce(TNDP::FILESYSTEMS.key_by_value(TNDP::FILESYSTEMS[args[:file_system]]),:any)
|
||||||
|
@catalog_offset=TNDP.coalesce(args[:catalog_offset],0)
|
||||||
super(args)
|
super(args)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
system_architecture_id=TNDP::SYSTEM_ARCHITECTURES[system_architecture]
|
||||||
|
file_system_id=TNDP::FILESYSTEMS[file_system]
|
||||||
|
super+"
|
||||||
|
ARCHITECTURE: #{system_architecture} [$#{"%02X"%system_architecture_id}]
|
||||||
|
FILE SYSTEM: #{file_system} [$#{"%02X"%file_system_id}]
|
||||||
|
CATALOG OFFSET: $#{"%04x" % catalog_offset}"
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_buffer
|
||||||
|
super+[TNDP::SYSTEM_ARCHITECTURES[system_architecture],TNDP::FILESYSTEMS[file_system],catalog_offset].pack("CCn")
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.from_buffer(buffer)
|
||||||
|
signature,version_id,transaction_id,opcode,system_architecture_id,file_system_id,catalog_offset=buffer.unpack("Z4CnCCCn")
|
||||||
|
self.new({:signature=>signature,:version=>version_id,:transaction_id=>transaction_id,
|
||||||
|
:opcode=>opcode,:system_architecture=>TNDP::SYSTEM_ARCHITECTURES.key_by_value(system_architecture_id),:file_system=>TNDP::FILESYSTEMS.key_by_value(file_system_id),:catalog_offset=>catalog_offset})
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
class VolumeCatalogResponseMessage < BaseMessage
|
class VolumeCatalogResponseMessage < BaseMessage
|
||||||
|
attr_reader :catalog_offset,:total_catalog_size,:catalog_entries
|
||||||
OPCODE=0x81
|
OPCODE=0x81
|
||||||
def initialize(args={})
|
def initialize(args={})
|
||||||
args[:opcode]=OPCODE
|
args[:opcode]=OPCODE
|
||||||
|
@catalog_entries=TNDP.coalesce(args[:catalog_entries],[])
|
||||||
|
@catalog_offset=TNDP.coalesce(args[:catalog_offset],0)
|
||||||
|
@total_catalog_size=TNDP.coalesce(args[:total_catalog_size],0)
|
||||||
super(args)
|
super(args)
|
||||||
end
|
end
|
||||||
|
def to_s
|
||||||
|
s="\nCATALOG ENTRIES:\n___________________"
|
||||||
|
catalog_entries.each do |entry|
|
||||||
|
s<<"\nVOLUME NAME: %s\n\tSYSTEM ARCHITECTURE: %s\n\tFILE SYSTEM: %s\n\tTRACKS : 0x%04X\n\tSECTOR SIZE: 0x%04X" % entry
|
||||||
|
end
|
||||||
|
super+"
|
||||||
|
CATALOG SIZE: $#{"%04x" % total_catalog_size}
|
||||||
|
CATALOG OFFSET: $#{"%04x" % catalog_offset}"+s
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_buffer
|
||||||
|
catalog_buffer=[total_catalog_size,catalog_offset,catalog_entries.length].pack("nnC")
|
||||||
|
catalog_entries.each do |entry|
|
||||||
|
catalog_buffer+=[entry[0][0,TNDP::MAX_VOLUME_NAME_LENGTH],TNDP::SYSTEM_ARCHITECTURES[entry[1]],TNDP::FILESYSTEMS[entry[2]],entry[3],entry[4]].pack("Z#{TNDP::MAX_VOLUME_NAME_LENGTH+1}CCnn")
|
||||||
|
end
|
||||||
|
super+catalog_buffer
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.from_buffer(buffer)
|
||||||
|
signature,version_id,transaction_id,opcode,total_catalog_size,catalog_offset,catalog_entries_length=buffer.unpack("Z4CnCnnC")
|
||||||
|
catalog_entries=[]
|
||||||
|
catalog_entries_length.times do |i|
|
||||||
|
entry_buffer=buffer[0x0D+(i*(TNDP::MAX_VOLUME_NAME_LENGTH+7)),TNDP::MAX_VOLUME_NAME_LENGTH+7]
|
||||||
|
volume_name,system_architecture_id,file_system_id,track_count,sector_size=entry_buffer.unpack("Z#{TNDP::MAX_VOLUME_NAME_LENGTH+1}CCnn")
|
||||||
|
system_architecture=TNDP.coalesce(TNDP::SYSTEM_ARCHITECTURES.key_by_value(system_architecture_id),:unknown)
|
||||||
|
file_system=TNDP.coalesce(TNDP::FILESYSTEMS.key_by_value(file_system_id),:unknown)
|
||||||
|
catalog_entries<<[volume_name,system_architecture,file_system,track_count,sector_size]
|
||||||
|
end
|
||||||
|
self.new({:signature=>signature,:version=>version_id,:transaction_id=>transaction_id,
|
||||||
|
:opcode=>opcode,:total_catalog_size=>total_catalog_size,:catalog_offset=>catalog_offset,:catalog_entries=>catalog_entries})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class SectorReadRequestMessage < BaseMessage
|
||||||
|
attr_reader :track_no,:sector_no,:sector_length,:volume_name
|
||||||
|
OPCODE=0x02
|
||||||
|
def initialize(args={})
|
||||||
|
args[:opcode]=OPCODE
|
||||||
|
[:track_no,:sector_no,:sector_length,:volume_name].each do |arg|
|
||||||
|
raise "#{arg} must be specified in a #{self.class}" if args[arg].nil?
|
||||||
|
end
|
||||||
|
|
||||||
|
@track_no=args[:track_no]
|
||||||
|
@sector_no=args[:sector_no]
|
||||||
|
@sector_length=args[:sector_length]
|
||||||
|
@volume_name=args[:volume_name]
|
||||||
|
|
||||||
|
super(args)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
super+"
|
||||||
|
VOLUME NAME: #{volume_name}
|
||||||
|
TRACK NO: $#{"%04X"%track_no}
|
||||||
|
SECTOR NO: $#{"%04X"%sector_no}
|
||||||
|
SECTOR LENGTH: $#{"%04x" % sector_length}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_buffer
|
||||||
|
super+[track_no,sector_no,sector_length,volume_name[0,MAX_VOLUME_NAME_LENGTH]].pack("nnnZ#{TNDP::MAX_VOLUME_NAME_LENGTH+1}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.from_buffer(buffer)
|
||||||
|
signature,version_id,transaction_id,opcode,track_no,sector_no,sector_length,volume_name=buffer.unpack("Z4CnCnnnZ#{TNDP::MAX_VOLUME_NAME_LENGTH+1}")
|
||||||
|
self.new({:signature=>signature,:version=>version_id,:transaction_id=>transaction_id,
|
||||||
|
:opcode=>opcode,:track_no=>track_no,:sector_no=>sector_no,:sector_length=>sector_length,:volume_name=>volume_name})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class SectorReadResponseMessage < SectorReadRequestMessage
|
||||||
|
attr_reader :sector_data
|
||||||
|
def initialize(args={})
|
||||||
|
raise "sector_data must be specified in a #{self.class}" if args[:sector_data].nil?
|
||||||
|
@sector_data=args[:sector_data]
|
||||||
|
super(args)
|
||||||
|
end
|
||||||
|
def to_s
|
||||||
|
super+"\nSECTOR DATA:\n"+TNDP.hex_dump(sector_data)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_buffer
|
||||||
|
super+sector_data.pack("C#{sector_length}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def from_buffer
|
||||||
|
signature,version_id,transaction_id,opcode,track_no,sector_no,sector_length,volume_name,sector_data=buffer.unpack("Z4CnCnnnZ#{TNDP::MAX_VOLUME_NAME_LENGTH+1}C*")
|
||||||
|
self.new({:signature=>signature,:version=>version_id,:transaction_id=>transaction_id,
|
||||||
|
:opcode=>opcode,:track_no=>track_no,:sector_no=>sector_no,:sector_length=>sector_length,:volume_name=>volume_name,:sector_data=>sector_data})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class ErrorResponseMessage < BaseMessage
|
class ErrorResponseMessage < BaseMessage
|
||||||
@ -252,20 +369,20 @@ private
|
|||||||
super(args)
|
super(args)
|
||||||
end
|
end
|
||||||
def self.from_buffer(buffer)
|
def self.from_buffer(buffer)
|
||||||
signature,version_id,transaction_id,opcode,errorcode,error_description,original_data_elements=buffer.unpack("Z4CnCnZ64Z16")
|
signature,version_id,transaction_id,opcode,errorcode,error_description,original_data_elements=buffer.unpack("Z4CnCnZ128Z16")
|
||||||
self.new({:signature=>signature,:version=>version_id,:transaction_id=>transaction_id,
|
self.new({:signature=>signature,:version=>version_id,:transaction_id=>transaction_id,
|
||||||
:opcode=>opcode,:errorcode=>errorcode,:error_description=>error_description,:original_data_elements=>original_data_elements})
|
:opcode=>opcode,:errorcode=>errorcode,:error_description=>error_description,:original_data_elements=>original_data_elements})
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_s
|
def to_s
|
||||||
super+"
|
super+"
|
||||||
ERROR CODE: 0x#{"%02X"%errorcode}
|
ERROR CODE: $#{"%02X"%errorcode}
|
||||||
DESCRIPTION: #{error_description}
|
DESCRIPTION: #{error_description}
|
||||||
ORIGINAL DATA: #{original_data_elements}
|
ORIGINAL DATA: #{original_data_elements}
|
||||||
"
|
"
|
||||||
end
|
end
|
||||||
def to_buffer
|
def to_buffer
|
||||||
super+[errorcode,error_description,0,original_data_elements].pack("nZ63CZ16")
|
super+[errorcode,error_description,0,original_data_elements].pack("nZ127CZ16")
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.create_error_response(original_request_buffer,errorcode,error_description)
|
def self.create_error_response(original_request_buffer,errorcode,error_description)
|
||||||
|
@ -7,23 +7,30 @@ require 'tndp'
|
|||||||
require 'socket'
|
require 'socket'
|
||||||
require 'RipXplore'
|
require 'RipXplore'
|
||||||
|
|
||||||
|
SYSTEM_ARCHITECTURE_TRANSLATIONS={
|
||||||
|
"Apple2"=>:apple2,
|
||||||
|
"C64"=>:c64,
|
||||||
|
}
|
||||||
|
FILE_SYSTEM_TRANSLATIONS={
|
||||||
|
"AppleDos"=>:apple_dos_33,
|
||||||
|
"CbmDos"=>:cbm_dos,
|
||||||
|
"RawDisk"=>:raw,
|
||||||
|
"ProDos"=>:prodos,
|
||||||
|
"AppleCPM"=>:cpm,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class TNDPServer
|
class TNDPServer
|
||||||
LISTENING_PORT=6502
|
LISTENING_PORT=6502
|
||||||
attr_reader :root_directory,:port ,:server_thread,:socket
|
attr_reader :root_directory,:port ,:server_thread,:socket,:volume_catalog
|
||||||
def initialize(root_directory,port=LISTENING_PORT)
|
def initialize(root_directory,port=LISTENING_PORT)
|
||||||
@root_directory=root_directory
|
@root_directory=root_directory
|
||||||
@port=port
|
@port=port
|
||||||
end
|
end
|
||||||
|
|
||||||
def file_system_images_in_directory
|
|
||||||
image_files=[]
|
|
||||||
Dir.foreach(@root_directory) do |filename|
|
|
||||||
image_files<< RipXplore.best_fit_from_filename("#{@root_directory}/#{filename}") if FileSystemImage.is_file_system_image_filename?(filename)
|
|
||||||
end
|
|
||||||
image_files
|
|
||||||
end
|
|
||||||
|
|
||||||
def start
|
def start
|
||||||
|
create_volume_catalog
|
||||||
@server_thread=Thread.start do
|
@server_thread=Thread.start do
|
||||||
@socket=UDPSocket.open
|
@socket=UDPSocket.open
|
||||||
@socket.bind("",port)
|
@socket.bind("",port)
|
||||||
@ -39,22 +46,32 @@ class TNDPServer
|
|||||||
case request.opcode
|
case request.opcode
|
||||||
when TNDP::CapabilitiesRequestMessage::OPCODE
|
when TNDP::CapabilitiesRequestMessage::OPCODE
|
||||||
supported_architectures={}
|
supported_architectures={}
|
||||||
file_system_images_in_directory.each do |file_system_image|
|
volume_catalog.each do |entry|
|
||||||
if file_system_image.image_format.host_system==Apple2
|
supported_architectures[entry[1]]=1+TNDP.coalesce(supported_architectures[entry[1]],0)
|
||||||
then
|
|
||||||
architecture_id=TNDP::SYSTEM_ARCHITECTURES[:apple2 ]
|
|
||||||
elsif file_system_image.image_format.host_system==C64 then
|
|
||||||
architecture_id=TNDP::SYSTEM_ARCHITECTURES[:c64]
|
|
||||||
else
|
|
||||||
architecture_id=TNDP::SYSTEM_ARCHITECTURES[:other]
|
|
||||||
end
|
|
||||||
supported_architectures[architecture_id]=1+TNDP.coalesce(supported_architectures[architecture_id],0)
|
|
||||||
end
|
end
|
||||||
response=TNDP::CapabilitiesResponseMessage.new({:supported_architectures=>supported_architectures})
|
response=TNDP::CapabilitiesResponseMessage.new({:supported_architectures=>supported_architectures})
|
||||||
when TNDP::VolumeCatalogRequestMessage::OPCODE
|
when TNDP::VolumeCatalogRequestMessage::OPCODE
|
||||||
response=TNDP::VolumeCatalogResponseMessage.new()
|
catalog_subset=subset_volume_catalog(request.system_architecture,request.file_system)
|
||||||
|
catalog_offset=request.catalog_offset
|
||||||
|
catalog_entries=catalog_subset[catalog_offset,TNDP::MAX_CATALOG_ENTRIES_PER_MESSAGE]
|
||||||
|
response=TNDP::VolumeCatalogResponseMessage.new({:catalog_entries=>catalog_entries,:catalog_offset=>catalog_offset,:total_catalog_size=>catalog_subset.length})
|
||||||
|
when TNDP::SectorReadRequestMessage::OPCODE
|
||||||
|
file_system_image=nil
|
||||||
|
begin
|
||||||
|
file_system_image=RipXplore.best_fit_from_filename("#{@root_directory}/#{request.volume_name}")
|
||||||
|
rescue Exception=>e
|
||||||
|
response=TNDP::ErrorResponseMessage.create_error_response(data,TNDP::ErrorCodes::INVALID_VOLUME_NAME,e.to_s)
|
||||||
|
end
|
||||||
|
if !(file_system_image.nil?) then
|
||||||
|
track_no=request.track_no
|
||||||
|
sector_no=request.sector_no
|
||||||
|
sector_length=request.sector_length
|
||||||
|
if (track_no<file_system_image.start_track) || (track_no>file_system_image.end_track) then
|
||||||
|
response=TNDP::ErrorResponseMessage.create_error_response(data,TNDP::ErrorCodes::INVALID_TRACK_NUMBER,"requested track $#{"%X"% track_no} outside allowable range of $#{"%X"% file_system_image.start_track}..$#{"%X"% file_system_image.end_track}")
|
||||||
|
end
|
||||||
|
end
|
||||||
else
|
else
|
||||||
response=TNDP::ErrorResponseMessage.create_error_response(data,TNDP::ErrorCodes::UNKNOWN_OPCODE,"unknown opcode 0x#{"%02X" % request.opcode}")
|
response=TNDP::ErrorResponseMessage.create_error_response(data,TNDP::ErrorCodes::UNKNOWN_OPCODE,"unknown opcode $#{"%02X" % request.opcode}")
|
||||||
end
|
end
|
||||||
response.transaction_id=request.transaction_id
|
response.transaction_id=request.transaction_id
|
||||||
rescue Exception=>e
|
rescue Exception=>e
|
||||||
@ -70,6 +87,37 @@ class TNDPServer
|
|||||||
def shutdown
|
def shutdown
|
||||||
log_msg("TNDP server on UDP port #{port} shutting down")
|
log_msg("TNDP server on UDP port #{port} shutting down")
|
||||||
@server_thread.kill
|
@server_thread.kill
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
#return a subset of the total volume catalog that includes just the entries that match the passed in system architecture & file system
|
||||||
|
def subset_volume_catalog(system_architecture,file_system)
|
||||||
|
# return volume_catalog
|
||||||
|
vc=[]
|
||||||
|
volume_catalog.each do |entry|
|
||||||
|
vc<<entry if (system_architecture==:any || system_architecture==entry[1]) && (file_system==:any || file_system==entry[2])
|
||||||
|
end
|
||||||
|
vc
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def create_volume_catalog
|
||||||
|
@volume_catalog=[]
|
||||||
|
Dir.foreach(@root_directory) do |filename|
|
||||||
|
if FileSystemImage.is_file_system_image_filename?(filename) then
|
||||||
|
begin
|
||||||
|
file_system_image=RipXplore.best_fit_from_filename("#{@root_directory}/#{filename}")
|
||||||
|
system_architecture=TNDP.coalesce(SYSTEM_ARCHITECTURE_TRANSLATIONS[file_system_image.image_format.host_system.to_s],:unknown)
|
||||||
|
file_system=TNDP.coalesce(FILE_SYSTEM_TRANSLATIONS[file_system_image.file_system.to_s],:unknown)
|
||||||
|
start_track=file_system_image.start_track
|
||||||
|
puts filename
|
||||||
|
@volume_catalog<<[filename,system_architecture,file_system,file_system_image.track_count,file_system_image.get_sector(start_track,0).length]
|
||||||
|
rescue Exception=>e
|
||||||
|
#don't stop if anything throws an exception
|
||||||
|
log_msg("ERROR: parsing of #{filename} failed:\n"+e.to_s)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
@ -4,7 +4,10 @@ $:.unshift(lib_path) unless $:.include?(lib_path)
|
|||||||
require 'test/unit'
|
require 'test/unit'
|
||||||
require 'tndp_server'
|
require 'tndp_server'
|
||||||
|
|
||||||
def log(msg)
|
|
||||||
|
TEST_IMAGES_DIR=File.dirname(__FILE__)+"/test_images"
|
||||||
|
|
||||||
|
def log_msg(msg)
|
||||||
puts msg
|
puts msg
|
||||||
end
|
end
|
||||||
class TestServer <Test::Unit::TestCase
|
class TestServer <Test::Unit::TestCase
|
||||||
@ -14,6 +17,7 @@ class TestServer <Test::Unit::TestCase
|
|||||||
|
|
||||||
|
|
||||||
def send_request_and_get_response(request)
|
def send_request_and_get_response(request)
|
||||||
|
puts "###########"
|
||||||
@@client_socket.send(request.to_buffer,0)
|
@@client_socket.send(request.to_buffer,0)
|
||||||
if (select([@@client_socket],nil,nil,5)).nil? then
|
if (select([@@client_socket],nil,nil,5)).nil? then
|
||||||
raise "no response from server"
|
raise "no response from server"
|
||||||
@ -25,14 +29,50 @@ class TestServer <Test::Unit::TestCase
|
|||||||
end
|
end
|
||||||
def test_server
|
def test_server
|
||||||
Thread.abort_on_exception = true
|
Thread.abort_on_exception = true
|
||||||
server=TNDPServer.new(File.dirname(__FILE__)+"/test_images")
|
server=TNDPServer.new(TEST_IMAGES_DIR)
|
||||||
server.start
|
server.start
|
||||||
capabilities_response_msg=send_request_and_get_response(TNDP::CapabilitiesRequestMessage.new())
|
capabilities_response_msg=send_request_and_get_response(TNDP::CapabilitiesRequestMessage.new())
|
||||||
assert(capabilities_response_msg.respond_to?(:supported_architectures),"capabilities response message should include list of architectures supported by server")
|
assert(capabilities_response_msg.respond_to?(:supported_architectures),"capabilities response message should include list of architectures supported by server")
|
||||||
|
|
||||||
volume_catalog_response_msg=send_request_and_get_response(TNDP::VolumeCatalogRequestMessage.new())
|
assert(capabilities_response_msg.supported_architectures[:apple2]>0,"should be at least 1 apple2 image")
|
||||||
assert(capabilities_response_msg.respond_to?(:supported_architectures),"capabilities response message should include list of architectures supported by server")
|
assert(capabilities_response_msg.supported_architectures[:c64]>0,"should be at least 1 C64 image")
|
||||||
|
[[:apple2,:apple_dos_33],[:apple2,:prodos],[:apple2,:any],[:c64,:cbm_dos],[:any,:any],[:any,:prodos]].each do |a|
|
||||||
|
desired_system_architecture=a[0]
|
||||||
|
desired_file_system=a[1]
|
||||||
|
log_msg ("TESTING CATALOG FOR ARCHITECTURE #{desired_system_architecture} / FILE SYSTEM #{desired_file_system}")
|
||||||
|
|
||||||
|
done=false
|
||||||
|
catalog_offset=0
|
||||||
|
while !done do
|
||||||
|
volume_catalog_request_msg=TNDP::VolumeCatalogRequestMessage.new({:system_architecture=>desired_system_architecture,:file_system=>desired_file_system,:catalog_offset=>catalog_offset})
|
||||||
|
assert_equal(desired_system_architecture,volume_catalog_request_msg.system_architecture)
|
||||||
|
assert_equal(desired_file_system,volume_catalog_request_msg.file_system)
|
||||||
|
volume_catalog_response_msg=send_request_and_get_response(volume_catalog_request_msg)
|
||||||
|
assert(volume_catalog_response_msg.respond_to?(:catalog_entries),"volume catalogue response message should include list of volumes on server")
|
||||||
|
assert(volume_catalog_response_msg.catalog_entries.length>0,"volume catalogue response message should have at least 1 entry")
|
||||||
|
volume_catalog_response_msg.catalog_entries.each do |entry|
|
||||||
|
assert_equal(desired_system_architecture,entry[1],"each entry should be for specified architecture") unless desired_system_architecture==:any
|
||||||
|
assert_equal(desired_file_system,entry[2],"each entry should be for specified file system") unless desired_file_system==:any
|
||||||
|
volume_name=entry[0]
|
||||||
|
track_no=entry[3]-1 #last track
|
||||||
|
sector_no=1
|
||||||
|
sector_length=entry[4]
|
||||||
|
assert_equal(TNDP::ErrorCodes::INVALID_VOLUME_NAME,send_request_and_get_response(TNDP::SectorReadRequestMessage.new({:track_no=>track_no,:sector_no=>sector_no,:sector_length=>sector_length,:volume_name=>"invalid file name"})).errorcode,"invalid volume name should return error")
|
||||||
|
assert_equal(TNDP::ErrorCodes::INVALID_TRACK_NUMBER,send_request_and_get_response(TNDP::SectorReadRequestMessage.new({:track_no=>0xDEAD,:sector_no=>sector_no,:sector_length=>sector_length,:volume_name=>volume_name})).errorcode,"invalid track number should return error")
|
||||||
|
assert_equal(TNDP::ErrorCodes::INVALID_SECTOR_NUMBER,send_request_and_get_response(TNDP::SectorReadRequestMessage.new({:track_no=>track_no,:sector_no=>0xBEEF,:sector_length=>sector_length,:volume_name=>volume_name})).errorcode,"invalid sector number should return error")
|
||||||
|
assert_equal(TNDP::ErrorCodes::INVALID_SECTOR_LENGTH,send_request_and_get_response(TNDP::SectorReadRequestMessage.new({:track_no=>track_no,:sector_no=>sector_no,:sector_length=>0xD00D,:volume_name=>volume_name})).errorcode,"invalid sector length should return error")
|
||||||
|
|
||||||
|
file_system_image=RipXplore.best_fit_from_filename("#{TEST_IMAGES_DIR}/#{volume_name}")
|
||||||
|
sector_read_request_msg=TNDP::SectorReadRequestMessage.new({:track_no=>track_no,:sector_no=>sector_no,:sector_length=>sector_length,:volume_name=>volume_name})
|
||||||
|
sector_read_response_msg=send_request_and_get_response(sector_read_request_msg)
|
||||||
|
|
||||||
|
assert(sector_read_response_msg.respond_to?(:sector_data),"sector read response message should include sector data")
|
||||||
|
assert(file_system_image.get_sector(track_no,sector_no)==sector_read_response_msg.sector_data,"data returned from server should match data read directly from disk")
|
||||||
|
end
|
||||||
|
catalog_offset+=volume_catalog_response_msg.catalog_entries.length
|
||||||
|
done=(catalog_offset>volume_catalog_response_msg.total_catalog_size-1)
|
||||||
|
end
|
||||||
|
end
|
||||||
server.shutdown
|
server.shutdown
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
$:.unshift(File.dirname(__FILE__)) unless
|
$:.unshift(File.dirname(__FILE__)) unless
|
||||||
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
||||||
|
|
||||||
|
def log_msg(msg)
|
||||||
|
puts msg
|
||||||
|
end
|
||||||
|
|
||||||
Dir.glob("tc_*.rb").each do |tc|
|
Dir.glob("tc_*.rb").each do |tc|
|
||||||
require tc
|
require tc
|
||||||
end
|
end
|
Loading…
Reference in New Issue
Block a user