veilid-core 0.5.5

Core library used to create a Veilid node and operate it as part of an application
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
use crate::attachment_manager::{AttachmentManager, AttachmentManagerStartupContext};
use crate::crypto::Crypto;
use crate::logging::*;
use crate::network_manager::{NetworkManager, NetworkManagerStartupContext};
use crate::routing_table::{RoutingTable, RoutingTableStartupContext};
use crate::rpc_processor::{RPCProcessor, RPCProcessorStartupContext};
use crate::storage_manager::StorageManager;
use crate::veilid_api::*;
use crate::veilid_config::*;
use crate::*;

impl_veilid_log_facility!("corectx");

/// Callback the application registers to receive `VeilidUpdate` events from a running node.
pub type UpdateCallback = Arc<dyn Fn(VeilidUpdate) + Send + Sync>;

type InitKey = (String, String);

/// Convert an eyre::Report into a VeilidAPIError, preserving typed variants
/// when possible. Falls back to `Internal` for unknown error chains.
fn eyre_to_veilid_api_error(report: eyre::Report) -> VeilidAPIError {
    match report.downcast::<VeilidAPIError>() {
        Ok(api_err) => api_err,
        Err(report) => VeilidAPIError::internal(report),
    }
}

/////////////////////////////////////////////////////////////////////////////
#[derive(Clone, Debug)]
pub(crate) struct VeilidCoreContext {
    registry: VeilidComponentRegistry,
}

impl_veilid_component_accessors!(VeilidCoreContext);

