zone-update.c 18.6 KB
Newer Older
1
/*  Copyright (C) 2016 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

    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/>.
 */

17
#include "knot/common/log.h"
18
#include "knot/dnssec/zone-events.h"
Daniel Salzman's avatar
Daniel Salzman committed
19
#include "knot/updates/zone-update.h"
20
#include "knot/zone/serial.h"
21
#include "contrib/mempattern.h"
22
#include "contrib/ucw/lists.h"
23
#include "contrib/ucw/mempool.h"
24

25 26
#include <urcu.h>

27 28
static int init_incremental(zone_update_t *update, zone_t *zone)
{
29 30 31
	if (zone->contents == NULL) {
		return KNOT_EINVAL;
	}
32

33 34 35 36
	int ret = changeset_init(&update->change, zone->name);
	if (ret != KNOT_EOK) {
		return ret;
	}
37

38 39 40 41 42 43
	ret = apply_prepare_zone_copy(zone->contents, &update->new_cont);
	if (ret != KNOT_EOK) {
		changeset_clear(&update->change);
		return ret;
	}

44 45
	uint32_t apply_flags = update->flags & UPDATE_STRICT ? APPLY_STRICT : 0;
	apply_init_ctx(&update->a_ctx, update->new_cont, apply_flags);
46

47
	/* Copy base SOA RR. */
48 49 50
	update->change.soa_from =
		node_create_rrset(update->zone->contents->apex, KNOT_RRTYPE_SOA);
	if (update->change.soa_from == NULL) {
51
		zone_contents_free(&update->new_cont);
52
		changeset_clear(&update->change);
53 54
		return KNOT_ENOMEM;
	}
55 56 57 58 59 60 61 62 63 64 65

	return KNOT_EOK;
}

static int init_full(zone_update_t *update, zone_t *zone)
{
	update->new_cont = zone_contents_new(zone->name);
	if (update->new_cont == NULL) {
		return KNOT_ENOMEM;
	}

66 67
	apply_init_ctx(&update->a_ctx, update->new_cont, 0);

68 69 70
	return KNOT_EOK;
}

71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
static int replace_soa(zone_contents_t *contents, const knot_rrset_t *rr)
{
	/* SOA possible only within apex. */
	if (!knot_dname_is_equal(rr->owner, contents->apex->owner)) {
		return KNOT_EDENIED;
	}

	knot_rrset_t old_soa = node_rrset(contents->apex, KNOT_RRTYPE_SOA);
	zone_node_t *n = contents->apex;
	int ret = zone_contents_remove_rr(contents, &old_soa, &n);
	if (ret != KNOT_EOK && ret != KNOT_EINVAL) {
		return ret;
	}

	return zone_contents_add_rr(contents, rr, &n);
}

88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
/* ------------------------------- API -------------------------------------- */

int zone_update_init(zone_update_t *update, zone_t *zone, zone_update_flags_t flags)
{
	if (update == NULL || zone == NULL) {
		return KNOT_EINVAL;
	}

	memset(update, 0, sizeof(*update));
	update->zone = zone;

	mm_ctx_mempool(&update->mm, MM_DEFAULT_BLKSIZE);
	update->flags = flags;

	if (flags & UPDATE_INCREMENTAL) {
		return init_incremental(update, zone);
	} else if (flags & UPDATE_FULL) {
		return init_full(update, zone);
	} else {
		return KNOT_EINVAL;
	}
}

const zone_node_t *zone_update_get_node(zone_update_t *update, const knot_dname_t *dname)
{
	if (update == NULL || dname == NULL) {
		return NULL;
	}

117
	return zone_contents_find_node(update->new_cont, dname);
118 119
}

120 121
const zone_node_t *zone_update_get_apex(zone_update_t *update)
{
122 123 124 125
	if (update == NULL) {
		return NULL;
	}

126 127 128 129 130 131
	return zone_update_get_node(update, update->zone->name);
}

uint32_t zone_update_current_serial(zone_update_t *update)
{
	const zone_node_t *apex = zone_update_get_apex(update);
132
	if (apex != NULL) {
133 134 135 136 137 138 139 140
		return knot_soa_serial(node_rdataset(apex, KNOT_RRTYPE_SOA));
	} else {
		return 0;
	}
}

