Module: Hop::FFI

Defined in:
lib/hop/ffi.rb

Constant Summary collapse

P =
Fiddle::TYPE_VOIDP
I =
Fiddle::TYPE_INT
LL =
Fiddle::TYPE_LONG_LONG
SZ =
Fiddle::TYPE_SIZE_T
CH =
Fiddle::TYPE_CHAR
V =
Fiddle::TYPE_VOID
ABI_EXPECTED =
3
LIB =
Fiddle.dlopen(lib_path)
ABI_VERSION =
fn("hop_abi_version", [], I)
NODE_NEW =
fn("hop_node_new", [], P)
NODE_WITH_SECRET =
fn("hop_node_with_secret", [P, SZ], P)
NODE_FREE =
fn("hop_node_free", [P], V)
NODE_ADDRESS =
fn("hop_node_address", [P, P], CH)
NODE_TICK =
fn("hop_node_tick", [P, LL], V)
fn("hop_link_up", [P, LL, I], V)
BYTES_RECEIVED =
fn("hop_bytes_received", [P, LL, P, SZ], V)
fn("hop_link_down", [P, LL], V)
DRAIN_OUTGOING =
fn("hop_drain_outgoing", [P, P, P], V)
SUBSCRIBE =
fn("hop_subscribe", [P, P], V)
PUBLISH_PREKEY =
fn("hop_publish_prekey", [P], CH)
SEND_SERVICE_REQUEST =
fn("hop_send_service_request", [P, P, P, P, P, SZ, P], CH)
SEND_SERVICE_RESPONSE =
fn("hop_send_service_response", [P, P, P, I, P, SZ], CH)
POLL_SERVICE_REQUESTS =
fn("hop_poll_service_requests", [P, P, P], V)
POLL_SERVICE_RESPONSES =
fn("hop_poll_service_responses", [P, P, P], V)
ADDRESS_TO_BASE58 =
fn("hop_address_to_base58", [P, P, SZ], SZ)
ADDRESS_FROM_BASE58 =
fn("hop_address_from_base58", [P, P], CH)
SIGN_REACH_RECORD =
fn("hop_sign_reach_record", [P, P, I, P, P], V)
VERIFY_REACH_RECORD =
fn("hop_verify_reach_record", [P, SZ, LL, P, P], CH)
CLUSTER_JOIN =

Endpoint clustering (DESIGN.md ยง40).

fn("hop_cluster_join", [P, P], V)
CLUSTER_JOIN_PASSPHRASE =
fn("hop_cluster_join_passphrase", [P, P, SZ], V)
CLUSTER_MEMBERS =
fn("hop_cluster_members", [P], I)
CLUSTER_SET_QUORUM =
fn("hop_cluster_set_quorum", [P, I], V)
Closure =
Fiddle::Closure::BlockCaller

Class Method Summary collapse

Class Method Details

.address(node) ⇒ Object



91
92
93
94
95
# File 'lib/hop/ffi.rb', line 91

def self.address(node)
  out = Fiddle::Pointer.malloc(32, Fiddle::RUBY_FREE)
  NODE_ADDRESS.call(node, out)
  out[0, 32].b
end

.assert_abi!Object



67
68
69
70
# File 'lib/hop/ffi.rb', line 67

def self.assert_abi!
  got = ABI_VERSION.call
  raise "libhop ABI mismatch: wrapper expects #{ABI_EXPECTED}, library reports #{got}" if got != ABI_EXPECTED
end

.cluster_join(node, secret) ⇒ Object



85
# File 'lib/hop/ffi.rb', line 85

def self.cluster_join(node, secret) = CLUSTER_JOIN.call(node, secret)

.cluster_join_passphrase(node, pass) ⇒ Object



86
# File 'lib/hop/ffi.rb', line 86

def self.cluster_join_passphrase(node, pass) = CLUSTER_JOIN_PASSPHRASE.call(node, pass, pass.bytesize)

.cluster_members(node) ⇒ Object



87
# File 'lib/hop/ffi.rb', line 87

def self.cluster_members(node) = CLUSTER_MEMBERS.call(node)

.cluster_set_quorum(node, min) ⇒ Object



88
# File 'lib/hop/ffi.rb', line 88

def self.cluster_set_quorum(node, min) = CLUSTER_SET_QUORUM.call(node, min)

.connected(node, link, initiator) ⇒ Object



81
# File 'lib/hop/ffi.rb', line 81

def self.connected(node, link, initiator) = LINK_UP.call(node, link, initiator ? 0 : 1)

.disconnected(node, link) ⇒ Object



82
# File 'lib/hop/ffi.rb', line 82

def self.disconnected(node, link) = LINK_DOWN.call(node, link)

.drain_outgoing(node) ⇒ Object



97
98
99
100
101
102
# File 'lib/hop/ffi.rb', line 97

def self.drain_outgoing(node)
  out = []
  sink = Closure.new(V, [P, LL, P, SZ]) { |_ctx, link, ptr, len| out << [link, read_bytes(ptr, len)] }
  DRAIN_OUTGOING.call(node, sink, nil)
  out
end

.from_b58(text) ⇒ Object



140
141
142
143
144
145
# File 'lib/hop/ffi.rb', line 140

def self.from_b58(text)
  out = Fiddle::Pointer.malloc(32, Fiddle::RUBY_FREE)
  raise "not a valid Hop address: #{text}" if ADDRESS_FROM_BASE58.call(text, out).zero?

  out[0, 32].b
end

.lib_pathObject



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/hop/ffi.rb', line 18

