Skip to main content

rns_net/common/
event.rs

1//! Event types for the driver loop — generic over the writer type.
2
3use std::fmt;
4use std::io::Read;
5use std::net::IpAddr;
6use std::sync::mpsc;
7use std::time::Duration;
8
9use rns_core::announce::ValidatedAnnounce;
10use rns_core::transport::announce_verify_queue::AnnounceVerifyKey;
11use rns_core::transport::types::{InterfaceId, InterfaceInfo};
12
13use crate::common::link_manager::RequestResponse;
14
15/// Identity hash and public key supplied to request callbacks.
16pub type RequestRemoteIdentity = ([u8; 16], [u8; 64]);
17/// Callback for an immediate byte response.
18pub type ByteRequestHandler =
19    dyn Fn([u8; 16], &str, &[u8], Option<&RequestRemoteIdentity>) -> Option<Vec<u8>> + Send;
20/// Callback for an immediate packet or resource response.
21pub type ResponseRequestHandler =
22    dyn Fn([u8; 16], &str, &[u8], Option<&RequestRemoteIdentity>) -> Option<RequestResponse> + Send;
23/// Callback for an application-managed deferred response.
24pub type DeferredRequestHandler =
25    dyn Fn([u8; 16], &str, [u8; 16], &[u8], Option<&RequestRemoteIdentity>) + Send;
26
27/// Why a best-effort link datagram could not be admitted by the node.
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub enum LinkDatagramError {
30    DriverQueueFull,
31    DriverStopped,
32    AdmissionTimedOut,
33    LinkNotFound,
34    LinkNotActive,
35    PayloadTooLarge { maximum: usize, actual: usize },
36    EncryptionFailed,
37    PacketEncodingFailed,
38}
39
40impl fmt::Display for LinkDatagramError {
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        match self {
43            Self::DriverQueueFull => write!(f, "driver queue is full"),
44            Self::DriverStopped => write!(f, "driver has stopped"),
45            Self::AdmissionTimedOut => write!(f, "driver admission timed out"),
46            Self::LinkNotFound => write!(f, "link was not found"),
47            Self::LinkNotActive => write!(f, "link is not active"),
48            Self::PayloadTooLarge { maximum, actual } => {
49                write!(
50                    f,
51                    "payload is too large ({actual} bytes, maximum {maximum})"
52                )
53            }
54            Self::EncryptionFailed => write!(f, "link encryption failed"),
55            Self::PacketEncodingFailed => write!(f, "link packet encoding failed"),
56        }
57    }
58}
59
60impl std::error::Error for LinkDatagramError {}
61
62#[derive(Debug, Clone, Default, PartialEq)]
63pub struct InterfaceTelemetry {
64    pub cpu_load: Option<f64>,
65    pub mem_load: Option<f64>,
66    pub switch_id: Option<[u8; 4]>,
67    pub endpoint_id: Option<[u8; 8]>,
68    pub via_switch_id: Option<[u8; 4]>,
69    pub peers: Option<usize>,
70}
71
72#[derive(Debug, Clone)]
73pub struct DynamicInterfaceRegistration {
74    pub info: InterfaceInfo,
75    pub interface_type: String,
76    pub parent_id: InterfaceId,
77    pub telemetry: InterfaceTelemetry,
78    pub ifac: Option<crate::ifac::IfacState>,
79}
80
81/// Policy for handling incoming direct-connect proposals.
82#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
83pub enum HolePunchPolicy {
84    /// Reject all proposals.
85    Reject,
86    /// Accept all proposals automatically.
87    #[default]
88    AcceptAll,
89    /// Accept proposals only after the Reticulum link peer has identified itself.
90    IdentifiedOnly,
91    /// Ask the application callback.
92    AskApp,
93}
94
95/// Scalar runtime-config value.
96#[derive(Debug, Clone, PartialEq)]
97pub enum RuntimeConfigValue {
98    Int(i64),
99    Float(f64),
100    Bool(bool),
101    String(String),
102    Null,
103}
104
105/// Source of a runtime-config value.
106#[derive(Debug, Clone, Copy, PartialEq, Eq)]
107pub enum RuntimeConfigSource {
108    Startup,
109    RuntimeOverride,
110}
111
112/// How a runtime-config change applies.
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
114pub enum RuntimeConfigApplyMode {
115    Immediate,
116    NewConnectionsOnly,
117    NextReconnect,
118    RestartRequired,
119}
120
121/// A runtime-config entry exposed by the daemon.
122#[derive(Debug, Clone, PartialEq)]
123pub struct RuntimeConfigEntry {
124    pub key: String,
125    pub value: RuntimeConfigValue,
126    pub default: RuntimeConfigValue,
127    pub source: RuntimeConfigSource,
128    pub apply_mode: RuntimeConfigApplyMode,
129    pub description: Option<String>,
130}
131
132/// Runtime-config mutation error.
133#[derive(Debug, Clone, PartialEq, Eq)]
134pub struct RuntimeConfigError {
135    pub code: RuntimeConfigErrorCode,
136    pub message: String,
137}
138
139/// Category of runtime-config mutation error.
140#[derive(Debug, Clone, Copy, PartialEq, Eq)]
141pub enum RuntimeConfigErrorCode {
142    UnknownKey,
143    InvalidType,
144    InvalidValue,
145    Unsupported,
146    NotFound,
147    ApplyFailed,
148}
149
150#[derive(Debug, Clone, Copy, PartialEq, Eq)]
151pub enum LifecycleState {
152    Active,
153    Draining,
154    Stopping,
155    Stopped,
156}
157
158#[derive(Debug, Clone, PartialEq)]
159pub struct DrainStatus {
160    pub state: LifecycleState,
161    pub drain_age_seconds: Option<f64>,
162    pub deadline_remaining_seconds: Option<f64>,
163    pub drain_complete: bool,
164    pub interface_writer_queued_frames: usize,
165    pub provider_backlog_events: usize,
166    pub provider_consumer_queued_events: usize,
167    pub detail: Option<String>,
168}
169
170/// Events sent to the driver thread.
171///
172/// `W` is the writer type (e.g. `Box<dyn Writer>` for sync,
173/// or a channel sender for async).
174// Boxing the large registration variant would change this published event API.
175#[allow(clippy::large_enum_variant)]
176pub enum Event<W: Send> {
177    /// A decoded frame arrived from an interface.
178    Frame {
179        interface_id: InterfaceId,
180        data: Vec<u8>,
181        rssi: Option<i16>,
182        snr: Option<f32>,
183    },
184    /// (Internal) An announce was verified off-thread and is ready for driver-side processing.
185    AnnounceVerified {
186        key: AnnounceVerifyKey,
187        validated: ValidatedAnnounce,
188        sig_cache_key: [u8; 32],
189    },
190    /// (Internal) An announce failed off-thread verification.
191    AnnounceVerifyFailed {
192        key: AnnounceVerifyKey,
193        sig_cache_key: [u8; 32],
194    },
195    /// An interface came online after (re)connecting.
196    /// Carries a new writer if the connection was re-established.
197    /// Carries InterfaceInfo if this is a new dynamic interface (e.g. TCP server client).
198    InterfaceUp(InterfaceId, Option<W>, Option<InterfaceInfo>),
199    /// Register a dynamic interface with an exact type and parent identity.
200    DynamicInterfaceUp {
201        id: InterfaceId,
202        writer: W,
203        registration: DynamicInterfaceRegistration,
204    },
205    /// Update optional device/interface telemetry without replacing counters.
206    InterfaceTelemetry {
207        interface_id: InterfaceId,
208        telemetry: InterfaceTelemetry,
209    },
210    /// An interface went offline (socket closed, error).
211    InterfaceDown(InterfaceId),
212    /// Periodic maintenance tick (1s interval).
213    Tick,
214    /// Enter drain mode and stop admitting new work.
215    BeginDrain { timeout: Duration },
216    /// Shut down the driver loop.
217    Shutdown,
218    /// Send an outbound packet.
219    SendOutbound {
220        raw: Vec<u8>,
221        dest_type: u8,
222        attached_interface: Option<InterfaceId>,
223    },
224    /// Register a local destination.
225    RegisterDestination { dest_hash: [u8; 16], dest_type: u8 },
226    /// Remember the latest explicit SINGLE announce for shared-client replay.
227    StoreSharedAnnounce {
228        dest_hash: [u8; 16],
229        name_hash: [u8; 10],
230        identity_prv_key: [u8; 64],
231        app_data: Option<Vec<u8>>,
232    },
233    /// Deregister a local destination.
234    DeregisterDestination { dest_hash: [u8; 16] },
235    /// Deregister a link destination (stop accepting incoming links).
236    DeregisterLinkDestination { dest_hash: [u8; 16] },
237    /// Query driver state. Response is sent via the provided channel.
238    Query(QueryRequest, mpsc::Sender<QueryResponse>),
239    /// Register a link destination (accepts incoming LINKREQUEST).
240    RegisterLinkDestination {
241        dest_hash: [u8; 16],
242        sig_prv_bytes: [u8; 32],
243        sig_pub_bytes: [u8; 32],
244        resource_strategy: u8,
245        max_request_size: Option<usize>,
246    },
247    /// Register a request handler for a path on established links.
248    RegisterRequestHandler {
249        path: String,
250        allowed_list: Option<Vec<[u8; 16]>>,
251        handler: Box<ByteRequestHandler>,
252    },
253    /// Register a request handler that may return resource responses with metadata.
254    RegisterRequestHandlerResponse {
255        path: String,
256        allowed_list: Option<Vec<[u8; 16]>>,
257        handler: Box<ResponseRequestHandler>,
258    },
259    /// Register a request handler that sends its response later.
260    RegisterDeferredRequestHandler {
261        path: String,
262        allowed_list: Option<Vec<[u8; 16]>>,
263        handler: Box<DeferredRequestHandler>,
264    },
265    /// Create an outbound link. Response sends (link_id) back.
266    CreateLink {
267        dest_hash: [u8; 16],
268        dest_sig_pub_bytes: [u8; 32],
269        response_tx: mpsc::Sender<[u8; 16]>,
270    },
271    /// Send a request on an established link.
272    SendRequest {
273        link_id: [u8; 16],
274        path: String,
275        data: Vec<u8>,
276        max_response_size: Option<usize>,
277    },
278    /// Complete a request previously delivered to a deferred handler.
279    SendDeferredResponse {
280        link_id: [u8; 16],
281        request_id: [u8; 16],
282        data: Vec<u8>,
283    },
284    /// Identify on a link (send identity to remote peer).
285    IdentifyOnLink {
286        link_id: [u8; 16],
287        identity_prv_key: [u8; 64],
288    },
289    /// Tear down a link.
290    TeardownLink { link_id: [u8; 16] },
291    /// Send a resource on a link.
292    SendResource {
293        link_id: [u8; 16],
294        data: Vec<u8>,
295        metadata: Option<Vec<u8>>,
296        auto_compress: bool,
297    },
298    /// Send a Resource from a sequential reader without buffering the full payload.
299    SendResourceStream {
300        link_id: [u8; 16],
301        transfer_id: crate::resource::ResourceTransferId,
302        reader: Box<dyn Read + Send>,
303        declared_length: u64,
304        metadata: Option<Vec<u8>>,
305        auto_compress: bool,
306    },
307    /// Set the resource acceptance strategy for a link.
308    SetResourceStrategy { link_id: [u8; 16], strategy: u8 },
309    /// Select in-memory or disk-backed delivery for independent Resources.
310    SetResourceReceiveMode {
311        link_id: [u8; 16],
312        mode: crate::resource::ResourceReceiveMode,
313    },
314    /// Accept or reject a pending resource (for AcceptApp strategy).
315    AcceptResource {
316        link_id: [u8; 16],
317        resource_hash: Vec<u8>,
318        accept: bool,
319    },
320    /// Send a channel message on a link.
321    SendChannelMessage {
322        link_id: [u8; 16],
323        msgtype: u16,
324        payload: Vec<u8>,
325        response_tx: mpsc::Sender<Result<(), String>>,
326    },
327    /// Send generic data on a link with a given context.
328    SendOnLink {
329        link_id: [u8; 16],
330        data: Vec<u8>,
331        context: u8,
332        /// Present when the caller needs explicit driver-level admission.
333        response_tx: Option<mpsc::Sender<Result<(), LinkDatagramError>>>,
334    },
335    /// Request a path to a destination from the network.
336    RequestPath { dest_hash: [u8; 16] },
337    /// Register a proof strategy for a destination.
338    RegisterProofStrategy {
339        dest_hash: [u8; 16],
340        strategy: rns_core::types::ProofStrategy,
341        /// Full identity private key (64 bytes) for signing proofs.
342        signing_key: Option<[u8; 64]>,
343    },
344    /// Propose a direct connection to a peer via hole punching.
345    ProposeDirectConnect { link_id: [u8; 16] },
346    /// Set the direct-connect policy.
347    SetDirectConnectPolicy { policy: HolePunchPolicy },
348    /// (Internal) Probe result arrived from a worker thread.
349    HolePunchProbeResult {
350        link_id: [u8; 16],
351        session_id: [u8; 16],
352        observed_addr: std::net::SocketAddr,
353        socket: std::net::UdpSocket,
354        /// The probe server that responded successfully.
355        probe_server: std::net::SocketAddr,
356    },
357    /// (Internal) Probe failed.
358    HolePunchProbeFailed {
359        link_id: [u8; 16],
360        session_id: [u8; 16],
361    },
362    /// An interface's configuration changed (placeholder for future use).
363    InterfaceConfigChanged(InterfaceId),
364    /// A backbone server accepted a new inbound peer connection.
365    BackbonePeerConnected {
366        server_interface_id: InterfaceId,
367        peer_interface_id: InterfaceId,
368        peer_ip: IpAddr,
369        peer_port: u16,
370    },
371    /// A backbone peer connection closed for any reason.
372    BackbonePeerDisconnected {
373        server_interface_id: InterfaceId,
374        peer_interface_id: InterfaceId,
375        peer_ip: IpAddr,
376        peer_port: u16,
377        connected_for: Duration,
378        had_received_data: bool,
379    },
380    /// A backbone peer was disconnected for idling without sending data.
381    BackbonePeerIdleTimeout {
382        server_interface_id: InterfaceId,
383        peer_interface_id: InterfaceId,
384        peer_ip: IpAddr,
385        peer_port: u16,
386        connected_for: Duration,
387    },
388    /// A backbone peer was disconnected because its writer stalled.
389    BackbonePeerWriteStall {
390        server_interface_id: InterfaceId,
391        peer_interface_id: InterfaceId,
392        peer_ip: IpAddr,
393        peer_port: u16,
394        connected_for: Duration,
395    },
396    /// A backbone peer IP was penalized due to abusive behavior.
397    BackbonePeerPenalty {
398        server_interface_id: InterfaceId,
399        peer_ip: IpAddr,
400        penalty_level: u8,
401        blacklist_for: Duration,
402    },
403    /// Load an in-memory WASM hook at runtime.
404    LoadHook {
405        name: String,
406        wasm_bytes: Vec<u8>,
407        attach_point: String,
408        priority: i32,
409        response_tx: mpsc::Sender<Result<(), String>>,
410    },
411    /// Load a hook from a server-local filesystem path at runtime.
412    LoadHookFile {
413        name: String,
414        path: String,
415        hook_type: String,
416        attach_point: String,
417        priority: i32,
418        response_tx: mpsc::Sender<Result<(), String>>,
419    },
420    /// Load a registered built-in hook by ID at runtime.
421    LoadBuiltinHook {
422        name: String,
423        builtin_id: String,
424        attach_point: String,
425        priority: i32,
426        response_tx: mpsc::Sender<Result<(), String>>,
427    },
428    /// Unload a hook at runtime.
429    UnloadHook {
430        name: String,
431        attach_point: String,
432        response_tx: mpsc::Sender<Result<(), String>>,
433    },
434    /// Reload an in-memory WASM hook at runtime (detach + recompile + reattach with same priority).
435    ReloadHook {
436        name: String,
437        attach_point: String,
438        wasm_bytes: Vec<u8>,
439        response_tx: mpsc::Sender<Result<(), String>>,
440    },
441    /// Reload a hook from a server-local filesystem path at runtime.
442    ReloadHookFile {
443        name: String,
444        attach_point: String,
445        path: String,
446        hook_type: String,
447        response_tx: mpsc::Sender<Result<(), String>>,
448    },
449    /// Reload a registered built-in hook by ID at runtime.
450    ReloadBuiltinHook {
451        name: String,
452        attach_point: String,
453        builtin_id: String,
454        response_tx: mpsc::Sender<Result<(), String>>,
455    },
456    /// Enable or disable a loaded hook at runtime.
457    SetHookEnabled {
458        name: String,
459        attach_point: String,
460        enabled: bool,
461        response_tx: mpsc::Sender<Result<(), String>>,
462    },
463    /// Update the priority of a loaded hook at runtime.
464    SetHookPriority {
465        name: String,
466        attach_point: String,
467        priority: i32,
468        response_tx: mpsc::Sender<Result<(), String>>,
469    },
470    /// List all loaded hooks.
471    ListHooks {
472        response_tx: mpsc::Sender<Vec<HookInfo>>,
473    },
474}
475
476/// Information about a loaded hook program.
477#[derive(Debug, Clone)]
478pub struct HookInfo {
479    pub name: String,
480    pub hook_type: String,
481    pub attach_point: String,
482    pub priority: i32,
483    pub enabled: bool,
484    pub consecutive_traps: u32,
485}
486
487/// Live behavioral state for a backbone peer IP.
488#[derive(Debug, Clone, PartialEq)]
489pub struct BackbonePeerStateEntry {
490    pub interface_name: String,
491    pub peer_ip: IpAddr,
492    pub connected_count: usize,
493    pub blacklisted_remaining_secs: Option<f64>,
494    pub blacklist_reason: Option<String>,
495    pub reject_count: u64,
496}
497
498/// Hook-visible snapshot of a backbone peer lifecycle event.
499#[derive(Debug, Clone, PartialEq, Eq)]
500pub struct BackbonePeerHookEvent {
501    pub server_interface_id: InterfaceId,
502    pub peer_interface_id: Option<InterfaceId>,
503    pub peer_ip: IpAddr,
504    pub peer_port: u16,
505    pub connected_for: Duration,
506    pub had_received_data: bool,
507    pub penalty_level: u8,
508    pub blacklist_for: Duration,
509}
510
511#[derive(Debug, Clone, PartialEq, Eq)]
512pub struct BackboneInterfaceEntry {
513    pub interface_id: InterfaceId,
514    pub interface_name: String,
515}
516
517#[derive(Debug, Clone, PartialEq)]
518pub struct KnownDestinationEntry {
519    pub dest_hash: [u8; 16],
520    pub identity_hash: [u8; 16],
521    pub public_key: [u8; 64],
522    pub app_data: Option<Vec<u8>>,
523    pub hops: u8,
524    pub received_at: f64,
525    pub receiving_interface: InterfaceId,
526    pub was_used: bool,
527    pub last_used_at: Option<f64>,
528    pub retained: bool,
529}
530
531#[derive(Debug, Clone, PartialEq, Eq)]
532pub struct ProviderBridgeConsumerStats {
533    pub id: u64,
534    pub connected: bool,
535    pub queue_len: usize,
536    pub queued_bytes: usize,
537    pub dropped_pending: u64,
538    pub dropped_total: u64,
539    pub queue_max_events: usize,
540    pub queue_max_bytes: usize,
541}
542
543#[derive(Debug, Clone, PartialEq, Eq)]
544pub struct ProviderBridgeStats {
545    pub connected: bool,
546    pub consumer_count: usize,
547    pub queue_max_events: usize,
548    pub queue_max_bytes: usize,
549    pub backlog_len: usize,
550    pub backlog_bytes: usize,
551    pub backlog_dropped_pending: u64,
552    pub backlog_dropped_total: u64,
553    pub total_disconnect_count: u64,
554    pub consumers: Vec<ProviderBridgeConsumerStats>,
555}
556
557/// Queries that can be sent to the driver.
558#[derive(Debug)]
559pub enum QueryRequest {
560    /// Get interface statistics and transport info.
561    InterfaceStats,
562    /// Get path table entries, optionally filtered by max hops.
563    PathTable { max_hops: Option<u8> },
564    /// Get rate table entries.
565    RateTable,
566    /// Get the slowest positive bitrate among online interfaces.
567    LowestInterfaceBitrate,
568    /// Estimate an MTU round trip on the slowest interface plus hop grace.
569    MediumPathTimeout,
570    /// Look up the next hop for a destination.
571    NextHop { dest_hash: [u8; 16] },
572    /// Look up the next hop interface name for a destination.
573    NextHopIfName { dest_hash: [u8; 16] },
574    /// Get link table entry count.
575    LinkCount,
576    /// Get validated or established link count.
577    ActiveLinkCount,
578    /// Drop a specific path.
579    DropPath { dest_hash: [u8; 16] },
580    /// Drop all paths that route via a given transport hash.
581    DropAllVia { transport_hash: [u8; 16] },
582    /// Drop all announce queues.
583    DropAnnounceQueues,
584    /// Get the transport identity hash.
585    TransportIdentity,
586    /// Snapshot restart-relevant transport routing state.
587    TransportStateSnapshot,
588    /// Get all blackholed identities.
589    GetBlackholed,
590    /// Add an identity to the blackhole list.
591    BlackholeIdentity {
592        identity_hash: [u8; 16],
593        duration_hours: Option<f64>,
594        reason: Option<String>,
595    },
596    /// Remove an identity from the blackhole list.
597    UnblackholeIdentity { identity_hash: [u8; 16] },
598    /// Check if a path exists to a destination.
599    HasPath { dest_hash: [u8; 16] },
600    /// Get hop count to a destination.
601    HopsTo { dest_hash: [u8; 16] },
602    /// Recall identity info for a destination.
603    RecallIdentity { dest_hash: [u8; 16] },
604    /// List known-destination lifecycle state.
605    KnownDestinations,
606    /// Get locally registered destinations.
607    LocalDestinations,
608    /// Get active links.
609    Links,
610    /// Get active resource transfers.
611    Resources,
612    /// Inject a path entry into the path table.
613    InjectPath {
614        dest_hash: [u8; 16],
615        next_hop: [u8; 16],
616        hops: u8,
617        expires: f64,
618        interface_name: String,
619        packet_hash: [u8; 32],
620    },
621    /// Inject an identity into the known destinations cache.
622    InjectIdentity {
623        dest_hash: [u8; 16],
624        identity_hash: [u8; 16],
625        public_key: [u8; 64],
626        app_data: Option<Vec<u8>>,
627        hops: u8,
628        received_at: f64,
629    },
630    /// Restore a known destination with full lifecycle state.
631    RestoreKnownDestination(KnownDestinationEntry),
632    /// Mark a known destination as explicitly retained.
633    RetainKnownDestination { dest_hash: [u8; 16] },
634    /// Mark all known destinations announced by an identity as retained.
635    RetainIdentity { identity_hash: [u8; 16] },
636    /// Clear the retained flag on a known destination.
637    UnretainKnownDestination { dest_hash: [u8; 16] },
638    /// Mark a known destination as used.
639    MarkKnownDestinationUsed { dest_hash: [u8; 16] },
640    /// Get discovered interfaces.
641    DiscoveredInterfaces {
642        only_available: bool,
643        only_transport: bool,
644    },
645    /// Send a probe packet to a destination and return (packet_hash, hops).
646    SendProbe {
647        dest_hash: [u8; 16],
648        payload_size: usize,
649    },
650    /// Check if a proof was received for a probe packet.
651    CheckProof { packet_hash: [u8; 32] },
652    /// List runtime-config entries currently supported by the daemon.
653    ListRuntimeConfig,
654    /// Get a single runtime-config entry by key.
655    GetRuntimeConfig { key: String },
656    /// Set a runtime-config value by key.
657    SetRuntimeConfig {
658        key: String,
659        value: RuntimeConfigValue,
660    },
661    /// Reset a runtime-config value to its startup/default value.
662    ResetRuntimeConfig { key: String },
663    /// List live backbone peer state, optionally filtered to one interface.
664    BackbonePeerState { interface_name: Option<String> },
665    /// List registered backbone server interfaces.
666    BackboneInterfaces,
667    /// Report live provider-bridge queue/drop state.
668    ProviderBridgeStats,
669    /// Report current lifecycle/drain status.
670    DrainStatus,
671    /// Clear live backbone peer state for one interface/IP pair.
672    ClearBackbonePeerState {
673        interface_name: String,
674        peer_ip: IpAddr,
675    },
676    /// Blacklist a backbone peer IP for a duration.
677    BlacklistBackbonePeer {
678        interface_name: String,
679        peer_ip: IpAddr,
680        duration: Duration,
681        reason: String,
682        penalty_level: u8,
683    },
684}
685
686/// Responses to queries.
687#[derive(Debug)]
688pub enum QueryResponse {
689    InterfaceStats(InterfaceStatsResponse),
690    PathTable(Vec<PathTableEntry>),
691    RateTable(Vec<RateTableEntry>),
692    LowestInterfaceBitrate(Option<u64>),
693    MediumPathTimeout(f64),
694    NextHop(Option<NextHopResponse>),
695    NextHopIfName(Option<String>),
696    LinkCount(usize),
697    ActiveLinkCount(usize),
698    DropPath(bool),
699    DropAllVia(usize),
700    DropAnnounceQueues,
701    TransportIdentity(Option<[u8; 16]>),
702    TransportStateSnapshot(rns_core::transport::persistence::TransportStateSnapshot),
703    Blackholed(Vec<BlackholeInfo>),
704    BlackholeResult(bool),
705    UnblackholeResult(bool),
706    HasPath(bool),
707    HopsTo(Option<u8>),
708    RecallIdentity(Option<crate::common::destination::AnnouncedIdentity>),
709    KnownDestinations(Vec<KnownDestinationEntry>),
710    LocalDestinations(Vec<LocalDestinationEntry>),
711    Links(Vec<LinkInfoEntry>),
712    Resources(Vec<ResourceInfoEntry>),
713    InjectPath(bool),
714    InjectIdentity(bool),
715    RestoreKnownDestination(bool),
716    RetainKnownDestination(bool),
717    RetainIdentity(bool),
718    UnretainKnownDestination(bool),
719    MarkKnownDestinationUsed(bool),
720    /// List of discovered interfaces.
721    DiscoveredInterfaces(Vec<crate::common::discovery::DiscoveredInterface>),
722    /// Probe sent: (packet_hash, hops) or None if identity unknown.
723    SendProbe(Option<([u8; 32], u8)>),
724    /// Proof check: RTT if received, None if still pending.
725    CheckProof(Option<f64>),
726    /// Runtime-config entries currently supported by the daemon.
727    RuntimeConfigList(Vec<RuntimeConfigEntry>),
728    /// A specific runtime-config entry, or None if the key is unknown.
729    RuntimeConfigEntry(Option<RuntimeConfigEntry>),
730    /// Result of setting a runtime-config value.
731    RuntimeConfigSet(Result<RuntimeConfigEntry, RuntimeConfigError>),
732    /// Result of resetting a runtime-config value.
733    RuntimeConfigReset(Result<RuntimeConfigEntry, RuntimeConfigError>),
734    /// Live backbone peer state entries.
735    BackbonePeerState(Vec<BackbonePeerStateEntry>),
736    /// Registered backbone server interfaces.
737    BackboneInterfaces(Vec<BackboneInterfaceEntry>),
738    /// Live provider-bridge queue/drop state if enabled.
739    ProviderBridgeStats(Option<ProviderBridgeStats>),
740    /// Current lifecycle/drain status.
741    DrainStatus(DrainStatus),
742    /// Result of clearing one backbone peer state entry.
743    ClearBackbonePeerState(bool),
744    /// Result of blacklisting a backbone peer.
745    BlacklistBackbonePeer(bool),
746}
747
748/// Interface statistics response.
749#[derive(Debug, Clone)]
750pub struct InterfaceStatsResponse {
751    pub interfaces: Vec<SingleInterfaceStat>,
752    pub transport_id: Option<[u8; 16]>,
753    pub transport_enabled: bool,
754    pub transport_uptime: f64,
755    /// Total received bytes across all interfaces.
756    pub total_rxb: u64,
757    /// Total transmitted bytes across all interfaces.
758    pub total_txb: u64,
759    /// Aggregate received packets per second, excluding local shared-instance traffic.
760    pub rx_pps: f64,
761    /// Aggregate transmitted packets per second, excluding local shared-instance traffic.
762    pub tx_pps: f64,
763    /// Aggregate byte counters and rates for total, announce and path-request traffic.
764    pub traffic: TrafficDetail,
765    /// Probe responder destination hash (if enabled).
766    pub probe_responder: Option<[u8; 16]>,
767    /// Outbound Backbone peer-pool state, if enabled.
768    pub backbone_peer_pool: Option<BackbonePeerPoolStatus>,
769}
770
771/// Detailed traffic counters and average rates in bits per second.
772#[derive(Debug, Clone, Copy, Default)]
773pub struct TrafficDetail {
774    pub rxs: f64,
775    pub txs: f64,
776    pub arxb: u64,
777    pub atxb: u64,
778    pub arxc: u64,
779    pub atxc: u64,
780    pub arxs: f64,
781    pub atxs: f64,
782    pub prxb: u64,
783    pub ptxb: u64,
784    pub prxc: u64,
785    pub ptxc: u64,
786    pub prxs: f64,
787    pub ptxs: f64,
788    pub arxf: f64,
789    pub atxf: f64,
790    pub prxf: f64,
791    pub ptxf: f64,
792}
793
794/// Runtime status for the outbound Backbone peer pool.
795#[derive(Debug, Clone)]
796pub struct BackbonePeerPoolStatus {
797    pub max_connected: usize,
798    pub active_count: usize,
799    pub standby_count: usize,
800    pub cooldown_count: usize,
801    pub members: Vec<BackbonePeerPoolMemberStatus>,
802}
803
804/// Runtime status for one outbound Backbone peer-pool member.
805#[derive(Debug, Clone)]
806pub struct BackbonePeerPoolMemberStatus {
807    pub name: String,
808    pub remote: String,
809    pub source: String,
810    pub priority: u8,
811    pub state: String,
812    pub interface_id: Option<u64>,
813    pub failure_count: usize,
814    pub last_error: Option<String>,
815    pub cooldown_remaining_seconds: Option<f64>,
816}
817
818/// Statistics for a single interface.
819#[derive(Debug, Clone)]
820pub struct SingleInterfaceStat {
821    pub id: u64,
822    pub name: String,
823    pub status: bool,
824    pub mode: u8,
825    /// Signed interface preference used by path selection.
826    pub gravity: i64,
827    /// Whether boundary ingress announces may propagate to internal interfaces.
828    pub announces_to_internal: Option<bool>,
829    pub rxb: u64,
830    pub txb: u64,
831    pub tx_drops: u64,
832    pub tx_dropped_bytes: u64,
833    pub tx_stalled: bool,
834    pub tx_buffered: usize,
835    /// Byte counters and rates for total, announce and path-request traffic.
836    pub traffic: TrafficDetail,
837    pub protocol_violations: u64,
838    pub ifac_violations: u64,
839    pub packet_filter_hits: u64,
840    pub rx_packets: u64,
841    pub tx_packets: u64,
842    pub cpu_load: Option<f64>,
843    pub mem_load: Option<f64>,
844    pub switch_id: Option<[u8; 4]>,
845    pub endpoint_id: Option<[u8; 8]>,
846    pub via_switch_id: Option<[u8; 4]>,
847    pub peers: Option<usize>,
848    pub bitrate: Option<u64>,
849    /// Hardware MTU advertised for this interface, when known.
850    pub mtu: Option<u32>,
851    pub ifac_size: Option<usize>,
852    pub started: f64,
853    /// Incoming announce frequency (per second).
854    pub ia_freq: f64,
855    /// Outgoing announce frequency (per second).
856    pub oa_freq: f64,
857    /// Incoming path request frequency (per second).
858    pub ip_freq: f64,
859    /// Outgoing path request frequency (per second).
860    pub op_freq: f64,
861    /// Outgoing path request samples currently retained.
862    pub op_samples: usize,
863    /// Whether announce ingress-control burst limiting is currently active.
864    pub burst_active: bool,
865    /// Timestamp when announce ingress-control burst limiting activated.
866    pub burst_activated: f64,
867    /// Whether path-request ingress-control burst limiting is currently active.
868    pub pr_burst_active: bool,
869    /// Timestamp when path-request burst limiting activated.
870    pub pr_burst_activated: f64,
871    /// Connected client count for aggregate server interfaces, if reported.
872    pub clients: Option<u64>,
873    /// Fast-flapping IPs currently blocked by an aggregate server interface.
874    pub blocked_ips: Option<u64>,
875    /// Sorted textual IP addresses currently blocked for fast flapping.
876    pub blocked_ip_list: Option<Vec<String>>,
877    /// Target interval for outgoing announce rate control, in seconds.
878    pub announce_rate_target: Option<f64>,
879    /// Announce-rate control grace count.
880    pub announce_rate_grace: u32,
881    /// Announce-rate control penalty, in seconds.
882    pub announce_rate_penalty: f64,
883    /// Human-readable interface type string (e.g. "TCPClientInterface").
884    pub interface_type: String,
885}
886
887/// A locally registered destination.
888#[derive(Debug, Clone)]
889pub struct LocalDestinationEntry {
890    pub hash: [u8; 16],
891    pub dest_type: u8,
892}
893
894/// Information about an active link.
895#[derive(Debug, Clone)]
896pub struct LinkInfoEntry {
897    pub link_id: [u8; 16],
898    pub state: String,
899    pub is_initiator: bool,
900    pub dest_hash: [u8; 16],
901    pub remote_identity: Option<[u8; 16]>,
902    pub rtt: Option<f64>,
903    pub expected_hops: u8,
904    /// Maximum plaintext bytes accepted by one direct Link data packet.
905    pub mdu: usize,
906    pub tx_packets: u64,
907    pub rx_packets: u64,
908    pub tx_bytes: u64,
909    pub rx_bytes: u64,
910    pub channel_window: Option<u16>,
911    pub channel_outstanding: Option<usize>,
912    pub pending_channel_packets: usize,
913    pub channel_send_ok: u64,
914    pub channel_send_not_ready: u64,
915    pub channel_send_too_big: u64,
916    pub channel_send_other_error: u64,
917    pub channel_messages_received: u64,
918    pub channel_proofs_sent: u64,
919    pub channel_proofs_received: u64,
920}
921
922/// Information about an active resource transfer.
923#[derive(Debug, Clone)]
924pub struct ResourceInfoEntry {
925    pub link_id: [u8; 16],
926    pub direction: String,
927    pub total_parts: usize,
928    pub transferred_parts: usize,
929    pub complete: bool,
930}
931
932/// A single path table entry for query responses.
933#[derive(Debug, Clone)]
934pub struct PathTableEntry {
935    pub hash: [u8; 16],
936    pub timestamp: f64,
937    pub via: [u8; 16],
938    pub hops: u8,
939    pub expires: f64,
940    pub interface: InterfaceId,
941    pub interface_name: String,
942}
943
944/// A single rate table entry for query responses.
945#[derive(Debug, Clone)]
946pub struct RateTableEntry {
947    pub hash: [u8; 16],
948    pub last: f64,
949    pub rate_violations: u32,
950    pub blocked_until: f64,
951    pub timestamps: Vec<f64>,
952}
953
954/// A blackholed identity for query responses.
955#[derive(Debug, Clone)]
956pub struct BlackholeInfo {
957    pub identity_hash: [u8; 16],
958    pub created: f64,
959    pub expires: f64,
960    pub reason: Option<String>,
961}
962
963/// Next hop lookup result.
964#[derive(Debug, Clone)]
965pub struct NextHopResponse {
966    pub next_hop: [u8; 16],
967    pub hops: u8,
968    pub interface: InterfaceId,
969}
970
971impl<W: Send> fmt::Debug for Event<W> {
972    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
973        match self {
974            Event::Frame {
975                interface_id,
976                data,
977                rssi,
978                snr,
979            } => f
980                .debug_struct("Frame")
981                .field("interface_id", interface_id)
982                .field("data_len", &data.len())
983                .field("rssi", rssi)
984                .field("snr", snr)
985                .finish(),
986            Event::AnnounceVerified { key, .. } => f
987                .debug_struct("AnnounceVerified")
988                .field("destination_hash", &key.destination_hash)
989                .field("received_from", &key.received_from)
990                .finish(),
991            Event::AnnounceVerifyFailed { key, .. } => f
992                .debug_struct("AnnounceVerifyFailed")
993                .field("destination_hash", &key.destination_hash)
994                .field("received_from", &key.received_from)
995                .finish(),
996            Event::InterfaceUp(id, writer, info) => f
997                .debug_tuple("InterfaceUp")
998                .field(id)
999                .field(&writer.is_some())
1000                .field(&info.is_some())
1001                .finish(),
1002            Event::DynamicInterfaceUp {
1003                id, registration, ..
1004            } => f
1005                .debug_struct("DynamicInterfaceUp")
1006                .field("id", id)
1007                .field("type", &registration.interface_type)
1008                .field("parent_id", &registration.parent_id)
1009                .finish(),
1010            Event::InterfaceTelemetry {
1011                interface_id,
1012                telemetry,
1013            } => f
1014                .debug_struct("InterfaceTelemetry")
1015                .field("interface_id", interface_id)
1016                .field("telemetry", telemetry)
1017                .finish(),
1018            Event::InterfaceDown(id) => f.debug_tuple("InterfaceDown").field(id).finish(),
1019            Event::Tick => write!(f, "Tick"),
1020            Event::BeginDrain { timeout } => f
1021                .debug_struct("BeginDrain")
1022                .field("timeout", timeout)
1023                .finish(),
1024            Event::Shutdown => write!(f, "Shutdown"),
1025            Event::SendOutbound { raw, dest_type, .. } => f
1026                .debug_struct("SendOutbound")
1027                .field("raw_len", &raw.len())
1028                .field("dest_type", dest_type)
1029                .finish(),
1030            Event::RegisterDestination {
1031                dest_hash,
1032                dest_type,
1033            } => f
1034                .debug_struct("RegisterDestination")
1035                .field("dest_hash", dest_hash)
1036                .field("dest_type", dest_type)
1037                .finish(),
1038            Event::StoreSharedAnnounce {
1039                dest_hash,
1040                name_hash,
1041                app_data,
1042                ..
1043            } => f
1044                .debug_struct("StoreSharedAnnounce")
1045                .field("dest_hash", dest_hash)
1046                .field("name_hash", name_hash)
1047                .field("app_data_len", &app_data.as_ref().map(|d| d.len()))
1048                .finish(),
1049            Event::DeregisterDestination { dest_hash } => f
1050                .debug_struct("DeregisterDestination")
1051                .field("dest_hash", dest_hash)
1052                .finish(),
1053            Event::DeregisterLinkDestination { dest_hash } => f
1054                .debug_struct("DeregisterLinkDestination")
1055                .field("dest_hash", dest_hash)
1056                .finish(),
1057            Event::Query(req, _) => f.debug_tuple("Query").field(req).finish(),
1058            Event::RegisterLinkDestination { dest_hash, .. } => f
1059                .debug_struct("RegisterLinkDestination")
1060                .field("dest_hash", dest_hash)
1061                .finish(),
1062            Event::RegisterRequestHandler { path, .. } => f
1063                .debug_struct("RegisterRequestHandler")
1064                .field("path", path)
1065                .finish(),
1066            Event::RegisterRequestHandlerResponse { path, .. } => f
1067                .debug_struct("RegisterRequestHandlerResponse")
1068                .field("path", path)
1069                .finish(),
1070            Event::RegisterDeferredRequestHandler { path, .. } => f
1071                .debug_struct("RegisterDeferredRequestHandler")
1072                .field("path", path)
1073                .finish(),
1074            Event::CreateLink { dest_hash, .. } => f
1075                .debug_struct("CreateLink")
1076                .field("dest_hash", dest_hash)
1077                .finish(),
1078            Event::SendRequest { link_id, path, .. } => f
1079                .debug_struct("SendRequest")
1080                .field("link_id", link_id)
1081                .field("path", path)
1082                .finish(),
1083            Event::SendDeferredResponse {
1084                link_id,
1085                request_id,
1086                data,
1087            } => f
1088                .debug_struct("SendDeferredResponse")
1089                .field("link_id", link_id)
1090                .field("request_id", request_id)
1091                .field("data_len", &data.len())
1092                .finish(),
1093            Event::IdentifyOnLink { link_id, .. } => f
1094                .debug_struct("IdentifyOnLink")
1095                .field("link_id", link_id)
1096                .finish(),
1097            Event::TeardownLink { link_id } => f
1098                .debug_struct("TeardownLink")
1099                .field("link_id", link_id)
1100                .finish(),
1101            Event::SendResource { link_id, data, .. } => f
1102                .debug_struct("SendResource")
1103                .field("link_id", link_id)
1104                .field("data_len", &data.len())
1105                .finish(),
1106            Event::SendResourceStream {
1107                link_id,
1108                transfer_id,
1109                declared_length,
1110                ..
1111            } => f
1112                .debug_struct("SendResourceStream")
1113                .field("link_id", link_id)
1114                .field("transfer_id", transfer_id)
1115                .field("declared_length", declared_length)
1116                .finish(),
1117            Event::SetResourceStrategy { link_id, strategy } => f
1118                .debug_struct("SetResourceStrategy")
1119                .field("link_id", link_id)
1120                .field("strategy", strategy)
1121                .finish(),
1122            Event::SetResourceReceiveMode { link_id, mode } => f
1123                .debug_struct("SetResourceReceiveMode")
1124                .field("link_id", link_id)
1125                .field("mode", mode)
1126                .finish(),
1127            Event::AcceptResource {
1128                link_id, accept, ..
1129            } => f
1130                .debug_struct("AcceptResource")
1131                .field("link_id", link_id)
1132                .field("accept", accept)
1133                .finish(),
1134            Event::SendChannelMessage {
1135                link_id,
1136                msgtype,
1137                payload,
1138                ..
1139            } => f
1140                .debug_struct("SendChannelMessage")
1141                .field("link_id", link_id)
1142                .field("msgtype", msgtype)
1143                .field("payload_len", &payload.len())
1144                .finish(),
1145            Event::SendOnLink {
1146                link_id,
1147                data,
1148                context,
1149                ..
1150            } => f
1151                .debug_struct("SendOnLink")
1152                .field("link_id", link_id)
1153                .field("data_len", &data.len())
1154                .field("context", context)
1155                .finish(),
1156            Event::RequestPath { dest_hash } => f
1157                .debug_struct("RequestPath")
1158                .field("dest_hash", dest_hash)
1159                .finish(),
1160            Event::RegisterProofStrategy {
1161                dest_hash,
1162                strategy,
1163                ..
1164            } => f
1165                .debug_struct("RegisterProofStrategy")
1166                .field("dest_hash", dest_hash)
1167                .field("strategy", strategy)
1168                .finish(),
1169            Event::ProposeDirectConnect { link_id } => f
1170                .debug_struct("ProposeDirectConnect")
1171                .field("link_id", link_id)
1172                .finish(),
1173            Event::SetDirectConnectPolicy { .. } => {
1174                write!(f, "SetDirectConnectPolicy")
1175            }
1176            Event::HolePunchProbeResult {
1177                link_id,
1178                session_id,
1179                observed_addr,
1180                probe_server,
1181                ..
1182            } => f
1183                .debug_struct("HolePunchProbeResult")
1184                .field("link_id", link_id)
1185                .field("session_id", session_id)
1186                .field("observed_addr", observed_addr)
1187                .field("probe_server", probe_server)
1188                .finish(),
1189            Event::HolePunchProbeFailed {
1190                link_id,
1191                session_id,
1192            } => f
1193                .debug_struct("HolePunchProbeFailed")
1194                .field("link_id", link_id)
1195                .field("session_id", session_id)
1196                .finish(),
1197            Event::InterfaceConfigChanged(id) => {
1198                f.debug_tuple("InterfaceConfigChanged").field(id).finish()
1199            }
1200            Event::BackbonePeerConnected {
1201                server_interface_id,
1202                peer_interface_id,
1203                peer_ip,
1204                peer_port,
1205            } => f
1206                .debug_struct("BackbonePeerConnected")
1207                .field("server_interface_id", server_interface_id)
1208                .field("peer_interface_id", peer_interface_id)
1209                .field("peer_ip", peer_ip)
1210                .field("peer_port", peer_port)
1211                .finish(),
1212            Event::BackbonePeerDisconnected {
1213                server_interface_id,
1214                peer_interface_id,
1215                peer_ip,
1216                peer_port,
1217                connected_for,
1218                had_received_data,
1219            } => f
1220                .debug_struct("BackbonePeerDisconnected")
1221                .field("server_interface_id", server_interface_id)
1222                .field("peer_interface_id", peer_interface_id)
1223                .field("peer_ip", peer_ip)
1224                .field("peer_port", peer_port)
1225                .field("connected_for", connected_for)
1226                .field("had_received_data", had_received_data)
1227                .finish(),
1228            Event::BackbonePeerIdleTimeout {
1229                server_interface_id,
1230                peer_interface_id,
1231                peer_ip,
1232                peer_port,
1233                connected_for,
1234            } => f
1235                .debug_struct("BackbonePeerIdleTimeout")
1236                .field("server_interface_id", server_interface_id)
1237                .field("peer_interface_id", peer_interface_id)
1238                .field("peer_ip", peer_ip)
1239                .field("peer_port", peer_port)
1240                .field("connected_for", connected_for)
1241                .finish(),
1242            Event::BackbonePeerWriteStall {
1243                server_interface_id,
1244                peer_interface_id,
1245                peer_ip,
1246                peer_port,
1247                connected_for,
1248            } => f
1249                .debug_struct("BackbonePeerWriteStall")
1250                .field("server_interface_id", server_interface_id)
1251                .field("peer_interface_id", peer_interface_id)
1252                .field("peer_ip", peer_ip)
1253                .field("peer_port", peer_port)
1254                .field("connected_for", connected_for)
1255                .finish(),
1256            Event::BackbonePeerPenalty {
1257                server_interface_id,
1258                peer_ip,
1259                penalty_level,
1260                blacklist_for,
1261            } => f
1262                .debug_struct("BackbonePeerPenalty")
1263                .field("server_interface_id", server_interface_id)
1264                .field("peer_ip", peer_ip)
1265                .field("penalty_level", penalty_level)
1266                .field("blacklist_for", blacklist_for)
1267                .finish(),
1268            Event::LoadHook {
1269                name,
1270                attach_point,
1271                priority,
1272                ..
1273            } => f
1274                .debug_struct("LoadHook")
1275                .field("name", name)
1276                .field("attach_point", attach_point)
1277                .field("priority", priority)
1278                .finish(),
1279            Event::LoadHookFile {
1280                name,
1281                path,
1282                hook_type,
1283                attach_point,
1284                priority,
1285                ..
1286            } => f
1287                .debug_struct("LoadHookFile")
1288                .field("name", name)
1289                .field("path", path)
1290                .field("hook_type", hook_type)
1291                .field("attach_point", attach_point)
1292                .field("priority", priority)
1293                .finish(),
1294            Event::LoadBuiltinHook {
1295                name,
1296                builtin_id,
1297                attach_point,
1298                priority,
1299                ..
1300            } => f
1301                .debug_struct("LoadBuiltinHook")
1302                .field("name", name)
1303                .field("builtin_id", builtin_id)
1304                .field("attach_point", attach_point)
1305                .field("priority", priority)
1306                .finish(),
1307            Event::UnloadHook {
1308                name, attach_point, ..
1309            } => f
1310                .debug_struct("UnloadHook")
1311                .field("name", name)
1312                .field("attach_point", attach_point)
1313                .finish(),
1314            Event::ReloadHook {
1315                name, attach_point, ..
1316            } => f
1317                .debug_struct("ReloadHook")
1318                .field("name", name)
1319                .field("attach_point", attach_point)
1320                .finish(),
1321            Event::ReloadHookFile {
1322                name,
1323                attach_point,
1324                path,
1325                hook_type,
1326                ..
1327            } => f
1328                .debug_struct("ReloadHookFile")
1329                .field("name", name)
1330                .field("attach_point", attach_point)
1331                .field("path", path)
1332                .field("hook_type", hook_type)
1333                .finish(),
1334            Event::ReloadBuiltinHook {
1335                name,
1336                attach_point,
1337                builtin_id,
1338                ..
1339            } => f
1340                .debug_struct("ReloadBuiltinHook")
1341                .field("name", name)
1342                .field("attach_point", attach_point)
1343                .field("builtin_id", builtin_id)
1344                .finish(),
1345            Event::SetHookEnabled {
1346                name,
1347                attach_point,
1348                enabled,
1349                ..
1350            } => f
1351                .debug_struct("SetHookEnabled")
1352                .field("name", name)
1353                .field("attach_point", attach_point)
1354                .field("enabled", enabled)
1355                .finish(),
1356            Event::SetHookPriority {
1357                name,
1358                attach_point,
1359                priority,
1360                ..
1361            } => f
1362                .debug_struct("SetHookPriority")
1363                .field("name", name)
1364                .field("attach_point", attach_point)
1365                .field("priority", priority)
1366                .finish(),
1367            Event::ListHooks { .. } => write!(f, "ListHooks"),
1368        }
1369    }
1370}