Module: XMODEM

Defined in:
lib/xmodem.rb,
lib/xmodem/version.rb

Defined Under Namespace

Classes: RXChecksumError, RXSynchError, RXTimeout

Constant Summary collapse

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 =
'XModem'
VERSION =
"0.1.1"

Class Method Summary collapse

Class Method Details

.block_sizeObject

how long does the protocol wait before giving up?



30
31
32
# File 'lib/xmodem.rb', line 30

def XMODEM::block_size
  @block_size
end

.block_size=(val) ⇒ Object



34
35
36
# File 'lib/xmodem.rb', line 34

def XMODEM::block_size=(val)
  @block_size = val
end

.ccitt16_crc(block) ⇒ Object

calculate a 16-bit CRC

Raises:



245
246
247
248
249
250
251
# File 'lib/xmodem.rb', line 245

def XMODEM::ccitt16_crc(block)
  # cribbed from http://www.hadermann.be/blog/32/ruby-crc16-implementation/
  raise RXChecksumError.new("checksum requested of invalid block {size should be #{block_size}, was #{block.length}") unless block.length==block_size
  crc=0
  block.each_byte{|x| crc = ((crc<<8) ^ CCITT_16[(crc>>8) ^ x])&0xffff}
  crc
end

.checksum(block) ⇒ Object

calculate an 8-bit XMODEM checksum this is just the sum of all bytes modulo 0x100

Raises:



235
236
237
238
239
240
241
242
# File 'lib/xmodem.rb', line 235

def XMODEM::checksum(block)
  raise RXChecksumError.new("checksum requested of invalid block {size should be #{block_size}, was #{block.length}") unless block.length==block_size
  checksum=0
  block.each_byte do |b|
    checksum = (checksum+b) % 0x100
  end
  checksum
end

.receive(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)



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
151
152
153
154
155
# File 'lib/xmodem.rb', line 44

def XMODEM::receive(remote,local,options=nil)

  mode = ( (options.nil?) || options[:mode].nil? ) ? :checksum : options[:mode]

  logger.debug "receiver: 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 "receiver: 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 = receive_getbyte(remote).ord

      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 "receiver: expected SOH (0x#{SOH}) got 0x#{"%x" % rx_cmd}  - pos = #{remote.pos}"
        next
      end

      data=""
      block= receive_getbyte(remote).ord
      block_check = receive_getbyte(remote).ord
      validity = :valid
      validity = :invalid unless (block_check + block)==0xFF

      logger.debug "receiver: #{validity} block number 0x#{"%02x" % block} / block number check 0x#{"%02x" % block_check}"
      logger.debug "receiver: 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
      block_size.times do
        b=(receive_getbyte(remote))
        data<<b
        Thread.pass
      end

      check_ok=false

      case mode
        when :crc
          rx_crc = ( receive_getbyte(remote).ord<<8) + receive_getbyte(remote).ord
          crc = ccitt16_crc(data)
          check_ok = (crc==rx_crc)
          if !check_ok then
            logger.warn "receiver: invalid crc-16 for block #{block}: calculated 0x#{'%04x' % crc}, got 0x#{'%02x' %  rx_crc}"
          end
        else
          rx_checksum = receive_getbyte(remote).ord
          checksum = XMODEM::checksum(data)
          check_ok = (checksum == rx_checksum)
          if !check_ok then
            logger.warn "receiver: 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 "receiver: #{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 "receiver: 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 "receiver: 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

.send(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



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
227
228
229
230
231
# File 'lib/xmodem.rb', line 161

def XMODEM::send(remote,local)
  block_number=1
  current_block=""
  sent_eot=false

  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 "sender: 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 "sender: timeout waiting for ACK of EOT"
      return
    end
    if remote.eof? then
      logger.warn "sender: unexpected eof on input"
      break
    end
    tx_cmd=remote.getc.ord
    logger.debug "sender: got 0x#{"%x" % tx_cmd}"
    if tx_cmd==ACK then
      if sent_eot then
        logger.debug "sender: got ACK of EOT"
        break
      end

      if local.eof? then
        remote.putc(EOT)
        logger.debug "sender: got ACK of last block"
        sent_eot=true
        next
      end
      block_number=((block_number+1)%0x100)
      current_block=""
      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 "sender: using crc-16 mode"
    end

    next unless [ACK,NAK,CRC_MODE].include?(tx_cmd.ord)
    logger.info "sender: 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 "sender: crc-16 for block #{block_number}:#{ "%04x" % crc}"
      else
        checksum = XMODEM::checksum(current_block)
        remote.putc(checksum)
        logger.debug "sender: checksum for block #{block_number}:#{ "%02x" % checksum}"
    end

  end
  logger.info "sending complete (eot_sent: #{sent_eot})"
end

.timeout_secondsObject

how long does the protocol wait before giving up?



21
22
23
# File 'lib/xmodem.rb', line 21

def XMODEM::timeout_seconds
  @timeout_seconds
end

.timeout_seconds=(val) ⇒ Object



25
26
27
# File 'lib/xmodem.rb', line 25

def XMODEM::timeout_seconds=(val)
  @timeout_seconds = val
end