iscc-lib 0.5.0 → 0.6.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 715e7aff39d5570a55f1bde98bd6c8c477152c870b9eff2f3e83a0e3744a100c
4
- data.tar.gz: 6f13f0a34d8adc6eb90e1aebc2dabca0cf8112207a749ca39d992d25b8687470
3
+ metadata.gz: 50fc883dc45f0160658cce96aca449059dcd83f1cd5a344c6254659e0214370c
4
+ data.tar.gz: cde84cb2f3bff34d8c0ae0bdeda3d2064dc852e3055fcfb2e3dff207240a5848
5
5
  SHA512:
6
- metadata.gz: e0b35d6acd961e3ab9450f18d09ad8c943a3292e3d2cc3ac57dcf25b38b720f4554bd315ac6a71287ef1d7793acf2961efccbc40ecdeb08ab7af026b52f85dc9
7
- data.tar.gz: 41f08243ccffbabab385911994c6dc5784b49476f8263691c6ae6104f45f432785d50265a9f80ce961d89d5f0c5495c4ad5bcc34903847c142cbd3493cb588a8
6
+ metadata.gz: b96e334c9c24acd5261bdc5d619f7eb92f606a496fe2b59389e76e4c6da0bfa3775969e48d42253921300d7cd3290725fa9975bdb69087cfed0eabadc78fab62
7
+ data.tar.gz: 449a1ca2f2d40441fcb871ce67b5fe7b45f61a7a08b14d774dcb5a6dd636ca5994fde435fa73ed0bb67abedf3f90cebcea970a8126e5a29ed888bfc16c926cd9
@@ -2,5 +2,5 @@
2
2
 
3
3
  # Version constant for the iscc-lib gem (synced from root Cargo.toml).
4
4
  module IsccLib
5
- VERSION = "0.5.0"
5
+ VERSION = "0.6.0"
6
6
  end
data/lib/iscc_lib.rb CHANGED
@@ -63,6 +63,9 @@ module IsccLib
63
63
  # Result from gen_sum_code_v0.
64
64
  class SumCodeResult < Result; end
65
65
 
66
+ # Result from gen_iscc_id_v1.
67
+ class IdCodeResult < Result; end
68
+
66
69
  # Generate a Meta-Code from name and optional metadata.
67
70
  #
68
71
  # @param name [String] content name (required, non-empty)
@@ -157,6 +160,21 @@ module IsccLib
157
160
  SumCodeResult[_gen_sum_code_v0(path, bits, wide, add_units)]
158
161
  end
159
162
 
163
+ # Generate an ISCC-IDv1 from a timestamp and a HUB-ID (experimental).
164
+ #
165
+ # Packs a 52-bit microsecond UTC timestamp and a 12-bit HUB-ID into an
166
+ # ISCC-ID unit with realm as SubType and Version V1. There is no dedicated
167
+ # decoder — use iscc_decode to decode.
168
+ #
169
+ # @param timestamp [Integer] microsecond UTC timestamp (0 <= timestamp < 2^52)
170
+ # @param hub_id [Integer] HUB-ID (0 <= hub_id < 4096)
171
+ # @param realm [Integer] realm (0 = testnet, 1 = mainnet)
172
+ # @return [IdCodeResult] hash with iscc
173
+ # @raise [RuntimeError] if any parameter is negative or out of range
174
+ def self.gen_iscc_id_v1(timestamp, hub_id, realm)
175
+ IdCodeResult[_gen_iscc_id_v1(timestamp, hub_id, realm)]
176
+ end
177
+
160
178
  # Streaming Data-Code generator (reopens native class).
161
179
  #
162
180
  # Incrementally processes data with content-defined chunking and MinHash
data/src/lib.rs CHANGED
@@ -4,10 +4,10 @@
4
4
  //! under the `IsccLib` module. The pure Ruby wrapper in `lib/iscc_lib.rb`
5
5
  //! provides idiomatic result classes and keyword arguments.
6
6
  //!
