devp2p 0.1.0

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.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +22 -0
  4. data/lib/devp2p.rb +57 -0
  5. data/lib/devp2p/app_helper.rb +85 -0
  6. data/lib/devp2p/base_app.rb +80 -0
  7. data/lib/devp2p/base_protocol.rb +136 -0
  8. data/lib/devp2p/base_service.rb +55 -0
  9. data/lib/devp2p/command.rb +82 -0
  10. data/lib/devp2p/configurable.rb +32 -0
  11. data/lib/devp2p/connection_monitor.rb +77 -0
  12. data/lib/devp2p/control.rb +32 -0
  13. data/lib/devp2p/crypto.rb +73 -0
  14. data/lib/devp2p/crypto/ecc_x.rb +133 -0
  15. data/lib/devp2p/crypto/ecies.rb +134 -0
  16. data/lib/devp2p/discovery.rb +118 -0
  17. data/lib/devp2p/discovery/address.rb +83 -0
  18. data/lib/devp2p/discovery/kademlia_protocol_adapter.rb +11 -0
  19. data/lib/devp2p/discovery/node.rb +32 -0
  20. data/lib/devp2p/discovery/protocol.rb +342 -0
  21. data/lib/devp2p/discovery/transport.rb +105 -0
  22. data/lib/devp2p/exception.rb +30 -0
  23. data/lib/devp2p/frame.rb +197 -0
  24. data/lib/devp2p/kademlia.rb +48 -0
  25. data/lib/devp2p/kademlia/k_bucket.rb +178 -0
  26. data/lib/devp2p/kademlia/node.rb +40 -0
  27. data/lib/devp2p/kademlia/protocol.rb +284 -0
  28. data/lib/devp2p/kademlia/routing_table.rb +131 -0
  29. data/lib/devp2p/kademlia/wire_interface.rb +30 -0
  30. data/lib/devp2p/multiplexed_session.rb +110 -0
  31. data/lib/devp2p/multiplexer.rb +358 -0
  32. data/lib/devp2p/p2p_protocol.rb +170 -0
  33. data/lib/devp2p/packet.rb +35 -0
  34. data/lib/devp2p/peer.rb +329 -0
  35. data/lib/devp2p/peer_errors.rb +35 -0
  36. data/lib/devp2p/peer_manager.rb +274 -0
  37. data/lib/devp2p/rlpx_session.rb +434 -0
  38. data/lib/devp2p/sync_queue.rb +76 -0
  39. data/lib/devp2p/utils.rb +106 -0
  40. data/lib/devp2p/version.rb +13 -0
  41. data/lib/devp2p/wired_service.rb +30 -0
  42. metadata +227 -0
