BCNP 3.2.1
Batched Command Network Protocol
Loading...
Searching...
No Matches
udp_posix.cpp
Go to the documentation of this file.
1
17
18#include <arpa/inet.h>
19#include <cerrno>
20#include <cstring>
21#include <fcntl.h>
22#include <iostream>
23#include <sys/socket.h>
24#include <unistd.h>
25
26namespace bcnp {
27
28namespace {
30constexpr uint32_t kPairingMagic = 0x42434E50U; // "BCNP"
31
37uint32_t LoadU32(const uint8_t* data) {
38 return (uint32_t(data[0]) << 24) |
39 (uint32_t(data[1]) << 16) |
40 (uint32_t(data[2]) << 8) |
41 uint32_t(data[3]);
42}
43
48void LogErr(const char* message) {
49 std::cerr << "UDP adapter error: " << message << " errno=" << errno << std::endl;
50}
51} // namespace
52
64UdpPosixAdapter::UdpPosixAdapter(uint16_t listenPort, const char* targetIp, uint16_t targetPort) {
65 m_socket = ::socket(AF_INET, SOCK_DGRAM, 0);
66 if (m_socket < 0) {
67 LogErr("socket");
68 return;
69 }
70
71 int yes = 1;
72 if (setsockopt(m_socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0) {
73 LogErr("setsockopt(SO_REUSEADDR)");
74 }
75
76 if (fcntl(m_socket, F_SETFL, O_NONBLOCK) < 0) {
77 LogErr("fcntl(O_NONBLOCK)");
78 ::close(m_socket);
79 m_socket = -1;
80 return;
81 }
82
83 m_bind = {};
84 m_bind.sin_family = AF_INET;
85 m_bind.sin_port = htons(listenPort);
86 m_bind.sin_addr.s_addr = INADDR_ANY;
87
88 if (bind(m_socket, reinterpret_cast<sockaddr*>(&m_bind), sizeof(m_bind)) < 0) {
89 LogErr("bind");
90 ::close(m_socket);
91 m_socket = -1;
92 return;
93 }
94
95 m_requirePairing = (listenPort > 0);
96
97 if (targetIp && targetPort > 0) {
98 m_lastPeer = {};
99 m_lastPeer.sin_family = AF_INET;
100 m_lastPeer.sin_port = htons(targetPort);
101 if (inet_pton(AF_INET, targetIp, &m_lastPeer.sin_addr) > 0) {
102 m_hasPeer = true;
103 m_peerLocked = true; // Lock to this peer for security
104 m_initialPeer = m_lastPeer;
105 m_pairingComplete = true;
106 m_fixedPeerConfigured = true;
107 } else {
108 LogErr("inet_pton (invalid target IP)");
109 }
110 }
111}
112
117 if (m_socket >= 0) {
118 ::close(m_socket);
119 }
120}
121
131bool UdpPosixAdapter::SendBytes(const uint8_t* data, std::size_t length) {
132 if (!data || length == 0) {
133 return true;
134 }
135 if (!m_hasPeer || m_socket < 0) {
136 return false;
137 }
138 const auto sent = ::sendto(m_socket, data, length, 0,
139 reinterpret_cast<sockaddr*>(&m_lastPeer), sizeof(m_lastPeer));
140 return sent == static_cast<ssize_t>(length);
141}
142
152 m_peerLocked = locked;
153 if (!locked) {
154 m_pairingComplete = false;
155 return;
156 }
157
158 if (m_fixedPeerConfigured && m_hasPeer) {
159 m_initialPeer = m_lastPeer;
160 m_pairingComplete = true;
161 return;
162 }
163
164 if (m_requirePairing) {
165 m_pairingComplete = false;
166 m_hasPeer = false;
167 }
168}
169
179 m_pairingToken = token;
180 if (m_peerLocked && m_requirePairing && !m_fixedPeerConfigured) {
181 m_pairingComplete = false;
182 m_hasPeer = false;
183 }
184}
185
192 if (!m_fixedPeerConfigured) {
193 m_pairingComplete = false;
194 m_hasPeer = false;
195 }
196}
197
210std::size_t UdpPosixAdapter::ReceiveChunk(uint8_t* buffer, std::size_t maxLength) {
211 if (m_socket < 0 || !buffer || maxLength == 0) {
212 return 0;
213 }
214
215 // Auto-unlock peer after timeout to allow re-pairing
216 const auto now = std::chrono::steady_clock::now();
217 if (m_peerLocked && m_hasPeer && !m_fixedPeerConfigured &&
218 m_lastPeerRx != std::chrono::steady_clock::time_point{} &&
219 now - m_lastPeerRx > kPeerTimeout) {
220 UnlockPeer();
221 }
222
223 sockaddr_in src{};
224 socklen_t slen = sizeof(src);
225 const auto received = ::recvfrom(m_socket, buffer, maxLength, MSG_DONTWAIT,
226 reinterpret_cast<sockaddr*>(&src), &slen);
227 if (received < 0) {
228 if (errno == EAGAIN || errno == EWOULDBLOCK) {
229 return 0;
230 }
231 LogErr("recvfrom");
232 return 0;
233 }
234
235 if (m_peerLocked) {
236 if (m_requirePairing && !m_pairingComplete && !m_fixedPeerConfigured) {
237 if (ProcessPairingPacket(buffer, static_cast<std::size_t>(received), src)) {
238 m_lastPeerRx = now;
239 return 0; // Handshake packets are not forwarded upwards
240 }
241 return 0;
242 }
243
244 if (m_hasPeer && (src.sin_addr.s_addr != m_initialPeer.sin_addr.s_addr ||
245 src.sin_port != m_initialPeer.sin_port)) {
246 return 0;
247 }
248 if (!m_hasPeer) {
249 m_initialPeer = src;
250 m_hasPeer = true;
251 }
252 m_lastPeer = src;
253 m_lastPeerRx = now;
254 } else {
255 m_lastPeer = src;
256 m_hasPeer = true;
257 m_lastPeerRx = now;
258 }
259
260 return static_cast<std::size_t>(received);
261}
262
274bool UdpPosixAdapter::ProcessPairingPacket(const uint8_t* buffer, std::size_t length, const sockaddr_in& src) {
275 // V3 handshake: "BCNP" (4 bytes) + schema hash (4 bytes)
276 if (length != kHandshakeSize) {
277 return false;
278 }
279
280 // Check magic bytes
281 if (std::memcmp(buffer, kHandshakeMagic.data(), 4) != 0) {
282 return false;
283 }
284
285 // Extract and validate schema hash
286 m_remoteSchemaHash = LoadU32(buffer + 4);
287 if (m_remoteSchemaHash != kSchemaHash) {
288 std::cerr << "UDP adapter: Schema mismatch! Local=0x" << std::hex << kSchemaHash
289 << " Remote=0x" << m_remoteSchemaHash << std::dec << std::endl;
290 m_schemaValidated = false;
291 // V3: Reject pairing if schema doesn't match
292 return false;
293 }
294
295 m_initialPeer = src;
296 m_lastPeer = src;
297 m_hasPeer = true;
298 m_pairingComplete = true;
299 m_schemaValidated = true;
300
301 // Send our handshake response
303
304 return true;
305}
306
316 if (!m_hasPeer || m_socket < 0) {
317 return false;
318 }
319
320 uint8_t handshake[kHandshakeSize];
321 if (!EncodeHandshake(handshake, sizeof(handshake))) {
322 return false;
323 }
324
325 const auto sent = ::sendto(m_socket, handshake, sizeof(handshake), 0,
326 reinterpret_cast<sockaddr*>(&m_lastPeer), sizeof(m_lastPeer));
327 return sent == static_cast<ssize_t>(sizeof(handshake));
328}
329
330} // namespace bcnp
~UdpPosixAdapter() override
Destructor. Closes the socket.
UdpPosixAdapter(uint16_t listenPort, const char *targetIp=nullptr, uint16_t targetPort=0)
Construct UDP adapter.
Definition udp_posix.cpp:64
std::size_t ReceiveChunk(uint8_t *buffer, std::size_t maxLength) override
Receives a UDP datagram.
void SetPairingToken(uint32_t token)
Sets the expected pairing token for handshake validation.
bool SendHandshake()
Sends the V3 protocol handshake to the current peer.
bool SendBytes(const uint8_t *data, std::size_t length) override
Sends bytes to the current peer via UDP.
void UnlockPeer()
Resets pairing state to allow re-pairing with a new peer.
void SetPeerLockMode(bool locked)
Enables or disables peer locking.
uint32_t LoadU32(const uint8_t *p)
bool EncodeHandshake(uint8_t *out, std::size_t capacity)
Encode handshake with default schema hash.
constexpr std::size_t kHandshakeSize
constexpr std::array< uint8_t, 4 > kHandshakeMagic
constexpr uint32_t kSchemaHash