compr.c 3.7 KB
Newer Older
1
/*  Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <assert.h>
18

19
#include "libknot/attribute.h"
20
#include "libknot/packet/compr.h"
Daniel Salzman's avatar
Daniel Salzman committed
21 22
#include "libknot/errcode.h"
#include "libknot/packet/pkt.h"
23
#include "contrib/tolower.h"
24

25
/*! \brief Helper for \ref knot_compr_put_dname, writes label(s) with size checks. */
26 27 28 29 30 31 32 33
#define WRITE_LABEL(dst, written, label, max, len) \
	if ((written) + (len) > (max)) { \
		return KNOT_ESPACE; \
	} else { \
		memcpy((dst) + (written), (label), (len)); \
		written += (len); \
	}

34
_public_
35 36
int knot_compr_put_dname(const knot_dname_t *dname, uint8_t *dst, uint16_t max,
                         knot_compr_t *compr)
37
{
38 39 40
	if (dname == NULL || dst == NULL) {
		return KNOT_EINVAL;
	}
41 42

	/* Write uncompressible names directly (zero label dname). */
43 44 45 46
	if (compr == NULL || *dname == '\0') {
		return knot_dname_to_wire(dst, dname, max);
	}

47
	/* Get number of labels (should not be a zero label dname). */
48
	size_t name_labels = knot_dname_labels(dname, NULL);
49
	assert(name_labels > 0);
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68

	/* Suffix must not be longer than whole name. */
	const knot_dname_t *suffix = compr->wire + compr->suffix.pos;
	int suffix_labels = compr->suffix.labels;
	while (suffix_labels > name_labels) {
		suffix = knot_wire_next_label(suffix, compr->wire);
		--suffix_labels;
	}

	/* Suffix is shorter than name, write labels until aligned. */
	uint8_t orig_labels = name_labels;
	uint16_t written = 0;
	while (name_labels > suffix_labels) {
		WRITE_LABEL(dst, written, dname, max, (*dname + 1));
		dname = knot_wire_next_label(dname, NULL);
		--name_labels;
	}

	/* Label count is now equal. */
69
	assert(name_labels == suffix_labels);
70 71 72 73 74 75 76 77 78
	const knot_dname_t *match_begin = dname;
	const knot_dname_t *compr_ptr = suffix;
	while (dname[0] != '\0') {

		/* Next labels. */
		const knot_dname_t *next_dname = knot_wire_next_label(dname, NULL);
		const knot_dname_t *next_suffix = knot_wire_next_label(suffix, compr->wire);

		/* Two labels match, extend suffix length. */
79
		if (!knot_dname_label_is_equal(dname, suffix)) {
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
			/* If they don't match, write unmatched labels. */
			uint16_t mismatch_len = (dname - match_begin) + (*dname + 1);
			WRITE_LABEL(dst, written, match_begin, max, mismatch_len);
			/* Start new potential match. */
			match_begin = next_dname;
			compr_ptr = next_suffix;
		}

		/* Jump to next labels. */
		dname = next_dname;
		suffix = next_suffix;
	}

	/* If match begins at the end of the name, write '\0' label. */
	if (match_begin == dname) {
		WRITE_LABEL(dst, written, dname, max, 1);
	} else {
		/* Match covers >0 labels, write out compression pointer. */
		if (written + sizeof(uint16_t) > max) {
			return KNOT_ESPACE;
		}
		knot_wire_put_pointer(dst + written, compr_ptr - compr->wire);
		written += sizeof(uint16_t);
	}

105 106 107 108
	assert(dst >= compr->wire);
	size_t wire_pos = dst - compr->wire;
	assert(wire_pos < KNOT_WIRE_MAX_PKTSIZE);

109
	/* Heuristics - expect similar names are grouped together. */
110 111
	if (written > sizeof(uint16_t) && wire_pos + written < KNOT_WIRE_PTR_MAX) {
		compr->suffix.pos = wire_pos;
112 113
		compr->suffix.labels = orig_labels;
	}
114

115 116 117 118
	return written;
}

#undef WRITE_LABEL