mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-06-05 20:13:44 +02:00
Bring service back to simple working state to end-to-end test.
This commit is contained in:
parent
0552d587a0
commit
2017dcf746
9 changed files with 164 additions and 59 deletions
|
@ -227,9 +227,23 @@ mod tests {
|
||||||
type TypeMap = HashMap<String, Type>;
|
type TypeMap = HashMap<String, Type>;
|
||||||
|
|
||||||
use super::{Dictionary, BOOL_TRUTH};
|
use super::{Dictionary, BOOL_TRUTH};
|
||||||
use crate::util::testutil::randstring;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
fn randstring(len: u8) -> String {
|
||||||
|
(0..len)
|
||||||
|
.map(|_| (rand::random::<u8>() % 26) + 'a' as u8)
|
||||||
|
.map(|c| {
|
||||||
|
if rand::random::<bool>() {
|
||||||
|
(c as char).to_ascii_uppercase()
|
||||||
|
} else {
|
||||||
|
c as char
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(|c| c.to_string())
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("")
|
||||||
|
}
|
||||||
|
|
||||||
fn make_dictionary() -> (Dictionary, TypeMap) {
|
fn make_dictionary() -> (Dictionary, TypeMap) {
|
||||||
let mut d = Dictionary::new();
|
let mut d = Dictionary::new();
|
||||||
let mut tm = TypeMap::new();
|
let mut tm = TypeMap::new();
|
||||||
|
|
|
@ -8,9 +8,6 @@ pub mod marshalable;
|
||||||
/// A value for ticks that indicates that something never happened, and is thus very long before zero ticks.
|
/// A value for ticks that indicates that something never happened, and is thus very long before zero ticks.
|
||||||
pub(crate) const NEVER_HAPPENED_TICKS: i64 = -2147483648;
|
pub(crate) const NEVER_HAPPENED_TICKS: i64 = -2147483648;
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
pub mod testutil;
|
|
||||||
|
|
||||||
#[cfg(feature = "debug_events")]
|
#[cfg(feature = "debug_events")]
|
||||||
#[allow(unused_macros)]
|
#[allow(unused_macros)]
|
||||||
macro_rules! debug_event {
|
macro_rules! debug_event {
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
// (c) 2020-2022 ZeroTier, Inc. -- currently propritery pending actual release and licensing. See LICENSE.md.
|
|
||||||
|
|
||||||
// from zeronsd
|
|
||||||
pub fn randstring(len: u8) -> String {
|
|
||||||
(0..len)
|
|
||||||
.map(|_| (rand::random::<u8>() % 26) + 'a' as u8)
|
|
||||||
.map(|c| {
|
|
||||||
if rand::random::<bool>() {
|
|
||||||
(c as char).to_ascii_uppercase()
|
|
||||||
} else {
|
|
||||||
c as char
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.map(|c| c.to_string())
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join("")
|
|
||||||
}
|
|
|
@ -459,6 +459,21 @@ mod tests {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn randstring(len: u8) -> String {
|
||||||
|
(0..len)
|
||||||
|
.map(|_| (rand::random::<u8>() % 26) + 'a' as u8)
|
||||||
|
.map(|c| {
|
||||||
|
if rand::random::<bool>() {
|
||||||
|
(c as char).to_ascii_uppercase()
|
||||||
|
} else {
|
||||||
|
c as char
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(|c| c.to_string())
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("")
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn endpoint_default() {
|
fn endpoint_default() {
|
||||||
let e: Endpoint = Default::default();
|
let e: Endpoint = Default::default();
|
||||||
|
@ -611,7 +626,6 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn endpoint_marshal_http() {
|
fn endpoint_marshal_http() {
|
||||||
use crate::util::buffer::Buffer;
|
use crate::util::buffer::Buffer;
|
||||||
use crate::util::testutil::randstring;
|
|
||||||
|
|
||||||
for _ in 0..1000 {
|
for _ in 0..1000 {
|
||||||
let http = Endpoint::Http(randstring(30));
|
let http = Endpoint::Http(randstring(30));
|
||||||
|
@ -650,7 +664,6 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn endpoint_to_from_string() {
|
fn endpoint_to_from_string() {
|
||||||
use crate::util::testutil::randstring;
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
for _ in 0..1000 {
|
for _ in 0..1000 {
|
||||||
|
|
|
@ -24,7 +24,7 @@ pub use event::Event;
|
||||||
pub use identity::Identity;
|
pub use identity::Identity;
|
||||||
pub use inetaddress::InetAddress;
|
pub use inetaddress::InetAddress;
|
||||||
pub use mac::MAC;
|
pub use mac::MAC;
|
||||||
pub use node::{HostSystem, InnerProtocol, Node, PathFilter, Storage};
|
pub use node::{DummyInnerProtocol, DummyPathFilter, HostSystem, InnerProtocol, Node, PathFilter, Storage};
|
||||||
pub use path::Path;
|
pub use path::Path;
|
||||||
pub use peer::Peer;
|
pub use peer::Peer;
|
||||||
pub use rootset::{Root, RootSet};
|
pub use rootset::{Root, RootSet};
|
||||||
|
|
|
@ -97,9 +97,9 @@ pub trait Storage: Sync + Send + 'static {
|
||||||
|
|
||||||
/// Trait to be implemented to provide path hints and a filter to approve physical paths
|
/// Trait to be implemented to provide path hints and a filter to approve physical paths
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait PathFilter<HostSystemImpl: HostSystem>: Sync + Send + 'static {
|
pub trait PathFilter: Sync + Send + 'static {
|
||||||
/// Called to check and see if a physical address should be used for ZeroTier traffic to a node.
|
/// Called to check and see if a physical address should be used for ZeroTier traffic to a node.
|
||||||
async fn check_path(
|
async fn check_path<HostSystemImpl: HostSystem>(
|
||||||
&self,
|
&self,
|
||||||
id: &Identity,
|
id: &Identity,
|
||||||
endpoint: &Endpoint,
|
endpoint: &Endpoint,
|
||||||
|
@ -108,7 +108,7 @@ pub trait PathFilter<HostSystemImpl: HostSystem>: Sync + Send + 'static {
|
||||||
) -> bool;
|
) -> bool;
|
||||||
|
|
||||||
/// Called to look up any statically defined or memorized paths to known nodes.
|
/// Called to look up any statically defined or memorized paths to known nodes.
|
||||||
async fn get_path_hints(
|
async fn get_path_hints<HostSystemImpl: HostSystem>(
|
||||||
&self,
|
&self,
|
||||||
id: &Identity,
|
id: &Identity,
|
||||||
) -> Option<
|
) -> Option<
|
||||||
|
@ -849,3 +849,79 @@ impl<HostSystemImpl: HostSystem> Node<HostSystemImpl> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Dummy no-op inner protocol for debugging and testing.
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct DummyInnerProtocol;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl InnerProtocol for DummyInnerProtocol {
|
||||||
|
async fn handle_packet<HostSystemImpl: HostSystem>(
|
||||||
|
&self,
|
||||||
|
_source: &Peer<HostSystemImpl>,
|
||||||
|
_source_path: &Path<HostSystemImpl>,
|
||||||
|
_verb: u8,
|
||||||
|
_payload: &PacketBuffer,
|
||||||
|
) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_error<HostSystemImpl: HostSystem>(
|
||||||
|
&self,
|
||||||
|
_source: &Peer<HostSystemImpl>,
|
||||||
|
_source_path: &Path<HostSystemImpl>,
|
||||||
|
_in_re_verb: u8,
|
||||||
|
_in_re_message_id: u64,
|
||||||
|
_error_code: u8,
|
||||||
|
_payload: &PacketBuffer,
|
||||||
|
_cursor: &mut usize,
|
||||||
|
) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_ok<HostSystemImpl: HostSystem>(
|
||||||
|
&self,
|
||||||
|
_source: &Peer<HostSystemImpl>,
|
||||||
|
_source_path: &Path<HostSystemImpl>,
|
||||||
|
_in_re_verb: u8,
|
||||||
|
_in_re_message_id: u64,
|
||||||
|
_payload: &PacketBuffer,
|
||||||
|
_cursor: &mut usize,
|
||||||
|
) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_communicate_with(&self, _id: &Identity) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dummy no-op path filter for debugging and testing.
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct DummyPathFilter;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl PathFilter for DummyPathFilter {
|
||||||
|
async fn check_path<HostSystemImpl: HostSystem>(
|
||||||
|
&self,
|
||||||
|
_id: &Identity,
|
||||||
|
_endpoint: &Endpoint,
|
||||||
|
_local_socket: Option<&<HostSystemImpl as HostSystem>::LocalSocket>,
|
||||||
|
_local_interface: Option<&<HostSystemImpl as HostSystem>::LocalInterface>,
|
||||||
|
) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_path_hints<HostSystemImpl: HostSystem>(
|
||||||
|
&self,
|
||||||
|
_id: &Identity,
|
||||||
|
) -> Option<
|
||||||
|
Vec<(
|
||||||
|
Endpoint,
|
||||||
|
Option<<HostSystemImpl as HostSystem>::LocalSocket>,
|
||||||
|
Option<<HostSystemImpl as HostSystem>::LocalInterface>,
|
||||||
|
)>,
|
||||||
|
> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -7,10 +7,12 @@ use std::sync::Arc;
|
||||||
use crate::localconfig::Config;
|
use crate::localconfig::Config;
|
||||||
use crate::utils::{read_limit, DEFAULT_FILE_IO_READ_LIMIT};
|
use crate::utils::{read_limit, DEFAULT_FILE_IO_READ_LIMIT};
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
|
||||||
use parking_lot::{Mutex, RwLock};
|
use parking_lot::{Mutex, RwLock};
|
||||||
|
|
||||||
use zerotier_crypto::random::next_u32_secure;
|
use zerotier_crypto::random::next_u32_secure;
|
||||||
use zerotier_network_hypervisor::vl1::Identity;
|
use zerotier_network_hypervisor::vl1::{Identity, Storage};
|
||||||
|
|
||||||
const AUTH_TOKEN_DEFAULT_LENGTH: usize = 48;
|
const AUTH_TOKEN_DEFAULT_LENGTH: usize = 48;
|
||||||
const AUTH_TOKEN_POSSIBLE_CHARS: &'static str = "0123456789abcdefghijklmnopqrstuvwxyz";
|
const AUTH_TOKEN_POSSIBLE_CHARS: &'static str = "0123456789abcdefghijklmnopqrstuvwxyz";
|
||||||
|
@ -26,6 +28,32 @@ pub struct DataDir {
|
||||||
authtoken: Mutex<String>,
|
authtoken: Mutex<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Storage for DataDir {
|
||||||
|
async fn load_node_identity(&self) -> Option<Identity> {
|
||||||
|
let id_data = read_limit(self.base_path.join(IDENTITY_SECRET_FILENAME), 4096).await;
|
||||||
|
if id_data.is_err() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let id_data = Identity::from_str(String::from_utf8_lossy(id_data.unwrap().as_slice()).as_ref());
|
||||||
|
if id_data.is_err() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(id_data.unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn save_node_identity(&self, id: &Identity) {
|
||||||
|
assert!(id.secret.is_some());
|
||||||
|
let id_secret_str = id.to_secret_string();
|
||||||
|
let id_public_str = id.to_string();
|
||||||
|
let secret_path = self.base_path.join(IDENTITY_SECRET_FILENAME);
|
||||||
|
// TODO: handle errors
|
||||||
|
let _ = tokio::fs::write(&secret_path, id_secret_str.as_bytes()).await;
|
||||||
|
assert!(crate::utils::fs_restrict_permissions(&secret_path));
|
||||||
|
let _ = tokio::fs::write(self.base_path.join(IDENTITY_PUBLIC_FILENAME), id_public_str.as_bytes()).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl DataDir {
|
impl DataDir {
|
||||||
pub async fn open<P: AsRef<Path>>(path: P) -> std::io::Result<Self> {
|
pub async fn open<P: AsRef<Path>>(path: P) -> std::io::Result<Self> {
|
||||||
let base_path = path.as_ref().to_path_buf();
|
let base_path = path.as_ref().to_path_buf();
|
||||||
|
@ -58,28 +86,6 @@ impl DataDir {
|
||||||
return Ok(Self { base_path, config, authtoken: Mutex::new(String::new()) });
|
return Ok(Self { base_path, config, authtoken: Mutex::new(String::new()) });
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load identity.secret from data directory.
|
|
||||||
pub async fn load_identity(&self) -> std::io::Result<Identity> {
|
|
||||||
let id_data = Identity::from_str(
|
|
||||||
String::from_utf8_lossy(read_limit(self.base_path.join(IDENTITY_SECRET_FILENAME), 4096).await?.as_slice()).as_ref(),
|
|
||||||
);
|
|
||||||
if id_data.is_err() {
|
|
||||||
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, id_data.err().unwrap()));
|
|
||||||
}
|
|
||||||
Ok(id_data.unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Save identity.secret and identity.public to data directory.
|
|
||||||
pub async fn save_identity(&self, id: &Identity) -> std::io::Result<()> {
|
|
||||||
assert!(id.secret.is_some());
|
|
||||||
let id_secret_str = id.to_secret_string();
|
|
||||||
let id_public_str = id.to_string();
|
|
||||||
let secret_path = self.base_path.join(IDENTITY_SECRET_FILENAME);
|
|
||||||
tokio::fs::write(&secret_path, id_secret_str.as_bytes()).await?;
|
|
||||||
assert!(crate::utils::fs_restrict_permissions(&secret_path));
|
|
||||||
tokio::fs::write(self.base_path.join(IDENTITY_PUBLIC_FILENAME), id_public_str.as_bytes()).await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get authorization token for local API, creating and saving if it does not exist.
|
/// Get authorization token for local API, creating and saving if it does not exist.
|
||||||
pub async fn authtoken(&self) -> std::io::Result<String> {
|
pub async fn authtoken(&self) -> std::io::Result<String> {
|
||||||
let authtoken = self.authtoken.lock().clone();
|
let authtoken = self.authtoken.lock().clone();
|
||||||
|
|
|
@ -10,12 +10,15 @@ pub mod utils;
|
||||||
pub mod vnic;
|
pub mod vnic;
|
||||||
|
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use clap::error::{ContextKind, ContextValue};
|
use clap::error::{ContextKind, ContextValue};
|
||||||
use clap::{Arg, ArgMatches, Command};
|
use clap::{Arg, ArgMatches, Command};
|
||||||
|
|
||||||
use zerotier_network_hypervisor::vl2::Switch;
|
|
||||||
use zerotier_network_hypervisor::{VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION};
|
use zerotier_network_hypervisor::{VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION};
|
||||||
|
use zerotier_vl1_service::VL1Service;
|
||||||
|
|
||||||
|
use crate::datadir::DataDir;
|
||||||
|
|
||||||
pub fn print_help() {
|
pub fn print_help() {
|
||||||
let h = crate::cmdline_help::make_cmdline_help();
|
let h = crate::cmdline_help::make_cmdline_help();
|
||||||
|
@ -39,6 +42,19 @@ pub struct Flags {
|
||||||
pub auth_token_override: Option<String>,
|
pub auth_token_override: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn open_datadir(flags: &Flags) -> Arc<DataDir> {
|
||||||
|
let datadir = DataDir::open(flags.base_path.as_str()).await;
|
||||||
|
if datadir.is_ok() {
|
||||||
|
return Arc::new(datadir.unwrap());
|
||||||
|
}
|
||||||
|
eprintln!(
|
||||||
|
"FATAL: unable to open data directory {}: {}",
|
||||||
|
flags.base_path,
|
||||||
|
datadir.err().unwrap().to_string()
|
||||||
|
);
|
||||||
|
std::process::exit(exitcode::ERR_IOERR);
|
||||||
|
}
|
||||||
|
|
||||||
async fn async_main(flags: Flags, global_args: Box<ArgMatches>) -> i32 {
|
async fn async_main(flags: Flags, global_args: Box<ArgMatches>) -> i32 {
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
match global_args.subcommand() {
|
match global_args.subcommand() {
|
||||||
|
@ -59,8 +75,10 @@ async fn async_main(flags: Flags, global_args: Box<ArgMatches>) -> i32 {
|
||||||
Some(("service", _)) => {
|
Some(("service", _)) => {
|
||||||
drop(global_args); // free unnecessary heap before starting service as we're done with CLI args
|
drop(global_args); // free unnecessary heap before starting service as we're done with CLI args
|
||||||
|
|
||||||
/*
|
let test_inner = Arc::new(zerotier_network_hypervisor::vl1::DummyInnerProtocol::default());
|
||||||
let svc = service::Service::new(tokio::runtime::Handle::current(), &flags.base_path, true).await;
|
let test_path_filter = Arc::new(zerotier_network_hypervisor::vl1::DummyPathFilter::default());
|
||||||
|
let datadir = open_datadir(&flags).await;
|
||||||
|
let svc = VL1Service::new(datadir, test_inner, test_path_filter, zerotier_vl1_service::Settings::default()).await;
|
||||||
if svc.is_ok() {
|
if svc.is_ok() {
|
||||||
let _ = tokio::signal::ctrl_c().await;
|
let _ = tokio::signal::ctrl_c().await;
|
||||||
println!("Terminate signal received, shutting down...");
|
println!("Terminate signal received, shutting down...");
|
||||||
|
@ -69,8 +87,6 @@ async fn async_main(flags: Flags, global_args: Box<ArgMatches>) -> i32 {
|
||||||
println!("FATAL: error launching service: {}", svc.err().unwrap().to_string());
|
println!("FATAL: error launching service: {}", svc.err().unwrap().to_string());
|
||||||
exitcode::ERR_IOERR
|
exitcode::ERR_IOERR
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
todo!()
|
|
||||||
}
|
}
|
||||||
Some(("identity", cmd_args)) => todo!(),
|
Some(("identity", cmd_args)) => todo!(),
|
||||||
Some(("rootset", cmd_args)) => cli::rootset::cmd(flags, cmd_args).await,
|
Some(("rootset", cmd_args)) => cli::rootset::cmd(flags, cmd_args).await,
|
||||||
|
|
|
@ -24,7 +24,7 @@ use tokio::time::Duration;
|
||||||
/// talks to the physical network, manages the vl1 node, and presents a templated interface for
|
/// talks to the physical network, manages the vl1 node, and presents a templated interface for
|
||||||
/// whatever inner protocol implementation is using it. This would typically be VL2 but could be
|
/// whatever inner protocol implementation is using it. This would typically be VL2 but could be
|
||||||
/// a test harness or just the controller for a controller that runs stand-alone.
|
/// a test harness or just the controller for a controller that runs stand-alone.
|
||||||
pub struct VL1Service<StorageImpl: Storage, PathFilterImpl: PathFilter<Self>, InnerProtocolImpl: InnerProtocol> {
|
pub struct VL1Service<StorageImpl: Storage, PathFilterImpl: PathFilter, InnerProtocolImpl: InnerProtocol> {
|
||||||
state: tokio::sync::RwLock<VL1ServiceMutableState>,
|
state: tokio::sync::RwLock<VL1ServiceMutableState>,
|
||||||
storage: Arc<StorageImpl>,
|
storage: Arc<StorageImpl>,
|
||||||
inner: Arc<InnerProtocolImpl>,
|
inner: Arc<InnerProtocolImpl>,
|
||||||
|
@ -38,7 +38,7 @@ struct VL1ServiceMutableState {
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<StorageImpl: Storage, PathFilterImpl: PathFilter<Self>, InnerProtocolImpl: InnerProtocol>
|
impl<StorageImpl: Storage, PathFilterImpl: PathFilter, InnerProtocolImpl: InnerProtocol>
|
||||||
VL1Service<StorageImpl, PathFilterImpl, InnerProtocolImpl>
|
VL1Service<StorageImpl, PathFilterImpl, InnerProtocolImpl>
|
||||||
{
|
{
|
||||||
pub async fn new(
|
pub async fn new(
|
||||||
|
@ -199,7 +199,7 @@ impl<StorageImpl: Storage, PathFilterImpl: PathFilter<Self>, InnerProtocolImpl:
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<StorageImpl: Storage, PathFilterImpl: PathFilter<Self>, InnerProtocolImpl: InnerProtocol> HostSystem
|
impl<StorageImpl: Storage, PathFilterImpl: PathFilter, InnerProtocolImpl: InnerProtocol> HostSystem
|
||||||
for VL1Service<StorageImpl, PathFilterImpl, InnerProtocolImpl>
|
for VL1Service<StorageImpl, PathFilterImpl, InnerProtocolImpl>
|
||||||
{
|
{
|
||||||
type LocalSocket = crate::LocalSocket;
|
type LocalSocket = crate::LocalSocket;
|
||||||
|
@ -297,7 +297,7 @@ impl<StorageImpl: Storage, PathFilterImpl: PathFilter<Self>, InnerProtocolImpl:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<StorageImpl: Storage, PathFilterImpl: PathFilter<Self>, InnerProtocolImpl: InnerProtocol> Drop
|
impl<StorageImpl: Storage, PathFilterImpl: PathFilter, InnerProtocolImpl: InnerProtocol> Drop
|
||||||
for VL1Service<StorageImpl, PathFilterImpl, InnerProtocolImpl>
|
for VL1Service<StorageImpl, PathFilterImpl, InnerProtocolImpl>
|
||||||
{
|
{
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
|
|
Loading…
Add table
Reference in a new issue