Module: BTC::WireFormat

Extended by:
WireFormat
Included in:
WireFormat
Defined in:
lib/btcruby/wire_format.rb

Constant Summary collapse

PACK_FORMAT_UINT8 =
"C".freeze
PACK_FORMAT_INT8 =
"c".freeze
PACK_FORMAT_UINT16LE =
"S<".freeze
PACK_FORMAT_INT16LE =
"s<".freeze
PACK_FORMAT_UINT32LE =
"L<".freeze
PACK_FORMAT_INT32LE =
"l<".freeze
PACK_FORMAT_UINT32BE =

used in BIP32

"L>".freeze
PACK_FORMAT_INT32BE =
"l>".freeze
PACK_FORMAT_UINT64LE =
"Q<".freeze
PACK_FORMAT_INT64LE =
"q<".freeze

Instance Method Summary collapse

Instance Method Details

#encode_array(array, &block) ⇒ Object



199
200
201
# File 'lib/btcruby/wire_format.rb', line 199

def encode_array(array, &block)
  write_array(array, &block)
end

#encode_int16le(int) ⇒ Object



347
# File 'lib/btcruby/wire_format.rb', line 347

def encode_int16le(int);  [int].pack(PACK_FORMAT_INT16LE);  end

#encode_int32be(int) ⇒ Object



348
# File 'lib/btcruby/wire_format.rb', line 348

def encode_int32be(int);  [int].pack(PACK_FORMAT_INT32BE);  end

#encode_int32le(int) ⇒ Object



350
# File 'lib/btcruby/wire_format.rb', line 350

def encode_int32le(int);  [int].pack(PACK_FORMAT_INT32LE);  end

#encode_int64le(int) ⇒ Object



353
# File 'lib/btcruby/wire_format.rb', line 353

def encode_int64le(int);  [int].pack(PACK_FORMAT_INT64LE);  end

#encode_int8(int) ⇒ Object



345
# File 'lib/btcruby/wire_format.rb', line 345

def encode_int8(int);     [int].pack(PACK_FORMAT_INT8);     end

#encode_string(string) ⇒ Object

Returns the binary representation of the var-length string.

Raises:

  • (ArgumentError)


166
167
168
169
# File 'lib/btcruby/wire_format.rb', line 166

def encode_string(string)
  raise ArgumentError, "String must be present" if !string
  encode_varint(string.bytesize) + BTC::Data.ensure_binary_encoding(string)
end

#encode_uint16le(int) ⇒ Object



346
# File 'lib/btcruby/wire_format.rb', line 346

def encode_uint16le(int); [int].pack(PACK_FORMAT_UINT16LE); end

#encode_uint32be(int) ⇒ Object

used in BIP32



351
# File 'lib/btcruby/wire_format.rb', line 351

def encode_uint32be(int); [int].pack(PACK_FORMAT_UINT32BE); end

#encode_uint32le(int) ⇒ Object



349
# File 'lib/btcruby/wire_format.rb', line 349

def encode_uint32le(int); [int].pack(PACK_FORMAT_UINT32LE); end

#encode_uint64le(int) ⇒ Object



352
# File 'lib/btcruby/wire_format.rb', line 352

def encode_uint64le(int); [int].pack(PACK_FORMAT_UINT64LE); end

#encode_uint8(int) ⇒ Object

Encode int into one of the formats



344
# File 'lib/btcruby/wire_format.rb', line 344

def encode_uint8(int);    [int].pack(PACK_FORMAT_UINT8);    end

#encode_uleb128(value) ⇒ Object

Encodes an unsigned integer using LEB128.

Raises:

  • (ArgumentError)


263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/btcruby/wire_format.rb', line 263

def encode_uleb128(value)
  raise ArgumentError, "Signed integers are not supported" if value < 0
  return "\x00" if value == 0
  bytes = []
  while value != 0
    byte = value & 0b01111111 # 0x7f
    value >>= 7
    if value != 0
      byte |= 0b10000000 # 0x80
    end
    bytes << byte
  end
  return BTC::Data.data_from_bytes(bytes)
end

#encode_varint(i) ⇒ Object

Encodes integer and returns its binary varint representation.

Raises:

  • (ArgumentError)


101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/btcruby/wire_format.rb', line 101