impl VeilidCoreContext {
    #[cfg_attr(
        feature = "instrument",
        instrument(
            level = "trace",
            target = "core_context",
            err,
            skip_all,
            fields(__VEILID_LOG_KEY)
        )
    )]
    async fn new_with_config(
        update_callback: UpdateCallback,
        config: VeilidConfig,
    ) -> VeilidAPIResult<VeilidCoreContext> {
        #[cfg(feature = "instrument")]
        tracing::Span::current().record(
            "__VEILID_LOG_KEY",
            VeilidLayerFilter::make_veilid_log_key(&config.program_name, &config.namespace),
        );

        // Set up config from json
        let config = VeilidStartupOptions::try_new(config, update_callback)?;

        Self::new_common(config).await
    }

    #[cfg_attr(
        feature = "instrument",
        instrument(
            level = "trace",
            target = "core_context",
            err,
            skip_all,
            fields(__VEILID_LOG_KEY)
        )
    )]
    async fn new_common(
        startup_options: VeilidStartupOptions,
    ) -> VeilidAPIResult<VeilidCoreContext> {
        cfg_if! {
            if #[cfg(target_os = "android")] {
                if !crate::veilid_api::android::is_android_ready() {
                    apibail_internal!("Android globals are not set up");
                }
            }
        }

        let (program_name, namespace, update_callback) = {
            let cfginner = startup_options.config();
            (
                cfginner.program_name.clone(),
                cfginner.namespace.clone(),
                startup_options.update_callback(),
            )
        };

        let log_key = VeilidLayerFilter::make_veilid_log_key(&program_name, &namespace).to_string();

        #[cfg(feature = "instrument")]
        tracing::Span::current().record("__VEILID_LOG_KEY", log_key.clone());
        ApiTracingLayer::add_callback(log_key.clone(), update_callback.clone())?;

        // Create component registry
        let registry = VeilidComponentRegistry::new(startup_options);

        // Warn if internal "footgun" tuning was provided without the footgun-config feature
        #[cfg(not(feature = "footgun-config"))]
        if registry
            .config()
            .internal
            .as_ref()
            .is_some_and(|i| *i != VeilidConfigInternal::default())
        {
            veilid_log!(registry warn "VeilidConfig.internal footgun tuning was provided but the 'footgun-config' feature is not enabled; ignoring it and using built-in defaults");
        }

        veilid_log!(registry info "Veilid API starting up");
        if let Some(target) = option_env!("TARGET") {
            veilid_log!(registry info     "Build Target: {}", target);
        }
        veilid_log!(registry info     "Program Name: {}", program_name);
        if !namespace.is_empty() {
            veilid_log!(registry info "Namespace:    {}", namespace);
        }
        veilid_log!(registry info     "Features:     {:?}", veilid_features());
        veilid_log!(registry info     "Version:      {}", veilid_version_string());
        #[cfg(feature = "footgun-nodeid-target")]
        {
            veilid_log!(registry warn
                "Footgun feature is enabled. This disables sender privacy protections and should be avoided in production.");
        }

        // Register all components
        registry.register(ProtectedStore::new);
        registry.register(Crypto::new);
        registry.register(TableStore::new);
        #[cfg(feature = "unstable-blockstore")]
        registry.register(BlockStore::new);
        registry.register_with_context(RoutingTable::new, RoutingTableStartupContext::default());
        registry.register(StorageManager::new);
        registry
            .register_with_context(NetworkManager::new, NetworkManagerStartupContext::default());
        registry.register_with_context(RPCProcessor::new, RPCProcessorStartupContext::default());
        registry.register_with_context(
            AttachmentManager::new,
            AttachmentManagerStartupContext::default(),
        );

        // Run initialization
        // This should make the majority of subsystems functional
        if let Err(e) = registry.init().await {
            ApiTracingLayer::remove_callback(log_key.clone())?;
            return Err(eyre_to_veilid_api_error(e));
        }
        // Run post-initialization
        // This should resolve any inter-subsystem dependencies
        // required for background processes that utilize multiple subsystems
        // Background processes also often require registry lookup of the
        // current subsystem, which is not available until after init succeeds
        // This is where the attachment manager starts the background tick
        if let Err(e) = registry.post_init().await {
            registry.terminate().await;
            ApiTracingLayer::remove_callback(log_key)?;
            return Err(eyre_to_veilid_api_error(e));
        }

        veilid_log!(registry info "Veilid API startup complete");

        Ok(Self { registry })
    }

    #[cfg_attr(
        feature = "instrument",
        instrument(level = "trace", target = "core_context", skip_all, fields(__VEILID_LOG_KEY = self.log_key()))
    )]
    async fn shutdown(self) {
        veilid_log!(self info "Veilid API shutting down");

        let config = self.registry.config();
        let program_name = &config.program_name;
        let namespace = &config.namespace;
        let update_callback = self.registry().update_callback();

        // Run pre-termination
        // This should shut down background processes that may require the existence of
        // other subsystems that may not exist during final termination
        self.registry.pre_terminate().await;

        // Run termination
        // This should finish any shutdown operations for the subsystems
        self.registry.terminate().await;

        veilid_log!(self info "Veilid API shutdown complete");

        let log_key = VeilidLayerFilter::make_veilid_log_key(program_name, namespace).to_string();
        if let Err(e) = ApiTracingLayer::remove_callback(log_key) {
            error!("Error removing callback from ApiTracingLayer: {}", e);
        }

        // send final shutdown update
        update_callback(VeilidUpdate::Shutdown);
    }
}

/////////////////////////////////////////////////////////////////////////////

pub(crate) trait RegisteredComponents {
    fn protected_store<'a>(&self) -> VeilidComponentGuard<'a, ProtectedStore>;
    fn crypto<'a>(&self) -> VeilidComponentGuard<'a, Crypto>;
    fn table_store<'a>(&self) -> VeilidComponentGuard<'a, TableStore>;
    fn storage_manager<'a>(&self) -> VeilidComponentGuard<'a, StorageManager>;
    fn routing_table<'a>(&self) -> VeilidComponentGuard<'a, RoutingTable>;
    fn network_manager<'a>(&self) -> VeilidComponentGuard<'a, NetworkManager>;
    fn rpc_processor<'a>(&self) -> VeilidComponentGuard<'a, RPCProcessor>;
    fn attachment_manager<'a>(&self) -> VeilidComponentGuard<'a, AttachmentManager>;
}

