Module: Hyperion::H2Codec

Defined in:
lib/hyperion/h2_codec.rb,
ext/hyperion_http/h2_codec_glue.c

Overview

Phase 6 (RFC §3 2.0.0) — native HPACK encoder/decoder + frame primitives implemented in Rust. The Ruby side here is a thin Fiddle-based loader; all real work happens in ‘ext/hyperion_h2_codec`.

The integration is OPT-IN at runtime: ‘Http2Handler` checks `Hyperion::H2Codec.available?` and uses the native path only when the cdylib loaded successfully. Operators on systems without Rust (older Debians, locked-down CI runners, JRuby) get the existing `protocol-http2` Ruby HPACK path automatically — no boot-time error.

ABI version: bumped on any breaking C ABI change. Ruby refuses to load a binary that disagrees so a stale on-disk codec from an older gem install can’t crash the process.

Defined Under Namespace

Modules: CGlue Classes: Decoder, Encoder, OutputOverflow

Constant Summary collapse

EXPECTED_ABI =
1

Class Method Summary collapse

Class Method Details

.available?Boolean

Try to load the native cdylib. Sets ‘@available = true/false`. Idempotent — second call is a no-op.

Returns:

  • (Boolean)


32
33
34
35
# File 'lib/hyperion/h2_codec.rb', line 32

def self.available?
  load!
  @available == true
end

.candidate_pathsObject



412
413
414
415
416
417
418
# File 'lib/hyperion/h2_codec.rb', line 412

def self.candidate_paths
  gem_lib = File.expand_path('../hyperion_h2_codec', __dir__)
  ext_target = File.expand_path('../../ext/hyperion_h2_codec/target/release', __dir__)
  %w[libhyperion_h2_codec.dylib libhyperion_h2_codec.so].flat_map do |name|
    [File.join(gem_lib, name), File.join(ext_target, name)]
  end
end

.cglue_available?Boolean

2.4-A — has the C glue (‘Hyperion::H2Codec::CGlue`) loaded AND successfully resolved the Rust HPACK symbols via dlopen/dlsym? Distinct from `available?` because CGlue can fail to load (older systems without dlfcn, hardened sandboxes blocking dlopen) while the Fiddle path still works. When this is true the per-call encode/decode hot path bypasses Fiddle entirely.

Returns:

  • (Boolean)


43
44
45
46
# File 'lib/hyperion/h2_codec.rb', line 43

def self.cglue_available?
  load!
  @cglue_available == true
end

.decoder_decode(ptr, input, in_len, out, cap) ⇒ Object



448
449
450
# File 'lib/hyperion/h2_codec.rb', line 448

def self.decoder_decode(ptr, input, in_len, out, cap)
  @decoder_dec_fn.call(ptr, input, in_len, out, cap)
end

.decoder_free(ptr) ⇒ Object



444
445
446
# File 'lib/hyperion/h2_codec.rb', line 444

def self.decoder_free(ptr)
  @decoder_free_fn.call(ptr)
end

.decoder_newObject



440
441
442
# File 'lib/hyperion/h2_codec.rb', line 440

def self.decoder_new
  @decoder_new_fn.call
end

.encoder_encode(ptr, names, name_lens, vals, val_lens, count, out, cap) ⇒ Object



431
432
433
# File 'lib/hyperion/h2_codec.rb', line 431

def self.encoder_encode(ptr, names, name_lens, vals, val_lens, count, out, cap)
  @encoder_enc_fn.call(ptr, names, name_lens, vals, val_lens, count, out, cap)
end

.encoder_encode_v2(ptr, blob, blob_len, argv, argv_count, out, out_cap) ⇒ Object

fix-B (2.2.x) — v2 flat-blob encode. See lib.rs:hyperion_h2_codec_encoder_encode_v2.



436
437
438
# File 'lib/hyperion/h2_codec.rb', line 436

def self.encoder_encode_v2(ptr, blob, blob_len, argv, argv_count, out, out_cap)
  @encoder_enc_v2_fn.call(ptr, blob, blob_len, argv, argv_count, out, out_cap)
end

.encoder_free(ptr) ⇒ Object



427
428
429
# File 'lib/hyperion/h2_codec.rb', line 427

def self.encoder_free(ptr)
  @encoder_free_fn.call(ptr)
end

.encoder_newObject

FFI wrappers — kept thin so callers don’t see Fiddle::Pointer types. Each method is a one-liner that the Encoder/Decoder classes above invoke.



423
424
425
# File 'lib/hyperion/h2_codec.rb', line 423

def self.encoder_new
  @encoder_new_fn.call
end

.install_cglue(path) ⇒ Object

