use std::io::{Cursor, Read, Write};
use opcua_types::{
process_decode_io_result, process_encode_io_result, read_u32, read_u8, status_code::StatusCode,
write_u32, write_u8, DecodingOptions, EncodingResult, Error, SimpleBinaryDecodable,
SimpleBinaryEncodable, UAString,
};
use tracing::trace;
use super::{
message_chunk_info::ChunkInfo,
secure_channel::SecureChannel,
security_header::{SecurityHeader, SequenceHeader},
tcp_types::{
CHUNK_FINAL, CHUNK_FINAL_ERROR, CHUNK_INTERMEDIATE, CHUNK_MESSAGE,
CLOSE_SECURE_CHANNEL_MESSAGE, MIN_CHUNK_SIZE, OPEN_SECURE_CHANNEL_MESSAGE,
},
};
pub const MESSAGE_CHUNK_HEADER_SIZE: usize = 3 + 1 + 4 + 4;
pub const MESSAGE_SIZE_OFFSET: usize = 3 + 1;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum MessageChunkType {
Message,
OpenSecureChannel,
CloseSecureChannel,
}
impl MessageChunkType {
pub fn is_open_secure_channel(&self) -> bool {
*self == MessageChunkType::OpenSecureChannel
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum MessageIsFinalType {
Intermediate,
Final,
FinalError,
}
#[derive(Debug, Clone, PartialEq)]
pub struct MessageChunkHeader {
pub message_type: MessageChunkType,
pub is_final: MessageIsFinalType,
pub message_size: u32,
pub secure_channel_id: u32,
}
impl SimpleBinaryEncodable for MessageChunkHeader {
fn byte_len(&self) -> usize {
MESSAGE_CHUNK_HEADER_SIZE
}
fn encode<S: Write + ?Sized>(&self, stream: &mut S) -> EncodingResult<()> {
let message_type = match self.message_type {
MessageChunkType::Message => CHUNK_MESSAGE,
MessageChunkType::OpenSecureChannel => OPEN_SECURE_CHANNEL_MESSAGE,
MessageChunkType::CloseSecureChannel => CLOSE_SECURE_CHANNEL_MESSAGE,
};
let is_final = match self.is_final {
MessageIsFinalType::Intermediate => CHUNK_INTERMEDIATE,
MessageIsFinalType::Final => CHUNK_FINAL,
MessageIsFinalType::FinalError => CHUNK_FINAL_ERROR,
};
process_encode_io_result(stream.write_all(message_type))?;
write_u8(stream, is_final)?;
write_u32(stream, self.message_size)?;
write_u32(stream, self.secure_channel_id)
}
}
impl SimpleBinaryDecodable for MessageChunkHeader {
fn decode<S: Read + ?Sized>(stream: &mut S, _: &DecodingOptions) -> EncodingResult<Self> {
let mut message_type_code = [0u8; 3];
process_decode_io_result(stream.read_exact(&mut message_type_code))?;
let message_type = match &message_type_code as &[u8] {
CHUNK_MESSAGE => MessageChunkType::Message,
OPEN_SECURE_CHANNEL_MESSAGE => MessageChunkType::OpenSecureChannel,
CLOSE_SECURE_CHANNEL_MESSAGE => MessageChunkType::CloseSecureChannel,
r => {
return Err(Error::decoding(format!(
"Invalid message chunk type: {r:?}"
)));
}
};
let chunk_type_code = read_u8(stream)?;
let is_final = match chunk_type_code {
CHUNK_FINAL => MessageIsFinalType::Final,
CHUNK_INTERMEDIATE => MessageIsFinalType::Intermediate,
CHUNK_FINAL_ERROR => MessageIsFinalType::FinalError,
r => {
return Err(Error::decoding(format!("Invalid message final type: {r}")));
}
};
let message_size = read_u32(stream)?;
let secure_channel_id = read_u32(stream)?;
Ok(MessageChunkHeader {
message_type,
is_final,
message_size,
secure_channel_id,
})
}
}
impl MessageChunkHeader {}
#[derive(Debug, Clone)]
pub struct MessageChunk {
pub data: bytes::Bytes,
}
impl SimpleBinaryEncodable for MessageChunk {
fn byte_len(&self) -> usize {
self.data.len()
}
fn encode<S: Write + ?Sized>(&self, stream: &mut S) -> EncodingResult<()> {
stream.write_all(&self.data).map_err(|e| {
Error::encoding(format!(
"Encoding error while writing message chunk to stream: {e}"
))
})
}
}
impl SimpleBinaryDecodable for MessageChunk {
fn decode<S: Read + ?Sized>(
in_stream: &mut S,
decoding_options: &DecodingOptions,
) -> EncodingResult<Self> {
let chunk_header = MessageChunkHeader::decode(in_stream, decoding_options)
.map_err(|err| Error::decoding(format!("Cannot decode chunk header {err:?}")))?;
let message_size = chunk_header.message_size as usize;
if decoding_options.max_message_size > 0 && message_size > decoding_options.max_message_size
{
Err(Error::new(
StatusCode::BadTcpMessageTooLarge,
format!(
"Message size {} exceeds maximum message size {}",
message_size, decoding_options.max_message_size
),
))
} else {
let data = vec![0u8; message_size];
let mut stream = Cursor::new(data);
let chunk_header_size = chunk_header.byte_len();
chunk_header.encode(&mut stream)?;
let mut data = stream.into_inner();
in_stream.read_exact(&mut data[chunk_header_size..])?;
Ok(MessageChunk {
data: bytes::Bytes::from(data),
})
}
}
}
#[derive(Debug)]
pub struct MessageChunkTooSmall;
impl MessageChunk {
pub fn new(
sequence_number: u32,
request_id: u32,
message_type: MessageChunkType,
is_final: MessageIsFinalType,
secure_channel: &SecureChannel,
data: &[u8],
) -> EncodingResult<MessageChunk> {
let security_header = secure_channel.make_security_header(message_type);
let sequence_header = SequenceHeader {
sequence_number,
request_id,
};
let mut message_size = MESSAGE_CHUNK_HEADER_SIZE;
message_size += security_header.byte_len();
message_size += sequence_header.byte_len();
message_size += data.len();
trace!(
"Creating a chunk with a size of {}, data excluding padding & signature",
message_size
);
let secure_channel_id = secure_channel.secure_channel_id();
let chunk_header = MessageChunkHeader {
message_type,
is_final,
message_size: message_size as u32,
secure_channel_id,
};
let mut buf = vec![0u8; message_size];
let buf_ref = &mut buf as &mut [u8];
let mut stream = Cursor::new(buf_ref);
chunk_header.encode(&mut stream)?;
security_header.encode(&mut stream)?;
sequence_header.encode(&mut stream)?;
stream.write_all(data)?;
Ok(MessageChunk {
data: bytes::Bytes::from(buf),
})
}
pub fn body_size_from_message_size(
message_type: MessageChunkType,
secure_channel: &SecureChannel,
max_chunk_size: usize,
) -> Result<usize, Error> {
if max_chunk_size < MIN_CHUNK_SIZE {
return Err(Error::new(
StatusCode::BadTcpInternalError,
format!(
"chunk size {} is less than minimum allowed by the spec",
max_chunk_size
),
));
}
let security_header = secure_channel.make_security_header(message_type);
let mut header_size = MESSAGE_CHUNK_HEADER_SIZE;
header_size += security_header.byte_len();
header_size += (SequenceHeader {
sequence_number: 0,
request_id: 0,
})
.byte_len();
let signature_size = secure_channel.signature_size(&security_header);
let (plain_text_block_size, minimum_padding) =
secure_channel.get_padding_block_sizes(&security_header, message_type)?;
let aligned_max_chunk_size = if plain_text_block_size > 0 {
max_chunk_size - (max_chunk_size % plain_text_block_size)
} else {
max_chunk_size
};
Ok(aligned_max_chunk_size - header_size - signature_size - minimum_padding)
}
pub fn message_header(
&self,
decoding_options: &DecodingOptions,
) -> EncodingResult<MessageChunkHeader> {
let mut stream = Cursor::new(&self.data);
MessageChunkHeader::decode(&mut stream, decoding_options)
}
pub fn is_open_secure_channel(&self, decoding_options: &DecodingOptions) -> bool {
if let Ok(message_header) = self.message_header(decoding_options) {
message_header.message_type.is_open_secure_channel()
} else {
false
}
}
pub fn chunk_info(&self, secure_channel: &SecureChannel) -> EncodingResult<ChunkInfo> {
ChunkInfo::new(self, secure_channel)
}
pub fn final_error_body(
&self,
chunk_info: &ChunkInfo,
decoding_options: &DecodingOptions,
) -> Result<MessageFinalError, Error> {
let start = chunk_info.body_offset;
let end = start.checked_add(chunk_info.body_length).ok_or_else(|| {
Error::new(
StatusCode::BadCommunicationError,
"Invalid error body length",
)
})?;
let body = self.data.get(start..end).ok_or_else(|| {
Error::new(
StatusCode::BadCommunicationError,
"Invalid error body length",
)
})?;
let mut cursor = std::io::Cursor::new(body);
MessageFinalError::decode(&mut cursor, decoding_options)
}
pub(crate) fn encrypted_data_offset(
&self,
decoding_options: &DecodingOptions,
) -> EncodingResult<usize> {
let mut stream = Cursor::new(&self.data);
let message_header = MessageChunkHeader::decode(&mut stream, decoding_options)?;
SecurityHeader::decode_from_stream(
&mut stream,
message_header.message_type.is_open_secure_channel(),
decoding_options,
)?;
Ok(stream.position() as usize)
}
}
#[derive(Debug)]
pub struct MessageFinalError {
pub status: StatusCode,
pub reason: UAString,
}
impl SimpleBinaryDecodable for MessageFinalError {
fn decode<S: Read + ?Sized>(
stream: &mut S,
decoding_options: &DecodingOptions,
) -> EncodingResult<Self> {
let status = StatusCode::decode(stream, decoding_options)?;
let reason = UAString::decode(stream, decoding_options)?;
Ok(Self { status, reason })
}
}