modem_protocols 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/modem_protocols.rb +322 -0
- data/test/test_xmodem.rb +241 -0
- metadata +56 -0
@@ -0,0 +1,322 @@
|
|
1
|
+
# Author:: Jonno Downes (jonno@jamtronix.com)
|
2
|
+
# License:: Mozilla Public License 1.1
|
3
|
+
|
4
|
+
|
5
|
+
require 'log4r'
|
6
|
+
include Log4r
|
7
|
+
|
8
|
+
module ModemProtocols
|
9
|
+
|
10
|
+
XMODEM_BLOCK_SIZE=128 #how many bytes (ecluding header & checksum) in each block?
|
11
|
+
XMODEM_MAX_TIMEOUTS=5 #how many timeouts in a row before the sender gives up?
|
12
|
+
XMODEM_MAX_ERRORS=10 #how many errors on a single block before the receiver gives up?
|
13
|
+
|
14
|
+
XMODEM_CRC_ATTEMPTS=3 #how many times should receiver attempt to use CRC?
|
15
|
+
LOG_NAME='ModemProtocols'
|
16
|
+
@timeout_seconds=5.0 #default timeout period
|
17
|
+
|
18
|
+
#how long does the protocol wait before giving up?
|
19
|
+
def ModemProtocols::timeout_seconds
|
20
|
+
@timeout_seconds
|
21
|
+
end
|
22
|
+
|
23
|
+
def ModemProtocols::timeout_seconds=(val)
|
24
|
+
@timeout_seconds=val
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
|
29
|
+
|
30
|
+
# receive a file using XMODEM protocol (block size = 128 bytes)
|
31
|
+
# remote:: must be an IO object connected to an XMODEM sender
|
32
|
+
# local:: must be an IO object - the inbound file (trimmed of any final padding) will be written to this
|
33
|
+
# options:: hash of options. options are: values :crc (use 16bit CRC instead of 8 bit checksum)
|
34
|
+
# - :mode=> :crc or :checksum (default is 8 bit checksum)
|
35
|
+
#
|
36
|
+
|
37
|
+
def ModemProtocols::xmodem_rx(remote,local,options=nil)
|
38
|
+
|
39
|
+
|
40
|
+
mode = ( (options.nil?) || options[:mode].nil? ) ? :checksum : options[:mode]
|
41
|
+
|
42
|
+
logger.debug "rx: XMODEM - #{mode}"
|
43
|
+
#flush the input buffer
|
44
|
+
loop do
|
45
|
+
break if (select([remote],nil,nil,0.01).nil?)
|
46
|
+
remote.getc
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
#trigger sending
|
51
|
+
case mode
|
52
|
+
when :crc
|
53
|
+
XMODEM_CRC_ATTEMPTS.times do |attempt|
|
54
|
+
remote.putc('C')
|
55
|
+
break unless (select([remote],nil,nil,timeout_seconds).nil?)
|
56
|
+
#if we don't get a response, fall back to checksum mode
|
57
|
+
if attempt==XMODEM_CRC_ATTEMPTS-1 then
|
58
|
+
logger.warn "rx: crc-16 request failed, falling back to checksum mode"
|
59
|
+
remote.putc(NAK)
|
60
|
+
mode=:checksum
|
61
|
+
end
|
62
|
+
end
|
63
|
+
else
|
64
|
+
remote.putc(NAK)
|
65
|
+
end
|
66
|
+
|
67
|
+
expected_block=1
|
68
|
+
error_count=0
|
69
|
+
last_block=""
|
70
|
+
data=""
|
71
|
+
loop do
|
72
|
+
|
73
|
+
begin
|
74
|
+
rx_cmd=xmodem_rx_getbyte(remote)
|
75
|
+
|
76
|
+
if rx_cmd==EOT then
|
77
|
+
remote.putc(ACK)
|
78
|
+
trimmed_block=last_block.sub(/(\x1A+)\Z/,'')
|
79
|
+
local<< trimmed_block#trim any trailing FILLER characters in last block
|
80
|
+
break
|
81
|
+
end
|
82
|
+
|
83
|
+
if rx_cmd!=SOH then
|
84
|
+
logger.warn "rx: expected SOH (0x#{SOH}) got 0x#{"%x" % rx_cmd} - pos = #{remote.pos}"
|
85
|
+
next
|
86
|
+
end
|
87
|
+
|
88
|
+
data=""
|
89
|
+
block=xmodem_rx_getbyte(remote)
|
90
|
+
block_check=xmodem_rx_getbyte(remote)
|
91
|
+
validity=:valid
|
92
|
+
validity=:invalid unless (block_check+block)==0xFF
|
93
|
+
|
94
|
+
logger.debug "rx: #{validity} block number 0x#{"%02x" % block} / block number check 0x#{"%02x" % block_check}"
|
95
|
+
logger.debug "rx: receiving block #{block} / expected block #{expected_block}"
|
96
|
+
raise RXSynchError if block!=expected_block && block!=((expected_block-1) % 0x100)
|
97
|
+
|
98
|
+
if (block==expected_block) && (validity==:valid) then
|
99
|
+
local<<last_block
|
100
|
+
last_block=""
|
101
|
+
end
|
102
|
+
XMODEM_BLOCK_SIZE.times do
|
103
|
+
b=(xmodem_rx_getbyte(remote))
|
104
|
+
data<<b
|
105
|
+
Thread.pass
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
check_ok=false
|
110
|
+
|
111
|
+
case mode
|
112
|
+
when :crc
|
113
|
+
rx_crc=(xmodem_rx_getbyte(remote)<<8)+xmodem_rx_getbyte(remote)
|
114
|
+
crc=ccitt16_crc(data)
|
115
|
+
check_ok=(crc==rx_crc)
|
116
|
+
if !check_ok then
|
117
|
+
logger.warn "invalid crc-16 for block #{block}: calculated 0x#{'%04x' % crc}, got 0x#{'%02x' % rx_crc}"
|
118
|
+
end
|
119
|
+
else
|
120
|
+
rx_checksum=xmodem_rx_getbyte(remote)
|
121
|
+
checksum=xmodem_checksum(data)
|
122
|
+
check_ok=(checksum==rx_checksum)
|
123
|
+
if !check_ok then
|
124
|
+
logger.warn "invalid checksum for block #{block}: calculated 0x#{'%02x' % checksum}, got 0x#{'%02x' % rx_checksum}"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
if (check_ok) then
|
129
|
+
last_block=data
|
130
|
+
logger.debug "rx: #{mode} test passed for block #{block}"
|
131
|
+
remote.putc(ACK)
|
132
|
+
if (block==expected_block) then
|
133
|
+
expected_block=((expected_block+1) % 0x100)
|
134
|
+
error_count=0 #reset the error count
|
135
|
+
end
|
136
|
+
else
|
137
|
+
remote.putc(NAK)
|
138
|
+
error_count+=1
|
139
|
+
logger.warn "rx: checksum error # #{error_count} / max #{XMODEM_MAX_ERRORS}"
|
140
|
+
raise RXChecksumError.new("too many receive errors on block #{block}") if error_count>XMODEM_MAX_ERRORS
|
141
|
+
end
|
142
|
+
rescue RXTimeout
|
143
|
+
error_count+=1
|
144
|
+
logger.warn "rx: timeout error # #{error_count} / max #{XMODEM_MAX_ERRORS}"
|
145
|
+
raise RXTimeout("too many receive errors on block #{block}").new if error_count>XMODEM_MAX_ERRORS
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
logger.info "receive complete"
|
150
|
+
end
|
151
|
+
|
152
|
+
# send a file using standard XMODEM protocol (block size = 128 bytes)
|
153
|
+
# will use CRC mode if requested by sender, else use 8-bit checksum
|
154
|
+
# remote:: must be an IO object connected to an XMODEM receiver
|
155
|
+
# local:: must be an IO object containing the data to be sent
|
156
|
+
def ModemProtocols::xmodem_tx(remote,local)
|
157
|
+
block_number=1
|
158
|
+
current_block=""
|
159
|
+
sent_eot=false
|
160
|
+
|
161
|
+
XMODEM_BLOCK_SIZE.times do
|
162
|
+
b=(local.eof? ? FILLER : local.getc)
|
163
|
+
current_block<<b.chr
|
164
|
+
Thread.pass
|
165
|
+
end
|
166
|
+
checksum=xmodem_checksum(current_block)
|
167
|
+
mode=:checksum
|
168
|
+
loop do
|
169
|
+
logger.info "tx: waiting for ACK/NAK/CAN (eot_sent: #{sent_eot})"
|
170
|
+
if select([remote],nil,nil,timeout_seconds*XMODEM_MAX_TIMEOUTS).nil? then
|
171
|
+
raise RXTimeout.new("timeout waiting for input on tx (#{timeout_seconds*XMODEM_MAX_TIMEOUTS} seconds)") unless sent_eot
|
172
|
+
logger.info "tx: timeout waiting for ACK of EOT"
|
173
|
+
return
|
174
|
+
end
|
175
|
+
if remote.eof? then
|
176
|
+
logger.warn "tx: unexpected eof on input"
|
177
|
+
break
|
178
|
+
end
|
179
|
+
tx_cmd=remote.getc
|
180
|
+
logger.debug "tx: got 0x#{"%x" % tx_cmd}"
|
181
|
+
if tx_cmd==ACK then
|
182
|
+
if sent_eot then
|
183
|
+
logger.debug "tx: got ACK of EOT"
|
184
|
+
break
|
185
|
+
end
|
186
|
+
|
187
|
+
if local.eof? then
|
188
|
+
remote.putc(EOT)
|
189
|
+
logger.debug "tx: got ACK of last block"
|
190
|
+
sent_eot=true
|
191
|
+
next
|
192
|
+
end
|
193
|
+
block_number=((block_number+1)%0x100)
|
194
|
+
current_block=""
|
195
|
+
XMODEM_BLOCK_SIZE.times do
|
196
|
+
b=(local.eof? ? FILLER : local.getc)
|
197
|
+
current_block<<b
|
198
|
+
Thread.pass
|
199
|
+
end
|
200
|
+
|
201
|
+
elsif (block_number==1) && (tx_cmd==CRC_MODE) then
|
202
|
+
mode=:crc
|
203
|
+
logger.debug "tx: using crc-16 mode"
|
204
|
+
end
|
205
|
+
|
206
|
+
next unless [ACK,NAK,CRC_MODE].include?(tx_cmd)
|
207
|
+
logger.info "tx: sending block #{block_number}"
|
208
|
+
remote.putc(SOH) #start of block
|
209
|
+
remote.putc(block_number) #block number
|
210
|
+
remote.putc(0xff-block_number) #1's complement of block number
|
211
|
+
current_block.each_byte {|b| remote.putc(b)}
|
212
|
+
case mode
|
213
|
+
when :crc then
|
214
|
+
crc=ccitt16_crc(current_block)
|
215
|
+
remote.putc(crc>>8) #crc hi byte
|
216
|
+
remote.putc(crc & 0xFF) #crc lo byte
|
217
|
+
logger.debug "tx: crc-16 for block #{block_number}:#{ "%04x" % crc}"
|
218
|
+
else
|
219
|
+
checksum=xmodem_checksum(current_block)
|
220
|
+
remote.putc(checksum)
|
221
|
+
logger.debug "tx: checksum for block #{block_number}:#{ "%02x" % checksum}"
|
222
|
+
end
|
223
|
+
|
224
|
+
end
|
225
|
+
logger.info "transmit complete (eot_sent: #{sent_eot})"
|
226
|
+
end
|
227
|
+
|
228
|
+
#calculate an 8-bit XMODEM checksum
|
229
|
+
#this is just the sum of all bytes modulo 0x100
|
230
|
+
def ModemProtocols::xmodem_checksum(block)
|
231
|
+
raise RXChecksumError("checksum requested of invalid block {size should be #{XMODEM_BLOCK_SIZE}, was #{block.length}") unless block.length==XMODEM_BLOCK_SIZE
|
232
|
+
checksum=0
|
233
|
+
block.each_byte do |b|
|
234
|
+
checksum=(checksum+b)%0x100
|
235
|
+
end
|
236
|
+
checksum
|
237
|
+
end
|
238
|
+
|
239
|
+
#calculate a 16-bit CRC
|
240
|
+
def ModemProtocols::ccitt16_crc(block)
|
241
|
+
#cribbed from http://www.hadermann.be/blog/32/ruby-crc16-implementation/
|
242
|
+
raise RXChecksumError("checksum requested of invalid block {size should be #{XMODEM_BLOCK_SIZE}, was #{block.length}") unless block.length==XMODEM_BLOCK_SIZE
|
243
|
+
crc=0
|
244
|
+
block.each_byte{|x| crc = ((crc<<8) ^ CCITT_16[(crc>>8) ^ x])&0xffff}
|
245
|
+
crc
|
246
|
+
end
|
247
|
+
|
248
|
+
private
|
249
|
+
|
250
|
+
SOH = 0x01
|
251
|
+
STX = 0x02
|
252
|
+
EOT = 0x04
|
253
|
+
ACK = 0x06
|
254
|
+
NAK = 0x15
|
255
|
+
CAN = 0x18
|
256
|
+
CRC_MODE=0x43 #'C'
|
257
|
+
FILLER = 0x1A
|
258
|
+
|
259
|
+
|
260
|
+
CCITT_16 = [
|
261
|
+
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
|
262
|
+
0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
|
263
|
+
0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
|
264
|
+
0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
|
265
|
+
0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
|
266
|
+
0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
|
267
|
+
0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
|
268
|
+
0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
|
269
|
+
0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
|
270
|
+
0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
|
271
|
+
0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
|
272
|
+
0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
|
273
|
+
0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
|
274
|
+
0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
|
275
|
+
0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
|
276
|
+
0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
|
277
|
+
0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
|
278
|
+
0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
|
279
|
+
0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
|
280
|
+
0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
|
281
|
+
0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
|
282
|
+
0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
|
283
|
+
0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
|
284
|
+
0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
|
285
|
+
0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
|
286
|
+
0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
|
287
|
+
0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
|
288
|
+
0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
|
289
|
+
0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
|
290
|
+
0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
|
291
|
+
0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
|
292
|
+
0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
|
293
|
+
]
|
294
|
+
|
295
|
+
|
296
|
+
|
297
|
+
def ModemProtocols::xmodem_rx_getbyte(remote)
|
298
|
+
|
299
|
+
if (select([remote],nil,nil,timeout_seconds).nil?) then
|
300
|
+
remote.putc(NAK)
|
301
|
+
raise RXTimeout
|
302
|
+
end
|
303
|
+
raise RXSynchError if remote.eof?
|
304
|
+
remote.getc
|
305
|
+
end
|
306
|
+
|
307
|
+
|
308
|
+
def ModemProtocols::logger
|
309
|
+
Logger.new(LOG_NAME) if Logger[LOG_NAME].nil?
|
310
|
+
Logger[LOG_NAME]
|
311
|
+
end
|
312
|
+
|
313
|
+
class RXTimeout < RuntimeError
|
314
|
+
end
|
315
|
+
|
316
|
+
class RXChecksumError < RuntimeError
|
317
|
+
end
|
318
|
+
|
319
|
+
class RXSynchError < RuntimeError
|
320
|
+
end
|
321
|
+
|
322
|
+
end
|
data/test/test_xmodem.rb
ADDED
@@ -0,0 +1,241 @@
|
|
1
|
+
|
2
|
+
#make sure the relevant folder with our libraries is in the require path
|
3
|
+
lib_path=File.expand_path(File.dirname(__FILE__)+"//..//lib")
|
4
|
+
$:.unshift(lib_path) unless $:.include?(lib_path)
|
5
|
+
|
6
|
+
|
7
|
+
require 'modem_protocols'
|
8
|
+
require 'test/unit'
|
9
|
+
require 'socket'
|
10
|
+
|
11
|
+
|
12
|
+
Thread.abort_on_exception = true
|
13
|
+
|
14
|
+
|
15
|
+
ModemProtocols::logger.outputters = Outputter.stdout
|
16
|
+
|
17
|
+
|
18
|
+
ModemProtocols::timeout_seconds=0.4 #so we don't wait so long for retransmissions
|
19
|
+
LOCAL_PORT=9999
|
20
|
+
|
21
|
+
|
22
|
+
module CorruptIn
|
23
|
+
attr_accessor :real_getc,:corruption_frequency
|
24
|
+
|
25
|
+
def getc
|
26
|
+
@char_count=0 if @char_count.nil?
|
27
|
+
|
28
|
+
raise "real_getc not initialised" if @real_getc.nil?
|
29
|
+
b=@real_getc.call
|
30
|
+
@char_count+=1
|
31
|
+
if ((@char_count % @corruption_frequency)==0) then
|
32
|
+
corrupted_char=0xff-b
|
33
|
+
$stdout.puts "corrupting : 0x%02x -> 0x%02x" % [b,corrupted_char]
|
34
|
+
b=corrupted_char
|
35
|
+
end
|
36
|
+
return b
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
module CorruptOut
|
42
|
+
attr_accessor :real_putc,:corruption_frequency
|
43
|
+
|
44
|
+
def putc (b)
|
45
|
+
@char_count=0 if @char_count.nil?
|
46
|
+
|
47
|
+
raise "real_putc not initialised" if @real_putc.nil?
|
48
|
+
@char_count+=1
|
49
|
+
if ((@char_count % @corruption_frequency)==0) then
|
50
|
+
corrupted_char=0xff-b
|
51
|
+
$stdout.puts "corrupting : 0x%02x -> 0x%02x" % [b,corrupted_char]
|
52
|
+
b=corrupted_char
|
53
|
+
end
|
54
|
+
@real_putc.call(b)
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
module DropIn
|
61
|
+
attr_accessor :real_getc,:drop_frequency
|
62
|
+
|
63
|
+
def getc
|
64
|
+
@char_count=0 if @char_count.nil?
|
65
|
+
|
66
|
+
raise "real_getc not initialised" if @real_getc.nil?
|
67
|
+
b=@real_getc.call
|
68
|
+
@char_count+=1
|
69
|
+
if ((@char_count % @drop_frequency)==0) then
|
70
|
+
$stdout.puts "dropping : 0x%02x " % [b]
|
71
|
+
@real_getc.call
|
72
|
+
end
|
73
|
+
return b
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
module DropOut
|
78
|
+
attr_accessor :real_putc,:drop_frequency
|
79
|
+
|
80
|
+
def putc (b)
|
81
|
+
@char_count=0 if @char_count.nil?
|
82
|
+
|
83
|
+
raise "real_putc not initialised" if @real_putc.nil?
|
84
|
+
@char_count+=1
|
85
|
+
if ((@char_count % @drop_frequency)==0) then
|
86
|
+
$stdout.puts "dropping : 0x%02x " % [b]
|
87
|
+
else
|
88
|
+
@real_putc.call(b)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
class XmodemTests< Test::Unit::TestCase
|
95
|
+
|
96
|
+
|
97
|
+
def test_checksum
|
98
|
+
assert_equal(0,ModemProtocols::xmodem_checksum( "\000"*128))
|
99
|
+
assert_equal(128,ModemProtocols::xmodem_checksum("\001"*128))
|
100
|
+
assert_equal(0,ModemProtocols::xmodem_checksum("\002"*128))
|
101
|
+
assert_equal(128,ModemProtocols::xmodem_checksum("\003"*128))
|
102
|
+
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
@@server=nil
|
107
|
+
def sendfile(file)
|
108
|
+
if @@server.nil? then
|
109
|
+
@@server = TCPServer.new('localhost', LOCAL_PORT)
|
110
|
+
else
|
111
|
+
puts "reusing existing server"
|
112
|
+
end
|
113
|
+
session = @@server.accept
|
114
|
+
session.sync=true
|
115
|
+
puts "Connected (sendfile)"
|
116
|
+
ModemProtocols::xmodem_tx(session,file)
|
117
|
+
session.close
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
def acceptfile(file,error_type=nil,error_frequency=nil,rx_options=nil)
|
122
|
+
socket=TCPSocket.new('localhost', LOCAL_PORT)
|
123
|
+
socket.sync=true
|
124
|
+
puts "Connected (acceptfile)"
|
125
|
+
if !error_frequency.nil? then
|
126
|
+
real_getc=socket.method(:getc)
|
127
|
+
|
128
|
+
case error_type
|
129
|
+
when :corruption_in
|
130
|
+
real_getc=socket.method(:getc)
|
131
|
+
socket.extend(CorruptIn)
|
132
|
+
socket.corruption_frequency=error_frequency
|
133
|
+
socket.real_getc=real_getc
|
134
|
+
when :packet_loss_in
|
135
|
+
real_getc=socket.method(:getc)
|
136
|
+
socket.extend(DropIn)
|
137
|
+
socket.drop_frequency=error_frequency
|
138
|
+
socket.real_getc=real_getc
|
139
|
+
when :packet_loss_out
|
140
|
+
real_putc=socket.method(:putc)
|
141
|
+
socket.extend(DropOut)
|
142
|
+
socket.drop_frequency=error_frequency
|
143
|
+
socket.real_putc=real_putc
|
144
|
+
when :corruption_out
|
145
|
+
real_putc=socket.method(:putc)
|
146
|
+
socket.extend(CorruptOut)
|
147
|
+
socket.corruption_frequency=error_frequency
|
148
|
+
socket.real_putc=real_putc
|
149
|
+
else
|
150
|
+
raise "unknown error_type #{error_type}"
|
151
|
+
end
|
152
|
+
file.flush
|
153
|
+
end
|
154
|
+
ModemProtocols::xmodem_rx(socket,file,rx_options)
|
155
|
+
loop {Thread.pass} until socket.closed?
|
156
|
+
|
157
|
+
end
|
158
|
+
|
159
|
+
def do_test(tx_file,rx_file,error_type=nil,error_frequency=nil,rx_options=nil)
|
160
|
+
test_description="test type: #{error_type}"
|
161
|
+
test_description+=" (freq=#{error_frequency})" unless error_frequency.nil?
|
162
|
+
made_tx=false
|
163
|
+
made_rx=false
|
164
|
+
if !(tx_file.respond_to?(:getc))
|
165
|
+
tx_filename=tx_file
|
166
|
+
tx_file=File.new(tx_filename,"rb")
|
167
|
+
else
|
168
|
+
tx_filename=tx_file.class
|
169
|
+
tx_file.rewind
|
170
|
+
end
|
171
|
+
|
172
|
+
|
173
|
+
if !(rx_file.respond_to?(:putc))
|
174
|
+
rx_filename=rx_file
|
175
|
+
rx_file=File.new(rx_filename,"wb+")
|
176
|
+
else
|
177
|
+
rx_filename=rx_file.class
|
178
|
+
rx_file.rewind
|
179
|
+
end
|
180
|
+
puts "#{test_description} : #{tx_filename}->#{rx_filename}"
|
181
|
+
|
182
|
+
|
183
|
+
tx_thread=Thread.new {sendfile(tx_file)}
|
184
|
+
rx_thread=Thread.new {acceptfile(rx_file,error_type,error_frequency,rx_options)}
|
185
|
+
loop do
|
186
|
+
break unless tx_thread.alive?
|
187
|
+
break unless rx_thread.alive?
|
188
|
+
sleep(0.01) #wake up occasionally to get keyboard input, so we break on ^C
|
189
|
+
end
|
190
|
+
tx_file.rewind
|
191
|
+
rx_file.rewind
|
192
|
+
rx_filecontents=rx_file.read
|
193
|
+
tx_filecontents=tx_file.read
|
194
|
+
assert_equal(tx_filecontents.length,rx_filecontents.length,"file length correct after round trip")
|
195
|
+
assert_equal(tx_filecontents,rx_filecontents,"file contents correct after round trip")
|
196
|
+
|
197
|
+
tx_file.close if made_tx
|
198
|
+
rx_file.close if made_rx
|
199
|
+
|
200
|
+
end
|
201
|
+
|
202
|
+
def test_all
|
203
|
+
sample_text_file="sample.test.txt"
|
204
|
+
sample_bin_file="sample.test.bin"
|
205
|
+
f=File.new(sample_text_file,"w")
|
206
|
+
f<<File.new(__FILE__,"r").read
|
207
|
+
f.close
|
208
|
+
|
209
|
+
f=File.new(sample_bin_file,"wb")
|
210
|
+
2000.times {|i| f<<(i%0x100).chr}
|
211
|
+
f.close
|
212
|
+
|
213
|
+
txstring_io=StringIO.new("this is a test string")
|
214
|
+
rxstring_io=StringIO.new("")
|
215
|
+
|
216
|
+
do_test(sample_text_file,"crc-simple.test.txt",nil,nil,{:mode=>:crc})
|
217
|
+
do_test(txstring_io,rxstring_io)
|
218
|
+
do_test(sample_text_file,"corrupt-crc.test.txt",:corruption_in,700,{:mode=>:crc})
|
219
|
+
|
220
|
+
|
221
|
+
do_test(txstring_io,rxstring_io)
|
222
|
+
do_test(sample_text_file,rxstring_io)
|
223
|
+
do_test(sample_text_file,"simple.test.txt")
|
224
|
+
do_test(sample_bin_file,"corrupt_out.test.bin",:corruption_out,4)
|
225
|
+
do_test(sample_text_file,"packet_loss_out.test.txt",:packet_loss_out,3)
|
226
|
+
do_test(sample_text_file,"packet_loss_in.test.txt",:packet_loss_in,200)
|
227
|
+
|
228
|
+
|
229
|
+
do_test(sample_text_file,"corrupt.test.txt",:corruption_in,700)
|
230
|
+
do_test(sample_text_file,"very_corrupt.test.txt",:corruption_in,200)
|
231
|
+
|
232
|
+
do_test(sample_bin_file,"simple.test.bin")
|
233
|
+
do_test(sample_bin_file,"corrupt.test.bin",:corruption_in,700)
|
234
|
+
|
235
|
+
bigstring=""
|
236
|
+
512.times {|i| bigstring<<((i%0x100).chr*128)}
|
237
|
+
do_test(StringIO.new(bigstring),"bigfile.test.txt")
|
238
|
+
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
metadata
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: modem_protocols
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jonno Downes
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-11-29 00:00:00 +11:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: jonno@jamtronix.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- lib/modem_protocols.rb
|
26
|
+
- test/test_xmodem.rb
|
27
|
+
has_rdoc: true
|
28
|
+
homepage: http://petboardstuff.rubyforge.org
|
29
|
+
licenses: []
|
30
|
+
|
31
|
+
post_install_message:
|
32
|
+
rdoc_options: []
|
33
|
+
|
34
|
+
require_paths:
|
35
|
+
- lib
|
36
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: "0"
|
41
|
+
version:
|
42
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: "0"
|
47
|
+
version:
|
48
|
+
requirements: []
|
49
|
+
|
50
|
+
rubyforge_project:
|
51
|
+
rubygems_version: 1.3.5
|
52
|
+
signing_key:
|
53
|
+
specification_version: 3
|
54
|
+
summary: a pure ruby implementation of XMODEM
|
55
|
+
test_files: []
|
56
|
+
|