proofmode 0.9.0

Capture, share, and preserve verifiable photos and videos
Documentation
# Foreign callback support for ProofModeCallbacks.
#
# The UniFFI Ruby backend does not generate vtable callback infrastructure
# for traits with `#[uniffi::export(with_foreign)]`. This file adds that
# support so Ruby code can implement ProofModeCallbacks and pass instances
# to generate_proof / check_files.
#
# Usage:
#   require "proofmode"
#   require "proofmode_callbacks"
#
# Then subclass Proofmode::ProofModeCallbacks and override the 7 methods.

require "proofmode"

module Proofmode
  # Thread-safe handle map for storing foreign (Ruby) callback objects.
  class CallbackHandleMap
    def initialize
      @mutex = Mutex.new
      @map = {}
      @next_handle = 1
    end

    def insert(obj)
      @mutex.synchronize do
        handle = (@next_handle << 1) | 1 # odd handles = foreign
        @next_handle += 1
        @map[handle] = obj
        handle
      end
    end

    def get(handle)
      @mutex.synchronize do
        @map.fetch(handle) { raise "CallbackHandleMap: unknown handle #{handle}" }
      end
    end

    def remove(handle)
      @mutex.synchronize do
        @map.delete(handle)
      end
    end

    def clone_handle(handle)
      @mutex.synchronize do
        obj = @map.fetch(handle) { raise "CallbackHandleMap: unknown handle #{handle}" }
        new_handle = (@next_handle << 1) | 1
        @next_handle += 1
        @map[new_handle] = obj
        new_handle
      end
    end
  end

  CALLBACK_HANDLE_MAP = CallbackHandleMap.new

  # Access the private UniFFILib module using const_get
  _uniffi_lib = const_get(:UniFFILib)

  # Define vtable callback functions using FFI::Function.
  # Each function receives a handle, arguments as RustBuffers, an out_return
  # pointer, and a call_status pointer.

  _vtable_free = FFI::Function.new(:void, [:uint64]) do |handle|
    CALLBACK_HANDLE_MAP.remove(handle)
  rescue => e
    $stderr.puts "proofmode vtable free error: #{e}"
  end

  _vtable_clone = FFI::Function.new(:uint64, [:uint64]) do |handle|
    CALLBACK_HANDLE_MAP.clone_handle(handle)
  rescue => e
    $stderr.puts "proofmode vtable clone error: #{e}"
    0
  end

  _vtable_get_location = FFI::Function.new(:void, [:uint64, :pointer, :pointer]) do |handle, out_return, _call_status|
    obj = CALLBACK_HANDLE_MAP.get(handle)
    result = obj.get_location
    buf = RustBuffer.alloc_from_OptionalTypeLocationData(result)
    # Copy RustBuffer struct bytes into the out_return pointer
    out_return.put_uint64(0, buf[:capacity])
    out_return.put_uint64(8, buf[:len])
    out_return.put_pointer(16, buf[:data])
  rescue => e
    $stderr.puts "proofmode vtable get_location error: #{e}\n#{e.backtrace.first(5).join("\n")}"
  end

  _vtable_get_device_info = FFI::Function.new(:void, [:uint64, :pointer, :pointer]) do |handle, out_return, _call_status|
    obj = CALLBACK_HANDLE_MAP.get(handle)
    result = obj.get_device_info
    buf = RustBuffer.alloc_from_OptionalTypeDeviceData(result)
    out_return.put_uint64(0, buf[:capacity])
    out_return.put_uint64(8, buf[:len])
    out_return.put_pointer(16, buf[:data])
  rescue => e
    $stderr.puts "proofmode vtable get_device_info error: #{e}\n#{e.backtrace.first(5).join("\n")}"
  end

  _vtable_get_network_info = FFI::Function.new(:void, [:uint64, :pointer, :pointer]) do |handle, out_return, _call_status|
    obj = CALLBACK_HANDLE_MAP.get(handle)
    result = obj.get_network_info
    buf = RustBuffer.alloc_from_OptionalTypeNetworkData(result)
    out_return.put_uint64(0, buf[:capacity])
    out_return.put_uint64(8, buf[:len])
    out_return.put_pointer(16, buf[:data])
  rescue => e
    $stderr.puts "proofmode vtable get_network_info error: #{e}\n#{e.backtrace.first(5).join("\n")}"
  end

  # save_data: (handle, hash_buf, filename_buf, data_buf, out_return_i8_ptr, call_status_ptr)
  # The RustBuffer args are passed by value. In Ruby FFI, callback functions
  # receive by-value structs as pointers that we need to cast.
  _vtable_save_data = FFI::Function.new(:void, [:uint64, RustBuffer.by_value, RustBuffer.by_value, RustBuffer.by_value, :pointer, :pointer]) do |handle, hash_buf, filename_buf, data_buf, out_return, _call_status|
    obj = CALLBACK_HANDLE_MAP.get(handle)
    hash_str = hash_buf.consumeIntoString
    filename_str = filename_buf.consumeIntoString
    data_bytes = data_buf.consumeIntoBytes
    result = obj.save_data(hash_str, filename_str, data_bytes)
    out_return.put_int8(0, result ? 1 : 0)
  rescue => e
    $stderr.puts "proofmode vtable save_data error: #{e}\n#{e.backtrace.first(5).join("\n")}"
  end

  _vtable_save_text = FFI::Function.new(:void, [:uint64, RustBuffer.by_value, RustBuffer.by_value, RustBuffer.by_value, :pointer, :pointer]) do |handle, hash_buf, filename_buf, text_buf, out_return, _call_status|
    obj = CALLBACK_HANDLE_MAP.get(handle)
    hash_str = hash_buf.consumeIntoString
    filename_str = filename_buf.consumeIntoString
    text_str = text_buf.consumeIntoString
    result = obj.save_text(hash_str, filename_str, text_str)
    out_return.put_int8(0, result ? 1 : 0)
  rescue => e
    $stderr.puts "proofmode vtable save_text error: #{e}\n#{e.backtrace.first(5).join("\n")}"
  end

  _vtable_sign_data = FFI::Function.new(:void, [:uint64, RustBuffer.by_value, :pointer, :pointer]) do |handle, data_buf, out_return, _call_status|
    obj = CALLBACK_HANDLE_MAP.get(handle)
    data_bytes = data_buf.consumeIntoBytes
    result = obj.sign_data(data_bytes)
    buf = RustBuffer.alloc_from_Optionalbytes(result)
    out_return.put_uint64(0, buf[:capacity])
    out_return.put_uint64(8, buf[:len])
    out_return.put_pointer(16, buf[:data])
  rescue => e
    $stderr.puts "proofmode vtable sign_data error: #{e}\n#{e.backtrace.first(5).join("\n")}"
  end

  _vtable_report_progress = FFI::Function.new(:void, [:uint64, RustBuffer.by_value, :pointer, :pointer]) do |handle, message_buf, _out_return, _call_status|
    obj = CALLBACK_HANDLE_MAP.get(handle)
    message_str = message_buf.consumeIntoString
    obj.report_progress(message_str)
  rescue => e
    $stderr.puts "proofmode vtable report_progress error: #{e}\n#{e.backtrace.first(5).join("\n")}"
  end

  # Build the vtable as an array of function pointers.
  # Layout order: free, clone, get_location, get_device_info, get_network_info,
  #               save_data, save_text, sign_data, report_progress
  vtable_fns = [
    _vtable_free,
    _vtable_clone,
    _vtable_get_location,
    _vtable_get_device_info,
    _vtable_get_network_info,
    _vtable_save_data,
    _vtable_save_text,
    _vtable_sign_data,
    _vtable_report_progress,
  ]

  # Keep references alive to prevent GC
  VTABLE_REFS = vtable_fns.freeze

  # Allocate persistent memory for the vtable (array of function pointers)
  VTABLE_PTR = FFI::MemoryPointer.new(:pointer, vtable_fns.length, true)
  vtable_fns.each_with_index do |fn, i|
    VTABLE_PTR.put_pointer(i * FFI::Pointer.size, fn)
  end

  # Register the vtable with Rust
  _rust_call_status = const_get(:RustCallStatus).new
  _uniffi_lib.uniffi_proofmode_fn_init_callback_vtable_proofmodecallbacks(
    VTABLE_PTR, _rust_call_status
  )

  # Patch ProofModeCallbacks to support foreign (Ruby) implementations
  class ProofModeCallbacks
    class << self
      def uniffi_check_lower(inst)
        unless inst.is_a?(self)
          raise TypeError, "Expected a ProofModeCallbacks instance, got #{inst}"
        end
      end

      def uniffi_lower(inst)
        if inst.instance_variable_defined?(:@handle)
          # Rust-created object
          inst.uniffi_clone_handle
        else
          # Ruby-created (foreign) object
          CALLBACK_HANDLE_MAP.insert(inst)
        end
      end
    end
  end
end