ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
handoff.h File Reference

Go to the source code of this file.

Data Structures

struct  consensus_handoff_message_t
struct  consensus_handoff_peer_t
 consensus handoff message is intentionally a small, concrete record. Fields such as kind, generation, sequence, upload_kbps give callers the identity and current value they need. It is normally borrowed from, or owned by, the surrounding subsystem—not independently managed. More...
struct  consensus_handoff_t

Macros

#define CONSENSUS_HANDOFF_PACKET   6090
#define CONSENSUS_HANDOFF_VERSION   1
#define CONSENSUS_HANDOFF_HEADER   64
#define CONSENSUS_HANDOFF_PROBE_BYTES   65536
#define CONSENSUS_HANDOFF_FRESH_MS   15000
#define CONSENSUS_HANDOFF_PREPARE_MS   5000
#define CONSENSUS_HANDOFF_RECOVERY_MS   15000

Enumerations

enum  consensus_handoff_kind_t {
  HANDOFF_SAMPLE_START = 1 , HANDOFF_SAMPLE_DATA , HANDOFF_PREPARE , HANDOFF_READY ,
  HANDOFF_PLAN , HANDOFF_COMMIT , HANDOFF_REJECT , HANDOFF_FAILED ,
  HANDOFF_SAMPLE_ACK
}

Functions

asciichat_error_t consensus_handoff_decode (const void *data, size_t length, consensus_handoff_message_t *out)
void consensus_handoff_encode (const consensus_handoff_message_t *message, uint8_t out[64])
void consensus_handoff_init (consensus_handoff_t *state, const uint8_t session[SESSION_ID_SIZE], const uint8_t host[SESSION_ID_SIZE])
asciichat_error_t consensus_handoff_join (consensus_handoff_t *state, const uint8_t id[SESSION_ID_SIZE])
void consensus_handoff_leave (consensus_handoff_t *state, const uint8_t id[SESSION_ID_SIZE])
asciichat_error_t consensus_handoff_receive (consensus_handoff_t *state, const consensus_handoff_message_t *message, size_t length, uint64_t now_ms)
 Accept a member's probe or preparation reply using receiver-clock time. Probe metadata becomes eligible only with a matched, timely body. Preparation replies must match the current generation, round, and selected successor.
bool consensus_handoff_tick (consensus_handoff_t *state, uint64_t now_ms, consensus_handoff_message_t *action)
 Advance measured successor selection and bounded host-loss recovery. Fresh eligible peers prepare within five seconds; all recovery attempts share one 15-second budget. Only a fresh prepared peer can commit, and failure is terminal.
void consensus_handoff_host_lost (consensus_handoff_t *state, const uint8_t host[SESSION_ID_SIZE])
 Record loss of the incumbent, ignoring delayed reports about old hosts. The caller authenticates the reporter before the next tick starts recovery.

Macro Definition Documentation

◆ CONSENSUS_HANDOFF_FRESH_MS

#define CONSENSUS_HANDOFF_FRESH_MS   15000

Definition at line 13 of file handoff.h.

Referenced by consensus_handoff_tick().

◆ CONSENSUS_HANDOFF_HEADER

#define CONSENSUS_HANDOFF_HEADER   64

◆ CONSENSUS_HANDOFF_PACKET

#define CONSENSUS_HANDOFF_PACKET   6090

Definition at line 9 of file handoff.h.

Referenced by acip_handle_acds_packet().

◆ CONSENSUS_HANDOFF_PREPARE_MS

#define CONSENSUS_HANDOFF_PREPARE_MS   5000

Definition at line 14 of file handoff.h.

Referenced by consensus_handoff_tick().

◆ CONSENSUS_HANDOFF_PROBE_BYTES

#define CONSENSUS_HANDOFF_PROBE_BYTES   65536

Definition at line 12 of file handoff.h.

Referenced by acds_main(), consensus_handoff_decode(), and consensus_handoff_receive().

◆ CONSENSUS_HANDOFF_RECOVERY_MS

#define CONSENSUS_HANDOFF_RECOVERY_MS   15000

Definition at line 15 of file handoff.h.