def encode_varint(i)
  raise ArgumentError, "int must be present" if !i
  raise ArgumentError, "int must be non-negative" if i < 0

  buf = if i <  0xfd
    [i].pack("C")
  elsif i <= 0xffff
    [0xfd, i].pack("Cv")
  elsif i <= 0xffffffff
    [0xfe, i].pack("CV")
  elsif i <= 0xffffffffffffffff
    [0xff, i].pack("CQ<")
  else
    raise ArgumentError, "Does not support integers larger 0xffffffffffffffff (i = 0x#{i.to_s(16)})"
  end

  buf
end

#read_array(data: nil, stream: nil, offset: 0) ⇒ Object

Reads varint length prefix, then calls the block appropriate number of times to read the items. Returns an array of items.



191
192
193
194
195
196
197
# File 'lib/btcruby/wire_format.rb', line 191

def read_array(data: nil, stream: nil, offset: 0)
  count, len = read_varint(data: data, stream: stream, offset: offset)
  return [nil, len] if !count
  (0...count).map do |i|
    yield
  end
end

#read_int16le(data: nil, stream: nil, offset: 0) ⇒ Object



315
316
317
# File 'lib/btcruby/wire_format.rb', line 315

def read_int16le(data: nil, stream: nil, offset: 0)
  _read_fixint(name: :int16le,  length: 2, pack_format: PACK_FORMAT_INT16LE,  data: data, stream: stream, offset: offset)
end

#read_int32be(data: nil, stream: nil, offset: 0) ⇒ Object



331
332
333
# File 'lib/btcruby/wire_format.rb', line 331

def read_int32be(data: nil, stream: nil, offset: 0)
  _read_fixint(name: :int32be,  length: 4, pack_format: PACK_FORMAT_INT32BE,  data: data, stream: stream, offset: offset)
end

#read_int32le(data: nil, stream: nil, offset: 0) ⇒ Object



323
324
325
# File 'lib/btcruby/wire_format.rb', line 323

def read_int32le(data: nil, stream: nil, offset: 0)
  _read_fixint(name: :int32le,  length: 4, pack_format: PACK_FORMAT_INT32LE,  data: data, stream: stream, offset: offset)
end

#read_int64le(data: nil, stream: nil, offset: 0) ⇒ Object



339
340
341
# File 'lib/btcruby/wire_format.rb', line 339

def read_int64le(data: nil, stream: nil, offset: 0)
  _read_fixint(name: :int64le,  length: 8, pack_format: PACK_FORMAT_INT64LE,  data: data, stream: stream, offset: offset)
end

#read_int8(data: nil, stream: nil, offset: 0) ⇒ Object



307
308
309
# File 'lib/btcruby/wire_format.rb', line 307

def read_int8(data: nil, stream: nil, offset: 0)
  _read_fixint(name: :int8,     length: 1, pack_format: PACK_FORMAT_INT8,     data: data, stream: stream, offset: offset)
end

#read_string(data: nil, stream: nil, offset: 0) ⇒ Object

Reads variable-length string from data buffer or IO stream. Either data or stream must be present (and only one of them). Returns [string, length] where length is a number of bytes read (includes length prefix and offset bytes). In case of failure, returns [nil, length] where length is a number of bytes read before the error was encountered.



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/btcruby/wire_format.rb', line 134

def read_string(data: nil, stream: nil, offset: 0)
  if data && !stream

    string_length, read_length = read_varint(data: data, offset: offset)

    # If failed to read the length prefix, return nil.
    return [nil, read_length] if !string_length

    # Check if we have enough bytes to read the string itself
    return [nil, read_length] if data.bytesize < read_length + string_length

    string = BTC::Data.ensure_binary_encoding(data)[read_length, string_length]

    return [string, read_length + string_length]

  elsif stream && !data

    string_length, read_length = read_varint(stream: stream, offset: offset)
    return [nil, read_length] if !string_length

    buf = stream.read(string_length)

    return [nil, read_length] if !buf
    return [nil, read_length + buf.bytesize] if buf.bytesize < string_length

    return [buf, read_length + buf.bytesize]
  else
    raise ArgumentError, "Either data or stream must be specified."
  end
end

#read_uint16le(data: nil, stream: nil, offset: 0) ⇒ Object



311
312
313
# File 'lib/btcruby/wire_format.rb', line 311

def read_uint16le(data: nil, stream: nil, offset: 0)
  _read_fixint(name: :uint16le, length: 2, pack_format: PACK_FORMAT_UINT16LE, data: data, stream: stream, offset: offset)
