OverviewFilesCommitsIssuesDocsReleasesPeers

hyperswarm-peer / swarmd / src / content.h

102 lines · c ref:
#ifndef SWARMD_CONTENT_H
#define SWARMD_CONTENT_H

// The content plane: fetch and serve content-addressed files over liboverlay.
//
// ⛔ BULK BYTES NEVER CROSS THE LOCAL SOCKET (docs/SWARMD-DESIGN.md §6). A .res
// is 108 MB. The socket carries a 32-byte hash and a path; the overlay writes
// the file and the client opens it. That is also what makes a fetch resumable
// across a daemon restart, because the partial and its journal are on disk.
//
// THREADING, which is the same rule the rest of the daemon follows and the same
// one src/overlay_node.h arrived at for Godot:
//
//   UDX thread   owns the Protocol OUTRIGHT. Protomux dispatches inbound overlay
//                frames there, and protomux/udx handles belong to that loop, so
//                both halves must live on it or they race. Every command from a
//                client is marshalled in with run_on_loop.
//   main thread  drains an event queue and writes to client sockets. It never
//                touches the Protocol — not even to read a counter.
//
// The one exception is hash_of_file(), which touches no overlay state at all: it
// streams a file through the chunker. It runs where it is called.

#include <cstdint>
#include <deque>
#include <functional>
#include <map>
#include <memory>
#include <mutex>
#include <string>
#include <vector>

#include "../../src/overlay_transport.h"
#include "../../src/udx_transport.h"
#include "overlay/file_index.h"
#include "overlay/protocol.h"

namespace swarmd {

class Content {
public:
	struct Event {
		enum Kind { DONE, FAILED, PROGRESS } kind;
		std::string hash;
		std::string detail;        // path for DONE, reason for FAILED
		uint64_t received = 0;
		uint64_t total = 0;
	};

	// `storage_dir` holds the index log, the journals and the partials. It must
	// survive restarts — that is what makes a resumed download cheap.
	bool open(const std::string &storage_dir, UdxTransport *transport);
	void close();
	bool is_open() const { return open_; }

	// The content hash of a file, computed the way the overlay computes it:
	// blake2b over the hypercore leaf construction, not a plain digest. Empty on
	// failure. Streams, so a file larger than RAM is not a different path.
	static std::string hash_of_file(const std::string &path);

	// All of these return immediately; results arrive as events.
	void serve(const std::string &hash, const std::string &path);
	void fetch(const std::string &hash, const std::string &dest_path);
	void cancel(const std::string &hash, bool discard);

	// UDX THREAD ONLY. Called from the daemon's mux callbacks — swarmd owns the
	// peer lifecycle, so it builds the overlay channels itself rather than
	// letting a second component take set_mux_callbacks.
	void add_peer(int32_t peer_id, Protomux *mux);
	void remove_peer(int32_t peer_id);

	// MAIN THREAD. Advances liboverlay's clock (it never calls now() itself, so
	// without this no watchdog fires and the library is correct and inert),
	// samples progress, and hands back everything that has happened.
	void poll(std::vector<Event> &out);

private:
	void _push(const Event &e);

	std::unique_ptr<hyperoverlay::index::FileIndex> index_;
	std::unique_ptr<hyperoverlay::Protocol> proto_;
	UdxTransport *t_ = nullptr;
	bool open_ = false;
	std::string storage_dir_;

	// UDX THREAD ONLY: one overlay channel per connection.
	std::map<int32_t, hyperswarm::OverlayTransport *> peers_;

	std::mutex m_;
	std::deque<Event> events_;
	// Bounded. A fetch that reports faster than the socket drains must drop
	// PROGRESS — never a terminal event, because losing one strands a client
	// waiting for a message that never comes.
	static constexpr size_t MAX_EVENTS = 4096;
	uint64_t dropped_progress_ = 0;
	uint64_t last_progress_ms_ = 0;
};

} // namespace swarmd

#endif