def self.lib_path
  ext = case RbConfig::CONFIG["host_os"]
        when /darwin/ then "dylib"
        when /mswin|mingw/ then "dll"
        else "so"
        end
  repo = File.expand_path("../../../..", __dir__) # sdk/ruby/lib/hop -> repo root
  candidates = []
  candidates << File.join(ENV["HOP_LIBDIR"], "libhop.#{ext}") if ENV["HOP_LIBDIR"]
  candidates << File.join(repo, "target", "debug", "libhop.#{ext}")
  candidates << File.join(repo, "target", "release", "libhop.#{ext}")
  found = candidates.find { |c| File.exist?(c) }
  raise "libhop.#{ext} not found. Build it with `cargo build -p hop` or set HOP_LIBDIR.\n" \
        "Looked in:\n  #{candidates.join("\n  ")}" unless found

  found
end

.node_free(node) ⇒ Object



79
# File 'lib/hop/ffi.rb', line 79

def self.node_free(node) = NODE_FREE.call(node)

.node_newObject

---- thin wrappers ----



77
# File 'lib/hop/ffi.rb', line 77

def self.node_new = NODE_NEW.call

.node_with_secret(secret) ⇒ Object



78
# File 'lib/hop/ffi.rb', line 78

def self.node_with_secret(secret) = NODE_WITH_SECRET.call(secret, secret.bytesize)

.publish_prekey(node) ⇒ Object



89
# File 'lib/hop/ffi.rb', line 89

def self.publish_prekey(node) = PUBLISH_PREKEY.call(node) != 0

.read_bytes(ptr, len) ⇒ Object

---- helpers: read C memory that is valid only during a call ----



73
# File 'lib/hop/ffi.rb', line 73

def self.read_bytes(ptr, len) = len.zero? ? "".b : Fiddle::Pointer.new(ptr)[0, len].b

.read_cstr(ptr) ⇒ Object



74
# File 'lib/hop/ffi.rb', line 74

def self.read_cstr(ptr) = Fiddle::Pointer.new(ptr).to_s

.received(node, link, data) ⇒ Object



83
# File 'lib/hop/ffi.rb', line 83

def self.received(node, link, data) = BYTES_RECEIVED.call(node, link, data, data.bytesize)

.send_service_request(node, dst, service, method, args) ⇒ Object



104
105
106
107
108
109
110
# File 'lib/hop/ffi.rb', line 104

def self.send_service_request(node, dst, service, method, args)
  out = Fiddle::Pointer.malloc(32, Fiddle::RUBY_FREE)
  ok = SEND_SERVICE_REQUEST.call(node, dst, service, method, args, args.bytesize, out) != 0
  raise "hop_send_service_request failed" unless ok

  out[0, 32].b
end

.send_service_response(node, to, for_request_id, status, body) ⇒ Object



112
113
114
# File 'lib/hop/ffi.rb', line 112

def self.send_service_response(node, to, for_request_id, status, body)
  SEND_SERVICE_RESPONSE.call(node, to, for_request_id, status, body, body.bytesize) != 0
end

.sign_reach(node, endpoint, ttl_secs) ⇒ Object



147
148
149
150
151
152
# File 'lib/hop/ffi.rb', line 147

def self.sign_reach(node, endpoint, ttl_secs)
  result = nil
  sink = Closure.new(V, [P, P, SZ]) { |_ctx, ptr, len| result = read_bytes(ptr, len) }
  SIGN_REACH_RECORD.call(node, endpoint, ttl_secs, sink, nil)
  result
end

.subscribe(node, topic) ⇒ Object



84
# File 'lib/hop/ffi.rb', line 84

def self.subscribe(node, topic) = SUBSCRIBE.call(node, topic)

.take_service_requests(node) ⇒ Object



116
117
118
119
120
121
122
123
# File 'lib/hop/ffi.rb', line 116

def self.take_service_requests(node)
  out = []
  sink = Closure.new(V, [P, P, P, P, P, P, SZ]) do |_ctx, frm, rid, service, method, args, arglen|
    out << [read_bytes(frm, 32), read_bytes(rid, 32), read_cstr(service), read_cstr(method), read_bytes(args, arglen)]
  end
  POLL_SERVICE_REQUESTS.call(node, sink, nil)
  out
end

.take_service_responses(node) ⇒ Object



125
126
127
128
129
130
131
132
# File 'lib/hop/ffi.rb', line 125

def self.take_service_responses(node)
  out = []
  sink = Closure.new(V, [P, P, P, I, P, SZ]) do |_ctx, frm, for_id, status, body, body_len|
    out << [read_bytes(frm, 32), read_bytes(for_id, 32), status & 0xFFFF, read_bytes(body, body_len)]
  end
  POLL_SERVICE_RESPONSES.call(node, sink, nil)
  out
end

.tick(node, now_ms) ⇒ Object



80
# File 'lib/hop/ffi.rb', line 80

def self.tick(node, now_ms) = NODE_TICK.call(node, now_ms)

.to_b58(addr32) ⇒ Object



134
135
136
137
138
# File 'lib/hop/ffi.rb', line 134

def self.to_b58(addr32)
  out = Fiddle::Pointer.malloc(64, Fiddle::RUBY_FREE)
  n = ADDRESS_TO_BASE58.call(addr32, out, 64)
  out[0, n]
end

.verify_reach(record, now_secs) ⇒ Object



154
155
156
157
158
159
160
161
# File 'lib/hop/ffi.rb', line 154

def self.verify_reach(record, now_secs)
  info = nil
  sink = Closure.new(V, [P, P, P, LL, I]) do |_ctx, addr, endpoint, issued_at, ttl_secs|
    info = { address: read_bytes(addr, 32), endpoint: read_cstr(endpoint), issued_at: issued_at, ttl_secs: ttl_secs }
  end
  ok = VERIFY_REACH_RECORD.call(record, record.bytesize, now_secs, sink, nil) != 0
  ok ? info : nil
end