blob: 3f466b91f148c96de308329ceee5fdda5763b6bb [file] [log] [blame]
using Lucene.Net.Attributes;
using Lucene.Net.Index;
using Lucene.Net.Replicator;
using Lucene.Net.Store;
using Lucene.Net.Util;
using NUnit.Framework;
using System;
using System.Linq;
using Assert = Lucene.Net.TestFramework.Assert;
namespace Lucene.Net.Tests.Replicator
{
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//Note: LUCENENET specific
[LuceneNetSpecific]
public class IndexInputStreamTest : LuceneTestCase
{
[Test]
[LuceneNetSpecific]
public void Read_RemainingIndexInputLargerThanReadCount_ReturnsReadCount()
{
byte[] buffer = new byte[8.KiloBytes()];
Random.NextBytes(buffer);
IndexInputStream stream = new IndexInputStream(new MockIndexInput(buffer));
int readBytes = 2.KiloBytes();
byte[] readBuffer = new byte[readBytes];
Assert.AreEqual(stream.Read(readBuffer, 0, readBytes), readBytes);
}
[Test]
[LuceneNetSpecific]
public void Read_RemainingIndexInputLargerThanReadCount_ReturnsExpectedSection([Range(1, 8)] int section)
{
byte[] buffer = new byte[8.KiloBytes()];
Random.NextBytes(buffer);
IndexInputStream stream = new IndexInputStream(new MockIndexInput(buffer));
int readBytes = 1.KiloBytes();
byte[] readBuffer = new byte[readBytes];
for (int i = section; i > 0; i--)
{
var numRead = stream.Read(readBuffer, 0, readBytes); // LUCENENET specific - asserting that we read the entire buffer
Assert.AreEqual(readBytes, numRead);
}
Assert.AreEqual(readBuffer, buffer.Skip((section - 1) * readBytes).Take(readBytes).ToArray());
}
/// <summary>
/// Test for GitHub issue #1158: Integer overflow in IndexInputStream.Read()
/// https://github.com/apache/lucenenet/issues/1158
/// </summary>
[Test]
[LuceneNetSpecific]
public void TestGitHubIssue1158_IndexInputStream_Read_IntegerOverflow()
{
// This test verifies the fix for the integer overflow bug in IndexInputStream.Read()
// Bug: When input.Length - input.Position > int.MaxValue, casting to int causes overflow
// Previously on line 68: int remaining = (int)(input.Length - input.Position);
// Arrange: Create a mock IndexInput that simulates a very large file
var largeIndexInput = new LargeFileMockIndexInput();
var stream = new IndexInputStream(largeIndexInput);
// Position the stream at the beginning (position = 0)
// Length is int.MaxValue + 1000L, so (Length - Position) = int.MaxValue + 1000L
// When cast to int, this overflows and becomes negative: -2147482649
largeIndexInput.Seek(0);
// Act: Try to read from the stream
byte[] buffer = new byte[50];
// The bug manifests here:
// remaining = (int)(int.MaxValue + 1000L) = -2147482649 (negative due to overflow)
// readCount = Math.Min(-2147482649, 50) = -2147482649 (negative)
// This would cause ReadBytes to be called with negative length
try
{
int bytesRead = stream.Read(buffer, 0, buffer.Length);
// If we get here without exception, check if the read was successful
// With the bug, bytesRead might be negative or cause other issues
Assert.IsTrue(bytesRead >= 0, $"BytesRead should not be negative, but was {bytesRead}");
Assert.AreEqual(50, bytesRead, "Should read exactly 50 bytes");
}
catch (ArgumentException ex)
{
// The bug may cause an ArgumentException when ReadBytes is called with negative length
Assert.Fail($"Integer overflow caused ArgumentException: {ex.Message}");
}
}
}
/// <summary>
/// Mock IndexInput that simulates a file larger than int.MaxValue
/// to test for integer overflow issues
/// </summary>
internal class LargeFileMockIndexInput : IndexInput
{
private long position = 0;
private readonly long length = (long)int.MaxValue + 1000L;
public LargeFileMockIndexInput() : base("LargeFileMockIndexInput")
{
}
public override long Length => length;
public override long Position => position;
public override byte ReadByte()
{
if (position >= length)
throw new System.IO.EndOfStreamException();
position++;
return 0;
}
public override void ReadBytes(byte[] b, int offset, int len)
{
// Validate parameters to prevent unexpected behavior
if (b == null)
{
throw new ArgumentNullException(nameof(b));
}
if (offset < 0)
{
throw new ArgumentOutOfRangeException(nameof(offset), "Offset cannot be negative");
}
if (len < 0)
{
throw new ArgumentOutOfRangeException(nameof(len), "Length cannot be negative");
}
if (offset + len > b.Length)
{
throw new ArgumentException("The sum of offset and length exceeds the buffer length");
}
ReadBytes(b.AsSpan(offset, len));
}
// LUCENENET: Use Span<byte> instead of byte[] for better compatibility.
public override void ReadBytes(Span<byte> destination)
{
int len = destination.Length;
long available = length - position;
if (available < len)
{
throw new System.IO.EndOfStreamException();
}
// Simulate reading by advancing position
position += len;
// Fill buffer with dummy data
for (int i = 0; i < len; i++)
{
destination[/*offset +*/ i] = 0;
}
}
public override void Seek(long pos)
{
if (pos < 0 || pos > length)
throw new ArgumentOutOfRangeException(nameof(pos));
position = pos;
}
public override object Clone()
{
var clone = new LargeFileMockIndexInput();
clone.position = this.position;
return clone;
}
protected override void Dispose(bool disposing)
{
// No resources to dispose
}
}
//Note: LUCENENET specific
internal static class ByteHelperExtensions
{
public static int KiloBytes(this int value)
{
return value * 1024;
}
}
}