xref: /linux/fs/ext4/hash-test.c (revision 0eaed89c18aeedf0898baf2dbf5ff027c6795152)
1*3147cac6SGuan-Chun Wu // SPDX-License-Identifier: GPL-2.0
2*3147cac6SGuan-Chun Wu /*
3*3147cac6SGuan-Chun Wu  * KUnit tests for ext4 directory hash computation.
4*3147cac6SGuan-Chun Wu  */
5*3147cac6SGuan-Chun Wu 
6*3147cac6SGuan-Chun Wu #include <kunit/test.h>
7*3147cac6SGuan-Chun Wu #include <kunit/resource.h>
8*3147cac6SGuan-Chun Wu #include <linux/fs.h>
9*3147cac6SGuan-Chun Wu #include <linux/stddef.h>
10*3147cac6SGuan-Chun Wu #include <linux/string.h>
11*3147cac6SGuan-Chun Wu #include <linux/unicode.h>
12*3147cac6SGuan-Chun Wu #include "ext4.h"
13*3147cac6SGuan-Chun Wu 
14*3147cac6SGuan-Chun Wu static void ext4_hash_init_fake_dir(struct inode *dir, struct super_block *sb)
15*3147cac6SGuan-Chun Wu {
16*3147cac6SGuan-Chun Wu 	memset(sb, 0, sizeof(*sb));
17*3147cac6SGuan-Chun Wu 	memset(dir, 0, sizeof(*dir));
18*3147cac6SGuan-Chun Wu 	dir->i_sb = sb;
19*3147cac6SGuan-Chun Wu 	strscpy(sb->s_id, "kunit-ext4", sizeof(sb->s_id));
20*3147cac6SGuan-Chun Wu }
21*3147cac6SGuan-Chun Wu 
22*3147cac6SGuan-Chun Wu static void ext4_hash_init_fake_dir_with_sbi(struct inode *dir,
23*3147cac6SGuan-Chun Wu 					     struct super_block *sb,
24*3147cac6SGuan-Chun Wu 					     struct ext4_sb_info *sbi)
25*3147cac6SGuan-Chun Wu {
26*3147cac6SGuan-Chun Wu 	ext4_hash_init_fake_dir(dir, sb);
27*3147cac6SGuan-Chun Wu 	memset(sbi, 0, sizeof(*sbi));
28*3147cac6SGuan-Chun Wu 	sb->s_fs_info = sbi;
29*3147cac6SGuan-Chun Wu 	sbi->s_sb = sb;
30*3147cac6SGuan-Chun Wu }
31*3147cac6SGuan-Chun Wu 
32*3147cac6SGuan-Chun Wu #ifdef CONFIG_FS_ENCRYPTION
33*3147cac6SGuan-Chun Wu static const struct fscrypt_operations ext4_hash_test_cryptops = {
34*3147cac6SGuan-Chun Wu 	.inode_info_offs =
35*3147cac6SGuan-Chun Wu 		(int)offsetof(struct ext4_inode_info, i_crypt_info) -
36*3147cac6SGuan-Chun Wu 		(int)offsetof(struct ext4_inode_info, vfs_inode),
37*3147cac6SGuan-Chun Wu };
38*3147cac6SGuan-Chun Wu #endif
39*3147cac6SGuan-Chun Wu 
40*3147cac6SGuan-Chun Wu static void ext4_hash_init_fake_ext4_dir(struct ext4_inode_info *ei,
41*3147cac6SGuan-Chun Wu 					 struct super_block *sb,
42*3147cac6SGuan-Chun Wu 					 struct ext4_sb_info *sbi)
43*3147cac6SGuan-Chun Wu {
44*3147cac6SGuan-Chun Wu 	struct inode *dir = &ei->vfs_inode;
45*3147cac6SGuan-Chun Wu 
46*3147cac6SGuan-Chun Wu 	memset(sb, 0, sizeof(*sb));
47*3147cac6SGuan-Chun Wu 	memset(ei, 0, sizeof(*ei));
48*3147cac6SGuan-Chun Wu 	memset(sbi, 0, sizeof(*sbi));
49*3147cac6SGuan-Chun Wu 
50*3147cac6SGuan-Chun Wu 	strscpy(sb->s_id, "kunit-ext4", sizeof(sb->s_id));
51*3147cac6SGuan-Chun Wu 	sb->s_fs_info = sbi;
52*3147cac6SGuan-Chun Wu 	sbi->s_sb = sb;
53*3147cac6SGuan-Chun Wu 
54*3147cac6SGuan-Chun Wu 	dir->i_sb = sb;
55*3147cac6SGuan-Chun Wu 	dir->i_mode = S_IFDIR;
56*3147cac6SGuan-Chun Wu 
57*3147cac6SGuan-Chun Wu #ifdef CONFIG_FS_ENCRYPTION
58*3147cac6SGuan-Chun Wu 	fscrypt_set_ops(sb, &ext4_hash_test_cryptops);
59*3147cac6SGuan-Chun Wu #endif
60*3147cac6SGuan-Chun Wu }
61*3147cac6SGuan-Chun Wu 
62*3147cac6SGuan-Chun Wu struct ext4_dirhash_test_case {
63*3147cac6SGuan-Chun Wu 	const char *name;
64*3147cac6SGuan-Chun Wu 	u32 hash_version;
65*3147cac6SGuan-Chun Wu 	const char *input;
66*3147cac6SGuan-Chun Wu 	int len;
67*3147cac6SGuan-Chun Wu 	u32 seed[4];
68*3147cac6SGuan-Chun Wu 	bool use_seed;
69*3147cac6SGuan-Chun Wu 	u32 expected_hash;
70*3147cac6SGuan-Chun Wu 	u32 expected_minor_hash;
71*3147cac6SGuan-Chun Wu };
72*3147cac6SGuan-Chun Wu 
73*3147cac6SGuan-Chun Wu static const struct ext4_dirhash_test_case ext4_dirhash_test_cases[] = {
74*3147cac6SGuan-Chun Wu 	{
75*3147cac6SGuan-Chun Wu 		.name = "legacy_abc",
76*3147cac6SGuan-Chun Wu 		.hash_version = DX_HASH_LEGACY,
77*3147cac6SGuan-Chun Wu 		.input = "abc",
78*3147cac6SGuan-Chun Wu 		.len = 3,
79*3147cac6SGuan-Chun Wu 		.use_seed = false,
80*3147cac6SGuan-Chun Wu 		.expected_hash = 0x75afd992,
81*3147cac6SGuan-Chun Wu 		.expected_minor_hash = 0x00000000,
82*3147cac6SGuan-Chun Wu 	},
83*3147cac6SGuan-Chun Wu 	{
84*3147cac6SGuan-Chun Wu 		.name = "legacy_unsigned_abc",
85*3147cac6SGuan-Chun Wu 		.hash_version = DX_HASH_LEGACY_UNSIGNED,
86*3147cac6SGuan-Chun Wu 		.input = "abc",
87*3147cac6SGuan-Chun Wu 		.len = 3,
88*3147cac6SGuan-Chun Wu 		.use_seed = false,
89*3147cac6SGuan-Chun Wu 		.expected_hash = 0x75afd992,
90*3147cac6SGuan-Chun Wu 		.expected_minor_hash = 0x00000000,
91*3147cac6SGuan-Chun Wu 	},
92*3147cac6SGuan-Chun Wu 	{
93*3147cac6SGuan-Chun Wu 		.name = "half_md4_abc",
94*3147cac6SGuan-Chun Wu 		.hash_version = DX_HASH_HALF_MD4,
95*3147cac6SGuan-Chun Wu 		.input = "abc",
96*3147cac6SGuan-Chun Wu 		.len = 3,
97*3147cac6SGuan-Chun Wu 		.use_seed = false,
98*3147cac6SGuan-Chun Wu 		.expected_hash = 0xd196a868,
99*3147cac6SGuan-Chun Wu 		.expected_minor_hash = 0xc420eb28,
100*3147cac6SGuan-Chun Wu 	},
101*3147cac6SGuan-Chun Wu 	{
102*3147cac6SGuan-Chun Wu 		.name = "half_md4_unsigned_abc",
103*3147cac6SGuan-Chun Wu 		.hash_version = DX_HASH_HALF_MD4_UNSIGNED,
104*3147cac6SGuan-Chun Wu 		.input = "abc",
105*3147cac6SGuan-Chun Wu 		.len = 3,
106*3147cac6SGuan-Chun Wu 		.use_seed = false,
107*3147cac6SGuan-Chun Wu 		.expected_hash = 0xd196a868,
108*3147cac6SGuan-Chun Wu 		.expected_minor_hash = 0xc420eb28,
109*3147cac6SGuan-Chun Wu 	},
110*3147cac6SGuan-Chun Wu 	{
111*3147cac6SGuan-Chun Wu 		.name = "tea_abc",
112*3147cac6SGuan-Chun Wu 		.hash_version = DX_HASH_TEA,
113*3147cac6SGuan-Chun Wu 		.input = "abc",
114*3147cac6SGuan-Chun Wu 		.len = 3,
115*3147cac6SGuan-Chun Wu 		.use_seed = false,
116*3147cac6SGuan-Chun Wu 		.expected_hash = 0xb1435ec4,
117*3147cac6SGuan-Chun Wu 		.expected_minor_hash = 0x3f7eaa0e,
118*3147cac6SGuan-Chun Wu 	},
119*3147cac6SGuan-Chun Wu 	{
120*3147cac6SGuan-Chun Wu 		.name = "tea_unsigned_abc",
121*3147cac6SGuan-Chun Wu 		.hash_version = DX_HASH_TEA_UNSIGNED,
122*3147cac6SGuan-Chun Wu 		.input = "abc",
123*3147cac6SGuan-Chun Wu 		.len = 3,
124*3147cac6SGuan-Chun Wu 		.use_seed = false,
125*3147cac6SGuan-Chun Wu 		.expected_hash = 0xb1435ec4,
126*3147cac6SGuan-Chun Wu 		.expected_minor_hash = 0x3f7eaa0e,
127*3147cac6SGuan-Chun Wu 	},
128*3147cac6SGuan-Chun Wu 	{
129*3147cac6SGuan-Chun Wu 		.name = "empty_half_md4",
130*3147cac6SGuan-Chun Wu 		.hash_version = DX_HASH_HALF_MD4,
131*3147cac6SGuan-Chun Wu 		.input = "",
132*3147cac6SGuan-Chun Wu 		.len = 0,
133*3147cac6SGuan-Chun Wu 		.use_seed = false,
134*3147cac6SGuan-Chun Wu 		.expected_hash = 0xefcdab88,
135*3147cac6SGuan-Chun Wu 		.expected_minor_hash = 0x98badcfe,
136*3147cac6SGuan-Chun Wu 	},
137*3147cac6SGuan-Chun Wu 	{
138*3147cac6SGuan-Chun Wu 		.name = "half_md4_31bytes",
139*3147cac6SGuan-Chun Wu 		.hash_version = DX_HASH_HALF_MD4,
140*3147cac6SGuan-Chun Wu 		.input = "1234567890123456789012345678901",
141*3147cac6SGuan-Chun Wu 		.len = 31,
142*3147cac6SGuan-Chun Wu 		.use_seed = false,
143*3147cac6SGuan-Chun Wu 		.expected_hash = 0xc4db1f78,
144*3147cac6SGuan-Chun Wu 		.expected_minor_hash = 0xea23921b,
145*3147cac6SGuan-Chun Wu 	},
146*3147cac6SGuan-Chun Wu 	{
147*3147cac6SGuan-Chun Wu 		.name = "half_md4_32bytes",
148*3147cac6SGuan-Chun Wu 		.hash_version = DX_HASH_HALF_MD4,
149*3147cac6SGuan-Chun Wu 		.input = "12345678901234567890123456789012",
150*3147cac6SGuan-Chun Wu 		.len = 32,
151*3147cac6SGuan-Chun Wu 		.use_seed = false,
152*3147cac6SGuan-Chun Wu 		.expected_hash = 0xfa6cc63e,
153*3147cac6SGuan-Chun Wu 		.expected_minor_hash = 0x2f77bd1c,
154*3147cac6SGuan-Chun Wu 	},
155*3147cac6SGuan-Chun Wu 	{
156*3147cac6SGuan-Chun Wu 		.name = "half_md4_33bytes",
157*3147cac6SGuan-Chun Wu 		.hash_version = DX_HASH_HALF_MD4,
158*3147cac6SGuan-Chun Wu 		.input = "123456789012345678901234567890123",
159*3147cac6SGuan-Chun Wu 		.len = 33,
160*3147cac6SGuan-Chun Wu 		.use_seed = false,
161*3147cac6SGuan-Chun Wu 		.expected_hash = 0xdc0c2dec,
162*3147cac6SGuan-Chun Wu 		.expected_minor_hash = 0x5ca23365,
163*3147cac6SGuan-Chun Wu 	},
164*3147cac6SGuan-Chun Wu 	{
165*3147cac6SGuan-Chun Wu 		.name = "half_md4_unsigned_31bytes",
166*3147cac6SGuan-Chun Wu 		.hash_version = DX_HASH_HALF_MD4_UNSIGNED,
167*3147cac6SGuan-Chun Wu 		.input = "1234567890123456789012345678901",
168*3147cac6SGuan-Chun Wu 		.len = 31,
169*3147cac6SGuan-Chun Wu 		.use_seed = false,
170*3147cac6SGuan-Chun Wu 		.expected_hash = 0xc4db1f78,
171*3147cac6SGuan-Chun Wu 		.expected_minor_hash = 0xea23921b,
172*3147cac6SGuan-Chun Wu 	},
173*3147cac6SGuan-Chun Wu 	{
174*3147cac6SGuan-Chun Wu 		.name = "half_md4_unsigned_32bytes",
175*3147cac6SGuan-Chun Wu 		.hash_version = DX_HASH_HALF_MD4_UNSIGNED,
176*3147cac6SGuan-Chun Wu 		.input = "12345678901234567890123456789012",
177*3147cac6SGuan-Chun Wu 		.len = 32,
178*3147cac6SGuan-Chun Wu 		.use_seed = false,
179*3147cac6SGuan-Chun Wu 		.expected_hash = 0xfa6cc63e,
180*3147cac6SGuan-Chun Wu 		.expected_minor_hash = 0x2f77bd1c,
181*3147cac6SGuan-Chun Wu 	},
182*3147cac6SGuan-Chun Wu 	{
183*3147cac6SGuan-Chun Wu 		.name = "half_md4_unsigned_33bytes",
184*3147cac6SGuan-Chun Wu 		.hash_version = DX_HASH_HALF_MD4_UNSIGNED,
185*3147cac6SGuan-Chun Wu 		.input = "123456789012345678901234567890123",
186*3147cac6SGuan-Chun Wu 		.len = 33,
187*3147cac6SGuan-Chun Wu 		.use_seed = false,
188*3147cac6SGuan-Chun Wu 		.expected_hash = 0xdc0c2dec,
189*3147cac6SGuan-Chun Wu 		.expected_minor_hash = 0x5ca23365,
190*3147cac6SGuan-Chun Wu 	},
191*3147cac6SGuan-Chun Wu 	{
192*3147cac6SGuan-Chun Wu 		.name = "tea_15bytes",
193*3147cac6SGuan-Chun Wu 		.hash_version = DX_HASH_TEA,
194*3147cac6SGuan-Chun Wu 		.input = "123456789abcdef",
195*3147cac6SGuan-Chun Wu 		.len = 15,
196*3147cac6SGuan-Chun Wu 		.use_seed = false,
197*3147cac6SGuan-Chun Wu 		.expected_hash = 0xa562903a,
198*3147cac6SGuan-Chun Wu 		.expected_minor_hash = 0x6174a00f,
199*3147cac6SGuan-Chun Wu 	},
200*3147cac6SGuan-Chun Wu 	{
201*3147cac6SGuan-Chun Wu 		.name = "tea_16bytes",
202*3147cac6SGuan-Chun Wu 		.hash_version = DX_HASH_TEA,
203*3147cac6SGuan-Chun Wu 		.input = "1234567890abcdef",
204*3147cac6SGuan-Chun Wu 		.len = 16,
205*3147cac6SGuan-Chun Wu 		.use_seed = false,
206*3147cac6SGuan-Chun Wu 		.expected_hash = 0x8449f258,
207*3147cac6SGuan-Chun Wu 		.expected_minor_hash = 0x49a16d46,
208*3147cac6SGuan-Chun Wu 	},
209*3147cac6SGuan-Chun Wu 	{
210*3147cac6SGuan-Chun Wu 		.name = "tea_17bytes",
211*3147cac6SGuan-Chun Wu 		.hash_version = DX_HASH_TEA,
212*3147cac6SGuan-Chun Wu 		.input = "123456789abcdefgh",
213*3147cac6SGuan-Chun Wu 		.len = 17,
214*3147cac6SGuan-Chun Wu 		.use_seed = false,
215*3147cac6SGuan-Chun Wu 		.expected_hash = 0xf32ec10c,
216*3147cac6SGuan-Chun Wu 		.expected_minor_hash = 0x58ceae61,
217*3147cac6SGuan-Chun Wu 	},
218*3147cac6SGuan-Chun Wu 	{
219*3147cac6SGuan-Chun Wu 		.name = "half_md4_seeded",
220*3147cac6SGuan-Chun Wu 		.hash_version = DX_HASH_HALF_MD4,
221*3147cac6SGuan-Chun Wu 		.input = "same-name",
222*3147cac6SGuan-Chun Wu 		.len = 9,
223*3147cac6SGuan-Chun Wu 		.seed = { 0x11111111, 0x22222222, 0x33333333, 0x44444444 },
224*3147cac6SGuan-Chun Wu 		.use_seed = true,
225*3147cac6SGuan-Chun Wu 		.expected_hash = 0x8aebf604,
226*3147cac6SGuan-Chun Wu 		.expected_minor_hash = 0x66ce48fe,
227*3147cac6SGuan-Chun Wu 	},
228*3147cac6SGuan-Chun Wu 	{
229*3147cac6SGuan-Chun Wu 		.name = "half_md4_non_ascii_signed",
230*3147cac6SGuan-Chun Wu 		.hash_version = DX_HASH_HALF_MD4,
231*3147cac6SGuan-Chun Wu 		.input = "\x80\x81\x82\x83\x84",
232*3147cac6SGuan-Chun Wu 		.len = 5,
233*3147cac6SGuan-Chun Wu 		.use_seed = false,
234*3147cac6SGuan-Chun Wu 		.expected_hash = 0x8bab0498,
235*3147cac6SGuan-Chun Wu 		.expected_minor_hash = 0xc326632d,
236*3147cac6SGuan-Chun Wu 	},
237*3147cac6SGuan-Chun Wu 	{
238*3147cac6SGuan-Chun Wu 		.name = "half_md4_non_ascii_unsigned",
239*3147cac6SGuan-Chun Wu 		.hash_version = DX_HASH_HALF_MD4_UNSIGNED,
240*3147cac6SGuan-Chun Wu 		.input = "\x80\x81\x82\x83\x84",
241*3147cac6SGuan-Chun Wu 		.len = 5,
242*3147cac6SGuan-Chun Wu 		.use_seed = false,
243*3147cac6SGuan-Chun Wu 		.expected_hash = 0xbc48596e,
244*3147cac6SGuan-Chun Wu 		.expected_minor_hash = 0xde0fad41,
245*3147cac6SGuan-Chun Wu 	},
246*3147cac6SGuan-Chun Wu 	{
247*3147cac6SGuan-Chun Wu 		.name = "tea_non_ascii_signed",
248*3147cac6SGuan-Chun Wu 		.hash_version = DX_HASH_TEA,
249*3147cac6SGuan-Chun Wu 		.input = "\x80\x81\x82\x83\x84",
250*3147cac6SGuan-Chun Wu 		.len = 5,
251*3147cac6SGuan-Chun Wu 		.use_seed = false,
252*3147cac6SGuan-Chun Wu 		.expected_hash = 0x21e3a154,
253*3147cac6SGuan-Chun Wu 		.expected_minor_hash = 0x90112c3d,
254*3147cac6SGuan-Chun Wu 	},
255*3147cac6SGuan-Chun Wu 	{
256*3147cac6SGuan-Chun Wu 		.name = "tea_non_ascii_unsigned",
257*3147cac6SGuan-Chun Wu 		.hash_version = DX_HASH_TEA_UNSIGNED,
258*3147cac6SGuan-Chun Wu 		.input = "\x80\x81\x82\x83\x84",
259*3147cac6SGuan-Chun Wu 		.len = 5,
260*3147cac6SGuan-Chun Wu 		.use_seed = false,
261*3147cac6SGuan-Chun Wu 		.expected_hash = 0x9b648616,
262*3147cac6SGuan-Chun Wu 		.expected_minor_hash = 0x011dd507,
263*3147cac6SGuan-Chun Wu 	},
264*3147cac6SGuan-Chun Wu };
265*3147cac6SGuan-Chun Wu 
266*3147cac6SGuan-Chun Wu static void test_ext4fs_dirhash_vectors(struct kunit *test)
267*3147cac6SGuan-Chun Wu {
268*3147cac6SGuan-Chun Wu 	struct super_block *sb;
269*3147cac6SGuan-Chun Wu 	struct inode *dir;
270*3147cac6SGuan-Chun Wu 	int i;
271*3147cac6SGuan-Chun Wu 
272*3147cac6SGuan-Chun Wu 	sb = kunit_kzalloc(test, sizeof(*sb), GFP_KERNEL);
273*3147cac6SGuan-Chun Wu 	dir = kunit_kzalloc(test, sizeof(*dir), GFP_KERNEL);
274*3147cac6SGuan-Chun Wu 	KUNIT_ASSERT_NOT_NULL(test, sb);
275*3147cac6SGuan-Chun Wu 	KUNIT_ASSERT_NOT_NULL(test, dir);
276*3147cac6SGuan-Chun Wu 
277*3147cac6SGuan-Chun Wu 	ext4_hash_init_fake_dir(dir, sb);
278*3147cac6SGuan-Chun Wu 
279*3147cac6SGuan-Chun Wu 	for (i = 0; i < ARRAY_SIZE(ext4_dirhash_test_cases); i++) {
280*3147cac6SGuan-Chun Wu 		const struct ext4_dirhash_test_case *tc =
281*3147cac6SGuan-Chun Wu 			&ext4_dirhash_test_cases[i];
282*3147cac6SGuan-Chun Wu 		struct dx_hash_info hinfo;
283*3147cac6SGuan-Chun Wu 		int ret;
284*3147cac6SGuan-Chun Wu 
285*3147cac6SGuan-Chun Wu 		memset(&hinfo, 0, sizeof(hinfo));
286*3147cac6SGuan-Chun Wu 		hinfo.hash_version = tc->hash_version;
287*3147cac6SGuan-Chun Wu 		hinfo.seed = tc->use_seed ? (u32 *)tc->seed : NULL;
288*3147cac6SGuan-Chun Wu 
289*3147cac6SGuan-Chun Wu 		ret = ext4fs_dirhash(dir, tc->input, tc->len, &hinfo);
290*3147cac6SGuan-Chun Wu 
291*3147cac6SGuan-Chun Wu 		KUNIT_ASSERT_EQ_MSG(test, ret, 0, "case=%s", tc->name);
292*3147cac6SGuan-Chun Wu 		KUNIT_EXPECT_EQ_MSG(test, hinfo.hash, tc->expected_hash,
293*3147cac6SGuan-Chun Wu 				    "case=%s", tc->name);
294*3147cac6SGuan-Chun Wu 		KUNIT_EXPECT_EQ_MSG(test, hinfo.minor_hash,
295*3147cac6SGuan-Chun Wu 				    tc->expected_minor_hash,
296*3147cac6SGuan-Chun Wu 				    "case=%s", tc->name);
297*3147cac6SGuan-Chun Wu 	}
298*3147cac6SGuan-Chun Wu }
299*3147cac6SGuan-Chun Wu 
300*3147cac6SGuan-Chun Wu static void test_ext4fs_dirhash_seed_changes_result(struct kunit *test)
301*3147cac6SGuan-Chun Wu {
302*3147cac6SGuan-Chun Wu 	struct super_block *sb;
303*3147cac6SGuan-Chun Wu 	struct inode *dir;
304*3147cac6SGuan-Chun Wu 	u32 seed[4] = { 0x11111111, 0x22222222, 0x33333333, 0x44444444 };
305*3147cac6SGuan-Chun Wu 	struct dx_hash_info plain = {
306*3147cac6SGuan-Chun Wu 		.hash_version = DX_HASH_HALF_MD4,
307*3147cac6SGuan-Chun Wu 	};
308*3147cac6SGuan-Chun Wu 	struct dx_hash_info seeded = {
309*3147cac6SGuan-Chun Wu 		.hash_version = DX_HASH_HALF_MD4,
310*3147cac6SGuan-Chun Wu 		.seed = seed,
311*3147cac6SGuan-Chun Wu 	};
312*3147cac6SGuan-Chun Wu 	int ret_plain, ret_seeded;
313*3147cac6SGuan-Chun Wu 
314*3147cac6SGuan-Chun Wu 	sb = kunit_kzalloc(test, sizeof(*sb), GFP_KERNEL);
315*3147cac6SGuan-Chun Wu 	dir = kunit_kzalloc(test, sizeof(*dir), GFP_KERNEL);
316*3147cac6SGuan-Chun Wu 	KUNIT_ASSERT_NOT_NULL(test, sb);
317*3147cac6SGuan-Chun Wu 	KUNIT_ASSERT_NOT_NULL(test, dir);
318*3147cac6SGuan-Chun Wu 
319*3147cac6SGuan-Chun Wu 	ext4_hash_init_fake_dir(dir, sb);
320*3147cac6SGuan-Chun Wu 
321*3147cac6SGuan-Chun Wu 	ret_plain = ext4fs_dirhash(dir, "same-name", 9, &plain);
322*3147cac6SGuan-Chun Wu 	ret_seeded = ext4fs_dirhash(dir, "same-name", 9, &seeded);
323*3147cac6SGuan-Chun Wu 
324*3147cac6SGuan-Chun Wu 	KUNIT_ASSERT_EQ(test, ret_plain, 0);
325*3147cac6SGuan-Chun Wu 	KUNIT_ASSERT_EQ(test, ret_seeded, 0);
326*3147cac6SGuan-Chun Wu 
327*3147cac6SGuan-Chun Wu 	KUNIT_EXPECT_TRUE(test,
328*3147cac6SGuan-Chun Wu 			  plain.hash != seeded.hash ||
329*3147cac6SGuan-Chun Wu 			  plain.minor_hash != seeded.minor_hash);
330*3147cac6SGuan-Chun Wu }
331*3147cac6SGuan-Chun Wu 
332*3147cac6SGuan-Chun Wu static void test_ext4fs_dirhash_invalid_version_returns_einval(struct kunit *test)
333*3147cac6SGuan-Chun Wu {
334*3147cac6SGuan-Chun Wu 	struct super_block *sb;
335*3147cac6SGuan-Chun Wu 	struct inode *dir;
336*3147cac6SGuan-Chun Wu 	struct ext4_sb_info *sbi;
337*3147cac6SGuan-Chun Wu 	struct dx_hash_info hinfo = {
338*3147cac6SGuan-Chun Wu 		.hash = 0xdeadbeef,
339*3147cac6SGuan-Chun Wu 		.minor_hash = 0xcafebabe,
340*3147cac6SGuan-Chun Wu 		.hash_version = DX_HASH_LAST + 1,
341*3147cac6SGuan-Chun Wu 	};
342*3147cac6SGuan-Chun Wu 	int ret;
343*3147cac6SGuan-Chun Wu 
344*3147cac6SGuan-Chun Wu 	sb = kunit_kzalloc(test, sizeof(*sb), GFP_KERNEL);
345*3147cac6SGuan-Chun Wu 	dir = kunit_kzalloc(test, sizeof(*dir), GFP_KERNEL);
346*3147cac6SGuan-Chun Wu 	sbi = kunit_kzalloc(test, sizeof(*sbi), GFP_KERNEL);
347*3147cac6SGuan-Chun Wu 	KUNIT_ASSERT_NOT_NULL(test, sb);
348*3147cac6SGuan-Chun Wu 	KUNIT_ASSERT_NOT_NULL(test, dir);
349*3147cac6SGuan-Chun Wu 	KUNIT_ASSERT_NOT_NULL(test, sbi);
350*3147cac6SGuan-Chun Wu 
351*3147cac6SGuan-Chun Wu 	ext4_hash_init_fake_dir_with_sbi(dir, sb, sbi);
352*3147cac6SGuan-Chun Wu 
353*3147cac6SGuan-Chun Wu 	ret = ext4fs_dirhash(dir, "abc", 3, &hinfo);
354*3147cac6SGuan-Chun Wu 
355*3147cac6SGuan-Chun Wu 	KUNIT_EXPECT_EQ(test, ret, -EINVAL);
356*3147cac6SGuan-Chun Wu 	KUNIT_EXPECT_EQ(test, hinfo.hash, 0);
357*3147cac6SGuan-Chun Wu 	KUNIT_EXPECT_EQ(test, hinfo.minor_hash, 0);
358*3147cac6SGuan-Chun Wu }
359*3147cac6SGuan-Chun Wu 
360*3147cac6SGuan-Chun Wu static void test_ext4fs_dirhash_siphash_without_key_returns_einval(struct kunit *test)
361*3147cac6SGuan-Chun Wu {
362*3147cac6SGuan-Chun Wu 	struct super_block *sb;
363*3147cac6SGuan-Chun Wu 	struct ext4_inode_info *ei;
364*3147cac6SGuan-Chun Wu 	struct inode *dir;
365*3147cac6SGuan-Chun Wu 	struct ext4_sb_info *sbi;
366*3147cac6SGuan-Chun Wu 	struct dx_hash_info hinfo = {
367*3147cac6SGuan-Chun Wu 		.hash_version = DX_HASH_SIPHASH,
368*3147cac6SGuan-Chun Wu 	};
369*3147cac6SGuan-Chun Wu 	int ret;
370*3147cac6SGuan-Chun Wu 
371*3147cac6SGuan-Chun Wu 	sb = kunit_kzalloc(test, sizeof(*sb), GFP_KERNEL);
372*3147cac6SGuan-Chun Wu 	ei = kunit_kzalloc(test, sizeof(*ei), GFP_KERNEL);
373*3147cac6SGuan-Chun Wu 	sbi = kunit_kzalloc(test, sizeof(*sbi), GFP_KERNEL);
374*3147cac6SGuan-Chun Wu 	KUNIT_ASSERT_NOT_NULL(test, sb);
375*3147cac6SGuan-Chun Wu 	KUNIT_ASSERT_NOT_NULL(test, ei);
376*3147cac6SGuan-Chun Wu 	KUNIT_ASSERT_NOT_NULL(test, sbi);
377*3147cac6SGuan-Chun Wu 
378*3147cac6SGuan-Chun Wu 	ext4_hash_init_fake_ext4_dir(ei, sb, sbi);
379*3147cac6SGuan-Chun Wu 	dir = &ei->vfs_inode;
380*3147cac6SGuan-Chun Wu 
381*3147cac6SGuan-Chun Wu 	ret = ext4fs_dirhash(dir, "name", strlen("name"), &hinfo);
382*3147cac6SGuan-Chun Wu 
383*3147cac6SGuan-Chun Wu 	KUNIT_EXPECT_EQ(test, ret, -EINVAL);
384*3147cac6SGuan-Chun Wu }
385*3147cac6SGuan-Chun Wu 
386*3147cac6SGuan-Chun Wu static void test_ext4fs_dirhash_signed_unsigned_differ_on_nonascii(struct kunit *test)
387*3147cac6SGuan-Chun Wu {
388*3147cac6SGuan-Chun Wu 	struct super_block *sb;
389*3147cac6SGuan-Chun Wu 	struct inode *dir;
390*3147cac6SGuan-Chun Wu 	static const char input[] = "\x80\xff\x81\xfe\101bc";
391*3147cac6SGuan-Chun Wu 	struct dx_hash_info legacy_signed = {
392*3147cac6SGuan-Chun Wu 		.hash_version = DX_HASH_LEGACY,
393*3147cac6SGuan-Chun Wu 	};
394*3147cac6SGuan-Chun Wu 	struct dx_hash_info legacy_unsigned = {
395*3147cac6SGuan-Chun Wu 		.hash_version = DX_HASH_LEGACY_UNSIGNED,
396*3147cac6SGuan-Chun Wu 	};
397*3147cac6SGuan-Chun Wu 	struct dx_hash_info md4_signed = {
398*3147cac6SGuan-Chun Wu 		.hash_version = DX_HASH_HALF_MD4,
399*3147cac6SGuan-Chun Wu 	};
400*3147cac6SGuan-Chun Wu 	struct dx_hash_info md4_unsigned = {
401*3147cac6SGuan-Chun Wu 		.hash_version = DX_HASH_HALF_MD4_UNSIGNED,
402*3147cac6SGuan-Chun Wu 	};
403*3147cac6SGuan-Chun Wu 	struct dx_hash_info tea_signed = {
404*3147cac6SGuan-Chun Wu 		.hash_version = DX_HASH_TEA,
405*3147cac6SGuan-Chun Wu 	};
406*3147cac6SGuan-Chun Wu 	struct dx_hash_info tea_unsigned = {
407*3147cac6SGuan-Chun Wu 		.hash_version = DX_HASH_TEA_UNSIGNED,
408*3147cac6SGuan-Chun Wu 	};
409*3147cac6SGuan-Chun Wu 	int ret;
410*3147cac6SGuan-Chun Wu 
411*3147cac6SGuan-Chun Wu 	sb = kunit_kzalloc(test, sizeof(*sb), GFP_KERNEL);
412*3147cac6SGuan-Chun Wu 	dir = kunit_kzalloc(test, sizeof(*dir), GFP_KERNEL);
413*3147cac6SGuan-Chun Wu 	KUNIT_ASSERT_NOT_NULL(test, sb);
414*3147cac6SGuan-Chun Wu 	KUNIT_ASSERT_NOT_NULL(test, dir);
415*3147cac6SGuan-Chun Wu 
416*3147cac6SGuan-Chun Wu 	ext4_hash_init_fake_dir(dir, sb);
417*3147cac6SGuan-Chun Wu 
418*3147cac6SGuan-Chun Wu 	ret = ext4fs_dirhash(dir, input, sizeof(input) - 1, &legacy_signed);
419*3147cac6SGuan-Chun Wu 	KUNIT_ASSERT_EQ(test, ret, 0);
420*3147cac6SGuan-Chun Wu 	ret = ext4fs_dirhash(dir, input, sizeof(input) - 1, &legacy_unsigned);
421*3147cac6SGuan-Chun Wu 	KUNIT_ASSERT_EQ(test, ret, 0);
422*3147cac6SGuan-Chun Wu 	KUNIT_EXPECT_NE(test, legacy_signed.hash, legacy_unsigned.hash);
423*3147cac6SGuan-Chun Wu 
424*3147cac6SGuan-Chun Wu 	ret = ext4fs_dirhash(dir, input, sizeof(input) - 1, &md4_signed);
425*3147cac6SGuan-Chun Wu 	KUNIT_ASSERT_EQ(test, ret, 0);
426*3147cac6SGuan-Chun Wu 	ret = ext4fs_dirhash(dir, input, sizeof(input) - 1, &md4_unsigned);
427*3147cac6SGuan-Chun Wu 	KUNIT_ASSERT_EQ(test, ret, 0);
428*3147cac6SGuan-Chun Wu 	KUNIT_EXPECT_TRUE(test,
429*3147cac6SGuan-Chun Wu 			  md4_signed.hash != md4_unsigned.hash ||
430*3147cac6SGuan-Chun Wu 			  md4_signed.minor_hash != md4_unsigned.minor_hash);
431*3147cac6SGuan-Chun Wu 
432*3147cac6SGuan-Chun Wu 	ret = ext4fs_dirhash(dir, input, sizeof(input) - 1, &tea_signed);
433*3147cac6SGuan-Chun Wu 	KUNIT_ASSERT_EQ(test, ret, 0);
434*3147cac6SGuan-Chun Wu 	ret = ext4fs_dirhash(dir, input, sizeof(input) - 1, &tea_unsigned);
435*3147cac6SGuan-Chun Wu 	KUNIT_ASSERT_EQ(test, ret, 0);
436*3147cac6SGuan-Chun Wu 	KUNIT_EXPECT_TRUE(test,
437*3147cac6SGuan-Chun Wu 			  tea_signed.hash != tea_unsigned.hash ||
438*3147cac6SGuan-Chun Wu 			  tea_signed.minor_hash != tea_unsigned.minor_hash);
439*3147cac6SGuan-Chun Wu }
440*3147cac6SGuan-Chun Wu 
441*3147cac6SGuan-Chun Wu #if IS_ENABLED(CONFIG_UNICODE)
442*3147cac6SGuan-Chun Wu KUNIT_DEFINE_ACTION_WRAPPER(utf8_unload_action, utf8_unload,
443*3147cac6SGuan-Chun Wu 			    struct unicode_map *);
444*3147cac6SGuan-Chun Wu static void test_ext4fs_dirhash_casefolded_names_hash_consistently(struct kunit *test)
445*3147cac6SGuan-Chun Wu {
446*3147cac6SGuan-Chun Wu 	struct super_block *sb;
447*3147cac6SGuan-Chun Wu 	struct ext4_inode_info *ei;
448*3147cac6SGuan-Chun Wu 	struct ext4_sb_info *sbi;
449*3147cac6SGuan-Chun Wu 	struct unicode_map *um;
450*3147cac6SGuan-Chun Wu 	struct dx_hash_info h1 = {
451*3147cac6SGuan-Chun Wu 		.hash_version = DX_HASH_HALF_MD4,
452*3147cac6SGuan-Chun Wu 	};
453*3147cac6SGuan-Chun Wu 	struct dx_hash_info h2 = {
454*3147cac6SGuan-Chun Wu 		.hash_version = DX_HASH_HALF_MD4,
455*3147cac6SGuan-Chun Wu 	};
456*3147cac6SGuan-Chun Wu 	int ret, ret1, ret2;
457*3147cac6SGuan-Chun Wu 
458*3147cac6SGuan-Chun Wu 	sb = kunit_kzalloc(test, sizeof(*sb), GFP_KERNEL);
459*3147cac6SGuan-Chun Wu 	ei = kunit_kzalloc(test, sizeof(*ei), GFP_KERNEL);
460*3147cac6SGuan-Chun Wu 	sbi = kunit_kzalloc(test, sizeof(*sbi), GFP_KERNEL);
461*3147cac6SGuan-Chun Wu 	KUNIT_ASSERT_NOT_NULL(test, sb);
462*3147cac6SGuan-Chun Wu 	KUNIT_ASSERT_NOT_NULL(test, ei);
463*3147cac6SGuan-Chun Wu 	KUNIT_ASSERT_NOT_NULL(test, sbi);
464*3147cac6SGuan-Chun Wu 
465*3147cac6SGuan-Chun Wu 	um = utf8_load(UTF8_LATEST);
466*3147cac6SGuan-Chun Wu 	if (IS_ERR(um)) {
467*3147cac6SGuan-Chun Wu 		kunit_skip(test, "utf8_load(UTF8_LATEST) failed: %pe",
468*3147cac6SGuan-Chun Wu 			   um);
469*3147cac6SGuan-Chun Wu 		return;
470*3147cac6SGuan-Chun Wu 	}
471*3147cac6SGuan-Chun Wu 
472*3147cac6SGuan-Chun Wu 	ret = kunit_add_action_or_reset(test, utf8_unload_action, um);
473*3147cac6SGuan-Chun Wu 	KUNIT_ASSERT_EQ(test, ret, 0);
474*3147cac6SGuan-Chun Wu 
475*3147cac6SGuan-Chun Wu 	ext4_hash_init_fake_ext4_dir(ei, sb, sbi);
476*3147cac6SGuan-Chun Wu 	sb->s_encoding = um;
477*3147cac6SGuan-Chun Wu 	ei->vfs_inode.i_flags |= S_CASEFOLD;
478*3147cac6SGuan-Chun Wu 
479*3147cac6SGuan-Chun Wu 	KUNIT_ASSERT_TRUE(test, IS_CASEFOLDED(&ei->vfs_inode));
480*3147cac6SGuan-Chun Wu 
481*3147cac6SGuan-Chun Wu 	ret1 = ext4fs_dirhash(&ei->vfs_inode, "Alpha", 5, &h1);
482*3147cac6SGuan-Chun Wu 	ret2 = ext4fs_dirhash(&ei->vfs_inode, "aLPHa", 5, &h2);
483*3147cac6SGuan-Chun Wu 
484*3147cac6SGuan-Chun Wu 	KUNIT_ASSERT_EQ(test, ret1, 0);
485*3147cac6SGuan-Chun Wu 	KUNIT_ASSERT_EQ(test, ret2, 0);
486*3147cac6SGuan-Chun Wu 	KUNIT_EXPECT_EQ(test, h1.hash, h2.hash);
487*3147cac6SGuan-Chun Wu 	KUNIT_EXPECT_EQ(test, h1.minor_hash, h2.minor_hash);
488*3147cac6SGuan-Chun Wu }
489*3147cac6SGuan-Chun Wu 
490*3147cac6SGuan-Chun Wu static void test_ext4fs_dirhash_casefold_fallback(struct kunit *test)
491*3147cac6SGuan-Chun Wu {
492*3147cac6SGuan-Chun Wu 	struct super_block *sb_cf, *sb_plain;
493*3147cac6SGuan-Chun Wu 	struct ext4_inode_info *ei;
494*3147cac6SGuan-Chun Wu 	struct ext4_sb_info *sbi;
495*3147cac6SGuan-Chun Wu 	struct inode *plain_dir;
496*3147cac6SGuan-Chun Wu 	struct unicode_map *um;
497*3147cac6SGuan-Chun Wu 	static const char invalid_utf8[] = "\xc3\x28";
498*3147cac6SGuan-Chun Wu 	struct dx_hash_info folded_dir = {
499*3147cac6SGuan-Chun Wu 		.hash_version = DX_HASH_HALF_MD4,
500*3147cac6SGuan-Chun Wu 	};
501*3147cac6SGuan-Chun Wu 	struct dx_hash_info plain = {
502*3147cac6SGuan-Chun Wu 		.hash_version = DX_HASH_HALF_MD4,
503*3147cac6SGuan-Chun Wu 	};
504*3147cac6SGuan-Chun Wu 	int ret, ret_cf, ret_plain;
505*3147cac6SGuan-Chun Wu 
506*3147cac6SGuan-Chun Wu 	sb_cf = kunit_kzalloc(test, sizeof(*sb_cf), GFP_KERNEL);
507*3147cac6SGuan-Chun Wu 	sb_plain = kunit_kzalloc(test, sizeof(*sb_plain), GFP_KERNEL);
508*3147cac6SGuan-Chun Wu 	ei = kunit_kzalloc(test, sizeof(*ei), GFP_KERNEL);
509*3147cac6SGuan-Chun Wu 	sbi = kunit_kzalloc(test, sizeof(*sbi), GFP_KERNEL);
510*3147cac6SGuan-Chun Wu 	plain_dir = kunit_kzalloc(test, sizeof(*plain_dir), GFP_KERNEL);
511*3147cac6SGuan-Chun Wu 	KUNIT_ASSERT_NOT_NULL(test, sb_cf);
512*3147cac6SGuan-Chun Wu 	KUNIT_ASSERT_NOT_NULL(test, sb_plain);
513*3147cac6SGuan-Chun Wu 	KUNIT_ASSERT_NOT_NULL(test, ei);
514*3147cac6SGuan-Chun Wu 	KUNIT_ASSERT_NOT_NULL(test, sbi);
515*3147cac6SGuan-Chun Wu 	KUNIT_ASSERT_NOT_NULL(test, plain_dir);
516*3147cac6SGuan-Chun Wu 
517*3147cac6SGuan-Chun Wu 	um = utf8_load(UTF8_LATEST);
518*3147cac6SGuan-Chun Wu 	if (IS_ERR(um)) {
519*3147cac6SGuan-Chun Wu 		kunit_skip(test, "utf8_load(UTF8_LATEST) failed: %pe",
520*3147cac6SGuan-Chun Wu 			   um);
521*3147cac6SGuan-Chun Wu 		return;
522*3147cac6SGuan-Chun Wu 	}
523*3147cac6SGuan-Chun Wu 
524*3147cac6SGuan-Chun Wu 	ret = kunit_add_action_or_reset(test, utf8_unload_action, um);
525*3147cac6SGuan-Chun Wu 	KUNIT_ASSERT_EQ(test, ret, 0);
526*3147cac6SGuan-Chun Wu 
527*3147cac6SGuan-Chun Wu 	ext4_hash_init_fake_ext4_dir(ei, sb_cf, sbi);
528*3147cac6SGuan-Chun Wu 	sb_cf->s_encoding = um;
529*3147cac6SGuan-Chun Wu 	ei->vfs_inode.i_flags |= S_CASEFOLD;
530*3147cac6SGuan-Chun Wu 
531*3147cac6SGuan-Chun Wu 	KUNIT_ASSERT_TRUE(test, IS_CASEFOLDED(&ei->vfs_inode));
532*3147cac6SGuan-Chun Wu 
533*3147cac6SGuan-Chun Wu 	ext4_hash_init_fake_dir(plain_dir, sb_plain);
534*3147cac6SGuan-Chun Wu 
535*3147cac6SGuan-Chun Wu 	ret_cf = ext4fs_dirhash(&ei->vfs_inode, invalid_utf8,
536*3147cac6SGuan-Chun Wu 				sizeof(invalid_utf8) - 1, &folded_dir);
537*3147cac6SGuan-Chun Wu 	ret_plain = ext4fs_dirhash(plain_dir, invalid_utf8,
538*3147cac6SGuan-Chun Wu 				   sizeof(invalid_utf8) - 1, &plain);
539*3147cac6SGuan-Chun Wu 
540*3147cac6SGuan-Chun Wu 	KUNIT_ASSERT_EQ(test, ret_cf, 0);
541*3147cac6SGuan-Chun Wu 	KUNIT_ASSERT_EQ(test, ret_plain, 0);
542*3147cac6SGuan-Chun Wu 	KUNIT_EXPECT_EQ(test, folded_dir.hash, plain.hash);
543*3147cac6SGuan-Chun Wu 	KUNIT_EXPECT_EQ(test, folded_dir.minor_hash, plain.minor_hash);
544*3147cac6SGuan-Chun Wu }
545*3147cac6SGuan-Chun Wu #endif
546*3147cac6SGuan-Chun Wu 
547*3147cac6SGuan-Chun Wu static struct kunit_case ext4_hash_test_cases[] = {
548*3147cac6SGuan-Chun Wu 	KUNIT_CASE(test_ext4fs_dirhash_vectors),
549*3147cac6SGuan-Chun Wu 	KUNIT_CASE(test_ext4fs_dirhash_seed_changes_result),
550*3147cac6SGuan-Chun Wu 	KUNIT_CASE(test_ext4fs_dirhash_invalid_version_returns_einval),
551*3147cac6SGuan-Chun Wu 	KUNIT_CASE(test_ext4fs_dirhash_siphash_without_key_returns_einval),
552*3147cac6SGuan-Chun Wu 	KUNIT_CASE(test_ext4fs_dirhash_signed_unsigned_differ_on_nonascii),
553*3147cac6SGuan-Chun Wu #if IS_ENABLED(CONFIG_UNICODE)
554*3147cac6SGuan-Chun Wu 	KUNIT_CASE(test_ext4fs_dirhash_casefolded_names_hash_consistently),
555*3147cac6SGuan-Chun Wu 	KUNIT_CASE(test_ext4fs_dirhash_casefold_fallback),
556*3147cac6SGuan-Chun Wu #endif
557*3147cac6SGuan-Chun Wu 	{}
558*3147cac6SGuan-Chun Wu };
559*3147cac6SGuan-Chun Wu 
560*3147cac6SGuan-Chun Wu static struct kunit_suite ext4_hash_test_suite = {
561*3147cac6SGuan-Chun Wu 	.name = "ext4_hash",
562*3147cac6SGuan-Chun Wu 	.test_cases = ext4_hash_test_cases,
563*3147cac6SGuan-Chun Wu };
564*3147cac6SGuan-Chun Wu 
565*3147cac6SGuan-Chun Wu kunit_test_suites(&ext4_hash_test_suite);
566*3147cac6SGuan-Chun Wu 
567*3147cac6SGuan-Chun Wu MODULE_LICENSE("GPL");
568