Module: BSV::Wallet::Wire::Frame
- Defined in:
- lib/bsv/wallet/wire/frame.rb
Overview
BRC-103 request and result frame codec.
Port of go-sdk/wallet/serializer/frame.go. Two frame types:
Request frame (client → wallet):
[1 byte: call][1 byte: originator_len][originator_len bytes: UTF-8][remaining: params]
Result frame (wallet → client):
[1 byte: error_code]
On success (0x00): [remaining bytes: payload]
On error (non-zero):
[VarInt: message_len][message bytes]
[VarInt: stack_len][stack bytes]
Constant Summary collapse
- MAX_ORIGINATOR_BYTES =
Maximum originator byte length enforced at write time. Matches the BRC-100
OriginatorDomainNameStringUnder250Bytesbranded type used byWire::Validation.originator_domain!. 250
Class Method Summary collapse
-
.decode_varint(data, offset = 0) ⇒ Array(Integer, Integer)
Decoded value, bytes consumed.
-
.encode_varint(n) ⇒ String
Bitcoin varint encoding.
-
.read_request(bytes) ⇒ Hash
Decode a request frame.
-
.read_result(bytes) ⇒ String
Decode a result frame.
-
.write_error(error:) ⇒ String
Encode an error result frame.
-
.write_request(call:, originator:, params: nil) ⇒ String
Encode a request frame.
-
.write_result(payload: nil) ⇒ String
Encode a success result frame.
Class Method Details
.decode_varint(data, offset = 0) ⇒ Array(Integer, Integer)
Returns decoded value, bytes consumed.
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
# File 'lib/bsv/wallet/wire/frame.rb', line 157 def decode_varint(data, offset = 0) raise ArgumentError, "varint: need 1 byte at #{offset}" if offset >= data.bytesize first = data.getbyte(offset) case first when 0..0xFC [first, 1] when 0xFD raise ArgumentError, "varint: need 3 bytes at #{offset}" if data.bytesize < offset + 3 [data.byteslice(offset + 1, 2).unpack1('v'), 3] when 0xFE raise ArgumentError, "varint: need 5 bytes at #{offset}" if data.bytesize < offset + 5 [data.byteslice(offset + 1, 4).unpack1('V'), 5] when 0xFF raise ArgumentError, "varint: need 9 bytes at #{offset}" if data.bytesize < offset + 9 [data.byteslice(offset + 1, 8).unpack1('Q<'), 9] end end |
.encode_varint(n) ⇒ String
Returns Bitcoin varint encoding.
142 143 144 145 146 147 148 149 150 151 152 |
# File 'lib/bsv/wallet/wire/frame.rb', line 142 def encode_varint(n) if n < 0xFD [n].pack('C') elsif n <= 0xFFFF [0xFD, n].pack('Cv') elsif n <= 0xFFFFFFFF [0xFE, n].pack('CV') else [0xFF, n].pack('CQ<') end end |
.read_request(bytes) ⇒ Hash
Decode a request frame.
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/bsv/wallet/wire/frame.rb', line 54 def read_request(bytes) data = bytes.b raise ArgumentError, 'frame too short: need at least 2 bytes' if data.bytesize < 2 call = data.getbyte(0) originator_len = data.getbyte(1) if data.bytesize < 2 + originator_len raise ArgumentError, "frame truncated: need #{2 + originator_len} bytes for originator, " \ "got #{data.bytesize}" end originator = data.byteslice(2, originator_len).force_encoding('UTF-8') raise ArgumentError, 'frame originator is not valid UTF-8' unless originator.valid_encoding? params = data.byteslice(2 + originator_len, data.bytesize - 2 - originator_len) || ''.b { call: call, originator: originator, params: params } end |
.read_result(bytes) ⇒ String
Decode a result frame.
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 |
# File 'lib/bsv/wallet/wire/frame.rb', line 110 def read_result(bytes) data = bytes.b raise ArgumentError, 'result frame is empty' if data.empty? code = data.getbyte(0) return data.byteslice(1, data.bytesize - 1) || ''.b if code.zero? offset = 1 msg_len, vi = decode_varint(data, offset) offset += vi raise ArgumentError, 'result frame truncated: message' if data.bytesize < offset + msg_len = data.byteslice(offset, msg_len).force_encoding('UTF-8') raise ArgumentError, 'result frame error message is not valid UTF-8' unless .valid_encoding? offset += msg_len stack_len, vi = decode_varint(data, offset) offset += vi raise ArgumentError, 'result frame truncated: stack' if data.bytesize < offset + stack_len stack = data.byteslice(offset, stack_len).force_encoding('UTF-8') raise ArgumentError, 'result frame stack is not valid UTF-8' unless stack.valid_encoding? raise BSV::Wallet.error_from_wire(code, , stack) end |
.write_error(error:) ⇒ String
Encode an error result frame.
90 91 92 93 94 95 96 97 98 99 100 101 102 |
# File 'lib/bsv/wallet/wire/frame.rb', line 90 def write_error(error:) wire = error.to_wire msg_bytes = wire[:message].to_s.b stack_bytes = wire[:stack].to_s.b buf = String.new(encoding: 'BINARY') buf << [wire[:code]].pack('C') buf << encode_varint(msg_bytes.bytesize) buf << msg_bytes buf << encode_varint(stack_bytes.bytesize) buf << stack_bytes buf end |
.write_request(call:, originator:, params: nil) ⇒ String
Encode a request frame.
34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/bsv/wallet/wire/frame.rb', line 34 def write_request(call:, originator:, params: nil) originator_bytes = originator.to_s.b if originator_bytes.bytesize > MAX_ORIGINATOR_BYTES raise ArgumentError, "originator must be at most #{MAX_ORIGINATOR_BYTES} bytes, " \ "got #{originator_bytes.bytesize}" end buf = String.new(encoding: 'BINARY') buf << [call, originator_bytes.bytesize].pack('CC') buf << originator_bytes buf << params.b if params && !params.empty? buf end |
.write_result(payload: nil) ⇒ String
Encode a success result frame.
79 80 81 82 83 84 |
# File 'lib/bsv/wallet/wire/frame.rb', line 79 def write_result(payload: nil) buf = String.new(encoding: 'BINARY') buf << "\x00" buf << payload.b if payload && !payload.empty? buf end |