mirror of
https://github.com/bobbimanners/emailler.git
synced 2025-01-13 22:33:54 +00:00
git-svn-id: http://svn.code.sf.net/p/netboot65/code@19 93682198-c243-4bdb-bd91-e943c89aac3b
This commit is contained in:
parent
36f9e60107
commit
e1dd101b7a
11
server/lib/SubclassTracking.rb
Normal file
11
server/lib/SubclassTracking.rb
Normal file
@ -0,0 +1,11 @@
|
||||
#derived from http://ola-bini.blogspot.com/2007/07/objectspace-to-have-or-not-to-have.html
|
||||
module SubclassTracking
|
||||
def self.extended(klazz)
|
||||
(class <<klazz; self; end).send :attr_accessor, :subclasses
|
||||
(class <<klazz; self; end).send :define_method, :inherited do |clzz|
|
||||
klazz.subclasses << clzz
|
||||
super
|
||||
end
|
||||
klazz.subclasses = []
|
||||
end
|
||||
end
|
277
server/lib/tndp.rb
Normal file
277
server/lib/tndp.rb
Normal file
@ -0,0 +1,277 @@
|
||||
#
|
||||
# message formats for Trivial Network Disk Protocol
|
||||
#
|
||||
$:.unshift(File.dirname(__FILE__)) unless
|
||||
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
||||
require 'SubclassTracking'
|
||||
|
||||
|
||||
#useful extension to Hash class
|
||||
|
||||
class Hash
|
||||
def key_by_value(value)
|
||||
self.keys.each do |key|
|
||||
return key if self[key]==value
|
||||
end
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
module TNDP
|
||||
|
||||
OPCODES={
|
||||
0x00 =>"CAPABILITIES REQUEST",
|
||||
0x01 =>"VOLUME CATALOG REQUEST",
|
||||
0x02 =>"READ SECTOR REQUEST",
|
||||
0x03 =>"WRITE SECTOR REQUEST",
|
||||
0x80 =>"CAPABILITIES RESPONSE",
|
||||
0x81 =>"VOLUME CATALOG RESPONSE",
|
||||
0x82 =>"READ SECTOR RESPONSE",
|
||||
0x83 =>"WRITE SECTOR RESPONSE",
|
||||
0xFF =>"ERROR RESPONSE",
|
||||
}
|
||||
|
||||
SYSTEM_ARCHITECTURES={
|
||||
:any=>0x00,
|
||||
:c64=>0x64,
|
||||
:apple2=>0xA2,
|
||||
:other=>0xFF,
|
||||
}
|
||||
|
||||
FILESYSTEMS={
|
||||
:any=>0x00,
|
||||
:raw=>0x01,
|
||||
:apple_dos_33=>0x02,
|
||||
:prodos=>0x03,
|
||||
:cpm=>0x04,
|
||||
:cbm_dos=>0x05
|
||||
}
|
||||
|
||||
class ErrorCodes
|
||||
OK=0x00
|
||||
UNKNOWN_OPCODE=0x02
|
||||
VERSION_NOT_SUPPORTED=0x03
|
||||
ARCHITECTURE_NOT_SUPPORTED=0x04
|
||||
FILESYSTEM_NOT_SUPPORTED=0x05
|
||||
INVALID_VOLUME_NAME=0x06
|
||||
INVALID_TRACK_NUMBER=0x07
|
||||
INVALID_SECTOR_NUMBER=0x08
|
||||
INVALID_SECTOR_LENGTH=0x09
|
||||
INTERNAL_SERVER_ERROR=0xFF
|
||||
end
|
||||
|
||||
class FormatError <RuntimeError
|
||||
end
|
||||
|
||||
class BufferTooShort < FormatError
|
||||
end
|
||||
|
||||
class InvalidSignature < FormatError
|
||||
end
|
||||
|
||||
class InvalidVersion < FormatError
|
||||
end
|
||||
|
||||
class InvalidOpcode < FormatError
|
||||
end
|
||||
|
||||
def TNDP.coalesce(a,b)
|
||||
if a.nil? then
|
||||
b
|
||||
else
|
||||
a
|
||||
end
|
||||
end
|
||||
|
||||
@@next_transaction_id=0
|
||||
def TNDP.next_transaction_id
|
||||
@@next_transaction_id+=1
|
||||
@@next_transaction_id
|
||||
end
|
||||
|
||||
MESSAGE_SIGNATURE='TNDP'
|
||||
VERSION_ID=1
|
||||
def TNDP.message_from_buffer(buffer)
|
||||
raise BufferTooShort unless buffer.length>=0x07
|
||||
signature,version_id,transaction_id,opcode=buffer.unpack("Z4CnC")
|
||||
raise InvalidSignature unless signature==MESSAGE_SIGNATURE
|
||||
raise InvalidVersion unless version_id==1
|
||||
|
||||
BaseMessage.subclasses.each do |msg_type|
|
||||
next unless defined?(msg_type::OPCODE)
|
||||
return msg_type.from_buffer(buffer) if opcode==msg_type::OPCODE
|
||||
end
|
||||
raise InvalidOpcode
|
||||
end
|
||||
|
||||
class BaseMessage
|
||||
attr_accessor :signature,:version_id,:transaction_id,:opcode,:raw_bytes
|
||||
|
||||
def raw_bytes
|
||||
@raw_bytes=to_buffer if @raw_bytes.nil?
|
||||
@raw_bytes
|
||||
end
|
||||
|
||||
def to_buffer
|
||||
packed_buffer=[signature,version_id,transaction_id,opcode].pack("Z4cnC")
|
||||
packed_buffer
|
||||
end
|
||||
|
||||
def to_s
|
||||
hex_dump+
|
||||
"
|
||||
TNDP MESSAGE
|
||||
SIGNATURE: #{signature}
|
||||
VERSION: #{version_id}
|
||||
TRANSACTION ID: 0x#{"%04x"%transaction_id}
|
||||
OPCODE: 0x#{"%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
|
||||
end
|
||||
|
||||
def initialize(args={})
|
||||
# puts "args:"
|
||||
# args.keys.each do |key|
|
||||
# puts "#{key}: #{args[key]}\n"
|
||||
# end
|
||||
@signature=TNDP.coalesce(args[:signature],MESSAGE_SIGNATURE)
|
||||
@version_id=TNDP.coalesce(args[:version_id],0x01)
|
||||
@transaction_id=TNDP.coalesce(args[:transaction_id],TNDP.next_transaction_id)
|
||||
@opcode=TNDP.coalesce(args[:opcode],0xFF)
|
||||
end
|
||||
|
||||
private
|
||||
def self.from_buffer(buffer)
|
||||
signature,version_id,transaction_id,opcode=buffer.unpack("Z4CnC")
|
||||
self.new({:signature=>signature,:version=>version_id,:transaction_id=>transaction_id,:opcode=>opcode})
|
||||
end
|
||||
|
||||
self.extend SubclassTracking
|
||||
|
||||
end
|
||||
|
||||
class RequestMessage < BaseMessage
|
||||
attr_reader :opcode
|
||||
end
|
||||
|
||||
class ResponseMessage < BaseMessage
|
||||
end
|
||||
|
||||
class CapabilitiesRequestMessage < BaseMessage
|
||||
OPCODE=0x00
|
||||
def initialize(args={})
|
||||
args[:opcode]=OPCODE
|
||||
super(args)
|
||||
end
|
||||
end
|
||||
|
||||
class CapabilitiesResponseMessage < BaseMessage
|
||||
OPCODE=0x80
|
||||
attr_reader :application_name,:highest_supported_version_id,:supported_architectures
|
||||
def initialize(args={})
|
||||
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]])
|
||||
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]
|
||||
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")}
|
||||
super+[highest_supported_version_id,application_name].pack("CZ20")+supported_architectures_buffer
|
||||
end
|
||||
|
||||
def self.from_buffer(buffer)
|
||||
signature,version_id,transaction_id,opcode,highest_supported_version_id,application_name,supported_architectures_length=buffer.unpack("Z4CnCCZ20C")
|
||||
supported_architectures={}
|
||||
supported_architectures_length.times do |i|
|
||||
system_architecture_id=buffer[(i*2)+0x1E]
|
||||
count=buffer[(i*2)+0x1F]
|
||||
supported_architectures[system_architecture_id]=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})
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class VolumeCatalogRequestMessage < BaseMessage
|
||||
OPCODE=0x01
|
||||
def initialize(args={})
|
||||
args[:opcode]=OPCODE
|
||||
super(args)
|
||||
end
|
||||
end
|
||||
|
||||
class VolumeCatalogResponseMessage < BaseMessage
|
||||
OPCODE=0x81
|
||||
def initialize(args={})
|
||||
args[:opcode]=OPCODE
|
||||
super(args)
|
||||
end
|
||||
end
|
||||
|
||||
class ErrorResponseMessage < BaseMessage
|
||||
OPCODE=0xFF
|
||||
attr_reader :errorcode,:error_description,:original_data_elements
|
||||
def initialize(args={})
|
||||
args[:opcode]=OPCODE
|
||||
@errorcode=TNDP.coalesce(args[:errorcode],0xFF)
|
||||
@error_description=TNDP.coalesce(args[:error_description],"an error occured")
|
||||
@original_data_elements=TNDP.coalesce(args[:original_data_elements],"")
|
||||
super(args)
|
||||
end
|
||||
def self.from_buffer(buffer)
|
||||
signature,version_id,transaction_id,opcode,errorcode,error_description,original_data_elements=buffer.unpack("Z4CnCnZ64Z16")
|
||||
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}
|
||||
DESCRIPTION: #{error_description}
|
||||
ORIGINAL DATA: #{original_data_elements}
|
||||
"
|
||||
end
|
||||
def to_buffer
|
||||
super+[errorcode,error_description,0,original_data_elements].pack("nZ63CZ16")
|
||||
end
|
||||
|
||||
def self.create_error_response(original_request_buffer,errorcode,error_description)
|
||||
transaction_id=original_request_buffer[5,2].unpack("n")[0] #pull direct from data in case "message_from_buffer" was what raised the error
|
||||
self.new({:signature=>MESSAGE_SIGNATURE,:version=>VERSION_ID,:transaction_id=>transaction_id,
|
||||
:opcode=>OPCODE,:errorcode=>errorcode,:error_description=>error_description,:original_data_elements=>original_request_buffer[0,16]})
|
||||
end
|
||||
end
|
||||
end
|
75
server/lib/tndp_server.rb
Normal file
75
server/lib/tndp_server.rb
Normal file
@ -0,0 +1,75 @@
|
||||
#
|
||||
# message formats for Trivial Network Disk Protocol
|
||||
#
|
||||
$:.unshift(File.dirname(__FILE__)) unless
|
||||
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
||||
require 'tndp'
|
||||
require 'socket'
|
||||
require 'RipXplore'
|
||||
|
||||
class TNDPServer
|
||||
LISTENING_PORT=6502
|
||||
attr_reader :root_directory,:port ,:server_thread,:socket
|
||||
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
|
||||
@server_thread=Thread.start do
|
||||
@socket=UDPSocket.open
|
||||
@socket.bind("",port)
|
||||
log("serving #{root_directory} on UDP port #{port}")
|
||||
loop do
|
||||
data,addr_info=@socket.recvfrom(4096)
|
||||
client_port=addr_info[1]
|
||||
client_ip=addr_info[3]
|
||||
log "#{data.length} bytes received from #{client_ip}:#{client_port}"
|
||||
begin
|
||||
request=TNDP.message_from_buffer(data)
|
||||
log(request.to_s)
|
||||
case request.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)
|
||||
end
|
||||
response=TNDP::CapabilitiesResponseMessage.new({:supported_architectures=>supported_architectures})
|
||||
when TNDP::VolumeCatalogRequestMessage::OPCODE
|
||||
response=TNDP::VolumeCatalogResponseMessage.new()
|
||||
else
|
||||
response=TNDP::ErrorResponseMessage.create_error_response(data,TNDP::ErrorCodes::UNKNOWN_OPCODE,"unknown opcode 0x#{"%02X" % request.opcode}")
|
||||
end
|
||||
response.transaction_id=request.transaction_id
|
||||
rescue Exception=>e
|
||||
response=TNDP::ErrorResponseMessage.create_error_response(data,TNDP::ErrorCodes::INTERNAL_SERVER_ERROR,e.to_s)
|
||||
end
|
||||
log("Response:")
|
||||
log(response.to_s)
|
||||
@socket.send(response.to_buffer,0,client_ip,client_port)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def shutdown
|
||||
log("TNDP server on UDP port #{port} shutting down")
|
||||
@server_thread.kill
|
||||
|
||||
end
|
||||
end
|
41
server/test/tc_test_message_formats.rb
Normal file
41
server/test/tc_test_message_formats.rb
Normal file
@ -0,0 +1,41 @@
|
||||
lib_path=File.expand_path(File.dirname(__FILE__)+"//..//lib")
|
||||
$:.unshift(lib_path) unless $:.include?(lib_path)
|
||||
|
||||
require 'test/unit'
|
||||
require 'tndp'
|
||||
|
||||
class TestMessageFormats <Test::Unit::TestCase
|
||||
|
||||
def test_message_validation
|
||||
|
||||
assert_raise TNDP::BufferTooShort do
|
||||
TNDP.message_from_buffer("1234")
|
||||
end
|
||||
|
||||
assert_raise TNDP::InvalidSignature do
|
||||
TNDP.message_from_buffer("1234567")
|
||||
end
|
||||
|
||||
assert_raise TNDP::InvalidVersion do
|
||||
TNDP.message_from_buffer("TNDP567")
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
def test_message_round_trip
|
||||
|
||||
[TNDP::CapabilitiesRequestMessage,TNDP::CapabilitiesResponseMessage,TNDP::ErrorResponseMessage,TNDP::VolumeCatalogRequestMessage,TNDP::VolumeCatalogRequestMessage].each do |msg_type|
|
||||
created_msg=msg_type.new()
|
||||
assert_equal(msg_type::OPCODE,created_msg.opcode,"opcode for #{msg_type} should be 0x#{"%02x" % msg_type::OPCODE}")
|
||||
puts created_msg.to_s
|
||||
round_trip_msg=TNDP.message_from_buffer(created_msg.raw_bytes)
|
||||
|
||||
assert_equal(created_msg.raw_bytes,round_trip_msg.raw_bytes,"raw bytes should round-trip for #{msg_type}")
|
||||
assert_equal(created_msg.to_s,round_trip_msg.to_s,"to_s should be same for round-tripped #{msg_type}")
|
||||
assert_equal(created_msg.class,round_trip_msg.class,"class should round-trip for #{msg_type}")
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
39
server/test/tc_test_server.rb
Normal file
39
server/test/tc_test_server.rb
Normal file
@ -0,0 +1,39 @@
|
||||
lib_path=File.expand_path(File.dirname(__FILE__)+"//..//lib")
|
||||
$:.unshift(lib_path) unless $:.include?(lib_path)
|
||||
|
||||
require 'test/unit'
|
||||
require 'tndp_server'
|
||||
|
||||
def log(msg)
|
||||
puts msg
|
||||
end
|
||||
class TestServer <Test::Unit::TestCase
|
||||
|
||||
@@client_socket=UDPSocket.open()
|
||||
@@client_socket.connect("localhost",TNDPServer::LISTENING_PORT)
|
||||
|
||||
|
||||
def send_request_and_get_response(request)
|
||||
@@client_socket.send(request.to_buffer,0)
|
||||
if (select([@@client_socket],nil,nil,5)).nil? then
|
||||
raise "no response from server"
|
||||
end
|
||||
response_bytes=@@client_socket.recvfrom(4096)[0]
|
||||
response=TNDP.message_from_buffer(response_bytes)
|
||||
assert_equal(request.transaction_id,response.transaction_id,"transaction id from request should be echoed in response")
|
||||
response
|
||||
end
|
||||
def test_server
|
||||
Thread.abort_on_exception = true
|
||||
server=TNDPServer.new(File.dirname(__FILE__)+"/test_images")
|
||||
server.start
|
||||
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")
|
||||
|
||||
volume_catalog_response_msg=send_request_and_get_response(TNDP::VolumeCatalogRequestMessage.new())
|
||||
assert(capabilities_response_msg.respond_to?(:supported_architectures),"capabilities response message should include list of architectures supported by server")
|
||||
|
||||
server.shutdown
|
||||
end
|
||||
|
||||
end
|
6
server/test/ts_test_all.rb
Normal file
6
server/test/ts_test_all.rb
Normal file
@ -0,0 +1,6 @@
|
||||
$:.unshift(File.dirname(__FILE__)) unless
|
||||
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
||||
|
||||
Dir.glob("tc_*.rb").each do |tc|
|
||||
require tc
|
||||
end
|
Loading…
x
Reference in New Issue
Block a user