const knot_rdataset_t *zone_update_from(zone_update_t *update)
{
141 142 143 144
	if (update == NULL) {
		return NULL;
	}

145 146 147 148 149 150
	if (update->flags & UPDATE_INCREMENTAL) {
		const zone_node_t *apex = update->zone->contents->apex;
		return node_rdataset(apex, KNOT_RRTYPE_SOA);
	}

	return NULL;
151 152 153 154
}

const knot_rdataset_t *zone_update_to(zone_update_t *update)
{
155 156 157
	if (update == NULL) {
		return NULL;
	}
158

159 160 161 162 163 164 165 166
	if (update->flags & UPDATE_FULL) {
		const zone_node_t *apex = update->new_cont->apex;
		return node_rdataset(apex, KNOT_RRTYPE_SOA);
	} else if (update->flags & UPDATE_INCREMENTAL) {
		if (update->change.soa_to == NULL) {
			return NULL;
		}
		return &update->change.soa_to->rrs;
167 168
	}

169
	return NULL;
170 171
}

172 173
void zone_update_clear(zone_update_t *update)
{
174 175
	if (update == NULL) {
		return;
176
	}
177 178 179 180

	if (update->flags & UPDATE_INCREMENTAL) {
		/* Revert any changes on error, do nothing on success. */
		update_rollback(&update->a_ctx);
181
		update_free_zone(&update->new_cont);
182 183 184 185 186 187
		changeset_clear(&update->change);
	} else if (update->flags & UPDATE_FULL) {
		zone_contents_deep_free(&update->new_cont);
	}
	mp_delete(update->mm.ctx);
	memset(update, 0, sizeof(*update));
188
}
189

190 191
int zone_update_add(zone_update_t *update, const knot_rrset_t *rrset)
{
192
	if (update == NULL || rrset == NULL) {
193 194 195
		return KNOT_EINVAL;
	}

196
	if (update->flags & UPDATE_INCREMENTAL) {
197 198 199 200 201 202 203
		int ret = changeset_add_addition(&update->change, rrset, CHANGESET_CHECK);
		if (ret != KNOT_EOK) {
			return ret;
		}

		if (rrset->type == KNOT_RRTYPE_SOA) {
			/* replace previous SOA */
204
			ret = apply_replace_soa(&update->a_ctx, &update->change);
205 206 207 208 209 210
			if (ret != KNOT_EOK) {
				changeset_remove_addition(&update->change, rrset);
			}
			return ret;
		}

211
		ret = apply_add_rr(&update->a_ctx, rrset);
212 213 214 215 216 217
		if (ret != KNOT_EOK) {
			changeset_remove_addition(&update->change, rrset);
			return ret;
		}

		return KNOT_EOK;
218
	} else if (update->flags & UPDATE_FULL) {
219 220 221 222 223
		if (rrset->type == KNOT_RRTYPE_SOA) {
			/* replace previous SOA */
			return replace_soa(update->new_cont, rrset);
		}

224 225 226 227 228 229 230 231 232
		zone_node_t *n = NULL;
		return zone_contents_add_rr(update->new_cont, rrset, &n);
	} else {
		return KNOT_EINVAL;
	}
}

int zone_update_remove(zone_update_t *update, const knot_rrset_t *rrset)
{
233
	if (update == NULL || rrset == NULL) {
234 235 236
		return KNOT_EINVAL;
	}

237
	if (update->flags & UPDATE_INCREMENTAL) {
238 239 240 241 242 243 244 245 246 247
		int ret = changeset_add_removal(&update->change, rrset, CHANGESET_CHECK);
		if (ret != KNOT_EOK) {
			return ret;
		}

		if (rrset->type == KNOT_RRTYPE_SOA) {
			/* SOA is replaced with addition */
			return KNOT_EOK;
		}

248
		ret = apply_remove_rr(&update->a_ctx, rrset);
249 250 251 252 253 254
		if (ret != KNOT_EOK) {
			changeset_remove_removal(&update->change, rrset);
			return ret;
		}

		return KNOT_EOK;
255 256
	} else if (update->flags & UPDATE_FULL) {
		zone_node_t *n = NULL;
257 258 259 260
		knot_rrset_t *rrs_copy = knot_rrset_copy(rrset, &update->mm);
		int ret = zone_contents_remove_rr(update->new_cont, rrs_copy, &n);
		knot_rrset_free(&rrs_copy, &update->mm);
		return ret;
261
	} else {
262
		return KNOT_EINVAL;
263 264 265
	}
}

