protected virtual byte[] ReadTo(params byte[] mark)
{
// By using byte[] as a marker instead of char[] or string gives us the advantage of processing "lines" in binary data, however,
// it definitely limits us to the expanding encondings only. That means, this will not work in the case encoding packs more characters into single byte.
// We could solve this eg. by params char[] mark override equivalent, but current limitations of decoders (eg. Utf8Decoder class) makes such parsing too complicated nowadays.
if (mark == null) throw new ArgumentNullException("mark");
if (mark.Length == 0) throw new ArgumentException("Mark must have non-zero length.");
if (IsReading) // DataReceived event is being requested so we use internal read buffer rather than the serial port directly
{
_readToEvent = new AutoResetEvent(false); // creates an AutoResetEvent so that the OnDataReceived event handler can signal us that new data are available to check
Timer readToTimeout = null; bool timedOut = false;
if (ReadTimeout > 0)
readToTimeout = new Timer(delegate { timedOut = true; _readToEvent.Set(); }, null, ReadTimeout, 0);
while (IsReading)
{
lock (_bufferSync)
{
int markIndex = BufferIndexOf(mark); // look for mark in the received data
if (markIndex >= 0) // If found,
{
byte[] receivedData = GetBufferedData(markIndex); // read the data up to the mark,
AdvancePosition(markIndex + mark.Length); // and remove them from the buffer.
if (readToTimeout != null)
readToTimeout.Dispose(); // We are finished, so cancel the timeout timer, if applicable.
return receivedData;
}
}
if (ReadTimeout == 0) // If the user does not want to wait and we don't have a line, return null.
return null;
_readToEvent.WaitOne(); // Wait until the OnDataReceived handler signals us there are new data available to check,
// or until the timeout timer signals us.
if (timedOut)
return null; // If it was the timer, return null.
}
// Here we are if the DataReceived event was being requested upon calling this method, but all subscribers has detached before any line marker came in.
_readToEvent = null; // do some cleaning of stuff we don't need for the direct serial port manipulation
if (timedOut) return null;
else
if (readToTimeout != null)
readToTimeout.Dispose();
}
byte[] data = new byte[System.Math.Max(_incomingBufferValidLength, _readBufferSize) + mark.Length];
int offset = GetBufferedData(data, 0, _incomingBufferValidLength); // read any data which left in the internal read buffer
int markSearchStart = 0;
while (true)
{
if (offset >= data.Length) // If we have filled the buffer, make a bigger one!
{ // (the >= is for paranoia reasons, the offset never becomes greater than data.Length in this method)
byte[] biggerData = new byte[data.Length * 2];
data.CopyTo(biggerData, 0);
data = biggerData;
}
int read = ReadDirect(data, offset, data.Length - offset); // read as much data from serial port as fits in our buffer
if (read < 1)
return null; // the operation has timed out
offset += read; // offset now points to where next read should start, or in other words valid length
int markPos = Array.IndexOf(data, mark[0], markSearchStart, offset - markSearchStart); // try to find the first byte of mark in the buffer
if (markPos < 0)
markSearchStart = offset; // we didn't find it, there is no reason to search the whole buffer again next time
else
{ // okay, we have the first byte
if (markPos + mark.Length <= offset) // do we have enough data in the buffer that whole mark could fit in?
{
int i = 1;
for (i = 1; i < mark.Length; i++) // if so, check if the next bytes in buffer match the mark bytes
if (data[markPos + i] != mark[i]) break;
if (i >= mark.Length)
{
byte[] finalData = new byte[markPos]; // if they do, copy data before marker into the new array
Array.Copy(data, 0, finalData, 0, markPos);
int remains = offset - markPos - mark.Length;
if (remains > 0) // If we grabbed any data we haven't used,
lock (_bufferSync) // push it to the internal read buffer (we are going to return now).
{
if (_incomingBuffer.Length < remains) // make enough space if the internal buffer is too small to store remaining data
_incomingBuffer = new byte[remains]; // We have already read all data that where in the buffer before the while loop,
// so it is okay to lose any current data.
Array.Copy(data, offset - remains, _incomingBuffer, 0, remains);
_incomingBufferPosition = 0; // And so we are storing at the beginning of the circular buffer.
_incomingBufferValidLength = remains;
}
return finalData;
}
else // If the other bytes do not match the mark bytes, it is not part of the mark,
markSearchStart = markPos + 1; // and start the next search at the next position.
}
else
markSearchStart = markPos; // We don't know if this is marker or not, so try again this position with more data.
}
}
}