@@ -0,0 +1,118 @@
1
+ ##
2
+ # # Node Discovery Protocol
3
+ #
4
+ # * [Node] - an entity on the network
5
+ # * [Node] ID - 512 bit public key of node
6
+ #
7
+ # The Node Discovery protocol provides a way to find RLPx nodes that can be
8
+ # connected to. It uses a Kademlia-like protocol to maintain a distributed
9
+ # database of the IDs and endpoints of all listening nodes.
10
+ #
11
+ # Each node keeps a node table as described in the Kademlia paper (Maymounkov,
12
+ # Mazières 2002). The node table is configured with a bucket size of 16
13
+ # (denoted `k` in Kademlia), concurrency of 3 (denoted `α` in Kademlia), and 8
14
+ # bits per hop (denoted `b` in Kademlia) for routing. The eviction check
15
+ # interval is 75 milliseconds, and the idle bucket-refresh interval is 3600
16
+ # seconds.
17
+ #
18
+ # In order to maintain a well-formed network, RLPx nodes should try to connect
19
+ # to an unspecified number of close nodes. To increase resilience against Sybil
20
+ # attacks, nodes should also connect to randomly chosen, non-close nodes.
21
+ #
22
+ # Each node runs the UDP-based RPC protocol defined below. The `FIND_DATA` and
23
+ # `STORE` requests from the Kademlia paper are not part of the protocol since
24
+ # the Node Discovery Protocol does not provide DHT functionality.
25
+ #
26
+ # ## Joining the network
27
+ #
28
+ # When joining the network, fills its node table by performing a recursive Find
29
+ # Node operation with its own ID as the 'Target'. The initial Find Node request
30
+ # is sent to one or more bootstrap nodes.
31
+ #
32
+ # ## RPC Protocol
33
+ #
34
+ # RLPx nodes that want to accept incoming connections should listen on the same
35
+ # port number for UDP packets (Node Discovery Protocol) and TCP connections
36
+ # (RLPx protocol).
37
+ #
38
+ # All requests time out after 300ms. Requests are not re-sent.
39
+ #
40
+ # ## Packet Data
41
+ #
42
+ # All packets contain an `Expiration` date to guard against replay attacks. The
43
+ # date should be interpreted as a UNIX timestamp. The receiver should discard
44
+ # any packet whose `Expiration` value is in the past.
45
+ #
46
+ # ### Ping (type 0x01)
47
+ #
48
+ # Ping packets can be sent and received at any time. The receiver should reply
49
+ # with a Pong packet and update the IP/Port of the sender in its node table.
50
+ #
51
+ # PingNode packet-type: 0x01
52
+ #
53
+ # struct PingNode <= 59 bytes
54
+ # {
55
+ # h256 version = 0x3; <= 1
56
+ # Endpoint from; <= 23
57
+ # Endpoint to; <= 23
58
+ # unsigned expiration; <= 9
59
+ # }
60
+ #
61
+ # struct Endpoint <= 24 = [17,3,3]
62
+ # {
63
+ # unsigned address; // BE encoded 32-bit or 128-bit unsigned (layer3 address; size determins ipv4 vs ipv6)
64
+ # unsigned udpPort; // BE encoded 16-bit unsigned
65
+ # unsigned tcpPort; // BE encoded 16-bit unsigned
66
+ # }
67
+ #
68
+ # ### Pong (type 0x02)
69
+ #
70
+ # Pong is the reply to a Ping packet.
71
+ #
72
+ # Pong packet-type: 0x02
73
+ #
74
+ # struct Pong <= 66 bytes
75
+ # {
76
+ # Endpoint to;
77
+ # h256 echo;
78
+ # unsigned expiration;
79
+ # }
80
+ #
81
+ # ### Find Node (type 0x03)
82
+ #
83
+ # Find Node packets are sent to locate nodes close to a given target ID. The
84
+ # receiver should reply with a Neighbours packet containing the `k` nodes
85
+ # closest to target that it knows about.
86
+ #
87
+ # FindNode packet-type: 0x03
88
+ #
89
+ # struct FindNode <= 76 bytes
90
+ # {
91
+ # NodeId target; // Id of a node. The responding node will send back nodes closest to the target.
92
+ # unsigned expiration;
93
+ # }
94
+ #
95
+ # ### Neighbours (type 0x04)
96
+ #
97
+ # Neighbours is the reply to Find Node. It contains up to `k` nodes that the
98
+ # sender knows which are closest to the requested 'Target`.
99
+ #
100
+ # Neighbours packet-type: 0x04
101
+ #
102
+ # struct Neighbours <= 1423
103
+ # {
104
+ # list nodes: struct Neighbours <= 88...1411; 76...1219
105
+ # {
106
+ # inline Endpoint endpoint;
107
+ # NodeId node;
108
+ # };
109
+ # unsigned expiration;
110
+ # }
111
+ #
112
+
113
+
114
+ require 'devp2p/discovery/address'
115
+ require 'devp2p/discovery/node'
116
+ require 'devp2p/discovery/kademlia_protocol_adapter'
117
+ require 'devp2p/discovery/protocol'
118
+ require 'devp2p/discovery/transport'
@@ -0,0 +1,83 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+
3
+ require 'ipaddr'
4
+ require 'resolv'
5
+
6
+ module DEVp2p
7
+ module Discovery
8
+
9
+ class Address
10
+
11
+ attr :udp_port, :tcp_port
12
+
13
+ def self.from_endpoint(ip, udp_port, tcp_port="\x00\x00")
14
+ new(ip, udp_port, tcp_port, true)
15
+ end
16
+
17
+ def initialize(ip, udp_port, tcp_port=nil, from_binary=false)
18
+ tcp_port ||= udp_port
19
+
20
+ if from_binary
21
+ raise ArgumentError, "invalid ip" unless [4,16].include?(ip.size)
22
+
23
+ @udp_port = dec_port udp_port
24
+ @tcp_port = dec_port tcp_port
25
+ else
26
+ raise ArgumentError, "udp_port must be Integer" unless udp_port.is_a?(Integer)
27
+ raise ArgumentError, "tcp_port must be Integer" unless tcp_port.is_a?(Integer)
28
+
29
+ @udp_port = udp_port
30
+ @tcp_port = tcp_port
31
+ end
32
+
33
+ begin
34
+ @ip = from_binary ? IPAddr.new_ntoh(ip) : IPAddr.new(ip)
35
+ rescue IPAddr::InvalidAddressError => e
36
+ ips = Resolv.getaddresses(ip).sort {|addr| addr =~ /:/ ? 1 : 0 } # use ipv4 first
37
+ raise e if ips.empty?
38
+ @ip = ips[0]
39
+ end
40
+ end
41
+
42
+ def ip
43
+ @ip.to_s
44
+ end
45
+
46
+ def update(addr)
47
+ @tcp_port = addr.tcp_port if @tcp_port.nil? || @tcp_port == 0
48
+ end
49
+
50
+ ##
51
+ # addresses equal if they share ip and udp_port
52
+ #
53
+ def ==(other)
54
+ [ip, udp_port] == [other.ip, other.udp_port]
55
+ end
56
+
57
+ def to_s
58
+ "Address(#{ip}:#{udp_port})"
59
+ end
60
+
61
+ def to_h
62
+ {ip: ip, udp_port: udp_port, tcp_port: tcp_port}
63
+ end
64
+
65
+ def to_b
66
+ [@ip.hton, enc_port(udp_port), enc_port(tcp_port)]
67
+ end
68
+ alias to_endpoint to_b
69
+
70
+ private
71
+
72
+ def enc_port(p)
73
+ Utils.int_to_big_endian4(p)[-2..-1]
74
+ end
75
+
76
+ def dec_port(b)
77
+ Utils.big_endian_to_int(b)
78
+ end
79
+
80
+ end
81
+
82
+ end
83
+ end
@@ -0,0 +1,11 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+
3
+ module DEVp2p
4
+ module Discovery
5
+
6
+ class KademliaProtocolAdapter < Kademlia::Protocol
7
+ # do nothing
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,32 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+
3
+ module DEVp2p
4
+ module Discovery
5
+
6
+ class Node < Kademlia::Node
7
+
8
+ def self.from_uri(uri)
9
+ ip, port, pubkey = Utils.host_port_pubkey_from_uri(uri)
10
+ new(pubkey, Address.new(ip, port.to_i))
11
+ end
12
+
13
+ attr_accessor :address
14
+
15
+ def initialize(pubkey, address=nil)
16
+ raise ArgumentError, 'invalid address' unless address.nil? || address.is_a?(Address)
17
+
18
+ super(pubkey)
19
+
20
+ self.address = address
21
+ @reputation = 0
22
+ @rlpx_version = 0
23
+ end
24
+
25
+ def to_uri
26
+ Utils.host_port_pubkey_to_uri(address.ip, address.udp_port, pubkey)
27
+ end
28
+
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,342 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+
3
+ module DEVp2p
4
+ module Discovery
5
+
6
+ class Protocol < Kademlia::WireInterface
7
+
8
+ VERSION = 4
9
+
10
+ EXPIRATION = 60 # let messages expire after N seconds
11
+
12
+ CMD_ID_MAP = {
13
+ ping: 1,
14
+ pong: 2,
15
+ find_node: 3,
16
+ neighbours: 4
17
+ }.freeze
18
+ REV_CMD_ID_MAP = CMD_ID_MAP.map {|k,v| [v,k] }.to_h.freeze
19
+
20
+ # number of required top-level list elements for each cmd_id.
21
+ # elements beyond this length are trimmed.
22
+ CMD_ELEM_COUNT_MAP = {
23
+ ping: 4,
24
+ poing: 3,
25
+ find_node: 2,
26
+ neighbours: 2
27
+ }
28
+
29
+ attr :pubkey, :kademlia
30
+
31
+ def initialize(app, transport)
32
+ @app = app
33
+ @transport = transport
34
+
35
+ @privkey = Utils.decode_hex app.config[:node][:privkey_hex]
36
+ @pubkey = Crypto.privtopub @privkey
37
+
38
+ @nodes = {} # nodeid => Node
39
+ @node = Node.new(pubkey, @transport.address)
40
+
41
+ @kademlia = KademliaProtocolAdapter.new @node, self
42
+
43
+ uri = Utils.host_port_pubkey_to_uri(ip, udp_port, pubkey)
44
+ logger.info "starting discovery proto", enode: uri
45
+ end
46
+
47
+ def bootstrap(nodes)
48
+ @kademlia.bootstrap(nodes) unless nodes.empty?
49
+ end
50
+
51
+ ##
52
+ # return node or create new, update address if supplied
53
+ #
54
+ def get_node(nodeid, address=nil)
55
+ raise ArgumentError, 'invalid nodeid' unless nodeid.size == Kademlia::PUBKEY_SIZE / 8
56
+ raise ArgumentError, 'must give either address or existing nodeid' unless address || @nodes.has_key?(nodeid)
57
+
58
+ @nodes[nodeid] = Node.new nodeid, address if !@nodes.has_key?(nodeid)
59
+ node = @nodes[nodeid]
60
+
61
+ if address
62
+ raise ArgumentError, 'address must be Address' unless address.instance_of?(Address)
63
+ node.address = address
64
+ end
65
+
66
+ node
67
+ end
68
+
69
+ def sign(msg)
70
+ msg = Crypto.keccak256 msg
71
+ Crypto.ecdsa_sign msg, @privkey
72
+ end
73
+
74
+ ##
75
+ # UDP packets are structured as follows:
76
+ #
77
+ # hash || signature || packet-type || packet-data
78
+ #
79
+ # * packet-type: single byte < 2**7 // valid values are [1,4]
80
+ # * packet-data: RLP encoded list. Packet properties are serialized in
81
+ # the order in which they're defined. See packet-data below.
82
+ #
83
+ # Offset |
84
+ # 0 | MDC | Ensures integrity of packet.
85
+ # 65 | signature | Ensures authenticity of sender, `SIGN(sender-privkey, MDC)`
86
+ # 97 | type | Single byte in range [1, 4] that determines the structure of Data
87
+ # 98 | data | RLP encoded, see section Packet Data
88
+ #
89
+ # The packets are signed and authenticated. The sender's Node ID is
90
+ # determined by recovering the public key from the signature.
91
+ #
92
+ # sender-pubkey = ECRECOVER(Signature)
93
+ #
94
+ # The integrity of the packet can then be verified by computing the
95
+ # expected MDC of the packet as:
96
+ #
97
+ # MDC = keccak256(sender-pubkey || type || data)
98
+ #
99
+ # As an optimization, implementations may look up the public key by the
100
+ # UDP sending address and compute MDC before recovering the sender ID. If
101
+ # the MDC values do not match, the packet can be dropped.
102
+ #
103
+ def pack(cmd_id, payload)
104
+ raise ArgumentError, 'invalid cmd_id' unless REV_CMD_ID_MAP.has_key?(cmd_id)
105
+ raise ArgumentError, 'payload must be Array' unless payload.is_a?(Array)
106
+
107
+ cmd_id = encode_cmd_id cmd_id
108
+ expiration = encode_expiration Time.now.to_i + EXPIRATION
109
+
110
+ encoded_data = RLP.encode(payload + [expiration])
111
+ signed_data = Crypto.keccak256 "#{cmd_id}#{encoded_data}"
112
+ signature = Crypto.ecdsa_sign signed_data, @privkey
113
+
114
+ raise InvalidSignatureError unless signature.size == 65
115
+
116
+ mdc = Crypto.keccak256 "#{signature}#{cmd_id}#{encoded_data}"
117
+ raise InvalidMACError unless mdc.size == 32
118
+
119
+ "#{mdc}#{signature}#{cmd_id}#{encoded_data}"
120
+ end
121
+
122
+ ##
123
+ # macSize = 256 / 8 = 32
124
+ # sigSize = 520 / 8 = 65
125
+ # headSize = macSize + sigSize = 97
126
+ #
127
+ def unpack(message)
128
+ mdc = message[0,32]
129
+ if mdc != Crypto.keccak256(message[32..-1])
130
+ logger.warn 'packet with wrong mcd'
131
+ raise InvalidMessageMAC
132
+ end
133
+
134
+ signature = message[32,65]
135
+ raise InvalidSignatureError unless signature.size == 65
136
+
137
+ signed_data = Crypto.keccak256(message[97..-1])
138
+ remote_pubkey = Crypto.ecdsa_recover(signed_data, signature)
139
+ raise InvalidKeyError unless remote_pubkey.size == Kademlia::PUBKEY_SIZE / 8
140
+
141
+ cmd_id = decode_cmd_id message[97]
142
+ cmd = REV_CMD_ID_MAP[cmd_id]
143
+
144
+ payload = RLP.decode message[98..-1], strict: false
145
+ raise InvalidPayloadError unless payload.instance_of?(Array)
146
+
147
+ # ignore excessive list elements as required by EIP-8
148
+ payload = payload[0, CMD_ELEM_COUNT_MAP[cmd]||payload.size]
149
+
150
+ return remote_pubkey, cmd_id, payload, mdc
151
+ end
152
+
153
+ def receive_message(address, message)
154
+ logger.debug "<<< message", address: address
155
+ raise ArgumentError, 'address must be Address' unless address.instance_of?(Address)
156
+
157
+ begin
158
+ remote_pubkey, cmd_id, payload, mdc = unpack message
159
+
160
+ # Note: as of discovery version 4, expiration is the last element for
161
+ # all packets. This might not be the case for a later version, but
162
+ # just popping the last element is good enough for now.
163
+ expiration = decode_expiration payload.pop
164
+ raise PacketExpired if Time.now.to_i > expiration
165
+ rescue DefectiveMessage
166
+ logger.debug $!
167
+ return
168
+ end
169
+
170
+ cmd = "recv_#{REV_CMD_ID_MAP[cmd_id]}"
171
+ nodeid = remote_pubkey
172
+
173
+ get_node(nodeid, address) unless @nodes.has_key?(nodeid)
174
+ send cmd, nodeid, payload, mdc
175
+ end
176
+
177
+ def send_message(node, message)
178
+ raise ArgumentError, 'node must have address' unless node.address
179
+ logger.debug ">>> message", address: node.address
180
+ @transport.send_message node.address, message
181
+ end
182
+
183
+ def send_ping(node)
184
+ raise ArgumentError, "node must be Node" unless node.is_a?(Node)
185
+ raise ArgumentError, "cannot ping self" if node == @node
186
+
187
+ logger.debug ">>> ping", remoteid: node
188
+
189
+ version = RLP::Sedes.big_endian_int.serialize VERSION
190
+ payload = [
191
+ version,
192
+ Address.new(ip, udp_port, tcp_port).to_endpoint,
193
+ node.address.to_endpoint
194
+ ]
195
+
196
+ message = pack CMD_ID_MAP[:ping], payload
197
+ send_message node, message
198
+
199
+ message[0,32] # return the MDC to identify pongs
200
+ end
201
+
202
+ ##
203
+ # Update ip, port in node table. Addresses can only be learned by ping
204
+ # messages.
205
+ #
206
+ def recv_ping(nodeid, payload, mdc)
207
+ if payload.size != 3
208
+ logger.error "invalid ping payload", payload: payload
209
+ return
210
+ end
211
+
212
+ node = get_node nodeid
213
+ logger.debug "<<< ping", node: node
214
+
215
+ remote_address = Address.from_endpoint(*payload[1]) # from
216
+ my_address = Address.from_endpoint(*payload[2]) # my address
217
+
218
+ get_node(nodeid).address.update remote_address
219
+ @kademlia.recv_ping node, mdc
220
+ end
221
+
222
+ def send_pong(node, token)
223
+ logger.debug ">>> pong", remoteid: node
224
+
225
+ payload = [node.address.to_endpoint, token]
226
+ raise InvalidPayloadError unless [4,16].include?(payload[0][0].size)
227
+
228
+ message = pack CMD_ID_MAP[:pong], payload
229
+ send_message node, message
230
+ end
231
+
232
+ def recv_pong(nodeid, payload, mdc)
233
+ if payload.size != 2
234
+ logger.error 'invalid pong payload', payload: payload
235
+ return
236
+ end
237
+
238
+ raise InvalidPayloadError unless payload[0].size == 3
239
+ raise InvalidPayloadError unless [4,16].include?(payload[0][0].size)
240
+
241
+ my_address = Address.from_endpoint *payload[0]
242
+ echoed = payload[1]
243
+
244
+ if @nodes.include?(nodeid)
245
+ node = get_node nodeid
246
+ @kademlia.recv_pong node, echoed
247
+ else
248
+ logger.debug "<<< unexpected pong from unknown node"
249
+ end
250
+ end
251
+
252
+ def send_find_node(node, target_node_id)
253
+ target_node_id = Utils.zpad_int target_node_id, Kademlia::PUBKEY_SIZE/8
254
+ logger.debug ">>> find_node", remoteid: node
255
+
256
+ message = pack CMD_ID_MAP[:find_node], [target_node_id]
257
+ send_message node, message
258
+ end
259
+
260
+ def recv_find_node(nodeid, payload, mdc)
261
+ node = get_node nodeid
262
+
263
+ logger.debug "<<< find_node", remoteid: node
264
+ raise InvalidPayloadError unless payload[0].size == Kademlia::PUBKEY_SIZE/8
265
+
266
+ target = Utils.big_endian_to_int payload[0]
267
+ @kademlia.recv_find_node node, target
268
+ end
269
+
270
+ def send_neighbours(node, neighbours)
271
+ raise ArgumentError, 'neighbours must be Array' unless neighbours.instance_of?(Array)
272
+ raise ArgumentError, 'neighbours must be Node' unless neighbours.all? {|n| n.is_a?(Node) }
273
+
274
+ nodes = neighbours.map {|n| n.address.to_endpoint + [n.pubkey] }
275
+ logger.debug ">>> neighbours", remoteid: node, count: nodes.size
276
+
277
+ message = pack CMD_ID_MAP[:neighbours], [nodes]
278
+ send_message node, message
279
+ end
280
+
281
+ def recv_neighbours(nodeid, payload, mdc)
282
+ node = get_node nodeid
283
+ raise InvalidPayloadError unless payload.size == 1
284
+ raise InvalidPayloadError unless payload[0].instance_of?(Array)
285
+ logger.debug "<<< neighbours", remoteid: node, count: payload[0].size
286
+
287
+ neighbours_set = payload[0].uniq
288
+ logger.warn "received duplicates" if neighbours_set.size < payload[0].size
289
+
290
+ neighbours = neighbours_set.map do |n|
291
+ if n.size != 4 || ![4,16].include?(n[0].size)
292
+ logger.error "invalid neighbours format", neighbours: n
293
+ return
294
+ end
295
+
296
+ n = n.dup
297
+ nodeid = n.pop
298
+ address = Address.from_endpoint *n
299
+ get_node nodeid, address
300
+ end
301
+
302
+ @kademlia.recv_neighbours node, neighbours
303
+ end
304
+
305
+ def ip
306
+ @app.config[:discovery][:listen_host]
307
+ end
308
+
309
+ def udp_port
310
+ @app.config[:discovery][:listen_port]
311
+ end
312
+
313
+ def tcp_port
314
+ @app.config[:p2p][:listen_port]
315
+ end
316
+
317
+ private
318
+
319
+ def logger
320
+ @logger ||= Logger.new("#{udp_port}.p2p.discovery").tap {|l| l.level = :info }
321
+ end
322
+
323
+ def encode_cmd_id(cmd_id)
324
+ cmd_id.chr
325
+ end
326
+
327
+ def decode_cmd_id(byte)
328
+ byte.ord
329
+ end
330
+
331
+ def encode_expiration(i)
332
+ RLP::Sedes.big_endian_int.serialize(i)
333
+ end
334
+
335
+ def decode_expiration(b)
336
+ RLP::Sedes.big_endian_int.deserialize(b)
337
+ end
338
+
339
+ end
340
+
341
+ end
342
+ end