GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.sbin/npppd/npppd/../common/radish.c Lines: 0 310 0.0 %
Date: 2017-11-07 Branches: 0 192 0.0 %

Line Branch Exec Source
1
/*	$OpenBSD: radish.c,v 1.5 2017/05/30 17:52:05 yasuoka Exp $ */
2
/*
3
 * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
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
 * 3. Neither the name of the project nor the names of its contributors
15
 *    may be used to endorse or promote products derived from this software
16
 *    without specific prior written permission.
17
 *
18
 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
19
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
22
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28
 * SUCH DAMAGE.
29
 *
30
 */
31
32
/*
33
 * radish.c
34
 *
35
 * Version:	0.9
36
 * Created:	May     27, 1995
37
 * Modified:	January 28, 1997
38
 * Author:	Kazu YAMAMOTO
39
 * Email: 	kazu@is.aist-nara.ac.jp
40
 */
41
42
#include <sys/types.h>
43
#include <sys/socket.h>
44
#include <sys/socketvar.h>
45
#include <string.h>
46
#include <stdlib.h>
47
#include <errno.h>
48
49
#include "radish.h"
50
51
#include <netinet/in.h>
52
#include <string.h>
53
#include <strings.h>
54
#include <stdlib.h>
55
#include <stdio.h>
56
57
#define FATAL(x)			\
58
	do {				\
59
		fputs(x, stderr);	\
60
		abort();		\
61
	} while (0/* CONSTCOND */)
62
63
static u_char rd_bmask [] = {
64
	0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe,
65
};
66
67
static u_char rd_btest [] = {
68
	0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01,
69
};
70
71
u_char rd_deleted_km[1024];
72
73
/*
74
 * return 1 if success
75
 * return 0 if error
76
 */
77
int
78
rd_inithead(void **headp, int family, int slen, int off, int alen,
79
    int (*match)(void *, void *))
80
{
81
	struct radish_head *head;
82
	struct radish *new;
83
	struct sockaddr *masks;
84
	u_char *m;
85
	int num = alen * 8 + 1, i, j, q, r;
86
	int len = sizeof(*head) + sizeof(*new) + slen * num;
87
88
	if (*headp) return (1);
89
	R_Malloc(head, struct radish_head *, len);
90
	if (head == NULL)
91
		return 0;
92
	Bzero(head, len);
93
	new = (struct radish *)(head + 1);
94
	masks = (struct sockaddr *)(new +1);
95
	*headp = head;
96
97
	/*
98
	 * prepare all continuous masks
99
	 */
100
	m = (u_char *)masks;
101
	for (i = 0; i < num; i++, m += slen) {
102
		*m = slen;
103
		*(m + 1) = family;
104
		q = i >> 3;
105
		r = i & 7;
106
		for(j = 0; j < q; j++)
107
			*(m + off + j) = 0xff;
108
		*(m + off + j) = rd_bmask[r];
109
	}
110
111
	head->rdh_slen = slen;
112
	head->rdh_offset = off;
113
	head->rdh_alen = alen;
114
	head->rdh_masks = masks;
115
	head->rdh_match = match;
116
	head->rdh_top = new;
117
118
	new->rd_route = masks;
119
	new->rd_mask = masks;
120
	new->rd_btest = rd_btest[0];
121
	/* other nembers are 0 */
122
123
	return(1);
124
}
125
126
struct sockaddr *
127
rd_mask(struct sockaddr *m_arg, struct radish_head *head, int *maskp)
128
{
129
	u_char *mp, *masks = (u_char *)head->rdh_masks;
130
	int off = head->rdh_offset;
131
	int slen = head->rdh_slen;
132
	int alen = head->rdh_alen;
133
	int i = 0, masklen = 0;
134
135
	if (m_arg == NULL) {
136
		masklen = alen * 8;
137
		*maskp = masklen;
138
		return((struct sockaddr *)(masks + slen * masklen));
139
	}
140
	mp = (u_char *)m_arg + off;
141
	while ((i < alen) && (mp[i] == 0xff)) {
142
		masklen += 8;
143
		i++;
144
	}
145
	if (i < alen)
146
		switch (mp[i]) {
147
		case 0xfe: masklen += 7; break;
148
		case 0xfc: masklen += 6; break;
149
		case 0xf8: masklen += 5; break;
150
		case 0xf0: masklen += 4; break;
151
		case 0xe0: masklen += 3; break;
152
		case 0xc0: masklen += 2; break;
153
		case 0x80: masklen += 1; break;
154
		case 0x00: break;
155
		}
156
	*maskp = masklen;
157
	return((struct sockaddr *)(masks + slen * masklen));
158
}
159
160
int
161
rd_insert(struct sockaddr *d_arg, struct sockaddr *m_arg,
162
	struct radish_head *head, void *rt)