Referenced by consensus_handoff_tick().

◆ CONSENSUS_HANDOFF_VERSION

#define CONSENSUS_HANDOFF_VERSION   1

Definition at line 10 of file handoff.h.

Referenced by consensus_handoff_decode(), and consensus_handoff_encode().

Enumeration Type Documentation

◆ consensus_handoff_kind_t

Enumerator
HANDOFF_SAMPLE_START 
HANDOFF_SAMPLE_DATA 
HANDOFF_PREPARE 
HANDOFF_READY 
HANDOFF_PLAN 
HANDOFF_COMMIT 
HANDOFF_REJECT 
HANDOFF_FAILED 
HANDOFF_SAMPLE_ACK 

Definition at line 17 of file handoff.h.

17 {
consensus_handoff_kind_t
Definition handoff.h:17
@ HANDOFF_REJECT
Definition handoff.h:24
@ HANDOFF_SAMPLE_DATA
Definition handoff.h:19
@ HANDOFF_COMMIT
Definition handoff.h:23
@ HANDOFF_FAILED
Definition handoff.h:25
@ HANDOFF_PLAN
Definition handoff.h:22
@ HANDOFF_SAMPLE_ACK
Definition handoff.h:26
@ HANDOFF_READY
Definition handoff.h:21
@ HANDOFF_PREPARE
Definition handoff.h:20
@ HANDOFF_SAMPLE_START
Definition handoff.h:18

Function Documentation

◆ consensus_handoff_decode()

asciichat_error_t consensus_handoff_decode ( const void * data,
size_t length,
consensus_handoff_message_t * out )

Definition at line 16 of file handoff.c.

16 {
17 if (!data || !m || length < CONSENSUS_HANDOFF_HEADER)
18 return SET_ERRNO(ERROR_INVALID_PARAM, "Truncated handoff frame");
19 const uint8_t *p = data;
22 : length != CONSENSUS_HANDOFF_HEADER))
23 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Invalid handoff version, kind or length");
24 memset(m, 0, sizeof(*m));
25 m->kind = p[1];
26 memcpy(m->session, p + 4, SESSION_ID_SIZE);
27 memcpy(m->participant, p + 20, SESSION_ID_SIZE);
28 m->generation = read32(p + 36);
29 m->sequence = read32(p + 40);
30 m->upload_kbps = read32(p + 44);
31 m->rtt_ms = read32(p + 48);
32 m->capabilities = p[52];
33 m->nat_tier = p[53];
34 m->port = (uint16_t)((p[54] << 8) | p[55]);
35 return ASCIICHAT_OK;
36}
#define SET_ERRNO(code,...)
unsigned short uint16_t
Definition common.h:61
unsigned char uint8_t
Definition common.h:60
@ ERROR_NETWORK_PROTOCOL
Definition error_codes.h:76
@ ASCIICHAT_OK
Definition error_codes.h:46
@ ERROR_INVALID_PARAM
#define SESSION_ID_SIZE
#define CONSENSUS_HANDOFF_HEADER
Definition handoff.h:11
#define CONSENSUS_HANDOFF_PROBE_BYTES
Definition handoff.h:12
#define CONSENSUS_HANDOFF_VERSION
Definition handoff.h:10

References ASCIICHAT_OK, consensus_handoff_message_t::capabilities, CONSENSUS_HANDOFF_HEADER, CONSENSUS_HANDOFF_PROBE_BYTES, CONSENSUS_HANDOFF_VERSION, ERROR_INVALID_PARAM, ERROR_NETWORK_PROTOCOL, consensus_handoff_message_t::generation, HANDOFF_SAMPLE_ACK, HANDOFF_SAMPLE_DATA, HANDOFF_SAMPLE_START, consensus_handoff_message_t::kind, consensus_handoff_message_t::nat_tier, consensus_handoff_message_t::participant, consensus_handoff_message_t::port, consensus_handoff_message_t::rtt_ms, consensus_handoff_message_t::sequence, consensus_handoff_message_t::session, SESSION_ID_SIZE, SET_ERRNO, and consensus_handoff_message_t::upload_kbps.

◆ consensus_handoff_encode()