266 267 268 269 270 271 272 273
int zone_update_remove_rrset(zone_update_t *update, knot_dname_t *owner, uint16_t type)
{
	if (update == NULL || owner == NULL) {
		return KNOT_EINVAL;
	}

	if (update->flags & UPDATE_INCREMENTAL) {
		/* Remove the RRSet from the original node */
274
		const zone_node_t *node = zone_contents_find_node(update->new_cont, owner);
275 276
		if (node != NULL) {
			knot_rrset_t rrset = node_rrset(node, type);
277 278 279
			if (rrset.owner == NULL) {
				return KNOT_ENOENT;
			}
Daniel Salzman's avatar
Daniel Salzman committed
280 281
			int ret = changeset_add_removal(&update->change, &rrset,
			                                CHANGESET_CHECK);
282 283 284 285
			if (ret != KNOT_EOK) {
				return ret;
			}

286 287 288 289 290
			if (type == KNOT_RRTYPE_SOA) {
				/* SOA is replaced with addition */
				return KNOT_EOK;
			}

291
			ret = apply_remove_rr(&update->a_ctx, &rrset);
292 293 294
			if (ret != KNOT_EOK) {
				return ret;
			}
295
		} else {
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321
			return KNOT_ENONODE;
		}
	} else if (update->flags & UPDATE_FULL) {
		/* Remove the RRSet from the non-synthesized new node */
		const zone_node_t *node = zone_contents_find_node(update->new_cont, owner);
		if (node == NULL) {
			return KNOT_ENONODE;
		}

		knot_rrset_t rrset = node_rrset(node, type);
		int ret = zone_update_remove(update, &rrset);
		if (ret != KNOT_EOK) {
			return ret;
		}
	}

	return KNOT_EOK;
}

int zone_update_remove_node(zone_update_t *update, const knot_dname_t *owner)
{
	if (update == NULL || owner == NULL) {
		return KNOT_EINVAL;
	}

	if (update->flags & UPDATE_INCREMENTAL) {
322 323
		/* Remove all RRSets from the new node */
		const zone_node_t *node = zone_contents_find_node(update->new_cont, owner);
324 325 326 327
		if (node != NULL) {
			size_t rrset_count = node->rrset_count;
			for (int i = 0; i < rrset_count; ++i) {
				knot_rrset_t rrset = node_rrset_at(node, rrset_count - 1 - i);
Daniel Salzman's avatar
Daniel Salzman committed
328 329
				int ret = changeset_add_removal(&update->change, &rrset,
				                                CHANGESET_CHECK);
330 331 332 333
				if (ret != KNOT_EOK) {
					return ret;
				}

334 335
				if (rrset.type == KNOT_RRTYPE_SOA) {
					/* SOA is replaced with addition */
336
					continue;
337 338
				}

339
				ret = apply_remove_rr(&update->a_ctx, &rrset);
340 341 342 343
				if (ret != KNOT_EOK) {
					return ret;
				}
			}
344
		} else {
345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366
			return KNOT_ENONODE;
		}
	} else if (update->flags & UPDATE_FULL) {
		/* Remove all RRSets from the non-synthesized new node */
		const zone_node_t *node = zone_contents_find_node(update->new_cont, owner);
		if (node == NULL) {
			return KNOT_ENONODE;
		}

		size_t rrset_count = node->rrset_count;
		for (int i = 0; i < rrset_count; ++i) {
			knot_rrset_t rrset = node_rrset_at(node, rrset_count - 1 - i);
			int ret = zone_update_remove(update, &rrset);
			if (ret != KNOT_EOK) {
				return ret;
			}
		}
	}

	return KNOT_EOK;
}

367 368 369 370
static bool apex_rr_changed(const zone_node_t *old_apex,
                            const zone_node_t *new_apex,
                            uint16_t type)
{
371 372
	assert(old_apex);
	assert(new_apex);
373 374 375 376 377 378
	knot_rrset_t old_rr = node_rrset(old_apex, type);
	knot_rrset_t new_rr = node_rrset(new_apex, type);

	return !knot_rrset_equal(&old_rr, &new_rr, KNOT_RRSET_COMPARE_WHOLE);
}

379
static bool apex_dnssec_changed(zone_update_t *update)
380 381
{
	const zone_node_t *new_apex = zone_update_get_apex(update);
382 383 384 385 386 387 388 389 390 391
	if (update->zone->contents != NULL) {
		const zone_node_t *old_apex = update->zone->contents->apex;
		return !changeset_empty(&update->change) &&
		       (apex_rr_changed(new_apex, old_apex, KNOT_RRTYPE_DNSKEY) ||
		        apex_rr_changed(new_apex, old_apex, KNOT_RRTYPE_NSEC3PARAM));
	} else if (new_apex != NULL) {
		return true;
	}

	return false;
392 393 394
}