163
{
164
	struct radish *cur = head->rdh_top, *parent, *new;
165
	int off = head->rdh_offset;
166
	int slen = head->rdh_slen;
167
	int alen = head->rdh_alen;
168
	int i, lim, q, r, masklen;
169
	u_char *dp, *np, *rp;
170
	struct sockaddr *mask;
171
172
	mask = rd_mask(m_arg, head, &masklen);
173
	q = masklen >> 3;
174
	r = masklen & 7;
175
176
	/* Allocate a new radish.
177
	 * This may be overhead in the case that
178
	 * 	masklen == cur->rd_masklen
179
	 * and
180
	 *	route == dest.
181
	 */
182
	R_Malloc(new, struct radish *, sizeof(*new) + slen);
183
	if (new == NULL)
184
		return ENOBUFS;
185
	Bzero(new, sizeof(*new) + slen);
186
	new->rd_route = (struct sockaddr *)(new + 1);
187
	new->rd_mask = mask;
188
	new->rd_masklen = masklen;
189
	new->rd_masklim = q;
190
	new->rd_bmask = rd_bmask[r];
191
	new->rd_btest = rd_btest[r];
192
	new->rd_rtent = rt;
193
194
	/* masked copy from dest to route */
195
	np = (u_char *)new->rd_route;
196
	dp = (u_char *)d_arg;
197
	*np = *dp; /* sa_len */
198
	np[1] = dp[1]; /* sa_family */
199
	dp += off;
200
	np += off;
201
	i = 0;
202
	while (i < q) {
203
		np[i] = dp[i];
204
		i++;
205
	}
206
	np[i] = dp[i] & rd_bmask[r]; /* just in case */
207
208
	while (cur) {
209
		if (masklen == cur->rd_masklen) {
210
			rp = (u_char *)cur->rd_route + off;
211
			for (i = 0; i < alen; i++)
212
				if (np[i] != rp[i]) {
213
					/*
214
					 * masklen == cur->rd_masklen
215
					 * dest != route
216
					 */
217
					return rd_glue(cur, new, i, head);
218
				}
219
			/*
220
			 * masklen == cur->rd_masklen
221
			 * dest == route
222
			 */
223
			Free(new);
224
			if (cur->rd_rtent != NULL)
225
				return EEXIST;
226
			cur->rd_rtent = rt;
227
			return 0;
228
		}
229
		/*
230
		 * masklen != cur->rd_masklen
231
		 */
232
		if (masklen > cur->rd_masklen) {
233
			/*
234
			 * See if dest matches with cur node.
235
			 * (dest & mask) == route
236
			 */
237
			rp = (u_char *)cur->rd_route + off;
238
			lim = cur->rd_masklim;
239
240
			/* mask is continuous, thus mask is 0xff here. */
241
			for (i = 0; i < lim; i++)
242
				if(np[i] != rp[i]) {
243
					/*
244
					 * masklen > cur->rd_masklen
245
					 * (dest & mask) != route
246
					 */
247
					return rd_glue(cur, new, i, head);
248
				}
249
			if (cur->rd_bmask)
250
				if ((np[lim] & cur->rd_bmask) != rp[lim]) {
251
					/*
252
					 * masklen > cur->rd_masklen
253
					 * (dest & mask) != route
254
					 */
255
					return rd_glue(cur, new, lim, head);
256
				}
257
			/*
258
			 * masklen > cur->rd_masklen
259
			 * (dest & mask) == route
260
			 */
261
			if (cur->rd_btest & np[cur->rd_masklim]) {
262
				if (cur->rd_r != NULL) {
263
					cur = cur->rd_r;
264
					continue;
265
				}
266
				cur->rd_r = new;
267
				new->rd_p = cur;
268
				return 0;
269
			} else {
270
				if (cur->rd_l != NULL) {
271
					cur = cur->rd_l;
272
					continue;
273
				}
274
				cur->rd_l = new;
275
				new->rd_p = cur;
276
				return 0;
277
			}
278
		}
279
		/*
280
		 * masklen < cur->rd_masklen
281
		 */
282
283
		/* See if route matches with dest, be carefull!
284
		 * 	dest == (route & dest_mask)
285
		 */
286
		rp = (u_char *)cur->rd_route + off;
287
		lim = new->rd_masklim;
288
289
		/* mask is continuous, thus mask is 0xff here. */
290
		for (i = 0; i < lim; i++)
291
			if(np[i] != rp[i]) {
292
				/*
293
				 * masklen < cur->rd_masklen
294
				 * dest != (route & dest_mask)
295
				 */
296
				return rd_glue(cur, new, i, head);
297
			}
298
		if (new->rd_bmask)
299
			if (np[lim] != (rp[lim] & new->rd_bmask)) {
300
				/*
301
				 * masklen < cur->rd_masklen
302
				 * dest != (route & dest_mask)
303
				 */
304
				return rd_glue(cur, new, lim, head);
305
			}
306
		/*
307
		 * masklen < cur->rd_masklen
308
		 * dest == (route & dest_mask)
309
		 */
310
311
		/* put the new radish between cur and its parent */
312
		parent = cur->rd_p;
313
		new->rd_p = parent;
314
		if (parent->rd_l == cur)
315
			parent->rd_l = new;
316
		else if (parent->rd_r == cur)
317
			parent->rd_r = new;
318
		else
319
			FATAL("rd_insert");
320
		if (new->rd_btest & rp[new->rd_masklim])
321
			new->rd_r = cur;
322
		else
323
			new->rd_l = cur;
324
325
		cur->rd_p = new;
326
		return 0;
327
	}
328
	return 1;
329
}
330
331
/*
332
 * Insert a glue radish between the current and its parent.
333
 * Let the current radish one child of glue radish.
334
 * Let the new radish the other child of glue radish.
335
 */
