Class: Oxygene::CID
- Inherits:
-
Object
- Object
- Oxygene::CID
- Defined in:
- lib/oxygene/cid.rb
Overview
Represents a Content Identifier (CID) of some piece of content like an ATProto record or a CAR section.
Only the DASL-compatible CID versions that are used in ATProto are supported:
- only CIDv1, not v0
- in binary CBOR form: with SHA-256 hash, 32 bytes hash size, and codec being either 0x71 (DRISL) or 0x55 (raw) (DRISL is used in CIDs of CAR sections, and raw codec is used in CIDs of blobs)
- in JSON string form: with 'b' prefix and Base32-encoded data, using lowercase alphabet and no '=' padding, and the CBOR form parameters listed above, resulting in an either "bafyrei" or "bafkrei" prefix
The CID instances can be created either from JSON strings or binary CBOR form, and the binary form can optionally include the \x00 prefix byte that is used when the binary CID is encoded in a CBOR tag 42 object. The instances will lazily convert between the two forms only when needed.
The code in this class is heavily optimized for performance when creating and processing a large number of CIDs e.g. when parsing a CAR repo or processing firehose events, for example:
- the input JSON or binary string is stored without copying or allocating new strings for modified versions, if possible
- the other form (JSON/CBOR) is memoized and only created on demand, not up front
- equality check tries to use whichever form exists in both instances if possible, to avoid unnecessary conversion
- validation code uses pre-generated header constants to avoid checking the headers byte by byte, and operates mostly on byte code numbers to avoid unnecessary string allocations
Related specifications:
Constant Summary collapse
- JSON_PREFIX =
Multibase prefix code for Base32, required for CIDs in JSON string form.
'b'- CBOR_TAG_PREFIX =
Multibase "identity" prefix code, required for binary CIDs stored in a CBOR tag 42 object.
"\x00".b.freeze
- DRISL_CODEC_ID =
DAG-CBOR or DRISL (used e.g. in CIDs of CAR sections)
0x71- RAW_CODEC_ID =
Raw binary (used in CIDs of blobs)
0x55
Class Method Summary collapse
-
.from_cbor_tag(tag) ⇒ CID
Builds a CID from a CBOR tag 42 value.
-
.from_json(string) ⇒ CID
Builds a CID from a Base32-encoded JSON form string.
Instance Method Summary collapse
-
#==(other) ⇒ Boolean
(also: #eql?)
Compares this CID with another CID to see if they're equal.
-
#cbor_form ⇒ String
Returns the CID data in binary form with a null byte identity prefix, as stored in CBOR tag 42.
-
#hash ⇒ Integer
Returns a hash code for the purposes of a Set or Hash.
-
#initialize(data, binary: true, cbor_prefix: false, codec: nil) ⇒ CID
constructor
Creates a CID instance from binary/CBOR data or a JSON string form.
-
#inspect ⇒ String
A representation of the CID object for debugging.
-
#json_form ⇒ String
Returns the CID's Base32-encoded JSON string representation.
-
#raw_data ⇒ String
(also: #data)
Returns the CID data in binary form, without the null byte identity prefix from CBOR tag.
-
#to_s ⇒ String
Returns the CID's Base32-encoded JSON string representation (same as #json_form).
Constructor Details
#initialize(data, binary: true, cbor_prefix: false, codec: nil) ⇒ CID
Creates a CID instance from binary/CBOR data or a JSON string form.
Three possible kinds of values are accepted as input:
- a binary string with \x00 identity prefix, as used when included in a
CBOR tag object's value - use
binary: trueandcbor_prefix: true - a binary string without the identity prefix, as used e.g. when decoded
from a CAR archive - use
binary: truewithoutcbor_prefix - a JSON string - use
binary: false
For backwards compatibility, the default when only the input argument is passed is to assume the "binary without CBOR prefix" form. For performance, for the "binary with CBOR prefix" and the JSON form types, the input string itself is stored without copying and frozen to prevent modification. If you need to modify the string later at the call site, call dup when passing the input argument here. (For the non-prefixed binary version, the input string is copied to add the prefix.)
Optionally, the initializer can also enforce that the CID has to use a selected one of the two available codecs (DRISL/DAG-CBOR vs. raw binary).
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
# File 'lib/oxygene/cid.rb', line 140 def initialize(data, binary: true, cbor_prefix: false, codec: nil) raise ArgumentError.new("Data cannot be nil") if data.nil? codec_id = case codec when nil then nil when :drisl then DRISL_CODEC_ID when :raw then RAW_CODEC_ID else raise ArgumentError.new("Unexpected CID codec: #{codec.inspect}") end if binary if cbor_prefix validate_binary_form(data, true, codec_id) @cbor_form = data.freeze else validate_binary_form(data, false, codec_id) @cbor_form = (CBOR_TAG_PREFIX + data).freeze end else raise ArgumentError.new("cbor_prefix cannot be used with JSON input") if cbor_prefix validate_json_form(data, codec_id) @json_form = data.freeze end end |
Class Method Details
Instance Method Details
#==(other) ⇒ Boolean Also known as: eql?
Compares this CID with another CID to see if they're equal.
If both CIDs have a generated CBOR or JSON form, those forms are used for comparison without conversion. If the two only have different forms, the JSON CID is converted to binary for comparison.
229 230 231 232 233 234 235 236 237 238 239 |
# File 'lib/oxygene/cid.rb', line 229 def ==(other) return false unless other.is_a?(CID) if @cbor_form && (other_cbor = other.instance_variable_get('@cbor_form')) @cbor_form == other_cbor elsif @json_form && (other_json = other.instance_variable_get('@json_form')) @json_form == other_json else self.cbor_form == other.cbor_form end end |
#cbor_form ⇒ String
Returns the CID data in binary form with a null byte identity prefix, as stored in CBOR tag 42.
If the CID was created from a JSON form, the data is decoded from Base32 (and memoized). If it was created from a CBOR tag, the input is returned directly.
174 175 176 |
# File 'lib/oxygene/cid.rb', line 174 def cbor_form @cbor_form ||= Base32.decode(@json_form, 1, CBOR_TAG_PREFIX).freeze end |
#hash ⇒ Integer
Returns a hash code for the purposes of a Set or Hash.
The binary CBOR form of the CID is used to derive the hash.
249 250 251 |
# File 'lib/oxygene/cid.rb', line 249 def hash cbor_form.hash end |
#inspect ⇒ String
Returns a representation of the CID object for debugging.
216 217 218 |
# File 'lib/oxygene/cid.rb', line 216 def inspect "CID(\"#{json_form}\")" end |
#json_form ⇒ String
Returns the CID's Base32-encoded JSON string representation.
If the CID was created from a binary form, the data is encoded into Base32 (and memoized). If it was created from a JSON form, the input is returned directly.
202 203 204 |
# File 'lib/oxygene/cid.rb', line 202 def json_form @json_form ||= Base32.encode(@cbor_form, 1, JSON_PREFIX).freeze end |
#raw_data ⇒ String Also known as: data
Returns the CID data in binary form, without the null byte identity prefix from CBOR tag.
If the CID was created from a JSON form, the data is decoded from Base32 (and memoized).
185 186 187 188 189 190 191 |
# File 'lib/oxygene/cid.rb', line 185 def raw_data @raw_data ||= if @cbor_form @cbor_form.byteslice(1, @cbor_form.bytesize - 1).freeze else Base32.decode(@json_form, 1).freeze end end |
#to_s ⇒ String
Returns the CID's Base32-encoded JSON string representation (same as #json_form).
210 211 212 |
# File 'lib/oxygene/cid.rb', line 210 def to_s json_form end |