mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-06-08 13:33:44 +02:00
Getting there on V2 controller!!!
This commit is contained in:
parent
9e6617b324
commit
bc31c35ae8
12 changed files with 157 additions and 144 deletions
|
@ -8,7 +8,9 @@ use tokio::time::{Duration, Instant};
|
|||
|
||||
use zerotier_network_hypervisor::protocol;
|
||||
use zerotier_network_hypervisor::protocol::{PacketBuffer, DEFAULT_MULTICAST_LIMIT, ZEROTIER_VIRTUAL_NETWORK_DEFAULT_MTU};
|
||||
use zerotier_network_hypervisor::vl1::{HostSystem, Identity, InnerProtocol, Node, PacketHandlerResult, Path, PathFilter, Peer};
|
||||
use zerotier_network_hypervisor::vl1::{
|
||||
debug_event, HostSystem, Identity, InnerProtocol, Node, PacketHandlerResult, Path, PathFilter, Peer,
|
||||
};
|
||||
use zerotier_network_hypervisor::vl2;
|
||||
use zerotier_network_hypervisor::vl2::networkconfig::*;
|
||||
use zerotier_network_hypervisor::vl2::v1::Revocation;
|
||||
|
@ -138,7 +140,6 @@ impl Controller {
|
|||
Ok(())
|
||||
},
|
||||
);
|
||||
// TODO: log errors
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -343,7 +344,7 @@ impl PathFilter for Controller {}
|
|||
impl InnerProtocol for Controller {
|
||||
fn handle_packet<HostSystemImpl: HostSystem + ?Sized>(
|
||||
&self,
|
||||
_: &HostSystemImpl,
|
||||
host_system: &HostSystemImpl,
|
||||
_: &Node,
|
||||
source: &Arc<Peer>,
|
||||
source_path: &Arc<Path>,
|
||||
|
@ -365,6 +366,14 @@ impl InnerProtocol for Controller {
|
|||
}
|
||||
let network_id = network_id.unwrap();
|
||||
|
||||
debug_event!(
|
||||
host_system,
|
||||
"[vl2] NETWORK_CONFIG_REQUEST from {}({}) for {:0>16x}",
|
||||
source.identity.address.to_string(),
|
||||
source_path.endpoint.to_string(),
|
||||
u64::from(network_id)
|
||||
);
|
||||
|
||||
let meta_data = if (cursor + 2) < payload.len() {
|
||||
let meta_data_len = payload.read_u16(&mut cursor);
|
||||
if meta_data_len.is_err() {
|
||||
|
@ -404,6 +413,7 @@ impl InnerProtocol for Controller {
|
|||
let node_id = peer.identity.address;
|
||||
let node_fingerprint = Blob::from(peer.identity.fingerprint);
|
||||
let now = ms_since_epoch();
|
||||
let _host = self2.service.read().unwrap().clone().upgrade().unwrap();
|
||||
|
||||
let (result, config) = match self2.get_network_config(&peer.identity, network_id, now).await {
|
||||
Result::Ok((result, Some(config), revocations)) => {
|
||||
|
@ -411,8 +421,8 @@ impl InnerProtocol for Controller {
|
|||
(result, Some(config))
|
||||
}
|
||||
Result::Ok((result, None, _)) => (result, None),
|
||||
Result::Err(_) => {
|
||||
// TODO: log invalid request or internal error
|
||||
Result::Err(e) => {
|
||||
debug_event!(_host, "[vl2] ERROR getting network config: {}", e.to_string());
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
@ -492,3 +502,7 @@ impl Drop for Controller {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dump_network_config(nc: &NetworkConfig) {
|
||||
println!("{}", serde_yaml::to_string(nc).unwrap());
|
||||
}
|
||||
|
|
|
@ -86,7 +86,7 @@ impl FileDatabase {
|
|||
}
|
||||
|
||||
fn network_path(&self, network_id: NetworkId) -> PathBuf {
|
||||
self.base_path.join(format!("N{:06x}", network_id.network_no())).join("config.yaml")
|
||||
self.base_path.join(format!("N{:06x}.yaml", network_id.network_no()))
|
||||
}
|
||||
|
||||
fn member_path(&self, network_id: NetworkId, member_id: Address) -> PathBuf {
|
||||
|
@ -130,12 +130,24 @@ impl Database for FileDatabase {
|
|||
let r = fs::read(self.network_path(id)).await;
|
||||
if let Ok(raw) = r {
|
||||
let mut network = serde_yaml::from_slice::<Network>(raw.as_slice())?;
|
||||
self.get_controller_address()
|
||||
.map(|a| network.id = network.id.change_network_controller(a));
|
||||
Ok(Some(network))
|
||||
//Ok(Some(serde_json::from_slice::<Network>(raw.as_slice())?))
|
||||
|
||||
// FileDatabase stores networks by their "network number" and automatically adapts their IDs
|
||||
// if the controller's identity changes. This is done to make it easy to just clone networks,
|
||||
// including storing them in "git."
|
||||
if let Some(controller_address) = self.get_controller_address() {
|
||||
let network_id_should_be = network.id.change_network_controller(controller_address);
|
||||
if id != network_id_should_be {
|
||||
return Ok(None);
|
||||
}
|
||||
if network.id != network_id_should_be {
|
||||
network.id = network_id_should_be;
|
||||
let _ = self.save_network(network.clone()).await;
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(Some(network));
|
||||
} else {
|
||||
Ok(None)
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,8 +8,6 @@ use serde::{Deserialize, Serialize};
|
|||
use zerotier_network_hypervisor::vl1::{Address, Identity, InetAddress};
|
||||
use zerotier_network_hypervisor::vl2::NetworkId;
|
||||
|
||||
use crate::model::ObjectType;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Member {
|
||||
#[serde(rename = "address")]
|
||||
|
@ -70,11 +68,6 @@ pub struct Member {
|
|||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(default)]
|
||||
pub advertised: Option<bool>,
|
||||
|
||||
/// API object type documentation field, not actually edited/used.
|
||||
#[serde(skip_deserializing)]
|
||||
#[serde(default = "ObjectType::member")]
|
||||
pub objtype: ObjectType,
|
||||
}
|
||||
|
||||
impl Member {
|
||||
|
@ -92,7 +85,6 @@ impl Member {
|
|||
tags: HashMap::new(),
|
||||
sso_exempt: None,
|
||||
advertised: None,
|
||||
objtype: ObjectType::Member,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,24 +22,6 @@ pub struct NetworkExport {
|
|||
pub members: HashMap<Address, Member>,
|
||||
}
|
||||
|
||||
/// Static string included in JSON-serializable objects to indicate their object type through the API.
|
||||
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum ObjectType {
|
||||
#[serde(rename = "network")]
|
||||
Network,
|
||||
#[serde(rename = "member")]
|
||||
Member,
|
||||
}
|
||||
|
||||
impl ObjectType {
|
||||
fn network() -> ObjectType {
|
||||
Self::Network
|
||||
}
|
||||
fn member() -> ObjectType {
|
||||
Self::Member
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[repr(u8)]
|
||||
pub enum AuthorizationResult {
|
||||
|
|
|
@ -11,7 +11,7 @@ use zerotier_network_hypervisor::vl2::rule::Rule;
|
|||
use zerotier_network_hypervisor::vl2::NetworkId;
|
||||
|
||||
use crate::database::Database;
|
||||
use crate::model::{Member, ObjectType};
|
||||
use crate::model::Member;
|
||||
|
||||
pub const CREDENTIAL_WINDOW_SIZE_DEFAULT: i64 = 1000 * 60 * 60;
|
||||
|
||||
|
@ -62,14 +62,16 @@ pub struct Network {
|
|||
pub enable_broadcast: Option<bool>,
|
||||
|
||||
/// Auto IP assignment mode(s) for IPv4 addresses.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(rename = "v4AssignMode")]
|
||||
#[serde(default)]
|
||||
pub v4_assign_mode: Ipv4AssignMode,
|
||||
pub v4_assign_mode: Option<Ipv4AssignMode>,
|
||||
|
||||
/// Auto IP assignment mode(s) for IPv6 addresses.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(rename = "v6AssignMode")]
|
||||
#[serde(default)]
|
||||
pub v6_assign_mode: Ipv6AssignMode,
|
||||
pub v6_assign_mode: Option<Ipv6AssignMode>,
|
||||
|
||||
/// IPv4 or IPv6 auto-assignment pools available, must be present to use 'zt' mode.
|
||||
#[serde(skip_serializing_if = "HashSet::is_empty")]
|
||||
|
@ -78,11 +80,13 @@ pub struct Network {
|
|||
pub ip_assignment_pools: HashSet<IpAssignmentPool>,
|
||||
|
||||
/// IPv4 or IPv6 routes to advertise.
|
||||
#[serde(rename = "ipRoutes")]
|
||||
#[serde(skip_serializing_if = "HashSet::is_empty")]
|
||||
#[serde(default)]
|
||||
pub ip_routes: HashSet<IpRoute>,
|
||||
|
||||
/// DNS records to push to members.
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "HashMap::is_empty")]
|
||||
pub dns: HashMap<String, HashSet<InetAddress>>,
|
||||
|
||||
|
@ -116,14 +120,10 @@ pub struct Network {
|
|||
pub private: bool,
|
||||
|
||||
/// If true this network will add not-authorized members for anyone who requests a config.
|
||||
#[serde(rename = "learnMembers")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(default)]
|
||||
pub learn_members: Option<bool>,
|
||||
|
||||
/// Static object type field for use with API.
|
||||
#[serde(skip_deserializing)]
|
||||
#[serde(default = "ObjectType::network")]
|
||||
pub objtype: ObjectType,
|
||||
}
|
||||
|
||||
impl Hash for Network {
|
||||
|
@ -150,7 +150,7 @@ impl Network {
|
|||
pub async fn check_zt_ip_assignments<DatabaseImpl: Database + ?Sized>(&self, database: &DatabaseImpl, member: &mut Member) -> bool {
|
||||
let mut modified = false;
|
||||
|
||||
if self.v4_assign_mode.zt {
|
||||
if self.v4_assign_mode.as_ref().map_or(false, |m| m.zt) {
|
||||
if !member.ip_assignments.iter().any(|ip| ip.is_ipv4()) {
|
||||
'ip_search: for pool in self.ip_assignment_pools.iter() {
|
||||
if pool.ip_range_start.is_ipv4() && pool.ip_range_end.is_ipv4() {
|
||||
|
@ -178,7 +178,7 @@ impl Network {
|
|||
}
|
||||
}
|
||||
|
||||
if self.v6_assign_mode.zt {
|
||||
if self.v6_assign_mode.as_ref().map_or(false, |m| m.zt) {
|
||||
if !member.ip_assignments.iter().any(|ip| ip.is_ipv6()) {
|
||||
'ip_search: for pool in self.ip_assignment_pools.iter() {
|
||||
if pool.ip_range_start.is_ipv6() && pool.ip_range_end.is_ipv6() {
|
||||
|
|
|
@ -535,9 +535,6 @@ pub struct ErrorHeader {
|
|||
pub error_code: u8,
|
||||
}
|
||||
|
||||
/// Maximum delta between the message ID of a sent packet and its response.
|
||||
pub(crate) const PACKET_RESPONSE_COUNTER_DELTA_MAX: u64 = 256;
|
||||
|
||||
/// Frequency for WHOIS retries in milliseconds.
|
||||
pub(crate) const WHOIS_RETRY_INTERVAL: i64 = 2000;
|
||||
|
||||
|
|
|
@ -26,17 +26,20 @@ pub use rootset::{Root, RootSet};
|
|||
|
||||
#[cfg(feature = "debug_events")]
|
||||
#[allow(unused_macros)]
|
||||
#[macro_export]
|
||||
macro_rules! debug_event {
|
||||
($si:expr, $fmt:expr $(, $($arg:tt)*)?) => {
|
||||
$si.event(crate::vl1::Event::Debug(file!(), line!(), format!($fmt, $($($arg)*)?)));
|
||||
use $crate::vl1::Event;
|
||||
$si.event(Event::Debug(file!(), line!(), format!($fmt, $($($arg)*)?)));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "debug_events"))]
|
||||
#[allow(unused_macros)]
|
||||
#[macro_export]
|
||||
macro_rules! debug_event {
|
||||
($si:expr, $fmt:expr $(, $($arg:tt)*)?) => {};
|
||||
}
|
||||
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use debug_event;
|
||||
pub use debug_event;
|
||||
|
|
|
@ -408,6 +408,7 @@ impl Node {
|
|||
root_spam_hello = !self.is_online();
|
||||
}
|
||||
|
||||
/*
|
||||
debug_event!(
|
||||
host_system,
|
||||
"[vl1] do_background_tasks:{}{}{}{}{}{} ----",
|
||||
|
@ -442,6 +443,7 @@ impl Node {
|
|||
""
|
||||
}
|
||||
);
|
||||
*/
|
||||
|
||||
if root_sync {
|
||||
if {
|
||||
|
@ -711,7 +713,7 @@ impl Node {
|
|||
}
|
||||
}
|
||||
|
||||
debug_event!(host_system, "[vl1] do_background_tasks DONE ----");
|
||||
//debug_event!(host_system, "[vl1] do_background_tasks DONE ----");
|
||||
INTERVAL
|
||||
}
|
||||
|
||||
|
@ -910,7 +912,6 @@ impl Node {
|
|||
waiting_packet: Option<(Weak<Path>, PooledPacketBuffer)>,
|
||||
time_ticks: i64,
|
||||
) {
|
||||
debug_event!(host_system, "[vl1] [v1] WHOIS {}", address.to_string());
|
||||
{
|
||||
let mut whois_queue = self.whois_queue.lock().unwrap();
|
||||
let qi = whois_queue.entry(address).or_insert_with(|| WhoisQueueItem {
|
||||
|
@ -934,6 +935,16 @@ impl Node {
|
|||
/// Send a WHOIS query to the current best root.
|
||||
fn send_whois<HostSystemImpl: HostSystem + ?Sized>(&self, host_system: &HostSystemImpl, mut addresses: &[Address], time_ticks: i64) {
|
||||
debug_assert!(!addresses.is_empty());
|
||||
debug_event!(host_system, "[vl1] [v1] sending WHOIS for {}", {
|
||||
let mut tmp = String::new();
|
||||
for a in addresses.iter() {
|
||||
if !tmp.is_empty() {
|
||||
tmp.push(',');
|
||||
}
|
||||
tmp.push_str(a.to_string().as_str());
|
||||
}
|
||||
tmp
|
||||
});
|
||||
if let Some(root) = self.best_root() {
|
||||
while !addresses.is_empty() {
|
||||
if !root
|
||||
|
|
|
@ -671,23 +671,24 @@ impl Peer {
|
|||
let mut cursor = 0;
|
||||
if let Ok(error_header) = payload.read_struct::<ErrorHeader>(&mut cursor) {
|
||||
let in_re_message_id: MessageId = u64::from_be_bytes(error_header.in_re_message_id);
|
||||
if self.message_id_counter.load(Ordering::Relaxed).wrapping_sub(in_re_message_id) <= PACKET_RESPONSE_COUNTER_DELTA_MAX {
|
||||
match error_header.in_re_verb {
|
||||
_ => {
|
||||
return inner.handle_error(
|
||||
host_system,
|
||||
node,
|
||||
self,
|
||||
&source_path,
|
||||
source_hops,
|
||||
message_id,
|
||||
error_header.in_re_verb,
|
||||
in_re_message_id,
|
||||
error_header.error_code,
|
||||
payload,
|
||||
cursor,
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: replay attack prevention filter
|
||||
|
||||
match error_header.in_re_verb {
|
||||
_ => {
|
||||
return inner.handle_error(
|
||||
host_system,
|
||||
node,
|
||||
self,
|
||||
&source_path,
|
||||
source_hops,
|
||||
message_id,
|
||||
error_header.in_re_verb,
|
||||
in_re_message_id,
|
||||
error_header.error_code,
|
||||
payload,
|
||||
cursor,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -709,86 +710,88 @@ impl Peer {
|
|||
let mut cursor = 0;
|
||||
if let Ok(ok_header) = payload.read_struct::<OkHeader>(&mut cursor) {
|
||||
let in_re_message_id: MessageId = u64::from_ne_bytes(ok_header.in_re_message_id);
|
||||
if self.message_id_counter.load(Ordering::Relaxed).wrapping_sub(in_re_message_id) <= PACKET_RESPONSE_COUNTER_DELTA_MAX {
|
||||
match ok_header.in_re_verb {
|
||||
verbs::VL1_HELLO => {
|
||||
if let Ok(_ok_hello_fixed_header_fields) =
|
||||
payload.read_struct::<v1::message_component_structs::OkHelloFixedHeaderFields>(&mut cursor)
|
||||
{
|
||||
if source_hops == 0 {
|
||||
debug_event!(host_system, "[vl1] {} OK(HELLO)", self.identity.address.to_string(),);
|
||||
if let Ok(reported_endpoint) = Endpoint::unmarshal(&payload, &mut cursor) {
|
||||
|
||||
// TODO: replay attack prevention filter
|
||||
|
||||
match ok_header.in_re_verb {
|
||||
verbs::VL1_HELLO => {
|
||||
if let Ok(_ok_hello_fixed_header_fields) =
|
||||
payload.read_struct::<v1::message_component_structs::OkHelloFixedHeaderFields>(&mut cursor)
|
||||
{
|
||||
if source_hops == 0 {
|
||||
debug_event!(host_system, "[vl1] {} OK(HELLO)", self.identity.address.to_string(),);
|
||||
if let Ok(reported_endpoint) = Endpoint::unmarshal(&payload, &mut cursor) {
|
||||
#[cfg(debug_assertions)]
|
||||
let reported_endpoint2 = reported_endpoint.clone();
|
||||
if self
|
||||
.remote_node_info
|
||||
.write()
|
||||
.unwrap()
|
||||
.reported_local_endpoints
|
||||
.insert(reported_endpoint, time_ticks)
|
||||
.is_none()
|
||||
{
|
||||
#[cfg(debug_assertions)]
|
||||
let reported_endpoint2 = reported_endpoint.clone();
|
||||
if self
|
||||
.remote_node_info
|
||||
.write()
|
||||
.unwrap()
|
||||
.reported_local_endpoints
|
||||
.insert(reported_endpoint, time_ticks)
|
||||
.is_none()
|
||||
{
|
||||
#[cfg(debug_assertions)]
|
||||
debug_event!(
|
||||
host_system,
|
||||
"[vl1] {} reported new remote perspective, local endpoint: {}",
|
||||
self.identity.address.to_string(),
|
||||
reported_endpoint2.to_string()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if source_hops == 0 && !path_is_known {
|
||||
self.learn_path(host_system, source_path, time_ticks);
|
||||
}
|
||||
|
||||
self.last_hello_reply_time_ticks.store(time_ticks, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
verbs::VL1_WHOIS => {
|
||||
if node.is_peer_root(self) {
|
||||
while cursor < payload.len() {
|
||||
let r = Identity::unmarshal(payload, &mut cursor);
|
||||
if let Ok(received_identity) = r {
|
||||
debug_event!(
|
||||
host_system,
|
||||
"[vl1] {} OK(WHOIS): new identity: {}",
|
||||
"[vl1] {} reported new remote perspective, local endpoint: {}",
|
||||
self.identity.address.to_string(),
|
||||
received_identity.to_string()
|
||||
reported_endpoint2.to_string()
|
||||
);
|
||||
node.handle_incoming_identity(host_system, inner, received_identity, time_ticks, true);
|
||||
} else {
|
||||
debug_event!(
|
||||
host_system,
|
||||
"[vl1] {} OK(WHOIS): bad identity: {}",
|
||||
self.identity.address.to_string(),
|
||||
r.err().unwrap().to_string()
|
||||
);
|
||||
return PacketHandlerResult::Error;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return PacketHandlerResult::Ok; // not invalid, just ignored
|
||||
}
|
||||
}
|
||||
|
||||
_ => {
|
||||
return inner.handle_ok(
|
||||
host_system,
|
||||
node,
|
||||
self,
|
||||
&source_path,
|
||||
source_hops,
|
||||
message_id,
|
||||
ok_header.in_re_verb,
|
||||
in_re_message_id,
|
||||
payload,
|
||||
cursor,
|
||||
);
|
||||
if source_hops == 0 && !path_is_known {
|
||||
self.learn_path(host_system, source_path, time_ticks);
|
||||
}
|
||||
|
||||
self.last_hello_reply_time_ticks.store(time_ticks, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
verbs::VL1_WHOIS => {
|
||||
debug_event!(host_system, "[vl1] OK(WHOIS)");
|
||||
if node.is_peer_root(self) {
|
||||
while cursor < payload.len() {
|
||||
let r = Identity::unmarshal(payload, &mut cursor);
|
||||
if let Ok(received_identity) = r {
|
||||
debug_event!(
|
||||
host_system,
|
||||
"[vl1] {} OK(WHOIS): received identity: {}",
|
||||
self.identity.address.to_string(),
|
||||
received_identity.to_string()
|
||||
);
|
||||
node.handle_incoming_identity(host_system, inner, received_identity, time_ticks, true);
|
||||
} else {
|
||||
debug_event!(
|
||||
host_system,
|
||||
"[vl1] {} OK(WHOIS): received bad identity: {}",
|
||||
self.identity.address.to_string(),
|
||||
r.err().unwrap().to_string()
|
||||
);
|
||||
return PacketHandlerResult::Error;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return PacketHandlerResult::Ok; // not invalid, just ignored
|
||||
}
|
||||
}
|
||||
|
||||
_ => {
|
||||
return inner.handle_ok(
|
||||
host_system,
|
||||
node,
|
||||
self,
|
||||
&source_path,
|
||||
source_hops,
|
||||
message_id,
|
||||
ok_header.in_re_verb,
|
||||
in_re_message_id,
|
||||
payload,
|
||||
cursor,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return PacketHandlerResult::Error;
|
||||
|
|
|
@ -56,8 +56,7 @@ impl NetworkId {
|
|||
|
||||
/// Consume this network ID and return one with the same network number but a different controller ID.
|
||||
pub fn change_network_controller(self, new_controller: Address) -> NetworkId {
|
||||
let new_controller: u64 = new_controller.into();
|
||||
Self(NonZeroU64::new((self.network_no() as u64) | new_controller.wrapping_shr(24)).unwrap())
|
||||
Self(NonZeroU64::new((self.network_no() as u64) | u64::from(new_controller).wrapping_shl(24)).unwrap())
|
||||
}
|
||||
|
||||
/// Get the 24-bit local network identifier minus the 40-bit controller address portion.
|
||||
|
|
|
@ -85,7 +85,7 @@ impl CertificateOfMembership {
|
|||
let mut fp = v1_signee_hasher.finish();
|
||||
fp[32..].fill(0);
|
||||
if let Some(signed_by) = signed_by {
|
||||
fp[32..38].copy_from_slice(&signed_by.to_bytes());
|
||||
fp[32..37].copy_from_slice(&signed_by.to_bytes());
|
||||
}
|
||||
fp
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@ impl CertificateOfOwnership {
|
|||
}
|
||||
|
||||
fn internal_to_bytes(&self, for_sign: bool, signed_by: Address) -> Option<Vec<u8>> {
|
||||
if self.things.len() > 0xffff || self.signature.len() != 96 {
|
||||
if self.things.len() > 0xffff {
|
||||
return None;
|
||||
}
|
||||
let mut v = Vec::with_capacity(256);
|
||||
|
|
Loading…
Add table
Reference in a new issue