Skip to content
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

Handle invalid references when browsing #77

Merged
merged 2 commits into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- Check only severity in `ua::StatusCode::is_good()`. Previously this would be an exact comparison
to `ua::StatusCode::GOOD`.
- No longer panic when unwrapping `ua::Variant` with array value.
- Allow invalid references array in `ua::BrowseResult` when request was otherwise successful.

## [0.5.0] - 2024-03-01

Expand Down
41 changes: 30 additions & 11 deletions src/async_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ impl AsyncClient {
return Err(Error::internal("browse should return a result"));
};

to_browse_result(result)
to_browse_result(result, Some(node_id))
}

/// Browses several nodes at once.
Expand Down Expand Up @@ -311,12 +311,16 @@ impl AsyncClient {
return Err(Error::internal("browse should return results"));
};

let results: Vec<_> = results.iter().map(to_browse_result).collect();

// The OPC UA specification state that the resulting list has the same number of elements as
// the request list. If not, we would not be able to match elements in the two lists anyway.
debug_assert_eq!(results.len(), node_ids.len());

let results: Vec<_> = results
.iter()
.zip(node_ids)
.map(|(result, node_id)| to_browse_result(result, Some(node_id)))
.collect();

Ok(results)
}

Expand Down Expand Up @@ -347,12 +351,15 @@ impl AsyncClient {
return Err(Error::internal("browse should return results"));
};

let results: Vec<_> = results.iter().map(to_browse_result).collect();

// The OPC UA specification state that the resulting list has the same number of elements as
// the request list. If not, we would not be able to match elements in the two lists anyway.
debug_assert_eq!(results.len(), continuation_points.len());

let results: Vec<_> = results
.iter()
.map(|result| to_browse_result(result, None))
.collect();

Ok(results)
}

Expand Down Expand Up @@ -506,14 +513,26 @@ async fn service_request<R: ServiceRequest>(
pub type BrowseResult = Result<(Vec<ua::ReferenceDescription>, Option<ua::ContinuationPoint>)>;

/// Converts [`ua::BrowseResult`] to our public result type.
fn to_browse_result(result: &ua::BrowseResult) -> BrowseResult {
// Make sure to verify the inner status code inside `BrowseResult`. The service request itself
// finishes without error, even when browsing the node has failed.
fn to_browse_result(result: &ua::BrowseResult, node_id: Option<&ua::NodeId>) -> BrowseResult {
// Make sure to verify the inner status code inside `BrowseResult`. The service request finishes
// without error, even when browsing the node has failed.
Error::verify_good(&result.status_code())?;

let Some(references) = result.references() else {
return Err(Error::internal("browse should return references"));
let references = if let Some(references) = result.references() {
references.into_vec()
} else {
// When no references exist, some OPC UA servers do not return an empty references array but
// an invalid (unset) one instead, e.g. Siemens SIMOTION. We treat it as an empty array, and
// continue without error.
if let Some(node_id) = node_id {
log::debug!("Browsing {node_id} returned unset references, assuming none exist");
} else {
log::debug!(
"Browsing continuation point returned unset references, assuming none exist",
);
}
Vec::new()
};

Ok((references.into_vec(), result.continuation_point()))
Ok((references, result.continuation_point()))
}