336
int
337
rd_glue(struct radish *cur, struct radish *new, int misbyte,
338
    struct radish_head *head)
339
{
340
	struct radish *parent = cur->rd_p, *glue;
341
	u_char *cp = (u_char *)cur->rd_route;
342
	u_char *np = (u_char *)new->rd_route;
343
	u_char *gp;
344
	int off = head->rdh_offset, slen = head->rdh_slen;
345
	int maskb, xor, i;
346
347
	/*
348
	 * Glue radish
349
	 */
350
	R_Malloc(glue, struct radish *, sizeof(*glue) + slen);
351
	if (glue == NULL) {
352
		Free (new);
353
		return ENOBUFS;
354
	}
355
	Bzero(glue, sizeof(*glue) + slen);
356
357
	/* calculate a bit to test */
358
	xor = (*(cp + off + misbyte) ^ *(np + off + misbyte)) & 0xff;
359
	maskb = 8;
360
	while(xor) {
361
		xor >>= 1;
362
		maskb--;
363
	}
364
365
	glue->rd_route = (struct sockaddr *)(glue + 1);
366
	glue->rd_masklen = 8 * misbyte + maskb;
367
	glue->rd_masklim = misbyte;
368
	glue->rd_bmask = rd_bmask[maskb];
369
	glue->rd_btest = rd_btest[maskb];
370
	glue->rd_rtent = NULL;
371
	glue->rd_p = parent;
372
	glue->rd_mask = (struct sockaddr *)
373
		((u_char *)head->rdh_masks + slen * glue->rd_masklen);
374
375
	/* masked copy of route */
376
	gp = (u_char *)glue->rd_route;
377
	*gp = *cp; /* sa_len */
378
	*(gp + 1) = *(cp + 1); /* sa_family */
379
	cp += off;
380
	gp += off;
381
	for(i = 0; i < misbyte; i++)
382
		gp[i] = cp[i];
383
	gp[misbyte] = cp[misbyte] & glue->rd_bmask;
384
385
	if (glue->rd_btest & cp[misbyte]) {
386
		glue->rd_r = cur;
387
		glue->rd_l = new;
388
	} else {
389
		glue->rd_r = new;
390
		glue->rd_l = cur;
391
	}
392
393
	/*
394
	 * Children
395
	 */
396
	new->rd_p = cur->rd_p = glue;
397
398
	/*
399
	 * Parent
400
	 */
401
	if (parent->rd_l == cur)
402
		parent->rd_l = glue;
403
	else if (parent->rd_r == cur)
404
		parent->rd_r = glue;
405
	else
406
		FATAL("rd_insert");
407
	return 0;
408
}
409
410
/*
411
 * Find the longest-match radish with the destination.
412
 * Return 1 if success, otherwise return 0.
413
 */
