Q2NS dev
ns-3 module
Loading...
Searching...
No Matches
q2ns-swap-app.cc
Go to the documentation of this file.
1/*-----------------------------------------------------------------------------
2 * Q2NS - Quantum Network Simulator
3 * Copyright (c) 2026 quantuminternet.it
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License version 2 as
7 * published by the Free Software Foundation.
8 *---------------------------------------------------------------------------*/
9/**
10 * @file q2ns-swap-app.cc
11 * @brief Defines q2ns::SwapApp.
12 */
13#include "ns3/q2ns-swap-app.h"
14
15#include "ns3/inet-socket-address.h"
16#include "ns3/inet6-socket-address.h"
17#include "ns3/ipv4-address.h"
18#include "ns3/log.h"
19#include "ns3/packet-sink-helper.h"
20#include "ns3/packet.h"
21#include "ns3/simulator.h"
22#include "ns3/socket.h"
23#include "ns3/string.h"
24#include "ns3/tcp-socket-factory.h"
25#include "ns3/udp-socket-factory.h"
26#include "ns3/uinteger.h"
27
28#include "ns3/names.h"
29
30#include "ns3/q2ns-analysis.h"
31#include "ns3/q2ns-netcontroller.h"
32#include "ns3/q2ns-qgate.h"
33#include "ns3/q2ns-qnode.h"
34#include "ns3/q2ns-qstate.h"
35#include "ns3/q2ns-qubit.h"
36
37#include <algorithm>
38#include <cstring>
39
40
41namespace q2ns {
42
43NS_LOG_COMPONENT_DEFINE("SwapApp");
44NS_OBJECT_ENSURE_REGISTERED(SwapApp);
45
46// Small ctrl header for demux at endpoints
47struct CtrlHeader {
48 uint64_t sid; // session id
49 uint8_t m1; // Z bit
50 uint8_t m2; // X bit
51} __attribute__((packed));
52
53ns3::TypeId SwapApp::GetTypeId() {
54 using namespace ns3;
55 static TypeId tid =
56 TypeId("q2ns::SwapApp")
57 .SetParent<Application>()
58 .SetGroupName("q2ns")
59 .AddTraceSource("RoundStart", "Session round begins (sid,time).",
60 MakeTraceSourceAccessor(&SwapApp::m_traceRoundStart),
61 "ns3::TracedCallback::Uint64Time")
62 .AddTraceSource("BSMDone", "Repeater BSM complete (sid,time,m1,m2).",
63 MakeTraceSourceAccessor(&SwapApp::m_traceBSMDone),
64 "ns3::TracedCallback::Uint64TimeTimeUint8Uint8")
65 .AddTraceSource("CtrlSent", "Repeater sent ctrl bits (sid,time,m1,m2).",
66 MakeTraceSourceAccessor(&SwapApp::m_traceCtrlSent),
67 "ns3::TracedCallback::Uint64TimeTimeUint8Uint8")
68 .AddTraceSource("FrameResolved", "Endpoint accumulated all BSMs (sid,time,M1,M2).",
69 MakeTraceSourceAccessor(&SwapApp::m_traceFrameResolved),
70 "ns3::TracedCallback::Uint64TimeTimeUint8Uint8")
71 .AddTraceSource("CorrectionApplied",
72 "Emitted when the sink applies final Pauli corrections for a session.",
73 ns3::MakeTraceSourceAccessor(&SwapApp::m_traceCorrectionApplied),
74 "ns3::TracedCallback<uint64_t, ns3::Time>")
75 .AddTraceSource("VerifyFidelity", "Endpoint Bell fidelity check (sid,time,value,stderr).",
76 ns3::MakeTraceSourceAccessor(&SwapApp::m_traceVerifyFidelity),
77 "ns3::TracedCallback::Uint64TimeDoubleDouble");
78
79 return tid;
80}
81
83 // Subscribe once to qubit arrivals for all sessions on this node
84 if (auto qnode = ns3::DynamicCast<q2ns::QNode>(GetNode())) {
85 qnode->SetRecvCallback([this](std::shared_ptr<q2ns::Qubit> q) {
86 if (!q)
87 return;
88 const std::string& L = q->GetLabel();
89 constexpr const char* PFX_LIN = "swap_in_from_prev_s"; // goes into qPrev
90 constexpr const char* PFX_RIN = "swap_in_from_next_s"; // goes into qNext
91
92 if (L.rfind(PFX_LIN, 0) == 0) {
93 uint64_t sid = std::strtoull(L.c_str() + std::strlen(PFX_LIN), nullptr, 10);
94 auto it = m_sessions.find(sid);
95 if (it != m_sessions.end()) {
96 auto& s = it->second;
97 if (!s.qPrev)
98 s.qPrev = std::move(q);
99
100 // If repeater and s.qNext exists (local), trigger BSM now:
101 if ((s.cfg.role == Role::Repeater) && s.qNext && !s.haveBSM) {
102 MaybeDoBsmAndAnnounce(s);
103 }
104
105 // If this is an endpoint role, this is its final qubit (Prev endpoint keeps qPrev)
106 if (s.cfg.role != Role::Repeater && !s.haveQ) {
107 s.targetQubit = s.qPrev; // endpoint keeps its arrived qubit
108 s.haveQ = true;
109 if (s.haveF)
110 TryApply(s); // apply if frame already complete
111 }
112 }
113 } else if (L.rfind(PFX_RIN, 0) == 0) {
114 uint64_t sid = std::strtoull(L.c_str() + std::strlen(PFX_RIN), nullptr, 10);
115 auto it = m_sessions.find(sid);
116 if (it != m_sessions.end()) {
117 auto& s = it->second;
118 if (!s.qNext)
119 s.qNext = std::move(q);
120
121 // If repeater and s.qPrev exists (local), trigger BSM now:
122 if ((s.cfg.role == Role::Repeater) && s.qPrev && !s.haveBSM) {
123 MaybeDoBsmAndAnnounce(s);
124 }
125
126 // If this is an endpoint role, this is its final qubit (Next endpoint keeps qNext)
127 if (s.cfg.role != Role::Repeater && !s.haveQ) {
128 s.targetQubit = s.qNext; // endpoint keeps its arrived qubit
129 s.haveQ = true;
130 if (s.haveF)
131 TryApply(s);
132 }
133 }
134 }
135 });
136 }
137
138 for (auto& kv : m_sessions) {
139 ScheduleSession(kv.second);
140 }
141}
142
143void SwapApp::StopApplication() {
144 for (auto& kv : m_udp4)
145 if (kv.second)
146 kv.second->Close();
147 for (auto& kv : m_udp6)
148 if (kv.second)
149 kv.second->Close();
150 for (auto& kv : m_tcp)
151 if (kv.second)
152 kv.second->Close();
153 m_udp4.clear();
154 m_udp6.clear();
155 m_tcp.clear();
156 m_tcpPendingOnce.clear();
157 m_tcpPending.clear();
158 m_sinksByPort.clear();
159}
160
161void SwapApp::AddSession(const SessionConfig& cfg) {
162 SessionState s;
163 s.cfg = cfg;
164 m_sessions.emplace(cfg.sid, std::move(s));
165
166 // If endpoint, ensure sink for its ctrlPort so late-added sessions still work
167 if (cfg.role != Role::Repeater) {
168 EnsureSink(cfg.ctrlPort, cfg.proto);
169 }
170}
171
172void SwapApp::ScheduleSession(const SessionState& s) {
173 ns3::Simulator::Schedule(s.cfg.start, [this, sid = s.cfg.sid] {
174 auto it = m_sessions.find(sid);
175 if (it == m_sessions.end())
176 return;
177 m_traceRoundStart(sid, ns3::Simulator::Now());
178
179 auto& st = it->second;
180
181 switch (st.cfg.role) {
182 case Role::Repeater:
183 DoRepeaterRound(st);
184 break;
185 case Role::Prev:
186 case Role::Next:
187 DoEndpointStart(st);
188 break;
189 }
190 });
191}
192
193void SwapApp::DoRepeaterRound(SessionState& s) {
194 using namespace ns3;
195 auto qnode = ns3::DynamicCast<q2ns::QNode>(GetNode());
196 NS_ASSERT_MSG(qnode, "SwapApp(repeater) must run on a QNode");
197
198 // Generate RIGHT link if requested
199 if (s.cfg.genNext) {
200 auto bellR = qnode->CreateBellPair();
201 s.qNext = bellR.first; // keep local for BSM
202 auto rRemote = bellR.second;
203 rRemote->SetLabel(std::string("swap_in_from_prev_s") + std::to_string(s.cfg.sid));
204 bool okR = qnode->Send(rRemote, s.cfg.nextPeerId);
205 NS_ASSERT_MSG(okR, "Send(next) failed");
206 }
207
208 // Generate LEFT link if requested
209 if (s.cfg.genPrev) {
210 auto bellL = qnode->CreateBellPair();
211 s.qPrev = bellL.first; // keep local for BSM
212 auto lRemote = bellL.second;
213 lRemote->SetLabel(std::string("swap_in_from_next_s") + std::to_string(s.cfg.sid));
214 bool okL = qnode->Send(lRemote, s.cfg.prevPeerId);
215 NS_ASSERT_MSG(okL, "Send(prev) failed");
216 }
217
218 if (s.qPrev && s.qNext && !s.haveBSM) {
219 MaybeDoBsmAndAnnounce(s);
220 }
221}
222
223void SwapApp::DoEndpointStart(SessionState& s) {
224 auto qnode = ns3::DynamicCast<q2ns::QNode>(GetNode());
225 if (!qnode)
226 return;
227
228 if (s.cfg.role == Role::Prev && s.cfg.genNext) {
229 auto bell = qnode->CreateBellPair();
230 s.targetQubit = bell.first; // local half: this is Alice's final qubit
231 s.haveQ = true;
232
233 auto remote = bell.second; // remote goes to the first repeater
234 remote->SetLabel(std::string("swap_in_from_prev_s") + std::to_string(s.cfg.sid));
235 bool ok = qnode->Send(remote, s.cfg.nextPeerId);
236 NS_ASSERT_MSG(ok, "Source send(remote) failed");
237 }
238
239 // Sink (Next) does nothing here; it will receive its qubit later.
240}
241
242
243void SwapApp::EnsureSink(uint16_t port, const std::string& proto) {
244 if (m_sinksByPort.count(port))
245 return;
246 using namespace ns3;
247
248 const std::string factory =
249 (proto == "tcp" || proto == "TCP") ? "ns3::TcpSocketFactory" : "ns3::UdpSocketFactory";
250 ApplicationContainer apps;
251 if (m_useIpv6) {
252 PacketSinkHelper sinkHelper(factory, Inet6SocketAddress(Ipv6Address::GetAny(), port));
253 apps = sinkHelper.Install(GetNode());
254 } else {
255 PacketSinkHelper sinkHelper(factory, InetSocketAddress(Ipv4Address::GetAny(), port));
256 apps = sinkHelper.Install(GetNode());
257 }
258
259 auto sink = ns3::DynamicCast<ns3::PacketSink>(apps.Get(0));
260
261 // Bind Rx callback with the port we're listening on
262 sink->TraceConnectWithoutContext("Rx", ns3::MakeCallback(&q2ns::SwapApp::OnCtrlRx, this));
263
264 m_sinksByPort.emplace(port, sink);
265}
266
267
268void SwapApp::OnCtrlRx(ns3::Ptr<const ns3::Packet> pkt, const ns3::Address& from) {
269
270 if (!pkt) {
271 NS_LOG_WARN("In OnCtrlRx. pkt is nullptr.");
272 return;
273 }
274 CtrlHeader hdr{};
275 if (pkt->GetSize() < sizeof(hdr)) {
276 NS_LOG_WARN("In OnCtrlRx. pkt is too small.");
277 return;
278 }
279 pkt->CopyData(reinterpret_cast<uint8_t*>(&hdr), sizeof(hdr));
280
281 auto it = m_sessions.find(hdr.sid);
282 if (it == m_sessions.end())
283 return; // not our session
284 auto& s = it->second;
285
286 s.accumM1 ^= hdr.m1;
287 s.accumM2 ^= hdr.m2;
288 s.recv++;
289
290 NS_LOG_INFO("EP sid=" << s.cfg.sid << " recv=" << s.recv << " expect=" << s.cfg.expectedMsgs
291 << " M=(" << int(s.accumM1) << "," << int(s.accumM2) << ")");
292 if (s.recv >= s.cfg.expectedMsgs) {
293
294 s.haveF = true;
295 m_traceFrameResolved(s.cfg.sid, ns3::Simulator::Now(), s.accumM1, s.accumM2);
296 TryApply(s);
297 }
298}
299
300void SwapApp::MaybeDoBsmAndAnnounce(SessionState& s) {
301 if (s.haveBSM || !s.qPrev || !s.qNext)
302 return;
303 auto qnode = ns3::DynamicCast<q2ns::QNode>(GetNode());
304 if (!qnode)
305 return;
306
307 auto bits = qnode->MeasureBell(s.qPrev, s.qNext);
308 s.haveBSM = true;
309
310 uint8_t m1 = static_cast<uint8_t>(bits.first);
311 uint8_t m2 = static_cast<uint8_t>(bits.second);
312 m_traceBSMDone(s.cfg.sid, ns3::Simulator::Now(), m1, m2);
313
314 CtrlHeader hdr{s.cfg.sid, m1, m2};
315
316 std::vector<uint8_t> buf(std::max<uint32_t>(sizeof(CtrlHeader), m_payloadBytes), 0);
317 std::memcpy(buf.data(), &hdr, sizeof(CtrlHeader));
318 ns3::Ptr<ns3::Packet> pkt = ns3::Create<ns3::Packet>(buf.data(), buf.size());
319
320
321 auto sendV4 = [&](const ns3::Ipv4Address& dst) {
322 if (dst == ns3::Ipv4Address("0.0.0.0"))
323 return;
324 if (s.cfg.proto == "udp" || s.cfg.proto == "UDP") {
325 auto sock = GetOrCreateUdpSender(s.cfg.ctrlPort, /*v6=*/false);
326 sock->SendTo(pkt->Copy(), 0, ns3::InetSocketAddress(dst, s.cfg.ctrlPort));
327 } else {
328 ns3::Address a = ns3::InetSocketAddress(dst, s.cfg.ctrlPort);
329 auto sock = GetOrCreateTcpSender(a, /*v6=*/false, s.cfg.ctrlPort);
330 auto id = reinterpret_cast<uintptr_t>(PeekPointer(sock));
331 // Try immediate send; if not connected yet, queue once and let OnTcpConnected handle it.
332 int sent = sock->Send(pkt->Copy());
333 if (sent < 0 && sock->GetErrno() == ns3::Socket::ERROR_NOTCONN) {
334 m_tcpPendingOnce[id] = pkt->Copy();
335 }
336 }
337 };
338
339 auto sendV6 = [&](const ns3::Ipv6Address& dst6) {
340 if (dst6 == ns3::Ipv6Address::GetAny())
341 return;
342 if (s.cfg.proto == "udp" || s.cfg.proto == "UDP") {
343 ns3::Ptr<ns3::Socket> sock =
344 ns3::Socket::CreateSocket(GetNode(), ns3::UdpSocketFactory::GetTypeId());
345 // Do NOT Bind() for v6. Let Connect choose a proper global source & egress.
346 sock->Connect(ns3::Inet6SocketAddress(dst6, s.cfg.ctrlPort));
347 sock->Send(pkt->Copy());
348 sock->Close();
349 } else {
350 ns3::Address a = ns3::Inet6SocketAddress(dst6, s.cfg.ctrlPort);
351 auto sock = GetOrCreateTcpSender(a, /*v6=*/true, s.cfg.ctrlPort);
352 auto id = reinterpret_cast<uintptr_t>(PeekPointer(sock));
353 int sent = sock->Send(pkt->Copy());
354 if (sent < 0 && sock->GetErrno() == ns3::Socket::ERROR_NOTCONN) {
355 m_tcpPendingOnce[id] = pkt->Copy();
356 }
357 }
358 };
359
360
361
362 if (m_useIpv6) {
363 NS_LOG_INFO("Prev: " << s.cfg.prevEndAddr6 << " | Next: " << s.cfg.nextEndAddr6);
364 // sendV6(s.cfg.prevEndAddr6);
365 sendV6(s.cfg.nextEndAddr6);
366 } else {
367 // sendV4(s.cfg.prevEndAddr);
368 sendV4(s.cfg.nextEndAddr);
369 }
370
371 m_traceCtrlSent(s.cfg.sid, ns3::Simulator::Now(), m1, m2);
372}
373
374
375void SwapApp::TryApply(SessionState& s) {
376
377 if (!s.cfg.applyCorrections) {
378 s.done = true;
379 return;
380 } else {
381 if (s.done || !s.haveQ || !s.haveF)
382 return;
383 auto qnode = ns3::DynamicCast<q2ns::QNode>(GetNode());
384 if (!qnode || !s.targetQubit)
385 return;
386
387 // Apply X then Z (consistent with Teleportation)
388 if (s.accumM2)
389 qnode->Apply(q2ns::gates::X(), {s.targetQubit});
390 if (s.accumM1)
391 qnode->Apply(q2ns::gates::Z(), {s.targetQubit});
392
393 s.done = true;
394
395 // Fidelity verification (endpoint roles only; opt-in)
396 if ((s.cfg.role == Role::Prev || s.cfg.role == Role::Next) && s.cfg.verifyFidelity) {
397 auto qnode = ns3::DynamicCast<q2ns::QNode>(GetNode());
398 if (qnode && s.targetQubit) {
399 // Actual post-swap 2-qubit state containing this qubit
400 auto actual = m_nc->GetState(s.targetQubit); // const QState* (shared_ptr)
401 if (actual && actual->NumQubits() == 2) { // sanity; typical for clean swap chains
402 // Build reference |Phi+> in the same backend by making a fresh Bell pair locally
403 auto refBell = qnode->CreateBellPair();
404 auto ref = m_nc->GetState(refBell.first); // same backend as current default
405
406 if (ref) {
407 auto f = q2ns::analysis::Fidelity(*actual, *ref); // value + stderr
408 m_traceVerifyFidelity(s.cfg.sid, ns3::Simulator::Now(), f);
409
410 NS_LOG_INFO("Final State:\n" << actual);
411
412 if (f < s.cfg.verifyThreshold) {
413 NS_LOG_WARN("Swap fidelity check FAILED for sid=" << s.cfg.sid << " value=" << f
414 << " < threshold="
415 << s.cfg.verifyThreshold);
416 // Optional hard fail for CI:
417 // NS_ASSERT_MSG(false, "Entanglement swap fidelity below threshold");
418 }
419 }
420 }
421 }
422 }
423
424 m_traceCorrectionApplied(s.cfg.sid, ns3::Simulator::Now());
425 }
426}
427
428void SwapApp::OnTcpConnected(ns3::Ptr<ns3::Socket> sock) {
429 auto id = reinterpret_cast<uintptr_t>(PeekPointer(sock));
430 auto it = m_tcpPendingOnce.find(id);
431 if (it != m_tcpPendingOnce.end()) {
432 sock->Send(it->second);
433 m_tcpPendingOnce.erase(it);
434 }
435}
436
437
438void SwapApp::OnTcpConnectFail(ns3::Ptr<ns3::Socket> s) {
439 auto key = reinterpret_cast<uintptr_t>(PeekPointer(s));
440 m_tcpPending.erase(key);
441 NS_LOG_WARN("SwapApp: TCP connect failed");
442 s->Close();
443}
444
445
446ns3::Ptr<ns3::Socket> SwapApp::GetOrCreateUdpSender(uint16_t port, bool v6) {
447 using namespace ns3;
448 auto& map = v6 ? m_udp6 : m_udp4;
449 auto it = map.find(port);
450 if (it != map.end())
451 return it->second;
452
453 Ptr<Socket> s = Socket::CreateSocket(GetNode(), UdpSocketFactory::GetTypeId());
454 // UDP senders don't have to bind, but binding to a local port is fine.
455 if (v6)
456 s->Bind(Inet6SocketAddress(Ipv6Address::GetAny(), 0));
457 else
458 s->Bind(InetSocketAddress(Ipv4Address::GetAny(), 0));
459 map.emplace(port, s);
460 return s;
461}
462
463ns3::Ptr<ns3::Socket> SwapApp::GetOrCreateTcpSender(const ns3::Address& dst, bool v6,
464 uint16_t port) {
465 using namespace ns3;
466 // stringify address to key
467 std::ostringstream oss;
468 if (v6) {
469 Inet6SocketAddress a = Inet6SocketAddress::ConvertFrom(dst);
470 oss << a.GetIpv6();
471 } else {
472 InetSocketAddress a = InetSocketAddress::ConvertFrom(dst);
473 oss << a.GetIpv4();
474 }
475 TcpKey key{v6, port, oss.str()};
476 auto it = m_tcp.find(key);
477 if (it != m_tcp.end())
478 return it->second;
479
480 Ptr<Socket> sock = Socket::CreateSocket(GetNode(), TcpSocketFactory::GetTypeId());
481 sock->SetConnectCallback(MakeCallback(&SwapApp::OnTcpConnected, this),
482 MakeCallback(&SwapApp::OnTcpConnectFail, this));
483
484 // kick off connect once; send will happen in OnTcpConnected if needed
485 sock->Connect(dst);
486 m_tcp.emplace(key, sock);
487 return sock;
488}
489
490
491ns3::Ptr<ns3::Socket> SwapApp::GetOrCreateUdpV6Connected(const ns3::Ipv6Address& dst,
492 uint16_t port) {
493 using namespace ns3;
494 // key by textual dest + port (same scheme as TCP)
495 std::ostringstream oss;
496 oss << dst;
497 TcpKey key{/*v6=*/true, port, oss.str()};
498 auto it = m_udp6_conn.find(key);
499 if (it != m_udp6_conn.end())
500 return it->second;
501
502 Ptr<Socket> s = Socket::CreateSocket(GetNode(), UdpSocketFactory::GetTypeId());
503 // IMPORTANT: do NOT bind to :: here. Let Connect() choose a proper global source.
504 s->Connect(Inet6SocketAddress(dst, port));
505 m_udp6_conn.emplace(key, s);
506 return s;
507}
508
509
510
511} // namespace q2ns
static ns3::TypeId GetTypeId()
void StartApplication() override
ns3::TracedCallback< uint64_t, ns3::Time, uint8_t, uint8_t > m_traceBSMDone
ns3::TracedCallback< uint64_t, ns3::Time, double > m_traceVerifyFidelity
void OnCtrlRx(ns3::Ptr< const ns3::Packet > pkt, const ns3::Address &from)
ns3::TracedCallback< uint64_t, ns3::Time, uint8_t, uint8_t > m_traceFrameResolved
ns3::TracedCallback< uint64_t, ns3::Time, uint8_t, uint8_t > m_traceCtrlSent
ns3::TracedCallback< uint64_t, ns3::Time > m_traceRoundStart
ns3::TracedCallback< uint64_t, ns3::Time > m_traceCorrectionApplied
double Fidelity(const QState &a, const QState &b)
Compute fidelity between two QState objects of the same backend type.
QGate X(ns3::Time d=ns3::Seconds(0))
Return the Pauli-X gate descriptor.
Definition q2ns-qgate.h:356
QGate Z(ns3::Time d=ns3::Seconds(0))
Return the Pauli-Z gate descriptor.
Definition q2ns-qgate.h:374
struct q2ns::CtrlHeader __attribute__((packed))
uint8_t m2
uint8_t m1
uint64_t sid
ns3::Ipv6Address nextEndAddr6
ns3::Ipv6Address prevEndAddr6
std::shared_ptr< Qubit > qNext
std::shared_ptr< q2ns::Qubit > targetQubit
std::shared_ptr< Qubit > qPrev