Module: Solana::Borsh
- Defined in:
- lib/solana/borsh.rb
Defined Under Namespace
Classes: DecodedFieldTooLarge
Constant Summary
collapse
- MAX_DECODED_FIELD_BYTES =
Cap on the bytes any single length-prefixed decode (vec, string) can claim. Malicious / corrupt RPC responses can carry a length field of e.g. 4_000_000_000, which without this guard would OOM the process when the caller allocates accordingly. 10MB is more than any sane Solana account / instruction payload — adjust per consumer needs.
10 * 1024 * 1024
Class Method Summary
collapse
-
.check_field_length!(length, kind) ⇒ Object
-
.decode_pubkey(bytes, offset = 0) ⇒ Object
-
.decode_string(bytes, offset = 0) ⇒ Object
-
.decode_u16(bytes, offset = 0) ⇒ Object
-
.decode_u32(bytes, offset = 0) ⇒ Object
-
.decode_u64(bytes, offset = 0) ⇒ Object
-
.decode_u8(bytes, offset = 0) ⇒ Object
-
.decode_vec(bytes, offset = 0, &block) ⇒ Object
-
.encode_bool(value) ⇒ Object
-
.encode_bytes32(bytes) ⇒ Object
-
.encode_i64(value) ⇒ Object
-
.encode_pubkey(pubkey_bytes) ⇒ Object
-
.encode_string(str) ⇒ Object
-
.encode_u16(value) ⇒ Object
-
.encode_u32(value) ⇒ Object
-
.encode_u64(value) ⇒ Object
-
.encode_u8(value) ⇒ Object
-
.encode_vec(items, &block) ⇒ Object
Class Method Details
.check_field_length!(length, kind) ⇒ Object
107
108
109
110
111
112
113
114
|
# File 'lib/solana/borsh.rb', line 107
def check_field_length!(length, kind)
if length > MAX_DECODED_FIELD_BYTES
raise DecodedFieldTooLarge,
"Borsh #{kind} declared length #{length} exceeds cap " \
"(MAX_DECODED_FIELD_BYTES=#{MAX_DECODED_FIELD_BYTES}). " \
"Likely a corrupt or malicious RPC response."
end
end
|
.decode_pubkey(bytes, offset = 0) ⇒ Object
79
80
81
|
# File 'lib/solana/borsh.rb', line 79
def decode_pubkey(bytes, offset = 0)
[bytes.byteslice(offset, 32), offset + 32]
end
|
.decode_string(bytes, offset = 0) ⇒ Object
Length-prefixed string. Reads u32 length then ‘length` bytes of UTF-8. Raises DecodedFieldTooLarge if the declared length exceeds the cap —protects callers from allocation-bomb DoS via crafted RPC responses.
86
87
88
89
90
91
|
# File 'lib/solana/borsh.rb', line 86
def decode_string(bytes, offset = 0)
length, offset = decode_u32(bytes, offset)
check_field_length!(length, "string")
str = bytes.byteslice(offset, length).to_s.force_encoding("UTF-8")
[str, offset + length]
end
|
.decode_u16(bytes, offset = 0) ⇒ Object
67
68
69
|
# File 'lib/solana/borsh.rb', line 67
def decode_u16(bytes, offset = 0)
[bytes.byteslice(offset, 2).unpack1("v"), offset + 2]
end
|
.decode_u32(bytes, offset = 0) ⇒ Object
71
72
73
|
# File 'lib/solana/borsh.rb', line 71
def decode_u32(bytes, offset = 0)
[bytes.byteslice(offset, 4).unpack1("V"), offset + 4]
end
|
.decode_u64(bytes, offset = 0) ⇒ Object
75
76
77
|
# File 'lib/solana/borsh.rb', line 75
def decode_u64(bytes, offset = 0)
[bytes.byteslice(offset, 8).unpack1("Q<"), offset + 8]
end
|
.decode_u8(bytes, offset = 0) ⇒ Object
63
64
65
|
# File 'lib/solana/borsh.rb', line 63
def decode_u8(bytes, offset = 0)
[bytes.byteslice(offset, 1).unpack1("C"), offset + 1]
end
|
.decode_vec(bytes, offset = 0, &block) ⇒ Object
Length-prefixed array. block is called per element with (bytes, offset) and must return [value, new_offset]. Bounded by MAX_DECODED_FIELD_BYTES on the declared count to prevent allocation-bomb DoS.
96
97
98
99
100
101
102
103
104
105
|
# File 'lib/solana/borsh.rb', line 96
def decode_vec(bytes, offset = 0, &block)
length, offset = decode_u32(bytes, offset)
check_field_length!(length, "vec")
items = []
length.times do
item, offset = block.call(bytes, offset)
items << item
end
[items, offset]
end
|
.encode_bool(value) ⇒ Object
57
58
59
|
# File 'lib/solana/borsh.rb', line 57
def encode_bool(value)
encode_u8(value ? 1 : 0)
end
|
.encode_bytes32(bytes) ⇒ Object
41
42
43
44
45
|
# File 'lib/solana/borsh.rb', line 41
def encode_bytes32(bytes)
bytes = bytes.b if bytes.is_a?(String)
raise "Expected 32 bytes, got #{bytes.bytesize}" unless bytes.bytesize == 32
bytes
end
|
.encode_i64(value) ⇒ Object
30
31
32
|
# File 'lib/solana/borsh.rb', line 30
def encode_i64(value)
[value].pack("q<") end
|
.encode_pubkey(pubkey_bytes) ⇒ Object
34
35
36
37
38
39
|
# File 'lib/solana/borsh.rb', line 34
def encode_pubkey(pubkey_bytes)
pubkey_bytes = Keypair.decode_base58(pubkey_bytes) if pubkey_bytes.is_a?(String) && pubkey_bytes.length != 32
pubkey_bytes = pubkey_bytes.b if pubkey_bytes.is_a?(String)
raise "Pubkey must be 32 bytes, got #{pubkey_bytes.bytesize}" unless pubkey_bytes.bytesize == 32
pubkey_bytes
end
|
.encode_string(str) ⇒ Object
52
53
54
55
|
# File 'lib/solana/borsh.rb', line 52
def encode_string(str)
bytes = str.encode("UTF-8").b
encode_u32(bytes.bytesize) + bytes
end
|
.encode_u16(value) ⇒ Object
18
19
20
|
# File 'lib/solana/borsh.rb', line 18
def encode_u16(value)
[value].pack("v") end
|
.encode_u32(value) ⇒ Object
22
23
24
|
# File 'lib/solana/borsh.rb', line 22
def encode_u32(value)
[value].pack("V") end
|
.encode_u64(value) ⇒ Object
26
27
28
|
# File 'lib/solana/borsh.rb', line 26
def encode_u64(value)
[value].pack("Q<") end
|
.encode_u8(value) ⇒ Object
14
15
16
|
# File 'lib/solana/borsh.rb', line 14
def encode_u8(value)
[value].pack("C")
end
|
.encode_vec(items, &block) ⇒ Object
47
48
49
50
|
# File 'lib/solana/borsh.rb', line 47
def encode_vec(items, &block)
encoded_items = items.map { |item| block.call(item) }.join
encode_u32(items.length) + encoded_items
end
|