static int sign_update(zone_update_t *update,
395
                       zone_contents_t *new_contents)
396
{
397 398
	assert(update);
	assert(new_contents);
399

400 401
	/* Check if the UPDATE changed DNSKEYs or NSEC3PARAM.
	 * If so, we have to sign the whole zone. */
402
	int ret = KNOT_EOK;
403
	zone_sign_reschedule_t resch = { 0 };
404 405 406 407 408 409
	changeset_t sec_ch;
	ret = changeset_init(&sec_ch, update->zone->name);
	if (ret != KNOT_EOK) {
		return ret;
	}

410
	const bool full_sign = changeset_empty(&update->change) ||
411
	                       apex_dnssec_changed(update);
412
	if (full_sign) {
413
		ret = knot_dnssec_zone_sign(new_contents, &sec_ch,
414
		                            ZONE_SIGN_KEEP_SOA_SERIAL,
415
					    &resch);
416
	} else {
417
		/* Sign the created changeset */
418
		ret = knot_dnssec_sign_changeset(new_contents, &update->change,
419
						 &sec_ch, &resch);
420 421
	}
	if (ret != KNOT_EOK) {
422
		changeset_clear(&sec_ch);
423 424 425
		return ret;
	}

426
	/* Apply DNSSEC changeset */
427
	ret = apply_changeset_directly(&update->a_ctx, &sec_ch);
428
	if (ret != KNOT_EOK) {
429
		changeset_clear(&sec_ch);
430 431 432
		return ret;
	}

433
	if (!full_sign) {
434
		/* Merge changesets */
435 436 437 438 439 440
		ret = changeset_merge(&update->change, &sec_ch);
		if (ret != KNOT_EOK) {
			update_rollback(&update->a_ctx);
			changeset_clear(&sec_ch);
			return ret;
		}
441 442
	}

443
	/* Plan next zone resign. */
444 445
	if (resch.next_sign > 0) {
		zone_events_schedule_at(update->zone, ZONE_EVENT_DNSSEC, resch.next_sign);
446 447
	}

448 449
	/* We are not calling update_cleanup, as the rollback data are merged
	 * into the main changeset and will get cleaned up with that. */
450 451
	changeset_clear(&sec_ch);

452 453 454
	return KNOT_EOK;
}

455
static int set_new_soa(zone_update_t *update, unsigned serial_policy)
456
{
457 458
	assert(update);

Daniel Salzman's avatar
Daniel Salzman committed
459 460
	knot_rrset_t *soa_cpy = node_create_rrset(zone_update_get_apex(update),
	                                          KNOT_RRTYPE_SOA);
461 462 463 464 465
	if (soa_cpy == NULL) {
		return KNOT_ENOMEM;
	}

	uint32_t old_serial = knot_soa_serial(&soa_cpy->rrs);
466
	uint32_t new_serial = serial_next(old_serial, serial_policy);
467 468 469 470 471 472 473 474 475 476 477 478
	if (serial_compare(old_serial, new_serial) >= 0) {
		log_zone_warning(update->zone->name, "updated serial is lower "
		                 "than current, serial %u -> %u",
		                  old_serial, new_serial);
	}

	knot_soa_serial_set(&soa_cpy->rrs, new_serial);
	update->change.soa_to = soa_cpy;

	return KNOT_EOK;
}

Daniel Salzman's avatar
Daniel Salzman committed
479 480
static int commit_incremental(conf_t *conf, zone_update_t *update,
                              zone_contents_t **contents_out)
