/// <summary> /// Forms a slice out of the given <see cref="ReadOnlyBuffer"/>, beginning at 'start', ending at 'end' (inclusive). /// </summary> /// <param name="start">The starting (inclusive) <see cref="Position"/> at which to begin this slice.</param> /// <param name="end">The ending (inclusive) <see cref="Position"/> of the slice</param> public ReadOnlyBuffer Slice(Position start, Position end) { ReadCursorOperations.BoundsCheck(BufferEnd, end); ReadCursorOperations.BoundsCheck(end, start); return(new ReadOnlyBuffer(start, end)); }
/// <summary> /// Forms a slice out of the given <see cref="ReadOnlyBuffer"/>, beginning at 'start', and is at most length bytes /// </summary> /// <param name="start">The index at which to begin this slice.</param> /// <param name="length">The length of the slice</param> public ReadOnlyBuffer Slice(long start, long length) { var begin = ReadCursorOperations.Seek(BufferStart, BufferEnd, start, false); var end = ReadCursorOperations.Seek(begin, BufferEnd, length, false); return(new ReadOnlyBuffer(begin, end)); }
/// <summary> /// Forms a slice out of the given <see cref="ReadOnlyBuffer"/>, beginning at 'start', ending at 'end' (inclusive). /// </summary> /// <param name="start">The index at which to begin this slice.</param> /// <param name="end">The end (inclusive) of the slice</param> public ReadOnlyBuffer Slice(long start, Position end) { ReadCursorOperations.BoundsCheck(BufferEnd, end); var begin = ReadCursorOperations.Seek(BufferStart, end, start); return(new ReadOnlyBuffer(begin, end)); }
// This test verifies that optimization for known cursors works and // avoids additional walk but it's only valid for multi segmented buffers public void ReadCursorSeekDoesNotCheckEndIfTrustingEnd() { var buffer = Factory.CreateOfSize(3); var buffer2 = Factory.CreateOfSize(3); ReadCursorOperations.Seek(buffer.Start, buffer2.End, 2, false); }
public void MemorySeek(string raw, string search, char expectResult, int expectIndex) { var cursors = BufferUtilities.CreateBuffer(raw); ReadCursor start = cursors.Start; ReadCursor end = cursors.End; ReadCursor result = default; var searchFor = search.ToCharArray(); int found = -1; if (searchFor.Length == 1) { found = ReadCursorOperations.Seek(start, end, out result, (byte)searchFor[0]); } else if (searchFor.Length == 2) { found = ReadCursorOperations.Seek(start, end, out result, (byte)searchFor[0], (byte)searchFor[1]); } else if (searchFor.Length == 3) { found = ReadCursorOperations.Seek(start, end, out result, (byte)searchFor[0], (byte)searchFor[1], (byte)searchFor[2]); } else { Assert.False(true, "Invalid test sample."); } Assert.Equal(expectResult, found); Assert.Equal(expectIndex, result.Index - start.Index); }
public void ReadCursorSeekChecksEndIfNotTrustingEnd() { var buffer = Factory.CreateOfSize(3); var buffer2 = Factory.CreateOfSize(3); Assert.Throws <InvalidOperationException>(() => ReadCursorOperations.Seek(buffer.Start, buffer2.End, 2, true)); }
public void TestSeekByteLimitAcrossBlocks(string input, char seek, int limit, int expectedBytesScanned, int expectedReturnValue) { // Arrange var input1 = input.Substring(0, input.Length / 2); var input2 = input.Substring(input.Length / 2); var buffer = BufferUtilities.CreateBuffer(input1, string.Empty, input2); var block1 = buffer.Start; var block2 = buffer.End; // Act ReadCursor result; var end = limit > input.Length ? buffer.End : buffer.Slice(0, limit).End; var returnValue = ReadCursorOperations.Seek(buffer.Start, end, out result, (byte)seek); var returnValue_1 = ReadCursorOperations.Seek(buffer.Start, end, out result, (byte)seek, (byte)seek); var returnValue_2 = ReadCursorOperations.Seek(buffer.Start, end, out result, (byte)seek, (byte)seek, (byte)seek); // Assert Assert.Equal(expectedReturnValue, returnValue); Assert.Equal(expectedReturnValue, returnValue_1); Assert.Equal(expectedReturnValue, returnValue_2); if (expectedReturnValue != -1) { var seekCharIndex = input.IndexOf(seek); var expectedEndBlock = limit <= input.Length / 2 ? block1.Segment : (seekCharIndex != -1 && seekCharIndex < input.Length / 2 ? block1.Segment : block2.Segment); Assert.Same(expectedEndBlock, result.Segment); var expectedEndIndex = expectedEndBlock.Start + (expectedEndBlock == block1.Segment ? input1.IndexOf(seek) : input2.IndexOf(seek)); Assert.Equal(expectedEndIndex, result.Index); } }
public void MemorySeek(string raw, string search, char expectResult, int expectIndex) { var cursors = Factory.CreateWithContent(raw); ReadCursor start = cursors.Start; ReadCursor end = cursors.End; ReadCursor result = default; var searchFor = search.ToCharArray(); int found = -1; if (searchFor.Length == 1) { found = ReadCursorOperations.Seek(start, end, out result, (byte)searchFor[0]); } else if (searchFor.Length == 2) { found = ReadCursorOperations.Seek(start, end, out result, (byte)searchFor[0], (byte)searchFor[1]); } else if (searchFor.Length == 3) { found = ReadCursorOperations.Seek(start, end, out result, (byte)searchFor[0], (byte)searchFor[1], (byte)searchFor[2]); } else { Assert.False(true, "Invalid test sample."); } Assert.Equal(expectResult, found); Assert.Equal(cursors.Slice(result).ToArray(), Encoding.ASCII.GetBytes(raw.Substring(expectIndex))); }
public Position Move(Position cursor, long count) { if (count < 0) { throw new ArgumentOutOfRangeException(nameof(count)); } return(ReadCursorOperations.Seek(cursor, BufferEnd, count, false)); }
/// <summary> /// Forms a slice out of the given <see cref="ReadOnlyBuffer"/>, beginning at 'start', and is at most length bytes /// </summary> /// <param name="start">The starting (inclusive) <see cref="Position"/> at which to begin this slice.</param> /// <param name="length">The length of the slice</param> public ReadOnlyBuffer Slice(Position start, long length) { ReadCursorOperations.BoundsCheck(BufferEnd, start); var end = ReadCursorOperations.Seek(start, BufferEnd, length, false); return(new ReadOnlyBuffer(start, end)); }
public void TestSeekIteratorLimitWithinSameBlock(string input, char seek, char limitAfter, int expectedReturnValue) { // Arrange var afterSeek = (byte)'B'; var buffer = BufferUtilities.CreateBuffer(input); var start = buffer.Start; var scan1 = buffer.Start; var veryEnd = buffer.End; var scan2_1 = scan1; var scan2_2 = scan1; var scan3_1 = scan1; var scan3_2 = scan1; var scan3_3 = scan1; var end = buffer.End; // Act var endReturnValue = ReadCursorOperations.Seek(start, veryEnd, out end, (byte)limitAfter); end = buffer.Slice(end, 1).End; var returnValue1 = ReadCursorOperations.Seek(start, end, out scan1, (byte)seek); var returnValue2_1 = ReadCursorOperations.Seek(start, end, out scan2_1, (byte)seek, afterSeek); var returnValue2_2 = ReadCursorOperations.Seek(start, end, out scan2_2, afterSeek, (byte)seek); var returnValue3_1 = ReadCursorOperations.Seek(start, end, out scan3_1, (byte)seek, afterSeek, afterSeek); var returnValue3_2 = ReadCursorOperations.Seek(start, end, out scan3_2, afterSeek, (byte)seek, afterSeek); var returnValue3_3 = ReadCursorOperations.Seek(start, end, out scan3_3, afterSeek, afterSeek, (byte)seek); // Assert Assert.Equal(input.Contains(limitAfter) ? limitAfter : -1, endReturnValue); Assert.Equal(expectedReturnValue, returnValue1); Assert.Equal(expectedReturnValue, returnValue2_1); Assert.Equal(expectedReturnValue, returnValue2_2); Assert.Equal(expectedReturnValue, returnValue3_1); Assert.Equal(expectedReturnValue, returnValue3_2); Assert.Equal(expectedReturnValue, returnValue3_3); if (expectedReturnValue != -1) { var block = start.Segment; Assert.Same(block, scan1.Segment); Assert.Same(block, scan2_1.Segment); Assert.Same(block, scan2_2.Segment); Assert.Same(block, scan3_1.Segment); Assert.Same(block, scan3_2.Segment); Assert.Same(block, scan3_3.Segment); var expectedEndIndex = expectedReturnValue != -1 ? start.Index + input.IndexOf(seek) : end.Index; Assert.Equal(expectedEndIndex, scan1.Index); Assert.Equal(expectedEndIndex, scan2_1.Index); Assert.Equal(expectedEndIndex, scan2_2.Index); Assert.Equal(expectedEndIndex, scan3_1.Index); Assert.Equal(expectedEndIndex, scan3_2.Index); Assert.Equal(expectedEndIndex, scan3_3.Index); } }
public bool TryGet(ref Position cursor, out ReadOnlyMemory <byte> data, bool advance = true) { var result = ReadCursorOperations.TryGetBuffer(cursor, End, out data, out var next); if (advance) { cursor = next; } return(result); }
/// <summary> /// Forms a slice out of the given <see cref="ReadOnlyBuffer"/>, beginning at 'start', ending at the existing <see cref="ReadOnlyBuffer"/>'s end. /// </summary> /// <param name="start">The start index at which to begin this slice.</param> public ReadOnlyBuffer Slice(long start) { if (start == 0) { return(this); } var begin = ReadCursorOperations.Seek(BufferStart, BufferEnd, start, false); return(new ReadOnlyBuffer(begin, BufferEnd)); }
private static Span <byte> TryGetNewLineSpan(ref ReadableBuffer buffer, out ReadCursor end) { var start = buffer.Start; if (ReadCursorOperations.Seek(start, buffer.End, out end, ByteLF) != -1) { // Move 1 byte past the \n end = buffer.Move(end, 1); return(buffer.Slice(start, end).ToSpan()); } return(default);
public void TestSeekIteratorLimitWithinSameBlock(string input, char seek, char limitAfter, int expectedReturnValue) { // Arrange var afterSeek = (byte)'B'; var buffer = Factory.CreateWithContent(input); var start = buffer.Start; var scan1 = buffer.Start; var veryEnd = buffer.End; var scan2_1 = scan1; var scan2_2 = scan1; var scan3_1 = scan1; var scan3_2 = scan1; var scan3_3 = scan1; var end = buffer.End; // Act var endReturnValue = ReadCursorOperations.Seek(start, veryEnd, out end, (byte)limitAfter); if (endReturnValue != -1) { end = buffer.Slice(end, 1).End; } var returnValue1 = ReadCursorOperations.Seek(start, end, out scan1, (byte)seek); var returnValue2_1 = ReadCursorOperations.Seek(start, end, out scan2_1, (byte)seek, afterSeek); var returnValue2_2 = ReadCursorOperations.Seek(start, end, out scan2_2, afterSeek, (byte)seek); var returnValue3_1 = ReadCursorOperations.Seek(start, end, out scan3_1, (byte)seek, afterSeek, afterSeek); var returnValue3_2 = ReadCursorOperations.Seek(start, end, out scan3_2, afterSeek, (byte)seek, afterSeek); var returnValue3_3 = ReadCursorOperations.Seek(start, end, out scan3_3, afterSeek, afterSeek, (byte)seek); // Assert Assert.Equal(input.Contains(limitAfter) ? limitAfter : -1, endReturnValue); Assert.Equal(expectedReturnValue, returnValue1); Assert.Equal(expectedReturnValue, returnValue2_1); Assert.Equal(expectedReturnValue, returnValue2_2); Assert.Equal(expectedReturnValue, returnValue3_1); Assert.Equal(expectedReturnValue, returnValue3_2); Assert.Equal(expectedReturnValue, returnValue3_3); if (expectedReturnValue != -1) { var expectedEndIndex = input.IndexOf(seek); Assert.Equal(buffer.Slice(scan1).ToArray(), Encoding.ASCII.GetBytes(input.Substring(expectedEndIndex))); Assert.Equal(buffer.Slice(scan2_1).ToArray(), Encoding.ASCII.GetBytes(input.Substring(expectedEndIndex))); Assert.Equal(buffer.Slice(scan2_2).ToArray(), Encoding.ASCII.GetBytes(input.Substring(expectedEndIndex))); Assert.Equal(buffer.Slice(scan3_1).ToArray(), Encoding.ASCII.GetBytes(input.Substring(expectedEndIndex))); Assert.Equal(buffer.Slice(scan3_2).ToArray(), Encoding.ASCII.GetBytes(input.Substring(expectedEndIndex))); Assert.Equal(buffer.Slice(scan3_3).ToArray(), Encoding.ASCII.GetBytes(input.Substring(expectedEndIndex))); } }
private void ParseExtension(ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined) { // Chunk-extensions not currently parsed // Just drain the data consumed = buffer.Start; examined = buffer.Start; do { ReadCursor extensionCursor; if (ReadCursorOperations.Seek(buffer.Start, buffer.End, out extensionCursor, ByteCR) == -1) { // End marker not found yet consumed = buffer.End; examined = buffer.End; AddAndCheckConsumedBytes(buffer.Length); return; } ; var charsToByteCRExclusive = buffer.Slice(0, extensionCursor).Length; var sufixBuffer = buffer.Slice(extensionCursor); if (sufixBuffer.Length < 2) { consumed = extensionCursor; examined = buffer.End; AddAndCheckConsumedBytes(charsToByteCRExclusive); return; } sufixBuffer = sufixBuffer.Slice(0, 2); var sufixSpan = sufixBuffer.ToSpan(); if (sufixSpan[1] == '\n') { // We consumed the \r\n at the end of the extension, so switch modes. _mode = _inputLength > 0 ? Mode.Data : Mode.Trailer; consumed = sufixBuffer.End; examined = sufixBuffer.End; AddAndCheckConsumedBytes(charsToByteCRExclusive + 2); } else { // Don't consume suffixSpan[1] in case it is also a \r. buffer = buffer.Slice(charsToByteCRExclusive + 1); consumed = extensionCursor; AddAndCheckConsumedBytes(charsToByteCRExclusive + 1); } } while (_mode == Mode.Extension); }
private static bool TryGetNewLineSpan(ref ReadableBuffer buffer, ref Span <byte> span, out ReadCursor end) { var start = buffer.Start; if (ReadCursorOperations.Seek(start, buffer.End, out end, ByteLF) != -1) { // Move 1 byte past the \n end = buffer.Move(end, 1); span = buffer.Slice(start, end).ToSpan(); return(true); } return(false); }
private static void FindAllNewLines(ReadableBuffer buffer) { var start = buffer.Start; var end = buffer.End; while (true) { if (ReadCursorOperations.Seek(start, end, out var found, (byte)'\n') == -1) { break; } start = buffer.Move(found, 1); } }
private static void FindAllNewLinesReadableBufferReader(ReadableBuffer buffer) { var reader = new ReadableBufferReader(buffer); var end = buffer.End; while (!reader.End) { var span = reader.Span; // Trim the start if we have an index if (reader.Index > 0) { span = span.Slice(reader.Index); } while (span.Length > 0) { var length = span.IndexOf((byte)'\n'); var skip = length; if (length == -1) { var current = reader.Cursor; if (ReadCursorOperations.Seek(current, end, out var found, (byte)'\n') == -1) { // We're done return; } length = span.Length; skip = (int)buffer.Slice(current, found).Length + 1; } else { length += 1; skip = length; } span = span.Slice(length); reader.Skip(skip); } } }
public void TestSeekByteLimitWithinSameBlock(string input, char seek, int limit, int expectedBytesScanned, int expectedReturnValue) { // Arrange var buffer = Factory.CreateWithContent(input); // Act var end = limit > input.Length ? buffer.End : buffer.Slice(0, limit).End; var returnValue = ReadCursorOperations.Seek(buffer.Start, end, out ReadCursor result, (byte)seek); var returnValue_1 = ReadCursorOperations.Seek(buffer.Start, end, out result, (byte)seek, (byte)seek); var returnValue_2 = ReadCursorOperations.Seek(buffer.Start, end, out result, (byte)seek, (byte)seek, (byte)seek); // Assert Assert.Equal(expectedReturnValue, returnValue); Assert.Equal(expectedReturnValue, returnValue_1); Assert.Equal(expectedReturnValue, returnValue_2); if (expectedReturnValue != -1) { Assert.Equal(buffer.Slice(result).ToArray(), Encoding.ASCII.GetBytes(input.Substring(expectedBytesScanned - 1))); } }
public void TestSeekByteLimitWithinSameBlock(string input, char seek, int limit, int expectedBytesScanned, int expectedReturnValue) { // Arrange var buffer = BufferUtilities.CreateBuffer(input); // Act var end = limit > input.Length ? buffer.End : buffer.Slice(0, limit).End; var returnValue = ReadCursorOperations.Seek(buffer.Start, end, out ReadCursor result, (byte)seek); var returnValue_1 = ReadCursorOperations.Seek(buffer.Start, end, out result, (byte)seek, (byte)seek); var returnValue_2 = ReadCursorOperations.Seek(buffer.Start, end, out result, (byte)seek, (byte)seek, (byte)seek); // Assert Assert.Equal(expectedReturnValue, returnValue); Assert.Equal(expectedReturnValue, returnValue_1); Assert.Equal(expectedReturnValue, returnValue_2); if (expectedReturnValue != -1) { Assert.Same(buffer.Start.Segment, result.Segment); Assert.Equal(result.Segment.Start + input.IndexOf(seek), result.Index); } }
public ParseResult ParseMessage(ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined, out byte[] message) { consumed = buffer.Start; examined = buffer.End; message = null; var start = consumed; var end = examined; while (buffer.Length > 0) { if (ReadCursorOperations.Seek(start, end, out var lineEnd, ByteLF) == -1) { // For the case of data: Foo\r\n\r\<Anytine except \n> if (_internalParserState == InternalParseState.ReadEndOfMessage) { if (ConvertBufferToSpan(buffer.Slice(start, buffer.End)).Length > 1) { throw new FormatException("Expected a \\r\\n frame ending"); } } // Partial message. We need to read more. return(ParseResult.Incomplete); } lineEnd = buffer.Move(lineEnd, 1); var line = ConvertBufferToSpan(buffer.Slice(start, lineEnd)); buffer = buffer.Slice(line.Length); if (line.Length <= 1) { throw new FormatException("There was an error in the frame format"); } // Skip comments if (line[0] == ByteColon) { start = lineEnd; consumed = lineEnd; continue; } if (IsMessageEnd(line)) { _internalParserState = InternalParseState.ReadEndOfMessage; } // To ensure that the \n was preceded by a \r // since messages can't contain \n. // data: foo\n\bar should be encoded as // data: foo\r\n // data: bar\r\n else if (line[line.Length - _sseLineEnding.Length] != ByteCR) { throw new FormatException("Unexpected '\n' in message. A '\n' character can only be used as part of the newline sequence '\r\n'"); } else { EnsureStartsWithDataPrefix(line); } var payload = Array.Empty <byte>(); switch (_internalParserState) { case InternalParseState.ReadMessagePayload: EnsureStartsWithDataPrefix(line); // Slice away the 'data: ' var payloadLength = line.Length - (_dataPrefix.Length + _sseLineEnding.Length); var newData = line.Slice(_dataPrefix.Length, payloadLength).ToArray(); _data.Add(newData); start = lineEnd; consumed = lineEnd; break; case InternalParseState.ReadEndOfMessage: if (_data.Count == 1) { payload = _data[0]; } else if (_data.Count > 1) { // Find the final size of the payload var payloadSize = 0; foreach (var dataLine in _data) { payloadSize += dataLine.Length; } payloadSize += _newLine.Length * _data.Count; // Allocate space in the payload buffer for the data and the new lines. // Subtract newLine length because we don't want a trailing newline. payload = new byte[payloadSize - _newLine.Length]; var offset = 0; foreach (var dataLine in _data) { dataLine.CopyTo(payload, offset); offset += dataLine.Length; if (offset < payload.Length) { _newLine.CopyTo(payload, offset); offset += _newLine.Length; } } } message = payload; consumed = lineEnd; examined = consumed; return(ParseResult.Completed); } if (buffer.Length > 0 && buffer.First.Span[0] == ByteCR) { _internalParserState = InternalParseState.ReadEndOfMessage; } } return(ParseResult.Incomplete); }
public unsafe bool ParseHeaders <T>(T handler, ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined, out int consumedBytes) where T : IHttpHeadersHandler { consumed = buffer.Start; examined = buffer.End; consumedBytes = 0; var bufferEnd = buffer.End; var reader = new ReadableBufferReader(buffer); var start = default(ReadableBufferReader); var done = false; try { while (!reader.End) { var span = reader.Span; var remaining = span.Length - reader.Index; fixed(byte *pBuffer = &span.DangerousGetPinnableReference()) { while (remaining > 0) { var index = reader.Index; int ch1; int ch2; // Fast path, we're still looking at the same span if (remaining >= 2) { ch1 = pBuffer[index]; ch2 = pBuffer[index + 1]; } else { // Store the reader before we look ahead 2 bytes (probably straddling // spans) start = reader; // Possibly split across spans ch1 = reader.Take(); ch2 = reader.Take(); } if (ch1 == ByteCR) { // Check for final CRLF. if (ch2 == -1) { // Reset the reader so we don't consume anything reader = start; return(false); } else if (ch2 == ByteLF) { // If we got 2 bytes from the span directly so skip ahead 2 so that // the reader's state matches what we expect if (index == reader.Index) { reader.Skip(2); } done = true; return(true); } // Headers don't end in CRLF line. RejectRequest(RequestRejectionReason.InvalidRequestHeadersNoCRLF); } // We moved the reader so look ahead 2 bytes so reset both the reader // and the index if (index != reader.Index) { reader = start; index = reader.Index; } var endIndex = new ReadOnlySpan <byte>(pBuffer + index, remaining).IndexOf(ByteLF); var length = 0; if (endIndex != -1) { length = endIndex + 1; var pHeader = pBuffer + index; TakeSingleHeader(pHeader, length, handler); } else { var current = reader.Cursor; // Split buffers if (ReadCursorOperations.Seek(current, bufferEnd, out var lineEnd, ByteLF) == -1) { // Not there return(false); } // Make sure LF is included in lineEnd lineEnd = buffer.Move(lineEnd, 1); var headerSpan = buffer.Slice(current, lineEnd).ToSpan(); length = headerSpan.Length; fixed(byte *pHeader = &headerSpan.DangerousGetPinnableReference()) { TakeSingleHeader(pHeader, length, handler); } // We're going to the next span after this since we know we crossed spans here // so mark the remaining as equal to the headerSpan so that we end up at 0 // on the next iteration remaining = length; } // Skip the reader forward past the header line reader.Skip(length); remaining -= length; } } } return(false); } finally { consumed = reader.Cursor; consumedBytes = reader.ConsumedBytes; if (done) { examined = consumed; } } }
public void TestSeekIteratorLimitAcrossBlocks(string input, char seek, char limitAt, int expectedReturnValue) { // Arrange var afterSeek = (byte)'B'; var input1 = input.Substring(0, input.Length / 2); var input2 = input.Substring(input.Length / 2); var buffer = BufferUtilities.CreateBuffer(input1, string.Empty, input2); var start = buffer.Start; var scan1 = buffer.Start; var veryEnd = buffer.End; var scan2_1 = scan1; var scan2_2 = scan1; var scan3_1 = scan1; var scan3_2 = scan1; var scan3_3 = scan1; var end = buffer.End; // Act var endReturnValue = ReadCursorOperations.Seek(start, veryEnd, out end, (byte)limitAt); end = buffer.Move(end, 1); var returnValue1 = ReadCursorOperations.Seek(start, end, out scan1, (byte)seek); var returnValue2_1 = ReadCursorOperations.Seek(start, end, out scan2_1, (byte)seek, afterSeek); var returnValue2_2 = ReadCursorOperations.Seek(start, end, out scan2_2, afterSeek, (byte)seek); var returnValue3_1 = ReadCursorOperations.Seek(start, end, out scan3_1, (byte)seek, afterSeek, afterSeek); var returnValue3_2 = ReadCursorOperations.Seek(start, end, out scan3_2, afterSeek, (byte)seek, afterSeek); var returnValue3_3 = ReadCursorOperations.Seek(start, end, out scan3_3, afterSeek, afterSeek, (byte)seek); // Assert Assert.Equal(input.Contains(limitAt) ? limitAt : -1, endReturnValue); Assert.Equal(expectedReturnValue, returnValue1); Assert.Equal(expectedReturnValue, returnValue2_1); Assert.Equal(expectedReturnValue, returnValue2_2); Assert.Equal(expectedReturnValue, returnValue3_1); Assert.Equal(expectedReturnValue, returnValue3_2); Assert.Equal(expectedReturnValue, returnValue3_3); if (expectedReturnValue != -1) { var seekCharIndex = input.IndexOf(seek); var limitAtIndex = input.IndexOf(limitAt); var expectedEndBlock = seekCharIndex != -1 && seekCharIndex < input.Length / 2 ? start.Segment : (limitAtIndex != -1 && limitAtIndex < input.Length / 2 ? start.Segment : end.Segment); Assert.Same(expectedEndBlock, scan1.Segment); Assert.Same(expectedEndBlock, scan2_1.Segment); Assert.Same(expectedEndBlock, scan2_2.Segment); Assert.Same(expectedEndBlock, scan3_1.Segment); Assert.Same(expectedEndBlock, scan3_2.Segment); Assert.Same(expectedEndBlock, scan3_3.Segment); var expectedEndIndex = expectedReturnValue != -1 ? expectedEndBlock.Start + (expectedEndBlock == start.Segment ? input1.IndexOf(seek) : input2.IndexOf(seek)) : end.Index; Assert.Equal(expectedEndIndex, scan1.Index); Assert.Equal(expectedEndIndex, scan2_1.Index); Assert.Equal(expectedEndIndex, scan2_2.Index); Assert.Equal(expectedEndIndex, scan3_1.Index); Assert.Equal(expectedEndIndex, scan3_2.Index); Assert.Equal(expectedEndIndex, scan3_3.Index); } }