7
- //! Symbols (32 of 32):
7
+ //! Symbols (33 of 33):
8
8
  //! - `gen_meta_code_v0`, `gen_text_code_v0`, `gen_image_code_v0`, `gen_audio_code_v0`
9
9
  //! - `gen_video_code_v0`, `gen_mixed_code_v0`, `gen_data_code_v0`
10
- //! - `gen_instance_code_v0`, `gen_iscc_code_v0`, `gen_sum_code_v0`
10
+ //! - `gen_instance_code_v0`, `gen_iscc_code_v0`, `gen_sum_code_v0`, `gen_iscc_id_v1`
11
11
  //! - `text_clean`, `text_remove_newlines`, `text_trim`, `text_collapse`
12
12
  //! - `encode_base64`, `iscc_decompose`, `encode_component`, `iscc_decode`
13
13
  //! - `json_to_data_url`, `conformance_selftest`
@@ -17,12 +17,15 @@
17
17
  //! - Constants: META_TRIM_NAME, META_TRIM_DESCRIPTION, META_TRIM_META,
18
18
  //! IO_READ_SIZE, TEXT_NGRAM_SIZE
19
19
 
20
- use magnus::{Error, RArray, RHash, RString, Ruby, TryConvert, function, method, prelude::*};
20
+ use magnus::{
21
+ Error, Integer, RArray, RHash, RString, Ruby, TryConvert, function, method, prelude::*,
22
+ };
21
23
  use std::cell::RefCell;
22
24
 
23
25
  /// Map an `IsccError` to a Magnus `RuntimeError`.
24
26
  fn to_magnus_err(e: iscc_lib::IsccError) -> Error {
25
- Error::new(magnus::exception::runtime_error(), e.to_string())
27
+ let ruby = Ruby::get().expect("called from Ruby");
28
+ Error::new(ruby.exception_runtime_error(), e.to_string())
26
29
  }
27
30
 
28
31
  /// Generate a Meta-Code from name and optional metadata.
@@ -187,6 +190,55 @@ fn gen_sum_code_v0(path: String, bits: u32, wide: bool, add_units: bool) -> Resu
187
190
  Ok(hash)
188
191
  }
189
192
 
193
+ /// Validate an ISCC-IDv1 parameter before narrowing to a fixed-width integer.
194
+ ///
195
+ /// Ruby Integers are arbitrary precision, so each parameter is taken as a
196
+ /// `magnus::Integer` and range-checked here — converting in the signature
197
+ /// would raise `RangeError` during argument marshalling for values beyond
198
+ /// `i64`, breaking the normative `timestamp -> hub_id -> realm` first-failure
199
+ /// order. Rejects negative and out-of-range (`>= max_exclusive`) values with
200
+ /// a `RuntimeError` so an invalid Ruby Integer raises instead of being
201
+ /// silently narrowed to a wrong ID.
202
+ fn checked(value: Integer, max_exclusive: u64, name: &str) -> Result<u64, Error> {
203
+ let ruby = Ruby::get().expect("called from Ruby");
204
+ let v = value.to_u64().map_err(|_| {
205
+ Error::new(
206
+ ruby.exception_runtime_error(),
207
+ format!("Invalid {name}: out of range"),
208
+ )
209
+ })?;
210
+ if v >= max_exclusive {
211
+ return Err(Error::new(
212
+ ruby.exception_runtime_error(),
213
+ format!("Invalid {name}: {v}"),
214
+ ));
215
+ }
216
+ Ok(v)
217
+ }
218
+
219
+ /// Generate an ISCC-IDv1 from a timestamp and a HUB-ID (experimental).
220
+ ///
221
+ /// Packs a 52-bit microsecond UTC `timestamp` into the high bits and a 12-bit
222
+ /// `hub_id` (0-4095) into the low bits, encoding the result as an ISCC-ID unit
223
+ /// with `realm` (0 = testnet, 1 = mainnet) as SubType and Version `V1`. The
224
+ /// three semantic thresholds are validated in `timestamp` → `hub_id` → `realm`
225
+ /// order before narrowing, so the first failing check wins.
226
+ ///
227
+ /// Returns a Ruby Hash with key: `iscc`. Raises `RuntimeError` if any parameter
228
+ /// is negative, or if `timestamp >= 2^52`, `hub_id >= 2^12`, or `realm` is not
229
+ /// `0` or `1` — for any Integer magnitude, including values beyond `i64`.
230
+ fn gen_iscc_id_v1(timestamp: Integer, hub_id: Integer, realm: Integer) -> Result<RHash, Error> {
231
+ let timestamp = checked(timestamp, 1u64 << 52, "timestamp")?;
232
+ let hub_id = checked(hub_id, 4096, "hub_id")?;
233
+ let realm = checked(realm, 2, "realm")?;
234
+ let r =
235
+ iscc_lib::gen_iscc_id_v1(timestamp, hub_id as u16, realm as u8).map_err(to_magnus_err)?;
236
+ let ruby = Ruby::get().expect("called from Ruby");
237
+ let hash = ruby.hash_new();
238
+ hash.aset("iscc", r.iscc)?;
239
+ Ok(hash)
240
+ }
241
+
190
242
  /// Clean and normalize text for display.