481
{
482
	assert(update);
483
	assert(contents_out);
484

485 486 487 488 489
	if (changeset_empty(&update->change)) {
		changeset_clear(&update->change);
		return KNOT_EOK;
	}

490
	zone_contents_t *new_contents = update->new_cont;
491
	int ret = KNOT_EOK;
492
	if (zone_update_to(update) == NULL) {
493
		/* No SOA in the update, create one according to the current policy */
494 495
		conf_val_t val = conf_zone_get(conf, C_SERIAL_POLICY, update->zone->name);
		ret = set_new_soa(update, conf_opt(&val));
496
		if (ret != KNOT_EOK) {
497
			zone_update_clear(update);
498 499 500
			return ret;
		}

501
		ret = apply_replace_soa(&update->a_ctx, &update->change);
502
		if (ret != KNOT_EOK) {
503
			zone_update_clear(update);
504 505
			return ret;
		}
506
	}
507

508
	conf_val_t val = conf_zone_get(conf, C_DNSSEC_SIGNING, update->zone->name);
509
	bool dnssec_enable = (update->flags & UPDATE_SIGN) && conf_bool(&val);
510

511
	/* Sign the update. */
512
	if (dnssec_enable) {
513 514
		ret = apply_prepare_to_sign(&update->a_ctx);
		if (ret != KNOT_EOK) {
515
			zone_update_clear(update);
516 517 518
			return ret;
		}

519
		ret = sign_update(update, new_contents);
520
		if (ret != KNOT_EOK) {
521
			zone_update_clear(update);
522 523
			return ret;
		}
524
	} else {
525 526
		ret = apply_finalize(&update->a_ctx);
		if (ret != KNOT_EOK) {
527
			zone_update_clear(update);
528 529
			return ret;
		}
530
	}
531

532
	/* Write changes to journal if all went well. (DNSSEC merged) */
533
	ret = zone_change_store(conf, update->zone, &update->change);
534
	if (ret != KNOT_EOK) {
535
		/* Recoverable error. */
536 537
		return ret;
	}
538

539 540
	*contents_out = new_contents;

541 542 543
	return KNOT_EOK;
}

544 545
static int commit_full(conf_t *conf, zone_update_t *update, zone_contents_t **contents_out)
{
546 547 548 549 550 551 552 553 554
	assert(update);
	assert(contents_out);

	/* Check if we have SOA. We might consider adding full semantic check here.
	 * But if we wanted full sem-check I'd consider being it controlled by a flag
	 * - to enable/disable it on demand. */
	if (!node_rrtype_exists(update->new_cont->apex, KNOT_RRTYPE_SOA)) {
		return KNOT_ESEMCHECK;
	}
555

556 557 558 559 560
	int ret = zone_contents_adjust_full(update->new_cont);
	if (ret != KNOT_EOK) {
		zone_update_clear(update);
		return ret;
	}
561 562

	conf_val_t val = conf_zone_get(conf, C_DNSSEC_SIGNING, update->zone->name);
563
	bool dnssec_enable = (update->flags & UPDATE_SIGN) && conf_bool(&val);
564

565
	/* Sign the update. */
566
	if (dnssec_enable) {
567
		int ret = sign_update(update, update->new_cont);
568
		if (ret != KNOT_EOK) {
569
			zone_update_clear(update);
570 571 572 573
			return ret;
		}
	}

574 575 576 577 578 579
	/* Store new zone contents in journal. */
	ret = zone_in_journal_store(conf, update->zone, update->new_cont);
	if (ret != KNOT_EOK && ret != KNOT_EEXIST && ret != KNOT_ENOTSUP) {
		return ret;
	}

580
	*contents_out = update->new_cont;
581

582 583 584
	return KNOT_EOK;
}

585
int zone_update_commit(conf_t *conf, zone_update_t *update)
586
{
587
	if (conf == NULL || update == NULL) {
588 589 590 591 592
		return KNOT_EINVAL;
	}

	int ret = KNOT_EOK;
	zone_contents_t *new_contents = NULL;
593
	if (update->flags & UPDATE_INCREMENTAL) {
594 595
		ret = commit_incremental(conf, update, &new_contents);
	} else {
596
		ret = commit_full(conf, update, &new_contents);
597 598 599
	}
	if (ret != KNOT_EOK) {
		return ret;
600 601
	}

602 603 604 605 606 607
	/* If there is anything to change. */
	if (new_contents == NULL) {
		return KNOT_EOK;
	}

	/* Check the zone size. */
608 609 610
	conf_val_t val = conf_zone_get(conf, C_MAX_ZONE_SIZE, update->zone->name);
	int64_t size_limit = conf_int(&val);

611 612
	if (new_contents->size > size_limit) {
		/* Recoverable error. */
613 614 615
		return KNOT_EZONESIZE;
	}

616 617 618 619 620 621 622 623 624 625 626
	/* Switch zone contents. */
	zone_contents_t *old_contents = zone_switch_contents(update->zone,
	                                                     new_contents);

	/* Sync RCU. */
	synchronize_rcu();
	if (update->flags & UPDATE_FULL) {
		zone_contents_deep_free(&old_contents);
	} else if (update->flags & UPDATE_INCREMENTAL) {
		update_free_zone(&old_contents);
		changeset_clear(&update->change);
627
	}
628 629
	update_cleanup(&update->a_ctx);
	update->new_cont = NULL;
630 631

	return KNOT_EOK;
632 633
}

