Skip to content

Increase strictness / correctness of URI micro form validation #18

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
3182423
Added checks and failure paths if UEntity and UResource do not have t…
PLeVasseur Dec 21, 2023
b27d63a
Cleaned up UEntity and UResource functions checking on overflow to re…
PLeVasseur Dec 25, 2023
2388cd5
Ran cargo fmt.
PLeVasseur Dec 25, 2023
5f03557
Worked the check is_micro_form() to use the check on small enough to …
PLeVasseur Dec 25, 2023
0d32bca
Added tests in urivalidator for new cases where is_micro_form() could…
PLeVasseur Dec 25, 2023
4aec055
Addressed TODO regarding check for fully resolved URI.
PLeVasseur Dec 25, 2023
b67a74d
Addressed UEntity MAJOR version overflow of allotted 8 bits.
PLeVasseur Dec 25, 2023
156d5ff
Added check for UAuthority ID and IP compliance for is_micro_form(). …
PLeVasseur Dec 25, 2023
6741bee
Revised MicroUriSerializer impl of serialize to remove now unnecessar…
PLeVasseur Dec 26, 2023
e392178
Minor rename of IpConformance enum members.
PLeVasseur Dec 26, 2023
b7ab7fe
Added documentation to validation functions within UAuthority and UEn…
PLeVasseur Dec 26, 2023
a094c1f
Fix clippy warning.
PLeVasseur Jan 9, 2024
46f991f
Began process of breaking up validate_micro_form() into relevant UFoo.
PLeVasseur Jan 23, 2024
a1ae166
Migrated validate_micro_form() logic into each component of a UUri.
PLeVasseur Jan 23, 2024
1dbf0e0
cargo fmt
PLeVasseur Jan 23, 2024
ee854bc
Bail out of validate_micro_form() functions as soon as we fail a vali…
PLeVasseur Jan 29, 2024
72a8458
Fixing unit tests after change to using protobuf crate from prost.
PLeVasseur Jan 29, 2024
269fcbc
Address clippy
PLeVasseur Jan 30, 2024
cfbdc84
Added a bit more documentation that serialize() can return a Serializ…
PLeVasseur Jan 30, 2024
7a41c6c
Moved a number of unit tests into doc tests :)
PLeVasseur Feb 7, 2024
5d0d7f9
Removed checking on string fragments due to its frailty. Added a chec…
PLeVasseur Feb 8, 2024
59b22d7
Updated feature flag for ureq to respect any proxy settings the user …
PLeVasseur Feb 8, 2024
9f43990
Adding .idea/ to .gitignore.
PLeVasseur Feb 14, 2024
3546d49
Updates: handling new up-core-api fixes to remove ip/id ambiguity in …
PLeVasseur Feb 14, 2024
e02f64c
Update doctests to refer to now instead of .
PLeVasseur Feb 14, 2024
2f75090
Removed equality checks against string fragments in unit tests.
PLeVasseur Feb 15, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ Cargo.lock
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb

.vscode/
.vscode/
.idea/
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ uuid = { version = "1.7", features = ["v8"] }
[build-dependencies]
protobuf-codegen = { version = "3.3" }
protoc-bin-vendored = { version = "3.0" }
ureq = { version = "2.7" }
ureq = { version = "2.9", features = ["proxy-from-env"] }

[dev-dependencies]
test-case = { version = "3.3" }
44 changes: 43 additions & 1 deletion src/proto/uprotocol/uauthority.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,52 @@
* SPDX-License-Identifier: Apache-2.0
********************************************************************************/

use crate::uprotocol::uri::UAuthority;
use crate::uprotocol::uri::uauthority::Number;
use crate::uprotocol::UAuthority;

pub use crate::uri::validator::ValidationError;

const REMOTE_IPV4_BYTES: usize = 4;
const REMOTE_IPV6_BYTES: usize = 16;
const REMOTE_ID_MINIMUM_BYTES: usize = 1;
const REMOTE_ID_MAXIMUM_BYTES: usize = 255;

