xref: /inferno-os/appl/lib/readpng.b (revision 37da2899f40661e3e9631e497da8dc59b971cbd0)
1implement RImagefile;
2
3include "sys.m";
4	sys: Sys;
5
6include "draw.m";
7	draw: Draw;
8	Point: import Draw;
9
10include "bufio.m";
11	bufio: Bufio;
12	Iobuf: import bufio;
13
14include "imagefile.m";
15
16include "crc.m";
17	crc: Crc;
18	CRCstate: import Crc;
19
20include "filter.m";
21	inflate: Filter;
22
23Chunk: adt {
24	size : int;
25	typ: string;
26	crc_state: ref CRCstate;
27};
28
29Png: adt {
30	depth: int;
31	filterbpp: int;
32	colortype: int;
33	compressionmethod: int;
34	filtermethod: int;
35	interlacemethod: int;
36	# tRNS
37	PLTEsize: int;
38	tRNS: array of byte;
39	# state for managing unpacking
40	alpha: int;
41	done: int;
42	error: string;
43	row, rowstep, colstart, colstep: int;
44	phase: int;
45	phasecols: int;
46	phaserows: int;
47	rowsize: int;
48	rowbytessofar: int;
49	thisrow: array of byte;
50	lastrow: array of byte;
51};
52
53init(iomod: Bufio)
54{
55	if(sys == nil)
56		sys = load Sys Sys->PATH;
57	if(crc == nil)
58		crc =  load Crc Crc->PATH;
59	if(inflate == nil)
60		inflate = load Filter "/dis/lib/inflate.dis";
61	inflate->init();
62	bufio = iomod;
63}
64
65readmulti(fd: ref Iobuf): (array of ref Rawimage, string)
66{
67	(i, err) := read(fd);
68	if(i != nil){
69		a := array[1] of { i };
70		return (a, err);
71	}
72	return (nil, err);
73}
74
75read(fd: ref Iobuf): (ref Rawimage, string)
76{
77	chunk := ref Chunk;
78	png := ref Png;
79	raw := ref Rawimage;
80
81	chunk.crc_state = crc->init(0, int 16rffffffff);
82# Check it's a PNG
83	if (!get_signature(fd))
84		return (nil, "not a PNG");
85# Get the IHDR
86	if (!get_chunk_header(fd, chunk))
87		return (nil, "duff header");
88	if (chunk.typ != "IHDR")
89		return (nil, "IHDR must come first");
90	if (chunk.size != 13)
91		return (nil, "IHDR wrong size");
92	raw.r.max.x = get_int(fd, chunk.crc_state);
93	if (raw.r.max.x <= 0)
94		return (nil, "invalid width");
95	raw.r.max.y = get_int(fd, chunk.crc_state);
96	if (raw.r.max.y <= 0)
97		return (nil, "invalid height");
98	png.depth = get_byte(fd, chunk.crc_state);
99	case png.depth {
100	1 or 2 or 4 or 8 or 16 =>
101		;
102	* =>
103		return (nil, "invalid depth");
104	}
105	png.colortype = get_byte(fd, chunk.crc_state);
106
107	okcombo : int;
108
109	case png.colortype {
110	0 =>
111		okcombo = 1;
112		raw.nchans = 1;
113		raw.chandesc = RImagefile->CY;
114		png.alpha = 0;
115	2  =>
116		okcombo = (png.depth == 8 || png.depth == 16);
117		raw.nchans = 3;
118		raw.chandesc = RImagefile->CRGB;
119		png.alpha = 0;
120	3 =>
121		okcombo = (png.depth != 16);
122		raw.nchans = 1;
123		raw.chandesc = RImagefile->CRGB1;
124		png.alpha = 0;
125	4 =>
126		okcombo = (png.depth == 8 || png.depth == 16);
127		raw.nchans = 1;
128		raw.chandesc = RImagefile->CY;
129		png.alpha = 1;
130	6 =>
131		okcombo = (png.depth == 8 || png.depth == 16);
132		raw.nchans = 3;
133		raw.chandesc = RImagefile->CRGB;
134		png.alpha = 1;
135	* =>
136		return (nil, "invalid colortype");
137	}
138	if (!okcombo)
139		return (nil, "invalid depth/colortype combination");
140	png.compressionmethod = get_byte(fd, chunk.crc_state);
141	if (png.compressionmethod != 0)
142		return (nil, "invalid compression method " + string png.compressionmethod);
143	png.filtermethod = get_byte(fd, chunk.crc_state);
144	if (png.filtermethod != 0)
145		return (nil, "invalid filter method");
146	png.interlacemethod = get_byte(fd, chunk.crc_state);
147	if (png.interlacemethod != 0 && png.interlacemethod != 1)
148		return (nil, "invalid interlace method");
149	if(0)
150		sys->print("width %d height %d depth %d colortype %d interlace %d\n",
151			raw.r.max.x, raw.r.max.y, png.depth, png.colortype, png.interlacemethod);
152	if (!get_crc_and_check(fd, chunk))
153		return (nil, "invalid CRC");
154# Stash some detail in raw
155	raw.r.min = Point(0, 0);
156	raw.transp = 0;
157	raw.chans = array[raw.nchans] of array of byte;
158	{
159		for (r:= 0; r < raw.nchans; r++)
160			raw.chans[r] = array[raw.r.max.x * raw.r.max.y] of byte;
161	}
162# Get the next chunk
163	seenPLTE := 0;
164	seenIDAT := 0;
165	seenLastIDAT := 0;
166	inflateFinished := 0;
167	seenIEND := 0;
168	seentRNS := 0;
169	rq: chan of ref Filter->Rq;
170
171	png.error = nil;
172	rq = nil;
173	while (png.error == nil) {
174		if (!get_chunk_header(fd, chunk)) {
175			if (!seenIEND)
176				png.error = "duff header";
177			break;
178		}
179		if (seenIEND) {
180			png.error = "rubbish at eof";
181			break;
182		}
183		case (chunk.typ) {
184		"IEND" =>
185			seenIEND = 1;
186		"PLTE" =>
187			if (seenPLTE) {
188				png.error = "too many PLTEs";
189				break;
190			}
191			if (seentRNS) {
192				png.error = "tRNS before PLTE";
193				break;
194			}
195			if (seenIDAT) {
196				png.error = "PLTE too late";
197				break;
198			}
199			if (chunk.size % 3 || chunk.size < 1 * 3 || chunk.size > 256 * 3) {
200				png.error = "PLTE strange size";
201				break;
202			}
203			if (png.colortype == 0 || png.colortype == 4) {
204				png.error = "superfluous PLTE";
205				break;
206			}
207			raw.cmap = array[256 * 3] of byte;
208			png.PLTEsize = chunk.size / 3;
209			if (!get_bytes(fd, chunk.crc_state, raw.cmap, chunk.size)) {
210				png.error = "eof in PLTE";
211				break;
212			}
213#			{
214#				x: int;
215#				sys->print("Palette:\n");
216#				for (x = 0; x < chunk.size; x += 3)
217#					sys->print("%3d: (%3d, %3d, %3d)\n",
218#						x / 3, int raw.cmap[x], int raw.cmap[x + 1], int raw.cmap[x + 2]);
219#			}
220			seenPLTE = 1;
221		"tRNS" =>
222			if (seenIDAT) {
223				png.error = "tRNS too late";
224				break;
225			}
226			case png.colortype {
227			0 =>
228				if (chunk.size != 2) {
229					png.error = "tRNS wrong size";
230					break;
231				}
232				level := get_ushort(fd, chunk.crc_state);
233				if (level < 0) {
234					png.error = "eof in tRNS";
235					break;
236				}
237				if (png.depth != 16) {
238					raw.transp = 1;
239					raw.trindex = byte level;
240				}
241			2 =>
242				# a legitimate coding, but we can't use the information
243				if (!skip_bytes(fd, chunk.crc_state, chunk.size))
244					png.error = "eof in skipped tRNS chunk";
245				break;
246			3 =>
247				if (!seenPLTE) {
248					png.error = "tRNS too early";
249					break;
250				}
251				if (chunk.size > png.PLTEsize) {
252					png.error = "tRNS too big";
253					break;
254				}
255				png.tRNS = array[png.PLTEsize] of byte;
256				for (x := chunk.size; x < png.PLTEsize; x++)
257					png.tRNS[x] = byte 255;
258				if (!get_bytes(fd, chunk.crc_state, png.tRNS, chunk.size)) {
259					png.error = "eof in tRNS";
260					break;
261				}
262#				{
263#					sys->print("tRNS:\n");
264#					for (x = 0; x < chunk.size; x++)
265#						sys->print("%3d: (%3d)\n", x, int png.tRNS[x]);
266#				}
267				if (png.error == nil) {
268					# analyse the tRNS chunk to see if it contains a single transparent index
269					# translucent entries are treated as opaque
270					for (x = 0; x < chunk.size; x++)
271						if (png.tRNS[x] == byte 0) {
272							raw.trindex = byte x;
273							if (raw.transp) {
274								raw.transp = 0;
275								break;
276							}
277							raw.transp = 1;
278						}
279#					if (raw.transp)
280#						sys->print("selected index %d\n", int raw.trindex);
281				}
282			4 or 6 =>
283				png.error = "tRNS invalid when alpha present";
284			}
285			seentRNS = 1;
286		"IDAT" =>
287			if (seenLastIDAT) {
288				png.error = "non contiguous IDATs";
289				break;
290			}
291			if (inflateFinished) {
292				png.error = "too many IDATs";
293				break;
294			}
295			remaining := 0;
296			if (!seenIDAT) {
297				# open channel to inflate filter
298				if (!processdatainit(png, raw))
299					break;
300				rq = inflate->start(nil);
301				skip_bytes(fd, chunk.crc_state, 2);
302				remaining = chunk.size - 2;
303			}
304			else
305				remaining = chunk.size;
306			while (remaining && png.error == nil) {
307				pick m := <- rq {
308				Fill =>
309#					sys->print("Fill(%d) remaining %d\n", len m.buf, remaining);
310					toget := len m.buf;
311					if (toget > remaining)
312						toget = remaining;
313					if (!get_bytes(fd, chunk.crc_state, m.buf, toget)) {
314						m.reply <-= -1;
315						png.error = "eof during IDAT";
316						break;
317					}
318					m.reply <-= toget;
319					remaining -= toget;
320				Result =>
321#					sys->print("Result(%d)\n", len m.buf);
322					m.reply <-= 0;
323					processdata(png, raw, m.buf);
324				Info =>
325#					sys->print("Info(%s)\n", m.msg);
326				Finished =>
327					inflateFinished = 1;
328#					sys->print("Finished\n");
329				Error =>
330					return (nil, "inflate error\n");
331				}
332			}
333			seenIDAT = 1;
334		* =>
335			# skip the blighter
336			if (!skip_bytes(fd, chunk.crc_state, chunk.size))
337				png.error = "eof in skipped chunk";
338		}
339		if (png.error != nil)
340			break;
341		if (!get_crc_and_check(fd, chunk))
342			return (nil, "invalid CRC");
343		if (chunk.typ != "IDAT" && seenIDAT)
344			seenLastIDAT = 1;
345	}
346	# can only get here if IEND was last chunk, or png.error set
347
348	if (png.error == nil && !seenIDAT) {
349		png.error = "no IDAT!";
350		inflateFinished = 1;
351	}
352	while (rq != nil && !inflateFinished) {
353		pick m := <-rq {
354		Fill =>
355#			sys->print("Fill(%d)\n", len m.buf);
356			png.error = "eof in zlib stream";
357			m.reply <-= -1;
358			inflateFinished = 1;
359		Result =>
360#			sys->print("Result(%d)\n", len m.buf);
361			if (png.error != nil) {
362				m.reply <-= -1;
363				inflateFinished = 1;
364			}
365			else {
366				m.reply <-= 0;
367				processdata(png, raw, m.buf);
368			}
369		Info =>
370#			sys->print("Info(%s)\n", m.msg);
371		Finished =>
372#			sys->print("Finished\n");
373			inflateFinished = 1;
374			break;
375		Error =>
376			png.error = "inflate error\n";
377			inflateFinished = 1;
378		}
379
380	}
381	if (png.error == nil && !png.done)
382		png.error = "insufficient data";
383	return (raw, png.error);
384}
385
386phase2stepping(phase: int): (int, int, int, int)
387{
388	case phase {
389	0 =>
390		return (0, 1, 0, 1);
391	1 =>
392		return (0, 8, 0, 8);
393	2 =>
394		return (0, 8, 4, 8);
395	3 =>
396		return (4, 8, 0, 4);
397	4 =>
398		return (0, 4, 2, 4);
399	5 =>
400		return (2, 4, 0, 2);
401	6 =>
402		return (0, 2, 1, 2);
403	7 =>
404		return (1, 2, 0, 1);
405	* =>
406		return (-1, -1, -1, -1);
407	}
408}
409
410processdatainitphase(png: ref Png, raw: ref Rawimage)
411{
412	(png.row, png.rowstep, png.colstart, png.colstep) = phase2stepping(png.phase);
413	if (raw.r.max.x > png.colstart)
414		png.phasecols = (raw.r.max.x - png.colstart + png.colstep - 1) / png.colstep;
415	else
416		png.phasecols = 0;
417	if (raw.r.max.y > png.row)
418		png.phaserows = (raw.r.max.y - png.row + png.rowstep - 1) / png.rowstep;
419	else
420		png.phaserows = 0;
421	png.rowsize = png.phasecols * (raw.nchans + png.alpha) * png.depth;
422	png.rowsize = (png.rowsize + 7) / 8;
423	png.rowsize++;		# for the filter byte
424	png.rowbytessofar = 0;
425	png.thisrow = array[png.rowsize] of byte;
426	png.lastrow = array[png.rowsize] of byte;
427#	sys->print("init phase %d: r (%d, %d, %d) c (%d, %d, %d) (%d)\n",
428#		png.phase, png.row, png.rowstep, png.phaserows,
429#		png.colstart, png.colstep, png.phasecols, png.rowsize);
430}
431
432processdatainit(png: ref Png, raw: ref Rawimage): int
433{
434	if (raw.nchans != 1&& raw.nchans != 3) {
435		png.error = "only 1 or 3 channels supported";
436		return 0;
437	}
438#	if (png.interlacemethod != 0) {
439#		png.error = "only progressive supported";
440#		return 0;
441#	}
442	if (png.colortype == 3 && raw.cmap == nil) {
443		png.error = "PLTE chunk missing";
444		return 0;
445	}
446	png.done = 0;
447	png.filterbpp = (png.depth * (raw.nchans + png.alpha) + 7) / 8;
448	png.phase = png.interlacemethod;
449
450	processdatainitphase(png, raw);
451
452	return 1;
453}
454
455upconvert(out: array of byte, outstride: int, in: array of byte, pixels: int, bpp: int)
456{
457	b: byte;
458	bits := pixels * bpp;
459	lim := bits / 8;
460	mask := byte ((1 << bpp) - 1);
461	outx := 0;
462	inx := 0;
463	for (x := 0; x < lim; x++) {
464		b = in[inx];
465		for (s := 8 - bpp; s >= 0; s -= bpp) {
466			pixel := (b >> s) & mask;
467			ucp := pixel;
468			for (y := bpp; y < 8; y += bpp)
469				ucp |= pixel << y;
470			out[outx] = ucp;
471			outx += outstride;
472		}
473		inx++;
474	}
475	residue := (bits % 8) / bpp;
476	if (residue) {
477		b = in[inx];
478		for (s := 8 - bpp; s >= 0; s -= bpp) {
479			pixel := (b >> s) & mask;
480			ucp := pixel;
481			for (y := bpp; y < 8; y += bpp)
482				ucp |= pixel << y;
483			out[outx] = ucp;
484			outx += outstride;
485			if (--residue <= 0)
486				break;
487		}
488	}
489}
490
491# expand (1 or 2 or 4) bit to 8 bit without scaling (for palletized stuff)
492
493expand(out: array of byte, outstride: int, in: array of byte, pixels: int, bpp: int)
494{
495	b: byte;
496	bits := pixels * bpp;
497	lim := bits / 8;
498	mask := byte ((1 << bpp) - 1);
499	outx := 0;
500	inx := 0;
501	for (x := 0; x < lim; x++) {
502		b = in[inx];
503		for (s := 8 - bpp; s >= 0; s -= bpp) {
504			out[outx] = (b >> s) & mask;
505			outx += outstride;
506		}
507		inx++;
508	}
509	residue := (bits % 8) / bpp;
510	if (residue) {
511		b = in[inx];
512		for (s := 8 - bpp; s >= 0; s -= bpp) {
513			out[outx] = (b >> s) & mask;
514			outx += outstride;
515			if (--residue <= 0)
516				break;
517		}
518	}
519}
520
521copybytes(out: array of byte, outstride: int, in: array of byte, instride: int, pixels: int)
522{
523	inx := 0;
524	outx := 0;
525	for (x := 0; x < pixels; x++) {
526		out[outx] = in[inx];
527		inx += instride;
528		outx += outstride;
529	}
530}
531
532outputrow(png: ref Png, raw: ref Rawimage, row: array of byte)
533{
534	offset := png.row * raw.r.max.x;
535	case raw.nchans {
536	1 =>
537		case (png.depth) {
538		* =>
539			png.error = "depth not supported";
540			return;
541		1 or 2 or 4 =>
542			if (raw.chandesc == RImagefile->CRGB1)
543				expand(raw.chans[0][offset + png.colstart:], png.colstep, row, png.phasecols, png.depth);
544			else
545				upconvert(raw.chans[0][offset + png.colstart:], png.colstep, row, png.phasecols, png.depth);
546		8 or 16 =>
547			# might have an Alpha channel to ignore!
548			stride := (png.alpha + 1) * png.depth / 8;
549			copybytes(raw.chans[0][offset + png.colstart:], png.colstep, row, stride, png.phasecols);
550		}
551	3 =>
552		case (png.depth) {
553		* =>
554			png.error = "depth not supported (2)";
555			return;
556		8 or 16 =>
557			# split rgb into three channels
558			bytespc := png.depth / 8;
559			stride := (3  + png.alpha) * bytespc;
560			copybytes(raw.chans[0][offset + png.colstart:], png.colstep, row, stride, png.phasecols);
561			copybytes(raw.chans[1][offset + png.colstart:], png.colstep, row[bytespc:], stride, png.phasecols);
562			copybytes(raw.chans[2][offset + png.colstart:], png.colstep, row[bytespc * 2:], stride, png.phasecols);
563		}
564	}
565}
566
567filtersub(png: ref Png)
568{
569	subx := 1;
570	for (x := int png.filterbpp + 1; x < png.rowsize; x++) {
571		png.thisrow[x] += png.thisrow[subx];
572		subx++;
573	}
574}
575
576filterup(png: ref Png)
577{
578	if (png.row == 0)
579		return;
580	for (x := 1; x < png.rowsize; x++)
581		png.thisrow[x] += png.lastrow[x];
582}
583
584filteraverage(png: ref Png)
585{
586	for (x := 1; x < png.rowsize; x++) {
587		a: int;
588		if (x > png.filterbpp)
589			a = int png.thisrow[x - png.filterbpp];
590		else
591			a = 0;
592		if (png.row != 0)
593			a += int png.lastrow[x];
594		png.thisrow[x] += byte (a / 2);
595	}
596}
597
598filterpaeth(png: ref Png)
599{
600	a, b, c: byte;
601	p, pa, pb, pc: int;
602	for (x := 1; x < png.rowsize; x++) {
603		if (x > png.filterbpp)
604			a = png.thisrow[x - png.filterbpp];
605		else
606			a = byte 0;
607		if (png.row == 0) {
608			b = byte 0;
609			c = byte 0;
610		} else {
611			b = png.lastrow[x];
612			if (x > png.filterbpp)
613				c = png.lastrow[x - png.filterbpp];
614			else
615				c = byte 0;
616		}
617		p = int a + int b - int c;
618		pa = p - int a;
619		if (pa < 0)
620			pa = -pa;
621		pb  = p - int b;
622		if (pb < 0)
623			pb = -pb;
624		pc = p - int c;
625		if (pc < 0)
626			pc = -pc;
627		if (pa <= pb && pa <= pc)
628			png.thisrow[x] += a;
629		else if (pb <= pc)
630			png.thisrow[x] += b;
631		else
632			png.thisrow[x] += c;
633	}
634}
635
636phaseendcheck(png: ref Png, raw: ref Rawimage): int
637{
638	if (png.row >= raw.r.max.y || png.rowsize <= 1) {
639		# this phase is over
640		if (png.phase == 0) {
641			png.done = 1;
642		}
643		else {
644			png.phase++;
645			if (png.phase > 7)
646				png.done = 1;
647			else
648				processdatainitphase(png, raw);
649		}
650		return 1;
651	}
652	return 0;
653}
654
655processdata(png: ref Png, raw: ref Rawimage, buf: array of byte)
656{
657#sys->print("processdata(%d)\n", len buf);
658	if (png.error != nil)
659		return;
660	i := 0;
661	while (i < len buf) {
662		if (png.done) {
663			png.error = "too much data";
664			return;
665		}
666		if (phaseendcheck(png, raw))
667			continue;
668		tocopy := (png.rowsize - png.rowbytessofar);
669		if (tocopy > (len buf - i))
670			tocopy = len buf - i;
671		png.thisrow[png.rowbytessofar :] = buf[i : i + tocopy];
672		i += tocopy;
673		png.rowbytessofar += tocopy;
674		if (png.rowbytessofar >= png.rowsize) {
675			# a new row has arrived
676			# apply filter here
677#sys->print("phase %d row %d\n", png.phase, png.row);
678			case int png.thisrow[0] {
679			0 =>
680				;
681			1 =>
682				filtersub(png);
683			2 =>
684				filterup(png);
685			3 =>
686				filteraverage(png);
687			4 =>
688				filterpaeth(png);
689			* =>
690#				sys->print("implement filter method %d\n", int png.thisrow[0]);
691				png.error = "filter method unsupported";
692				return;
693			}
694			# output row
695			if (png.row >= raw.r.max.y) {
696				png.error = "too much data";
697				return;
698			}
699			outputrow(png, raw, png.thisrow[1 :]);
700			png.row += png.rowstep;
701			save := png.lastrow;
702			png.lastrow = png.thisrow;
703			png.thisrow = save;
704			png.rowbytessofar = 0;
705		}
706	}
707	phaseendcheck(png, raw);
708}
709
710get_signature(fd: ref Iobuf): int
711{
712	sig := array[8] of { byte 137, byte 80, byte 78, byte 71, byte 13, byte 10, byte 26, byte 10 };
713	x: int;
714	for (x = 0; x < 8; x++)
715		if (fd.getb() != int sig[x])
716			return 0;
717	return 1;
718}
719
720get_bytes(fd: ref Iobuf, crc_state: ref CRCstate, buf: array of byte, n: int): int
721{
722	if (buf == nil) {
723		fd.seek(big n, bufio->SEEKRELA);
724		return 1;
725	}
726	if (fd.read(buf, n) != n)
727		return 0;
728	if (crc_state != nil)
729		crc->crc(crc_state, buf, n);
730	return 1;
731}
732
733skip_bytes(fd: ref Iobuf, crc_state: ref CRCstate, n: int): int
734{
735	buf := array[1024] of byte;
736	while (n) {
737		thistime: int = 1024;
738		if (thistime > n)
739			thistime = n;
740		if (!get_bytes(fd, crc_state, buf, thistime))
741			return 0;
742		n -= thistime;
743	}
744	return 1;
745}
746
747get_4(fd: ref Iobuf, crc_state: ref CRCstate, signed: int): (int, int)
748{
749	buf := array[4] of byte;
750	if (!get_bytes(fd, crc_state, buf, 4))
751		return (0, 0);
752	if (signed && int buf[0] & 16r80)
753		return (0, 0);
754	r:int  = (int buf[0] << 24) | (int buf[1] << 16) | (int buf[2] << 8) | (int buf[3]);
755#	sys->print("got int %d\n", r);
756	return (1, r);
757}
758
759get_int(fd: ref Iobuf, crc_state: ref CRCstate): int
760{
761	ok, r: int;
762	(ok, r) = get_4(fd, crc_state, 1);
763	if (ok)
764		return r;
765	return -1;
766}
767
768get_ushort(fd: ref Iobuf, crc_state: ref CRCstate): int
769{
770	buf := array[2] of byte;
771	if (!get_bytes(fd, crc_state, buf, 2))
772		return -1;
773	return (int buf[0] << 8) | int buf[1];
774}
775
776get_crc_and_check(fd: ref Iobuf, chunk: ref Chunk): int
777{
778	crc, ok: int;
779	(ok, crc) = get_4(fd, nil, 0);
780	if (!ok)
781		return 0;
782#	sys->print("crc: computed %.8ux expected %.8ux\n", chunk.crc_state.crc, crc);
783	if (chunk.crc_state.crc != crc)
784		return 1;
785	return 1;
786}
787
788get_byte(fd: ref Iobuf, crc_state: ref CRCstate): int
789{
790	buf := array[1] of byte;
791	if (!get_bytes(fd, crc_state, buf, 1))
792		return -1;
793#	sys->print("got byte %d\n", int buf[0]);
794	return int buf[0];
795}
796
797get_type(fd: ref Iobuf, crc_state: ref CRCstate): string
798{
799	x: int;
800	buf := array[4] of byte;
801	if (!get_bytes(fd, crc_state, buf, 4))
802		return nil;
803	for (x = 0; x < 4; x++) {
804		c: int;
805		c = int buf[x];
806		if (c == bufio->EOF || (c < 65 || c > 90 && c < 97) || c > 122)
807			return nil;
808	}
809	return string buf;
810}
811
812get_chunk_header(fd: ref Iobuf, chunk: ref Chunk): int
813{
814	chunk.size = get_int(fd, nil);
815	if (chunk.size < 0)
816		return 0;
817	crc->reset(chunk.crc_state);
818	chunk.typ = get_type(fd, chunk.crc_state);
819	if (chunk.typ == nil)
820		return 0;
821#	sys->print("%s(%d)\n", chunk.typ, chunk.size);
822	return 1;
823}
824