634 635 636 637
static int iter_init_tree_iters(zone_update_iter_t *it, zone_update_t *update,
                                bool nsec3)
{
	/* Set zone iterator. */
638
	zone_contents_t *_contents = update->new_cont;
639 640

	/* Begin iteration. We can safely assume _contents is a valid pointer. */
641
	zone_tree_t *tree = nsec3 ? _contents->nsec3_nodes : _contents->nodes;
642
	it->tree_it = trie_it_begin(tree);
643
	if (it->tree_it == NULL) {
644 645 646
		return KNOT_ENOMEM;
	}

647
	it->cur_node = (zone_node_t *)(*trie_it_val(it->tree_it));
648

649 650 651
	return KNOT_EOK;
}

652
static int iter_get_next_node(zone_update_iter_t *it)
653
{
654 655 656
	trie_it_next(it->tree_it);
	if (trie_it_finished(it->tree_it)) {
		trie_it_free(it->tree_it);
657 658
		it->tree_it = NULL;
		it->cur_node = NULL;
659 660 661
		return KNOT_ENOENT;
	}

662
	it->cur_node = (zone_node_t *)(*trie_it_val(it->tree_it));
663 664 665 666 667 668 669 670 671 672 673 674 675 676 677

	return KNOT_EOK;
}

static int iter_init(zone_update_iter_t *it, zone_update_t *update, const bool nsec3)
{
	memset(it, 0, sizeof(*it));

	it->update = update;
	it->nsec3 = nsec3;
	int ret = iter_init_tree_iters(it, update, nsec3);
	if (ret != KNOT_EOK) {
		return ret;
	}

678
	it->cur_node = (zone_node_t *)(*trie_it_val(it->tree_it));
679 680 681 682 683 684

	return KNOT_EOK;
}

int zone_update_iter(zone_update_iter_t *it, zone_update_t *update)
{
685 686 687 688
	if (it == NULL || update == NULL) {
		return KNOT_EINVAL;
	}

689 690 691 692 693
	return iter_init(it, update, false);
}

int zone_update_iter_nsec3(zone_update_iter_t *it, zone_update_t *update)
{
694 695 696 697
	if (it == NULL || update == NULL) {
		return KNOT_EINVAL;
	}

698 699
	if (update->flags & UPDATE_FULL) {
		if (update->new_cont->nsec3_nodes == NULL) {
700
			/* No NSEC3 tree. */
701 702 703 704 705
			return KNOT_ENOENT;
		}
	} else {
		if (update->change.add->nsec3_nodes == NULL &&
		    update->change.remove->nsec3_nodes == NULL) {
706
			/* No NSEC3 changes. */
707 708 709 710 711 712 713 714 715 716 717 718 719
			return KNOT_ENOENT;
		}
	}

	return iter_init(it, update, true);
}

int zone_update_iter_next(zone_update_iter_t *it)
{
	if (it == NULL) {
		return KNOT_EINVAL;
	}

720 721
	if (it->tree_it != NULL) {
		int ret = iter_get_next_node(it);
722 723 724 725 726 727 728 729 730 731
		if (ret != KNOT_EOK && ret != KNOT_ENOENT) {
			return ret;
		}
	}

	return KNOT_EOK;
}

const zone_node_t *zone_update_iter_val(zone_update_iter_t *it)
{
732
	if (it != NULL) {
733
		return it->cur_node;
734 735 736 737 738
	} else {
		return NULL;
	}
}

739
void zone_update_iter_finish(zone_update_iter_t *it)
740
{
741
	if (it == NULL) {
742
		return;
743 744
	}

745
	trie_it_free(it->tree_it);
746 747
}

748
bool zone_update_no_change(zone_update_t *update)
749
{
750 751 752 753
	if (update == NULL) {
		return true;
	}

754 755 756
	if (update->flags & UPDATE_INCREMENTAL) {
		return changeset_empty(&update->change);
	} else {
757 758 759
		/* This branch does not make much sense and FULL update will most likely
		 * be a change every time anyway, just return false. */
		return false;
760
	}
761
}