Skip to content

Commit

Permalink
Handle invalid references when browsing (#77)
Browse files Browse the repository at this point in the history
## Description

Some PLCs return an invalid (unset) array of references when browsing a
node with no children. While not exactly specified in the OPC UA
standard, the sensible thing to do here is to threat it the same as an
empty array and not as an error.
  • Loading branch information
sgoll authored Mar 18, 2024
1 parent d0d4697 commit 2213439
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 11 deletions.
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()))
}

0 comments on commit 2213439

Please sign in to comment.