/// Helper functions to deal with `UAuthority::Remote` structure
impl UAuthority {
pub fn get_name(&self) -> Option<&str> {
self.name.as_deref()
}

/// Returns whether a `UAuthority` satisfies the requirements of a micro form URI
///
/// # Returns
/// Returns a `Result<(), ValidationError>` where the ValidationError will contain the reasons it failed or OK(())
/// otherwise
///
/// # Errors
///
/// Returns a `ValidationError` in the failure case
pub fn validate_micro_form(&self) -> Result<(), ValidationError> {
let Some(number) = &self.number else {
return Err(ValidationError::new(
"Must have IP address or ID set as UAuthority for micro form. Neither are set.",
));
};

match number {
Number::Ip(ip) => {
if !(ip.len() == REMOTE_IPV4_BYTES || ip.len() == REMOTE_IPV6_BYTES) {
return Err(ValidationError::new(
"IP address is not IPv4 (4 bytes) or IPv6 (16 bytes)",
));
}
}
Number::Id(id) => {
if !matches!(id.len(), REMOTE_ID_MINIMUM_BYTES..=REMOTE_ID_MAXIMUM_BYTES) {
return Err(ValidationError::new("ID doesn't fit in bytes allocated"));
}
}
}
Ok(())
}
}
40 changes: 40 additions & 0 deletions src/proto/uprotocol/uentity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,48 @@

use crate::uprotocol::uri::UEntity;

pub use crate::uri::validator::ValidationError;

const UENTITY_ID_LENGTH: usize = 16;
const UENTITY_ID_VALID_BITMASK: u32 = 0xffff << UENTITY_ID_LENGTH;
const UENTITY_MAJOR_VERSION_LENGTH: usize = 8;
const UENTITY_MAJOR_VERSION_VALID_BITMASK: u32 = 0xffffff << UENTITY_MAJOR_VERSION_LENGTH;

impl UEntity {
pub fn has_id(&self) -> bool {
self.id.is_some()
}

/// Returns whether a `UEntity` satisfies the requirements of a micro form URI
///
/// # Returns
/// Returns a `Result<(), ValidationError>` where the ValidationError will contain the reasons it failed or OK(())
/// otherwise
///
/// # Errors
///
/// Returns a `ValidationError` in the failure case
pub fn validate_micro_form(&self) -> Result<(), ValidationError> {
if let Some(id) = self.id {
if id & UENTITY_ID_VALID_BITMASK != 0 {
return Err(ValidationError::new(
"ID does not fit within allotted 16 bits in micro form",
));
}
} else {
return Err(ValidationError::new("ID must be present"));
}

if let Some(major_version) = self.version_major {
if major_version & UENTITY_MAJOR_VERSION_VALID_BITMASK != 0 {
return Err(ValidationError::new(
"Major version does not fit within 8 allotted bits in micro form",
));
}
} else {
return Err(ValidationError::new("Major version must be present"));
}

Ok(())
}
}
28 changes: 28 additions & 0 deletions src/proto/uprotocol/uresource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@

use crate::uprotocol::uri::UResource;

pub use crate::uri::validator::ValidationError;

const URESOURCE_ID_LENGTH: usize = 16;
const URESOURCE_ID_VALID_BITMASK: u32 = 0xffff << URESOURCE_ID_LENGTH;