414
415
int
416
rd_match(struct sockaddr *d_arg, struct radish_head *head, struct radish **rdp)
417
{
418
	return rd_match_next(d_arg, head, rdp, NULL);
419
}
420
421
int
422
rd_match_next(struct sockaddr *d_arg, struct radish_head *head,
423
    struct radish **rdp, struct radish *cur)
424
{
425
	struct radish *target = NULL;
426
	int off = head->rdh_offset, i, lim;
427
	u_char *dp = (u_char *)d_arg + off, *cp;
428
429
	if (cur == NULL) {
430
		cur = head->rdh_top;
431
		while (cur) {
432
			target = cur;
433
			if (cur->rd_btest & *(dp + cur->rd_masklim))
434
				cur = cur->rd_r;
435
			else
436
				cur = cur->rd_l;
437
		}
438
	} else {
439
		target = cur->rd_p;
440
		if (target == NULL) {
441
			*rdp = NULL;
442
			return 0;
443
		}
444
	}
445
446
	/* We are now on the leaf radish. Backtrace to find the radish
447
	   which contains route to match. */
448
	do {
449
		/* If this radish doesn't have route,
450
		   we skip it and chase the next parent. */
451
		if (target->rd_rtent != NULL) {
452
			cp = (u_char *)target->rd_route + off;
453
			lim = target->rd_masklim;
454
455
			/* Check the edge for slight speed up */
456
			if (target->rd_bmask)
457
				if ((*(dp + lim) & target->rd_bmask)
458
				    != *(cp + lim)){
459
				nextparent:
460
					continue;
461
				}
462
463
			/* mask is always 0xff */
464
			for (i = 0; i < lim; i++)
465
				if(dp[i] != cp[i])
466
					/* to break the for loop */
467
					goto nextparent;
468
			/* Matched to this radish! */
469
			*rdp = target;
470
			return 1;
471
		}
472
	} while ((target = target->rd_p));
473
	*rdp = NULL;
474
	return 0;
475
}
476
477
/*
478
 * Lookup the same radish according to a pair of destination and mask.
479
 * Return a pointer to rtentry if exists. Otherwise, return NULL.
480
 */
481
482
void *
483
rd_lookup(struct sockaddr *d_arg, struct sockaddr *m_arg,
484
    struct radish_head *head)
