/* * PROJECT: ReactOS kernel-mode tests * LICENSE: LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later) * PURPOSE: Test for Rtl Range Lists * COPYRIGHT: Copyright 2020 Thomas Faber (thomas.faber@reactos.org) */ #include #include static UCHAR MyUserData1, MyUserData2; static UCHAR MyOwner1, MyOwner2; /* Helpers *******************************************************************/ static NTSTATUS RtlAddRangeWrapper( _Inout_ PRTL_RANGE_LIST RangeList, _In_ const RTL_RANGE *Range, _In_ ULONG Flags) { return RtlAddRange(RangeList, Range->Start, Range->End, Range->Attributes, Flags, Range->UserData, Range->Owner); } static void ExpectRange( _In_ PCSTR File, _In_ INT Line, _In_ ULONG Index, _In_ const RTL_RANGE *ActualRange, _In_ const RTL_RANGE *ExpectedRange) { CHAR FileAndLine[128]; RtlStringCbPrintfA(FileAndLine, sizeof(FileAndLine), "%s:%d", File, Line); KmtOk(ActualRange->Start == ExpectedRange->Start, FileAndLine, "[%lu] Start = 0x%I64x, expected 0x%I64x\n", Index, ActualRange->Start, ExpectedRange->Start); KmtOk(ActualRange->End == ExpectedRange->End, FileAndLine, "[%lu] End = 0x%I64x, expected 0x%I64x\n", Index, ActualRange->End, ExpectedRange->End); KmtOk(ActualRange->UserData == ExpectedRange->UserData, FileAndLine, "[%lu] UserData = %p, expected %p\n", Index, ActualRange->UserData, ExpectedRange->UserData); KmtOk(ActualRange->Owner == ExpectedRange->Owner, FileAndLine, "[%lu] Owner = %p, expected %p\n", Index, ActualRange->Owner, ExpectedRange->Owner); KmtOk(ActualRange->Attributes == ExpectedRange->Attributes, FileAndLine, "[%lu] Attributes = 0x%x, expected 0x%x\n", Index, ActualRange->Attributes, ExpectedRange->Attributes); KmtOk(ActualRange->Flags == ExpectedRange->Flags, FileAndLine, "[%lu] Flags = 0x%x, expected 0x%x\n", Index, ActualRange->Flags, ExpectedRange->Flags); } static void ExpectRangeEntryList( _In_ PCSTR File, _In_ INT Line, _In_ RTL_RANGE_LIST *RangeList, _In_ ULONG NumRanges, _In_reads_(NumRanges) const RTL_RANGE *Ranges) { NTSTATUS Status; ULONG i; RTL_RANGE_LIST_ITERATOR Iterator; PRTL_RANGE Range; CHAR FileAndLine[128]; RtlStringCbPrintfA(FileAndLine, sizeof(FileAndLine), "%s:%d", File, Line); RtlFillMemory(&Iterator, sizeof(Iterator), 0x55); Range = KmtInvalidPointer; Status = RtlGetFirstRange(RangeList, &Iterator, &Range); #ifdef _WIN64 /* Padding at the end is uninitialized */ C_ASSERT(sizeof(Iterator) == RTL_SIZEOF_THROUGH_FIELD(RTL_RANGE_LIST_ITERATOR, Stamp) + sizeof(ULONG)); KmtOk((&Iterator.Stamp)[1] == 0x55555555, FileAndLine, "Padding is 0x%lx\n", (&Iterator.Stamp)[1]); #endif for (i = 0; i < NumRanges; i++) { if (!KmtSkip(NT_SUCCESS(Status), FileAndLine, "Range does not have %lu element(s)\n", i + 1)) { ExpectRange(File, Line, i, Range, &Ranges[i]); /* Validate iterator */ KmtOk(Iterator.RangeListHead == &RangeList->ListHead, FileAndLine, "[%lu] Iterator.RangeListHead = %p, expected %p\n", i, Iterator.RangeListHead, &RangeList->ListHead); KmtOk(Iterator.MergedHead == NULL, FileAndLine, "[%lu] Iterator.MergedHead = %p\n", i, Iterator.MergedHead); KmtOk(Iterator.Current == Range, FileAndLine, "[%lu] Iterator.Current = %p, expected %p\n", i, Iterator.Current, Range); KmtOk(Iterator.Stamp == RangeList->Stamp, FileAndLine, "[%lu] Iterator.Stamp = %lu, expected %lu\n", i, Iterator.Stamp, RangeList->Stamp); } Range = KmtInvalidPointer; Status = RtlGetNextRange(&Iterator, &Range, TRUE); } /* Final iteration status */ KmtOk(Status == STATUS_NO_MORE_ENTRIES, FileAndLine, "Status = 0x%lx after enumeration\n", Status); KmtOk(Range == NULL, FileAndLine, "[%lu] Range = %p\n", i, Range); KmtOk(Iterator.RangeListHead == &RangeList->ListHead, FileAndLine, "[%lu] Iterator.RangeListHead = %p, expected %p\n", i, Iterator.RangeListHead, &RangeList->ListHead); KmtOk(Iterator.MergedHead == NULL, FileAndLine, "[%lu] Iterator.MergedHead = %p\n", i, Iterator.MergedHead); KmtOk(Iterator.Current == NULL, FileAndLine, "[%lu] Iterator.Current = %p\n", i, Iterator.Current); KmtOk(Iterator.Stamp == RangeList->Stamp, FileAndLine, "[%lu] Iterator.Stamp = %lu, expected %lu\n", i, Iterator.Stamp, RangeList->Stamp); /* Try one more iteration */ Range = KmtInvalidPointer; Status = RtlGetNextRange(&Iterator, &Range, TRUE); KmtOk(Status == STATUS_NO_MORE_ENTRIES, FileAndLine, "Status = 0x%lx after enumeration\n", Status); KmtOk(Range == NULL, FileAndLine, "[%lu] Range = %p\n", i, Range); KmtOk(Iterator.RangeListHead == &RangeList->ListHead, FileAndLine, "[%lu] Iterator.RangeListHead = %p, expected %p\n", i, Iterator.RangeListHead, &RangeList->ListHead); KmtOk(Iterator.MergedHead == NULL, FileAndLine, "[%lu] Iterator.MergedHead = %p\n", i, Iterator.MergedHead); KmtOk(Iterator.Current == NULL, FileAndLine, "[%lu] Iterator.Current = %p\n", i, Iterator.Current); KmtOk(Iterator.Stamp == RangeList->Stamp, FileAndLine, "[%lu] Iterator.Stamp = %lu, expected %lu\n", i, Iterator.Stamp, RangeList->Stamp); } #define expect_range_entries(RangeList, NumRanges, Ranges) \ ExpectRangeEntryList(__FILE__, __LINE__, RangeList, NumRanges, Ranges) /* Test functions ************************************************************/ static void TestStartGreaterThanEnd( _Inout_ PRTL_RANGE_LIST RangeList, _Inout_ PRTL_RANGE Ranges) { NTSTATUS Status; ULONG StartStamp = RangeList->Stamp; Ranges[1].Start = 0x300; Ranges[1].End = 0x2ff; Ranges[1].Attributes = 2; Ranges[1].Flags = 0; Ranges[1].UserData = &MyUserData2; Ranges[1].Owner = &MyOwner2; /* Start > End bails out early with invalid parameter */ Status = RtlAddRangeWrapper(RangeList, &Ranges[1], 0); ok_eq_hex(Status, STATUS_INVALID_PARAMETER); /* List should be unchanged */ ok_eq_ulong(RangeList->Flags, 0UL); ok_eq_ulong(RangeList->Count, 1UL); ok_eq_ulong(RangeList->Stamp, StartStamp); expect_range_entries(RangeList, 1, &Ranges[0]); } static void TestStartEqualsEnd( _Inout_ PRTL_RANGE_LIST RangeList, _Inout_ PRTL_RANGE Ranges) { NTSTATUS Status; ULONG StartStamp = RangeList->Stamp; Ranges[1].Start = 0x300; Ranges[1].End = 0x300; Ranges[1].Attributes = 0xff; Ranges[1].Flags = 0; Ranges[1].UserData = &MyUserData2; Ranges[1].Owner = &MyOwner2; /* Start == End is valid */ Status = RtlAddRangeWrapper(RangeList, &Ranges[1], 0); ok_eq_hex(Status, STATUS_SUCCESS); /* List now has two entries */ ok_eq_ulong(RangeList->Flags, 0UL); ok_eq_ulong(RangeList->Count, 2UL); ok_eq_ulong(RangeList->Stamp, StartStamp + 1); expect_range_entries(RangeList, 2, &Ranges[0]); /* Delete our new entry -- List goes back to one entry */ Status = RtlDeleteRange(RangeList, Ranges[1].Start, Ranges[1].End, Ranges[1].Owner); ok_eq_hex(Status, STATUS_SUCCESS); ok_eq_ulong(RangeList->Flags, 0UL); ok_eq_ulong(RangeList->Count, 1UL); ok_eq_ulong(RangeList->Stamp, StartStamp + 2); expect_range_entries(RangeList, 1, &Ranges[0]); } static void TestSharedFlag( _Inout_ PRTL_RANGE_LIST RangeList, _Inout_ PRTL_RANGE Ranges) { NTSTATUS Status; ULONG StartStamp = RangeList->Stamp; Ranges[1].Start = 0x300; Ranges[1].End = 0x400; Ranges[1].Attributes = 2; Ranges[1].Flags = RTL_RANGE_SHARED; Ranges[1].UserData = &MyUserData2; Ranges[1].Owner = &MyOwner2; /* Pass in the shared flag */ Status = RtlAddRangeWrapper(RangeList, &Ranges[1], RTL_RANGE_LIST_ADD_SHARED); ok_eq_hex(Status, STATUS_SUCCESS); /* List now has two entries */ ok_eq_ulong(RangeList->Flags, 0UL); ok_eq_ulong(RangeList->Count, 2UL); ok_eq_ulong(RangeList->Stamp, StartStamp + 1); expect_range_entries(RangeList, 2, &Ranges[0]); /* Delete our new entry -- List goes back to one entry */ Status = RtlDeleteRange(RangeList, Ranges[1].Start, Ranges[1].End, Ranges[1].Owner); ok_eq_hex(Status, STATUS_SUCCESS); ok_eq_ulong(RangeList->Flags, 0UL); ok_eq_ulong(RangeList->Count, 1UL); ok_eq_ulong(RangeList->Stamp, StartStamp + 2); expect_range_entries(RangeList, 1, &Ranges[0]); } static void TestIsAvailable( _Inout_ PRTL_RANGE_LIST RangeList, _Inout_ PRTL_RANGE Ranges) { NTSTATUS Status; BOOLEAN Available; ULONG StartStamp = RangeList->Stamp; #define is_range_available(RangeList, Start, End, pAvail) \ RtlIsRangeAvailable(RangeList, \ Start, \ End, \ 0, \ 0, \ NULL, \ NULL, \ pAvail) /* Single item range before Start */ Status = is_range_available(RangeList, Ranges[0].Start - 1, Ranges[0].Start - 1, &Available); ok_eq_hex(Status, STATUS_SUCCESS); ok_eq_bool(Available, TRUE); /* Single item range at Start */ Status = is_range_available(RangeList, Ranges[0].Start, Ranges[0].Start, &Available); ok_eq_hex(Status, STATUS_SUCCESS); ok_eq_bool(Available, FALSE); /* Single item range at End */ Status = is_range_available(RangeList, Ranges[0].End, Ranges[0].End, &Available); ok_eq_hex(Status, STATUS_SUCCESS); ok_eq_bool(Available, FALSE); /* Single item range after End */ Status = is_range_available(RangeList, Ranges[0].End + 1, Ranges[0].End + 1, &Available); ok_eq_hex(Status, STATUS_SUCCESS); ok_eq_bool(Available, TRUE); /* Range ending before Start */ Status = is_range_available(RangeList, 0x0, Ranges[0].Start - 1, &Available); ok_eq_hex(Status, STATUS_SUCCESS); ok_eq_bool(Available, TRUE); /* Range ending at Start */ Status = is_range_available(RangeList, 0x0, Ranges[0].Start, &Available); ok_eq_hex(Status, STATUS_SUCCESS); ok_eq_bool(Available, FALSE); /* Range ending in the middle */ Status = is_range_available(RangeList, 0x0, (Ranges[0].Start + Ranges[0].End) / 2, &Available); ok_eq_hex(Status, STATUS_SUCCESS); ok_eq_bool(Available, FALSE); /* Range going all the way through */ Status = is_range_available(RangeList, 0x0, Ranges[0].End + 0x100, &Available); ok_eq_hex(Status, STATUS_SUCCESS); ok_eq_bool(Available, FALSE); /* Range starting in the middle */ Status = is_range_available(RangeList, (Ranges[0].Start + Ranges[0].End) / 2, Ranges[0].End + 0x100, &Available); ok_eq_hex(Status, STATUS_SUCCESS); ok_eq_bool(Available, FALSE); /* Range starting at End */ Status = is_range_available(RangeList, Ranges[0].End, Ranges[0].End + 0x100, &Available); ok_eq_hex(Status, STATUS_SUCCESS); ok_eq_bool(Available, FALSE); /* Range starting after End */ Status = is_range_available(RangeList, Ranges[0].End + 1, Ranges[0].End + 0x100, &Available); ok_eq_hex(Status, STATUS_SUCCESS); ok_eq_bool(Available, TRUE); /* Start > End, at start */ Status = is_range_available(RangeList, Ranges[0].Start, Ranges[0].Start - 1, &Available); ok_eq_hex(Status, STATUS_SUCCESS); ok_eq_bool(Available, TRUE); /* Start > End, at start */ Status = is_range_available(RangeList, Ranges[0].Start + 1, Ranges[0].Start, &Available); ok_eq_hex(Status, STATUS_SUCCESS); ok_eq_bool(Available, FALSE); /* Start > End, at end */ Status = is_range_available(RangeList, Ranges[0].End + 1, Ranges[0].End, &Available); ok_eq_hex(Status, STATUS_SUCCESS); ok_eq_bool(Available, TRUE); /* Start > End, at end */ Status = is_range_available(RangeList, Ranges[0].End, Ranges[0].End - 1, &Available); ok_eq_hex(Status, STATUS_SUCCESS); ok_eq_bool(Available, FALSE); /* Start > End, through the range */ Status = is_range_available(RangeList, Ranges[0].End + 1, Ranges[0].Start - 1, &Available); ok_eq_hex(Status, STATUS_SUCCESS); ok_eq_bool(Available, TRUE); /* AttributesAvailableMask will make our range available */ Status = RtlIsRangeAvailable(RangeList, 0x0, Ranges[0].End + 0x100, 0, Ranges[0].Attributes, NULL, NULL, &Available); ok_eq_hex(Status, STATUS_SUCCESS); ok_eq_bool(Available, TRUE); /* AttributesAvailableMask with additional bits */ Status = RtlIsRangeAvailable(RangeList, 0x0, Ranges[0].End + 0x100, 0, 0xFF, NULL, NULL, &Available); ok_eq_hex(Status, STATUS_SUCCESS); ok_eq_bool(Available, TRUE); ok_eq_ulong(RangeList->Stamp, StartStamp); } /* Entry point ***************************************************************/ START_TEST(RtlRangeList) { NTSTATUS Status; RTL_RANGE_LIST RangeList; RTL_RANGE Ranges[5]; ULONG Stamp; RtlFillMemory(&RangeList, sizeof(RangeList), 0x55); RtlInitializeRangeList(&RangeList); ok(IsListEmpty(&RangeList.ListHead), "RangeList.ListHead %p %p %p, expected empty\n", &RangeList.ListHead, RangeList.ListHead.Flink, RangeList.ListHead.Blink); ok_eq_ulong(RangeList.Flags, 0UL); ok_eq_ulong(RangeList.Count, 0UL); ok_eq_ulong(RangeList.Stamp, 0UL); #ifdef _WIN64 /* Padding at the end is uninitialized */ C_ASSERT(sizeof(RangeList) == RTL_SIZEOF_THROUGH_FIELD(RTL_RANGE_LIST, Stamp) + sizeof(ULONG)); ok_eq_ulong((&RangeList.Stamp)[1], 0x55555555UL); #endif /* Add a simple range */ Ranges[0].Start = 0x100; Ranges[0].End = 0x200; Ranges[0].Attributes = 1; Ranges[0].Flags = 0; Ranges[0].UserData = &MyUserData1; Ranges[0].Owner = &MyOwner1; Status = RtlAddRangeWrapper(&RangeList, &Ranges[0], 0); ok_eq_hex(Status, STATUS_SUCCESS); ok_eq_ulong(RangeList.Flags, 0UL); ok_eq_ulong(RangeList.Count, 1UL); ok_eq_ulong(RangeList.Stamp, 1UL); expect_range_entries(&RangeList, 1, &Ranges[0]); /* * Individual tests. * These should always leave the list with our single start entry. * Stamp may change between tests. */ TestStartGreaterThanEnd(&RangeList, Ranges); TestStartEqualsEnd(&RangeList, Ranges); TestSharedFlag(&RangeList, Ranges); TestIsAvailable(&RangeList, Ranges); Stamp = RangeList.Stamp; /* Free it and check the result */ RtlFreeRangeList(&RangeList); ok_eq_ulong(RangeList.Flags, 0UL); ok_eq_ulong(RangeList.Count, 0UL); ok_eq_ulong(RangeList.Stamp, Stamp); expect_range_entries(&RangeList, 0, NULL); }