Rename all project references from quicproquo/qpq to quicprochat/qpc across documentation, Docker configuration, CI workflows, packaging scripts, operational configs, and build tooling. - Docker: crate paths, binary names, user/group, data dirs, env vars - CI: workflow crate references, binary names, artifact names - Docs: all markdown files under docs/, SDK READMEs, book.toml - Packaging: OpenWrt Makefile, init script, UCI config (file renames) - Scripts: justfile, dev-shell, screenshot, cross-compile, ai_team - Operations: Prometheus config, alert rules, Grafana dashboard - Config: .env.example (QPQ_* → QPC_*), CODEOWNERS paths - Top-level: README, CONTRIBUTING, ROADMAP, CLAUDE.md
123 lines
3.6 KiB
Ruby
123 lines
3.6 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "json"
|
|
|
|
module QuicProChat
|
|
# High-level quicprochat client for Ruby.
|
|
#
|
|
# Wraps +libquicprochat_ffi+ via the +ffi+ gem.
|
|
#
|
|
# client = QuicProChat::Client.new("127.0.0.1:5001", ca_cert: "ca.pem")
|
|
# client.login("alice", "secret")
|
|
# client.send("bob", "hello")
|
|
# messages = client.receive(timeout_ms: 5000)
|
|
# client.disconnect
|
|
#
|
|
# Or use the block form for automatic cleanup:
|
|
#
|
|
# QuicProChat::Client.open("127.0.0.1:5001", ca_cert: "ca.pem") do |c|
|
|
# c.login("alice", "secret")
|
|
# c.send("bob", "hello")
|
|
# end
|
|
class Client
|
|
# Connect to a quicprochat server.
|
|
#
|
|
# @param server [String] Server address as +host:port+.
|
|
# @param ca_cert [String] Path to PEM-encoded CA certificate.
|
|
# @param server_name [String] TLS SNI server name (defaults to host).
|
|
# @raise [ConnectionError] if the connection fails.
|
|
def initialize(server, ca_cert:, server_name: nil)
|
|
sn = server_name || server.split(":").first
|
|
@handle = FFIBindings.qpq_connect(server, ca_cert, sn)
|
|
raise ConnectionError, "qpq_connect failed for #{server}" if @handle.null?
|
|
end
|
|
|
|
# Connect and yield the client, disconnecting when the block returns.
|
|
def self.open(server, **opts)
|
|
client = new(server, **opts)
|
|
begin
|
|
yield client
|
|
ensure
|
|
client.disconnect
|
|
end
|
|
end
|
|
|
|
# Authenticate with OPAQUE credentials.
|
|
#
|
|
# @param username [String]
|
|
# @param password [String]
|
|
# @raise [AuthError] on bad credentials.
|
|
def login(username, password)
|
|
check_connected!
|
|
code = FFIBindings.qpq_login(@handle, username, password)
|
|
check_status!(code)
|
|
end
|
|
|
|
# Send a message to a recipient by username.
|
|
#
|
|
# @param recipient [String] Recipient username.
|
|
# @param message [String] Message payload (UTF-8 string or binary).
|
|
def send(recipient, message)
|
|
check_connected!
|
|
msg_bytes = message.encode("BINARY")
|
|
buf = FFI::MemoryPointer.new(:uint8, msg_bytes.bytesize)
|
|
buf.put_bytes(0, msg_bytes)
|
|
code = FFIBindings.qpq_send(@handle, recipient, buf, msg_bytes.bytesize)
|
|
check_status!(code)
|
|
end
|
|
|
|
# Receive pending messages, blocking up to +timeout_ms+ milliseconds.
|
|
#
|
|
# @param timeout_ms [Integer] Timeout in milliseconds (default 5000).
|
|
# @return [Array<String>] Message strings (UTF-8).
|
|
def receive(timeout_ms: 5000)
|
|
check_connected!
|
|
out_ptr = FFI::MemoryPointer.new(:pointer)
|
|
code = FFIBindings.qpq_receive(@handle, timeout_ms, out_ptr)
|
|
check_status!(code)
|
|
|
|
json_ptr = out_ptr.read_pointer
|
|
return [] if json_ptr.null?
|
|
|
|
json_str = json_ptr.read_string
|
|
FFIBindings.qpq_free_string(json_ptr)
|
|
JSON.parse(json_str)
|
|
end
|
|
|
|
# Disconnect from the server and release resources.
|
|
def disconnect
|
|
return if @handle.nil? || @handle.null?
|
|
|
|
FFIBindings.qpq_disconnect(@handle)
|
|
@handle = nil
|
|
end
|
|
|
|
# Whether the client is connected.
|
|
def connected?
|
|
!@handle.nil? && !@handle.null?
|
|
end
|
|
|
|
private
|
|
|
|
def check_connected!
|
|
raise NotConnectedError, "not connected" unless connected?
|
|
end
|
|
|
|
def check_status!(code)
|
|
return if code == FFIBindings::QPQ_OK
|
|
|
|
msg = FFIBindings.qpq_last_error(@handle) || "unknown error"
|
|
case code
|
|
when FFIBindings::QPQ_AUTH_FAILED
|
|
raise AuthError, msg
|
|
when FFIBindings::QPQ_TIMEOUT
|
|
raise TimeoutError, msg
|
|
when FFIBindings::QPQ_NOT_CONNECTED
|
|
raise NotConnectedError, msg
|
|
else
|
|
raise Error, msg
|
|
end
|
|
end
|
|
end
|
|
end
|