1 /*
2  * PROJECT:     ReactOS kernel-mode tests
3  * LICENSE:     LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later)
4  * PURPOSE:     Test for Rtl Range Lists
5  * COPYRIGHT:   Copyright 2020 Thomas Faber (thomas.faber@reactos.org)
6  */
7 
8 #include <kmt_test.h>
9 #include <ndk/rtlfuncs.h>
10 
11 static UCHAR MyUserData1, MyUserData2;
12 static UCHAR MyOwner1, MyOwner2;
13 
14 /* Helpers *******************************************************************/
15 static
16 NTSTATUS
17 RtlAddRangeWrapper(
18     _Inout_ PRTL_RANGE_LIST RangeList,
19     _In_ const RTL_RANGE *Range,
20     _In_ ULONG Flags)
21 {
22     return RtlAddRange(RangeList,
23                        Range->Start,
24                        Range->End,
25                        Range->Attributes,
26                        Flags,
27                        Range->UserData,
28                        Range->Owner);
29 }
30 
31 static
32 void
33 ExpectRange(
34     _In_ PCSTR File,
35     _In_ INT Line,
36     _In_ ULONG Index,
37     _In_ const RTL_RANGE *ActualRange,
38     _In_ const RTL_RANGE *ExpectedRange)
39 {
40     CHAR FileAndLine[128];
41     RtlStringCbPrintfA(FileAndLine, sizeof(FileAndLine), "%s:%d", File, Line);
42 
43     KmtOk(ActualRange->Start == ExpectedRange->Start, FileAndLine,
44         "[%lu] Start = 0x%I64x, expected 0x%I64x\n", Index, ActualRange->Start, ExpectedRange->Start);
45     KmtOk(ActualRange->End == ExpectedRange->End, FileAndLine,
46         "[%lu] End = 0x%I64x, expected 0x%I64x\n", Index, ActualRange->End, ExpectedRange->End);
47     KmtOk(ActualRange->UserData == ExpectedRange->UserData, FileAndLine,
48         "[%lu] UserData = %p, expected %p\n", Index, ActualRange->UserData, ExpectedRange->UserData);
49     KmtOk(ActualRange->Owner == ExpectedRange->Owner, FileAndLine,
50         "[%lu] Owner = %p, expected %p\n", Index, ActualRange->Owner, ExpectedRange->Owner);
51     KmtOk(ActualRange->Attributes == ExpectedRange->Attributes, FileAndLine,
52         "[%lu] Attributes = 0x%x, expected 0x%x\n", Index, ActualRange->Attributes, ExpectedRange->Attributes);
53     KmtOk(ActualRange->Flags == ExpectedRange->Flags, FileAndLine,
54         "[%lu] Flags = 0x%x, expected 0x%x\n", Index, ActualRange->Flags, ExpectedRange->Flags);
55 }
56 
57 static
58 void
59 ExpectRangeEntryList(
60     _In_ PCSTR File,
61     _In_ INT Line,
62     _In_ RTL_RANGE_LIST *RangeList,
63     _In_ ULONG NumRanges,
64     _In_reads_(NumRanges) const RTL_RANGE *Ranges)
65 {
66     NTSTATUS Status;
67     ULONG i;
68     RTL_RANGE_LIST_ITERATOR Iterator;
69     PRTL_RANGE Range;
70     CHAR FileAndLine[128];
71     RtlStringCbPrintfA(FileAndLine, sizeof(FileAndLine), "%s:%d", File, Line);
72 
73     RtlFillMemory(&Iterator, sizeof(Iterator), 0x55);
74     Range = KmtInvalidPointer;
75     Status = RtlGetFirstRange(RangeList, &Iterator, &Range);
76 #ifdef _WIN64
77     /* Padding at the end is uninitialized */
78     C_ASSERT(sizeof(Iterator) == RTL_SIZEOF_THROUGH_FIELD(RTL_RANGE_LIST_ITERATOR, Stamp) + sizeof(ULONG));
79     KmtOk((&Iterator.Stamp)[1] == 0x55555555, FileAndLine,
80         "Padding is 0x%lx\n", (&Iterator.Stamp)[1]);
81 #endif
82 
83     for (i = 0; i < NumRanges; i++)
84     {
85         if (!KmtSkip(NT_SUCCESS(Status), FileAndLine, "Range does not have %lu element(s)\n", i + 1))
86         {
87             ExpectRange(File, Line, i, Range, &Ranges[i]);
88 
89             /* Validate iterator */
90             KmtOk(Iterator.RangeListHead == &RangeList->ListHead, FileAndLine,
91                 "[%lu] Iterator.RangeListHead = %p, expected %p\n", i, Iterator.RangeListHead, &RangeList->ListHead);
92             KmtOk(Iterator.MergedHead == NULL, FileAndLine,
93                 "[%lu] Iterator.MergedHead = %p\n", i, Iterator.MergedHead);
94             KmtOk(Iterator.Current == Range, FileAndLine,
95                 "[%lu] Iterator.Current = %p, expected %p\n", i, Iterator.Current, Range);
96             KmtOk(Iterator.Stamp == RangeList->Stamp, FileAndLine,
97                 "[%lu] Iterator.Stamp = %lu, expected %lu\n", i, Iterator.Stamp, RangeList->Stamp);
98         }
99 
100         Range = KmtInvalidPointer;
101         Status = RtlGetNextRange(&Iterator, &Range, TRUE);
102     }
103 
104     /* Final iteration status */
105     KmtOk(Status == STATUS_NO_MORE_ENTRIES, FileAndLine,
106         "Status = 0x%lx after enumeration\n", Status);
107     KmtOk(Range == NULL, FileAndLine,
108         "[%lu] Range = %p\n", i, Range);
109     KmtOk(Iterator.RangeListHead == &RangeList->ListHead, FileAndLine,
110         "[%lu] Iterator.RangeListHead = %p, expected %p\n", i, Iterator.RangeListHead, &RangeList->ListHead);
111     KmtOk(Iterator.MergedHead == NULL, FileAndLine,
112         "[%lu] Iterator.MergedHead = %p\n", i, Iterator.MergedHead);
113     KmtOk(Iterator.Current == NULL, FileAndLine,
114         "[%lu] Iterator.Current = %p\n", i, Iterator.Current);
115     KmtOk(Iterator.Stamp == RangeList->Stamp, FileAndLine,
116         "[%lu] Iterator.Stamp = %lu, expected %lu\n", i, Iterator.Stamp, RangeList->Stamp);
117 
118     /* Try one more iteration */
119     Range = KmtInvalidPointer;
120     Status = RtlGetNextRange(&Iterator, &Range, TRUE);
121     KmtOk(Status == STATUS_NO_MORE_ENTRIES, FileAndLine,
122         "Status = 0x%lx after enumeration\n", Status);
123     KmtOk(Range == NULL, FileAndLine,
124         "[%lu] Range = %p\n", i, Range);
125     KmtOk(Iterator.RangeListHead == &RangeList->ListHead, FileAndLine,
126         "[%lu] Iterator.RangeListHead = %p, expected %p\n", i, Iterator.RangeListHead, &RangeList->ListHead);
127     KmtOk(Iterator.MergedHead == NULL, FileAndLine,
128         "[%lu] Iterator.MergedHead = %p\n", i, Iterator.MergedHead);
129     KmtOk(Iterator.Current == NULL, FileAndLine,
130         "[%lu] Iterator.Current = %p\n", i, Iterator.Current);
131     KmtOk(Iterator.Stamp == RangeList->Stamp, FileAndLine,
132         "[%lu] Iterator.Stamp = %lu, expected %lu\n", i, Iterator.Stamp, RangeList->Stamp);
133 }
134 
135 #define expect_range_entries(RangeList, NumRanges, Ranges) \
136     ExpectRangeEntryList(__FILE__, __LINE__, RangeList, NumRanges, Ranges)
137 
138 /* Test functions ************************************************************/
139 static
140 void
141 TestStartGreaterThanEnd(
142     _Inout_ PRTL_RANGE_LIST RangeList,
143     _Inout_ PRTL_RANGE Ranges)
144 {
145     NTSTATUS Status;
146     ULONG StartStamp = RangeList->Stamp;
147 
148     Ranges[1].Start = 0x300;
149     Ranges[1].End = 0x2ff;
150     Ranges[1].Attributes = 2;
151     Ranges[1].Flags = 0;
152     Ranges[1].UserData = &MyUserData2;
153     Ranges[1].Owner = &MyOwner2;
154 
155     /* Start > End bails out early with invalid parameter */
156     Status = RtlAddRangeWrapper(RangeList, &Ranges[1], 0);
157     ok_eq_hex(Status, STATUS_INVALID_PARAMETER);
158 
159     /* List should be unchanged */
160     ok_eq_ulong(RangeList->Flags, 0UL);
161     ok_eq_ulong(RangeList->Count, 1UL);
162     ok_eq_ulong(RangeList->Stamp, StartStamp);
163     expect_range_entries(RangeList, 1, &Ranges[0]);
164 }
165 
166 static
167 void
168 TestStartEqualsEnd(
169     _Inout_ PRTL_RANGE_LIST RangeList,
170     _Inout_ PRTL_RANGE Ranges)
171 {
172     NTSTATUS Status;
173     ULONG StartStamp = RangeList->Stamp;
174 
175     Ranges[1].Start = 0x300;
176     Ranges[1].End = 0x300;
177     Ranges[1].Attributes = 0xff;
178     Ranges[1].Flags = 0;
179     Ranges[1].UserData = &MyUserData2;
180     Ranges[1].Owner = &MyOwner2;
181 
182     /* Start == End is valid */
183     Status = RtlAddRangeWrapper(RangeList, &Ranges[1], 0);
184     ok_eq_hex(Status, STATUS_SUCCESS);
185 
186     /* List now has two entries */
187     ok_eq_ulong(RangeList->Flags, 0UL);
188     ok_eq_ulong(RangeList->Count, 2UL);
189     ok_eq_ulong(RangeList->Stamp, StartStamp + 1);
190     expect_range_entries(RangeList, 2, &Ranges[0]);
191 
192     /* Delete our new entry -- List goes back to one entry */
193     Status = RtlDeleteRange(RangeList, Ranges[1].Start, Ranges[1].End, Ranges[1].Owner);
194     ok_eq_hex(Status, STATUS_SUCCESS);
195     ok_eq_ulong(RangeList->Flags, 0UL);
196     ok_eq_ulong(RangeList->Count, 1UL);
197     ok_eq_ulong(RangeList->Stamp, StartStamp + 2);
198     expect_range_entries(RangeList, 1, &Ranges[0]);
199 }
200 
201 static
202 void
203 TestSharedFlag(
204     _Inout_ PRTL_RANGE_LIST RangeList,
205     _Inout_ PRTL_RANGE Ranges)
206 {
207     NTSTATUS Status;
208     ULONG StartStamp = RangeList->Stamp;
209 
210     Ranges[1].Start = 0x300;
211     Ranges[1].End = 0x400;
212     Ranges[1].Attributes = 2;
213     Ranges[1].Flags = RTL_RANGE_SHARED;
214     Ranges[1].UserData = &MyUserData2;
215     Ranges[1].Owner = &MyOwner2;
216 
217     /* Pass in the shared flag */
218     Status = RtlAddRangeWrapper(RangeList, &Ranges[1], RTL_RANGE_LIST_ADD_SHARED);
219     ok_eq_hex(Status, STATUS_SUCCESS);
220 
221     /* List now has two entries */
222     ok_eq_ulong(RangeList->Flags, 0UL);
223     ok_eq_ulong(RangeList->Count, 2UL);
224     ok_eq_ulong(RangeList->Stamp, StartStamp + 1);
225     expect_range_entries(RangeList, 2, &Ranges[0]);
226 
227     /* Delete our new entry -- List goes back to one entry */
228     Status = RtlDeleteRange(RangeList, Ranges[1].Start, Ranges[1].End, Ranges[1].Owner);
229     ok_eq_hex(Status, STATUS_SUCCESS);
230     ok_eq_ulong(RangeList->Flags, 0UL);
231     ok_eq_ulong(RangeList->Count, 1UL);
232     ok_eq_ulong(RangeList->Stamp, StartStamp + 2);
233     expect_range_entries(RangeList, 1, &Ranges[0]);
234 }
235 
236 static
237 void
238 TestIsAvailable(
239     _Inout_ PRTL_RANGE_LIST RangeList,
240     _Inout_ PRTL_RANGE Ranges)
241 {
242     NTSTATUS Status;
243     BOOLEAN Available;
244     ULONG StartStamp = RangeList->Stamp;
245 
246 #define is_range_available(RangeList, Start, End, pAvail)   \
247     RtlIsRangeAvailable(RangeList,                          \
248                         Start,                              \
249                         End,                                \
250                         0,                                  \
251                         0,                                  \
252                         NULL,                               \
253                         NULL,                               \
254                         pAvail)
255 
256     /* Single item range before Start */
257     Status = is_range_available(RangeList,
258                                 Ranges[0].Start - 1,
259                                 Ranges[0].Start - 1,
260                                 &Available);
261     ok_eq_hex(Status, STATUS_SUCCESS);
262     ok_eq_bool(Available, TRUE);
263 
264     /* Single item range at Start */
265     Status = is_range_available(RangeList,
266                                 Ranges[0].Start,
267                                 Ranges[0].Start,
268                                 &Available);
269     ok_eq_hex(Status, STATUS_SUCCESS);
270     ok_eq_bool(Available, FALSE);
271 
272     /* Single item range at End */
273     Status = is_range_available(RangeList,
274                                 Ranges[0].End,
275                                 Ranges[0].End,
276                                 &Available);
277     ok_eq_hex(Status, STATUS_SUCCESS);
278     ok_eq_bool(Available, FALSE);
279 
280     /* Single item range after End */
281     Status = is_range_available(RangeList,
282                                 Ranges[0].End + 1,
283                                 Ranges[0].End + 1,
284                                 &Available);
285     ok_eq_hex(Status, STATUS_SUCCESS);
286     ok_eq_bool(Available, TRUE);
287 
288     /* Range ending before Start */
289     Status = is_range_available(RangeList,
290                                 0x0,
291                                 Ranges[0].Start - 1,
292                                 &Available);
293     ok_eq_hex(Status, STATUS_SUCCESS);
294     ok_eq_bool(Available, TRUE);
295 
296     /* Range ending at Start */
297     Status = is_range_available(RangeList,
298                                 0x0,
299                                 Ranges[0].Start,
300                                 &Available);
301     ok_eq_hex(Status, STATUS_SUCCESS);
302     ok_eq_bool(Available, FALSE);
303 
304     /* Range ending in the middle */
305     Status = is_range_available(RangeList,
306                                 0x0,
307                                 (Ranges[0].Start + Ranges[0].End) / 2,
308                                 &Available);
309     ok_eq_hex(Status, STATUS_SUCCESS);
310     ok_eq_bool(Available, FALSE);
311 
312     /* Range going all the way through */
313     Status = is_range_available(RangeList,
314                                 0x0,
315                                 Ranges[0].End + 0x100,
316                                 &Available);
317     ok_eq_hex(Status, STATUS_SUCCESS);
318     ok_eq_bool(Available, FALSE);
319 
320     /* Range starting in the middle */
321     Status = is_range_available(RangeList,
322                                 (Ranges[0].Start + Ranges[0].End) / 2,
323                                 Ranges[0].End + 0x100,
324                                 &Available);
325     ok_eq_hex(Status, STATUS_SUCCESS);
326     ok_eq_bool(Available, FALSE);
327 
328     /* Range starting at End */
329     Status = is_range_available(RangeList,
330                                 Ranges[0].End,
331                                 Ranges[0].End + 0x100,
332                                 &Available);
333     ok_eq_hex(Status, STATUS_SUCCESS);
334     ok_eq_bool(Available, FALSE);
335 
336     /* Range starting after End */
337     Status = is_range_available(RangeList,
338                                 Ranges[0].End + 1,
339                                 Ranges[0].End + 0x100,
340                                 &Available);
341     ok_eq_hex(Status, STATUS_SUCCESS);
342     ok_eq_bool(Available, TRUE);
343 
344     /* Start > End, at start */
345     Status = is_range_available(RangeList,
346                                 Ranges[0].Start,
347                                 Ranges[0].Start - 1,
348                                 &Available);
349     ok_eq_hex(Status, STATUS_SUCCESS);
350     ok_eq_bool(Available, TRUE);
351 
352     /* Start > End, at start */
353     Status = is_range_available(RangeList,
354                                 Ranges[0].Start + 1,
355                                 Ranges[0].Start,
356                                 &Available);
357     ok_eq_hex(Status, STATUS_SUCCESS);
358     ok_eq_bool(Available, FALSE);
359 
360     /* Start > End, at end */
361     Status = is_range_available(RangeList,
362                                 Ranges[0].End + 1,
363                                 Ranges[0].End,
364                                 &Available);
365     ok_eq_hex(Status, STATUS_SUCCESS);
366     ok_eq_bool(Available, TRUE);
367 
368     /* Start > End, at end */
369     Status = is_range_available(RangeList,
370                                 Ranges[0].End,
371                                 Ranges[0].End - 1,
372                                 &Available);
373     ok_eq_hex(Status, STATUS_SUCCESS);
374     ok_eq_bool(Available, FALSE);
375 
376     /* Start > End, through the range */
377     Status = is_range_available(RangeList,
378                                 Ranges[0].End + 1,
379                                 Ranges[0].Start - 1,
380                                 &Available);
381     ok_eq_hex(Status, STATUS_SUCCESS);
382     ok_eq_bool(Available, TRUE);
383 
384     /* AttributesAvailableMask will make our range available */
385     Status = RtlIsRangeAvailable(RangeList,
386                                  0x0,
387                                  Ranges[0].End + 0x100,
388                                  0,
389                                  Ranges[0].Attributes,
390                                  NULL,
391                                  NULL,
392                                  &Available);
393     ok_eq_hex(Status, STATUS_SUCCESS);
394     ok_eq_bool(Available, TRUE);
395 
396     /* AttributesAvailableMask with additional bits */
397     Status = RtlIsRangeAvailable(RangeList,
398                                  0x0,
399                                  Ranges[0].End + 0x100,
400                                  0,
401                                  0xFF,
402                                  NULL,
403                                  NULL,
404                                  &Available);
405     ok_eq_hex(Status, STATUS_SUCCESS);
406     ok_eq_bool(Available, TRUE);
407 
408     ok_eq_ulong(RangeList->Stamp, StartStamp);
409 }
410 
411 /* Entry point ***************************************************************/
412 START_TEST(RtlRangeList)
413 {
414     NTSTATUS Status;
415     RTL_RANGE_LIST RangeList;
416     RTL_RANGE Ranges[5];
417     ULONG Stamp;
418 
419     RtlFillMemory(&RangeList, sizeof(RangeList), 0x55);
420     RtlInitializeRangeList(&RangeList);
421     ok(IsListEmpty(&RangeList.ListHead),
422        "RangeList.ListHead %p %p %p, expected empty\n",
423        &RangeList.ListHead, RangeList.ListHead.Flink, RangeList.ListHead.Blink);
424     ok_eq_ulong(RangeList.Flags, 0UL);
425     ok_eq_ulong(RangeList.Count, 0UL);
426     ok_eq_ulong(RangeList.Stamp, 0UL);
427 #ifdef _WIN64
428     /* Padding at the end is uninitialized */
429     C_ASSERT(sizeof(RangeList) == RTL_SIZEOF_THROUGH_FIELD(RTL_RANGE_LIST, Stamp) + sizeof(ULONG));
430     ok_eq_ulong((&RangeList.Stamp)[1], 0x55555555UL);
431 #endif
432 
433     /* Add a simple range */
434     Ranges[0].Start = 0x100;
435     Ranges[0].End = 0x200;
436     Ranges[0].Attributes = 1;
437     Ranges[0].Flags = 0;
438     Ranges[0].UserData = &MyUserData1;
439     Ranges[0].Owner = &MyOwner1;
440     Status = RtlAddRangeWrapper(&RangeList, &Ranges[0], 0);
441     ok_eq_hex(Status, STATUS_SUCCESS);
442     ok_eq_ulong(RangeList.Flags, 0UL);
443     ok_eq_ulong(RangeList.Count, 1UL);
444     ok_eq_ulong(RangeList.Stamp, 1UL);
445     expect_range_entries(&RangeList, 1, &Ranges[0]);
446 
447     /*
448      * Individual tests.
449      * These should always leave the list with our single start entry.
450      * Stamp may change between tests.
451      */
452     TestStartGreaterThanEnd(&RangeList, Ranges);
453     TestStartEqualsEnd(&RangeList, Ranges);
454     TestSharedFlag(&RangeList, Ranges);
455     TestIsAvailable(&RangeList, Ranges);
456 
457     Stamp = RangeList.Stamp;
458 
459     /* Free it and check the result */
460     RtlFreeRangeList(&RangeList);
461     ok_eq_ulong(RangeList.Flags, 0UL);
462     ok_eq_ulong(RangeList.Count, 0UL);
463     ok_eq_ulong(RangeList.Stamp, Stamp);
464     expect_range_entries(&RangeList, 0, NULL);
465 }
466