Skip to content

Commit

Permalink
Replace references to server state, session and address space in serv…
Browse files Browse the repository at this point in the history
…er services. This takes a lot more code but allows locking to be more finegrained, potentially asynchronous services in future.
  • Loading branch information
locka99 committed Dec 4, 2019
1 parent a16c659 commit ed7d34b
Show file tree
Hide file tree
Showing 22 changed files with 506 additions and 320 deletions.
15 changes: 10 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,21 @@
Planned future work is listed at the bottom.

## 0.8 (FUTURE)
This is NOT COMPLETED AND REPRESENTS A WISHLIST
- Move crypto code into a opcua-crypto crate and attempt to enable / disable it via feature
- identify issue with monitored items stalling sometimes, spurious errors on some clients
- Cryptography functionality has been moved into an opcua-crypto crate
ITEMS BELOW ARE NOT COMPLETED AND ARE SUBJECT TO CHANGE
- Update to OPC UA 1.04 schemas and definitions
- Allow crypto functionality that depends on OpenSSL in opcua-crypto to be enabled / disabled via a feature (i.e. when
disabled only no-encryption `None` endpoints are available)
- identify issue with monitored items stalling sometimes, spurious acknowledgment errors on some clients
- Session restore after disconnect in server. The server has to stash sessions that were
abnormally disconnected so the session state can be restored if a new connection provides the token.
- prevent nested arrays from being deserialized.
- Prevent nested arrays from being deserialized.
- Multiple chunk support in client and server, sending and receiving.
- Add session diagnostics to the address space
- Add more session diagnostics to the address space
- Update Tokio/Futures for `async`/`await` - Rust 2018 will implement new async functionality over time
and this project will reflect best practice.
- Better access control, i.e. user access level reflecting the active session
- Certificate trust via signed certificate chain / trusted cert store

## 0.7
- Minimum compiler is Rust 1.37 or later due to use of Self on enums and other uses of refined syntax.
Expand Down
24 changes: 19 additions & 5 deletions docs/compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ The following services are supported:
* Attribute service set
* Read
* Write
* History Read - 0.8+. Server side requires host application to implement a HistoricalDataProvider backend
* History Update - 0.8+. Server side requires host application to implement a HistoricalDataProvider backend

* Session service set
* CreateSession
Expand All @@ -44,7 +46,7 @@ The following services are supported:
* MonitoredItem service set
* CreateMonitoredItems
- Data change filter including dead band filtering.
- Event filter (work in progress)
- Event filter
* ModifyMonitoredItems
* SetMonitoringMode
* SetTriggering
Expand Down Expand Up @@ -76,6 +78,7 @@ Currently the following are not supported
* Diagnostic info. OPC UA allows for you to ask for diagnostics with any request. None is supplied at this time
* Session resumption. If your client disconnects, all information is discarded.
* Default node set is mostly static. Certain fields of server information will contain their default values unless explicitly set.
* Access control is limited to setting read/write permissions on nodes that apply to all sessions.

## Client

Expand Down Expand Up @@ -105,9 +108,18 @@ The config files are specified in YAML but this is controlled via serde so the f

## Encryption modes

Server and client support endpoints with the standard message security modes - None, Sign, SignAndEncrypt.
Server and client support endpoints with the standard message security modes:

The following security policies are supported - None, Basic128Rsa15, Basic256, Basic256Rsa256.
* None
* Sign
* SignAndEncrypt.

The following security policies are supported:

* None
* Basic128Rsa15
* Basic256
* Basic256Rsa256.

## User identities

Expand All @@ -122,8 +134,8 @@ The server and client support the following user identity tokens
OPC UA for Rust uses cryptographic algorithms for signing, verifying, encrypting and decrypting data. In addition
it creates, loads and saves certificates and keys.

OpenSSL is used for this purpose although it would be nice to go to a pure Rust implementation assuming a crate
delivers everything required. Most of the crypto+OpenSSL code is abstracted to make it easier to remove in the future.
OpenSSL is used for encryption although it would be nice to go to a pure Rust implementation assuming a crate
delivers everything required. The crypto+OpenSSL code is isolated in an `opcua-crypto` crate.

