Module: ModemProtocols
- Defined in:
- lib/modem_protocols.rb
Defined Under Namespace
Classes: RXChecksumError, RXSynchError, RXTimeout
Constant Summary collapse
- XMODEM_BLOCK_SIZE =
how many bytes (ecluding header & checksum) in each block?
128
- XMODEM_MAX_TIMEOUTS =
how many timeouts in a row before the sender gives up?
5
- XMODEM_MAX_ERRORS =
how many errors on a single block before the receiver gives up?
10
- XMODEM_CRC_ATTEMPTS =
how many times should receiver attempt to use CRC?
3
- LOG_NAME =
'ModemProtocols'
Class Method Summary collapse
-
.ccitt16_crc(block) ⇒ Object
calculate a 16-bit CRC.
-
.timeout_seconds ⇒ Object
how long does the protocol wait before giving up?.
- .timeout_seconds=(val) ⇒ Object
-
.xmodem_checksum(block) ⇒ Object
calculate an 8-bit XMODEM checksum this is just the sum of all bytes modulo 0x100.
-
.xmodem_rx(remote, local, options = nil) ⇒ Object
- receive a file using XMODEM protocol (block size = 128 bytes) remote
- must be an IO object connected to an XMODEM sender local
- must be an IO object - the inbound file (trimmed of any final padding) will be written to this options
-
hash of options.
-
.xmodem_tx(remote, local) ⇒ Object
- send a file using standard XMODEM protocol (block size = 128 bytes) will use CRC mode if requested by sender, else use 8-bit checksum remote
- must be an IO object connected to an XMODEM receiver local
-
must be an IO object containing the data to be sent.
Class Method Details
.ccitt16_crc(block) ⇒ Object
calculate a 16-bit CRC
240 241 242 243 244 245 246 |
# File 'lib/modem_protocols.rb', line 240 def ModemProtocols::ccitt16_crc(block) #cribbed from http://www.hadermann.be/blog/32/ruby-crc16-implementation/ raise RXChecksumError("checksum requested of invalid block {size should be #{XMODEM_BLOCK_SIZE}, was #{block.length}") unless block.length==XMODEM_BLOCK_SIZE crc=0 block.each_byte{|x| crc = ((crc<<8) ^ CCITT_16[(crc>>8) ^ x])&0xffff} crc end |
.timeout_seconds ⇒ Object
how long does the protocol wait before giving up?
19 20 21 |
# File 'lib/modem_protocols.rb', line 19 def ModemProtocols::timeout_seconds @timeout_seconds end |
.timeout_seconds=(val) ⇒ Object
23 24 25 |
# File 'lib/modem_protocols.rb', line 23 def ModemProtocols::timeout_seconds=(val) @timeout_seconds=val end |
.xmodem_checksum(block) ⇒ Object
calculate an 8-bit XMODEM checksum this is just the sum of all bytes modulo 0x100
230 231 232 233 234 235 236 237 |
# File 'lib/modem_protocols.rb', line 230 def ModemProtocols::xmodem_checksum(block) raise RXChecksumError("checksum requested of invalid block {size should be #{XMODEM_BLOCK_SIZE}, was #{block.length}") unless block.length==XMODEM_BLOCK_SIZE checksum=0 block.each_byte do |b| checksum=(checksum+b)%0x100 end checksum end |
.xmodem_rx(remote, local, options = nil) ⇒ Object
receive a file using XMODEM protocol (block size = 128 bytes)
- remote
-
must be an IO object connected to an XMODEM sender
- local
-
must be an IO object - the inbound file (trimmed of any final padding) will be written to this
- options
-
hash of options. options are: values :crc (use 16bit CRC instead of 8 bit checksum)
-
:mode=> :crc or :checksum (default is 8 bit checksum)
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/modem_protocols.rb', line 37 def ModemProtocols::xmodem_rx(remote,local,=nil) mode = ( (.nil?) || [:mode].nil? ) ? :checksum : [:mode] logger.debug "rx: XMODEM - #{mode}" #flush the input buffer loop do break if (select([remote],nil,nil,0.01).nil?) remote.getc end #trigger sending case mode when :crc XMODEM_CRC_ATTEMPTS.times do |attempt| remote.putc('C') break unless (select([remote],nil,nil,timeout_seconds).nil?) #if we don't get a response, fall back to checksum mode if attempt==XMODEM_CRC_ATTEMPTS-1 then logger.warn "rx: crc-16 request failed, falling back to checksum mode" remote.putc(NAK) mode=:checksum end end else remote.putc(NAK) end expected_block=1 error_count=0 last_block="" data="" loop do begin rx_cmd=xmodem_rx_getbyte(remote) if rx_cmd==EOT then remote.putc(ACK) trimmed_block=last_block.sub(/(\x1A+)\Z/,'') local<< trimmed_block#trim any trailing FILLER characters in last block break end if rx_cmd!=SOH then logger.warn "rx: expected SOH (0x#{SOH}) got 0x#{"%x" % rx_cmd} - pos = #{remote.pos}" next end data="" block=xmodem_rx_getbyte(remote) block_check=xmodem_rx_getbyte(remote) validity=:valid validity=:invalid unless (block_check+block)==0xFF logger.debug "rx: #{validity} block number 0x#{"%02x" % block} / block number check 0x#{"%02x" % block_check}" logger.debug "rx: receiving block #{block} / expected block #{expected_block}" raise RXSynchError if block!=expected_block && block!=((expected_block-1) % 0x100) if (block==expected_block) && (validity==:valid) then local<<last_block last_block="" end XMODEM_BLOCK_SIZE.times do b=(xmodem_rx_getbyte(remote)) data<<b Thread.pass end check_ok=false case mode when :crc rx_crc=(xmodem_rx_getbyte(remote)<<8)+xmodem_rx_getbyte(remote) crc=ccitt16_crc(data) check_ok=(crc==rx_crc) if !check_ok then logger.warn "invalid crc-16 for block #{block}: calculated 0x#{'%04x' % crc}, got 0x#{'%02x' % rx_crc}" end else rx_checksum=xmodem_rx_getbyte(remote) checksum=xmodem_checksum(data) check_ok=(checksum==rx_checksum) if !check_ok then logger.warn "invalid checksum for block #{block}: calculated 0x#{'%02x' % checksum}, got 0x#{'%02x' % rx_checksum}" end end if (check_ok) then last_block=data logger.debug "rx: #{mode} test passed for block #{block}" remote.putc(ACK) if (block==expected_block) then expected_block=((expected_block+1) % 0x100) error_count=0 #reset the error count end else remote.putc(NAK) error_count+=1 logger.warn "rx: checksum error # #{error_count} / max #{XMODEM_MAX_ERRORS}" raise RXChecksumError.new("too many receive errors on block #{block}") if error_count>XMODEM_MAX_ERRORS end rescue RXTimeout error_count+=1 logger.warn "rx: timeout error # #{error_count} / max #{XMODEM_MAX_ERRORS}" raise RXTimeout("too many receive errors on block #{block}").new if error_count>XMODEM_MAX_ERRORS end end logger.info "receive complete" end |
.xmodem_tx(remote, local) ⇒ Object
send a file using standard XMODEM protocol (block size = 128 bytes) will use CRC mode if requested by sender, else use 8-bit checksum
- remote
-
must be an IO object connected to an XMODEM receiver
- local
-
must be an IO object containing the data to be sent
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 |
# File 'lib/modem_protocols.rb', line 156 def ModemProtocols::xmodem_tx(remote,local) block_number=1 current_block="" sent_eot=false XMODEM_BLOCK_SIZE.times do b=(local.eof? ? FILLER : local.getc) current_block<<b.chr Thread.pass end checksum=xmodem_checksum(current_block) mode=:checksum loop do logger.info "tx: waiting for ACK/NAK/CAN (eot_sent: #{sent_eot})" if select([remote],nil,nil,timeout_seconds*XMODEM_MAX_TIMEOUTS).nil? then raise RXTimeout.new("timeout waiting for input on tx (#{timeout_seconds*XMODEM_MAX_TIMEOUTS} seconds)") unless sent_eot logger.info "tx: timeout waiting for ACK of EOT" return end if remote.eof? then logger.warn "tx: unexpected eof on input" break end tx_cmd=remote.getc logger.debug "tx: got 0x#{"%x" % tx_cmd}" if tx_cmd==ACK then if sent_eot then logger.debug "tx: got ACK of EOT" break end if local.eof? then remote.putc(EOT) logger.debug "tx: got ACK of last block" sent_eot=true next end block_number=((block_number+1)%0x100) current_block="" XMODEM_BLOCK_SIZE.times do b=(local.eof? ? FILLER : local.getc) current_block<<b Thread.pass end elsif (block_number==1) && (tx_cmd==CRC_MODE) then mode=:crc logger.debug "tx: using crc-16 mode" end next unless [ACK,NAK,CRC_MODE].include?(tx_cmd) logger.info "tx: sending block #{block_number}" remote.putc(SOH) #start of block remote.putc(block_number) #block number remote.putc(0xff-block_number) #1's complement of block number current_block.each_byte {|b| remote.putc(b)} case mode when :crc then crc=ccitt16_crc(current_block) remote.putc(crc>>8) #crc hi byte remote.putc(crc & 0xFF) #crc lo byte logger.debug "tx: crc-16 for block #{block_number}:#{ "%04x" % crc}" else checksum=xmodem_checksum(current_block) remote.putc(checksum) logger.debug "tx: checksum for block #{block_number}:#{ "%02x" % checksum}" end end logger.info "transmit complete (eot_sent: #{sent_eot})" end |