2.4-A — wire the C extension’s dlopen-based path. We require the bundled C extension (already loaded by ‘c_parser.rb` at gem boot in normal use, but we guard the constant lookup in case someone required `hyperion/h2_codec` directly without the C ext). Returns true iff CGlue is now installed and `encoder_encode_v3` is safe to call.



402
403
404
405
406
407
408
409
410
# File 'lib/hyperion/h2_codec.rb', line 402

def self.install_cglue(path)
  @cglue_available = false
  return unless defined?(Hyperion::H2Codec::CGlue)
  return unless Hyperion::H2Codec::CGlue.respond_to?(:install)

  @cglue_available = Hyperion::H2Codec::CGlue.install(path) ? true : false
rescue StandardError
  @cglue_available = false
end

.load!Object

rubocop:disable Metrics/MethodLength



329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# File 'lib/hyperion/h2_codec.rb', line 329

def self.load!
  return unless @available.nil?

  @available = false

  path = candidate_paths.find { |p| File.exist?(p) }
  unless path
    @lib = nil
    return
  end

  @lib = Fiddle.dlopen(path)

  @abi_fn = Fiddle::Function.new(@lib['hyperion_h2_codec_abi_version'],
                                 [], Fiddle::TYPE_INT)
  abi = @abi_fn.call
  if abi != EXPECTED_ABI
    warn "[hyperion] H2Codec ABI mismatch (got #{abi}, expected #{EXPECTED_ABI}); using Ruby fallback"
    @lib = nil
    return
  end

  @encoder_new_fn  = Fiddle::Function.new(@lib['hyperion_h2_codec_encoder_new'],
                                          [], Fiddle::TYPE_VOIDP)
  @encoder_free_fn = Fiddle::Function.new(@lib['hyperion_h2_codec_encoder_free'],
                                          [Fiddle::TYPE_VOIDP], Fiddle::TYPE_VOID)
  @encoder_enc_fn  = Fiddle::Function.new(@lib['hyperion_h2_codec_encoder_encode'],
                                          [Fiddle::TYPE_VOIDP, Fiddle::TYPE_VOIDP, Fiddle::TYPE_VOIDP,
                                           Fiddle::TYPE_VOIDP, Fiddle::TYPE_VOIDP, Fiddle::TYPE_INT,
                                           Fiddle::TYPE_VOIDP, Fiddle::TYPE_INT],
                                          Fiddle::TYPE_INT)
  # fix-B (2.2.x) — v2 flat-blob encode entry.
  # Signature: (handle, blob_ptr, blob_len, argv_ptr, argv_count, out_ptr, out_cap) -> i64
  # Sizes are passed as size_t so Fiddle::TYPE_SIZE_T matches the
  # Rust `usize` exactly on both 64-bit Linux and macOS arm64.
  @encoder_enc_v2_fn = Fiddle::Function.new(@lib['hyperion_h2_codec_encoder_encode_v2'],
                                            [Fiddle::TYPE_VOIDP, Fiddle::TYPE_VOIDP, Fiddle::TYPE_SIZE_T,
                                             Fiddle::TYPE_VOIDP, Fiddle::TYPE_SIZE_T,
                                             Fiddle::TYPE_VOIDP, Fiddle::TYPE_SIZE_T],
                                            Fiddle::TYPE_LONG_LONG)
  @decoder_new_fn  = Fiddle::Function.new(@lib['hyperion_h2_codec_decoder_new'],
                                          [], Fiddle::TYPE_VOIDP)
  @decoder_free_fn = Fiddle::Function.new(@lib['hyperion_h2_codec_decoder_free'],
                                          [Fiddle::TYPE_VOIDP], Fiddle::TYPE_VOID)
  @decoder_dec_fn  = Fiddle::Function.new(@lib['hyperion_h2_codec_decoder_decode'],
                                          [Fiddle::TYPE_VOIDP, Fiddle::TYPE_VOIDP, Fiddle::TYPE_INT,
                                           Fiddle::TYPE_VOIDP, Fiddle::TYPE_INT],
                                          Fiddle::TYPE_INT)

  @available = true

  # 2.4-A — try to install the C glue with the same path the
  # Fiddle loader just used. CGlue is defined by the bundled C
  # extension (`hyperion_http/hyperion_http.bundle`); if that
  # extension didn't compile or the dlopen fails, `@cglue_available`
  # stays nil and Encoder/Decoder transparently fall back to the
  # v2 (Fiddle) path. No warning on this branch — it's purely a
  # perf optimization, not a correctness gate.
  install_cglue(path)
rescue Fiddle::DLError, StandardError => e
  warn "[hyperion] H2Codec failed to load (#{e.class}: #{e.message}); using Ruby fallback"
  @lib = nil
  @available = false
  @cglue_available = false
end

.reset!Object

Force a reload (test seam). Unsets the memoized state so the next ‘available?` call probes the filesystem again.



50
51
52
53
54
# File 'lib/hyperion/h2_codec.rb', line 50

def self.reset!
  @available = nil
  @cglue_available = nil
  @lib = nil
end