Class: Takagi::CBOR::Encoder
- Inherits:
-
Object
- Object
- Takagi::CBOR::Encoder
- Defined in:
- lib/takagi/cbor/encoder.rb,
sig/takagi/cbor/encoder.rbs
Overview
CBOR Encoder (RFC 8949)
Encodes Ruby objects to CBOR binary format. Optimized for IoT/CoAP workloads with minimal footprint.
Supported types:
- Integers (signed/unsigned, up to 64-bit)
- Floats (64-bit IEEE 754)
- Strings (UTF-8)
- Byte strings (binary data)
- Arrays
- Hashes (maps)
- Booleans (true/false)
- nil (null)
- Time (timestamp, tag 1)
Constant Summary collapse
- MAJOR_TYPE_UNSIGNED_INT =
CBOR Major Types (RFC 8949 §3)
0- MAJOR_TYPE_NEGATIVE_INT =
1- MAJOR_TYPE_BYTE_STRING =
2- MAJOR_TYPE_TEXT_STRING =
3- MAJOR_TYPE_ARRAY =
4- MAJOR_TYPE_MAP =
5- MAJOR_TYPE_TAG =
6- MAJOR_TYPE_SIMPLE =
7- SIMPLE_FALSE =
Simple values (RFC 8949 §3.3)
20- SIMPLE_TRUE =
21- SIMPLE_NULL =
22- SIMPLE_FLOAT64 =
27- TAG_EPOCH_TIMESTAMP =
Tag values (RFC 8949 §3.4)
1- MAX_UINT8 =
Maximum safe integer values
0xFF- MAX_UINT16 =
0xFFFF- MAX_UINT32 =
0xFFFFFFFF- MAX_UINT64 =
0xFFFFFFFFFFFFFFFF- TYPE_HANDLERS =
{ Integer => :encode_integer, Float => :encode_float, String => :encode_string, Array => :encode_array, Hash => :encode_map, TrueClass => :encode_simple, FalseClass => :encode_simple, NilClass => :encode_simple, Time => :encode_timestamp }.freeze
Class Method Summary collapse
-
.encode(obj) ⇒ String
Encode a Ruby object to CBOR bytes.
Instance Method Summary collapse
-
#encode(obj) ⇒ String
Encode a Ruby object to CBOR bytes.
-
#encode_array(arr) ⇒ Object
Encode array (major type 4) RFC 8949 §3.1.
-
#encode_byte_string(bytes) ⇒ Object
Encode byte string (major type 2) RFC 8949 §3.1.
-
#encode_float(float) ⇒ Object
Encode float (major type 7, additional info 27) RFC 8949 §3.3: Always use 64-bit IEEE 754.
-
#encode_integer(int) ⇒ Object
Encode integer (major type 0 or 1).
-
#encode_map(hash) ⇒ Object
Encode map/hash (major type 5) RFC 8949 §3.1.
-
#encode_negative_int(int) ⇒ Object
Encode negative integer (major type 1) RFC 8949 §3.1: -1 - n.
-
#encode_simple(obj) ⇒ Object
Encode simple values (major type 7) RFC 8949 §3.3.
-
#encode_string(str) ⇒ Object
Encode UTF-8 string (major type 3) RFC 8949 §3.1.
- #encode_symbol(symbol) ⇒ Object
-
#encode_timestamp(time) ⇒ Object
Encode timestamp (tag 1, epoch seconds) RFC 8949 §3.4.2.
-
#encode_unsigned_int(int) ⇒ Object
Encode unsigned integer (major type 0) RFC 8949 §3.1.
- #encode_value(obj) ⇒ Object
-
#encode_with_length(major_type, length) ⇒ Object
Encode major type with length/value RFC 8949 §3: Additional Information encoding.
- #handler_for(obj) ⇒ :encode_symbol, untyped
Class Method Details
.encode(obj) ⇒ String
Encode a Ruby object to CBOR bytes
78 79 80 |
# File 'lib/takagi/cbor/encoder.rb', line 78 def encode(obj) new.encode(obj) end |
Instance Method Details
#encode(obj) ⇒ String
Encode a Ruby object to CBOR bytes
88 89 90 |
# File 'lib/takagi/cbor/encoder.rb', line 88 def encode(obj) encode_value(obj) end |
#encode_array(arr) ⇒ Object
Encode array (major type 4) RFC 8949 §3.1
176 177 178 179 180 181 182 183 184 |
# File 'lib/takagi/cbor/encoder.rb', line 176 def encode_array(arr) result = encode_with_length(MAJOR_TYPE_ARRAY, arr.size) arr.each do |item| result << encode_value(item) end result end |
#encode_byte_string(bytes) ⇒ Object
Encode byte string (major type 2) RFC 8949 §3.1
168 169 170 171 172 |
# File 'lib/takagi/cbor/encoder.rb', line 168 def encode_byte_string(bytes) byte_length = bytes.bytesize encode_with_length(MAJOR_TYPE_BYTE_STRING, byte_length) + bytes end |
#encode_float(float) ⇒ Object
Encode float (major type 7, additional info 27) RFC 8949 §3.3: Always use 64-bit IEEE 754
148 149 150 151 152 153 154 |
# File 'lib/takagi/cbor/encoder.rb', line 148 def encode_float(float) # Major type 7, additional info 27 (64-bit float) major_byte = (MAJOR_TYPE_SIMPLE << 5) | SIMPLE_FLOAT64 # Pack as big-endian 64-bit float (network byte order) [major_byte].pack('C') + [float].pack('G') end |
#encode_integer(int) ⇒ Object
Encode integer (major type 0 or 1)
118 119 120 121 122 123 124 |
# File 'lib/takagi/cbor/encoder.rb', line 118 def encode_integer(int) if int >= 0 encode_unsigned_int(int) else encode_negative_int(int) end end |
#encode_map(hash) ⇒ Object
Encode map/hash (major type 5) RFC 8949 §3.1
188 189 190 191 192 193 194 195 196 197 |
# File 'lib/takagi/cbor/encoder.rb', line 188 def encode_map(hash) result = encode_with_length(MAJOR_TYPE_MAP, hash.size) hash.each do |key, value| result << encode_value(key) result << encode_value(value) end result end |
#encode_negative_int(int) ⇒ Object
Encode negative integer (major type 1) RFC 8949 §3.1: -1 - n
136 137 138 139 140 141 142 143 144 |
# File 'lib/takagi/cbor/encoder.rb', line 136 def encode_negative_int(int) # Convert to CBOR representation: -1 - n # Example: -1 => 0, -2 => 1, -500 => 499 n = -1 - int raise EncodeError, "Integer too small: #{int}" if n > MAX_UINT64 encode_with_length(MAJOR_TYPE_NEGATIVE_INT, n) end |
#encode_simple(obj) ⇒ Object
Encode simple values (major type 7) RFC 8949 §3.3
201 202 203 204 205 206 207 208 209 210 |
# File 'lib/takagi/cbor/encoder.rb', line 201 def encode_simple(obj) simple_value = case obj when false then SIMPLE_FALSE when true then SIMPLE_TRUE when nil then SIMPLE_NULL end major_byte = (MAJOR_TYPE_SIMPLE << 5) | simple_value [major_byte].pack('C') end |
#encode_string(str) ⇒ Object
Encode UTF-8 string (major type 3) RFC 8949 §3.1
158 159 160 161 162 163 164 |
# File 'lib/takagi/cbor/encoder.rb', line 158 def encode_string(str) # Ensure UTF-8 encoding utf8_str = str.encode('UTF-8') byte_length = utf8_str.bytesize encode_with_length(MAJOR_TYPE_TEXT_STRING, byte_length) + utf8_str end |
#encode_symbol(symbol) ⇒ Object
113 114 115 |
# File 'lib/takagi/cbor/encoder.rb', line 113 def encode_symbol(symbol) encode_string(symbol.to_s) end |
#encode_timestamp(time) ⇒ Object
Encode timestamp (tag 1, epoch seconds) RFC 8949 §3.4.2
214 215 216 217 218 219 220 221 222 |
# File 'lib/takagi/cbor/encoder.rb', line 214 def (time) # Tag 1: Epoch-based timestamp (integer seconds since 1970-01-01) tag_byte = encode_with_length(MAJOR_TYPE_TAG, TAG_EPOCH_TIMESTAMP) # Encode timestamp as integer (seconds since epoch) = time.to_i tag_byte + encode_value() end |
#encode_unsigned_int(int) ⇒ Object
Encode unsigned integer (major type 0) RFC 8949 §3.1
128 129 130 131 132 |
# File 'lib/takagi/cbor/encoder.rb', line 128 def encode_unsigned_int(int) raise EncodeError, "Integer too large: #{int}" if int > MAX_UINT64 encode_with_length(MAJOR_TYPE_UNSIGNED_INT, int) end |
#encode_value(obj) ⇒ Object
94 95 96 97 98 99 100 101 |
# File 'lib/takagi/cbor/encoder.rb', line 94 def encode_value(obj) handler = handler_for(obj) send(handler, obj) rescue EncodeError raise rescue StandardError => e raise EncodeError, "Encoding failed: #{e.}" end |
#encode_with_length(major_type, length) ⇒ Object
Encode major type with length/value RFC 8949 §3: Additional Information encoding
Additional info: 0-23: Value directly in additional info 24: 1-byte uint8 follows 25: 2-byte uint16 follows 26: 4-byte uint32 follows 27: 8-byte uint64 follows
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 |
# File 'lib/takagi/cbor/encoder.rb', line 233 def encode_with_length(major_type, length) if length < 24 # Value fits in additional info (0-23) major_byte = (major_type << 5) | length [major_byte].pack('C') elsif length <= MAX_UINT8 # 1-byte length follows (24-255) major_byte = (major_type << 5) | 24 [major_byte, length].pack('CC') elsif length <= MAX_UINT16 # 2-byte length follows (256-65535) major_byte = (major_type << 5) | 25 [major_byte].pack('C') + [length].pack('n') elsif length <= MAX_UINT32 # 4-byte length follows major_byte = (major_type << 5) | 26 [major_byte].pack('C') + [length].pack('N') elsif length <= MAX_UINT64 # 8-byte length follows major_byte = (major_type << 5) | 27 [major_byte].pack('C') + [length].pack('Q>') else raise EncodeError, "Length too large: #{length}" end end |
#handler_for(obj) ⇒ :encode_symbol, untyped
103 104 105 106 107 108 109 110 111 |
# File 'lib/takagi/cbor/encoder.rb', line 103 def handler_for(obj) return :encode_symbol if obj.is_a?(Symbol) TYPE_HANDLERS.each do |klass, method| return method if obj.is_a?(klass) end raise EncodeError, "Cannot encode #{obj.class}: #{obj.inspect}" end |