Skip to main content

opcua_core/comms/
message_chunk.rs

1// OPCUA for Rust
2// SPDX-License-Identifier: MPL-2.0
3// Copyright (C) 2017-2024 Adam Lock
4
5//! A message chunk is a message or a portion of a message, optionally encrypted & signed, which
6//! has been split for transmission.
7
8use std::io::{Cursor, Read, Write};
9
10use opcua_types::{
11    process_decode_io_result, process_encode_io_result, read_u32, read_u8, status_code::StatusCode,
12    write_u32, write_u8, DecodingOptions, EncodingResult, Error, SimpleBinaryDecodable,
13    SimpleBinaryEncodable,
14};
15use tracing::trace;
16
17use super::{
18    message_chunk_info::ChunkInfo,
19    secure_channel::SecureChannel,
20    security_header::{SecurityHeader, SequenceHeader},
21    tcp_types::{
22        CHUNK_FINAL, CHUNK_FINAL_ERROR, CHUNK_INTERMEDIATE, CHUNK_MESSAGE,
23        CLOSE_SECURE_CHANNEL_MESSAGE, MIN_CHUNK_SIZE, OPEN_SECURE_CHANNEL_MESSAGE,
24    },
25};
26
27/// The size of a chunk header, used by several places
28pub const MESSAGE_CHUNK_HEADER_SIZE: usize = 3 + 1 + 4 + 4;
29/// Offset of the MessageSize in chunk headers. This comes after the chunk type and
30/// the is_final flag.
31pub const MESSAGE_SIZE_OFFSET: usize = 3 + 1;
32
33#[derive(Debug, Clone, Copy, PartialEq)]
34/// Type of message chunk.
35pub enum MessageChunkType {
36    /// Chunk is part of a normal service message.
37    Message,
38    /// Chunk is an open secure channel message.
39    OpenSecureChannel,
40    /// Chunk is a close secure channel message.
41    CloseSecureChannel,
42}
43
44impl MessageChunkType {
45    /// `true` if this is an `OpenSecureChannel` message.
46    pub fn is_open_secure_channel(&self) -> bool {
47        *self == MessageChunkType::OpenSecureChannel
48    }
49}
50
51#[derive(Debug, Clone, Copy, PartialEq)]
52/// Type of message chunk.
53pub enum MessageIsFinalType {
54    /// Intermediate
55    Intermediate,
56    /// Final chunk
57    Final,
58    /// Abort
59    FinalError,
60}
61
62#[derive(Debug, Clone, PartialEq)]
63/// Message chunk header.
64pub struct MessageChunkHeader {
65    /// The kind of chunk - message, open or close
66    pub message_type: MessageChunkType,
67    /// The chunk type - C == intermediate, F = the final chunk, A = the final chunk when aborting
68    pub is_final: MessageIsFinalType,
69    /// The size of the chunk (message) including the header
70    pub message_size: u32,
71    /// Secure channel id
72    pub secure_channel_id: u32,
73}
74
75impl SimpleBinaryEncodable for MessageChunkHeader {
76    fn byte_len(&self) -> usize {
77        MESSAGE_CHUNK_HEADER_SIZE
78    }
79
80    fn encode<S: Write + ?Sized>(&self, stream: &mut S) -> EncodingResult<()> {
81        let message_type = match self.message_type {
82            MessageChunkType::Message => CHUNK_MESSAGE,
83            MessageChunkType::OpenSecureChannel => OPEN_SECURE_CHANNEL_MESSAGE,
84            MessageChunkType::CloseSecureChannel => CLOSE_SECURE_CHANNEL_MESSAGE,
85        };
86
87        let is_final = match self.is_final {
88            MessageIsFinalType::Intermediate => CHUNK_INTERMEDIATE,
89            MessageIsFinalType::Final => CHUNK_FINAL,
90            MessageIsFinalType::FinalError => CHUNK_FINAL_ERROR,
91        };
92
93        process_encode_io_result(stream.write_all(message_type))?;
94        write_u8(stream, is_final)?;
95        write_u32(stream, self.message_size)?;
96        write_u32(stream, self.secure_channel_id)
97    }
98}
99
100impl SimpleBinaryDecodable for MessageChunkHeader {
101    fn decode<S: Read + ?Sized>(stream: &mut S, _: &DecodingOptions) -> EncodingResult<Self> {
102        let mut message_type_code = [0u8; 3];
103        process_decode_io_result(stream.read_exact(&mut message_type_code))?;
104        let message_type = match &message_type_code as &[u8] {
105            CHUNK_MESSAGE => MessageChunkType::Message,
106            OPEN_SECURE_CHANNEL_MESSAGE => MessageChunkType::OpenSecureChannel,
107            CLOSE_SECURE_CHANNEL_MESSAGE => MessageChunkType::CloseSecureChannel,
108            r => {
109                return Err(Error::decoding(format!(
110                    "Invalid message chunk type: {r:?}"
111                )));
112            }
113        };
114
115        let chunk_type_code = read_u8(stream)?;
116        let is_final = match chunk_type_code {
117            CHUNK_FINAL => MessageIsFinalType::Final,
118            CHUNK_INTERMEDIATE => MessageIsFinalType::Intermediate,
119            CHUNK_FINAL_ERROR => MessageIsFinalType::FinalError,
120            r => {
121                return Err(Error::decoding(format!("Invalid message final type: {r}")));
122            }
123        };
124
125        let message_size = read_u32(stream)?;
126        let secure_channel_id = read_u32(stream)?;
127
128        Ok(MessageChunkHeader {
129            message_type,
130            is_final,
131            message_size,
132            secure_channel_id,
133        })
134    }
135}
136
137impl MessageChunkHeader {}
138
139/// A chunk holds a message or a portion of a message, if the message has been split into multiple chunks.
140/// The chunk's data may be signed and encrypted. To extract the message requires all the chunks
141/// to be available in sequence so they can be formed back into the message.
142#[derive(Debug)]
143pub struct MessageChunk {
144    /// All of the chunk's data including headers, payload, padding, signature
145    pub data: Vec<u8>,
146}
147
148impl SimpleBinaryEncodable for MessageChunk {
149    fn byte_len(&self) -> usize {
150        self.data.len()
151    }
152
153    fn encode<S: Write + ?Sized>(&self, stream: &mut S) -> EncodingResult<()> {
154        stream.write_all(&self.data).map_err(|e| {
155            Error::encoding(format!(
156                "Encoding error while writing message chunk to stream: {e}"
157            ))
158        })
159    }
160}
161
162impl SimpleBinaryDecodable for MessageChunk {
163    fn decode<S: Read + ?Sized>(
164        in_stream: &mut S,
165        decoding_options: &DecodingOptions,
166    ) -> EncodingResult<Self> {
167        // Read the header out first
168        let chunk_header =
169            MessageChunkHeader::decode(in_stream, decoding_options).map_err(|err| {
170                Error::new(
171                    StatusCode::BadCommunicationError,
172                    format!("Cannot decode chunk header {err:?}"),
173                )
174            })?;
175
176        let message_size = chunk_header.message_size as usize;
177        if decoding_options.max_message_size > 0 && message_size > decoding_options.max_message_size
178        {
179            // Message_size should be sanity checked and rejected if too large.
180            Err(Error::new(
181                StatusCode::BadTcpMessageTooLarge,
182                format!(
183                    "Message size {} exceeds maximum message size {}",
184                    message_size, decoding_options.max_message_size
185                ),
186            ))
187        } else {
188            // Now make a buffer to write the header and message into
189            let data = vec![0u8; message_size];
190            let mut stream = Cursor::new(data);
191
192            // Write header to a buffer
193            let chunk_header_size = chunk_header.byte_len();
194            chunk_header.encode(&mut stream)?;
195
196            // Get the data (with header written to it)
197            let mut data = stream.into_inner();
198
199            // Read remainder of stream into slice after the header
200            in_stream.read_exact(&mut data[chunk_header_size..])?;
201
202            Ok(MessageChunk { data })
203        }
204    }
205}
206
207#[derive(Debug)]
208/// Error returned if the chunk is too small, this indicates
209/// an error somewhere else.
210pub struct MessageChunkTooSmall;
211
212impl MessageChunk {
213    /// Create a new message chunk.
214    pub fn new(
215        sequence_number: u32,
216        request_id: u32,
217        message_type: MessageChunkType,
218        is_final: MessageIsFinalType,
219        secure_channel: &SecureChannel,
220        data: &[u8],
221    ) -> EncodingResult<MessageChunk> {
222        // security header depends on message type
223        let security_header = secure_channel.make_security_header(message_type);
224        let sequence_header = SequenceHeader {
225            sequence_number,
226            request_id,
227        };
228
229        // Calculate the chunk body size
230        let mut message_size = MESSAGE_CHUNK_HEADER_SIZE;
231        message_size += security_header.byte_len();
232        message_size += sequence_header.byte_len();
233        message_size += data.len();
234
235        trace!(
236            "Creating a chunk with a size of {}, data excluding padding & signature",
237            message_size
238        );
239        let secure_channel_id = secure_channel.secure_channel_id();
240        let chunk_header = MessageChunkHeader {
241            message_type,
242            is_final,
243            message_size: message_size as u32,
244            secure_channel_id,
245        };
246
247        let mut buf = vec![0u8; message_size];
248        let buf_ref = &mut buf as &mut [u8];
249        let mut stream = Cursor::new(buf_ref);
250        // write chunk header
251        chunk_header.encode(&mut stream)?;
252        // write security header
253        security_header.encode(&mut stream)?;
254        // write sequence header
255        sequence_header.encode(&mut stream)?;
256        // write message
257        stream.write_all(data)?;
258
259        Ok(MessageChunk { data: buf })
260    }
261
262    /// Calculates the body size that fit inside of a message chunk of a particular size.
263    /// This requires calculating the size of the header, the signature, padding etc. and deducting it
264    /// to reveal the message size
265    pub fn body_size_from_message_size(
266        message_type: MessageChunkType,
267        secure_channel: &SecureChannel,
268        max_chunk_size: usize,
269    ) -> Result<usize, Error> {
270        if max_chunk_size < MIN_CHUNK_SIZE {
271            return Err(Error::new(
272                StatusCode::BadTcpInternalError,
273                format!(
274                    "chunk size {} is less than minimum allowed by the spec",
275                    max_chunk_size
276                ),
277            ));
278        }
279        // First, get the size of the various message chunk headers.
280        let security_header = secure_channel.make_security_header(message_type);
281
282        let mut header_size = MESSAGE_CHUNK_HEADER_SIZE;
283        header_size += security_header.byte_len();
284        header_size += (SequenceHeader {
285            sequence_number: 0,
286            request_id: 0,
287        })
288        .byte_len();
289
290        // Next, get the size of the signature, which may be zero if the
291        // header isn't signed.
292        let signature_size = secure_channel.signature_size(&security_header);
293
294        // Get the encryption block size, and the minimum padding length.
295        // The minimum padding depends on the key length.
296        let (plain_text_block_size, minimum_padding) =
297            secure_channel.get_padding_block_sizes(&security_header, message_type)?;
298
299        // Compute the max chunk size aligned to an encryption block.
300        // When encrypting, the size must be a whole multiple of `plain_text_block_size`,
301        // we round the max chunk size down to the nearest such chunk.
302        let aligned_max_chunk_size = if plain_text_block_size > 0 {
303            max_chunk_size - (max_chunk_size % plain_text_block_size)
304        } else {
305            max_chunk_size
306        };
307        // So, the total maximum chunk body size is
308        // - the aligned chunk size,
309        // - minus the size of the message chunk headers.
310        // - minus the size of the signature.
311        // - minus the minimum padding.
312
313        // This means that if the chunk size is 8191, with 32 bytes header and 32 bytes signature,
314        // 1 minimum padding and 16 bytes block size,
315        // we get a chunk size of 8111. Add the header, 8143, add the signature
316        // 8175, pad with a minimum padding of 1 -> 8176.
317
318        // Note that if we used a chunk size of 8112 instead, we would end up with
319        // 8176 bytes after header and signature, and we would need to pad _16_ bytes,
320        // which would put us at 8192, which exceeds the configured limit.
321        // So 8111 is the largest the chunk can possibly be with this chunk size.
322
323        Ok(aligned_max_chunk_size - header_size - signature_size - minimum_padding)
324    }
325
326    /// Decode the message header from the inner data.
327    pub fn message_header(
328        &self,
329        decoding_options: &DecodingOptions,
330    ) -> EncodingResult<MessageChunkHeader> {
331        // Message header is first so just read it
332        let mut stream = Cursor::new(&self.data);
333        MessageChunkHeader::decode(&mut stream, decoding_options)
334    }
335
336    /// Check if this message is an OpenSecureChannel request.
337    pub fn is_open_secure_channel(&self, decoding_options: &DecodingOptions) -> bool {
338        if let Ok(message_header) = self.message_header(decoding_options) {
339            message_header.message_type.is_open_secure_channel()
340        } else {
341            false
342        }
343    }
344
345    /// Decode info about this chunk.
346    pub fn chunk_info(&self, secure_channel: &SecureChannel) -> EncodingResult<ChunkInfo> {
347        ChunkInfo::new(self, secure_channel)
348    }
349
350    pub(crate) fn encrypted_data_offset(
351        &self,
352        decoding_options: &DecodingOptions,
353    ) -> EncodingResult<usize> {
354        // Fetch just the encrypted data offset, which may be slightly more efficient than
355        // fetching the entire ChunkInfo struct.
356        let mut stream = Cursor::new(&self.data);
357        let message_header = MessageChunkHeader::decode(&mut stream, decoding_options)?;
358        SecurityHeader::decode_from_stream(
359            &mut stream,
360            message_header.message_type.is_open_secure_channel(),
361            decoding_options,
362        )?;
363        Ok(stream.position() as usize)
364    }
365}