Skip to content

Validate range of DateTime values#217

Draft
uklotzde wants to merge 2 commits intoHMIProject:mainfrom
uklotzde:date-time-min-max
Draft

Validate range of DateTime values#217
uklotzde wants to merge 2 commits intoHMIProject:mainfrom
uklotzde:date-time-min-max

Conversation

@uklotzde
Copy link
Copy Markdown
Collaborator

  • Add associated constants with min/max limits to ua::DateTime
  • Check min/max limits when converting time::OffsetDateTime into ua::DateTime.

- Add associated constants with min/max limits to `ua::DateTime`
- Check min/max limits when converting `time::OffsetDateTime`
into `ua::DateTime`.
@uklotzde uklotzde requested a review from sgoll April 10, 2025 11:16
pub fn to_utc(&self) -> Option<time::OffsetDateTime> {
use open62541_sys::{UA_DATETIME_UNIX_EPOCH, UA_DATETIME_USEC};

// TODO: How to handle values that are out of range?
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sgoll ??

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since out-of-range values should never be sent over OPC UA, we can treat them as an exception. I would not panic, however, because those values are still runtime values from another system.

That being said, I'd treat them similar to the possible error from from_unix_timestamp_nanos() at the end of the function, i.e. by returning None.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the spec out of range values should be clamped for encoding, i.e. either 0 (too low) or i64::MAX (too high):

https://reference.opcfoundation.org/Core/Part6/v105/docs/5.2.2.5

🤔

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So DateTime::MAX should never be sent over the wire. Strange rules.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's how I read it, too—when the value is equal to 9999-12-31T11:59:59Z or later, then send i64::MAX (i.e. 9_223_372_036_854_775_807 instead of 2_650_466_915_990_000_000).

Similarly, for 1601-01-01T12:00:00Z or earlier, there is also the ambiguity that we can't know whether the value was exactly that time (encoded as 0) or earlier (also encoded as 0).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That said, I think we have to handle this case specially, i.e. we should have the following conditions:

  • value < 0None (not a valid value)
  • value == 0Some(Self::MIN), but it may have been clamped
  • 0 < value && value < 2_650_466_915_990_000_000Some(…)
  • 2_650_466_915_990_000_000 <= value && value < 9_223_372_036_854_775_807 None (not a valid value)
  • value == 9_223_372_036_854_775_807Some(Self::MAX), but it may have been clamped

Copy link
Copy Markdown
Contributor

@sgoll sgoll Apr 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could expose the "possibly clamped" state in the return type, either as None, Some(…), or use a third state with an appropriate enum variant.

On the other hand, since time::OffsedDateTime can represent times outside the range of 1601-01-01 and 9999-12-31 (the latter with feature flag large-dates), we could accept such values when we receive them (marked with "not a valid value" above)—be conservative in what you send, be liberal in what you accept.

So, we could leave it to from_unix_timestamp_nanos() to deal with it, possibly only handling the value of i64::MAX explicitly because that has special meaning and may mean 9999-12-31T11:59:59Z instead (in which case, I'd probably return None).

pub fn to_utc(&self) -> Option<time::OffsetDateTime> {
use open62541_sys::{UA_DATETIME_UNIX_EPOCH, UA_DATETIME_USEC};

// TODO: How to handle values that are out of range?
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since out-of-range values should never be sent over OPC UA, we can treat them as an exception. I would not panic, however, because those values are still runtime values from another system.

That being said, I'd treat them similar to the possible error from from_unix_timestamp_nanos() at the end of the function, i.e. by returning None.

/// Maximum value.
///
/// See also: <https://reference.opcfoundation.org/Core/Part6/v105/docs/5.2.2.5>
pub const MAX: Self = Self(2_650_467_743_990_000_000);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where does this exact value come from? Is it the number of 100 nanosecond intervals that matches 9999-12-31 23:59:59 UTC? Maybe we can add a comment or otherwise make it clearer what the value represents.

/// Minimum UTC timestamp.
///
/// See also: <https://reference.opcfoundation.org/Core/Part6/v105/docs/5.2.2.5>
#[cfg(feature = "time")]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can move all the individually guarded attributes and method to_utc() to a separate impl block. This way, we'd only need one guard pragma, making it clearer what belongs together.

@uklotzde uklotzde marked this pull request as draft April 16, 2025 11:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants