upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/btfrv.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/btfrv.cpp')
-rw-r--r--src/btfrv.cpp250
1 files changed, 250 insertions, 0 deletions
diff --git a/src/btfrv.cpp b/src/btfrv.cpp
new file mode 100644
index 0000000..ffda08c
--- /dev/null
+++ b/src/btfrv.cpp
@@ -0,0 +1,250 @@
1// Copyright (c) 2026 Bitflash developers
2// Distributed under the MIT/X11 software license.
3//
4// Bitflash rendezvous transport. See btfrv.h.
5// Windows/Winsock implementation (Layer 6 will #ifdef this for Linux/BSD sockets).
6
7#include "btfrv.h"
8
9// Portable sockets: Winsock on Windows, BSD sockets on Linux/POSIX (for the
10// headless relay `bitflashd` that runs on the VPS).
11#ifdef _WIN32
12#include <winsock2.h>
13#include <ws2tcpip.h>
14#define CLOSESOCK closesocket
15#else
16#include <sys/socket.h>
17#include <netinet/in.h>
18#include <arpa/inet.h>
19#include <netdb.h>
20#include <unistd.h>
21#include <csignal>
22typedef int SOCKET;
23#define INVALID_SOCKET (-1)
24#define SOCKET_ERROR (-1)
25#define CLOSESOCK ::close
26#ifndef SD_BOTH
27#define SD_BOTH SHUT_RDWR
28#endif
29#endif
30
31#include <cstring>
32#include <cstdio>
33#include <string>
34#include <map>
35#include <thread>
36#include <mutex>
37#include <atomic>
38
39namespace btf
40{
41
42static std::atomic<bool> g_stop(false);
43static SOCKET g_listen = INVALID_SOCKET;
44
45// Services that have registered and are waiting for a client, keyed by pubkey.
46static std::map<std::string, SOCKET> g_waiting;
47static std::mutex g_mtx;
48
49bool RvInit()
50{
51 static bool done = false;
52 if (done) return true;
53#ifdef _WIN32
54 WSADATA wsa;
55 if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) return false;
56#else
57 signal(SIGPIPE, SIG_IGN); // don't die when writing to a closed peer
58#endif
59 done = true;
60 return true;
61}
62
63// ---- low-level helpers ----
64
65static bool ReadN(SOCKET s, void* buf, int n)
66{
67 char* p = (char*)buf;
68 int off = 0;
69 while (off < n)
70 {
71 int r = recv(s, p + off, n - off, 0);
72 if (r <= 0) return false;
73 off += r;
74 }
75 return true;
76}
77
78static bool WriteN(SOCKET s, const void* buf, int n)
79{
80 const char* p = (const char*)buf;
81 int off = 0;
82 while (off < n)
83 {
84 int r = send(s, p + off, n - off, 0);
85 if (r <= 0) return false;
86 off += r;
87 }
88 return true;
89}
90
91static SOCKET ConnectTo(const char* host, unsigned short port)
92{
93 struct addrinfo hints, *res = NULL;
94 memset(&hints, 0, sizeof(hints));
95 hints.ai_family = AF_INET;
96 hints.ai_socktype = SOCK_STREAM;
97 char portstr[16]; sprintf(portstr, "%u", (unsigned)port);
98 if (getaddrinfo(host, portstr, &hints, &res) != 0 || !res) return INVALID_SOCKET;
99 SOCKET s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
100 if (s == INVALID_SOCKET) { freeaddrinfo(res); return INVALID_SOCKET; }
101 if (connect(s, res->ai_addr, (int)res->ai_addrlen) != 0)
102 {
103 CLOSESOCK(s); freeaddrinfo(res); return INVALID_SOCKET;
104 }
105 freeaddrinfo(res);
106 return s;
107}
108
109// ---- relay side ----
110
111// Pump bytes from `from` to `to`. Owns closing `from`; shuts down `to` on exit
112// so the peer-direction thread unblocks and closes its own socket exactly once.
113static void Forward(SOCKET from, SOCKET to)
114{
115 char buf[32 * 1024];
116 for (;;)
117 {
118 int r = recv(from, buf, sizeof(buf), 0);
119 if (r <= 0) break;
120 if (!WriteN(to, buf, r)) break;
121 }
122 shutdown(to, SD_BOTH);
123 CLOSESOCK(from);
124}
125
126static void HandleIncoming(SOCKET s)
127{
128 unsigned char hdr[33];
129 if (!ReadN(s, hdr, 33)) { CLOSESOCK(s); return; }
130 char role = (char)hdr[0];
131 std::string pubkey((char*)hdr + 1, 32);
132
133 if (role == 'S')
134 {
135 // Register and wait; a future client will pair us.
136 std::lock_guard<std::mutex> lk(g_mtx);
137 // If a stale service for this key exists, drop it.
138 std::map<std::string, SOCKET>::iterator it = g_waiting.find(pubkey);
139 if (it != g_waiting.end()) CLOSESOCK(it->second);
140 g_waiting[pubkey] = s;
141 // leave the socket open in the map; do not close here.
142 }
143 else if (role == 'C')
144 {
145 SOCKET svc = INVALID_SOCKET;
146 {
147 std::lock_guard<std::mutex> lk(g_mtx);
148 std::map<std::string, SOCKET>::iterator it = g_waiting.find(pubkey);
149 if (it != g_waiting.end()) { svc = it->second; g_waiting.erase(it); }
150 }
151 if (svc == INVALID_SOCKET)
152 {
153 unsigned char no = 0x00;
154 WriteN(s, &no, 1);
155 CLOSESOCK(s);
156 return;
157 }
158 // Pair: tell both sides, then forward raw bytes bidirectionally.
159 unsigned char ok = 0x01;
160 bool a = WriteN(svc, &ok, 1);
161 bool b = WriteN(s, &ok, 1);
162 if (!a || !b) { CLOSESOCK(svc); CLOSESOCK(s); return; }
163 std::thread(Forward, s, svc).detach();
164 std::thread(Forward, svc, s).detach();
165 }
166 else
167 {
168 CLOSESOCK(s);
169 }
170}
171
172bool RvRelayRun(unsigned short port)
173{
174 if (!RvInit()) return false;
175 g_stop = false;
176
177 g_listen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
178 if (g_listen == INVALID_SOCKET) return false;
179 int yes = 1;
180 setsockopt(g_listen, SOL_SOCKET, SO_REUSEADDR, (char*)&yes, sizeof(yes));
181
182 struct sockaddr_in addr;
183 memset(&addr, 0, sizeof(addr));
184 addr.sin_family = AF_INET;
185 addr.sin_addr.s_addr = INADDR_ANY;
186 addr.sin_port = htons(port);
187 if (bind(g_listen, (struct sockaddr*)&addr, sizeof(addr)) != 0) { CLOSESOCK(g_listen); return false; }
188 if (listen(g_listen, 16) != 0) { CLOSESOCK(g_listen); return false; }
189
190 while (!g_stop)
191 {
192 struct sockaddr_in cli;
193 socklen_t len = sizeof(cli);
194 SOCKET s = accept(g_listen, (struct sockaddr*)&cli, &len);
195 if (s == INVALID_SOCKET)
196 {
197 if (g_stop) break;
198 continue;
199 }
200 std::thread(HandleIncoming, s).detach();
201 }
202 CLOSESOCK(g_listen);
203 g_listen = INVALID_SOCKET;
204 return true;
205}
206
207void RvRelayStop()
208{
209 g_stop = true;
210 if (g_listen != INVALID_SOCKET)
211 CLOSESOCK(g_listen); // unblock accept()
212}
213
214// ---- service / client helpers ----
215
216RvSocket RvServiceRegister(const char* relay_host, unsigned short port,
217 const unsigned char my_pubkey[32])
218{
219 if (!RvInit()) return RV_INVALID;
220 SOCKET s = ConnectTo(relay_host, port);
221 if (s == INVALID_SOCKET) return RV_INVALID;
222 unsigned char hdr[33];
223 hdr[0] = (unsigned char)'S';
224 memcpy(hdr + 1, my_pubkey, 32);
225 if (!WriteN(s, hdr, 33)) { CLOSESOCK(s); return RV_INVALID; }
226 unsigned char paired = 0;
227 if (!ReadN(s, &paired, 1) || paired != 0x01) { CLOSESOCK(s); return RV_INVALID; }
228 return (RvSocket)s;
229}
230
231RvSocket RvClientConnect(const char* relay_host, unsigned short port,
232 const unsigned char target_pubkey[32])
233{
234 if (!RvInit()) return RV_INVALID;
235 SOCKET s = ConnectTo(relay_host, port);
236 if (s == INVALID_SOCKET) return RV_INVALID;
237 unsigned char hdr[33];
238 hdr[0] = (unsigned char)'C';
239 memcpy(hdr + 1, target_pubkey, 32);
240 if (!WriteN(s, hdr, 33)) { CLOSESOCK(s); return RV_INVALID; }
241 unsigned char paired = 0;
242 if (!ReadN(s, &paired, 1) || paired != 0x01) { CLOSESOCK(s); return RV_INVALID; }
243 return (RvSocket)s;
244}
245
246bool RvReadN(RvSocket s, void* buf, int n) { return ReadN((SOCKET)s, buf, n); }
247bool RvWriteN(RvSocket s, const void* buf, int n) { return WriteN((SOCKET)s, buf, n); }
248void RvClose(RvSocket s) { if (s != RV_INVALID) CLOSESOCK((SOCKET)s); }
249
250} // namespace btf