impl<T: VeilidComponentRegistryAccessor + ?Sized> RegisteredComponents for T {
    fn protected_store<'a>(&self) -> VeilidComponentGuard<'a, ProtectedStore> {
        self.registry().lookup::<ProtectedStore>().unwrap_or_log()
    }
    fn crypto<'a>(&self) -> VeilidComponentGuard<'a, Crypto> {
        self.registry().lookup::<Crypto>().unwrap_or_log()
    }
    fn table_store<'a>(&self) -> VeilidComponentGuard<'a, TableStore> {
        self.registry().lookup::<TableStore>().unwrap_or_log()
    }
    fn storage_manager<'a>(&self) -> VeilidComponentGuard<'a, StorageManager> {
        self.registry().lookup::<StorageManager>().unwrap_or_log()
    }
    fn routing_table<'a>(&self) -> VeilidComponentGuard<'a, RoutingTable> {
        self.registry().lookup::<RoutingTable>().unwrap_or_log()
    }
    fn network_manager<'a>(&self) -> VeilidComponentGuard<'a, NetworkManager> {
        self.registry().lookup::<NetworkManager>().unwrap_or_log()
    }
    fn rpc_processor<'a>(&self) -> VeilidComponentGuard<'a, RPCProcessor> {
        self.registry().lookup::<RPCProcessor>().unwrap_or_log()
    }
    fn attachment_manager<'a>(&self) -> VeilidComponentGuard<'a, AttachmentManager> {
        self.registry()
            .lookup::<AttachmentManager>()
            .unwrap_or_log()
    }
}

/////////////////////////////////////////////////////////////////////////////

lazy_static::lazy_static! {
    static ref INITIALIZED: Mutex<HashSet<InitKey>> = Mutex::new(HashSet::new());
    static ref STARTUP_TABLE: AsyncTagLockTable<InitKey> = AsyncTagLockTable::new();
}

/// Initialize a Veilid node, with the configuration in JSON format.
///
/// Must be called only once per 'program_name + namespace' combination at the start of an application.
/// The 'config_json' must specify a unique 'program_name + namespace' combination per simulataneous call to api_startup.
/// You can use the same program_name multiple times to create separate storage locations.
/// Multiple namespaces for the same program_name will use the same databases and on-disk locations, but will partition keys internally
/// to keep the namespaces distict.
///
/// * `update_callback` - called when internal state of the Veilid node changes, for example, when app-level messages are received, when private routes die and need to be reallocated, or when routing table states change.
/// * `config_json` - called at startup to supply a JSON configuration object.
///
/// Returns a [VeilidAPI] object that can be used to operate the node. Errors with
/// `VeilidAPIError::AlreadyInitialized` if a node is already running for the same
/// `program_name + namespace`; the previous [VeilidAPI] must be shut down first.
/// Errors with `VeilidAPIError::Generic` if `config_json` is not valid JSON, or if the
/// parsed config fails validation (empty `program_name`, a `program_name`/`namespace` that is
/// not a valid filename, out-of-range connection caps, or invalid RPC/DHT tuning). Errors with
/// `VeilidAPIError::Internal` if subsystem init or post-init fails (or, on Android, if the
/// Android globals were not set up); init/post-init may also surface a more specific
/// `VeilidAPIError` variant from the failing subsystem.
///
/// Blocks until subsystems are initialized (disk stores opened, background tasks started);
/// the network is not bound until the node attaches. Startup/shutdown is serialized per
/// `program_name + namespace`.
/// [VeilidAPI] is reference-counted: dropping the last clone spawns a detached shutdown that
/// releases the `program_name + namespace` slot. Call [VeilidAPI::shutdown] to shut down
/// deterministically and wait for completion.
pub async fn api_startup_json(
    update_callback: UpdateCallback,
    config_json: String,
) -> VeilidAPIResult<VeilidAPI> {
    // Parse the JSON config, collecting any keys that don't map to a known config field
    // (likely a typo or a setting that moved under 'internal'). We warn about these once the
    // node is up rather than erroring, so an unknown key never blocks startup.
    let mut unknown_keys = Vec::<String>::new();
    let config: VeilidConfig = {
        let mut de = serde_json::Deserializer::from_str(&config_json);
        let config = serde_ignored::deserialize(&mut de, |path| {
            unknown_keys.push(path.to_string());
        })
        .map_err(VeilidAPIError::generic)?;
        de.end().map_err(VeilidAPIError::generic)?;
        config
    };

    let veilid_api = api_startup(update_callback, config).await?;

    // Now that the node (and its logging) is up, surface any unrecognized config keys
    if !unknown_keys.is_empty() {
        let registry = veilid_api.core_context()?.registry();
        for key in &unknown_keys {
            veilid_log!(registry warn "ignoring unknown veilid config key: '{}'", key);
        }
    }

    Ok(veilid_api)
}