void consensus_handoff_encode ( const consensus_handoff_message_t * message,
uint8_t out[64] )

◆ consensus_handoff_host_lost()

void consensus_handoff_host_lost ( consensus_handoff_t * state,
const uint8_t host[SESSION_ID_SIZE] )

Record loss of the incumbent, ignoring delayed reports about old hosts. The caller authenticates the reporter before the next tick starts recovery.

Definition at line 105 of file handoff.c.

105 {
106 if (!memcmp(s->host, host, sizeof(s->host)))
107 s->host_lost = true;
108}

References consensus_handoff_t::host, consensus_handoff_t::host_lost, and SESSION_ID_SIZE.

◆ consensus_handoff_init()

void consensus_handoff_init ( consensus_handoff_t * state,
const uint8_t session[SESSION_ID_SIZE],
const uint8_t host[SESSION_ID_SIZE] )

Definition at line 52 of file handoff.c.

53 {
54 memset(s, 0, sizeof(*s));
55 memcpy(s->session, session, SESSION_ID_SIZE);
56 memcpy(s->host, host, SESSION_ID_SIZE);
57 s->generation = 1;
58}

References consensus_handoff_t::generation, consensus_handoff_t::host, consensus_handoff_t::session, and SESSION_ID_SIZE.

◆ consensus_handoff_join()

asciichat_error_t consensus_handoff_join ( consensus_handoff_t * state,
const uint8_t id[SESSION_ID_SIZE] )

Definition at line 65 of file handoff.c.

65 {
66 if (find_peer(s, id))
67 return ASCIICHAT_OK;
68 if (s->count == MAX_SESSION_PARTICIPANTS)
69 return SET_ERRNO(ERROR_INVALID_STATE, "Handoff membership full");
70 consensus_handoff_peer_t *p = &s->peers[s->count++];
71 memset(p, 0, sizeof(*p));
72 memcpy(p->id, id, SESSION_ID_SIZE);
73 p->nat_tier = 255;
74 ++s->generation;
75 s->ready = false;
76 memset(s->successor, 0, SESSION_ID_SIZE);
77 return ASCIICHAT_OK;
78}
@ ERROR_INVALID_STATE
#define MAX_SESSION_PARTICIPANTS
consensus handoff message is intentionally a small, concrete record. Fields such as kind,...
Definition handoff.h:47
uint8_t id[SESSION_ID_SIZE]
Definition handoff.h:48

References ASCIICHAT_OK, consensus_handoff_t::count, ERROR_INVALID_STATE, consensus_handoff_t::generation, consensus_handoff_peer_t::id, MAX_SESSION_PARTICIPANTS, consensus_handoff_peer_t::nat_tier, consensus_handoff_t::peers, consensus_handoff_t::ready, SESSION_ID_SIZE, SET_ERRNO, and consensus_handoff_t::successor.

◆ consensus_handoff_leave()

void consensus_handoff_leave ( consensus_handoff_t * state,
const uint8_t id[SESSION_ID_SIZE] )

Definition at line 79 of file handoff.c.

79 {
80 // The incumbent can leave before its first measurement creates a peer entry.
81 if (memcmp(s->host, id, SESSION_ID_SIZE) == 0)
82 s->host_lost = true;
83 for (size_t i = 0; i < s->count; ++i) {
84 if (memcmp(s->peers[i].id, id, SESSION_ID_SIZE) != 0)
85 continue;
86 memmove(&s->peers[i], &s->peers[i + 1], (s->count - i - 1) * sizeof(s->peers[0]));
87 --s->count;
88 ++s->generation;
89 if (memcmp(s->host, id, SESSION_ID_SIZE) == 0)
90 s->host_lost = true;
91 // Pending acknowledgements carry the old membership generation. Reissue
92 // their preparation instead of rejecting the late reply and exhausting
93 // recovery without trying the still-eligible successor again.
94 if (!s->ready || memcmp(s->successor, id, SESSION_ID_SIZE) == 0) {
95 memset(s->successor, 0, SESSION_ID_SIZE);
96 s->ready = false;
97 }
98 return;
99 }
100}

