GCC Code Coverage Report


Directory: src/
File: src/mpq-verify.c
Date: 2026-10-06 13:47:41
Exec Total Coverage
Lines: 138 155 89.0%
Functions: 2 2 100.0%
Branches: 112 132 84.8%

Line Branch Exec Source
1 /*
2 * mpq-verify.c -- explicit file checksum verification.
3 *
4 * Copyright (c) 2003-2026 Maik Broemme <mbroemme@libmpq.org>
5 *
6 * This file is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation; either version 2.1 of the License, or
9 * (at your option) any later version.
10 *
11 * This file is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this file; if not, see <https://www.gnu.org/licenses/>.
18 */
19
20 #include "mpq-verify.h"
21 #include "mpq-archive.h"
22 #include "mpq-attributes.h"
23 #include "mpq-block.h"
24 #include "mpq-md5.h"
25 #include "mpq-reader.h"
26
27 #include <limits.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <zlib.h>
31
32 /*
33 * Reuse the same table loader and packed/decrypted checksum check as file
34 * verification. Do not publish outputs until reading and decoding succeed.
35 */
36 int32_t
37 1784 libmpq__verify_block(
38 mpq_archive_s *archive, uint32_t file_number, uint32_t block_number, uint32_t *checksum,
39 uint32_t *mismatches
40 )
41 {
42 1784 uint32_t *checksums = NULL;
43 1784 uint8_t *buffer = NULL;
44 1784 uint32_t mismatch_mask = 0;
45 1784 uint32_t stored = 0;
46 uint32_t storage;
47 1784 libmpq__off_t size = 0;
48 1784 libmpq__off_t transferred = 0;
49 int32_t status;
50
51
2/2
✓ Branch 0 taken 1783 times.
✓ Branch 1 taken 1 times.
1784 if (checksum != NULL)
52 1783 *checksum = 0;
53
2/2
✓ Branch 0 taken 1783 times.
✓ Branch 1 taken 1 times.
1784 if (mismatches != NULL)
54 1783 *mismatches = 0;
55
4/6
✓ Branch 0 taken 1781 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 1781 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1781 times.
1784 if (archive == NULL || checksum == NULL || mismatches == NULL)
56 3 return LIBMPQ_ERROR_EXIST;
57
2/2
✓ Branch 0 taken 98 times.
✓ Branch 1 taken 1683 times.
1781 if (archive->write_mode)
58 98 return LIBMPQ_ERROR_NOT_INITIALIZED;
59
4/4
✓ Branch 1 taken 1615 times.
✓ Branch 2 taken 68 times.
✓ Branch 3 taken 78 times.
✓ Branch 4 taken 1537 times.
3298 if (libmpq__reader_validate_file_number(archive, file_number) < 0 ||
60 1615 libmpq__reader_validate_block_number(archive, file_number, block_number) < 0)
61 146 return LIBMPQ_ERROR_EXIST;
62 1537 storage = archive->mpq_block[archive->mpq_map[file_number].block_table_indices].flags;
63
3/4
✓ Branch 0 taken 1490 times.
✓ Branch 1 taken 47 times.
✓ Branch 2 taken 1490 times.
✗ Branch 3 not taken.
1537 if ((storage & LIBMPQ_FLAG_CRC) == 0 || (storage & LIBMPQ_FLAG_SINGLE) != 0 ||
64
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1490 times.
1490 (storage & (LIBMPQ_FLAG_COMPRESSED | LIBMPQ_FLAG_COMPRESS_PKZIP)) == 0)
65 47 return LIBMPQ_ERROR_EXIST;
66 1490 status = libmpq__reader_offsets_acquire(archive, file_number, NULL);
67
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1490 times.
1490 if (status < 0)
68 ✗ return status;
69 1490 status = libmpq__reader_sector_checksums(archive, file_number, &checksums);
70
2/2
✓ Branch 0 taken 66 times.
✓ Branch 1 taken 1424 times.
1490 if (status < 0)
71 66 goto cleanup;
72
4/4
✓ Branch 0 taken 1407 times.
✓ Branch 1 taken 17 times.
✓ Branch 2 taken 1398 times.
✓ Branch 3 taken 9 times.
1424 if (checksums == NULL || checksums[block_number] == 0 ||
73
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 1390 times.
1398 checksums[block_number] == UINT32_MAX) {
74 34 status = LIBMPQ_ERROR_EXIST;
75 34 goto cleanup;
76 }
77 1390 stored = checksums[block_number];
78 1390 status = libmpq__block_size_unpacked(archive, file_number, block_number, &size);
79
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1390 times.
1390 if (status < 0)
80 ✗ goto cleanup;
81
2/4
✓ Branch 0 taken 1390 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1390 times.
1390 if (size < 0 || (uint64_t)size > SIZE_MAX || (uint64_t)size > UINT_MAX) {
82 ✗ status = LIBMPQ_ERROR_SIZE;
83 ✗ goto cleanup;
84 }
85
1/2
✓ Branch 0 taken 1390 times.
✗ Branch 1 not taken.
1390 buffer = malloc(size == 0 ? 1 : (size_t)size);
86
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1390 times.
1390 if (buffer == NULL) {
87 ✗ status = LIBMPQ_ERROR_MALLOC;
88 ✗ goto cleanup;
89 }
90 1390 status = libmpq__reader_block_read(
91 archive, file_number, block_number, buffer, size, &transferred, &stored, &mismatch_mask,
92 NULL
93 );
94
3/4
✓ Branch 0 taken 66 times.
✓ Branch 1 taken 1324 times.
✓ Branch 2 taken 1324 times.
✗ Branch 3 not taken.
1390 if (status == 0 && transferred != size)
95 ✗ status = LIBMPQ_ERROR_READ;
96 1390 cleanup:
97 1490 free(buffer);
98 1490 free(checksums);
99 {
100 1490 int32_t close_status = libmpq__reader_offsets_release(archive, file_number);
101
2/2
✓ Branch 0 taken 1324 times.
✓ Branch 1 taken 166 times.
1490 if (status == 0)
102 1324 status = close_status;
103 }
104
2/2
✓ Branch 0 taken 1324 times.
✓ Branch 1 taken 166 times.
1490 if (status == 0) {
105 1324 *checksum = stored;
106 1324 *mismatches = mismatch_mask;
107 }
108 1490 return status;
109 }
110
111 /*
112 * Hash logical blocks through the existing reader without changing extraction.
113 * Publish mismatch bits only after the entire verification operation succeeds.
114 */
115 int32_t
116 1484 libmpq__verify_file(
117 mpq_archive_s *archive, uint32_t file_number, uint32_t verify_flags, uint32_t *mismatches
118 )
119 {
120 mpq_file_attributes_s attributes;
121 mpq_md5_s md5;
122 uint8_t digest[16];
123 1484 uint8_t *buffer = NULL;
124 1484 size_t buffer_capacity = 0;
125 1484 uint32_t *checksums = NULL;
126 1484 uint32_t crc = 0;
127 1484 uint32_t blocks = 0;
128 uint32_t i;
129 1484 uint32_t mismatch_mask = 0;
130 uint32_t storage;
131 1484 libmpq__off_t expected = 0;
132 1484 libmpq__off_t total = 0;
133 int32_t status;
134
135
2/2
✓ Branch 0 taken 1460 times.
✓ Branch 1 taken 24 times.
1484 if (mismatches != NULL)
136 1460 *mismatches = 0;
137
4/4
✓ Branch 0 taken 1460 times.
✓ Branch 1 taken 24 times.
✓ Branch 2 taken 24 times.
✓ Branch 3 taken 1436 times.
1484 if (archive == NULL || mismatches == NULL)
138 48 return LIBMPQ_ERROR_EXIST;
139
2/2
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 1412 times.
1436 if (archive->write_mode)
140 24 return LIBMPQ_ERROR_NOT_INITIALIZED;
141
2/2
✓ Branch 1 taken 24 times.
✓ Branch 2 taken 1388 times.
1412 if (libmpq__reader_validate_file_number(archive, file_number) < 0)
142 24 return LIBMPQ_ERROR_EXIST;
143
2/2
✓ Branch 0 taken 32 times.
✓ Branch 1 taken 1356 times.
1388 if ((verify_flags & ~LIBMPQ_VERIFY_ALL) != 0)
144 32 return LIBMPQ_ERROR_FORMAT;
145
2/2
✓ Branch 0 taken 124 times.
✓ Branch 1 taken 1232 times.
1356 if (verify_flags == 0)
146 124 return 0;
147 1232 memset(&attributes, 0, sizeof(attributes));
148
2/2
✓ Branch 0 taken 812 times.
✓ Branch 1 taken 420 times.
1232 if ((verify_flags & (LIBMPQ_VERIFY_FILE_CRC32 | LIBMPQ_VERIFY_FILE_MD5)) != 0) {
149 812 status = libmpq__file_attributes(archive, file_number, &attributes);
150
2/2
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 783 times.
812 if (status < 0)
151 29 return status;
152 }
153
2/2
✓ Branch 0 taken 449 times.
✓ Branch 1 taken 754 times.
1203 if ((attributes.flags & LIBMPQ_ATTRIBUTE_CRC32) == 0)
154 449 verify_flags &= ~LIBMPQ_VERIFY_FILE_CRC32;
155
2/2
✓ Branch 0 taken 455 times.
✓ Branch 1 taken 748 times.
1203 if ((attributes.flags & LIBMPQ_ATTRIBUTE_MD5) == 0)
156 455 verify_flags &= ~LIBMPQ_VERIFY_FILE_MD5;
157 1203 storage = archive->mpq_block[archive->mpq_map[file_number].block_table_indices].flags;
158
4/4
✓ Branch 0 taken 835 times.
✓ Branch 1 taken 368 times.
✓ Branch 2 taken 819 times.
✓ Branch 3 taken 16 times.
1203 if ((storage & LIBMPQ_FLAG_CRC) == 0 || (storage & LIBMPQ_FLAG_SINGLE) != 0 ||
159
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 803 times.
819 (storage & (LIBMPQ_FLAG_COMPRESSED | LIBMPQ_FLAG_COMPRESS_PKZIP)) == 0)
160 400 verify_flags &= ~LIBMPQ_VERIFY_SECTOR_CRC;
161
2/2
✓ Branch 0 taken 122 times.
✓ Branch 1 taken 1081 times.
1203 if (verify_flags == 0)
162 122 return 0;
163 1081 status = libmpq__file_size_unpacked(archive, file_number, &expected);
164
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1081 times.
1081 if (status < 0)
165 ✗ return status;
166 1081 status = libmpq__file_blocks(archive, file_number, &blocks);
167
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1081 times.
1081 if (status < 0)
168 ✗ return status;
169 1081 status = libmpq__reader_offsets_acquire(archive, file_number, NULL);
170
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 1073 times.
1081 if (status < 0)
171 8 return status;
172
2/2
✓ Branch 0 taken 611 times.
✓ Branch 1 taken 462 times.
1073 if ((verify_flags & LIBMPQ_VERIFY_SECTOR_CRC) != 0) {
173 611 status = libmpq__reader_sector_checksums(archive, file_number, &checksums);
174
2/2
✓ Branch 0 taken 90 times.
✓ Branch 1 taken 521 times.
611 if (status < 0)
175 90 goto cleanup;
176 }
177
2/2
✓ Branch 0 taken 463 times.
✓ Branch 1 taken 520 times.
983 if (checksums == NULL &&
178
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 462 times.
463 (verify_flags & (LIBMPQ_VERIFY_FILE_CRC32 | LIBMPQ_VERIFY_FILE_MD5)) == 0)
179 1 goto cleanup;
180
2/2
✓ Branch 0 taken 564 times.
✓ Branch 1 taken 418 times.
982 if ((verify_flags & LIBMPQ_VERIFY_FILE_MD5) != 0)
181 564 libmpq__md5_init(&md5);
182
2/2
✓ Branch 0 taken 9703 times.
✓ Branch 1 taken 916 times.
10619 for (i = 0; i < blocks; ++i) {
183 9703 libmpq__off_t size = 0;
184 9703 libmpq__off_t transferred = 0;
185 9703 status = libmpq__block_size_unpacked(archive, file_number, i, &size);
186
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9703 times.
9703 if (status < 0)
187 66 goto cleanup;
188
2/4
✓ Branch 0 taken 9703 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9703 times.
✗ Branch 3 not taken.
9703 if (size < 0 || (uint64_t)size > SIZE_MAX || (uint64_t)size > UINT_MAX ||
189
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9703 times.
9703 size > expected - total) {
190 ✗ status = LIBMPQ_ERROR_SIZE;
191 ✗ goto cleanup;
192 }
193
194 /* The operation owns one decoded-sector buffer and already holds offsets. */
195
4/4
✓ Branch 0 taken 8753 times.
✓ Branch 1 taken 950 times.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 8747 times.
9703 if ((size_t)size > buffer_capacity || buffer == NULL) {
196
2/2
✓ Branch 0 taken 950 times.
✓ Branch 1 taken 6 times.
956 size_t needed = size == 0 ? 1 : (size_t)size;
197 956 uint8_t *resized = realloc(buffer, needed);
198
199
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 956 times.
956 if (resized == NULL) {
200 ✗ status = LIBMPQ_ERROR_MALLOC;
201 ✗ goto cleanup;
202 }
203 956 buffer = resized;
204 956 buffer_capacity = needed;
205 }
206 9703 status = libmpq__reader_block_read_acquired(
207 archive, file_number, i, buffer, size, &transferred,
208
2/2
✓ Branch 0 taken 5377 times.
✓ Branch 1 taken 4326 times.
9703 checksums != NULL ? checksums + i : NULL, &mismatch_mask, NULL
209 );
210
2/2
✓ Branch 0 taken 66 times.
✓ Branch 1 taken 9637 times.
9703 if (status < 0)
211 66 goto cleanup;
212
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9637 times.
9637 if (transferred != size) {
213 ✗ status = LIBMPQ_ERROR_READ;
214 ✗ goto cleanup;
215 }
216
2/2
✓ Branch 0 taken 5630 times.
✓ Branch 1 taken 4007 times.
9637 if ((verify_flags & LIBMPQ_VERIFY_FILE_CRC32) != 0)
217 5630 crc = (uint32_t)crc32(crc, buffer, (uInt)size);
218
2/2
✓ Branch 0 taken 5624 times.
✓ Branch 1 taken 4013 times.
9637 if ((verify_flags & LIBMPQ_VERIFY_FILE_MD5) != 0)
219 5624 libmpq__md5_update(&md5, buffer, (size_t)size);
220 9637 total += size;
221 }
222
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 916 times.
916 if (total != expected) {
223 ✗ status = LIBMPQ_ERROR_READ;
224 ✗ goto cleanup;
225 }
226
2/2
✓ Branch 0 taken 548 times.
✓ Branch 1 taken 368 times.
916 if ((verify_flags & LIBMPQ_VERIFY_FILE_MD5) != 0) {
227 548 libmpq__md5_final(&md5, digest);
228 }
229 916 libmpq__attributes_compare_file(&attributes, verify_flags, crc, digest, &mismatch_mask);
230 1073 cleanup:
231 1073 free(checksums);
232 1073 free(buffer);
233 {
234 1073 int32_t close_status = libmpq__reader_offsets_release(archive, file_number);
235
2/2
✓ Branch 0 taken 917 times.
✓ Branch 1 taken 156 times.
1073 if (status == 0)
236 917 status = close_status;
237 }
238
2/2
✓ Branch 0 taken 917 times.
✓ Branch 1 taken 156 times.
1073 if (status == 0)
239 917 *mismatches = mismatch_mask;
240 1073 return status;
241 }
242