1 // SPDX-License-Identifier: BSD-2-Clause-FreeBSD
2 //
3 // Copyright (c) 2021 Tobias Kortkamp <tobik@FreeBSD.org>
4 // All rights reserved.
5 //
6 // Redistribution and use in source and binary forms, with or without
7 // modification, are permitted provided that the following conditions
8 // are met:
9 // 1. Redistributions of source code must retain the above copyright
10 // notice, this list of conditions and the following disclaimer.
11 // 2. Redistributions in binary form must reproduce the above copyright
12 // notice, this list of conditions and the following disclaimer in the
13 // documentation and/or other materials provided with the distribution.
14 //
15 // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 // SUCH DAMAGE.
26
27 #include "config.h"
28
29 #include <inttypes.h>
30 #include <stdbool.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34
35 #include "distinfo.h"
36 #include "flow.h"
37 #include "io.h"
38 #include "map.h"
39 #include "mem.h"
40 #include "mempool.h"
41 #include "peg.h"
42 #include "peg/distinfo.h"
43 #include "stack.h"
44 #include "str.h"
45
46 struct Distinfo {
47 time_t timestamp;
48 struct Mempool *pool;
49 struct Map *map;
50 struct Stack *filenames;
51 };
52
53 // Prototypes
54 static int hex2int(char);
55 static bool parse_hexstr(const char *, size_t, uint8_t *, size_t *);
56 static enum PEGCaptureFlag capture_machine(struct PEGCapture *, void *);
57
58 int
hex2int(char c)59 hex2int(char c)
60 {
61 switch (c) {
62 case '0': return 0;
63 case '1': return 1;
64 case '2': return 2;
65 case '3': return 3;
66 case '4': return 4;
67 case '5': return 5;
68 case '6': return 6;
69 case '7': return 7;
70 case '8': return 8;
71 case '9': return 9;
72 case 'A': case 'a': return 10;
73 case 'B': case 'b': return 11;
74 case 'C': case 'c': return 12;
75 case 'D': case 'd': return 13;
76 case 'E': case 'e': return 14;
77 case 'F': case 'f': return 15;
78 default: return -1;
79 }
80 }
81
82 bool
parse_hexstr(const char * buf,size_t buflen,uint8_t * outbuf,size_t * outlen)83 parse_hexstr(const char *buf, size_t buflen, uint8_t *outbuf, size_t *outlen)
84 {
85 size_t i = 0;
86 size_t j = 0;
87 while (i < buflen && j < DISTINFO_MAX_DIGEST_LEN) {
88 int c = hex2int(buf[i++]);
89 if (c == -1) {
90 return false;
91 }
92 if (i >= buflen) {
93 return false;
94 }
95 int d = hex2int(buf[i++]);
96 if (d == -1) {
97 return false;
98 }
99 outbuf[j++] = (uint8_t)((c << 4) | d);
100 }
101 if (i == buflen) {
102 *outlen = j;
103 return true;
104 } else {
105 return false;
106 }
107 }
108
109 enum PEGCaptureFlag
capture_machine(struct PEGCapture * capture,void * userdata)110 capture_machine(struct PEGCapture *capture, void *userdata)
111 {
112 SCOPE_MEMPOOL(pool);
113 struct Distinfo *this = userdata;
114 switch ((enum PEGDistinfoCaptureState)capture->state) {
115 case PEG_DISTINFO_ACCEPT:
116 break;
117 case PEG_DISTINFO_DIGEST: {
118 struct PEGCapture *c = stack_peek(this->filenames);
119 const char *filename = str_ndup(this->pool, c->buf, c->len);
120 struct DistinfoEntry *e = map_get(this->map, filename);
121 unless (e) {
122 e = mempool_alloc(this->pool, sizeof(struct DistinfoEntry));
123 e->filename = filename;
124 map_add(this->map, filename, e);
125 }
126 unless (parse_hexstr(capture->buf, capture->len, e->digest, &e->digest_len)) {
127 return PEG_CAPTURE_STOP;
128 }
129 break;
130 } case PEG_DISTINFO_FILENAME:
131 stack_push(this->filenames, capture);
132 break;
133 case PEG_DISTINFO_SIZE: {
134 struct PEGCapture *c = stack_peek(this->filenames);
135 const char *filename = str_ndup(this->pool, c->buf, c->len);
136 struct DistinfoEntry *e = map_get(this->map, filename);
137 unless (e) {
138 e = mempool_alloc(this->pool, sizeof(struct DistinfoEntry));
139 e->filename = filename;
140 map_add(this->map, filename, e);
141 }
142 const char *error = NULL;
143 const char *size = str_ndup(pool, capture->buf, capture->len);
144 e->size = strtonum(size, 0, INT64_MAX, &error);
145 if (error) {
146 return PEG_CAPTURE_STOP;
147 }
148 break;
149 } case PEG_DISTINFO_TIMESTAMP: {
150 const char *error = NULL;
151 const char *timestamp = str_ndup(pool, capture->buf, capture->len);
152 this->timestamp = strtonum(timestamp, 0, INT64_MAX, &error);
153 if (error) {
154 return PEG_CAPTURE_STOP;
155 }
156 break;
157 } case PEG_DISTINFO_CHECKSUM_ALGORITHM:
158 break;
159 }
160 return PEG_CAPTURE_CONTINUE;
161 }
162
163 struct Distinfo *
distinfo_parse(FILE * f,struct Mempool * errpool,struct Array ** errors)164 distinfo_parse(FILE *f, struct Mempool *errpool, struct Array **errors)
165 {
166 SCOPE_MEMPOOL(pool);
167
168 const char *s = slurp(f, pool);
169 unless (s) {
170 return NULL;
171 }
172
173 struct Distinfo *this = distinfo_new();
174 this->filenames = mempool_stack(pool);
175 struct PEG *peg = mempool_add(pool, peg_new(s, strlen(s)), peg_free);
176 if (peg_match(peg, peg_distinfo_decode, capture_machine, this)) {
177 this->filenames = NULL;
178 return this;
179 } else {
180 if (errors) {
181 *errors = peg_backtrace(peg, errpool);
182 }
183 distinfo_free(this);
184 return NULL;
185 }
186 }
187
188 struct Distinfo *
distinfo_new()189 distinfo_new()
190 {
191 struct Distinfo *this = xmalloc(sizeof(struct Distinfo));
192 this->pool = mempool_new();
193 this->map = mempool_map(this->pool, str_compare, NULL);
194 return this;
195 }
196
197 void
distinfo_free(struct Distinfo * this)198 distinfo_free(struct Distinfo *this)
199 {
200 if (this) {
201 mempool_free(this->pool);
202 free(this);
203 }
204 }
205
206 struct Array *
distinfo_entries(struct Distinfo * this,struct Mempool * pool)207 distinfo_entries(struct Distinfo *this, struct Mempool *pool)
208 {
209 return map_values(this->map, pool);
210 }
211
212 struct DistinfoEntry *
distinfo_entry(struct Distinfo * this,const char * filename)213 distinfo_entry(struct Distinfo *this, const char *filename)
214 {
215 return map_get(this->map, filename);
216 }
217
218 void
distinfo_add_entry(struct Distinfo * this,struct DistinfoEntry * e)219 distinfo_add_entry(struct Distinfo *this, struct DistinfoEntry *e)
220 {
221 panic_if(e->digest_len > DISTINFO_MAX_DIGEST_LEN, "digest_len > DISTINFO_MAX_DIGEST_LEN");
222 panic_unless(e->filename, "adding distinfo entry without filename");
223 unless (map_contains(this->map, e->filename)) {
224 struct DistinfoEntry *entry = mempool_alloc(this->pool, sizeof(struct DistinfoEntry));
225 entry->size = e->size;
226 entry->filename = str_dup(this->pool, e->filename);
227 if (e->digest_len > 0) {
228 memcpy(entry->digest, e->digest, e->digest_len);
229 entry->digest_len = e->digest_len;
230 }
231 map_add(this->map, entry->filename, entry);
232 }
233 }
234
235 void
distinfo_remove_entry(struct Distinfo * this,const char * filename)236 distinfo_remove_entry(struct Distinfo *this, const char *filename)
237 {
238 map_remove(this->map, filename);
239 }
240
241 time_t
distinfo_timestamp(struct Distinfo * this)242 distinfo_timestamp(struct Distinfo *this)
243 {
244 return this->timestamp;
245 }
246
247 void
distinfo_set_timestamp(struct Distinfo * this,time_t timestamp)248 distinfo_set_timestamp(struct Distinfo *this, time_t timestamp)
249 {
250 this->timestamp = timestamp;
251 }
252
253 void
distinfo_entry_serialize(struct DistinfoEntry * entry,FILE * f)254 distinfo_entry_serialize(struct DistinfoEntry *entry, FILE *f)
255 {
256 panic_if(entry->digest_len > DISTINFO_MAX_DIGEST_LEN, "digest_len > DISTINFO_MAX_DIGEST_LEN");
257 fprintf(f, "SHA256 (%s) = ", entry->filename);
258 for (size_t i = 0; i < entry->digest_len; i++) {
259 fprintf(f, "%02x", entry->digest[i]);
260 }
261 fprintf(f, "\nSIZE (%s) = %lld\n", entry->filename, (long long)entry->size);
262 }
263
264 void
distinfo_serialize(struct Distinfo * this,FILE * f)265 distinfo_serialize(struct Distinfo *this, FILE *f)
266 {
267 if (this->timestamp > 0) {
268 fprintf(f, "TIMESTAMP = %ju\n", (uintmax_t)this->timestamp);
269 }
270 MAP_FOREACH(this->map, const char *, filename, struct DistinfoEntry *, entry) {
271 distinfo_entry_serialize(entry, f);
272 }
273 }
274