diff --git a/DHT/DHTRecord.cpp b/DHT/DHTRecord.cpp new file mode 100644 index 0000000..07b1a87 --- /dev/null +++ b/DHT/DHTRecord.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2026 by Geoffrey Merck F4FXL / KC3FRA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "DHTRecord.h" + +using nlohmann::json; + +/* + * Serialize LastHeard to JSON + */ +void to_json(json& j, const LastHeard& lh) +{ + j = json{ + {"repeater", lh.repeater}, + {"gateway", lh.gateway}, + {"at", lh.at} + }; +} + +/* + * Deserialize LastHeard from JSON + */ +void from_json(const json& j, LastHeard& lh) +{ + j.at("repeater").get_to(lh.repeater); + j.at("gateway").get_to(lh.gateway); + j.at("at").get_to(lh.at); +} + +/* + * Serialize DHTRecord to JSON + */ +void to_json(json& j, const DHTRecord& rec) +{ + j = json{ + {"version", rec.version}, + {"kind", rec.kind}, + {"callsign", rec.callsign}, + {"gateway", rec.gateway}, + {"publisher", rec.publisher}, + {"instance_id", rec.instance_id}, + {"published_at", rec.published_at}, + {"expires_at", rec.expires_at}, + {"seq", rec.seq} + }; + + // Add optional last_heard block only if present + if (rec.last_heard.has_value()) { + j["last_heard"] = rec.last_heard.value(); + } +} + +/* + * Deserialize DHTRecord from JSON + */ +void from_json(const json& j, DHTRecord& rec) +{ + j.at("version").get_to(rec.version); + j.at("kind").get_to(rec.kind); + j.at("callsign").get_to(rec.callsign); + j.at("gateway").get_to(rec.gateway); + j.at("publisher").get_to(rec.publisher); + j.at("instance_id").get_to(rec.instance_id); + j.at("published_at").get_to(rec.published_at); + j.at("expires_at").get_to(rec.expires_at); + j.at("seq").get_to(rec.seq); + + // Optional field + if (j.contains("last_heard")) { + rec.last_heard = j.at("last_heard").get(); + } else { + rec.last_heard.reset(); + } +} \ No newline at end of file diff --git a/DHT/DHTRecord.h b/DHT/DHTRecord.h new file mode 100644 index 0000000..4005412 --- /dev/null +++ b/DHT/DHTRecord.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2026 by Geoffrey Merck F4FXL / KC3FRA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#pragma once + +#include +#include +#include +#include + +/* + * Represents the "last heard" information for a user. + * This block is optional and only present for user records. + */ +struct LastHeard +{ + std::string repeater; // Repeater callsign (e.g. "F1ZXX B") + std::string gateway; // Gateway callsign (e.g. "F1ZXX G") + std::string at; // ISO8601 timestamp (UTC) +}; + +/* + * Represents a generic DHT record. + * + * This structure is used for both: + * - repeater presence (kind = "repeater") + * - user presence (kind = "user") + * + * The difference is that "user" records include the optional last_heard block. + */ +struct DHTRecord +{ + uint32_t version = 1; // Schema version + + std::string kind; // "repeater" or "user" + std::string callsign; // Subject callsign (user or repeater) + + std::string gateway; // Gateway associated to the subject + std::string publisher; // Gateway that published this record + + std::string instance_id; // Unique instance identifier (per process) + + std::string published_at; // ISO8601 timestamp (UTC) + std::string expires_at; // ISO8601 timestamp (UTC) + + uint64_t seq = 0; // Monotonic sequence number + + std::optional last_heard; // Present only for user records +}; + +/* + * JSON serialization helpers (nlohmann::json) + */ +void to_json(nlohmann::json& j, const LastHeard& lh); +void from_json(const nlohmann::json& j, LastHeard& lh); + +void to_json(nlohmann::json& j, const DHTRecord& rec); +void from_json(const nlohmann::json& j, DHTRecord& rec); \ No newline at end of file diff --git a/DHT/Makefile b/DHT/Makefile new file mode 100644 index 0000000..fac2bbf --- /dev/null +++ b/DHT/Makefile @@ -0,0 +1,15 @@ +SRCS = $(wildcard *.cpp) +OBJS = $(SRCS:.cpp=.o) +DEPS = $(SRCS:.cpp=.d) + +DStarBase.a: $(OBJS) + $(AR) rcs DStarBase.a $(OBJS) + +%.o : %.cpp + $(CC) -I../BaseCommon $(CPPFLAGS) -MMD -MD -c $< -o $@ +-include $(DEPS) + +clean: + $(RM) *.o *.d DHT.a + +../BaseCommon/BaseCommon.a: diff --git a/DHT/README.md b/DHT/README.md new file mode 100644 index 0000000..717c6ce --- /dev/null +++ b/DHT/README.md @@ -0,0 +1,33 @@ +Repeater + +``` +{ + "version": 1, + "kind": "repeater", + "callsign": "F1ZXX B", + "gateway": "F1ZXX G", + "publisher": "F1ZXX G", + "instance_id": "1c8dfd1a-6ab5-4ef6-b2fc-fd089db1d7be", + "published_at": "2026-03-30T12:34:56Z", + "expires_at": "2026-03-30T12:37:56Z", + "seq": 1842 +} +``` +Last Heard +``` +{ + "version": 1, + "kind": "user", + "callsign": "F4FXL", + "publisher": "F1ZXX G", + "instance_id": "1c8dfd1a-6ab5-4ef6-b2fc-fd089db1d7be", + "published_at": "2026-03-30T12:35:12Z", + "expires_at": "2026-03-30T12:37:12Z", + "seq": 99231, + "last_heard": { + "repeater": "F1ZXX B", + "gateway": "F1ZXX G", + "at": "2026-03-30T12:35:10Z" + } +} +``` \ No newline at end of file diff --git a/Makefile b/Makefile index a9e1485..155eaf2 100644 --- a/Makefile +++ b/Makefile @@ -68,6 +68,9 @@ DGWTimeServer/dgwtimeserver: VersionInfo/GitVersion.h $(OBJS) DStarBase/DStarBas DGWVoiceTransmit/dgwvoicetransmit: VersionInfo/GitVersion.h $(OBJS) DStarBase/DStarBase.a BaseCommon/BaseCommon.a FORCE $(MAKE) -C DGWVoiceTransmit +DHT/DHT.a: BaseCommon/BaseCommon.a FORCE + $(MAKE) -C DHT + IRCDDB/IRCDDB.a: VersionInfo/GitVersion.h BaseCommon/BaseCommon.a FORCE $(MAKE) -C IRCDDB