end

#read_uint32be(data: nil, stream: nil, offset: 0) ⇒ Object

used in BIP32



327
328
329
# File 'lib/btcruby/wire_format.rb', line 327

def read_uint32be(data: nil, stream: nil, offset: 0) # used in BIP32
  _read_fixint(name: :uint32be, length: 4, pack_format: PACK_FORMAT_UINT32BE, data: data, stream: stream, offset: offset)
end

#read_uint32le(data: nil, stream: nil, offset: 0) ⇒ Object



319
320
321
# File 'lib/btcruby/wire_format.rb', line 319

def read_uint32le(data: nil, stream: nil, offset: 0)
  _read_fixint(name: :uint32le, length: 4, pack_format: PACK_FORMAT_UINT32LE, data: data, stream: stream, offset: offset)
end

#read_uint64le(data: nil, stream: nil, offset: 0) ⇒ Object



335
336
337
# File 'lib/btcruby/wire_format.rb', line 335

def read_uint64le(data: nil, stream: nil, offset: 0)
  _read_fixint(name: :uint64le, length: 8, pack_format: PACK_FORMAT_UINT64LE, data: data, stream: stream, offset: offset)
end

#read_uint8(data: nil, stream: nil, offset: 0) ⇒ Object

These read fixed-length integer in appropriate format ("le" stands for "little-endian") Return [value, length] or [nil, length] just like #read_varint method (see above).



303
304
305
# File 'lib/btcruby/wire_format.rb', line 303

def read_uint8(data: nil, stream: nil, offset: 0)
  _read_fixint(name: :uint8,    length: 1, pack_format: PACK_FORMAT_UINT8,    data: data, stream: stream, offset: offset)
end

#read_uleb128(data: nil, stream: nil, offset: 0) ⇒ Object

Decodes an unsigned integer encoded in LEB128. Returns [value, length] where value is an integer decoded from LEB128 and length is a number of bytes read (includes length prefix and offset bytes).



226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/btcruby/wire_format.rb', line 226

def read_uleb128(data: nil, stream: nil, offset: 0)
  if (data && stream) || (!data && !stream)
    raise ArgumentError, "Either data or stream must be specified."
  end
  if data
    data = BTC::Data.ensure_binary_encoding(data)
  end
  if stream
    if stream.eof?
      raise ArgumentError, "Can't read LEB128 from stream because it is already closed."
    end
    if offset > 0
      buf = stream.read(offset)
      return [nil, 0] if !buf
      return [nil, buf.bytesize] if buf.bytesize < offset
    end
  end
  result = 0
  shift = 0
  while true
    byte = if data
      return [nil, offset] if data.bytesize < 1 + offset
      BTC::Data.bytes_from_data(data, offset: offset, limit: 1)[0]
    elsif stream
      buf = stream.read(1)
      return [nil, offset] if !buf || buf.bytesize == 0
      buf.bytes[0]
    end
    result |= (byte & 0x7f) << shift
    break if byte & 0x80 == 0
    shift += 7
    offset += 1
  end
  [result, offset + 1]
end

#read_varint(data: nil, stream: nil, offset: 0) ⇒ Object

Reads varint from data or stream. Either data or stream must be present (and only one of them). Optional offset is useful when reading from data. Returns [value, length] where value is a decoded integer value and length is number of bytes read (including offset bytes). Value may be nil when decoding failed (length might be zero or greater, depending on how much data was consumed before failing). Usage:

i, _ = read_varint(data: buffer, offset: 42)
i, _ = read_varint(stream: File.open('someblock','r'))


26
27
28
29
30
31
32
33
34
35
36
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
# File 'lib/btcruby/wire_format.rb', line 26

