ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
happy_eyeballs.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file happy_eyeballs.cpp
6 * @brief Dual-stack destination selection + Happy Eyeballs fallback (see happy_eyeballs.h).
7 */
8
10
11#if PC_ENABLE_HAPPY_EYEBALLS
12
13namespace
14{
15// Effective family for interleave: an IPv4-mapped IPv6 address is treated as IPv4.
16bool eff_is_v6(const pc_ip *ip)
17{
19}
20
21int scope_rank(const pc_ip *ip)
22{
23 switch (pc_ip_classify(ip))
24 {
26 return 5;
28 return 4;
30 return 3;
32 return 2;
34 return 1;
35 default:
36 return 0; // unspecified
37 }
38}
39} // namespace
40
41int pc_he_pref(const pc_ip *ip)
42{
43 if (!ip || ip->family == pc_ip_family::PC_IP_NONE)
44 {
45 return -1;
46 }
47 // Scope dominates; within a scope a native IPv6 outranks IPv4 (RFC 6724 default policy).
48 return scope_rank(ip) * 2 + (eff_is_v6(ip) ? 1 : 0);
49}
50
51void pc_he_order(pc_ip *list, size_t n)
52{
53 if (!list || n < 2)
54 {
55 return;
56 }
57
58 // Stable insertion sort by preference (descending).
59 for (size_t i = 1; i < n; i++)
60 {
61 pc_ip key = list[i];
62 int kp = pc_he_pref(&key);
63 size_t j = i;
64 while (j > 0 && pc_he_pref(&list[j - 1]) < kp)
65 {
66 list[j] = list[j - 1];
67 j--;
68 }
69 list[j] = key;
70 }
71
72 if (n > PC_HE_MAX)
73 {
74 return; // too large to interleave in the fixed scratch; sorted order stands.
75 }
76
77 // RFC 8305 sec 4: interleave families so successive attempts alternate v6/v4. Preserve the
78 // preference order within each family; start with the family of the top-preference address.
79 pc_ip out[PC_HE_MAX];
80 size_t o = 0;
81 size_t iv6 = 0;
82 size_t iv4 = 0;
83 // Collect indices per family in preference order.
84 size_t v6[PC_HE_MAX];
85 size_t v4[PC_HE_MAX];
86 size_t nv6 = 0;
87 size_t nv4 = 0;
88 for (size_t i = 0; i < n; i++)
89 {
90 if (eff_is_v6(&list[i]))
91 {
92 v6[nv6++] = i;
93 }
94 else
95 {
96 v4[nv4++] = i;
97 }
98 }
99 bool take_v6 = eff_is_v6(&list[0]); // whichever family the best address belongs to goes first
100 while (iv6 < nv6 || iv4 < nv4)
101 {
102 if (take_v6 && iv6 < nv6)
103 {
104 out[o++] = list[v6[iv6++]];
105 }
106 else if (!take_v6 && iv4 < nv4)
107 {
108 out[o++] = list[v4[iv4++]];
109 }
110 else if (iv6 < nv6) // the preferred family is exhausted; drain the other
111 {
112 out[o++] = list[v6[iv6++]];
113 }
114 else
115 {
116 out[o++] = list[v4[iv4++]];
117 }
118 take_v6 = !take_v6;
119 }
120 for (size_t i = 0; i < n; i++)
121 {
122 list[i] = out[i];
123 }
124}
125
126bool pc_he_attempt_due(uint32_t last_start_ms, uint32_t now_ms, uint32_t attempt_delay_ms)
127{
128 uint32_t elapsed = now_ms - last_start_ms; // wrap-safe modular subtraction
129 return elapsed >= attempt_delay_ms;
130}
131
132#endif // PC_ENABLE_HAPPY_EYEBALLS
Dual-stack destination selection + Happy Eyeballs fallback (PC_ENABLE_HAPPY_EYEBALLS).
pc_ip_scope pc_ip_classify(const pc_ip *ip)
Classify ip into a pc_ip_scope.
Definition ip.cpp:568
bool pc_ip_is_v4_mapped(const pc_ip *ip)
True if ip is an IPv4-mapped IPv6 address (::ffff:a.b.c.d, RFC 4291 ยง2.5.5.2).
Definition ip.cpp:563
@ PC_IP_SCOPE_GLOBAL
globally routable unicast
@ PC_IP_SCOPE_LINK_LOCAL
169.254.0.0/16 / fe80::/10
@ PC_IP_SCOPE_LOOPBACK
127.0.0.0/8 / ::1
@ PC_IP_SCOPE_MULTICAST
224.0.0.0/4 / ff00::/8
@ PC_IP_SCOPE_PRIVATE
RFC1918 (10/8, 172.16/12, 192.168/16) / ULA fc00::/7.
@ PC_IP_NONE
empty / unparsed
@ PC_IP_V6
IPv6 (bytes[0..15])
A v4 or v6 address in network (big-endian) byte order.
Definition ip.h:52
pc_ip_family family
address family tag
Definition ip.h:53