diff --git a/DtronixMessageQueue.Tests/DtronixMessageQueue.Tests.csproj b/DtronixMessageQueue.Tests/DtronixMessageQueue.Tests.csproj index 0645e80..1276ba4 100644 --- a/DtronixMessageQueue.Tests/DtronixMessageQueue.Tests.csproj +++ b/DtronixMessageQueue.Tests/DtronixMessageQueue.Tests.csproj @@ -112,7 +112,7 @@ - + diff --git a/DtronixMessageQueue.Tests/MqMessageBuilderReaderTests.cs b/DtronixMessageQueue.Tests/MqMessageWriterReaderTests.cs similarity index 81% rename from DtronixMessageQueue.Tests/MqMessageBuilderReaderTests.cs rename to DtronixMessageQueue.Tests/MqMessageWriterReaderTests.cs index 4e6bb6a..03666cd 100644 --- a/DtronixMessageQueue.Tests/MqMessageBuilderReaderTests.cs +++ b/DtronixMessageQueue.Tests/MqMessageWriterReaderTests.cs @@ -5,21 +5,21 @@ using Assert = Xunit.Assert; namespace DtronixMessageQueue.Tests { - public class MqMessageBuilderReaderTests { + public class MqMessageWriterReaderTests { public ITestOutputHelper Output; private MqMessageWriter message_builder; private MqMessageReader message_reader; private const string FillerText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."; - public MqMessageBuilderReaderTests(ITestOutputHelper output) { + public MqMessageWriterReaderTests(ITestOutputHelper output) { this.Output = output; message_builder = new MqMessageWriter(); message_reader = new MqMessageReader(); } [Fact] - public void MessageBuilder_writes_bool_true() { + public void MessageWriter_writes_bool_true() { var expected_value = (bool) true; message_builder.Write(expected_value); var message = message_builder.ToMessage(); @@ -30,7 +30,7 @@ public void MessageBuilder_writes_bool_true() { } [Fact] - public void MessageBuilder_writes_bool_false() { + public void MessageWriter_writes_bool_false() { var expected_value = (bool)false; message_builder.Write(expected_value); var message = message_builder.ToMessage(); @@ -41,7 +41,7 @@ public void MessageBuilder_writes_bool_false() { } [Fact] - public void MessageBuilder_writes_byte() { + public void MessageWriter_writes_byte() { var expected_value = (byte)221; message_builder.Write(expected_value); var message = message_builder.ToMessage(); @@ -53,7 +53,7 @@ public void MessageBuilder_writes_byte() { } [Fact] - public void MessageBuilder_writes_sbyte_positive() { + public void MessageWriter_writes_sbyte_positive() { var expected_value = (sbyte)101; message_builder.Write(expected_value); var message = message_builder.ToMessage(); @@ -64,7 +64,7 @@ public void MessageBuilder_writes_sbyte_positive() { } [Fact] - public void MessageBuilder_writes_sbyte_negative() { + public void MessageWriter_writes_sbyte_negative() { var expected_value = (sbyte)-101; message_builder.Write(expected_value); var message = message_builder.ToMessage(); @@ -75,18 +75,7 @@ public void MessageBuilder_writes_sbyte_negative() { } [Fact] - public void MessageBuilder_writes_char() { - var expected_value = (char)'D'; - message_builder.Write(expected_value); - var message = message_builder.ToMessage(); - message_reader.Message = message; - - Assert.Equal(expected_value, message_reader.ReadChar()); - Assert.True(message_reader.IsAtEnd); - } - - [Fact] - public void MessageBuilder_writes_short_positive() { + public void MessageWriter_writes_short_positive() { var expected_value = (short)21457; message_builder.Write(expected_value); var message = message_builder.ToMessage(); @@ -97,7 +86,7 @@ public void MessageBuilder_writes_short_positive() { } [Fact] - public void MessageBuilder_writes_short_negative() { + public void MessageWriter_writes_short_negative() { var expected_value = (short)-21457; message_builder.Write(expected_value); var message = message_builder.ToMessage(); @@ -108,7 +97,7 @@ public void MessageBuilder_writes_short_negative() { } [Fact] - public void MessageBuilder_writes_ushort() { + public void MessageWriter_writes_ushort() { var expected_value = (ushort)51574; message_builder.Write(expected_value); var message = message_builder.ToMessage(); @@ -119,7 +108,7 @@ public void MessageBuilder_writes_ushort() { } [Fact] - public void MessageBuilder_writes_int_positive() { + public void MessageWriter_writes_int_positive() { var expected_value = (int)515725234; message_builder.Write(expected_value); var message = message_builder.ToMessage(); @@ -130,7 +119,7 @@ public void MessageBuilder_writes_int_positive() { } [Fact] - public void MessageBuilder_writes_int_negative() { + public void MessageWriter_writes_int_negative() { var expected_value = (int)-515725234; message_builder.Write(expected_value); var message = message_builder.ToMessage(); @@ -141,7 +130,7 @@ public void MessageBuilder_writes_int_negative() { } [Fact] - public void MessageBuilder_writes_uint() { + public void MessageWriter_writes_uint() { var expected_value = (uint)1215725234; message_builder.Write(expected_value); var message = message_builder.ToMessage(); @@ -152,7 +141,7 @@ public void MessageBuilder_writes_uint() { } [Fact] - public void MessageBuilder_writes_long_positive() { + public void MessageWriter_writes_long_positive() { var expected_value = (long)515352135236725234; message_builder.Write(expected_value); var message = message_builder.ToMessage(); @@ -163,7 +152,7 @@ public void MessageBuilder_writes_long_positive() { } [Fact] - public void MessageBuilder_writes_long_negative() { + public void MessageWriter_writes_long_negative() { var expected_value = (long)-515352135236725234; message_builder.Write(expected_value); var message = message_builder.ToMessage(); @@ -174,7 +163,7 @@ public void MessageBuilder_writes_long_negative() { } [Fact] - public void MessageBuilder_writes_ulong() { + public void MessageWriter_writes_ulong() { var expected_value = (ulong)12231512365365725234; message_builder.Write(expected_value); var message = message_builder.ToMessage(); @@ -185,7 +174,7 @@ public void MessageBuilder_writes_ulong() { } [Fact] - public void MessageBuilder_writes_float() { + public void MessageWriter_writes_float() { var expected_value = (float)123.456; message_builder.Write(expected_value); var message = message_builder.ToMessage(); @@ -196,7 +185,7 @@ public void MessageBuilder_writes_float() { } [Fact] - public void MessageBuilder_writes_double() { + public void MessageWriter_writes_double() { var expected_value = (double)12345.67891; message_builder.Write(expected_value); var message = message_builder.ToMessage(); @@ -207,7 +196,7 @@ public void MessageBuilder_writes_double() { } [Fact] - public void MessageBuilder_writes_decimal() { + public void MessageWriter_writes_decimal() { var expected_value = (decimal)9123456789123456789.9123456789123456789; message_builder.Write(expected_value); var message = message_builder.ToMessage(); @@ -218,7 +207,7 @@ public void MessageBuilder_writes_decimal() { } [Fact] - public void MessageBuilder_writes_multi_frame_byte_array() { + public void MessageWriter_writes_multi_frame_byte_array() { var expected_value = new byte[1024*32]; var number = 0; for (int i = 0; i < 1024 * 32; i++) { @@ -254,7 +243,7 @@ private void VerifyMessageBytes(byte[] expected_value, MqMessage message) { } [Fact] - public void MessageBuilder_writes_multi_frame_string_bytes() { + public void MessageWriter_writes_multi_frame_string_bytes() { var sb = new StringBuilder(); for (int i = 0; i < 100; i++) { @@ -302,7 +291,7 @@ public void MessageReader_reads_multi_frame_byte_array() { [Fact] - public void MessageBuilder_writes_string() { + public void MessageWriter_writes_string() { var expected_value = FillerText; message_builder.Write(expected_value); var message = message_builder.ToMessage(); @@ -339,6 +328,7 @@ public void MessageWriter_multiple_reads_writes() { message_builder.Write(false); message_builder.Write((char)'D'); + message_builder.Write(new char[] { 'A', 'Y', 'X', '0', '9', '8'}); message_builder.Write((byte)214); message_builder.Write((sbyte)125); @@ -373,6 +363,8 @@ public void MessageWriter_multiple_reads_writes() { Assert.Equal(false, message_reader.ReadBoolean()); Assert.Equal('D', message_reader.ReadChar()); + Assert.Equal(new char[] { 'A', 'Y', 'X', '0', '9', '8' }, message_reader.ReadChars(6)); + Assert.Equal((byte)214, message_reader.ReadByte()); Assert.Equal((sbyte)125, message_reader.ReadSByte()); @@ -399,7 +391,57 @@ public void MessageWriter_multiple_reads_writes() { Assert.Equal(expected_byte_array, read_byte_array); Assert.Equal(FillerText, message_reader.ReadString()); + Assert.True(message_reader.IsAtEnd); + } + + + [Fact] + public void MessageWriter_writes_char() { + var expected_value = (char)'D'; + message_builder.Write(expected_value); + var message = message_builder.ToMessage(); + message_reader.Message = message; + + Assert.Equal(expected_value, message_reader.ReadChar()); + Assert.True(message_reader.IsAtEnd); + } + + [Fact] + public void MessageWriter_writes_char_array() { + var expected_value = new char[] {'A', 'B', 'C', '1', '2', '3'}; + message_builder.Write(expected_value); + var message = message_builder.ToMessage(); + message_reader.Message = message; + + Assert.Equal(expected_value, message_reader.ReadChars(expected_value.Length)); + Assert.True(message_reader.IsAtEnd); + } + + [Fact] + public void MessageWriter_writes_char_array_slice() { + var input_value = new char[] { 'A', 'B', 'C', '1', '2', '3' }; + var expected_value = new char[] { 'B', 'C', '1', '2'}; + message_builder.Write(input_value, 1, 4); + var message = message_builder.ToMessage(); + message_reader.Message = message; + + Assert.Equal(expected_value, message_reader.ReadChars(expected_value.Length)); + Assert.True(message_reader.IsAtEnd); + } + + [Fact] + public void MessageReader_peeks_char() { + var expected_value = new char[] { 'D', 'Z' }; + message_builder.Write(expected_value); + var message = message_builder.ToMessage(); + message_reader.Message = message; + + Assert.Equal(expected_value[0], message_reader.PeekChar()); + Assert.Equal(expected_value, message_reader.ReadChars(expected_value.Length)); + Assert.True(message_reader.IsAtEnd); } + + } } \ No newline at end of file diff --git a/DtronixMessageQueue/MqMessageReader.cs b/DtronixMessageQueue/MqMessageReader.cs index ea35df5..448ee45 100644 --- a/DtronixMessageQueue/MqMessageReader.cs +++ b/DtronixMessageQueue/MqMessageReader.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.Contracts; using System.IO; using System.Linq; using System.Text; @@ -7,6 +8,7 @@ namespace DtronixMessageQueue { public class MqMessageReader : BinaryReader { + private readonly Encoding encoding; private int position; private MqMessage message; @@ -14,6 +16,25 @@ public class MqMessageReader : BinaryReader { private int message_position; + private readonly bool two_bytes_per_char; + + private const int MaxCharBytesSize = 128; + + private byte[] char_bytes; + + + private readonly Decoder decoder; + + /// + /// Current frame that is being read. + /// + public MqFrame CurrentFrame => current_frame; + + + /// + /// Gets the current message. + /// Setting a message resets reading positions to the beginning. + /// public MqMessage Message { get { return message; } set { @@ -24,21 +45,46 @@ public MqMessage Message { } } + /// + /// Unused. Stream.Null + /// + public override Stream BaseStream { get; } = Stream.Null; + + /// + /// True if we are at the end of the last frame of the message. + /// public bool IsAtEnd { get { var last_frame = message.Frames[message.Frames.Count - 1]; return current_frame == last_frame && last_frame.DataLength == position; - } } + + /// + /// Creates a new message reader with no message and the default encoding of UTF8. + /// public MqMessageReader() : this(null){ } - public MqMessageReader(MqMessage initial_message) : base(Stream.Null) { - Message = initial_message; + /// + /// Creates a new message reader with the specified message to read and the default encoding of UTF8. + /// + /// Message to read. + public MqMessageReader(MqMessage initial_message) : this(initial_message, Encoding.UTF8) { } + /// + /// Creates a new message reader with the specified message to read and the specified encoding. + /// + /// Message to read. + /// Encoding to use for string interpretation. + public MqMessageReader(MqMessage initial_message, Encoding encoding) : base(Stream.Null) { + this.encoding = encoding; + decoder = this.encoding.GetDecoder(); + two_bytes_per_char = encoding is UnicodeEncoding; + Message = initial_message; + } private void EnsureBuffer(int length) { @@ -62,6 +108,15 @@ private void NextNonEmptyFrame() { } + /// + /// Advances the reader to the next frame and resets reading positions. + /// + public void NextFrame() { + position = 0; + message_position++; + current_frame = message[message_position]; + } + /// /// Reads a boolean value. /// 1 Byte. @@ -96,33 +151,114 @@ public override sbyte ReadSByte() { return value; } + /// /// Reads a char value. - /// 1 Byte. + /// >=1 Byte. /// public override char ReadChar() { - EnsureBuffer(1); - var value = current_frame.ReadChar(position); - position += 1; - return value; + var read_char = new char[1]; + Read(read_char, 0, 1); + return read_char[0]; } + /// - /// Peeks at the next char value. - /// 1 Byte. + /// Reads the specified number of chars from the message. + /// >1 Byte. + /// 1 or more frames. /// - public override int PeekChar() { - EnsureBuffer(1); - var value = current_frame.ReadChar(position); - return value; + /// Number of chars to read. + /// Char array. + public override char[] ReadChars(int count) { + var chars = new char[count]; + Read(chars, 0, count); + return chars; } - protected override void FillBuffer(int numBytes) { - throw new NotImplementedException(); + + /// + /// Reads the specified number of chars from the message into the passed char buffer. + /// + /// Buffer of chars to copy into. + /// Starting index start copying the chars into. + /// Number of chars to read. + /// Number of chars read from the message. Can be less than requested if the end of the message is reached. + public override int Read(char[] buffer, int index, int count) { + var chars_remaining = count; + + if (char_bytes == null) { + char_bytes = new byte[MaxCharBytesSize]; + } + + while (chars_remaining > 0) { + var chars_read = 0; + // We really want to know what the minimum number of bytes per char + // is for our encoding. Otherwise for UnicodeEncoding we'd have to + // do ~1+log(n) reads to read n characters. + var num_bytes = chars_remaining; + + + // TODO: special case for UTF8Decoder when there are residual bytes from previous loop + /*UTF8Encoding.UTF8Decoder decoder = m_decoder as UTF8Encoding.UTF8Decoder; + if (decoder != null && decoder.HasState && numBytes > 1) { + numBytes -= 1; + }*/ + + + if (two_bytes_per_char) { + num_bytes <<= 1; + } + + if (num_bytes > MaxCharBytesSize) { + num_bytes = MaxCharBytesSize; + } + + var char_position = 0; + + num_bytes = Read(char_bytes, 0, num_bytes); + var byte_buffer = char_bytes; + + + if (num_bytes == 0) { + return count - chars_remaining; + } + + unsafe + { + fixed (byte* bytes = byte_buffer) + fixed (char* chars = buffer) { + chars_read = decoder.GetChars(bytes + char_position, num_bytes, chars + index, chars_remaining, false); + } + } + + chars_remaining -= chars_read; + index += chars_read; + } + + // we may have read fewer than the number of characters requested if end of stream reached + // or if the encoding makes the char count too big for the buffer (e.g. fallback sequence) + return count - chars_remaining; } - public override int Read() { - ReadInt32(); + /// + /// Peeks at the next char value. + /// >=1 Byte. + /// + public override int PeekChar() { + // Store the temporary state of the reader. + var previous_frame = current_frame; + var previous_position = position; + var previous_message_position = message_position; + + var value = ReadChar(); + + // Restore the original state of the reader. + current_frame = previous_frame; + position = previous_position; + message_position = previous_message_position; + + return (int) value; } /// @@ -148,6 +284,15 @@ public override ushort ReadUInt16() { } + /// + /// Reads a int value. + /// 4 Bytes. + /// + public override int Read() { + return ReadInt32(); + } + + /// /// Reads a int value. /// 4 Bytes. @@ -241,10 +386,23 @@ public override string ReadString() { var str_buffer = new byte[str_len]; Read(str_buffer, 0, str_len); - return Encoding.UTF8.GetString(str_buffer); + return encoding.GetString(str_buffer); } + /// + /// Reads the specified number of bytes from the message. + /// >1 Byte. + /// 1 or more frames. + /// + /// Number of bytes to read. + /// Filled byte array with the data from the message. + public override byte[] ReadBytes(int count) { + var bytes = new byte[count]; + Read(bytes, 0, count); + return bytes; + } + /// /// Reads the bytes from this message. /// diff --git a/DtronixMessageQueue/MqMessageWriter.cs b/DtronixMessageQueue/MqMessageWriter.cs index c427a2b..6df2b49 100644 --- a/DtronixMessageQueue/MqMessageWriter.cs +++ b/DtronixMessageQueue/MqMessageWriter.cs @@ -11,15 +11,44 @@ namespace DtronixMessageQueue { /// Builder to aid in the creation of messages and their frames. /// public class MqMessageWriter : BinaryWriter { + + /// + /// Encoding used for chars and strings + /// + private readonly Encoding encoding; + + /// + /// Internal set position for the builder_frame. + /// private int position = 0; + /// + /// List of frames for the message. + /// private readonly List frames = new List(); + /// + /// Internal frame used to write data to and copy from. + /// private readonly MqFrame builder_frame; + /// + /// Unused. Stream.Null + /// public override Stream BaseStream { get; } = Stream.Null; - public MqMessageWriter() { + /// + /// Creates a new message writer with the default encoding. + /// + public MqMessageWriter() : this(Encoding.UTF8) { + } + + /// + /// Creates a new message writer with the specified encoding. + /// + /// + public MqMessageWriter(Encoding encoding) { + this.encoding = encoding; builder_frame = new MqFrame(new byte[MqFrame.MaxFrameSize], MqFrameType.More); } @@ -30,7 +59,9 @@ private void EnsureSpace(int length) { } } - + /// + /// Closes the current frame. If no data has been written is empty, creates an empty frame. + /// public void FinalizeFrame() { if (position == 0) { frames.Add(new MqFrame(null, MqFrameType.Empty)); @@ -39,6 +70,9 @@ public void FinalizeFrame() { } } + /// + /// Copies the current data in the builder_frame into a new frame of the correct size. Resets position to 0. + /// private void InternalFinalizeFrame() { if (position == 0) { throw new InvalidOperationException("Can not finalize frame when it is empty."); @@ -87,6 +121,39 @@ public override void Write(sbyte value) { } + /// + /// Writes a char value. + /// >=1 Byte. + /// + /// Value to write to the message. + public override void Write(char value) { + Write(new[] {value}); + } + + /// + /// Writes a whole character array to this one or more frames. + /// >=1 byte + /// + /// + public override void Write(char[] chars) { + byte[] bytes = encoding.GetBytes(chars); + Write(bytes); + } + + /// + /// Writes a character array to this one or more frames. + /// >=1 Byte + /// 1 or more frames. + /// + /// Character array to write to the + /// Offset in the buffer to write from + /// Number of bytes to write to the message from the buffer. + public override void Write(char[] chars, int index, int count) { + byte[] bytes = encoding.GetBytes(chars, index, count); + Write(bytes); + } + + /// /// Writes a short value. /// 2 Bytes. @@ -111,8 +178,6 @@ public override void Write(ushort value) { } - - /// /// Writes a int value. /// 4 Bytes. @@ -196,19 +261,6 @@ public override void Write(decimal value) { position += 16; } - - /// - /// Writes a char value. - /// 1 Byte. - /// - /// Value to write to the message. - public override void Write(char value) { - EnsureSpace(1); - builder_frame.Write(position, value); - position += 1; - } - - /// /// Appends an existing message to this message. /// @@ -255,11 +307,13 @@ public override void Write(string value) { /// /// Writes a byte array to this one or more frames. + /// >1 Byte. + /// 1 or more frames. /// /// Buffer to write to the message. - /// Offset in the buffer to write from + /// Offset in the buffer to write from /// Number of bytes to write to the message from the buffer. - public override void Write(byte[] buffer, int offset, int count) { + public override void Write(byte[] buffer, int index, int count) { int buffer_left = count; while (buffer_left > 0) { var max_write_length = builder_frame.DataLength - position; @@ -271,9 +325,9 @@ public override void Write(byte[] buffer, int offset, int count) { continue; } - builder_frame.Write(position, buffer, offset, write_length); + builder_frame.Write(position, buffer, index, write_length); position += write_length; - offset += write_length; + index += write_length; buffer_left -= write_length; //return; @@ -289,23 +343,6 @@ public override void Write(byte[] buffer) { Write(buffer, 0, buffer.Length); } - /// - /// Writes a whole character array to this one or more frames. - /// - /// - public override void Write(char[] chars) { - Write(new string(chars)); - } - - /// - /// Writes a character array to this one or more frames. - /// - /// Character array to write to the - /// Offset in the buffer to write from - /// Number of bytes to write to the message from the buffer. - public override void Write(char[] chars, int offset, int count) { - Write(new string(chars, offset, count)); - } /// /// Seeking is disabled.