Skip to content

Commit 1aa2e68

Browse files
hyperpolymathclaude
andcommitted
feat(v-lang): add 27 V-lang connectors — Wave A (network, security, application)
Network/Routing (9): v_bgp, v_ospf, v_bfd, v_stun, v_sdn, v_proxy, v_socks, v_loadbalancer, v_mdns Security (9): v_ca, v_kms, v_kerberos, v_radius, v_tacacs, v_pqc, v_ids, v_zerotrust, v_ocsp Application (9): v_smtp, v_imap, v_pop3, v_ftp, v_irc, v_xmpp, v_chat, v_voip, v_rtsp 102 files, 12,952 lines. All with tests, doc comments, real logic. V-lang connector count: 48/94 protocols covered (51%). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 7746e32 commit 1aa2e68

102 files changed

Lines changed: 12952 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
= v_bfd
2+
// SPDX-License-Identifier: PMPL-1.0-or-later
3+
4+
Bidirectional Forwarding Detection (BFD) session management and fast failure
5+
detection for the V-Ecosystem API interfaces layer. Implements session state
6+
machine, interval negotiation, and diagnostic codes per RFC 5880.
7+
Maps to `proven-servers/protocols/proven-bfd`.
8+
9+
== Author
10+
11+
Jonathan D.A. Jewell
12+
13+
== Source
14+
15+
`src/bfd.v`
16+
17+
== Tests
18+
19+
`tests/bfd_test.v`
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
//
4+
// v_bfd -- Bidirectional Forwarding Detection (BFD) session management and
5+
// fast failure detection for the V-Ecosystem.
6+
// Maps to proven-servers/protocols/proven-bfd.
7+
// Implements the BFD state machine, interval negotiation, and diagnostic
8+
// codes per RFC 5880.
9+
module v_bfd
10+
11+
import rand
12+
import time
13+
14+
// SessionState enumerates the BFD session states per RFC 5880 section 4.1.
15+
pub enum SessionState as u8 {
16+
admin_down = 0
17+
down = 1
18+
init = 2
19+
up = 3
20+
}
21+
22+
// session_state_to_string returns the human-readable label for a SessionState.
23+
pub fn session_state_to_string(ss SessionState) string {
24+
return match ss {
25+
.admin_down { 'AdminDown' }
26+
.down { 'Down' }
27+
.init { 'Init' }
28+
.up { 'Up' }
29+
}
30+
}
31+
32+
// DiagnosticCode enumerates the BFD diagnostic codes per RFC 5880 section 4.1.
33+
pub enum DiagnosticCode as u8 {
34+
none = 0
35+
control_expired = 1
36+
echo_failed = 2
37+
neighbor_down = 3
38+
forwarding_reset = 4
39+
path_down = 5
40+
}
41+
42+
// diagnostic_to_string returns the human-readable label for a DiagnosticCode.
43+
pub fn diagnostic_to_string(dc DiagnosticCode) string {
44+
return match dc {
45+
.none { 'No Diagnostic' }
46+
.control_expired { 'Control Detection Time Expired' }
47+
.echo_failed { 'Echo Function Failed' }
48+
.neighbor_down { 'Neighbor Signaled Session Down' }
49+
.forwarding_reset { 'Forwarding Plane Reset' }
50+
.path_down { 'Path Down' }
51+
}
52+
}
53+
54+
// BfdSession represents a single BFD session with a remote peer including
55+
// discriminators, state, timing parameters, and diagnostic information.
56+
pub struct BfdSession {
57+
pub:
58+
// local_disc is the locally-assigned session discriminator.
59+
local_disc u32
60+
// remote_addr is the IP address of the remote BFD peer.
61+
remote_addr string
62+
// desired_min_tx is the minimum desired transmit interval in microseconds.
63+
desired_min_tx u32 = 1_000_000
64+
// required_min_rx is the minimum required receive interval in microseconds.
65+
required_min_rx u32 = 1_000_000
66+
// detect_mult is the detection time multiplier.
67+
detect_mult u8 = 3
68+
pub mut:
69+
// remote_disc is the discriminator assigned by the remote peer.
70+
remote_disc u32
71+
// state is the current session state.
72+
state SessionState = .down
73+
// remote_state is the last known state of the remote peer.
74+
remote_state SessionState = .down
75+
// diag is the current diagnostic code.
76+
diag DiagnosticCode = .none
77+
// local_min_tx is the negotiated transmit interval in microseconds.
78+
local_min_tx u32 = 1_000_000
79+
// remote_min_rx is the remote peer's minimum receive interval.
80+
remote_min_rx u32 = 1_000_000
81+
// last_rx is the timestamp of the last received packet.
82+
last_rx ?time.Time
83+
// demand_mode indicates whether demand mode is active.
84+
demand_mode bool
85+
}
86+
87+
// detection_time computes the detection time in microseconds based on the
88+
// negotiated intervals and the detection multiplier.
89+
pub fn (s BfdSession) detection_time() u64 {
90+
// Detection time = detect_mult * max(negotiated tx, remote min rx)
91+
agreed_interval := if s.local_min_tx > s.remote_min_rx {
92+
u64(s.local_min_tx)
93+
} else {
94+
u64(s.remote_min_rx)
95+
}
96+
return u64(s.detect_mult) * agreed_interval
97+
}
98+
99+
// is_timed_out checks whether the session has exceeded its detection time
100+
// without receiving a packet from the remote peer.
101+
pub fn (s BfdSession) is_timed_out() bool {
102+
rx := s.last_rx or { return true }
103+
elapsed := u64(time.since(rx).microseconds())
104+
return elapsed > s.detection_time()
105+
}
106+
107+
// BfdServer manages multiple BFD sessions and handles packet processing.
108+
pub struct BfdServer {
109+
pub:
110+
// listen_port is the UDP port for BFD control packets (default 3784).
111+
listen_port int = 3784
112+
pub mut:
113+
// sessions contains all active BFD sessions indexed by local discriminator.
114+
sessions []BfdSession
115+
// next_disc is the next discriminator value to assign.
116+
next_disc u32 = 1
117+
}
118+
119+
// new_server creates a new BfdServer listening on the given port.
120+
pub fn new_server(port int) &BfdServer {
121+
return &BfdServer{
122+
listen_port: port
123+
}
124+
}
125+
126+
// create_session establishes a new BFD session with the given remote address
127+
// and timing parameters. Returns the local discriminator.
128+
pub fn (mut s BfdServer) create_session(remote_addr string, desired_min_tx u32, required_min_rx u32, detect_mult u8) u32 {
129+
disc := s.next_disc
130+
s.next_disc++
131+
s.sessions << BfdSession{
132+
local_disc: disc
133+
remote_addr: remote_addr
134+
desired_min_tx: desired_min_tx
135+
required_min_rx: required_min_rx
136+
detect_mult: detect_mult
137+
state: .down
138+
local_min_tx: desired_min_tx
139+
}
140+
return disc
141+
}
142+
143+
// find_session looks up a session by its local discriminator.
144+
// Returns an error if no session with that discriminator exists.
145+
pub fn (s BfdServer) find_session(local_disc u32) !&BfdSession {
146+
for i, _ in s.sessions {
147+
if s.sessions[i].local_disc == local_disc {
148+
return unsafe { &s.sessions[i] }
149+
}
150+
}
151+
return error('BFD session not found: discriminator ${local_disc}')
152+
}
153+
154+
// process_packet handles an incoming BFD control packet by updating the
155+
// corresponding session's state machine. The state transitions follow
156+
// RFC 5880 section 6.8.6.
157+
pub fn (mut s BfdServer) process_packet(local_disc u32, remote_disc u32, remote_state SessionState, remote_diag DiagnosticCode) ! {
158+
mut idx := -1
159+
for i, session in s.sessions {
160+
if session.local_disc == local_disc {
161+
idx = i
162+
break
163+
}
164+
}
165+
if idx < 0 {
166+
return error('BFD session not found: discriminator ${local_disc}')
167+
}
168+
169+
s.sessions[idx].remote_disc = remote_disc
170+
s.sessions[idx].remote_state = remote_state
171+
s.sessions[idx].last_rx = time.now()
172+
173+
// BFD state machine transitions per RFC 5880 section 6.8.6
174+
match s.sessions[idx].state {
175+
.admin_down {
176+
// No transitions from AdminDown via received packets
177+
}
178+
.down {
179+
match remote_state {
180+
.down {
181+
s.sessions[idx].state = .init
182+
}
183+
.init {
184+
s.sessions[idx].state = .up
185+
s.sessions[idx].diag = .none
186+
}
187+
else {}
188+
}
189+
}
190+
.init {
191+
match remote_state {
192+
.init, .up {
193+
s.sessions[idx].state = .up
194+
s.sessions[idx].diag = .none
195+
}
196+
else {}
197+
}
198+
}
199+
.up {
200+
match remote_state {
201+
.down, .admin_down {
202+
s.sessions[idx].state = .down
203+
s.sessions[idx].diag = .neighbor_down
204+
}
205+
else {}
206+
}
207+
}
208+
}
209+
}
210+
211+
// set_intervals updates the timing parameters for a session and triggers
212+
// renegotiation of the actual transmit interval.
213+
pub fn (mut s BfdServer) set_intervals(local_disc u32, desired_min_tx u32, required_min_rx u32) ! {
214+
for i, session in s.sessions {
215+
if session.local_disc == local_disc {
216+
s.sessions[i] = BfdSession{
217+
...s.sessions[i]
218+
desired_min_tx: desired_min_tx
219+
required_min_rx: required_min_rx
220+
local_min_tx: if desired_min_tx > s.sessions[i].remote_min_rx {
221+
desired_min_tx
222+
} else {
223+
s.sessions[i].remote_min_rx
224+
}
225+
}
226+
return
227+
}
228+
}
229+
return error('BFD session not found: discriminator ${local_disc}')
230+
}
231+
232+
// get_session_state returns the current state and diagnostic for a session.
233+
pub fn (s BfdServer) get_session_state(local_disc u32) !(SessionState, DiagnosticCode) {
234+
for session in s.sessions {
235+
if session.local_disc == local_disc {
236+
return session.state, session.diag
237+
}
238+
}
239+
return error('BFD session not found: discriminator ${local_disc}')
240+
}
241+
242+
// session_count returns the number of active BFD sessions.
243+
pub fn (s BfdServer) session_count() int {
244+
return s.sessions.len
245+
}
246+
247+
// admin_down forces a session into AdminDown state.
248+
pub fn (mut s BfdServer) admin_down(local_disc u32) ! {
249+
for i, session in s.sessions {
250+
if session.local_disc == local_disc {
251+
s.sessions[i].state = .admin_down
252+
s.sessions[i].diag = .none
253+
return
254+
}
255+
}
256+
return error('BFD session not found: discriminator ${local_disc}')
257+
}

0 commit comments

Comments
 (0)