1/*-
2 * Copyright (c) 2004 Sam Leffler, Errno Consulting
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer,
10 *    without modification.
11 * 2. Redistributions in binary form must reproduce at minimum a disclaimer
12 *    similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
13 *    redistribution must be conditioned upon including a substantially
14 *    similar Disclaimer requirement for further binary redistribution.
15 * 3. Neither the names of the above-listed copyright holders nor the names
16 *    of any contributors may be used to endorse or promote products derived
17 *    from this software without specific prior written permission.
18 *
19 * NO WARRANTY
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
23 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
24 * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,
25 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
28 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
30 * THE POSSIBILITY OF SUCH DAMAGES.
31 *
32 * $FreeBSD$
33 */
34
35/*
36 * Simple tool for testing hardware/system crypto support.
37 *
38 * cryptotest [-czsbv] [-a algorithm] [count] [size ...]
39 *
40 * Run count iterations of a crypt+decrypt or mac operation on a buffer of
41 * size bytes.  A random key and iv are used.  Options:
42 *	-c	check the results
43 *	-d dev	pin work on device dev
44 *	-z	run all available algorithms on a variety of buffer sizes
45 *	-v	be verbose
46 *	-b	mark operations for batching
47 *	-p	profile kernel crypto operations (must be root)
48 *	-t n	fork n threads and run tests concurrently
49 * Known algorithms are:
50 *	null	null cbc
51 *	des	des cbc
52 *	3des	3des cbc
53 *	blf	blowfish cbc
54 *	cast	cast cbc
55 *	skj	skipjack cbc
56 *	aes	rijndael/aes 128-bit cbc
57 *	aes192	rijndael/aes 192-bit cbc
58 *	aes256	rijndael/aes 256-bit cbc
59 *	chacha20 Chacha20 stream cipher
60 *	blake2b	Blake2b
61 *	blake2s	Blake2s
62 *	md5	md5 hmac
63 *	sha1	sha1 hmac
64 *	sha256	256-bit sha2 hmac
65 *	sha384	384-bit sha2 hmac
66 *	sha512	512--bit sha2 hmac
67 *
68 * For a test of how fast a crypto card is, use something like:
69 *	cryptotest -z 1024
70 * This will run a series of tests using the available crypto/cipher
71 * algorithms over a variety of buffer sizes.  The 1024 says to do 1024
72 * iterations.  Extra arguments can be used to specify one or more buffer
73 * sizes to use in doing tests.
74 *
75 * To fork multiple processes all doing the same work, specify -t X on the
76 * command line to get X "threads" running simultaneously.  No effort is made
77 * to synchronize the threads or otherwise maximize load.
78 *
79 * If the kernel crypto code is built with CRYPTO_TIMING and you run as root,
80 * then you can specify the -p option to get a "profile" of the time spent
81 * processing crypto operations.  At present this data is only meaningful for
82 * symmetric operations.  To get meaningful numbers you must run on an idle
83 * machine.
84 *
85 * Expect ~400 Mb/s for a Broadcom 582x for 8K buffers on a reasonable CPU
86 * (64-bit PCI helps).  Hifn 7811 parts top out at ~110 Mb/s.
87 */
88
89#include <sys/param.h>
90#include <sys/cpuset.h>
91#include <sys/ioctl.h>
92#include <sys/mman.h>
93#include <sys/sysctl.h>
94#include <sys/time.h>
95#include <sys/wait.h>
96
97#include <err.h>
98#include <fcntl.h>
99#include <paths.h>
100#include <stdio.h>
101#include <stdlib.h>
102#include <string.h>
103#include <sysexits.h>
104#include <unistd.h>
105
106#include <crypto/cryptodev.h>
107
108#define	CHUNK	64	/* how much to display */
109#define	streq(a,b)	(strcasecmp(a,b) == 0)
110
111void	hexdump(char *, int);
112
113int	verbose = 0;
114int	opflags = 0;
115int	verify = 0;
116int	crid = CRYPTO_FLAG_HARDWARE;
117
118struct alg {
119	const char* name;
120	int	ishash;
121	int	blocksize;
122	int	minkeylen;
123	int	maxkeylen;
124	int	code;
125} algorithms[] = {
126#ifdef CRYPTO_NULL_CBC
127	{ "null",	0,	8,	1,	256,	CRYPTO_NULL_CBC },
128#endif
129	{ "des",	0,	8,	8,	8,	CRYPTO_DES_CBC },
130	{ "3des",	0,	8,	24,	24,	CRYPTO_3DES_CBC },
131	{ "blf",	0,	8,	5,	56,	CRYPTO_BLF_CBC },
132	{ "cast",	0,	8,	5,	16,	CRYPTO_CAST_CBC },
133	{ "skj",	0,	8,	10,	10,	CRYPTO_SKIPJACK_CBC },
134	{ "rij",	0,	16,	16,	16,	CRYPTO_RIJNDAEL128_CBC},
135	{ "aes",	0,	16,	16,	16,	CRYPTO_AES_CBC},
136	{ "aes192",	0,	16,	24,	24,	CRYPTO_AES_CBC},
137	{ "aes256",	0,	16,	32,	32,	CRYPTO_AES_CBC},
138	{ "chacha20",	0,	1,	32,	32,	CRYPTO_CHACHA20},
139	{ "blake2b",	1,	128,	64,	64,	CRYPTO_BLAKE2B },
140	{ "blake2s",	1,	64,	32,	32,	CRYPTO_BLAKE2S },
141	{ "md5",	1,	8,	16,	16,	CRYPTO_MD5_HMAC },
142	{ "sha1",	1,	8,	20,	20,	CRYPTO_SHA1_HMAC },
143	{ "sha256",	1,	8,	32,	32,	CRYPTO_SHA2_256_HMAC },
144	{ "sha384",	1,	8,	48,	48,	CRYPTO_SHA2_384_HMAC },
145	{ "sha512",	1,	8,	64,	64,	CRYPTO_SHA2_512_HMAC },
146};
147
148void
149usage(const char* cmd)
150{
151	printf("usage: %s [-czsbv] [-d dev] [-a algorithm] [count] [size ...]\n",
152		cmd);
153	printf("where algorithm is one of:\n");
154	printf("    null des 3des (default) blowfish cast skipjack rij\n");
155	printf("    aes aes192 aes256 chacha20 md5 sha1 sha256 sha384 sha512\n");
156	printf("    blake2b blake2s\n");
157	printf(" or an encryption algorithm concatented with authentication\n");
158	printf(" algorithm with '+' in the middle, e.g., aes+sha1.\n");
159	printf("count is the number of encrypt/decrypt ops to do\n");
160	printf("size is the number of bytes of text to encrypt+decrypt\n");
161	printf("\n");
162	printf("-c check the results (slows timing)\n");
163	printf("-d use specific device, specify 'soft' for testing software implementations\n");
164	printf("\tNOTE: to use software you must set:\n\t sysctl kern.cryptodevallowsoft=1\n");
165	printf("-z run all available algorithms on a variety of sizes\n");
166	printf("-v be verbose\n");
167	printf("-b mark operations for batching\n");
168	printf("-p profile kernel crypto operation (must be root)\n");
169	printf("-t n for n threads and run tests concurrently\n");
170	exit(-1);
171}
172
173struct alg*
174getalgbycode(int cipher)
175{
176	int i;
177
178	for (i = 0; i < nitems(algorithms); i++)
179		if (cipher == algorithms[i].code)
180			return &algorithms[i];
181	return NULL;
182}
183
184struct alg*
185getalgbyname(const char* name)
186{
187	int i;
188
189	for (i = 0; i < nitems(algorithms); i++)
190		if (streq(name, algorithms[i].name))
191			return &algorithms[i];
192	return NULL;
193}
194
195int
196devcrypto(void)
197{
198	int fd = -1;
199
200	if (fd < 0) {
201		fd = open(_PATH_DEV "crypto", O_RDWR, 0);
202		if (fd < 0)
203			err(1, _PATH_DEV "crypto");
204		if (fcntl(fd, F_SETFD, 1) == -1)
205			err(1, "fcntl(F_SETFD) (devcrypto)");
206	}
207	return fd;
208}
209
210int
211crlookup(const char *devname)
212{
213	struct crypt_find_op find;
214
215	if (strncmp(devname, "soft", 4) == 0)
216		return CRYPTO_FLAG_SOFTWARE;
217
218	find.crid = -1;
219	strlcpy(find.name, devname, sizeof(find.name));
220	if (ioctl(devcrypto(), CIOCFINDDEV, &find) == -1)
221		err(1, "ioctl(CIOCFINDDEV)");
222	return find.crid;
223}
224
225const char *
226crfind(int crid)
227{
228	static struct crypt_find_op find;
229
230	bzero(&find, sizeof(find));
231	find.crid = crid;
232	if (ioctl(devcrypto(), CRIOFINDDEV, &find) == -1)
233		err(1, "ioctl(CIOCFINDDEV): crid %d", crid);
234	return find.name;
235}
236
237int
238crget(void)
239{
240	int fd;
241
242	if (ioctl(devcrypto(), CRIOGET, &fd) == -1)
243		err(1, "ioctl(CRIOGET)");
244	if (fcntl(fd, F_SETFD, 1) == -1)
245		err(1, "fcntl(F_SETFD) (crget)");
246	return fd;
247}
248
249char
250rdigit(void)
251{
252	const char a[] = {
253		0x10,0x54,0x11,0x48,0x45,0x12,0x4f,0x13,0x49,0x53,0x14,0x41,
254		0x15,0x16,0x4e,0x55,0x54,0x17,0x18,0x4a,0x4f,0x42,0x19,0x01
255	};
256	return 0x20+a[random()%nitems(a)];
257}
258
259void
260runtest(struct alg *ealg, struct alg *alg, int count, int size, u_long cmd, struct timeval *tv)
261{
262	int i, fd = crget();
263	struct timeval start, stop, dt;
264	char *cleartext, *ciphertext, *originaltext, *key;
265	struct session2_op sop;
266	struct crypt_op cop;
267	char iv[EALG_MAX_BLOCK_LEN];
268	char digest[512/8];
269
270	/* Canonicalize 'ealg' to crypt alg and 'alg' to authentication alg. */
271	if (ealg == NULL && !alg->ishash) {
272		ealg = alg;
273		alg = NULL;
274	}
275
276	bzero(&sop, sizeof(sop));
277	if (ealg != NULL) {
278		sop.keylen = (ealg->minkeylen + ealg->maxkeylen)/2;
279		key = (char *) malloc(sop.keylen);
280		if (key == NULL)
281			err(1, "malloc (key)");
282		for (i = 0; i < sop.keylen; i++)
283			key[i] = rdigit();
284		sop.key = key;
285		sop.cipher = ealg->code;
286	}
287	if (alg != NULL) {
288		sop.mackeylen = (alg->minkeylen + alg->maxkeylen)/2;
289		key = (char *) malloc(sop.mackeylen);
290		if (key == NULL)
291			err(1, "malloc (mac)");
292		for (i = 0; i < sop.mackeylen; i++)
293			key[i] = rdigit();
294		sop.mackey = key;
295		sop.mac = alg->code;
296	}
297
298	sop.crid = crid;
299	if (ioctl(fd, cmd, &sop) < 0) {
300		if (cmd == CIOCGSESSION || cmd == CIOCGSESSION2) {
301			close(fd);
302			if (verbose) {
303				printf("cipher %s%s%s", ealg? ealg->name : "",
304				    (ealg && alg) ? "+" : "",
305				    alg? alg->name : "");
306
307				if (alg->ishash)
308					printf(" mackeylen %u\n", sop.mackeylen);
309				else
310					printf(" keylen %u\n", sop.keylen);
311				perror("CIOCGSESSION");
312			}
313			/* hardware doesn't support algorithm; skip it */
314			return;
315		}
316		printf("cipher %s%s%s keylen %u mackeylen %u\n",
317		    ealg? ealg->name : "", (ealg && alg) ? "+" : "",
318		    alg? alg->name : "", sop.keylen, sop.mackeylen);
319		err(1, "CIOCGSESSION");
320	}
321
322	originaltext = malloc(3*size);
323	if (originaltext == NULL)
324		err(1, "malloc (text)");
325	cleartext = originaltext+size;
326	ciphertext = cleartext+size;
327	for (i = 0; i < size; i++)
328		cleartext[i] = rdigit();
329	memcpy(originaltext, cleartext, size);
330	for (i = 0; i < nitems(iv); i++)
331		iv[i] = rdigit();
332
333	if (verbose) {
334		printf("session = 0x%x\n", sop.ses);
335		printf("device = %s\n", crfind(sop.crid));
336		printf("count = %d, size = %d\n", count, size);
337		if (ealg) {
338			printf("iv:");
339			hexdump(iv, sizeof iv);
340		}
341		printf("cleartext:");
342		hexdump(cleartext, MIN(size, CHUNK));
343	}
344
345	gettimeofday(&start, NULL);
346	if (ealg) {
347		for (i = 0; i < count; i++) {
348			cop.ses = sop.ses;
349			cop.op = COP_ENCRYPT;
350			cop.flags = opflags | COP_F_CIPHER_FIRST;
351			cop.len = size;
352			cop.src = cleartext;
353			cop.dst = ciphertext;
354			if (alg)
355				cop.mac = digest;
356			else
357				cop.mac = 0;
358			cop.iv = iv;
359
360			if (ioctl(fd, CIOCCRYPT, &cop) < 0)
361				err(1, "ioctl(CIOCCRYPT)");
362
363			if (verify && bcmp(ciphertext, cleartext, size) == 0) {
364				printf("cipher text unchanged:");
365				hexdump(ciphertext, size);
366			}
367
368			memset(cleartext, 'x', MIN(size, CHUNK));
369			cop.ses = sop.ses;
370			cop.op = COP_DECRYPT;
371			cop.flags = opflags;
372			cop.len = size;
373			cop.src = ciphertext;
374			cop.dst = cleartext;
375			if (alg)
376				cop.mac = digest;
377			else
378				cop.mac = 0;
379			cop.iv = iv;
380
381			if (ioctl(fd, CIOCCRYPT, &cop) < 0)
382				err(1, "ioctl(CIOCCRYPT)");
383
384			if (verify && bcmp(cleartext, originaltext, size) != 0) {
385				printf("decrypt mismatch:\n");
386				printf("original:");
387				hexdump(originaltext, size);
388				printf("cleartext:");
389				hexdump(cleartext, size);
390			}
391		}
392	} else {
393		for (i = 0; i < count; i++) {
394			cop.ses = sop.ses;
395			cop.op = 0;
396			cop.flags = opflags;
397			cop.len = size;
398			cop.src = cleartext;
399			cop.dst = 0;
400			cop.mac = ciphertext;
401			cop.iv = 0;
402
403			if (ioctl(fd, CIOCCRYPT, &cop) < 0)
404				err(1, "ioctl(CIOCCRYPT)");
405		}
406	}
407	gettimeofday(&stop, NULL);
408
409	if (ioctl(fd, CIOCFSESSION, &sop.ses) < 0)
410		perror("ioctl(CIOCFSESSION)");
411
412	if (verbose) {
413		printf("cleartext:");
414		hexdump(cleartext, MIN(size, CHUNK));
415	}
416	timersub(&stop, &start, tv);
417
418	free(originaltext);
419
420	close(fd);
421}
422
423#ifdef __FreeBSD__
424void
425resetstats()
426{
427	struct cryptostats stats;
428	size_t slen;
429
430	slen = sizeof (stats);
431	if (sysctlbyname("kern.crypto_stats", &stats, &slen, NULL, 0) < 0) {
432		perror("kern.crypto_stats");
433		return;
434	}
435	bzero(&stats.cs_invoke, sizeof (stats.cs_invoke));
436	bzero(&stats.cs_done, sizeof (stats.cs_done));
437	bzero(&stats.cs_cb, sizeof (stats.cs_cb));
438	bzero(&stats.cs_finis, sizeof (stats.cs_finis));
439	stats.cs_invoke.min.tv_sec = 10000;
440	stats.cs_done.min.tv_sec = 10000;
441	stats.cs_cb.min.tv_sec = 10000;
442	stats.cs_finis.min.tv_sec = 10000;
443	if (sysctlbyname("kern.crypto_stats", NULL, NULL, &stats, sizeof (stats)) < 0)
444		perror("kern.cryptostats");
445}
446
447void
448printt(const char* tag, struct cryptotstat *ts)
449{
450	uint64_t avg, min, max;
451
452	if (ts->count == 0)
453		return;
454	avg = (1000000000LL*ts->acc.tv_sec + ts->acc.tv_nsec) / ts->count;
455	min = 1000000000LL*ts->min.tv_sec + ts->min.tv_nsec;
456	max = 1000000000LL*ts->max.tv_sec + ts->max.tv_nsec;
457	printf("%16.16s: avg %6llu ns : min %6llu ns : max %7llu ns [%u samps]\n",
458		tag, avg, min, max, ts->count);
459}
460#endif
461
462void
463runtests(struct alg *ealg, struct alg *alg, int count, int size, u_long cmd, int threads, int profile)
464{
465	int i, status;
466	double t;
467	void *region;
468	struct timeval *tvp;
469	struct timeval total;
470	int otiming;
471
472	if (size % alg->blocksize || (ealg && size % ealg->blocksize)) {
473		if (verbose)
474			printf("skipping blocksize %u 'cuz not a multiple of "
475				"%s blocksize %u (or %s blocksize %u)\n",
476				size, alg->name, alg->blocksize,
477				ealg ?  ealg->name : "n/a",
478				ealg ? ealg->blocksize : 0);
479		return;
480	}
481
482	region = mmap(NULL, threads * sizeof (struct timeval),
483			PROT_READ|PROT_WRITE, MAP_ANON|MAP_SHARED, -1, 0);
484	if (region == MAP_FAILED) {
485		perror("mmap");
486		return;
487	}
488	tvp = (struct timeval *) region;
489#ifdef __FreeBSD__
490	if (profile) {
491		size_t tlen = sizeof (otiming);
492		int timing = 1;
493
494		resetstats();
495		if (sysctlbyname("debug.crypto_timing", &otiming, &tlen,
496				&timing, sizeof (timing)) < 0)
497			perror("debug.crypto_timing");
498	}
499#endif
500
501	if (threads > 1) {
502		for (i = 0; i < threads; i++)
503			if (fork() == 0) {
504				cpuset_t mask;
505				CPU_ZERO(&mask);
506				CPU_SET(i, &mask);
507				cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_PID,
508				    -1, sizeof(mask), &mask);
509				runtest(ealg, alg, count, size, cmd, &tvp[i]);
510				exit(0);
511			}
512		while (waitpid(WAIT_MYPGRP, &status, 0) != -1)
513			;
514	} else
515		runtest(ealg, alg, count, size, cmd, tvp);
516
517	t = 0;
518	for (i = 0; i < threads; i++)
519		t += (((double)tvp[i].tv_sec * 1000000 + tvp[i].tv_usec) / 1000000);
520	if (t) {
521		int nops = alg->ishash ? count : 2*count;
522
523		nops *= threads;
524		printf("%8.3lf sec, %7d %6s%s%6s crypts, %7d bytes, %8.0lf byte/sec, %7.1lf Mb/sec\n",
525		    t, nops, alg->name, ealg? "+" : "", ealg? ealg->name : "",
526		    size, (double)nops*size / t,
527		    (double)nops*size / t * 8 / 1024 / 1024);
528	}
529#ifdef __FreeBSD__
530	if (profile) {
531		struct cryptostats stats;
532		size_t slen = sizeof (stats);
533
534		if (sysctlbyname("debug.crypto_timing", NULL, NULL,
535				&otiming, sizeof (otiming)) < 0)
536			perror("debug.crypto_timing");
537		if (sysctlbyname("kern.crypto_stats", &stats, &slen, NULL, 0) < 0)
538			perror("kern.cryptostats");
539		if (stats.cs_invoke.count) {
540			printt("dispatch->invoke", &stats.cs_invoke);
541			printt("invoke->done", &stats.cs_done);
542			printt("done->cb", &stats.cs_cb);
543			printt("cb->finis", &stats.cs_finis);
544		}
545	}
546#endif
547	fflush(stdout);
548}
549
550int
551main(int argc, char **argv)
552{
553	struct alg *alg = NULL, *ealg = NULL;
554	char *tmp;
555	int count = 1;
556	int sizes[128], nsizes = 0;
557	u_long cmd = CIOCGSESSION2;
558	int testall = 0;
559	int maxthreads = 1;
560	int profile = 0;
561	int i, ch;
562
563	while ((ch = getopt(argc, argv, "cpzsva:bd:t:")) != -1) {
564		switch (ch) {
565#ifdef CIOCGSSESSION
566		case 's':
567			cmd = CIOCGSSESSION;
568			break;
569#endif
570		case 'v':
571			verbose++;
572			break;
573		case 'a':
574			tmp = strchr(optarg, '+');
575			if (tmp != NULL) {
576				*tmp = '\0';
577				ealg = getalgbyname(optarg);
578				if (ealg == NULL || ealg->ishash)
579					usage(argv[0]);
580				optarg = tmp + 1;
581			}
582
583			alg = getalgbyname(optarg);
584			if (alg == NULL) {
585				if (streq(optarg, "rijndael"))
586					alg = getalgbyname("aes");
587				else
588					usage(argv[0]);
589			} else if (ealg != NULL && !alg->ishash)
590				usage(argv[0]);
591			break;
592		case 'd':
593			crid = crlookup(optarg);
594			break;
595		case 't':
596			maxthreads = atoi(optarg);
597			break;
598		case 'z':
599			testall = 1;
600			break;
601		case 'p':
602			profile = 1;
603			break;
604		case 'b':
605			opflags |= COP_F_BATCH;
606			break;
607		case 'c':
608			verify = 1;
609			break;
610		default:
611			usage(argv[0]);
612		}
613	}
614	argc -= optind, argv += optind;
615	if (argc > 0)
616		count = atoi(argv[0]);
617	while (argc > 1) {
618		int s = atoi(argv[1]);
619		if (nsizes < nitems(sizes)) {
620			sizes[nsizes++] = s;
621		} else {
622			printf("Too many sizes, ignoring %u\n", s);
623		}
624		argc--, argv++;
625	}
626	if (maxthreads > CPU_SETSIZE)
627		errx(EX_USAGE, "Too many threads, %d, choose fewer.", maxthreads);
628
629	if (nsizes == 0) {
630		if (alg)
631			sizes[nsizes++] = alg->blocksize;
632		else
633			sizes[nsizes++] = 8;
634		if (testall) {
635			while (sizes[nsizes-1] < 8*1024) {
636				sizes[nsizes] = sizes[nsizes-1]<<1;
637				nsizes++;
638			}
639		}
640	}
641
642	if (testall) {
643		for (i = 0; i < nitems(algorithms); i++) {
644			int j;
645			alg = &algorithms[i];
646			for (j = 0; j < nsizes; j++)
647				runtests(ealg, alg, count, sizes[j], cmd, maxthreads, profile);
648		}
649	} else {
650		if (alg == NULL)
651			alg = getalgbycode(CRYPTO_3DES_CBC);
652		for (i = 0; i < nsizes; i++)
653			runtests(ealg, alg, count, sizes[i], cmd, maxthreads, profile);
654	}
655
656	return (0);
657}
658
659void
660hexdump(char *p, int n)
661{
662	int i, off;
663
664	for (off = 0; n > 0; off += 16, n -= 16) {
665		printf("%s%04x:", off == 0 ? "\n" : "", off);
666		i = (n >= 16 ? 16 : n);
667		do {
668			printf(" %02x", *p++ & 0xff);
669		} while (--i);
670		printf("\n");
671	}
672}
673