191
243
  ///
192
244
  /// Applies NFKC normalization, removes control characters (except newlines),
@@ -265,7 +317,7 @@ fn iscc_decode(iscc: String) -> Result<RArray, Error> {
265
317
  arr.push(st)?;
266
318
  arr.push(vs)?;
267
319
  arr.push(li)?;
268
- arr.push(RString::from_slice(&digest))?;
320
+ arr.push(ruby.str_from_slice(&digest))?;
269
321
  Ok(arr)
270
322
  }
271
323
 
@@ -307,7 +359,8 @@ fn alg_simhash(hash_digests: RArray) -> Result<RString, Error> {
307
359
  })
308
360
  .collect::<Result<Vec<_>, Error>>()?;
309
361
  let result = iscc_lib::alg_simhash(&digests).map_err(to_magnus_err)?;
310
- Ok(RString::from_slice(&result))
362
+ let ruby = Ruby::get().expect("called from Ruby");
363
+ Ok(ruby.str_from_slice(&result))
311
364
  }
312
365
 
313
366
  /// Compute a 256-bit MinHash digest from 32-bit integer features.
@@ -315,7 +368,8 @@ fn alg_simhash(hash_digests: RArray) -> Result<RString, Error> {
315
368
  /// Returns a 32-byte binary String.
316
369
  fn alg_minhash_256(features: Vec<u32>) -> RString {
317
370
  let result = iscc_lib::alg_minhash_256(&features);
318
- RString::from_slice(&result)
371
+ let ruby = Ruby::get().expect("called from Ruby");
372
+ ruby.str_from_slice(&result)
319
373
  }
320
374
 
321
375
  /// Split data into content-defined chunks using gear rolling hash.
@@ -330,7 +384,7 @@ fn alg_cdc_chunks(data: RString, utf32: bool, avg_chunk_size: u32) -> Result<RAr
330
384
  let ruby = Ruby::get().expect("called from Ruby");
331
385
  let arr = ruby.ary_new_capa(chunks.len());
332
386
  for chunk in chunks {
333
- arr.push(RString::from_slice(chunk))?;
387
+ arr.push(ruby.str_from_slice(chunk))?;
334
388
  }
335
389
  Ok(arr)
336
390
  }
@@ -348,7 +402,8 @@ fn soft_hash_video_v0(frame_sigs: RArray, bits: u32) -> Result<RString, Error> {
348
402
  })
349
403
  .collect::<Result<Vec<_>, Error>>()?;
350
404
  let result = iscc_lib::soft_hash_video_v0(&frames, bits).map_err(to_magnus_err)?;
351
- Ok(RString::from_slice(&result))
405
+ let ruby = Ruby::get().expect("called from Ruby");
406
+ Ok(ruby.str_from_slice(&result))
352
407
  }
353
408
 
354
409
  /// Streaming Data-Code generator for Ruby.
