From 830d2719799c1bae1af1a605c5abf4ca68f04b90 Mon Sep 17 00:00:00 2001 From: jonnosan Date: Sat, 24 Jan 2009 10:55:33 +0000 Subject: [PATCH] git-svn-id: http://svn.code.sf.net/p/netboot65/code@21 93682198-c243-4bdb-bd91-e943c89aac3b --- client/clients/Makefile | 6 + client/clients/bootmenu.s | 141 ++++++++++++++++++++++++ doc/protocol.txt | 18 +-- server/lib/tndp.rb | 201 +++++++++++++++++++++++++++------- server/lib/tndp_server.rb | 98 ++++++++++++----- server/test/tc_test_server.rb | 48 +++++++- server/test/ts_test_all.rb | 4 + 7 files changed, 436 insertions(+), 80 deletions(-) create mode 100644 client/clients/bootmenu.s diff --git a/client/clients/Makefile b/client/clients/Makefile index d70cc5c..1d1eeb3 100644 --- a/client/clients/Makefile +++ b/client/clients/Makefile @@ -15,6 +15,8 @@ IP65LIB=../ip65/ip65.lib C64NETLIB=../drivers/c64net.lib APPLE2NETLIB=../drivers/apple2net.lib +BOOTA2.BIN=../../server/boot/BOOTA2.BIN + INCFILES=\ ../inc/common.i\ ../inc/commonprint.i\ @@ -23,6 +25,7 @@ INCFILES=\ all: \ rrnetboot.bin \ utherboot.dsk \ + $(BOOTA2.BIN) \ rrnetboot.bin: rrnetboot.o $(IP65LIB) $(C64NETLIB) $(INCFILES) ../cfg/rrbin.cfg $(LD) -m rrnetboot.map -C ../cfg/rrbin.cfg -o rrnetboot.bin $(AFLAGS) $< $(IP65LIB) $(C64NETLIB) @@ -34,6 +37,9 @@ utherboot.bin: utherboot.o $(IP65LIB) $(APPLE2NETLIB) $(INCFILES) ../cfg/a2langu utherboot.dsk: utherboot.bin dsktool.rb --init dos33 utherboot.dsk -a utherboot.bin -t B 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: rm -f *.o diff --git a/client/clients/bootmenu.s b/client/clients/bootmenu.s new file mode 100644 index 0000000..cc0bc84 --- /dev/null +++ b/client/clients/bootmenu.s @@ -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 + diff --git a/doc/protocol.txt b/doc/protocol.txt index 3cc7a80..0cfe3ec 100644 --- a/doc/protocol.txt +++ b/doc/protocol.txt @@ -2,7 +2,7 @@ TRIVIAL NETWORK DISK PROTOCOL * "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 Offset meaning @@ -45,8 +45,8 @@ For READ/WRITE: $08..$09 2 byte track # $0A..$0B 2 byte sector # $0C..$0D 2 byte sector length (in bytes) -$0E..$4B volume name (null padded 61 bytes) -$4C null byte (i.e. filename can be 61 bytes long, MUST be at least 1 null at the 62nd byte) +$0E..$4B volume name (null padded 55 bytes) +$4C null byte (i.e. filename can be 55 bytes long, MUST be at least 1 null at the 56th byte) FOR WRITE $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. FOR VOLUME CATALOG -$06..$07 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 Number of Entries in this response (maximum of 10) +$08..$09 Total Catalogue Entries +$0A..$0B Catalogue Offset (i.e. how many entries are there in the catalogue before the first entry in this response) +$0C Number of Entries in this response (maximum of 10) -$0b.. the volume catalog entries. +$0D.. the volume catalog entries. for each entry $00..$3c volume name (up to 56 chars, null padded) @@ -91,8 +91,8 @@ $37 null byte $38 system architecture ID $39 file system ID $3A..$3B number of tracks -$3C..$3D number of sectors -$3E..$3F sector length (in bytes) +$3C..$3D sector length (in bytes) +$3E..$3F ?? diff --git a/server/lib/tndp.rb b/server/lib/tndp.rb index c974b62..d783339 100644 --- a/server/lib/tndp.rb +++ b/server/lib/tndp.rb @@ -17,6 +17,7 @@ class Hash end end + module TNDP OPCODES={ @@ -30,12 +31,14 @@ module TNDP 0x83 =>"WRITE SECTOR RESPONSE", 0xFF =>"ERROR RESPONSE", } - + + MAX_CATALOG_ENTRIES_PER_MESSAGE=8 + MAX_VOLUME_NAME_LENGTH=57 SYSTEM_ARCHITECTURES={ :any=>0x00, :c64=>0x64, :apple2=>0xA2, - :other=>0xFF, + :unknown=>0xFF, } FILESYSTEMS={ @@ -44,7 +47,8 @@ FILESYSTEMS={ :apple_dos_33=>0x02, :prodos=>0x03, :cpm=>0x04, - :cbm_dos=>0x05 + :cbm_dos=>0x05, + :unknown=>0xFF, } class ErrorCodes @@ -74,7 +78,26 @@ FILESYSTEMS={ class InvalidOpcode < FormatError 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) if a.nil? then b @@ -123,30 +146,13 @@ FILESYSTEMS={ TNDP MESSAGE SIGNATURE: #{signature} VERSION: #{version_id} -TRANSACTION ID: 0x#{"%04x"%transaction_id} -OPCODE: 0x#{"%02X"%opcode} [#{OPCODES[opcode].nil? ? "UNKNOWN" : OPCODES[opcode]}]" +TRANSACTION ID: $#{"%04x"%transaction_id} +OPCODE: $#{"%02X"%opcode} [#{OPCODES[opcode].nil? ? "UNKNOWN" : OPCODES[opcode]}]" end def hex_dump - buffer=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 + TNDP.hex_dump(raw_bytes) end - def initialize(args={}) # puts "args:" # args.keys.each do |key| @@ -168,13 +174,6 @@ private end - class RequestMessage < BaseMessage - attr_reader :opcode - end - - class ResponseMessage < BaseMessage - end - class CapabilitiesRequestMessage < BaseMessage OPCODE=0x00 def initialize(args={}) @@ -190,24 +189,23 @@ private args[:opcode]=OPCODE @application_name=TNDP.coalesce(args[:application_name],"netboot 65") @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) end def to_s s="" - supported_architectures.each do |sfs| - system_architecture_id=sfs[0] - system_architecture_name=TNDP.coalesce(TNDP::SYSTEM_ARCHITECTURES.key_by_value(system_architecture_id),:unknown) - count=sfs[1] - s<<"\n%s [0x%02X] - 0x%04X" % [system_architecture_name,system_architecture_id,count] + supported_architectures.each do |entry| + system_architecture_id=TNDP::SYSTEM_ARCHITECTURES[entry[0]] + count=entry[1] + s<<"\n%s [0x%02X] - 0x%04X" % [entry[0],system_architecture_id,count] end super+s end def to_buffer 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 end @@ -216,8 +214,9 @@ private supported_architectures={} supported_architectures_length.times do |i| 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] - supported_architectures[system_architecture_id]=count + supported_architectures[system_architecture]=count end 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}) @@ -226,19 +225,137 @@ private end class VolumeCatalogRequestMessage < BaseMessage + attr_reader :system_architecture, :file_system, :catalog_offset OPCODE=0x01 def initialize(args={}) 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) 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 class VolumeCatalogResponseMessage < BaseMessage + attr_reader :catalog_offset,:total_catalog_size,:catalog_entries OPCODE=0x81 def initialize(args={}) 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) 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 class ErrorResponseMessage < BaseMessage @@ -252,20 +369,20 @@ private super(args) end 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, :opcode=>opcode,:errorcode=>errorcode,:error_description=>error_description,:original_data_elements=>original_data_elements}) end def to_s super+" -ERROR CODE: 0x#{"%02X"%errorcode} +ERROR CODE: $#{"%02X"%errorcode} DESCRIPTION: #{error_description} ORIGINAL DATA: #{original_data_elements} " end def to_buffer - super+[errorcode,error_description,0,original_data_elements].pack("nZ63CZ16") + super+[errorcode,error_description,0,original_data_elements].pack("nZ127CZ16") end def self.create_error_response(original_request_buffer,errorcode,error_description) diff --git a/server/lib/tndp_server.rb b/server/lib/tndp_server.rb index 03fdf26..b1e99c8 100644 --- a/server/lib/tndp_server.rb +++ b/server/lib/tndp_server.rb @@ -7,23 +7,30 @@ require 'tndp' require 'socket' 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 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) @root_directory=root_directory @port=port 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 + create_volume_catalog @server_thread=Thread.start do @socket=UDPSocket.open @socket.bind("",port) @@ -37,24 +44,34 @@ class TNDPServer request=TNDP.message_from_buffer(data) log_msg(request.to_s) case request.opcode - when TNDP::CapabilitiesRequestMessage::OPCODE + when TNDP::CapabilitiesRequestMessage::OPCODE supported_architectures={} - file_system_images_in_directory.each do |file_system_image| - if file_system_image.image_format.host_system==Apple2 - 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) + volume_catalog.each do |entry| + supported_architectures[entry[1]]=1+TNDP.coalesce(supported_architectures[entry[1]],0) end response=TNDP::CapabilitiesResponseMessage.new({:supported_architectures=>supported_architectures}) - when TNDP::VolumeCatalogRequestMessage::OPCODE - response=TNDP::VolumeCatalogResponseMessage.new() + when TNDP::VolumeCatalogRequestMessage::OPCODE + 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_nofile_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 - 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 response.transaction_id=request.transaction_id rescue Exception=>e @@ -69,7 +86,38 @@ class TNDPServer def shutdown log_msg("TNDP server on UDP port #{port} shutting down") - @server_thread.kill - + @server_thread.kill 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<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 \ No newline at end of file diff --git a/server/test/tc_test_server.rb b/server/test/tc_test_server.rb index 6a838c3..66a2057 100644 --- a/server/test/tc_test_server.rb +++ b/server/test/tc_test_server.rb @@ -4,7 +4,10 @@ $:.unshift(lib_path) unless $:.include?(lib_path) require 'test/unit' require 'tndp_server' -def log(msg) + +TEST_IMAGES_DIR=File.dirname(__FILE__)+"/test_images" + +def log_msg(msg) puts msg end class TestServer 0,"should be at least 1 apple2 image") + 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 end diff --git a/server/test/ts_test_all.rb b/server/test/ts_test_all.rb index d804513..3e57a4b 100644 --- a/server/test/ts_test_all.rb +++ b/server/test/ts_test_all.rb @@ -1,6 +1,10 @@ $:.unshift(File.dirname(__FILE__)) unless $:.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| require tc end \ No newline at end of file