sharpemu/tests/SharpEmu.Libs.Tests/Kernel/KernelReadEventTests.cs

181 lines
7.0 KiB
C#

// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Buffers.Binary;
using System.Net;
using System.Net.Sockets;
using SharpEmu.HLE;
using SharpEmu.Libs.Kernel;
using Xunit;
namespace SharpEmu.Libs.Tests.Kernel;
public sealed class KernelReadEventTests
{
private const ulong BaseAddress = 0x1_0000_0000;
private const ulong EqueueAddress = BaseAddress + 0x100;
private const ulong SockaddrAddress = BaseAddress + 0x200;
private const ulong EventAddress = BaseAddress + 0x300;
private const ulong OutCountAddress = BaseAddress + 0x400;
private const ulong TimeoutAddress = BaseAddress + 0x500;
private const ulong ReadBufferAddress = BaseAddress + 0x600;
[Fact]
public async Task ReadEvent_WakesForSocketDataAndClearsAfterRead()
{
var memory = new FakeCpuMemory(BaseAddress, 0x2000);
var ctx = new CpuContext(memory, Generation.Gen5);
using var listener = new TcpListener(IPAddress.Loopback, 0);
listener.Start();
var acceptTask = listener.AcceptTcpClientAsync();
Assert.Equal(
(int)OrbisGen2Result.ORBIS_GEN2_OK,
KernelSocketCompatExports.Socket(ctx));
var fileDescriptor = unchecked((int)ctx[CpuRegister.Rax]);
Assert.True(fileDescriptor >= 3);
var port = ((IPEndPoint)listener.LocalEndpoint).Port;
WriteSockaddr(memory, SockaddrAddress, port);
ctx[CpuRegister.Rdi] = unchecked((uint)fileDescriptor);
ctx[CpuRegister.Rsi] = SockaddrAddress;
ctx[CpuRegister.Rdx] = 16;
Assert.Equal(
(int)OrbisGen2Result.ORBIS_GEN2_OK,
KernelSocketCompatExports.Connect(ctx));
Assert.Equal(0UL, ctx[CpuRegister.Rax]);
using var server = await acceptTask.WaitAsync(TimeSpan.FromSeconds(2));
var equeue = CreateEqueue(ctx, memory);
const ulong expectedUserData = 0xCAFE_BABE_1234_5678;
ctx[CpuRegister.Rdi] = equeue;
ctx[CpuRegister.Rsi] = unchecked((uint)fileDescriptor);
ctx[CpuRegister.Rdx] = 1;
ctx[CpuRegister.Rcx] = expectedUserData;
Assert.Equal(
(int)OrbisGen2Result.ORBIS_GEN2_OK,
KernelEventQueueCompatExports.KernelAddReadEvent(ctx));
await server.GetStream().WriteAsync(new byte[] { 0x5A });
WriteUInt32(memory, TimeoutAddress, 1_000_000);
ConfigureWait(ctx, equeue, TimeoutAddress);
Assert.Equal(
(int)OrbisGen2Result.ORBIS_GEN2_OK,
KernelEventQueueCompatExports.KernelWaitEqueue(ctx));
Assert.Equal(1u, ReadUInt32(memory, OutCountAddress));
Span<byte> eventBytes = stackalloc byte[0x20];
Assert.True(memory.TryRead(EventAddress, eventBytes));
Assert.Equal(
unchecked((uint)fileDescriptor),
BinaryPrimitives.ReadUInt64LittleEndian(eventBytes[0x00..]));
Assert.Equal(
KernelEventQueueCompatExports.KernelEventFilterRead,
BinaryPrimitives.ReadInt16LittleEndian(eventBytes[0x08..]));
Assert.Equal(0, BinaryPrimitives.ReadUInt16LittleEndian(eventBytes[0x0A..]));
Assert.True(BinaryPrimitives.ReadUInt64LittleEndian(eventBytes[0x10..]) >= 1);
Assert.Equal(
expectedUserData,
BinaryPrimitives.ReadUInt64LittleEndian(eventBytes[0x18..]));
ctx[CpuRegister.Rdi] = unchecked((uint)fileDescriptor);
ctx[CpuRegister.Rsi] = ReadBufferAddress;
ctx[CpuRegister.Rdx] = 1;
Assert.Equal(
(int)OrbisGen2Result.ORBIS_GEN2_OK,
KernelMemoryCompatExports.KernelReadUnderscore(ctx));
Assert.Equal(1UL, ctx[CpuRegister.Rax]);
Span<byte> payload = stackalloc byte[1];
Assert.True(memory.TryRead(ReadBufferAddress, payload));
Assert.Equal(0x5A, payload[0]);
WriteUInt32(memory, TimeoutAddress, 0);
ConfigureWait(ctx, equeue, TimeoutAddress);
Assert.Equal(
(int)OrbisGen2Result.ORBIS_GEN2_ERROR_TIMED_OUT,
KernelEventQueueCompatExports.KernelWaitEqueue(ctx));
Assert.Equal(0u, ReadUInt32(memory, OutCountAddress));
ctx[CpuRegister.Rdi] = equeue;
ctx[CpuRegister.Rsi] = unchecked((uint)fileDescriptor);
Assert.Equal(
(int)OrbisGen2Result.ORBIS_GEN2_OK,
KernelEventQueueCompatExports.KernelDeleteReadEvent(ctx));
ctx[CpuRegister.Rdi] = unchecked((uint)fileDescriptor);
Assert.Equal(
(int)OrbisGen2Result.ORBIS_GEN2_OK,
KernelMemoryCompatExports.KernelClose(ctx));
ctx[CpuRegister.Rdi] = equeue;
Assert.Equal(
(int)OrbisGen2Result.ORBIS_GEN2_OK,
KernelEventQueueCompatExports.KernelDeleteEqueue(ctx));
}
[Fact]
public void AddReadEvent_RejectsUnknownDescriptor()
{
var memory = new FakeCpuMemory(BaseAddress, 0x1000);
var ctx = new CpuContext(memory, Generation.Gen5);
var equeue = CreateEqueue(ctx, memory);
ctx[CpuRegister.Rdi] = equeue;
ctx[CpuRegister.Rsi] = 0x7FFF_FFFF;
ctx[CpuRegister.Rdx] = 1;
Assert.Equal(
(int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND,
KernelEventQueueCompatExports.KernelAddReadEvent(ctx));
ctx[CpuRegister.Rdi] = equeue;
Assert.Equal(
(int)OrbisGen2Result.ORBIS_GEN2_OK,
KernelEventQueueCompatExports.KernelDeleteEqueue(ctx));
}
private static ulong CreateEqueue(CpuContext ctx, FakeCpuMemory memory)
{
ctx[CpuRegister.Rdi] = EqueueAddress;
Assert.Equal(
(int)OrbisGen2Result.ORBIS_GEN2_OK,
KernelEventQueueCompatExports.KernelCreateEqueue(ctx));
Span<byte> bytes = stackalloc byte[sizeof(ulong)];
Assert.True(memory.TryRead(EqueueAddress, bytes));
return BinaryPrimitives.ReadUInt64LittleEndian(bytes);
}
private static void ConfigureWait(CpuContext ctx, ulong equeue, ulong timeoutAddress)
{
ctx[CpuRegister.Rdi] = equeue;
ctx[CpuRegister.Rsi] = EventAddress;
ctx[CpuRegister.Rdx] = 1;
ctx[CpuRegister.Rcx] = OutCountAddress;
ctx[CpuRegister.R8] = timeoutAddress;
}
private static void WriteSockaddr(FakeCpuMemory memory, ulong address, int port)
{
Span<byte> sockaddr = stackalloc byte[16];
sockaddr[0] = 16;
sockaddr[1] = 2;
BinaryPrimitives.WriteUInt16BigEndian(sockaddr[2..], unchecked((ushort)port));
IPAddress.Loopback.GetAddressBytes().CopyTo(sockaddr[4..]);
Assert.True(memory.TryWrite(address, sockaddr));
}
private static void WriteUInt32(FakeCpuMemory memory, ulong address, uint value)
{
Span<byte> bytes = stackalloc byte[sizeof(uint)];
BinaryPrimitives.WriteUInt32LittleEndian(bytes, value);
Assert.True(memory.TryWrite(address, bytes));
}
private static uint ReadUInt32(FakeCpuMemory memory, ulong address)
{
Span<byte> bytes = stackalloc byte[sizeof(uint)];
Assert.True(memory.TryRead(address, bytes));
return BinaryPrimitives.ReadUInt32LittleEndian(bytes);
}
}