@@ -372,10 +427,11 @@ impl RbDataHasher {
372
427
  ///
373
428
  /// Raises `RuntimeError` if called after `finalize`.
374
429
  fn update(&self, data: RString) -> Result<(), Error> {
430
+ let ruby = Ruby::get().expect("called from Ruby");
375
431
  let mut inner = self.inner.borrow_mut();
376
432
  let hasher = inner.as_mut().ok_or_else(|| {
377
433
  Error::new(
378
- magnus::exception::runtime_error(),
434
+ ruby.exception_runtime_error(),
379
435
  "DataHasher already finalized",
380
436
  )
381
437
  })?;
@@ -391,14 +447,14 @@ impl RbDataHasher {
391
447
  /// Returns an `RHash` with key `"iscc"`. Raises `RuntimeError` if
392
448
  /// called more than once.
393
449
  fn finalize(&self, bits: u32) -> Result<RHash, Error> {
450
+ let ruby = Ruby::get().expect("called from Ruby");
394
451
  let hasher = self.inner.borrow_mut().take().ok_or_else(|| {
395
452
  Error::new(
396
- magnus::exception::runtime_error(),
453
+ ruby.exception_runtime_error(),
397
454
  "DataHasher already finalized",
398
455
  )
399
456
  })?;
400
457
  let r = hasher.finalize(bits).map_err(to_magnus_err)?;
401
- let ruby = Ruby::get().expect("called from Ruby");
402
458
  let hash = ruby.hash_new();
403
459
  hash.aset("iscc", r.iscc)?;
404
460
  Ok(hash)
@@ -426,10 +482,11 @@ impl RbInstanceHasher {
426
482
  ///
427
483
  /// Raises `RuntimeError` if called after `finalize`.
428
484
  fn update(&self, data: RString) -> Result<(), Error> {
485
+ let ruby = Ruby::get().expect("called from Ruby");
429
486
  let mut inner = self.inner.borrow_mut();
430
487
  let hasher = inner.as_mut().ok_or_else(|| {
431
488
  Error::new(
432
- magnus::exception::runtime_error(),
489
+ ruby.exception_runtime_error(),
433
490
  "InstanceHasher already finalized",
434
491
  )
435
492
  })?;
@@ -445,14 +502,14 @@ impl RbInstanceHasher {
445
502
  /// Returns an `RHash` with keys `"iscc"`, `"datahash"`, `"filesize"`.
446
503
  /// Raises `RuntimeError` if called more than once.
447
504
  fn finalize(&self, bits: u32) -> Result<RHash, Error> {
505
+ let ruby = Ruby::get().expect("called from Ruby");
448
506
  let hasher = self.inner.borrow_mut().take().ok_or_else(|| {
449
507
  Error::new(
450
- magnus::exception::runtime_error(),
508
+ ruby.exception_runtime_error(),
451
509
  "InstanceHasher already finalized",
452
510
  )
453
511
  })?;
454
512
  let r = hasher.finalize(bits).map_err(to_magnus_err)?;
455
- let ruby = Ruby::get().expect("called from Ruby");
456
513
  let hash = ruby.hash_new();
457
514
  hash.aset("iscc", r.iscc)?;
458
515
  hash.aset("datahash", r.datahash)?;
@@ -477,6 +534,7 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
477
534
  module.define_module_function("_gen_instance_code_v0", function!(gen_instance_code_v0, 2))?;
478
535
  module.define_module_function("_gen_iscc_code_v0", function!(gen_iscc_code_v0, 2))?;
479
536
  module.define_module_function("_gen_sum_code_v0", function!(gen_sum_code_v0, 4))?;
537
+ module.define_module_function("_gen_iscc_id_v1", function!(gen_iscc_id_v1, 3))?;
480
538
 
481
539
  // Text utility functions
482
540
  module.define_module_function("text_clean", function!(text_clean, 1))?;
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: iscc-lib
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Titusz Pan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-06-18 00:00:00.000000000 Z
11
+ date: 2026-07-30 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: High-performance Ruby bindings for ISO 24138:2024 (ISCC). Native Rust
14
14
  extension via Magnus for content identification and matching.