You must read the [setup](./setup.md) to configure OpenSSL for your environment.

Expand All @@ -148,6 +160,8 @@ For encrypted connections the following applies:
* The server will reject the first connection from an unrecognized client. It will create a file representing
the cert in its the `pki/rejected/` folder and you, the administrator must move the cert to the `trusted/` folder
to permit connections from that client in future.
* NOTE: Signed certificates are not supported at this time. Potentially a cert signed with a trusted CA could
be automatically moved to the `trusted/` folder.
* Likewise, the client shall reject unrecognized servers in the same fashion, and the cert must be moved from the
`rejected/` to `trusted/` folder for connection to succeed.
* Servers that register with a discovery server may find the discovery server rejects their registration attempts if the
Expand Down
33 changes: 20 additions & 13 deletions server/src/historical/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use std::result::Result;
use std::{
result::Result,
sync::{Arc, RwLock},
};

use opcua_types::*;
use opcua_types::status_code::StatusCode;
Expand All @@ -7,46 +10,50 @@ use crate::{
address_space::AddressSpace,
};

/// The trait describes the functions that a server must implement to process historical data operations
/// The traits describes the functions that a server must implement to process historical event operations
/// from the HistoryRead / HistoryUpdate commands.
pub trait HistoricalDataProvider {
fn read_event_details(address_space: &AddressSpace, details: ReadEventDetails) -> Result<(), StatusCode> {
pub trait HistoricalEventProvider {
fn read_event_details(&self, _address_space: Arc<RwLock<AddressSpace>>, _request: ReadEventDetails) -> Result<(), StatusCode> {
Err(StatusCode::BadHistoryOperationUnsupported)
}

fn read_raw_modified_details(address_space: &AddressSpace, details: ReadRawModifiedDetails) -> Result<(), StatusCode> {
fn update_event_details(&self, _address_space: Arc<RwLock<AddressSpace>>, _request: UpdateEventDetails) -> Result<(), StatusCode> {
Err(StatusCode::BadHistoryOperationUnsupported)
}

fn read_processed_details(address_space: &AddressSpace, details: ReadProcessedDetails) -> Result<(), StatusCode> {
fn delete_event_details(&self, _address_space: Arc<RwLock<AddressSpace>>, _request: DeleteEventDetails) -> Result<(), StatusCode> {
Err(StatusCode::BadHistoryOperationUnsupported)
}
}

fn read_at_time_details(address_space: &AddressSpace, details: ReadAtTimeDetails) -> Result<(), StatusCode> {
/// The trait describes the functions that a server must implement to process historical data operations
/// from the HistoryRead / HistoryUpdate commands.
pub trait HistoricalDataProvider {
fn read_raw_modified_details(&self, _address_space: Arc<RwLock<AddressSpace>>, _request: ReadRawModifiedDetails) -> Result<(), StatusCode> {
Err(StatusCode::BadHistoryOperationUnsupported)
}

fn update_data_details(address_space: &AddressSpace, request: UpdateDataDetails) -> Result<(), StatusCode> {
fn read_processed_details(&self, _address_space: Arc<RwLock<AddressSpace>>, _request: ReadProcessedDetails) -> Result<(), StatusCode> {
Err(StatusCode::BadHistoryOperationUnsupported)
}

fn update_structure_data_details(address_space: &AddressSpace, request: UpdateStructureDataDetails) -> Result<(), StatusCode> {
fn read_at_time_details(&self, _address_space: Arc<RwLock<AddressSpace>>, _request: ReadAtTimeDetails) -> Result<(), StatusCode> {
Err(StatusCode::BadHistoryOperationUnsupported)
}

fn update_event_details(address_space: &AddressSpace, request: UpdateEventDetails) -> Result<(), StatusCode> {
fn update_data_details(&self, _address_space: Arc<RwLock<AddressSpace>>, _request: UpdateDataDetails) -> Result<(), StatusCode> {
Err(StatusCode::BadHistoryOperationUnsupported)
}

fn delete_raw_modified_details(address_space: &AddressSpace, request: DeleteRawModifiedDetails) -> Result<(), StatusCode> {
fn update_structure_data_details(&self, _address_space: Arc<RwLock<AddressSpace>>, _request: UpdateStructureDataDetails) -> Result<(), StatusCode> {
Err(StatusCode::BadHistoryOperationUnsupported)
}

fn delete_at_time_details(address_space: &AddressSpace, request: DeleteAtTimeDetails) -> Result<(), StatusCode> {
fn delete_raw_modified_details(&self, _address_space: Arc<RwLock<AddressSpace>>, _request: DeleteRawModifiedDetails) -> Result<(), StatusCode> {
Err(StatusCode::BadHistoryOperationUnsupported)
}

fn delete_event_details(address_space: &AddressSpace, request: DeleteEventDetails) -> Result<(), StatusCode> {
fn delete_at_time_details(&self, _address_space: Arc<RwLock<AddressSpace>>, _request: DeleteAtTimeDetails) -> Result<(), StatusCode> {
Err(StatusCode::BadHistoryOperationUnsupported)
}
}
68 changes: 56 additions & 12 deletions server/src/services/attribute.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,31 @@
use std::result::Result;
use std::{
result::Result,
sync::{Arc, RwLock},
};

use opcua_types::*;
use opcua_types::status_code::StatusCode;

use crate::{
services::Service,
address_space::{AccessLevel, AddressSpace, node::NodeType},
historical::HistoricalDataProvider,
};

/// The attribute service. Allows attributes to be read and written from the address space.
pub(crate) struct AttributeService;
pub(crate) struct AttributeService {
historical_data_provider: Option<Box<dyn HistoricalDataProvider + Send + Sync>>
}

impl Service for AttributeService {
fn name(&self) -> String { String::from("AttributeService") }
}

impl AttributeService {
pub fn new() -> AttributeService {
AttributeService {}
AttributeService {
historical_data_provider: None
}
}

/// Used to read historical values or Events of one or more Nodes. For
Expand All @@ -26,7 +34,7 @@ impl AttributeService {
/// elements or to read ranges of elements of the composite. Servers may make historical
/// values available to Clients using this Service, although the historical values themselves
/// are not visible in the AddressSpace.
pub fn read(&self, address_space: &AddressSpace, request: &ReadRequest) -> Result<SupportedMessage, StatusCode> {
pub fn read(&self, address_space: Arc<RwLock<AddressSpace>>, request: &ReadRequest) -> Result<SupportedMessage, StatusCode> {
if is_empty_option_vec!(request.nodes_to_read) {
Ok(self.service_fault(&request.request_header, StatusCode::BadNothingToDo))
} else if request.max_age < 0f64 {
Expand All @@ -35,8 +43,8 @@ impl AttributeService {
Ok(self.service_fault(&request.request_header, StatusCode::BadMaxAgeInvalid))
} else {
let nodes_to_read = request.nodes_to_read.as_ref().unwrap();

// Read nodes and their attributes
let address_space = trace_read_lock_unwrap!(address_space);
let timestamps_to_return = request.timestamps_to_return;
let results = nodes_to_read.iter().map(|node_to_read| {
Self::read_node_value(&address_space, node_to_read, request.max_age, timestamps_to_return)
Expand All @@ -52,21 +60,51 @@ impl AttributeService {
}
}

/// Used to
pub fn history_read(&self, address_space: &AddressSpace, request: &HistoryReadRequest) -> Result<SupportedMessage, StatusCode> {
Err(StatusCode::BadServiceUnsupported)
/// Used to read historical values
pub fn history_read(&self, _address_space: Arc<RwLock<AddressSpace>>, request: &HistoryReadRequest) -> Result<SupportedMessage, StatusCode> {
if let Some(ref historical_data_provider) = self.historical_data_provider {
// TODO call the provider
let history_read_details = &request.history_read_details;
let node_id = &history_read_details.node_id;
if node_id.namespace == 0 {
match node_id.identifier {
Identifier::Numeric(value) => {
if value == ObjectId::ReadEventDetails_Encoding_DefaultBinary as u32 {
Err(StatusCode::BadServiceUnsupported)
} else if value == ObjectId::ReadRawModifiedDetails_Encoding_DefaultBinary as u32 {
Err(StatusCode::BadServiceUnsupported)
} else if value == ObjectId::ReadProcessedDetails_Encoding_DefaultBinary as u32 {
Err(StatusCode::BadServiceUnsupported)
} else if value == ObjectId::ReadAtTimeDetails_Encoding_DefaultBinary as u32 {
Err(StatusCode::BadServiceUnsupported)
} else {
Err(StatusCode::BadServiceUnsupported)
}
}
_ => {
// Error
Err(StatusCode::BadServiceUnsupported)
}
}
} else {
Err(StatusCode::BadServiceUnsupported)
}
} else {
Err(StatusCode::BadServiceUnsupported)
}
}

/// Used to write values to one or more Attributes of one or more Nodes. For
/// constructed Attribute values whose elements are indexed, such as an array, this Service
/// allows Clients to write the entire set of indexed values as a composite, to write individual
/// elements or to write ranges of elements of the composite.
pub fn write(&self, address_space: &mut AddressSpace, request: &WriteRequest) -> Result<SupportedMessage, StatusCode> {
pub fn write(&self, address_space: Arc<RwLock<AddressSpace>>, request: &WriteRequest) -> Result<SupportedMessage, StatusCode> {
if is_empty_option_vec!(request.nodes_to_write) {
Ok(self.service_fault(&request.request_header, StatusCode::BadNothingToDo))
} else {
let results = request.nodes_to_write.as_ref().unwrap().iter().map(|node_to_write| {
Self::write_node_value(address_space, node_to_write)
let mut address_space = trace_write_lock_unwrap!(address_space);
Self::write_node_value(&mut address_space, node_to_write)
}).collect();

let diagnostic_infos = None;
Expand All @@ -79,8 +117,14 @@ impl AttributeService {
}
}

pub fn history_update(&mut self, address_space: &mut AddressSpace, request: &HistoryUpdateRequest) -> Result<SupportedMessage, StatusCode> {
Err(StatusCode::BadServiceUnsupported)
/// Used to update or update historical values
pub fn history_update(&mut self, _address_space: Arc<RwLock<AddressSpace>>, _request: &HistoryUpdateRequest) -> Result<SupportedMessage, StatusCode> {
if let Some(ref _historical_data_provider) = self.historical_data_provider {
// TODO call the provider
Err(StatusCode::BadServiceUnsupported)
} else {
Err(StatusCode::BadServiceUnsupported)
}
}

fn read_node_value(address_space: &AddressSpace, node_to_read: &ReadValueId, max_age: f64, timestamps_to_return: TimestampsToReturn) -> DataValue {
Expand Down
8 changes: 6 additions & 2 deletions server/src/services/discovery.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use std::result::Result;
use std::{
result::Result,
sync::{Arc, RwLock},
};

use opcua_types::*;
use opcua_types::status_code::StatusCode;
Expand All @@ -17,7 +20,8 @@ impl DiscoveryService {
DiscoveryService {}
}

pub fn get_endpoints(&self, server_state: &ServerState, request: &GetEndpointsRequest) -> Result<SupportedMessage, StatusCode> {
pub fn get_endpoints(&self, server_state: Arc<RwLock<ServerState>>, request: &GetEndpointsRequest) -> Result<SupportedMessage, StatusCode> {
let server_state = trace_read_lock_unwrap!(server_state);
// TODO some of the arguments in the request are ignored
// endpointUrl - for diagnostics and to determine what urls to return in response
// localeIds - list of locales to use for human readable strings (in the endpoint descriptions)
Expand Down
Loading

0 comments on commit ed7d34b

Please sign in to comment.