hop-endpoint

Version, currently 0.0.12 versions

github.com/hopmesh/hop-sdk-crystal

Embeddable Hop mesh endpoint for Crystal (Sinatra/Rails-shaped) over the libhop C ABI. Your service becomes directly reachable on the mesh, no relay. Zero shards (Crystal binds C directly).

0 stars
0 dependents
License: Apache-2.0

Nothing has been indexed for 0.0.1 yet. The tag is recorded, its shard.yml has not been read, so the manifest and dependency list below are empty because they are unknown rather than because they are absent.

Installation

# Add this to your shard.yml
dependencies:
  hop-endpoint:
    github: hopmesh/hop-sdk-crystal
    version: ~> 0.0.1

Then run:

shards install

shard.yml

No shard.yml has been indexed for 0.0.1. You can read it on the repository.

Dependencies

Unknown: the shard.yml for this version has not been read yet.

README

This README is the one indexed from the repository at its latest ref, not from the tag for this version.


Hop is a delay-tolerant mesh: end-to-end encrypted datagrams that hop device to device, over BLE, Wi-Fi, and the internet, until they reach the person or service you meant. Held, never dropped.

hop-endpoint is the server side: your Crystal service becomes a first-class address on the mesh, so senders hand messages straight to it. Self-host is an import, not an ops project. No inbound port to open to the world, no bearer tokens to rotate, no message queue to run: the sender identity is authenticated by the ratchet, and delivery is durable and store-and-forward. Zero shards: Crystal binds libhop directly with lib, and the WSS bearer plus discovery ride the stdlib.

Install

Add it to shard.yml, then shards install:

dependencies:
  hop-endpoint:
    github: hopmesh/hop-sdk-crystal

You also need libhop, the Rust protocol core, as a prebuilt binary or a local build. Crystal links it at compile time; its default -L points at a local build, or add your libhop dir to CRYSTAL_LIBRARY_PATH (or HOP_LIBDIR). See libhop.

Quick start

require "hop"
require "json"

hop = Hop::Endpoint.new

hop.on("acme/orders") do |req, reply|
  # req.from is a VERIFIED identity (base58), not a spoofable header
  order = JSON.parse(req.text)
  reply.call(201, {ok: true, order: order}.to_json) # uint16 status + body
end

Hop::TcpBearer.listen(hop, 9944) # reachable by any device
puts hop.address                 # publish this (or its name); senders reach you by it

Crystal has channels, so besides the block handler you can consume verified requests on a Channel and reply from your own fiber (natural for a long-running server, or a select across several services):

orders = hop.channel("acme/orders")
spawn do
  loop do
    req, reply = orders.receive
    reply.call(201, req.text)
  end
end

Same delivery either way; channel is just on wired to a Channel.

The DX looks like HTTP; the semantics are better. Inbound is a durable, store-and-forward consume; a reply is a new addressed message that may arrive later, even after a restart. It works when the peer is offline, and there is no auth layer to bolt on, the identity is cryptographic. core is poll-model, so the endpoint runs a background pump fiber (the node is thread-safe).

Reachable by name

Make an endpoint reachable at myaddress.com with no new port, on Crystal's stdlib HTTP::WebSocket + HTTP::Server (zero shards). attach wires the WSS bearer (/_hop) and the discovery route (/.well-known/hop) in one call:

tls = OpenSSL::SSL::Context::Server.new
tls.certificate_chain = "cert.pem"
tls.private_key = "key.pem"
hop.attach(443, tls, "wss://myaddress.com/_hop")

A client reaches it by name, verified end to end:

address = client.dial_by_name("https://myaddress.com")
status, body = client.request(address, "acme/orders", "create", order)

TLS proves the domain, a signed reach record proves the address, and the Noise handshake confirms it. Spoof the A record or MITM the lookup and the attacker still can't forge the cert or complete the handshake as the address, and a request sealed to that address is unreadable to anyone else.

How it maps to the core

The endpoint is a hop-core node in host-a-mailbox mode, over the same C ABI every Hop SDK binds (via lib), with zero core changes:

Endpointlibhop C ABI
hop.on(svc) { }hop_subscribe + hop_poll_service_requests
reply.call(status, body)hop_send_service_response (status is a uint16)
hop.request(...)hop_send_service_request + hop_poll_service_responses
the Internet bearerhop_link_up / hop_bytes_received / hop_drain_outgoing

Examples

Build or fetch libhop, then:

crystal spec                        # in-process + reach record + WSS discovery, all pass
crystal run examples/raw_roundtrip.cr # raw C ABI round trip (proves the lib bindings)
crystal run examples/echo.cr        # the hop.on / reply DX in-process
crystal run examples/tcp.cr         # the same round trip over a real TCP bearer
crystal run examples/discovery.cr   # the full reachable-by-name chain (HTTPS + WSS)

Two-process shape (a standalone server on the channel surface, plus a client):

crystal run examples/server.cr              # prints its address, listens on tcp://0.0.0.0:9944
crystal run examples/client.cr -- <address> localhost 9944

The discovery test generates its self-signed cert in-process (Hop::DevTls), no openssl CLI.

Status

Prototype. Built and working: the on block handler and the channel surface, reply, the client request, the in-process / TCP / WSS bearers, base58 addressing, reach-record attach / dial_by_name discovery, sibling-replica clustering, the ABI-version assert, and a use-after-free-safe close (a bearer fiber that fires after teardown short-circuits instead of touching a freed node). HNS name publish/resolve and multi-tenant hosting are on the roadmap (each an SDK-level follow-up, not a core change).

The Hop family

hop-endpoint is one of several SDKs over the same C ABI. Same surface, your language: node · python · go · ruby · crystal · elixir. The protocol core is libhop / hop-core.

License

Apache-2.0, embed it freely. Only the protocol core (hop-core) is FSL-1.1-ALv2, source-available and converting to Apache-2.0 after two years.