Skip to content

Commit

Permalink
Resource ID refactor partially complete, fixed not-invented-here synd…
Browse files Browse the repository at this point in the history
…rome around semver
  • Loading branch information
NotGyro committed Sep 10, 2024
1 parent a1d9786 commit 4f0be3a
Show file tree
Hide file tree
Showing 13 changed files with 208 additions and 422 deletions.
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions gestalt-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ once_cell = "1.17"
parking_lot = "0.12.0"
rand = "^0.8"
rand_core = "0.6.4"
semver = { version = "1.0.23", features = ["default", "std", "serde"]}
sha2 = "0.10.1"
string_cache = "0.8"
thiserror = "1.0.30"
Expand Down
18 changes: 12 additions & 6 deletions gestalt-core/src/common/identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,11 @@ pub type SignatureError = ed25519::signature::Error;

#[repr(C)]
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Hash)]
pub struct NodeIdentity([u8; PUBLIC_KEY_LENGTH]);
pub struct PublicKey(pub [u8; PUBLIC_KEY_LENGTH]);

impl NodeIdentity {
pub type NodeIdentity = PublicKey;

impl PublicKey {
pub fn get_bytes(&self) -> &[u8] {
&self.0
}
Expand All @@ -57,7 +59,7 @@ impl NodeIdentity {
if bytes.len() == PUBLIC_KEY_LENGTH {
let mut smaller_buf = [0u8; PUBLIC_KEY_LENGTH];
smaller_buf.copy_from_slice(&bytes[0..PUBLIC_KEY_LENGTH]);
Ok(NodeIdentity(smaller_buf))
Ok(PublicKey(smaller_buf))
} else {
Err(DecodeIdentityError::WrongLength(bytes.len()))
}
Expand All @@ -72,7 +74,11 @@ impl NodeIdentity {
converted_key.verify(message, &converted_signature)
}
}

impl From<[u8; PUBLIC_KEY_LENGTH]> for NodeIdentity {
fn from(value: [u8; PUBLIC_KEY_LENGTH]) -> Self {
PublicKey(value)
}
}
impl std::fmt::Debug for NodeIdentity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "NodeIdentity({})", self.to_base64())
Expand All @@ -87,7 +93,7 @@ impl From<&NodeIdentity> for ed25519_dalek::VerifyingKey {

impl From<&ed25519_dalek::VerifyingKey> for NodeIdentity {
fn from(value: &ed25519_dalek::VerifyingKey) -> Self {
NodeIdentity(value.to_bytes())
PublicKey(value.to_bytes())
}
}

Expand Down Expand Up @@ -334,7 +340,7 @@ impl KeyFile {
}

let private = PrivateKey(priv_key_bytes);
let public = NodeIdentity(pub_key_bytes);
let public = NodeIdentity::from(pub_key_bytes);
Ok(IdentityKeyPair { public, private })
}
}
Expand Down
283 changes: 26 additions & 257 deletions gestalt-core/src/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@ pub mod message;
pub mod voxelmath;
pub mod directories;

use core::str;
use std::{
collections::{HashMap, HashSet},
fmt::Display,
future::Future,
marker::PhantomData,
pin::Pin,
pin::Pin, ptr,
};

use log::warn;
use semver::Version;
use serde::{Deserialize, Serialize};
use xxhash_rust::xxh3::Xxh3Builder;

Expand Down Expand Up @@ -114,213 +116,6 @@ pub fn new_fast_hash_set<T>() -> FastHashSet<T> {
HashSet::with_hasher(Xxh3Builder::new())
}

// Any HashMap which does not need to be resistant against HashDos / collision attacks.
// pub type FastHash =

macro_rules! version {
($major:expr,$minor:expr,$patch:expr,$build:expr) => {
crate::common::Version::new($major as u32, $minor as u32, $patch as u32, $build as u32)
};
($major:expr,$minor:expr,$patch:expr) => {
crate::common::Version::new($major as u32, $minor as u32, $patch as u32, 0u32)
};
($major:expr,$minor:expr) => {
crate::common::Version::new($major as u32, $minor as u32, 0u32, 0u32)
};
}

///Array of 4 u32s stored in little-endian byte order (least significant to most): build, patch, minor, major.
#[derive(Copy, Clone, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[repr(transparent)]
pub struct Version {
inner: u128,
}