def read_varint(data: nil, stream: nil, offset: 0)
  if data && !stream
    return [nil, 0] if data.bytesize < 1 + offset

    bytes = BTC::Data.bytes_from_data(data, offset: offset, limit: 9) # we don't need more than 9 bytes.

    byte = bytes[0]

    if byte < 0xfd
      return [byte, offset + 1]
    elsif byte == 0xfd
      return [nil, 1] if data.bytesize < 3 + offset # 1 byte prefix, 2 bytes uint16
      return [bytes[1] +
              bytes[2]*256, offset + 3]
    elsif byte == 0xfe
      return [nil, 1] if data.bytesize < 5 + offset # 1 byte prefix, 4 bytes uint32
      return [bytes[1] +
              bytes[2]*256 +
              bytes[3]*256*256 +
              bytes[4]*256*256*256, offset + 5]
    elsif byte == 0xff
      return [nil, 1] if data.bytesize < 9 + offset # 1 byte prefix, 8 bytes uint64
      return [bytes[1] +
              bytes[2]*256 +
              bytes[3]*256*256 +
              bytes[4]*256*256*256 +
              bytes[5]*256*256*256*256 +
              bytes[6]*256*256*256*256*256 +
              bytes[7]*256*256*256*256*256*256 +
              bytes[8]*256*256*256*256*256*256*256, offset + 9]
    end

  elsif stream && !data

    if stream.eof?
      raise ArgumentError, "Can't parse varint from stream because it is already closed."
    end

    if offset > 0
      buf = stream.read(offset)
      return [nil, 0] if !buf
      return [nil, buf.bytesize] if buf.bytesize < offset
    end

    prefix = stream.read(1)

    return [nil, offset] if !prefix || prefix.bytesize == 0

    byte = prefix.bytes[0]

    if byte < 0xfd
      return [byte, offset + 1]
    elsif byte == 0xfd
      buf = stream.read(2)
      return [nil, offset + 1] if !buf
      return [nil, offset + 1 + buf.bytesize] if buf.bytesize < 2
      return [buf.unpack("v").first, offset + 3]
    elsif byte == 0xfe
      buf = stream.read(4)
      return [nil, offset + 1] if !buf
      return [nil, offset + 1 + buf.bytesize] if buf.bytesize < 4
      return [buf.unpack("V").first, offset + 5]
    elsif byte == 0xff
      buf = stream.read(8)
      return [nil, offset + 1] if !buf
      return [nil, offset + 1 + buf.bytesize] if buf.bytesize < 8
      return [buf.unpack("Q<").first, offset + 9]
    end

  else
    raise ArgumentError, "Either data or stream must be specified."
  end
end

#write_array(array, data: nil, stream: nil, &block) ⇒ Object

Raises:

  • (ArgumentError)


203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/btcruby/wire_format.rb', line 203

def write_array(array, data: nil, stream: nil, &block)
  raise ArgumentError, "Array must be present" if !array
  raise ArgumentError, "Parsing block must be present" if !block
  intbuf = write_varint(array.size, data: data, stream: stream)
  array.inject(intbuf) do |buf, e|
    string = block.call(e)
    stringbuf = BTC::Data.ensure_binary_encoding(string)
    data << stringbuf if data
    stream.write(stringbuf) if stream
    buf << stringbuf
    buf
  end
end

#write_string(string, data: nil, stream: nil) ⇒ Object

Writes variable-length string to a data buffer or IO stream. If data is given, appends to a data. If stream is given, writes to a stream. Returns the binary representation of the var-length string.

Raises:

  • (ArgumentError)


175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/btcruby/wire_format.rb', line 175

def write_string(string, data: nil, stream: nil)
  raise ArgumentError, "String must be present" if !string

  intbuf = write_varint(string.bytesize, data: data, stream: stream)

  stringbuf = BTC::Data.ensure_binary_encoding(string)

  data << stringbuf if data
  stream.write(stringbuf) if stream

  intbuf + stringbuf
end

#write_uleb128(value, data: nil, stream: nil) ⇒ Object

Writes an unsigned integer encoded in LEB128 to a data buffer or a stream. Returns LEB128-encoded binary string.

Raises:

  • (ArgumentError)


280
281
282
283
284
285
286
# File 'lib/btcruby/wire_format.rb', line 280

def write_uleb128(value, data: nil, stream: nil)
  raise ArgumentError, "Integer must be present" if !value
  buf = encode_uleb128(value)
  data << buf if data
  stream.write(buf) if stream
  buf
end

#write_varint(i, data: nil, stream: nil) ⇒ Object

Encodes integer and returns its binary varint representation. If data is given, appends to a data. If stream is given, writes to a stream.



123
124
125
126
127
128
# File 'lib/btcruby/wire_format.rb', line 123

def write_varint(i, data: nil, stream: nil)
  buf = encode_varint(i)
  data << buf if data
  stream.write(buf) if stream
  buf
end