/// Initialize a Veilid node, with the configuration object.
///
/// Must be called only once at the start of an application.
///
/// * `update_callback` - called when internal state of the Veilid node changes, for example, when app-level messages are received, when private routes die and need to be reallocated, or when routing table states change.
/// * `config` - called at startup to supply a configuration object.
///
/// Returns a [VeilidAPI] object that can be used to operate the node. Errors with
/// `VeilidAPIError::AlreadyInitialized` if a node is already running for the same
/// `program_name + namespace`; the previous [VeilidAPI] must be shut down first.
/// Errors with `VeilidAPIError::Generic` if `config` fails validation (empty `program_name`,
/// a `program_name`/`namespace` that is not a valid filename, out-of-range connection caps,
/// or invalid RPC/DHT tuning). Errors with `VeilidAPIError::Internal` if subsystem init or
/// post-init fails (or, on Android, if the Android globals were not set up); init/post-init
/// may also surface a more specific `VeilidAPIError` variant from the failing subsystem.
///
/// Blocks until subsystems are initialized (disk stores opened, background tasks started);
/// the network is not bound until the node attaches. Startup/shutdown is serialized per
/// `program_name + namespace`.
/// [VeilidAPI] is reference-counted: dropping the last clone spawns a detached shutdown that
/// releases the `program_name + namespace` slot. Call [VeilidAPI::shutdown] to shut down
/// deterministically and wait for completion.
#[cfg_attr(
    feature = "instrument",
    instrument(
        level = "trace",
        target = "core_context",
        err,
        skip_all,
        fields(__VEILID_LOG_KEY)
    )
)]
pub async fn api_startup(
    update_callback: UpdateCallback,
    config: VeilidConfig,
) -> VeilidAPIResult<VeilidAPI> {
    #[cfg(feature = "debug-locks")]
    veilid_tools::deadlock_detector::start_deadlock_detector();

    // Get the program_name and namespace we're starting up in
    let program_name = config.program_name.clone();
    let namespace = config.namespace.clone();

    #[cfg(feature = "instrument")]
    tracing::Span::current().record(
        "__VEILID_LOG_KEY",
        VeilidLayerFilter::make_veilid_log_key(&program_name, &namespace),
    );

    let init_key = (program_name, namespace);

    // Only allow one startup/shutdown per program_name+namespace combination simultaneously
    let _tag_guard = STARTUP_TABLE.lock_tag(init_key.clone()).await;
    // See if we have an API started up already
    if INITIALIZED.lock().contains(&init_key) {
        apibail_already_initialized!();
    }

    // Create core context
    let context = VeilidCoreContext::new_with_config(update_callback, config).await?;

    // Return an API object around our context
    let veilid_api = VeilidAPI::new(context);

    // Add to the initialized set
    INITIALIZED.lock().insert(init_key);

    Ok(veilid_api)
}

#[cfg_attr(
    feature = "instrument",
    instrument(
        level = "trace",
        target = "core_context",
        skip_all,
        fields(__VEILID_LOG_KEY = context.log_key())
    )
)]
pub(crate) async fn api_shutdown(context: VeilidCoreContext) {
    let init_key = {
        let registry = context.registry();
        let config = registry.config();
        (config.program_name.clone(), config.namespace.clone())
    };

    // Only allow one startup/shutdown per program_name+namespace combination simultaneously
    let _tag_guard = STARTUP_TABLE.lock_tag(init_key.clone()).await;

    // See if we have an API started up already
    if !INITIALIZED.lock().contains(&init_key) {
        return;
    }

    // Shutdown the context
    context.shutdown().await;

    // Remove from the initialized set
    INITIALIZED.lock().remove(&init_key);
}