impl Version {
pub const fn new(major: u32, minor: u32, patch: u32, build: u32) -> Self {
let major_bytes = major.to_le_bytes();
let minor_bytes = minor.to_le_bytes();
let patch_bytes = patch.to_le_bytes();
let build_bytes = build.to_le_bytes();

//Little endian implies least-significant first.
//Every helper function that makes this easier is incompatible with const fn, so we have to do this the silly way.
let result_bytes: [u8; 16] = [
build_bytes[0],
build_bytes[1],
build_bytes[2],
build_bytes[3],
patch_bytes[0],
patch_bytes[1],
patch_bytes[2],
patch_bytes[3],
minor_bytes[0],
minor_bytes[1],
minor_bytes[2],
minor_bytes[3],
major_bytes[0],
major_bytes[1],
major_bytes[2],
major_bytes[3],
];

Version {
inner: u128::from_le_bytes(result_bytes),
}
}
pub fn from_bytes(bytes: &[u8; 16]) -> Self {
Version {
inner: u128::from_le_bytes(*bytes),
}
}
pub const fn as_bytes(&self) -> [u8; 16] {
self.inner.to_le_bytes()
}
pub const fn major(&self) -> u32 {
let bytes = &self.inner.to_le_bytes();
let result_bytes = [bytes[12], bytes[13], bytes[14], bytes[15]];
u32::from_le_bytes(result_bytes)
}
pub const fn minor(&self) -> u32 {
let bytes = &self.inner.to_le_bytes();
let result_bytes = [bytes[8], bytes[9], bytes[10], bytes[11]];
u32::from_le_bytes(result_bytes)
}
pub const fn patch(&self) -> u32 {
let bytes = &self.inner.to_le_bytes();
let result_bytes = [bytes[4], bytes[5], bytes[6], bytes[7]];
u32::from_le_bytes(result_bytes)
}
pub const fn build(&self) -> u32 {
let bytes = &self.inner.to_le_bytes();
let result_bytes = [bytes[0], bytes[1], bytes[2], bytes[3]];
u32::from_le_bytes(result_bytes)
}
pub fn parse(value: &str) -> std::result::Result<Self, ParseVersionError> {
let mut in_progress = value.to_ascii_lowercase();
//Elide all of the unnecessary stuff.
in_progress.remove_matches("build");
in_progress.remove_matches("v");
in_progress.remove_matches("version");

//Make sure it contains at least one separator character.
if !(in_progress.contains('.') || in_progress.contains(':') || in_progress.contains('-')) {
return Err(ParseVersionError::NoSeparators(value.to_string()));
}
let split = in_progress.split(|c| {
let pattern = ['.', ':', '-', '(', ')', '[', ']'];
pattern.contains(&c) || c.is_whitespace()
});
//Make sure none of these are just the space between two separators for some reason.
let mut fields: Vec<&str> = split.filter(|val| !(*val).is_empty()).collect();
if fields.len() < 3 {
return Err(ParseVersionError::TooShort(value.to_string()));
} else if fields.len() > 4 {
//Gestalt engine does not use version information more detailed than build number.
fields.truncate(4);
}

//Internal method to convert a field of the string to a version field, to avoid repetition.
fn number_from_field(
field: &str,
original_string: String,
) -> Result<u32, ParseVersionError> {
let big_number = field.parse::<u128>().map_err(|_e| {
ParseVersionError::NotNumber(field.to_string(), original_string.clone())
})?;
if big_number > (u32::MAX as u128) {
return Err(ParseVersionError::TooBig(original_string, big_number));
}
//Truncate
Ok(big_number as u32)
}

//We have just ensured there are at least three fields. We can unwrap here.
let major: u32 = number_from_field(*(fields.get(0).unwrap()), value.to_string())?;
let minor: u32 = number_from_field(*(fields.get(1).unwrap()), value.to_string())?;
let patch: u32 = number_from_field(*(fields.get(2).unwrap()), value.to_string())?;
//Build is optional.
if fields.len() < 4 {
Ok(version!(major, minor, patch))
} else {
let build: u32 = number_from_field(*(fields.get(3).unwrap()), value.to_string())?;
Ok(version!(major, minor, patch, build))
}
}
}