References consensus_handoff_t::count, consensus_handoff_t::generation, consensus_handoff_t::host, consensus_handoff_t::host_lost, consensus_handoff_peer_t::id, consensus_handoff_t::peers, consensus_handoff_t::ready, SESSION_ID_SIZE, and consensus_handoff_t::successor.

◆ consensus_handoff_receive()

asciichat_error_t consensus_handoff_receive ( consensus_handoff_t * s,
const consensus_handoff_message_t * m,
size_t length,
uint64_t now )

Accept a member's probe or preparation reply using receiver-clock time. Probe metadata becomes eligible only with a matched, timely body. Preparation replies must match the current generation, round, and selected successor.

Returns
ASCIICHAT_OK if accepted, or a protocol error without accepting the frame.

Definition at line 116 of file handoff.c.

117 {
118 if (memcmp(s->session, m->session, SESSION_ID_SIZE) != 0)
119 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Wrong handoff session");
120 consensus_handoff_peer_t *p = find_peer(s, m->participant);
121 if (!p)
122 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Handoff sender is not a member");
123 if (m->kind == HANDOFF_SAMPLE_START) {
124 if (m->sequence <= p->sequence || (p->measured_ms && now - p->measured_ms < 1000))
125 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Repeated or excessive handoff probe");
126 p->sequence = m->sequence;
127 p->probe_started_ms = now;
128 p->measuring = true;
130 p->pending_nat_tier = m->nat_tier <= 4 ? m->nat_tier : 255;
131 p->pending_port = m->port;
132 } else if (m->kind == HANDOFF_SAMPLE_DATA) {
133 if (!p->measuring || m->sequence != p->sequence ||
135 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Unmatched handoff probe");
136 p->measuring = false;
137 uint64_t elapsed = now - p->probe_started_ms;
138 if (elapsed == 0)
139 elapsed = 1;
140 p->valid = elapsed <= 5000;
141 if (p->valid) {
144 p->port = p->pending_port;
145 // A quality refresh cannot extend a failed recovery by retrying the same host.
146 if (!s->host_lost)
147 p->rejected = false;
148 }
150 p->measured_ms = now;
151 } else if (m->kind == HANDOFF_READY || m->kind == HANDOFF_REJECT) {
152 if (m->generation != s->generation || m->sequence != s->round ||
153 memcmp(m->participant, s->successor, SESSION_ID_SIZE))
154 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Obsolete handoff preparation");
155 if (m->kind == HANDOFF_READY && now <= s->prepare_deadline_ms)
156 s->ready = true;
157 else {
158 p->rejected = true;
159 s->ready = false;
160 memset(s->successor, 0, SESSION_ID_SIZE);
161 }
162 } else
163 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Client cannot announce a handoff decision");
164 return ASCIICHAT_OK;
165}
unsigned int uint32_t
Definition common.h:62
unsigned long long uint64_t
Definition common.h:63
uint8_t session[SESSION_ID_SIZE]
Definition handoff.h:31
uint8_t participant[SESSION_ID_SIZE]
Definition handoff.h:32
uint64_t probe_started_ms
Definition handoff.h:57
uint8_t pending_capabilities
Definition handoff.h:52
uint8_t session[SESSION_ID_SIZE]
Definition handoff.h:67
uint8_t successor[SESSION_ID_SIZE]
Definition handoff.h:69
uint32_t generation
Definition handoff.h:70

References ASCIICHAT_OK, consensus_handoff_message_t::capabilities, consensus_handoff_peer_t::capabilities, CONSENSUS_HANDOFF_HEADER, CONSENSUS_HANDOFF_PROBE_BYTES, ERROR_NETWORK_PROTOCOL, consensus_handoff_message_t::generation, consensus_handoff_t::generation, HANDOFF_READY, HANDOFF_REJECT, HANDOFF_SAMPLE_DATA, HANDOFF_SAMPLE_START, consensus_handoff_t::host_lost, consensus_handoff_message_t::kind, consensus_handoff_peer_t::measured_ms, consensus_handoff_peer_t::measuring, consensus_handoff_message_t::nat_tier, consensus_handoff_peer_t::nat_tier, consensus_handoff_message_t::participant, consensus_handoff_peer_t::pending_capabilities, consensus_handoff_peer_t::pending_nat_tier, consensus_handoff_peer_t::pending_port, consensus_handoff_message_t::port, consensus_handoff_peer_t::port, consensus_handoff_peer_t::probe_started_ms, consensus_handoff_t::ready, consensus_handoff_peer_t::rejected, consensus_handoff_t::round, consensus_handoff_message_t::sequence, consensus_handoff_peer_t::sequence, consensus_handoff_message_t::session, consensus_handoff_t::session, SESSION_ID_SIZE, SET_ERRNO, consensus_handoff_t::successor, consensus_handoff_peer_t::upload_kbps, and consensus_handoff_peer_t::valid.

◆ consensus_handoff_tick()

bool consensus_handoff_tick ( consensus_handoff_t * s,
uint64_t now,
consensus_handoff_message_t * a )

Advance measured successor selection and bounded host-loss recovery. Fresh eligible peers prepare within five seconds; all recovery attempts share one 15-second budget. Only a fresh prepared peer can commit, and failure is terminal.

Parameters
nowMonotonic receiver time in milliseconds.
aOutput action to send when the function returns true.
Returns
True if a PREPARE, COMMIT, or terminal FAILED action was produced.

Definition at line 207 of file handoff.c.

207 {
208 memset(a, 0, sizeof(*a));
209 memcpy(a->session, s->session, SESSION_ID_SIZE);
210 a->generation = s->generation;
211 if (s->failure_announced)
212 return false; // Terminal until the session is recreated.
213 if (!s->monitoring) {
214 s->monitoring = true;
215 s->monitoring_started_ms = now;
216 }
217 consensus_handoff_peer_t *incumbent = find_peer(s, s->host);
218 uint64_t last_host_sample = incumbent && incumbent->valid ? incumbent->measured_ms : s->monitoring_started_ms;
219 if (now - last_host_sample > CONSENSUS_HANDOFF_FRESH_MS)
220 s->host_lost = true;
221 if (s->host_lost && !s->failure_deadline_ms)
223 // The recovery budget is shared by all candidates, including silent/rejecting
224 // candidates that continue sending fresh probes. A late READY cannot revive it.
225 if (s->host_lost && now >= s->failure_deadline_ms) {
226 s->failure_announced = true;
227 s->ready = false;
228 memset(s->successor, 0, sizeof(s->successor));
229 a->kind = HANDOFF_FAILED;
230 a->sequence = s->round;
231 handoff_record_action(s, a, now, 0);
232 return true;
233 }
234 if (s->ready && s->host_lost) {
235 consensus_handoff_peer_t *p = find_peer(s, s->successor);
236 if (p && p->valid && p->capabilities && now - p->measured_ms <= CONSENSUS_HANDOFF_FRESH_MS) {
237 a->kind = HANDOFF_COMMIT;
238 memcpy(a->participant, s->successor, SESSION_ID_SIZE);
239 a->sequence = s->round;
240 a->port = p->port;
242 handoff_record_action(s, a, now, 1);
243 s->host_sequence = a->sequence;
244 memcpy(s->host, s->successor, SESSION_ID_SIZE);
245 s->host_lost = false;
246 s->monitoring_started_ms = now;
247 s->ready = false;
248 s->failure_deadline_ms = 0;
249 s->failure_announced = false;
250 for (size_t i = 0; i < s->count; ++i)
251 s->peers[i].rejected = false;
252 memset(s->successor, 0, SESSION_ID_SIZE);
253 return true;
254 }
255 s->ready = false;
256 memset(s->successor, 0, SESSION_ID_SIZE);
257 }
258 uint8_t zero[SESSION_ID_SIZE] = {0};
259 if (memcmp(s->successor, zero, sizeof(s->successor))) {
260 consensus_handoff_peer_t *p = find_peer(s, s->successor);
261 if (p && p->valid && p->capabilities && now - p->measured_ms <= CONSENSUS_HANDOFF_FRESH_MS &&
262 (s->ready || now < s->prepare_deadline_ms))
263 return false;
264 if (p && !s->ready)
265 p->rejected = true;
266 memset(s->successor, 0, SESSION_ID_SIZE);
267 s->ready = false;
268 }
269 participant_metrics_t metrics[MAX_SESSION_PARTICIPANTS];
270 size_t count = 0;
271 for (size_t i = 0; i < s->count; ++i) {
273 if (!p->valid || p->rejected || !p->capabilities || now - p->measured_ms > CONSENSUS_HANDOFF_FRESH_MS ||
274 memcmp(p->id, s->host, SESSION_ID_SIZE) == 0)
275 continue;
276 participant_metrics_t *m = &metrics[count++];
277 memset(m, 0, sizeof(*m));
278 memcpy(m->participant_id, p->id, SESSION_ID_SIZE);
279 m->nat_tier = p->nat_tier <= 4 ? p->nat_tier : 4;
280 m->upload_kbps = p->upload_kbps;
281 m->rtt_ns = 500000000; /* Unknown RTT earns no latency bonus. */
282 }
283 if (!count)
284
285 return false;
286 int best = -1, backup = -1;
287 if (consensus_election_choose_hosts(metrics, (int)count, &best, &backup) != ASCIICHAT_OK)
288 return false;
289 memcpy(s->successor, metrics[best].participant_id, SESSION_ID_SIZE);
290 ++s->round;
293 memcpy(a->participant, s->successor, SESSION_ID_SIZE);
294 a->sequence = s->round;
295 handoff_record_action(s, a, now, count);
296 return true;
297}
asciichat_error_t consensus_election_choose_hosts(const participant_metrics_t *metrics, int num_metrics, int *out_best_index, int *out_backup_index)
Elect best and backup host from metrics.
Definition election.c:79
#define CONSENSUS_HANDOFF_PREPARE_MS
Definition handoff.h:14
#define CONSENSUS_HANDOFF_FRESH_MS
Definition handoff.h:13
#define CONSENSUS_HANDOFF_RECOVERY_MS
Definition handoff.h:15
uint32_t host_sequence
Last committed round; preparation does not change host authority.
Definition handoff.h:72
uint64_t monitoring_started_ms
Definition handoff.h:78
uint64_t prepare_deadline_ms
Definition handoff.h:73
consensus_handoff_peer_t peers[MAX_SESSION_PARTICIPANTS]
Definition handoff.h:80
uint64_t failure_deadline_ms
Definition handoff.h:77
uint8_t host[SESSION_ID_SIZE]
Definition handoff.h:68

References ASCIICHAT_OK, consensus_handoff_message_t::capabilities, consensus_handoff_peer_t::capabilities, consensus_election_choose_hosts(), CONSENSUS_HANDOFF_FRESH_MS, CONSENSUS_HANDOFF_PREPARE_MS, CONSENSUS_HANDOFF_RECOVERY_MS, consensus_handoff_t::count, consensus_handoff_t::failure_announced, consensus_handoff_t::failure_deadline_ms, consensus_handoff_message_t::generation, consensus_handoff_t::generation, HANDOFF_COMMIT, HANDOFF_FAILED, HANDOFF_PREPARE, consensus_handoff_t::host, consensus_handoff_t::host_lost, consensus_handoff_t::host_sequence, consensus_handoff_peer_t::id, consensus_handoff_message_t::kind, MAX_SESSION_PARTICIPANTS, consensus_handoff_peer_t::measured_ms, consensus_handoff_t::monitoring, consensus_handoff_t::monitoring_started_ms, consensus_handoff_peer_t::nat_tier, consensus_handoff_message_t::participant, consensus_handoff_t::peers, consensus_handoff_message_t::port, consensus_handoff_peer_t::port, consensus_handoff_t::prepare_deadline_ms, consensus_handoff_t::ready, consensus_handoff_peer_t::rejected, consensus_handoff_t::round, consensus_handoff_message_t::sequence, consensus_handoff_message_t::session, consensus_handoff_t::session, SESSION_ID_SIZE, consensus_handoff_t::successor, consensus_handoff_peer_t::upload_kbps, and consensus_handoff_peer_t::valid.