mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-04-26 17:03:43 +02:00
Some cleanup, move some more V1 only fields into V1 credentials struct.
This commit is contained in:
parent
5772a135f5
commit
6bf978d4de
8 changed files with 41 additions and 44 deletions
|
@ -43,10 +43,6 @@ impl Cache {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_cached_networks(&self) -> Vec<NetworkId> {
|
|
||||||
self.by_nwid.read().unwrap().keys().cloned().collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update a network if changed, returning whether or not any update was made and the old version if any.
|
/// Update a network if changed, returning whether or not any update was made and the old version if any.
|
||||||
/// A value of (true, None) indicates that there was no network by that ID in which case it is added.
|
/// A value of (true, None) indicates that there was no network by that ID in which case it is added.
|
||||||
pub fn on_network_updated(&self, network: Network) -> (bool, Option<Network>) {
|
pub fn on_network_updated(&self, network: Network) -> (bool, Option<Network>) {
|
||||||
|
|
|
@ -183,8 +183,8 @@ impl Controller {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send one or more revocation object(s) to a peer (V1 protocol only).
|
/// Send one or more revocation object(s) to a peer.
|
||||||
fn v1_proto_send_revocations(&self, peer: &Peer, mut revocations: Vec<Revocation>) {
|
fn send_revocations(&self, peer: &Peer, mut revocations: Vec<Revocation>) {
|
||||||
if let Some(host_system) = self.service.read().unwrap().upgrade() {
|
if let Some(host_system) = self.service.read().unwrap().upgrade() {
|
||||||
let time_ticks = ms_monotonic();
|
let time_ticks = ms_monotonic();
|
||||||
while !revocations.is_empty() {
|
while !revocations.is_empty() {
|
||||||
|
@ -336,11 +336,9 @@ impl Controller {
|
||||||
nc.name = network.name.clone();
|
nc.name = network.name.clone();
|
||||||
nc.private = network.private;
|
nc.private = network.private;
|
||||||
nc.timestamp = now;
|
nc.timestamp = now;
|
||||||
nc.credential_ttl = credential_ttl;
|
|
||||||
nc.revision = Some(now as u64);
|
|
||||||
nc.mtu = network.mtu.unwrap_or(ZEROTIER_VIRTUAL_NETWORK_DEFAULT_MTU as u16);
|
|
||||||
nc.multicast_limit = network.multicast_limit.unwrap_or(DEFAULT_MULTICAST_LIMIT as u32);
|
nc.multicast_limit = network.multicast_limit.unwrap_or(DEFAULT_MULTICAST_LIMIT as u32);
|
||||||
nc.multicast_like_expire = Some(protocol::VL2_DEFAULT_MULTICAST_LIKE_EXPIRE as u32);
|
nc.multicast_like_expire = Some(protocol::VL2_DEFAULT_MULTICAST_LIKE_EXPIRE as u32);
|
||||||
|
nc.mtu = network.mtu.unwrap_or(ZEROTIER_VIRTUAL_NETWORK_DEFAULT_MTU as u16);
|
||||||
nc.routes = network.ip_routes;
|
nc.routes = network.ip_routes;
|
||||||
nc.static_ips = member.ip_assignments.clone();
|
nc.static_ips = member.ip_assignments.clone();
|
||||||
nc.rules = network.rules;
|
nc.rules = network.rules;
|
||||||
|
@ -354,6 +352,8 @@ impl Controller {
|
||||||
vl2::v1::CertificateOfMembership::new(&self.local_identity, network_id, &source_identity, now, credential_ttl)
|
vl2::v1::CertificateOfMembership::new(&self.local_identity, network_id, &source_identity, now, credential_ttl)
|
||||||
{
|
{
|
||||||
let mut v1cred = V1Credentials {
|
let mut v1cred = V1Credentials {
|
||||||
|
revision: now as u64,
|
||||||
|
max_delta: credential_ttl,
|
||||||
certificate_of_membership: com,
|
certificate_of_membership: com,
|
||||||
certificates_of_ownership: Vec::new(),
|
certificates_of_ownership: Vec::new(),
|
||||||
tags: HashMap::new(),
|
tags: HashMap::new(),
|
||||||
|
@ -383,7 +383,7 @@ impl Controller {
|
||||||
// For anyone who has been deauthorized but is still in the window, send revocations.
|
// For anyone who has been deauthorized but is still in the window, send revocations.
|
||||||
if let Ok(deauthed_members_still_in_window) = self
|
if let Ok(deauthed_members_still_in_window) = self
|
||||||
.database
|
.database
|
||||||
.list_members_deauthorized_after(network.id, now - credential_ttl)
|
.list_members_deauthorized_after(network.id, now - (credential_ttl as i64))
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
if !deauthed_members_still_in_window.is_empty() {
|
if !deauthed_members_still_in_window.is_empty() {
|
||||||
|
@ -419,7 +419,7 @@ impl Controller {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.entry(source_identity.fingerprint)
|
.entry(source_identity.fingerprint)
|
||||||
.or_default()
|
.or_default()
|
||||||
.insert(network_id, ms_monotonic() + nc.credential_ttl);
|
.insert(network_id, ms_monotonic() + (credential_ttl as i64));
|
||||||
|
|
||||||
network_config = Some(nc);
|
network_config = Some(nc);
|
||||||
}
|
}
|
||||||
|
@ -497,7 +497,7 @@ impl InnerProtocol for Controller {
|
||||||
//println!("{}", serde_yaml::to_string(&config).unwrap());
|
//println!("{}", serde_yaml::to_string(&config).unwrap());
|
||||||
self2.send_network_config(source.as_ref(), &config, Some(message_id));
|
self2.send_network_config(source.as_ref(), &config, Some(message_id));
|
||||||
if let Some(revocations) = revocations {
|
if let Some(revocations) = revocations {
|
||||||
self2.v1_proto_send_revocations(source.as_ref(), revocations);
|
self2.send_revocations(source.as_ref(), revocations);
|
||||||
}
|
}
|
||||||
(result, Some(config))
|
(result, Some(config))
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,6 +74,7 @@ impl FileDatabase {
|
||||||
if let Some((record_type, network_id, node_id)) =
|
if let Some((record_type, network_id, node_id)) =
|
||||||
Self::record_type_from_path(controller_address, path0.as_path())
|
Self::record_type_from_path(controller_address, path0.as_path())
|
||||||
{
|
{
|
||||||
|
// Paths to objects that were deleted or changed. Changed includes adding new objects.
|
||||||
let mut deleted = None;
|
let mut deleted = None;
|
||||||
let mut changed = None;
|
let mut changed = None;
|
||||||
|
|
||||||
|
@ -117,6 +118,7 @@ impl FileDatabase {
|
||||||
}
|
}
|
||||||
|
|
||||||
if deleted.is_some() {
|
if deleted.is_some() {
|
||||||
|
println!("DELETED: {}", deleted.unwrap().as_os_str().to_string_lossy());
|
||||||
match record_type {
|
match record_type {
|
||||||
RecordType::Network => {
|
RecordType::Network => {
|
||||||
if let Some((network, mut members)) = db.cache.on_network_deleted(network_id) {
|
if let Some((network, mut members)) = db.cache.on_network_deleted(network_id) {
|
||||||
|
@ -138,6 +140,7 @@ impl FileDatabase {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(changed) = changed {
|
if let Some(changed) = changed {
|
||||||
|
println!("CHANGED: {}", changed.as_os_str().to_string_lossy());
|
||||||
match record_type {
|
match record_type {
|
||||||
RecordType::Network => {
|
RecordType::Network => {
|
||||||
if let Ok(Some(new_network)) = Self::get_network_internal(changed).await {
|
if let Ok(Some(new_network)) = Self::get_network_internal(changed).await {
|
||||||
|
@ -375,6 +378,7 @@ impl Database for FileDatabase {
|
||||||
let mut member = Self::get_member_internal(&self.member_path(network_id, node_id)).await?;
|
let mut member = Self::get_member_internal(&self.member_path(network_id, node_id)).await?;
|
||||||
if let Some(member) = member.as_mut() {
|
if let Some(member) = member.as_mut() {
|
||||||
if member.network_id != network_id {
|
if member.network_id != network_id {
|
||||||
|
// Also auto-update member network IDs, see get_network().
|
||||||
member.network_id = network_id;
|
member.network_id = network_id;
|
||||||
self.save_member(member.clone()).await?;
|
self.save_member(member.clone()).await?;
|
||||||
}
|
}
|
||||||
|
@ -385,7 +389,6 @@ impl Database for FileDatabase {
|
||||||
async fn save_member(&self, obj: Member) -> Result<(), Box<dyn Error + Send + Sync>> {
|
async fn save_member(&self, obj: Member) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||||
let base_member_path = self.member_path(obj.network_id, obj.node_id);
|
let base_member_path = self.member_path(obj.network_id, obj.node_id);
|
||||||
let _ = fs::create_dir_all(base_member_path.parent().unwrap()).await;
|
let _ = fs::create_dir_all(base_member_path.parent().unwrap()).await;
|
||||||
//let _ = fs::write(base_member_path, to_json_pretty(&obj).as_bytes()).await?;
|
|
||||||
let _ = fs::write(base_member_path, serde_yaml::to_string(&obj)?.as_bytes()).await?;
|
let _ = fs::write(base_member_path, serde_yaml::to_string(&obj)?.as_bytes()).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ use zerotier_network_hypervisor::vl2::NetworkId;
|
||||||
use crate::database::Database;
|
use crate::database::Database;
|
||||||
use crate::model::Member;
|
use crate::model::Member;
|
||||||
|
|
||||||
pub const CREDENTIAL_WINDOW_SIZE_DEFAULT: i64 = 1000 * 60 * 60;
|
pub const CREDENTIAL_WINDOW_SIZE_DEFAULT: u64 = 1000 * 60 * 60;
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
|
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||||
pub struct Ipv4AssignMode {
|
pub struct Ipv4AssignMode {
|
||||||
|
@ -105,7 +105,7 @@ pub struct Network {
|
||||||
/// Usually this does not need to be changed.
|
/// Usually this does not need to be changed.
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
#[serde(rename = "credentialTtl")]
|
#[serde(rename = "credentialTtl")]
|
||||||
pub credential_ttl: Option<i64>,
|
pub credential_ttl: Option<u64>,
|
||||||
|
|
||||||
/// Minimum supported ZeroTier protocol version for this network (default: undefined, up to members)
|
/// Minimum supported ZeroTier protocol version for this network (default: undefined, up to members)
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
|
|
@ -41,17 +41,6 @@ pub struct NetworkConfig {
|
||||||
/// Network configuration timestamp
|
/// Network configuration timestamp
|
||||||
pub timestamp: i64,
|
pub timestamp: i64,
|
||||||
|
|
||||||
/// TTL for credentials on this network (or window size for V1 nodes)
|
|
||||||
pub credential_ttl: i64,
|
|
||||||
|
|
||||||
/// Network configuration revision number (V1)
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
#[serde(default)]
|
|
||||||
pub revision: Option<u64>,
|
|
||||||
|
|
||||||
/// L2 Ethernet MTU for this network.
|
|
||||||
pub mtu: u16,
|
|
||||||
|
|
||||||
/// Suggested horizon limit for multicast (not a hard limit, but 0 disables multicast)
|
/// Suggested horizon limit for multicast (not a hard limit, but 0 disables multicast)
|
||||||
pub multicast_limit: u32,
|
pub multicast_limit: u32,
|
||||||
|
|
||||||
|
@ -60,6 +49,9 @@ pub struct NetworkConfig {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub multicast_like_expire: Option<u32>,
|
pub multicast_like_expire: Option<u32>,
|
||||||
|
|
||||||
|
/// L2 Ethernet MTU for this network.
|
||||||
|
pub mtu: u16,
|
||||||
|
|
||||||
/// ZeroTier-assigned L3 routes for this node.
|
/// ZeroTier-assigned L3 routes for this node.
|
||||||
#[serde(skip_serializing_if = "HashSet::is_empty")]
|
#[serde(skip_serializing_if = "HashSet::is_empty")]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
@ -105,8 +97,6 @@ impl NetworkConfig {
|
||||||
motd: String::new(),
|
motd: String::new(),
|
||||||
private: true,
|
private: true,
|
||||||
timestamp: 0,
|
timestamp: 0,
|
||||||
credential_ttl: 0,
|
|
||||||
revision: None,
|
|
||||||
mtu: 0,
|
mtu: 0,
|
||||||
multicast_limit: 0,
|
multicast_limit: 0,
|
||||||
multicast_like_expire: None,
|
multicast_like_expire: None,
|
||||||
|
@ -143,8 +133,6 @@ impl NetworkConfig {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
d.set_u64(proto_v1_field_name::network_config::TIMESTAMP, self.timestamp as u64);
|
d.set_u64(proto_v1_field_name::network_config::TIMESTAMP, self.timestamp as u64);
|
||||||
d.set_u64(proto_v1_field_name::network_config::MAX_DELTA, self.credential_ttl as u64);
|
|
||||||
d.set_u64(proto_v1_field_name::network_config::REVISION, self.revision.unwrap_or(0));
|
|
||||||
d.set_u64(proto_v1_field_name::network_config::MTU, self.mtu as u64);
|
d.set_u64(proto_v1_field_name::network_config::MTU, self.mtu as u64);
|
||||||
d.set_u64(proto_v1_field_name::network_config::MULTICAST_LIMIT, self.multicast_limit as u64);
|
d.set_u64(proto_v1_field_name::network_config::MULTICAST_LIMIT, self.multicast_limit as u64);
|
||||||
|
|
||||||
|
@ -193,6 +181,9 @@ impl NetworkConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(v1cred) = self.v1_credentials.as_ref() {
|
if let Some(v1cred) = self.v1_credentials.as_ref() {
|
||||||
|
d.set_u64(proto_v1_field_name::network_config::REVISION, v1cred.revision);
|
||||||
|
d.set_u64(proto_v1_field_name::network_config::MAX_DELTA, v1cred.max_delta);
|
||||||
|
|
||||||
d.set_bytes(
|
d.set_bytes(
|
||||||
proto_v1_field_name::network_config::CERTIFICATE_OF_MEMBERSHIP,
|
proto_v1_field_name::network_config::CERTIFICATE_OF_MEMBERSHIP,
|
||||||
v1cred
|
v1cred
|
||||||
|
@ -268,8 +259,6 @@ impl NetworkConfig {
|
||||||
nc.timestamp = d
|
nc.timestamp = d
|
||||||
.get_i64(proto_v1_field_name::network_config::TIMESTAMP)
|
.get_i64(proto_v1_field_name::network_config::TIMESTAMP)
|
||||||
.ok_or(InvalidParameterError("missing timestamp"))?;
|
.ok_or(InvalidParameterError("missing timestamp"))?;
|
||||||
nc.credential_ttl = d.get_i64(proto_v1_field_name::network_config::MAX_DELTA).unwrap_or(0);
|
|
||||||
nc.revision = Some(d.get_u64(proto_v1_field_name::network_config::REVISION).unwrap_or(0));
|
|
||||||
nc.mtu = d
|
nc.mtu = d
|
||||||
.get_u64(proto_v1_field_name::network_config::MTU)
|
.get_u64(proto_v1_field_name::network_config::MTU)
|
||||||
.unwrap_or(crate::protocol::ZEROTIER_VIRTUAL_NETWORK_DEFAULT_MTU as u64) as u16;
|
.unwrap_or(crate::protocol::ZEROTIER_VIRTUAL_NETWORK_DEFAULT_MTU as u64) as u16;
|
||||||
|
@ -327,6 +316,8 @@ impl NetworkConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut v1cred = V1Credentials {
|
let mut v1cred = V1Credentials {
|
||||||
|
revision: d.get_u64(proto_v1_field_name::network_config::REVISION).unwrap_or(0),
|
||||||
|
max_delta: d.get_u64(proto_v1_field_name::network_config::MAX_DELTA).unwrap_or(0),
|
||||||
certificate_of_membership: CertificateOfMembership::from_bytes(
|
certificate_of_membership: CertificateOfMembership::from_bytes(
|
||||||
d.get_bytes(proto_v1_field_name::network_config::CERTIFICATE_OF_MEMBERSHIP)
|
d.get_bytes(proto_v1_field_name::network_config::CERTIFICATE_OF_MEMBERSHIP)
|
||||||
.ok_or(InvalidParameterError("missing certificate of membership"))?,
|
.ok_or(InvalidParameterError("missing certificate of membership"))?,
|
||||||
|
@ -446,6 +437,8 @@ pub struct SSOAuthConfiguration {
|
||||||
/// networks that support older protocol versions.
|
/// networks that support older protocol versions.
|
||||||
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
pub struct V1Credentials {
|
pub struct V1Credentials {
|
||||||
|
pub revision: u64,
|
||||||
|
pub max_delta: u64,
|
||||||
pub certificate_of_membership: CertificateOfMembership,
|
pub certificate_of_membership: CertificateOfMembership,
|
||||||
pub certificates_of_ownership: Vec<CertificateOfOwnership>,
|
pub certificates_of_ownership: Vec<CertificateOfOwnership>,
|
||||||
#[serde(skip_serializing_if = "HashMap::is_empty")]
|
#[serde(skip_serializing_if = "HashMap::is_empty")]
|
||||||
|
|
|
@ -25,7 +25,7 @@ use zerotier_utils::memory;
|
||||||
pub struct CertificateOfMembership {
|
pub struct CertificateOfMembership {
|
||||||
pub network_id: NetworkId,
|
pub network_id: NetworkId,
|
||||||
pub timestamp: i64,
|
pub timestamp: i64,
|
||||||
pub max_delta: i64,
|
pub max_delta: u64,
|
||||||
pub issued_to: Address,
|
pub issued_to: Address,
|
||||||
pub issued_to_fingerprint: Blob<32>,
|
pub issued_to_fingerprint: Blob<32>,
|
||||||
pub signature: ArrayVec<u8, { Identity::MAX_SIGNATURE_SIZE }>,
|
pub signature: ArrayVec<u8, { Identity::MAX_SIGNATURE_SIZE }>,
|
||||||
|
@ -34,7 +34,7 @@ pub struct CertificateOfMembership {
|
||||||
impl CertificateOfMembership {
|
impl CertificateOfMembership {
|
||||||
/// Create a new signed certificate of membership.
|
/// Create a new signed certificate of membership.
|
||||||
/// None is returned if an error occurs, such as the issuer missing its secrets.
|
/// None is returned if an error occurs, such as the issuer missing its secrets.
|
||||||
pub fn new(issuer: &Identity, network_id: NetworkId, issued_to: &Identity, timestamp: i64, max_delta: i64) -> Option<Self> {
|
pub fn new(issuer: &Identity, network_id: NetworkId, issued_to: &Identity, timestamp: i64, max_delta: u64) -> Option<Self> {
|
||||||
let mut com = CertificateOfMembership {
|
let mut com = CertificateOfMembership {
|
||||||
network_id,
|
network_id,
|
||||||
timestamp,
|
timestamp,
|
||||||
|
@ -128,7 +128,7 @@ impl CertificateOfMembership {
|
||||||
match qt {
|
match qt {
|
||||||
0 => {
|
0 => {
|
||||||
timestamp = i64::from_be_bytes(q);
|
timestamp = i64::from_be_bytes(q);
|
||||||
max_delta = qd as i64;
|
max_delta = qd;
|
||||||
}
|
}
|
||||||
1 => {
|
1 => {
|
||||||
network_id = u64::from_be_bytes(q);
|
network_id = u64::from_be_bytes(q);
|
||||||
|
|
|
@ -28,7 +28,7 @@ pub mod reaper;
|
||||||
pub use tokio;
|
pub use tokio;
|
||||||
|
|
||||||
/// A monotonic ticks value for "never happened" that should be lower than any initial value.
|
/// A monotonic ticks value for "never happened" that should be lower than any initial value.
|
||||||
pub const NEVER_HAPPENED_TICKS: i64 = i64::MIN / 2;
|
pub const NEVER_HAPPENED_TICKS: i64 = i64::MIN;
|
||||||
|
|
||||||
/// Get milliseconds since unix epoch.
|
/// Get milliseconds since unix epoch.
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -44,15 +44,19 @@ pub fn ms_since_epoch() -> i64 {
|
||||||
pub fn ms_monotonic() -> i64 {
|
pub fn ms_monotonic() -> i64 {
|
||||||
static STARTUP_INSTANT: std::sync::RwLock<Option<std::time::Instant>> = std::sync::RwLock::new(None);
|
static STARTUP_INSTANT: std::sync::RwLock<Option<std::time::Instant>> = std::sync::RwLock::new(None);
|
||||||
let si = *STARTUP_INSTANT.read().unwrap();
|
let si = *STARTUP_INSTANT.read().unwrap();
|
||||||
let instant_zero = if let Some(si) = si {
|
if let Some(si) = si {
|
||||||
si
|
si.elapsed().as_millis() as i64
|
||||||
} else {
|
} else {
|
||||||
*STARTUP_INSTANT.write().unwrap().get_or_insert(std::time::Instant::now())
|
STARTUP_INSTANT
|
||||||
};
|
.write()
|
||||||
std::time::Instant::now().duration_since(instant_zero).as_millis() as i64
|
.unwrap()
|
||||||
|
.get_or_insert(std::time::Instant::now())
|
||||||
|
.elapsed()
|
||||||
|
.as_millis() as i64
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wait for a kill signal (e.g. SIGINT or OS-equivalent) and return when received.
|
/// Wait for a kill signal (e.g. SIGINT or OS-equivalent) sent to this process and return when received.
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub fn wait_for_process_abort() {
|
pub fn wait_for_process_abort() {
|
||||||
if let Ok(mut signals) = signal_hook::iterator::Signals::new(&[libc::SIGINT, libc::SIGTERM, libc::SIGQUIT]) {
|
if let Ok(mut signals) = signal_hook::iterator::Signals::new(&[libc::SIGINT, libc::SIGTERM, libc::SIGQUIT]) {
|
||||||
|
@ -65,10 +69,10 @@ pub fn wait_for_process_abort() {
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
std::thread::sleep(std::time::Duration::from_millis(500));
|
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
panic!("unable to listen to OS signals");
|
panic!("unable to listen for OS signals");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ use std::ptr::{drop_in_place, read, write};
|
||||||
/// This will panic if the capacity is too small. If that occurs, it must be enlarged. It will
|
/// This will panic if the capacity is too small. If that occurs, it must be enlarged. It will
|
||||||
/// also panic if any of the accessors (other than the try_ versions) are used to try to get
|
/// also panic if any of the accessors (other than the try_ versions) are used to try to get
|
||||||
/// a type other than the one it was constructed with.
|
/// a type other than the one it was constructed with.
|
||||||
|
#[repr(C)]
|
||||||
pub struct Thing<const CAPACITY: usize> {
|
pub struct Thing<const CAPACITY: usize> {
|
||||||
storage: [u8; CAPACITY],
|
storage: [u8; CAPACITY],
|
||||||
dropper: fn(*mut u8),
|
dropper: fn(*mut u8),
|
||||||
|
|
Loading…
Add table
Reference in a new issue