#ifndef HYPEROVERLAY_TRANSPORT_H
#define HYPEROVERLAY_TRANSPORT_H
// The seam between the overlay protocol and whatever carries it.
//
// WHY THIS EXISTS RATHER THAN INCLUDING PROTOMUX DIRECTLY. liboverlay is meant to
// lift out by moving the directory, so it must not reach into
// hyperswarm-peer/src/ — and Protomux lives there. Inverting the dependency keeps
// the library standalone: liboverlay says "send message N with these bytes", and
// the host supplies something that can. hyperswarm-peer provides a Protomux
// adapter; the tests provide an in-memory loopback.
//
// The second payoff is that the protocol becomes testable WITHOUT a socket. The
// JS reference harness does the same thing — two overlays over an in-memory
// duplex pair — which is why its transcripts are a usable reference here.
//
// MESSAGE IDS ARE SLOT ORDER, NOT ARBITRARY. Protomux numbers channel messages by
// registration order, and hyper-overlay/lib/protocol.js registers them in exactly
// this sequence. An adapter must register in the same order or every message
// lands in the wrong handler on a peer that did.
#include <cstdint>
#include <cstddef>
#include <vector>
namespace hyperoverlay {
enum class MsgId : uint8_t {
SyncState = 0,
FileOffer = 1,
FileRequest = 2,
ChunkHashes = 3,
ChunkNeed = 4,
ChunkData = 5,
ChunkCancel = 6,
TransferComplete = 7,
Conflict = 8,
TreeRequest = 9,
TreeResponse = 10,
ContentRequest = 11,
TransferControl = 12,
TransferProgress = 13,
COUNT = 14
};
inline const char *msg_name(MsgId id) {
switch (id) {
case MsgId::SyncState: return "syncState";
case MsgId::FileOffer: return "fileOffer";
case MsgId::FileRequest: return "fileRequest";
case MsgId::ChunkHashes: return "chunkHashes";
case MsgId::ChunkNeed: return "chunkNeed";
case MsgId::ChunkData: return "chunkData";
case MsgId::ChunkCancel: return "chunkCancel";
case MsgId::TransferComplete: return "transferComplete";
case MsgId::Conflict: return "conflict";
case MsgId::TreeRequest: return "treeRequest";
case MsgId::TreeResponse: return "treeResponse";
case MsgId::ContentRequest: return "contentRequest";
case MsgId::TransferControl: return "transferControl";
case MsgId::TransferProgress: return "transferProgress";
default: return "?";
}
}
// One connected peer. The host implements this over Protomux (or anything else);
// the protocol driver only ever calls send().
class Transport {
public:
virtual ~Transport() = default;
// Returns whether the carrier can take more RIGHT NOW. False does not mean the
// frame was dropped — it was accepted and queued — it means the write buffer is
// full and the sender should stop producing until on_writable fires.
//
// This return value is the whole point of step 6. libudx already reports it
// (udx_stream_write returns 0 at the high watermark) and every layer above was
// throwing it away, so a chunk pump had no way to know it was burying the
// connection. The frames it buries are not only its own: sibling protocols
// share this Noise stream — the game's own RPC channel, DHT traffic — and an
// unthrottled serve starves them.
virtual bool send(MsgId id, const std::vector<uint8_t> &frame) = 0;
// Stable identifier for the remote, used for logging and for keying
// per-peer state. Content is opaque to the protocol.
virtual const char *peer_label() const { return "peer"; }
// Smoothed round-trip time, if the carrier knows one. The scheduler sizes each
// peer's request window from it: a far peer needs more requests outstanding to
// keep the link busy, a near one does not.
//
// ZERO MEANS "NO SIGNAL", NOT "INSTANT" — and the distinction is load-bearing.
// The reference reads it off the udx stream and falls back to a modest fixed
// window when the stream is not udx (an in-memory pair, a relay). Returning 0
// selects that fallback; returning a real 0ms would ask for the minimum window
// on the fastest link, which is backwards.
virtual uint32_t rtt_ms() const { return 0; }
};
} // namespace hyperoverlay
#endif // HYPEROVERLAY_TRANSPORT_H