impl UResource {
pub fn has_id(&self) -> bool {
self.id.is_some()
Expand All @@ -25,6 +30,29 @@ impl UResource {
pub fn get_instance(&self) -> Option<&str> {
self.instance.as_deref()
}

/// Returns whether a `UResource` satisfies the requirements of a micro form URI
///
/// # Returns
/// Returns a `Result<(), ValidationError>` where the ValidationError will contain the reasons it failed or OK(())
/// otherwise
///
/// # Errors
///
/// Returns a `ValidationError` in the failure case
pub fn validate_micro_form(&self) -> Result<(), ValidationError> {
if let Some(id) = self.id {
if id & URESOURCE_ID_VALID_BITMASK != 0 {
return Err(ValidationError::new(
"ID does not fit within allotted 16 bits in micro form",
));
}
} else {
return Err(ValidationError::new("ID must be present"));
}

Ok(())
}
}

impl From<&str> for UResource {
Expand Down
161 changes: 125 additions & 36 deletions src/uri/serializer/microuriserializer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,69 @@ impl UriSerializer<Vec<u8>> for MicroUriSerializer {
///
/// # Returns
/// A `Vec<u8>` representing the serialized `UUri`.
///
/// # Errors
///
/// Returns a `SerializationError` noting which which portion failed Micro Uri validation
/// or another error which occurred during serialization.
///
/// # Examples
///
/// ## Example which passes the Micro Uri validation
///
/// ```
/// use up_rust::uprotocol::{UEntity, UUri, UResource};
/// use up_rust::uri::serializer::{UriSerializer, MicroUriSerializer};
///
/// let uri = UUri {
/// entity: Some(UEntity {
/// id: Some(19999),
/// version_major: Some(254),
/// ..Default::default()
/// })
/// .into(),
/// resource: Some(UResource {
/// id: Some(29999),
/// ..Default::default()
/// })
/// .into(),
/// ..Default::default()
/// };
/// let uprotocol_uri = MicroUriSerializer::serialize(&uri);
/// assert!(uprotocol_uri.is_ok());
/// let expected_uri_bytes = vec![0x01, 0x00, 0x75, 0x2F, 0x4E, 0x1F, 0xFE, 0x00];
/// assert_eq!(uprotocol_uri.unwrap(), expected_uri_bytes);
/// ```
///
/// ## Example which fails the Micro Uri validation due to UEntity ID being > 16 bits
///
/// ```
/// use up_rust::uprotocol::{UEntity, UUri, UResource};
/// use up_rust::uri::serializer::{UriSerializer, MicroUriSerializer};
///
/// let uri = UUri {
/// entity: Some(UEntity {
/// id: Some(0x10000), // <- note that we've exceeded the allotted 16 bits
/// version_major: Some(254),
/// ..Default::default()
/// })
/// .into(),
/// resource: Some(UResource {
/// id: Some(29999),
/// ..Default::default()
/// })
/// .into(),
/// ..Default::default()
/// };
/// let uprotocol_uri = MicroUriSerializer::serialize(&uri);
/// assert!(uprotocol_uri.is_err());
/// ```
#[allow(clippy::cast_possible_truncation)]
fn serialize(uri: &UUri) -> Result<Vec<u8>, SerializationError> {
if UriValidator::is_empty(uri) || !UriValidator::is_micro_form(uri) {
return Err(SerializationError::new("URI is empty or not in micro form"));
if let Err(validation_error) = UriValidator::validate_micro_form(uri) {
let error_message =
format!("Failed to validate micro URI format: {}", validation_error);
return Err(SerializationError::new(error_message));
}

let mut buf = vec![];
Expand Down Expand Up @@ -266,10 +325,6 @@ mod tests {
let uri = UUri::default();
let uprotocol_uri = MicroUriSerializer::serialize(&uri);
assert!(uprotocol_uri.is_err());
assert_eq!(
uprotocol_uri.unwrap_err().to_string(),
"URI is empty or not in micro form"
);
}

#[test]
Expand Down Expand Up @@ -318,10 +373,6 @@ mod tests {
};
let uprotocol_uri = MicroUriSerializer::serialize(&uri);
assert!(uprotocol_uri.is_err());
assert_eq!(
uprotocol_uri.unwrap_err().to_string(),
"URI is empty or not in micro form"
);
}

#[test]
Expand All @@ -337,38 +388,29 @@ mod tests {
};
let uprotocol_uri = MicroUriSerializer::serialize(&uri);
assert!(uprotocol_uri.is_err());
assert_eq!(
uprotocol_uri.unwrap_err().to_string(),
"URI is empty or not in micro form"
);
}

#[test]
fn test_serialize_uri_missing_resource_ids() {
fn test_serialize_uri_missing_resource() {
let uri = UUri {
entity: Some(UEntity {
name: "kaputt".to_string(),
id: Some(2999),
version_major: Some(1),
..Default::default()
})
.into(),
..Default::default()
};
let uprotocol_uri = MicroUriSerializer::serialize(&uri);
assert!(uprotocol_uri.is_err());
assert_eq!(
uprotocol_uri.unwrap_err().to_string(),
"URI is empty or not in micro form"
);
}

#[test]
fn test_deserialize_bad_microuri_length() {
let bad_uri: Vec<u8> = vec![0x1, 0x0, 0x0, 0x0, 0x0];
let uprotocol_uri = MicroUriSerializer::deserialize(bad_uri);
assert_eq!(
uprotocol_uri.unwrap_err().to_string(),
"URI is empty or not in micro form"
);
assert!(uprotocol_uri.is_err());
}

#[test]
Expand All @@ -390,26 +432,14 @@ mod tests {
let bad_uri: Vec<u8> = vec![0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0];
let uprotocol_uri = MicroUriSerializer::deserialize(bad_uri);
assert!(uprotocol_uri.is_err());
assert_eq!(
uprotocol_uri.unwrap_err().to_string(),
"Invalid micro URI length"
);

let bad_uri: Vec<u8> = vec![0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0];
let uprotocol_uri = MicroUriSerializer::deserialize(bad_uri);
assert!(uprotocol_uri.is_err());
assert_eq!(
uprotocol_uri.unwrap_err().to_string(),
"Invalid micro URI length"
);

let bad_uri: Vec<u8> = vec![0x1, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0];
let uprotocol_uri = MicroUriSerializer::deserialize(bad_uri);
assert!(uprotocol_uri.is_err());
assert_eq!(
uprotocol_uri.unwrap_err().to_string(),
"Invalid micro URI length"
);
}

#[test]
Expand Down Expand Up @@ -538,7 +568,6 @@ mod tests {
};
let uprotocol_uri = MicroUriSerializer::serialize(&uri);
assert!(uprotocol_uri.is_err());
assert_eq!(uprotocol_uri.unwrap_err().to_string(), "Invalid IP address");
}

#[test]
Expand Down Expand Up @@ -577,4 +606,64 @@ mod tests {
assert!(UriValidator::is_micro_form(uri2.as_ref().unwrap()));
assert_eq!(uri, uri2.unwrap());
}

#[test]
fn test_serialize_uri_overflow_resource_id() {
let uri = UUri {
entity: Some(UEntity {
id: Some(29999),
version_major: Some(254),
..Default::default()
})
.into(),
resource: Some(UResource {
id: Some(0x10000),
..Default::default()
})
.into(),
..Default::default()
};
let uprotocol_uri = MicroUriSerializer::serialize(&uri);
assert!(uprotocol_uri.is_err());
}

#[test]
fn test_serialize_uri_overflow_entity_id() {
let uri = UUri {
entity: Some(UEntity {
id: Some(0x10000),
version_major: Some(254),
..Default::default()
})
.into(),
resource: Some(UResource {
id: Some(29999),
..Default::default()
})
.into(),
..Default::default()
};
let uprotocol_uri = MicroUriSerializer::serialize(&uri);
assert!(uprotocol_uri.is_err());
}

#[test]
fn test_serialize_version_overflow_entity_version() {
let uri = UUri {
entity: Some(UEntity {
id: Some(29999),
version_major: Some(0x100),
..Default::default()
})
.into(),
resource: Some(UResource {
id: Some(29999),
..Default::default()
})
.into(),
..Default::default()
};
let uprotocol_uri = MicroUriSerializer::serialize(&uri);
assert!(uprotocol_uri.is_err());
}
}
Loading