#[derive(thiserror::Error, Debug, Clone)]
pub enum ParseVersionError {
#[error("tried to parse {0} into a version but it contained no valid separators")]
NoSeparators(String),
#[error("tried to parse {0} into a version but there were 3 or fewer fields")]
TooShort(String),
#[error("could not parse `{0}` in version string {1} as a version field: Not a number")]
NotNumber(String, String),
#[error("version `{0}` contained {1} as a version string which is larger than the u32 maximum and not permitted")]
TooBig(String, u128),
}

impl Display for Version {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let build = self.build();
if build == 0 {
write!(f, "{}.{}.{}", self.major(), self.minor(), self.patch())
} else {
write!(f, "{}.{}.{}-build{}", self.major(), self.minor(), self.patch(), build)
}
}
}
impl std::fmt::Debug for Version {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Version({})", self)
}
}

// Used for serializing and deserializing for Serde
// for example in a `#[serde(with = "crate::common::version_string")]` attribute
pub mod version_string {
use std::fmt;

use serde::{
de::{self, Visitor},
Deserializer, Serializer,
};

use super::*;

struct VersionVisitor;

impl<'de> Visitor<'de> for VersionVisitor {
type Value = Version;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a version string with 3 or 4 delimited fields for major.minor.patch or major.minor.patch.build i.e. \"1.12.2-build33\"")
}

fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
Version::parse(v).map_err(E::custom)
}
}

pub fn serialize<S>(val: &Version, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(val.to_string().as_str())
}

pub fn deserialize<'de, D>(deserializer: D) -> Result<Version, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_string(VersionVisitor {})
}
}

/// Option-like semantics entirely within the type system.
/// The compiler MAY optimize to this anyway, but this is a way to be sure if you'd
/// prefer to have, for example, two different methods emitted by codegen for the Some
Expand Down Expand Up @@ -390,54 +185,28 @@ pub const fn byte_to_hex(value: u8) -> &'static str {
HEX_TABLE[value as usize]
}

#[test]
fn version_order_correct() {
let ver_new = Version::new(20, 2, 3, 64);
let ver_old = Version::new(1, 1, 1, 20000);

assert!(ver_new > ver_old);
}

#[test]
fn version_macro() {
let a = version!(2, 10, 1);
assert_eq!(a.major(), 2);
assert_eq!(a.minor(), 10);
assert_eq!(a.patch(), 1);
}

#[test]
fn version_to_string() {
let ver = version!(20, 1, 2);
let stringified = ver.to_string();
assert_eq!(stringified.as_str(), "20.1.2");
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct FixedString([u8; 8]);

let ver2 = version!(13, 13, 2, 242);
let stringified2 = ver2.to_string();
assert_eq!(stringified2.as_str(), "13.13.2-build242");
}

#[test]
fn test_parse_version() {
let stringy = "v0.1.12";
let ver = Version::parse(stringy).unwrap();
assert_eq!(ver.major(), 0);
assert_eq!(ver.minor(), 1);
assert_eq!(ver.patch(), 12);

let stringy_with_build = "v7.20.1-build18";
let ver_with_build = Version::parse(stringy_with_build).unwrap();

assert_eq!(ver_with_build.major(), 7);
assert_eq!(ver_with_build.minor(), 20);
assert_eq!(ver_with_build.patch(), 1);
assert_eq!(ver_with_build.build(), 18);
impl FixedString {
pub fn as_str<'a>(&'a self) -> &'a str {
unsafe { str::from_raw_parts(ptr::from_ref(&self.0[0]), 8) }
}
pub fn from_str(value: &str) -> Self {
if value.len() > 8 {
warn!("Constructing a FixedString out of {value} which is {0} bytes long, truncating to first 8", value.len());
}
let mut out = [0; 8];
let end_copy = value.len().min(8);
let slice = &value.as_bytes()[0..end_copy];
out.copy_from_slice(slice);

//Gracefully interpret weird version numbers
let terrible_version_string = "v20 - 19 - 19 :: BUILD(01)";
let ver_cleaned = Version::parse(terrible_version_string).unwrap();
assert_eq!(ver_cleaned.major(), 20);
assert_eq!(ver_cleaned.minor(), 19);
assert_eq!(ver_cleaned.patch(), 19);
assert_eq!(ver_cleaned.build(), 1);
Self(out)
}
pub const fn from_const(value: &'static str) -> Self {
let bytes = value.as_bytes();
assert!(bytes.len() == 8);

Self([bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7]])
}
}
Loading

0 comments on commit 4f0be3a

Please sign in to comment.