485
{
486
	struct radish *cur = head->rdh_top;
487
	int off = head->rdh_offset, i, lim, olim = 0, masklen;
488
	u_char *dp = (u_char *)d_arg + off, *cp;
489
490
	rd_mask(m_arg, head, &masklen);
491
492
	/* Skipping forward search */
493
	while (cur) {
494
		/* Skip a radish if it doesn't have a route */
495
		if (cur->rd_rtent != NULL) {
496
			cp = (u_char *)(cur->rd_route) + off;
497
			lim = cur->rd_masklim;
498
			/* check the edge to speed up a bit */
499
			if (cur->rd_bmask)
500
				if ((*(dp + lim) & cur->rd_bmask)
501
				    != *(cp + lim))
502
					return NULL;
503
			/* mask is always 0xff */
504
			for (i = olim; i < lim; i++)
505
				if(dp[i] != cp[i])
506
					return NULL;
507
			if (masklen == cur->rd_masklen)
508
				return cur->rd_rtent;
509
			olim = lim;
510
		}
511
		if (cur->rd_btest & *(dp + cur->rd_masklim))
512
			cur = cur->rd_r;
513
		else
514
			cur = cur->rd_l;
515
	}
516
	return NULL;
517
}
518
519
/*
520
 * Delete the radish for dest and mask.
521
 * Return 0 if success.
522
 * Return ENOENT if no such radish exists.
523
 * Return EFAULT if try to delete intermediate radish which doesn't have route.
524
 */
525
526
int
527
rd_delete(struct sockaddr *d_arg, struct sockaddr *m_arg,
528
    struct radish_head *head, void **item)
529
{
530
	struct radish *cur = head->rdh_top;
531
	int off = head->rdh_offset, i, lim, masklen;
532
	u_char *dp = (u_char *)d_arg + off, *cp;
533
534
	rd_mask(m_arg, head, &masklen);
535
	*item = NULL; /* just in case */
536
537
	while (cur) {
538
		/* exit loop if dest does not match with the current node
539
		 * 	(dest & mask) != route
540
		 */
541
		cp = (u_char *)cur->rd_route + off;
542
		/* check the edge to speed up */
543
		if (cur->rd_bmask)
544
			if ((*(dp + cur->rd_masklim) & cur->rd_bmask)
545
			    != *(cp + cur->rd_masklim))
546
				return ENOENT;
547
		/* mask is always 0xff */
548
		lim = cur->rd_masklim;
549
		for (i = 0; i < lim; i++)
550
			if(dp[i] != cp[i])
551
				return ENOENT;
552
553
		/* See if cur is exactly what we delete */
554
555
		/* Check mask to speed up */
556
		if (cur->rd_masklen != masklen)
557
			goto next;
558
559
		cp = (u_char *)cur->rd_route + off;
560
		lim = head->rdh_alen;
561
		for (i = 0; i < lim; i++)
562
			if (dp[i] != cp[i])
563
				goto next;
564
		/*
565
		 * Both route and mask are the same.
566
		 */
567
		if (cur->rd_rtent == NULL) {
568
			/* Leaf always has route, so this radish
569
			 * must be intermediate.
570
			 * Can't delete intermediate radish which
571
			 * doesn't have route.
572
			 */
573
			return EFAULT;
574
		}
575
		*item = cur->rd_rtent;
576
		{
577
			/* used to report the deleted entry back */
578
			u_char rl = cur->rd_route->sa_len;
579
			u_char ml = cur->rd_mask->sa_len;
580
581
			bcopy(cur->rd_route, rd_deleted_km, rl);
582
			bcopy(cur->rd_mask, rd_deleted_km + rl, ml);
583
		}
584
		if (cur->rd_l && cur->rd_r) {
585
			/* This radish has two children */
586
			cur->rd_rtent = NULL;
587
			return 0;
588
		}
589
		/* Unlink the radish that has 0 or 1 child
590
		 * and surely has a route.
591
		 */
592
		rd_unlink(cur, head->rdh_top);
593
		return 0;
594
595
	next:
596
		/* seach corresponding subtree */
597
		if (cur->rd_btest & *(dp + cur->rd_masklim)) {
598
			if (cur->rd_r) {
599
				cur = cur->rd_r;
600
				continue;
601
			} else
602
				break;
603
		} else {
604
			if (cur->rd_l) {
605
				cur = cur->rd_l;
606
				continue;
607
			} else
608
				break;
609
		}
610
	}
611
	return ENOENT;
612
}
613
614
/*
615
 * Free radish and refine radish tree.
616
 * rd_unlink is called with radish which have 0 or 1 child and route.
617
 * Error causes panic, so return only when success.
618
 */
