[SDK][APITESTS:NTDLL][NTDLL] Add RtlMemIoResource support (#9392)

* [SDK][APITESTS:NTDLL][NTDLL] Add RtlMemIoResource support
- RtlCmEncodeMemIoResource
- RtlCmDecodeMemIoResource
- RtlIoEncodeMemIoResource
- RtlIoDecodeMemIoResource
- RtlFindClosestEncodableLength
This commit is contained in:
Justin Miller
2026-08-14 07:26:09 +00:00
committed by GitHub
parent e40c4d89cf
commit 53a4bb8aa6
6 changed files with 840 additions and 5 deletions

View File

@@ -678,8 +678,8 @@
@ stdcall RtlClearBits(ptr long long)
@ stdcall RtlCloneMemoryStream(ptr ptr)
@ stdcall -stub -version=0x600+ RtlCloneUserProcess(long long long long long)
@ stdcall -stub -version=0x600+ RtlCmDecodeMemIoResource(ptr ptr)
@ stdcall -stub -version=0x600+ RtlCmEncodeMemIoResource(ptr long long long)
@ stdcall -ret64 -version=0x600+ RtlCmDecodeMemIoResource(ptr ptr)
@ stdcall -version=0x600+ RtlCmEncodeMemIoResource(ptr long int64 int64)
@ stdcall -stub -version=0x600+ RtlCommitDebugInfo(ptr long)
@ stdcall RtlCommitMemoryStream(ptr long)
@ stdcall RtlCompactHeap(long long)
@@ -840,7 +840,7 @@
@ stdcall RtlFindClearBits(ptr long long)
@ stdcall RtlFindClearBitsAndSet(ptr long long)
@ stdcall RtlFindClearRuns(ptr ptr long long)
@ stdcall -stub -version=0x600+ RtlFindClosestEncodableLength(long ptr)
@ stdcall -version=0x600+ RtlFindClosestEncodableLength(int64 ptr)
@ stdcall RtlFindLastBackwardRunClear(ptr long ptr)
@ stdcall RtlFindLeastSignificantBit(double)
@ stdcall RtlFindLongestRunClear(ptr long)
@@ -973,8 +973,8 @@
@ stdcall -arch=x86_64 RtlInterlockedPushListSList(ptr ptr ptr long)
@ stdcall -version=0x602+ -arch=i386 RtlInterlockedPushListSListEx(ptr ptr ptr long)
@ stdcall -version=0x602+ -arch=!i386 RtlInterlockedPushListSListEx(ptr ptr ptr long) RtlInterlockedPushListSList
@ stdcall -stub -version=0x600+ RtlIoDecodeMemIoResource(ptr ptr ptr ptr)
@ stdcall -stub -version=0x600+ RtlIoEncodeMemIoResource(ptr long long long long long)
@ stdcall -ret64 -version=0x600+ RtlIoDecodeMemIoResource(ptr ptr ptr ptr)
@ stdcall -version=0x600+ RtlIoEncodeMemIoResource(ptr long int64 int64 int64 int64)
@ stdcall RtlIpv4AddressToStringA(ptr ptr)
@ stdcall RtlIpv4AddressToStringExA(ptr long ptr ptr)
@ stdcall RtlIpv4AddressToStringExW(ptr long ptr ptr)

View File

@@ -128,6 +128,7 @@ list(APPEND SOURCE
RtlIsNameInExpression.c
RtlIsNameLegalDOS8Dot3.c
RtlLocale.c
RtlMemIoResource.c
RtlMemoryStream.c
RtlMultipleAllocateHeap.c
RtlNtPathNameToDosPathName.c

View File

@@ -0,0 +1,502 @@
/*
* PROJECT: ReactOS API Tests
* LICENSE: MIT (https://spdx.org/licenses/MIT)
* PURPOSE: Tests for the Rtl memory/port resource descriptor encoding API:
* RtlCmEncodeMemIoResource, RtlCmDecodeMemIoResource,
* RtlIoEncodeMemIoResource, RtlIoDecodeMemIoResource and
* RtlFindClosestEncodableLength.
* COPYRIGHT: Copyright 2026 Justin Miller <justin.miller@reactos.org>
*/
#include "precomp.h"
#include <limits.h>
static ULONGLONG (NTAPI *pRtlCmDecodeMemIoResource)(
_In_ PCM_PARTIAL_RESOURCE_DESCRIPTOR Descriptor,
_Out_opt_ PULONGLONG Start);
static NTSTATUS (NTAPI *pRtlCmEncodeMemIoResource)(
_In_ PCM_PARTIAL_RESOURCE_DESCRIPTOR Descriptor,
_In_ UCHAR Type,
_In_ ULONGLONG Length,
_In_ ULONGLONG Start);
static NTSTATUS (NTAPI *pRtlFindClosestEncodableLength)(
_In_ ULONGLONG SourceLength,
_Out_ PULONGLONG TargetLength);
static ULONGLONG (NTAPI *pRtlIoDecodeMemIoResource)(
_In_ PIO_RESOURCE_DESCRIPTOR Descriptor,
_Out_opt_ PULONGLONG Alignment,
_Out_opt_ PULONGLONG MinimumAddress,
_Out_opt_ PULONGLONG MaximumAddress);
static NTSTATUS (NTAPI *pRtlIoEncodeMemIoResource)(
_In_ PIO_RESOURCE_DESCRIPTOR Descriptor,
_In_ UCHAR Type,
_In_ ULONGLONG Length,
_In_ ULONGLONG Alignment,
_In_ ULONGLONG MinimumAddress,
_In_ ULONGLONG MaximumAddress);
static
void
Test_CmEncode(void)
{
CM_PARTIAL_RESOURCE_DESCRIPTOR Desc, Saved;
NTSTATUS Status;
/* Unsupported types must fail without touching the descriptor */
RtlFillMemory(&Desc, sizeof(Desc), 0x55);
Saved = Desc;
Status = pRtlCmEncodeMemIoResource(&Desc, CmResourceTypeNull, 0x1000, 0);
ok_eq_hex(Status, STATUS_INVALID_PARAMETER);
ok(!memcmp(&Desc, &Saved, sizeof(Desc)), "Descriptor modified on failure\n");
Status = pRtlCmEncodeMemIoResource(&Desc, CmResourceTypeInterrupt, 0x1000, 0);
ok_eq_hex(Status, STATUS_INVALID_PARAMETER);
Status = pRtlCmEncodeMemIoResource(&Desc, CmResourceTypeDma, 0x1000, 0);
ok_eq_hex(Status, STATUS_INVALID_PARAMETER);
ok(!memcmp(&Desc, &Saved, sizeof(Desc)), "Descriptor modified on failure\n");
/* Basic port descriptor */
RtlFillMemory(&Desc, sizeof(Desc), 0x55);
Status = pRtlCmEncodeMemIoResource(&Desc, CmResourceTypePort, 0x100, 0x3F8);
ok_eq_hex(Status, STATUS_SUCCESS);
ok_eq_uint(Desc.Type, CmResourceTypePort);
ok_eq_ulong(Desc.u.Port.Length, 0x100UL);
ok_eq_hex64(Desc.u.Port.Start.QuadPart, 0x3F8);
/* The port path does not clean stale large flags */
ok_eq_hex(Desc.Flags, 0x5555);
/* A port length cannot exceed 32 bits, and the failure leaves no writes */
RtlFillMemory(&Desc, sizeof(Desc), 0x55);
Saved = Desc;
Status = pRtlCmEncodeMemIoResource(&Desc, CmResourceTypePort, 0x100000000ULL, 0);
ok_eq_hex(Status, STATUS_INVALID_PARAMETER);
ok(!memcmp(&Desc, &Saved, sizeof(Desc)), "Descriptor modified on failure\n");
/* Small memory length: plain descriptor, stale large flags are cleared */
RtlZeroMemory(&Desc, sizeof(Desc));
Desc.Flags = CM_RESOURCE_MEMORY_LARGE_48 | CM_RESOURCE_MEMORY_READ_ONLY;
Status = pRtlCmEncodeMemIoResource(&Desc, CmResourceTypeMemory, 0x1000, 0xFFFFFFFFFF000000ULL);
ok_eq_hex(Status, STATUS_SUCCESS);
ok_eq_uint(Desc.Type, CmResourceTypeMemory);
ok_eq_ulong(Desc.u.Memory.Length, 0x1000UL);
ok_eq_hex64(Desc.u.Memory.Start.QuadPart, 0xFFFFFFFFFF000000ULL);
ok_eq_hex(Desc.Flags, CM_RESOURCE_MEMORY_READ_ONLY);
/* Asking for MemoryLarge with a small length demotes to plain Memory */
RtlZeroMemory(&Desc, sizeof(Desc));
Status = pRtlCmEncodeMemIoResource(&Desc, CmResourceTypeMemoryLarge, MAXULONG, 0);
ok_eq_hex(Status, STATUS_SUCCESS);
ok_eq_uint(Desc.Type, CmResourceTypeMemory);
ok_eq_ulong(Desc.u.Memory.Length, MAXULONG);
/* Asking for plain Memory with a big length promotes to MemoryLarge (40-bit) */
RtlZeroMemory(&Desc, sizeof(Desc));
Status = pRtlCmEncodeMemIoResource(&Desc, CmResourceTypeMemory, 0x100000000ULL, 0x8000000000ULL);
ok_eq_hex(Status, STATUS_SUCCESS);
ok_eq_uint(Desc.Type, CmResourceTypeMemoryLarge);
ok_eq_hex(Desc.Flags, CM_RESOURCE_MEMORY_LARGE_40);
ok_eq_ulong(Desc.u.Memory.Length, 0x01000000UL);
ok_eq_hex64(Desc.u.Memory.Start.QuadPart, 0x8000000000ULL);
/* 40-bit tier upper bound */
RtlZeroMemory(&Desc, sizeof(Desc));
Status = pRtlCmEncodeMemIoResource(&Desc, CmResourceTypeMemoryLarge, CM_RESOURCE_MEMORY_LARGE_40_MAXLEN, 0);
ok_eq_hex(Status, STATUS_SUCCESS);
ok_eq_hex(Desc.Flags, CM_RESOURCE_MEMORY_LARGE_40);
ok_eq_ulong(Desc.u.Memory.Length, MAXULONG);
/* Low 8 bits set in the 40-bit tier: not encodable */
Status = pRtlCmEncodeMemIoResource(&Desc, CmResourceTypeMemory, 0x100000080ULL, 0);
ok_eq_hex(Status, STATUS_UNSUCCESSFUL);
/* 48-bit tier */
RtlZeroMemory(&Desc, sizeof(Desc));
Status = pRtlCmEncodeMemIoResource(&Desc, CmResourceTypeMemory, 0x10000000000ULL, 0);
ok_eq_hex(Status, STATUS_SUCCESS);
ok_eq_uint(Desc.Type, CmResourceTypeMemoryLarge);
ok_eq_hex(Desc.Flags, CM_RESOURCE_MEMORY_LARGE_48);
ok_eq_ulong(Desc.u.Memory.Length, 0x01000000UL);
/*
* The tier is picked by magnitude alone: a 256-byte-aligned length in the
* 48-bit tier still fails because it is not 64K-aligned
*/
Status = pRtlCmEncodeMemIoResource(&Desc, CmResourceTypeMemory, 0x10000000100ULL, 0);
ok_eq_hex(Status, STATUS_UNSUCCESSFUL);
/* 64-bit tier and its upper bound */
RtlZeroMemory(&Desc, sizeof(Desc));
Status = pRtlCmEncodeMemIoResource(&Desc, CmResourceTypeMemory, 0x1000000000000ULL, 0);
ok_eq_hex(Status, STATUS_SUCCESS);
ok_eq_hex(Desc.Flags, CM_RESOURCE_MEMORY_LARGE_64);
ok_eq_ulong(Desc.u.Memory.Length, 0x10000UL);
RtlZeroMemory(&Desc, sizeof(Desc));
Status = pRtlCmEncodeMemIoResource(&Desc, CmResourceTypeMemory, CM_RESOURCE_MEMORY_LARGE_64_MAXLEN, 0);
ok_eq_hex(Status, STATUS_SUCCESS);
ok_eq_hex(Desc.Flags, CM_RESOURCE_MEMORY_LARGE_64);
ok_eq_ulong(Desc.u.Memory.Length, MAXULONG);
/* Low 32 bits set in the 64-bit tier, and beyond-the-max lengths */
Status = pRtlCmEncodeMemIoResource(&Desc, CmResourceTypeMemory, 0x100000000000100ULL, 0);
ok_eq_hex(Status, STATUS_UNSUCCESSFUL);
Status = pRtlCmEncodeMemIoResource(&Desc, CmResourceTypeMemory, CM_RESOURCE_MEMORY_LARGE_64_MAXLEN + 1, 0);
ok_eq_hex(Status, STATUS_UNSUCCESSFUL);
Status = pRtlCmEncodeMemIoResource(&Desc, CmResourceTypeMemory, ULLONG_MAX, 0);
ok_eq_hex(Status, STATUS_UNSUCCESSFUL);
}
static
void
Test_CmDecode(void)
{
static const struct
{
ULONGLONG Length;
USHORT ExpectedFlags;
} RoundTrips[] =
{
{ 0x1000, 0 },
{ MAXULONG, 0 },
{ 0x100000000ULL, CM_RESOURCE_MEMORY_LARGE_40 },
{ 0xFFFFFFFF00ULL, CM_RESOURCE_MEMORY_LARGE_40 },
{ 0x10000000000ULL, CM_RESOURCE_MEMORY_LARGE_48 },
{ 0xFFFFFFFF0000ULL, CM_RESOURCE_MEMORY_LARGE_48 },
{ 0x1000000000000ULL, CM_RESOURCE_MEMORY_LARGE_64 },
{ 0xFFFFFFFF00000000ULL, CM_RESOURCE_MEMORY_LARGE_64 },
};
CM_PARTIAL_RESOURCE_DESCRIPTOR Desc;
ULONGLONG Length, Start;
NTSTATUS Status;
ULONG i;
/* Plain port descriptor decodes verbatim */
RtlZeroMemory(&Desc, sizeof(Desc));
Desc.Type = CmResourceTypePort;
Desc.u.Port.Start.QuadPart = 0x1122334455667788ULL;
Desc.u.Port.Length = 0x1234;
Start = 0;
Length = pRtlCmDecodeMemIoResource(&Desc, &Start);
ok_eq_hex64(Length, 0x1234);
ok_eq_hex64(Start, 0x1122334455667788ULL);
/* Start is optional */
Length = pRtlCmDecodeMemIoResource(&Desc, NULL);
ok_eq_hex64(Length, 0x1234);
/* Large flags are ignored for plain port/memory types */
Desc.Flags = CM_RESOURCE_MEMORY_LARGE_64;
Length = pRtlCmDecodeMemIoResource(&Desc, NULL);
ok_eq_hex64(Length, 0x1234);
/* Hand-built large forms decode to the shifted length */
RtlZeroMemory(&Desc, sizeof(Desc));
Desc.Type = CmResourceTypeMemoryLarge;
Desc.u.Memory.Length = 0x01000000;
Desc.Flags = CM_RESOURCE_MEMORY_LARGE_40;
ok_eq_hex64(pRtlCmDecodeMemIoResource(&Desc, NULL), 0x100000000ULL);
Desc.Flags = CM_RESOURCE_MEMORY_LARGE_48;
ok_eq_hex64(pRtlCmDecodeMemIoResource(&Desc, NULL), 0x10000000000ULL);
Desc.Flags = CM_RESOURCE_MEMORY_LARGE_64;
ok_eq_hex64(pRtlCmDecodeMemIoResource(&Desc, NULL), 0x100000000000000ULL);
/* A large type without any large flag decodes to zero, Start still returned */
Desc.Flags = 0;
Desc.u.Memory.Start.QuadPart = 0xABCD;
Start = 0;
ok_eq_hex64(pRtlCmDecodeMemIoResource(&Desc, &Start), 0);
ok_eq_hex64(Start, 0xABCD);
/* Encode/decode round-trips across all tiers */
for (i = 0; i < ARRAYSIZE(RoundTrips); i++)
{
RtlZeroMemory(&Desc, sizeof(Desc));
Status = pRtlCmEncodeMemIoResource(&Desc,
CmResourceTypeMemory,
RoundTrips[i].Length,
0x40000000ULL + i);
ok(Status == STATUS_SUCCESS, "[%lu] encode failed: 0x%lx\n", i, Status);
ok(Desc.Flags == RoundTrips[i].ExpectedFlags,
"[%lu] Flags = 0x%x, expected 0x%x\n",
i, Desc.Flags, RoundTrips[i].ExpectedFlags);
Start = 0;
Length = pRtlCmDecodeMemIoResource(&Desc, &Start);
ok(Length == RoundTrips[i].Length,
"[%lu] Length = 0x%I64x, expected 0x%I64x\n",
i, Length, RoundTrips[i].Length);
ok(Start == 0x40000000ULL + i,
"[%lu] Start = 0x%I64x\n", i, Start);
}
}
static
void
Test_IoEncode(void)
{
IO_RESOURCE_DESCRIPTOR Desc, Saved;
NTSTATUS Status;
/* Unsupported types fail without touching the descriptor */
RtlFillMemory(&Desc, sizeof(Desc), 0x55);
Saved = Desc;
Status = pRtlIoEncodeMemIoResource(&Desc, CmResourceTypeInterrupt, 0x1000, 4, 0, 0xFFFF);
ok_eq_hex(Status, STATUS_INVALID_PARAMETER);
ok(!memcmp(&Desc, &Saved, sizeof(Desc)), "Descriptor modified on failure\n");
/* Basic port requirement */
RtlFillMemory(&Desc, sizeof(Desc), 0x55);
Status = pRtlIoEncodeMemIoResource(&Desc, CmResourceTypePort, 0x100, 4, 0x1000, 0xFFFF);
ok_eq_hex(Status, STATUS_SUCCESS);
ok_eq_uint(Desc.Type, CmResourceTypePort);
ok_eq_ulong(Desc.u.Port.Length, 0x100UL);
ok_eq_ulong(Desc.u.Port.Alignment, 4UL);
ok_eq_hex64(Desc.u.Port.MinimumAddress.QuadPart, 0x1000);
ok_eq_hex64(Desc.u.Port.MaximumAddress.QuadPart, 0xFFFF);
/* Port length/alignment above 32 bits are rejected, no writes */
RtlFillMemory(&Desc, sizeof(Desc), 0x55);
Saved = Desc;
Status = pRtlIoEncodeMemIoResource(&Desc, CmResourceTypePort, 0x100000000ULL, 4, 0, 0xFFFF);
ok_eq_hex(Status, STATUS_INVALID_PARAMETER);
Status = pRtlIoEncodeMemIoResource(&Desc, CmResourceTypePort, 0x100, 0x100000000ULL, 0, 0xFFFF);
ok_eq_hex(Status, STATUS_INVALID_PARAMETER);
ok(!memcmp(&Desc, &Saved, sizeof(Desc)), "Descriptor modified on failure\n");
/* Small memory requirement stays plain and clears stale large flags */
RtlZeroMemory(&Desc, sizeof(Desc));
Desc.Flags = CM_RESOURCE_MEMORY_LARGE_40 | CM_RESOURCE_MEMORY_PREFETCHABLE;
Status = pRtlIoEncodeMemIoResource(&Desc, CmResourceTypeMemory, 0x1000, 0x1000, 0, 0xFFFFFFFFULL);
ok_eq_hex(Status, STATUS_SUCCESS);
ok_eq_uint(Desc.Type, CmResourceTypeMemory);
ok_eq_hex(Desc.Flags, CM_RESOURCE_MEMORY_PREFETCHABLE);
ok_eq_ulong(Desc.u.Memory.Length, 0x1000UL);
ok_eq_ulong(Desc.u.Memory.Alignment, 0x1000UL);
ok_eq_hex64(Desc.u.Memory.MinimumAddress.QuadPart, 0);
ok_eq_hex64(Desc.u.Memory.MaximumAddress.QuadPart, 0xFFFFFFFFULL);
/* Large length: a small alignment is scaled up to the tier granularity */
RtlZeroMemory(&Desc, sizeof(Desc));
Status = pRtlIoEncodeMemIoResource(&Desc, CmResourceTypeMemory,
0x100000000ULL, 1,
0, 0xFFFFFFFFFFULL);
ok_eq_hex(Status, STATUS_SUCCESS);
ok_eq_uint(Desc.Type, CmResourceTypeMemoryLarge);
ok_eq_hex(Desc.Flags, CM_RESOURCE_MEMORY_LARGE_40);
ok_eq_ulong(Desc.u.Memory.Length, 0x01000000UL);
ok_eq_ulong(Desc.u.Memory.Alignment, 1UL); /* i.e. 0x100 bytes */
ok_eq_hex64(Desc.u.Memory.MaximumAddress.QuadPart, 0xFFFFFFFFFFULL);
/* Zero alignment encodes as zero */
RtlZeroMemory(&Desc, sizeof(Desc));
Status = pRtlIoEncodeMemIoResource(&Desc, CmResourceTypeMemory, 0x100000000ULL, 0, 0, 0);
ok_eq_hex(Status, STATUS_SUCCESS);
ok_eq_ulong(Desc.u.Memory.Alignment, 0UL);
/* A non-power-of-two alignment is doubled until it fits the granularity */
RtlZeroMemory(&Desc, sizeof(Desc));
Status = pRtlIoEncodeMemIoResource(&Desc, CmResourceTypeMemory, 0x100000000ULL, 0x81, 0, 0);
ok_eq_hex(Status, STATUS_SUCCESS);
ok_eq_ulong(Desc.u.Memory.Alignment, 0x81UL); /* 0x81 << 8 == 0x8100 bytes */
/*
* A small length with a >4GB alignment still selects the large form
* (the plain path needs both to fit in 32 bits)
*/
RtlZeroMemory(&Desc, sizeof(Desc));
Status = pRtlIoEncodeMemIoResource(&Desc, CmResourceTypeMemory,
0x1000, 0x100000000ULL, 0, 0);
ok_eq_hex(Status, STATUS_SUCCESS);
ok_eq_uint(Desc.Type, CmResourceTypeMemoryLarge);
ok_eq_hex(Desc.Flags, CM_RESOURCE_MEMORY_LARGE_40);
ok_eq_ulong(Desc.u.Memory.Length, 0x10UL);
ok_eq_ulong(Desc.u.Memory.Alignment, 0x01000000UL);
/* Length not representable at the tier granularity */
Status = pRtlIoEncodeMemIoResource(&Desc, CmResourceTypeMemory, 0x100000080ULL, 1, 0, 0);
ok_eq_hex(Status, STATUS_UNSUCCESSFUL);
Status = pRtlIoEncodeMemIoResource(&Desc, CmResourceTypeMemory, 0x10000000100ULL, 1, 0, 0);
ok_eq_hex(Status, STATUS_UNSUCCESSFUL);
/* Alignment above the tier picked by the length */
Status = pRtlIoEncodeMemIoResource(&Desc, CmResourceTypeMemory,
0x100000000ULL, 0x10000000000ULL, 0, 0);
ok_eq_hex(Status, STATUS_UNSUCCESSFUL);
/* Alignment whose scaling overflows 64 bits */
Status = pRtlIoEncodeMemIoResource(&Desc, CmResourceTypeMemory,
0x1000000000000ULL, 0x8000000000000001ULL, 0, 0);
ok_eq_hex(Status, STATUS_UNSUCCESSFUL);
/* 64-bit tier */
RtlZeroMemory(&Desc, sizeof(Desc));
Status = pRtlIoEncodeMemIoResource(&Desc, CmResourceTypeMemory,
0x1000000000000ULL, 0x100000000ULL,
0x100000000ULL, 0xFFFFFFFFFFFFFFFFULL);
ok_eq_hex(Status, STATUS_SUCCESS);
ok_eq_hex(Desc.Flags, CM_RESOURCE_MEMORY_LARGE_64);
ok_eq_ulong(Desc.u.Memory.Length, 0x10000UL);
ok_eq_ulong(Desc.u.Memory.Alignment, 1UL);
ok_eq_hex64(Desc.u.Memory.MinimumAddress.QuadPart, 0x100000000ULL);
ok_eq_hex64(Desc.u.Memory.MaximumAddress.QuadPart, 0xFFFFFFFFFFFFFFFFULL);
/* Length beyond every tier */
Status = pRtlIoEncodeMemIoResource(&Desc, CmResourceTypeMemory,
ULLONG_MAX, 1, 0, 0);
ok_eq_hex(Status, STATUS_UNSUCCESSFUL);
}
static
void
Test_IoDecode(void)
{
IO_RESOURCE_DESCRIPTOR Desc;
ULONGLONG Length, Alignment, Minimum, Maximum;
NTSTATUS Status;
/* Plain memory requirement */
RtlZeroMemory(&Desc, sizeof(Desc));
Desc.Type = CmResourceTypeMemory;
Desc.u.Memory.Length = 0x2000;
Desc.u.Memory.Alignment = 0x1000;
Desc.u.Memory.MinimumAddress.QuadPart = 0x100000;
Desc.u.Memory.MaximumAddress.QuadPart = 0xFFFFFFFFULL;
Alignment = Minimum = Maximum = 0;
Length = pRtlIoDecodeMemIoResource(&Desc, &Alignment, &Minimum, &Maximum);
ok_eq_hex64(Length, 0x2000);
ok_eq_hex64(Alignment, 0x1000);
ok_eq_hex64(Minimum, 0x100000);
ok_eq_hex64(Maximum, 0xFFFFFFFFULL);
Length = pRtlIoDecodeMemIoResource(&Desc, NULL, NULL, NULL);
ok_eq_hex64(Length, 0x2000);
/* Length and alignment both scale */
RtlZeroMemory(&Desc, sizeof(Desc));
Desc.Type = CmResourceTypeMemoryLarge;
Desc.Flags = CM_RESOURCE_MEMORY_LARGE_40;
Desc.u.Memory.Length = 0x01000000;
Desc.u.Memory.Alignment = 0x01000000;
Alignment = 0;
Length = pRtlIoDecodeMemIoResource(&Desc, &Alignment, NULL, NULL);
ok_eq_hex64(Length, 0x100000000ULL);
ok_eq_hex64(Alignment, 0x100000000ULL);
Desc.Flags = CM_RESOURCE_MEMORY_LARGE_48;
Length = pRtlIoDecodeMemIoResource(&Desc, &Alignment, NULL, NULL);
ok_eq_hex64(Length, 0x10000000000ULL);
ok_eq_hex64(Alignment, 0x10000000000ULL);
Desc.Flags = CM_RESOURCE_MEMORY_LARGE_64;
Length = pRtlIoDecodeMemIoResource(&Desc, &Alignment, NULL, NULL);
ok_eq_hex64(Length, 0x100000000000000ULL);
ok_eq_hex64(Alignment, 0x100000000000000ULL);
/* A large type without large flags decodes to zero, bounds still returned */
Desc.Flags = 0;
Desc.u.Memory.MinimumAddress.QuadPart = 0x1234;
Desc.u.Memory.MaximumAddress.QuadPart = 0x5678;
Alignment = Minimum = Maximum = ULLONG_MAX;
Length = pRtlIoDecodeMemIoResource(&Desc, &Alignment, &Minimum, &Maximum);
ok_eq_hex64(Length, 0);
ok_eq_hex64(Alignment, 0);
ok_eq_hex64(Minimum, 0x1234);
ok_eq_hex64(Maximum, 0x5678);
/* Encode/decode round-trip: the rounded-up alignment is what comes back */
RtlZeroMemory(&Desc, sizeof(Desc));
Status = pRtlIoEncodeMemIoResource(&Desc, CmResourceTypeMemory,
0x10000000000ULL, 0x1000,
0x10000, 0xFFFFFFFFFFFFULL);
ok_eq_hex(Status, STATUS_SUCCESS);
Alignment = Minimum = Maximum = 0;
Length = pRtlIoDecodeMemIoResource(&Desc, &Alignment, &Minimum, &Maximum);
ok_eq_hex64(Length, 0x10000000000ULL);
ok_eq_hex64(Alignment, 0x10000); /* 0x1000 rounded up to the 64K granularity */
ok_eq_hex64(Minimum, 0x10000);
ok_eq_hex64(Maximum, 0xFFFFFFFFFFFFULL);
}
static
void
Test_FindClosest(void)
{
static const struct
{
ULONGLONG Source;
ULONGLONG Expected;
} Cases[] =
{
/* 32-bit values are always exact */
{ 0, 0 },
{ 0x1234, 0x1234 },
{ MAXULONG, MAXULONG },
/* 40-bit tier: rounded up to 256 bytes */
{ 0x100000000ULL, 0x100000000ULL },
{ 0x100000001ULL, 0x100000100ULL },
{ 0xFFFFFFFF00ULL, 0xFFFFFFFF00ULL },
/* just past the 40-bit tier: 64K granularity now */
{ 0xFFFFFFFF01ULL, 0x10000000000ULL },
{ 0x10000000001ULL, 0x10000010000ULL },
{ 0xFFFFFFFF0000ULL, 0xFFFFFFFF0000ULL },
/* just past the 48-bit tier: 4G granularity */
{ 0xFFFFFFFF0001ULL, 0x1000000000000ULL },
{ 0x1000000000000ULL, 0x1000000000000ULL },
{ 0x1000000000001ULL, 0x1000100000000ULL },
{ 0xFFFFFFFF00000000ULL, 0xFFFFFFFF00000000ULL },
};
CM_PARTIAL_RESOURCE_DESCRIPTOR Desc;
ULONGLONG Target;
NTSTATUS Status;
ULONG i;
for (i = 0; i < ARRAYSIZE(Cases); i++)
{
Target = ULLONG_MAX;
Status = pRtlFindClosestEncodableLength(Cases[i].Source, &Target);
ok(Status == STATUS_SUCCESS, "[%lu] failed: 0x%lx\n", i, Status);
ok(Target == Cases[i].Expected,
"[%lu] Target = 0x%I64x, expected 0x%I64x\n",
i, Target, Cases[i].Expected);
/* The returned length must itself be encodable */
RtlZeroMemory(&Desc, sizeof(Desc));
Status = pRtlCmEncodeMemIoResource(&Desc, CmResourceTypeMemory, Target, 0);
ok(Status == STATUS_SUCCESS, "[%lu] closest length not encodable: 0x%lx\n", i, Status);
}
/* Beyond the 64-bit tier there is nothing to round to */
Target = ULLONG_MAX;
Status = pRtlFindClosestEncodableLength(CM_RESOURCE_MEMORY_LARGE_64_MAXLEN + 1, &Target);
ok_eq_hex(Status, STATUS_UNSUCCESSFUL);
ok_eq_hex64(Target, 0);
Status = pRtlFindClosestEncodableLength(ULLONG_MAX, &Target);
ok_eq_hex(Status, STATUS_UNSUCCESSFUL);
}
START_TEST(RtlMemIoResource)
{
HMODULE hNtdll = GetModuleHandleW(L"ntdll.dll");
pRtlCmDecodeMemIoResource = (PVOID)GetProcAddress(hNtdll, "RtlCmDecodeMemIoResource");
pRtlCmEncodeMemIoResource = (PVOID)GetProcAddress(hNtdll, "RtlCmEncodeMemIoResource");
pRtlFindClosestEncodableLength = (PVOID)GetProcAddress(hNtdll, "RtlFindClosestEncodableLength");
pRtlIoDecodeMemIoResource = (PVOID)GetProcAddress(hNtdll, "RtlIoDecodeMemIoResource");
pRtlIoEncodeMemIoResource = (PVOID)GetProcAddress(hNtdll, "RtlIoEncodeMemIoResource");
if (!pRtlCmDecodeMemIoResource || !pRtlCmEncodeMemIoResource ||
!pRtlFindClosestEncodableLength ||
!pRtlIoDecodeMemIoResource || !pRtlIoEncodeMemIoResource)
{
skip("Rtl*MemIoResource API not available (NT 6.0+ only)\n");
return;
}
Test_CmEncode();
Test_CmDecode();
Test_IoEncode();
Test_IoDecode();
Test_FindClosest();
}

View File

@@ -114,6 +114,7 @@ extern void func_RtlIntSafe(void);
extern void func_RtlIsNameInExpression(void);
extern void func_RtlIsNameLegalDOS8Dot3(void);
extern void func_RtlLocale(void);
extern void func_RtlMemIoResource(void);
extern void func_RtlMemoryStream(void);
extern void func_RtlMultipleAllocateHeap(void);
extern void func_RtlNtPathNameToDosPathName(void);
@@ -252,6 +253,7 @@ const struct test winetest_testlist[] =
{ "RtlIsNameInExpression", func_RtlIsNameInExpression},
{ "RtlIsNameLegalDOS8Dot3", func_RtlIsNameLegalDOS8Dot3 },
{ "RtlLocale", func_RtlLocale },
{ "RtlMemIoResource", func_RtlMemIoResource },
{ "RtlMemoryStream", func_RtlMemoryStream },
{ "RtlMultipleAllocateHeap", func_RtlMultipleAllocateHeap },
{ "RtlNtPathNameToDosPathName", func_RtlNtPathNameToDosPathName },

View File

@@ -61,6 +61,7 @@ list(APPEND SOURCE
largeint.c
locale.c
luid.c
memres.c
memstream.c
message.c
network.c

329
sdk/lib/rtl/memres.c Normal file
View File

@@ -0,0 +1,329 @@
/*
* PROJECT: ReactOS system libraries
* LICENSE: MIT (https://spdx.org/licenses/MIT)
* PURPOSE: Encode/decode helpers for memory and port resource descriptors.
* COPYRIGHT: Copyright 2026 Justin Miller <justin.miller@reactos.org>
*/
/* INCLUDES *****************************************************************/
#include <rtl.h>
#define NDEBUG
#include <debug.h>
/* FUNCTIONS ****************************************************************/
/*
* A plain Port/Memory descriptor stores its Length (and, for a requirement, its
* Alignment) in a 32-bit field, so it can only describe regions up to 4 GB. A
* CmResourceTypeMemoryLarge descriptor reuses that same 32-bit field to hold a
* bigger value by dropping the low bits that are known to be zero; the Flags say
* how many were dropped:
*
* CM_RESOURCE_MEMORY_LARGE_40 (0x200) field holds bits [39:8] (<< 8)
* CM_RESOURCE_MEMORY_LARGE_48 (0x400) field holds bits [47:16] (<< 16)
* CM_RESOURCE_MEMORY_LARGE_64 (0x800) field holds bits [63:32] (<< 32)
*/
static
ULONGLONG
RtlpDecodeMemIoValue(
_In_ UCHAR Type,
_In_ USHORT Flags,
_In_ ULONG Encoded)
{
if (Type == CmResourceTypePort || Type == CmResourceTypeMemory)
return Encoded;
if (Flags & CM_RESOURCE_MEMORY_LARGE_40)
return (ULONGLONG)Encoded << 8;
if (Flags & CM_RESOURCE_MEMORY_LARGE_48)
return (ULONGLONG)Encoded << 16;
if (Flags & CM_RESOURCE_MEMORY_LARGE_64)
return (ULONGLONG)Encoded << 32;
return 0;
}
/*
* @implemented
*
* Decode an assigned (CM_PARTIAL_RESOURCE_DESCRIPTOR) memory or port descriptor:
* return its full 64-bit length and, optionally, its start address.
*/
ULONGLONG
NTAPI
RtlCmDecodeMemIoResource(
_In_ PCM_PARTIAL_RESOURCE_DESCRIPTOR Descriptor,
_Out_opt_ PULONGLONG Start)
{
if (Start != NULL)
*Start = Descriptor->u.Generic.Start.QuadPart;
return RtlpDecodeMemIoValue(Descriptor->Type,
Descriptor->Flags,
Descriptor->u.Generic.Length);
}
/*
* @implemented
*
* Decode a requirement (IO_RESOURCE_DESCRIPTOR) memory or port descriptor:
* return its full 64-bit length, maybe its (also large-encoded)
* alignment and the 64-bit minimum/maximum address bounds.
*/
ULONGLONG
NTAPI
RtlIoDecodeMemIoResource(
_In_ PIO_RESOURCE_DESCRIPTOR Descriptor,
_Out_opt_ PULONGLONG Alignment,
_Out_opt_ PULONGLONG MinimumAddress,
_Out_opt_ PULONGLONG MaximumAddress)
{
if (Alignment != NULL)
{
*Alignment = RtlpDecodeMemIoValue(Descriptor->Type,
Descriptor->Flags,
Descriptor->u.Generic.Alignment);
}
if (MinimumAddress != NULL)
*MinimumAddress = Descriptor->u.Generic.MinimumAddress.QuadPart;
if (MaximumAddress != NULL)
*MaximumAddress = Descriptor->u.Generic.MaximumAddress.QuadPart;
return RtlpDecodeMemIoValue(Descriptor->Type,
Descriptor->Flags,
Descriptor->u.Generic.Length);
}
/*
* @implemented
*
* Fill in a CM_PARTIAL_RESOURCE_DESCRIPTOR for a port or memory region. When a
* memory Length does not fit in 32 bits the descriptor is promoted to a
* CmResourceTypeMemoryLarge form.
*
* Returns STATUS_INVALID_PARAMETER for an unsupported Type (or a port length
* above 4 GB) and STATUS_UNSUCCESSFUL if the length cannot be represented by any
* large form without dropping bits.
*/
NTSTATUS
NTAPI
RtlCmEncodeMemIoResource(
_In_ PCM_PARTIAL_RESOURCE_DESCRIPTOR Descriptor,
_In_ UCHAR Type,
_In_ ULONGLONG Length,
_In_ ULONGLONG Start)
{
if (Type != CmResourceTypePort &&
Type != CmResourceTypeMemory &&
Type != CmResourceTypeMemoryLarge)
{
return STATUS_INVALID_PARAMETER;
}
/* A port descriptor has only a 32-bit length and no large form. */
if (Type == CmResourceTypePort)
{
if (Length > MAXULONG)
return STATUS_INVALID_PARAMETER;
Descriptor->Type = CmResourceTypePort;
Descriptor->u.Generic.Start.QuadPart = Start;
Descriptor->u.Generic.Length = (ULONG)Length;
return STATUS_SUCCESS;
}
/* Drop any stale large-form flags and record the start address. */
Descriptor->Flags &= ~CM_RESOURCE_MEMORY_LARGE;
Descriptor->u.Generic.Start.QuadPart = Start;
/* A length that fits 32 bits stays a plain memory descriptor. */
if (Length <= MAXULONG)
{
Descriptor->Type = CmResourceTypeMemory;
Descriptor->u.Generic.Length = (ULONG)Length;
return STATUS_SUCCESS;
}
/* Otherwise pick the tightest large form whose dropped low bits are zero. */
if (Length <= ((ULONGLONG)MAXULONG << 8) && (Length & 0xFF) == 0)
{
Descriptor->u.Generic.Length = (ULONG)(Length >> 8);
Descriptor->Flags |= CM_RESOURCE_MEMORY_LARGE_40;
}
else if (Length <= ((ULONGLONG)MAXULONG << 16) && (Length & 0xFFFF) == 0)
{
Descriptor->u.Generic.Length = (ULONG)(Length >> 16);
Descriptor->Flags |= CM_RESOURCE_MEMORY_LARGE_48;
}
else if (Length <= ((ULONGLONG)MAXULONG << 32) && (Length & 0xFFFFFFFF) == 0)
{
Descriptor->u.Generic.Length = (ULONG)(Length >> 32);
Descriptor->Flags |= CM_RESOURCE_MEMORY_LARGE_64;
}
else
{
return STATUS_UNSUCCESSFUL;
}
Descriptor->Type = CmResourceTypeMemoryLarge;
return STATUS_SUCCESS;
}
/*
* A large descriptor expresses alignment at the same granularity as its length.
* Scale the alignment up until it is a multiple of that granularity.
*/
static
BOOLEAN
RtlpEncodeLargeAlignment(
_In_ ULONGLONG Alignment,
_In_ ULONG Shift,
_Out_ PULONG Encoded)
{
ULONGLONG LowMask = ((ULONGLONG)1 << Shift) - 1;
while ((Alignment & LowMask) != 0)
{
ULONGLONG Doubled = Alignment << 1;
if (Doubled < Alignment) /* overflowed past 64 bits */
return FALSE;
Alignment = Doubled;
}
*Encoded = (ULONG)(Alignment >> Shift);
return TRUE;
}
/*
* @implemented
*
* Fill in an IO_RESOURCE_DESCRIPTOR (a resource *requirement*) for a port or
* memory range. Also encodes the alignment and the 64-bit address bounds.
* A memory length above 4 GB is promoted to a CmResourceTypeMemoryLarge form,
* which matters as drivers depend on this behavior.
*/
NTSTATUS
NTAPI
RtlIoEncodeMemIoResource(
_In_ PIO_RESOURCE_DESCRIPTOR Descriptor,
_In_ UCHAR Type,
_In_ ULONGLONG Length,
_In_ ULONGLONG Alignment,
_In_ ULONGLONG MinimumAddress,
_In_ ULONGLONG MaximumAddress)
{
ULONG Shift;
USHORT LargeFlag;
if (Type != CmResourceTypePort &&
Type != CmResourceTypeMemory &&
Type != CmResourceTypeMemoryLarge)
{
return STATUS_INVALID_PARAMETER;
}
/* A port descriptor is limited to 32-bit length and alignment. */
if (Type == CmResourceTypePort)
{
if (Length > MAXULONG || Alignment > MAXULONG)
return STATUS_INVALID_PARAMETER;
Descriptor->Type = CmResourceTypePort;
Descriptor->u.Generic.MinimumAddress.QuadPart = MinimumAddress;
Descriptor->u.Generic.MaximumAddress.QuadPart = MaximumAddress;
Descriptor->u.Generic.Length = (ULONG)Length;
Descriptor->u.Generic.Alignment = (ULONG)Alignment;
return STATUS_SUCCESS;
}
/* Memory: reset any stale large-form flags and record the address bounds. */
Descriptor->Flags &= ~CM_RESOURCE_MEMORY_LARGE;
Descriptor->u.Generic.MinimumAddress.QuadPart = MinimumAddress;
Descriptor->u.Generic.MaximumAddress.QuadPart = MaximumAddress;
/* Both values fit 32 bits: a plain memory descriptor. */
if (Length <= MAXULONG && Alignment <= MAXULONG)
{
Descriptor->Type = CmResourceTypeMemory;
Descriptor->u.Generic.Length = (ULONG)Length;
Descriptor->u.Generic.Alignment = (ULONG)Alignment;
return STATUS_SUCCESS;
}
/* Select the large form from the length; the alignment must fit that tier. */
if (Length <= ((ULONGLONG)MAXULONG << 8))
{
if (Alignment > ((ULONGLONG)MAXULONG << 8))
return STATUS_UNSUCCESSFUL;
Shift = 8;
LargeFlag = CM_RESOURCE_MEMORY_LARGE_40;
}
else if (Length <= ((ULONGLONG)MAXULONG << 16))
{
if (Alignment > ((ULONGLONG)MAXULONG << 16))
return STATUS_UNSUCCESSFUL;
Shift = 16;
LargeFlag = CM_RESOURCE_MEMORY_LARGE_48;
}
else if (Length <= ((ULONGLONG)MAXULONG << 32))
{
Shift = 32;
LargeFlag = CM_RESOURCE_MEMORY_LARGE_64;
}
else
{
return STATUS_UNSUCCESSFUL;
}
/* The length must be exactly representable at this granularity. */
if ((Length & (((ULONGLONG)1 << Shift) - 1)) != 0)
return STATUS_UNSUCCESSFUL;
if (!RtlpEncodeLargeAlignment(Alignment, Shift, &Descriptor->u.Generic.Alignment))
return STATUS_UNSUCCESSFUL;
Descriptor->Type = CmResourceTypeMemoryLarge;
Descriptor->u.Generic.Length = (ULONG)(Length >> Shift);
Descriptor->Flags |= LargeFlag;
return STATUS_SUCCESS;
}
/*
* @implemented
*
* Round a requested length up to the nearest value the descriptor
* 1:1 when it fits 32 bits, otherwise the next multiple of the
* granularity.
*/
NTSTATUS
NTAPI
RtlFindClosestEncodableLength(
_In_ ULONGLONG SourceLength,
_Out_ PULONGLONG TargetLength)
{
if (SourceLength <= MAXULONG)
*TargetLength = SourceLength;
else if (SourceLength <= ((ULONGLONG)MAXULONG << 8))
*TargetLength = (SourceLength + 0xFF) & ~(ULONGLONG)0xFF;
else if (SourceLength <= ((ULONGLONG)MAXULONG << 16))
*TargetLength = (SourceLength + 0xFFFF) & ~(ULONGLONG)0xFFFF;
else if (SourceLength <= ((ULONGLONG)MAXULONG << 32))
*TargetLength = (SourceLength + 0xFFFFFFFF) & ~(ULONGLONG)0xFFFFFFFF;
else
{
*TargetLength = 0;
return STATUS_UNSUCCESSFUL;
}
return STATUS_SUCCESS;
}