1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
// OPCUA for Rust
// SPDX-License-Identifier: MPL-2.0
// Copyright (C) 2017-2022 Adam Lock
//! Contains code for turning messages into chunks and chunks into messages.
use std::io::Cursor;
use opcua_crypto::SecurityPolicy;
use opcua_types::{
encoding::BinaryEncoder, node_id::NodeId, node_ids::ObjectId, status_code::StatusCode,
};
use crate::{
comms::{
message_chunk::{MessageChunk, MessageChunkType, MessageIsFinalType},
secure_channel::SecureChannel,
},
supported_message::SupportedMessage,
};
/// The Chunker is responsible for turning messages to chunks and chunks into messages.
pub struct Chunker;
impl Chunker {
/// Tests what kind of chunk type is used for the supported message.
fn message_type(message: &SupportedMessage) -> MessageChunkType {
match message {
SupportedMessage::OpenSecureChannelRequest(_)
| SupportedMessage::OpenSecureChannelResponse(_) => MessageChunkType::OpenSecureChannel,
SupportedMessage::CloseSecureChannelRequest(_)
| SupportedMessage::CloseSecureChannelResponse(_) => {
MessageChunkType::CloseSecureChannel
}
_ => MessageChunkType::Message,
}
}
/// Ensure all of the supplied chunks have a valid secure channel id, and sequence numbers
/// greater than the input sequence number and the preceding chunk
///
/// The function returns the last sequence number in the series for success, or
/// `BadSequenceNumberInvalid` or `BadSecureChannelIdInvalid` for failure.
pub fn validate_chunks(
starting_sequence_number: u32,
secure_channel: &SecureChannel,
chunks: &[MessageChunk],
) -> Result<u32, StatusCode> {
let first_sequence_number = {
let chunk_info = chunks[0].chunk_info(secure_channel)?;
chunk_info.sequence_header.sequence_number
};
if first_sequence_number < starting_sequence_number {
error!(
"First sequence number of {} is less than last value {}",
first_sequence_number, starting_sequence_number
);
Err(StatusCode::BadSequenceNumberInvalid)
} else {
let secure_channel_id = secure_channel.secure_channel_id();
// Validate that all chunks have incrementing sequence numbers and valid chunk types
let mut expected_request_id: u32 = 0;
for (i, chunk) in chunks.iter().enumerate() {
let chunk_info = chunk.chunk_info(secure_channel)?;
// Check the channel id of each chunk
if secure_channel_id != 0
&& chunk_info.message_header.secure_channel_id != secure_channel_id
{
error!(
"Secure channel id {} does not match expected id {}",
chunk_info.message_header.secure_channel_id, secure_channel_id
);
return Err(StatusCode::BadSecureChannelIdInvalid);
}
// Check the sequence id - should be larger than the last one decoded
let sequence_number = chunk_info.sequence_header.sequence_number;
let expected_sequence_number = first_sequence_number + i as u32;
if sequence_number != expected_sequence_number {
error!(
"Chunk sequence number of {} is not the expected value of {}, idx {}",
sequence_number, expected_sequence_number, i
);
return Err(StatusCode::BadSecurityChecksFailed);
}
// Check the request id against the first chunk's request id
if i == 0 {
expected_request_id = chunk_info.sequence_header.request_id;
} else if chunk_info.sequence_header.request_id != expected_request_id {
error!("Chunk sequence number of {} has a request id {} which is not the expected value of {}, idx {}", sequence_number, chunk_info.sequence_header.request_id, expected_request_id, i);
return Err(StatusCode::BadSecurityChecksFailed);
}
}
Ok(first_sequence_number + chunks.len() as u32 - 1)
}
}
/// Encodes a message using the supplied sequence number and secure channel info and emits the corresponding chunks
///
/// max_chunk_count refers to the maximum byte length that a chunk should not exceed or 0 for no limit
/// max_message_size refers to the maximum byte length of a message or 0 for no limit
///
pub fn encode(
sequence_number: u32,
request_id: u32,
max_message_size: usize,
max_chunk_size: usize,
secure_channel: &SecureChannel,
supported_message: &SupportedMessage,
) -> std::result::Result<Vec<MessageChunk>, StatusCode> {
let security_policy = secure_channel.security_policy();
if security_policy == SecurityPolicy::Unknown {
panic!("Security policy cannot be unknown");
}
// Client / server stacks should validate the length of a message before sending it and
// here makes as good a place as any to do that.
let mut message_size = supported_message.byte_len();
if max_message_size > 0 && message_size > max_message_size {
error!(
"Max message size is {} and message {} exceeds that",
max_message_size, message_size
);
// Client stack should report a BadRequestTooLarge, server BadResponseTooLarge
Err(if secure_channel.is_client_role() {
StatusCode::BadRequestTooLarge
} else {
StatusCode::BadResponseTooLarge
})
} else {
let node_id = supported_message.node_id();
message_size += node_id.byte_len();
let message_type = Chunker::message_type(supported_message);
let mut stream = Cursor::new(vec![0u8; message_size]);
trace!("Encoding node id {:?}", node_id);
let _ = node_id.encode(&mut stream);
let _ = supported_message.encode(&mut stream)?;
let data = stream.into_inner();
let result = if max_chunk_size > 0 {
let max_body_per_chunk = MessageChunk::body_size_from_message_size(
message_type,
secure_channel,
max_chunk_size,
)
.map_err(|_| {
error!(
"body_size_from_message_size error for max_chunk_size = {}",
max_chunk_size
);
StatusCode::BadTcpInternalError
})?;
// Multiple chunks means breaking the data up into sections. Fortunately
// Rust has a nice function to do just that.
let data_chunks = data.chunks(max_body_per_chunk);
let data_chunks_len = data_chunks.len();
let mut chunks = Vec::with_capacity(data_chunks_len);
for (i, data_chunk) in data_chunks.enumerate() {
let is_final = if i == data_chunks_len - 1 {
MessageIsFinalType::Final
} else {
MessageIsFinalType::Intermediate
};
let chunk = MessageChunk::new(
sequence_number + i as u32,
request_id,
message_type,
is_final,
secure_channel,
data_chunk,
)?;
chunks.push(chunk);
}
chunks
} else {
let chunk = MessageChunk::new(
sequence_number,
request_id,
message_type,
MessageIsFinalType::Final,
secure_channel,
&data,
)?;
vec![chunk]
};
Ok(result)
}
}
/// Decodes a series of chunks to create a message. The message must be of a `SupportedMessage`
/// type otherwise an error will occur.
pub fn decode(
chunks: &[MessageChunk],
secure_channel: &SecureChannel,
expected_node_id: Option<NodeId>,
) -> std::result::Result<SupportedMessage, StatusCode> {
// Calculate the size of data held in all chunks
let mut data_size: usize = 0;
for (i, chunk) in chunks.iter().enumerate() {
let chunk_info = chunk.chunk_info(secure_channel)?;
// The last most chunk is expected to be final, the rest intermediate
let expected_is_final = if i == chunks.len() - 1 {
MessageIsFinalType::Final
} else {
MessageIsFinalType::Intermediate
};
if chunk_info.message_header.is_final != expected_is_final {
return Err(StatusCode::BadDecodingError);
}
// Calculate how much space data is in the chunk
let body_start = chunk_info.body_offset;
let body_end = body_start + chunk_info.body_length;
data_size += chunk.data[body_start..body_end].len();
}
// Read the data into a contiguous buffer. The assumption is the data is decrypted / verified by now
// TODO this buffer should be externalized so it is not allocated each time
let mut data = Vec::with_capacity(data_size);
for chunk in chunks.iter() {
let chunk_info = chunk.chunk_info(secure_channel)?;
let body_start = chunk_info.body_offset;
let body_end = body_start + chunk_info.body_length;
let body_data = &chunk.data[body_start..body_end];
data.extend_from_slice(body_data);
}
// Make a stream around the data
let mut data = Cursor::new(data);
// The extension object prefix is just the node id. A point the spec rather unhelpfully doesn't
// elaborate on. Probably because people enjoy debugging why the stream pos is out by 1 byte
// for hours.
let decoding_options = secure_channel.decoding_options();
// Read node id from stream
let node_id = NodeId::decode(&mut data, &decoding_options)?;
let object_id = Self::object_id_from_node_id(node_id, expected_node_id)?;
// Now decode the payload using the node id.
match SupportedMessage::decode_by_object_id(&mut data, object_id, &decoding_options) {
Ok(decoded_message) => {
if let SupportedMessage::Invalid(_) = decoded_message {
debug!("Message {:?} is unsupported", object_id);
Err(StatusCode::BadServiceUnsupported)
} else {
// debug!("Returning decoded msg {:?}", decoded_message);
Ok(decoded_message)
}
}
Err(err) => {
debug!("Cannot decode message {:?}, err = {:?}", object_id, err);
Err(StatusCode::BadServiceUnsupported)
}
}
}
fn object_id_from_node_id(
node_id: NodeId,
expected_node_id: Option<NodeId>,
) -> Result<ObjectId, StatusCode> {
let valid_node_id = if node_id.namespace != 0 || !node_id.is_numeric() {
// Must be ns 0 and numeric
error!("Expecting chunk to contain a OPC UA request or response");
false
} else if let Some(expected_node_id) = expected_node_id {
let matches_expected = expected_node_id == node_id;
if !matches_expected {
error!(
"Chunk node id {:?} does not match expected {:?}",
node_id, expected_node_id
);
}
matches_expected
} else {
true
};
if !valid_node_id {
error!(
"The node id read from the stream was not accepted in this context {:?}",
node_id
);
Err(StatusCode::BadUnexpectedError)
} else {
node_id
.as_object_id()
.map_err(|_| {
error!("The node {:?} was not an object id", node_id);
StatusCode::BadUnexpectedError
})
.map(|object_id| {
trace!("Decoded node id / object id of {:?}", object_id);
object_id
})
}
}
}