619
620
void
621
rd_unlink(struct radish *cur, struct radish *top)
622
{
623
	struct radish *parent, *child;
624
625
	if (cur == top) {
626
		cur->rd_rtent = NULL;
627
		return;
628
	}
629
630
	if ((cur->rd_l == 0) && (cur->rd_r == 0)) {
631
		/* No child, just delete it. */
632
		parent = cur->rd_p;
633
		if (parent->rd_l == cur) {
634
			parent->rd_l = NULL;
635
			Free(cur);
636
		} else if (parent->rd_r == cur) {
637
			parent->rd_r = NULL;
638
			Free(cur);
639
		} else
640
			FATAL("rd_unlink");
641
		if (parent->rd_rtent == NULL && parent != top)
642
			/* Parent is not necessary, refine radish. */
643
			rd_unlink(parent, top);
644
	} else {
645
		/*
646
		 * There is one child, never two.
647
		 * Make its child its parent's child.
648
		 */
649
		if (cur->rd_l)
650
			child = cur->rd_l;
651
		else
652
			child = cur->rd_r;
653
		parent = cur->rd_p;
654
		child->rd_p = parent;
655
		if (parent->rd_l == cur) {
656
			parent->rd_l = child;
657
			Free(cur);
658
		} else if (parent->rd_r == cur) {
659
			parent->rd_r = child;
660
			Free(cur);
661
		} else
662
			FATAL("rd_unlink");
663
	}
664
}
665
666
int
667
rd_walktree(struct radish_head *h, register int (*f)(struct radish *, void *),
668
    void *w)
669
{
670
	int error = 0;
671
	struct radish *par = NULL, *cur, *top = h->rdh_top;
672
673
	cur = top;
674
	for (;;) {
675
		while (cur) {
676
			if (cur->rd_rtent != NULL)
677
				if ((error = (*f)(cur, w)))
678
					return error;
679
			par = cur;
680
			cur = cur->rd_l;
681
		}
682
		cur = par;
683
		while (cur->rd_r == NULL || par == cur->rd_r) {
684
			par = cur;
685
			cur = cur->rd_p;
686
			if (cur == NULL) return 0;
687
		}
688
		par = cur;
689
		cur = cur->rd_r;
690
	}
691
}
692
693
/* This function can be mush easier in the context of radish.
694
 * For instance, using rd_mask. But we stay the original because
695
 * it works.
696
 */
697
int
698
rd_refines(void *m_arg, void *n_arg)
699
{
700
	register caddr_t m = m_arg, n = n_arg;
701
	register caddr_t lim, lim2 = lim = n + *(u_char *)n;
702
	int longer = (*(u_char *)n++) - (int)(*(u_char *)m++);
703
	int masks_are_equal = 1;
704
705
	if (longer > 0)
706
		lim -= longer;
707
	while (n < lim) {
708
		if (*n & ~(*m))
709
			return 0;
710
		if (*n++ != *m++)
711
			masks_are_equal = 0;
712
713
	}
714
	while (n < lim2)
715
		if (*n++)
716
			return 0;
717
	if (masks_are_equal && (longer < 0))
718
		for (lim2 = m - longer; m < lim2; )
719
			if (*m++)
720
				return 1;
721
	return (!masks_are_equal);
722
}