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