1/*
2 * Copyright (c) 1997 - 2006 Kungliga Tekniska H�gskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
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 *
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 *
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * 3. Neither the name of the Institute nor the names of its contributors
18 *    may be used to endorse or promote products derived from this software
19 *    without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34#include "gen_locl.h"
35#include "lex.h"
36
37RCSID("$Id: gen_decode.c 21503 2007-07-12 11:57:19Z lha $");
38
39static void
40decode_primitive (const char *typename, const char *name, const char *forwstr)
41{
42#if 0
43    fprintf (codefile,
44	     "e = decode_%s(p, len, %s, &l);\n"
45	     "%s;\n",
46	     typename,
47	     name,
48	     forwstr);
49#else
50    fprintf (codefile,
51	     "e = der_get_%s(p, len, %s, &l);\n"
52	     "if(e) %s;\np += l; len -= l; ret += l;\n",
53	     typename,
54	     name,
55	     forwstr);
56#endif
57}
58
59static int
60is_primitive_type(int type)
61{
62    switch(type) {
63    case TInteger:
64    case TBoolean:
65    case TOctetString:
66    case TBitString:
67    case TEnumerated:
68    case TGeneralizedTime:
69    case TGeneralString:
70    case TOID:
71    case TUTCTime:
72    case TUTF8String:
73    case TPrintableString:
74    case TIA5String:
75    case TBMPString:
76    case TUniversalString:
77    case TVisibleString:
78    case TNull:
79	return 1;
80    default:
81	return 0;
82    }
83}
84
85static void
86find_tag (const Type *t,
87	  Der_class *cl, Der_type *ty, unsigned *tag)
88{
89    switch (t->type) {
90    case TBitString:
91	*cl  = ASN1_C_UNIV;
92	*ty  = PRIM;
93	*tag = UT_BitString;
94	break;
95    case TBoolean:
96	*cl  = ASN1_C_UNIV;
97	*ty  = PRIM;
98	*tag = UT_Boolean;
99	break;
100    case TChoice:
101	errx(1, "Cannot have recursive CHOICE");
102    case TEnumerated:
103	*cl  = ASN1_C_UNIV;
104	*ty  = PRIM;
105	*tag = UT_Enumerated;
106	break;
107    case TGeneralString:
108	*cl  = ASN1_C_UNIV;
109	*ty  = PRIM;
110	*tag = UT_GeneralString;
111	break;
112    case TGeneralizedTime:
113	*cl  = ASN1_C_UNIV;
114	*ty  = PRIM;
115	*tag = UT_GeneralizedTime;
116	break;
117    case TIA5String:
118	*cl  = ASN1_C_UNIV;
119	*ty  = PRIM;
120	*tag = UT_IA5String;
121	break;
122    case TInteger:
123	*cl  = ASN1_C_UNIV;
124	*ty  = PRIM;
125	*tag = UT_Integer;
126	break;
127    case TNull:
128	*cl  = ASN1_C_UNIV;
129	*ty  = PRIM;
130	*tag = UT_Null;
131	break;
132    case TOID:
133	*cl  = ASN1_C_UNIV;
134	*ty  = PRIM;
135	*tag = UT_OID;
136	break;
137    case TOctetString:
138	*cl  = ASN1_C_UNIV;
139	*ty  = PRIM;
140	*tag = UT_OctetString;
141	break;
142    case TPrintableString:
143	*cl  = ASN1_C_UNIV;
144	*ty  = PRIM;
145	*tag = UT_PrintableString;
146	break;
147    case TSequence:
148    case TSequenceOf:
149	*cl  = ASN1_C_UNIV;
150	*ty  = CONS;
151	*tag = UT_Sequence;
152	break;
153    case TSet:
154    case TSetOf:
155	*cl  = ASN1_C_UNIV;
156	*ty  = CONS;
157	*tag = UT_Set;
158	break;
159    case TTag:
160	*cl  = t->tag.tagclass;
161	*ty  = is_primitive_type(t->subtype->type) ? PRIM : CONS;
162	*tag = t->tag.tagvalue;
163	break;
164    case TType:
165	if ((t->symbol->stype == Stype && t->symbol->type == NULL)
166	    || t->symbol->stype == SUndefined) {
167	    error_message("%s is imported or still undefined, "
168			  " can't generate tag checking data in CHOICE "
169			  "without this information",
170			  t->symbol->name);
171	    exit(1);
172	}
173	find_tag(t->symbol->type, cl, ty, tag);
174	return;
175    case TUTCTime:
176	*cl  = ASN1_C_UNIV;
177	*ty  = PRIM;
178	*tag = UT_UTCTime;
179	break;
180    case TUTF8String:
181	*cl  = ASN1_C_UNIV;
182	*ty  = PRIM;
183	*tag = UT_UTF8String;
184	break;
185    case TBMPString:
186	*cl  = ASN1_C_UNIV;
187	*ty  = PRIM;
188	*tag = UT_BMPString;
189	break;
190    case TUniversalString:
191	*cl  = ASN1_C_UNIV;
192	*ty  = PRIM;
193	*tag = UT_UniversalString;
194	break;
195    case TVisibleString:
196	*cl  = ASN1_C_UNIV;
197	*ty  = PRIM;
198	*tag = UT_VisibleString;
199	break;
200    default:
201	abort();
202    }
203}
204
205static void
206range_check(const char *name,
207	    const char *length,
208	    const char *forwstr,
209	    struct range *r)
210{
211    if (r->min == r->max + 2 || r->min < r->max)
212	fprintf (codefile,
213		 "if ((%s)->%s > %d) {\n"
214		 "e = ASN1_MAX_CONSTRAINT; %s;\n"
215		 "}\n",
216		 name, length, r->max, forwstr);
217    if (r->min - 1 == r->max || r->min < r->max)
218	fprintf (codefile,
219		 "if ((%s)->%s < %d) {\n"
220		 "e = ASN1_MIN_CONSTRAINT; %s;\n"
221		 "}\n",
222		 name, length, r->min, forwstr);
223    if (r->max == r->min)
224	fprintf (codefile,
225		 "if ((%s)->%s != %d) {\n"
226		 "e = ASN1_EXACT_CONSTRAINT; %s;\n"
227		 "}\n",
228		 name, length, r->min, forwstr);
229}
230
231static int
232decode_type (const char *name, const Type *t, int optional,
233	     const char *forwstr, const char *tmpstr)
234{
235    switch (t->type) {
236    case TType: {
237	if (optional)
238	    fprintf(codefile,
239		    "%s = calloc(1, sizeof(*%s));\n"
240		    "if (%s == NULL) %s;\n",
241		    name, name, name, forwstr);
242	fprintf (codefile,
243		 "e = decode_%s(p, len, %s, &l);\n",
244		 t->symbol->gen_name, name);
245	if (optional) {
246	    fprintf (codefile,
247		     "if(e) {\n"
248		     "free(%s);\n"
249		     "%s = NULL;\n"
250		     "} else {\n"
251		     "p += l; len -= l; ret += l;\n"
252		     "}\n",
253		     name, name);
254	} else {
255	    fprintf (codefile,
256		     "if(e) %s;\n",
257		     forwstr);
258	    fprintf (codefile,
259		     "p += l; len -= l; ret += l;\n");
260	}
261	break;
262    }
263    case TInteger:
264	if(t->members) {
265	    fprintf(codefile,
266		    "{\n"
267		    "int enumint;\n");
268	    decode_primitive ("integer", "&enumint", forwstr);
269	    fprintf(codefile,
270		    "*%s = enumint;\n"
271		    "}\n",
272		    name);
273	} else if (t->range == NULL) {
274	    decode_primitive ("heim_integer", name, forwstr);
275	} else if (t->range->min == INT_MIN && t->range->max == INT_MAX) {
276	    decode_primitive ("integer", name, forwstr);
277	} else if (t->range->min == 0 && t->range->max == UINT_MAX) {
278	    decode_primitive ("unsigned", name, forwstr);
279	} else if (t->range->min == 0 && t->range->max == INT_MAX) {
280	    decode_primitive ("unsigned", name, forwstr);
281	} else
282	    errx(1, "%s: unsupported range %d -> %d",
283		 name, t->range->min, t->range->max);
284	break;
285    case TBoolean:
286      decode_primitive ("boolean", name, forwstr);
287      break;
288    case TEnumerated:
289	decode_primitive ("enumerated", name, forwstr);
290	break;
291    case TOctetString:
292	decode_primitive ("octet_string", name, forwstr);
293	if (t->range)
294	    range_check(name, "length", forwstr, t->range);
295	break;
296    case TBitString: {
297	Member *m;
298	int pos = 0;
299
300	if (ASN1_TAILQ_EMPTY(t->members)) {
301	    decode_primitive ("bit_string", name, forwstr);
302	    break;
303	}
304	fprintf(codefile,
305		"if (len < 1) return ASN1_OVERRUN;\n"
306		"p++; len--; ret++;\n");
307	fprintf(codefile,
308		"do {\n"
309		"if (len < 1) break;\n");
310	ASN1_TAILQ_FOREACH(m, t->members, members) {
311	    while (m->val / 8 > pos / 8) {
312		fprintf (codefile,
313			 "p++; len--; ret++;\n"
314			 "if (len < 1) break;\n");
315		pos += 8;
316	    }
317	    fprintf (codefile,
318		     "(%s)->%s = (*p >> %d) & 1;\n",
319		     name, m->gen_name, 7 - m->val % 8);
320	}
321	fprintf(codefile,
322		"} while(0);\n");
323	fprintf (codefile,
324		 "p += len; ret += len;\n");
325	break;
326    }
327    case TSequence: {
328	Member *m;
329
330	if (t->members == NULL)
331	    break;
332
333	ASN1_TAILQ_FOREACH(m, t->members, members) {
334	    char *s;
335
336	    if (m->ellipsis)
337		continue;
338
339	    asprintf (&s, "%s(%s)->%s", m->optional ? "" : "&",
340		      name, m->gen_name);
341	    if (s == NULL)
342		errx(1, "malloc");
343	    decode_type (s, m->type, m->optional, forwstr, m->gen_name);
344	    free (s);
345	}
346
347	break;
348    }
349    case TSet: {
350	Member *m;
351	unsigned int memno;
352
353	if(t->members == NULL)
354	    break;
355
356	fprintf(codefile, "{\n");
357	fprintf(codefile, "unsigned int members = 0;\n");
358	fprintf(codefile, "while(len > 0) {\n");
359	fprintf(codefile,
360		"Der_class class;\n"
361		"Der_type type;\n"
362		"int tag;\n"
363		"e = der_get_tag (p, len, &class, &type, &tag, NULL);\n"
364		"if(e) %s;\n", forwstr);
365	fprintf(codefile, "switch (MAKE_TAG(class, type, tag)) {\n");
366	memno = 0;
367	ASN1_TAILQ_FOREACH(m, t->members, members) {
368	    char *s;
369
370	    assert(m->type->type == TTag);
371
372	    fprintf(codefile, "case MAKE_TAG(%s, %s, %s):\n",
373		    classname(m->type->tag.tagclass),
374		    is_primitive_type(m->type->subtype->type) ? "PRIM" : "CONS",
375		    valuename(m->type->tag.tagclass, m->type->tag.tagvalue));
376
377	    asprintf (&s, "%s(%s)->%s", m->optional ? "" : "&", name, m->gen_name);
378	    if (s == NULL)
379		errx(1, "malloc");
380	    if(m->optional)
381		fprintf(codefile,
382			"%s = calloc(1, sizeof(*%s));\n"
383			"if (%s == NULL) { e = ENOMEM; %s; }\n",
384			s, s, s, forwstr);
385	    decode_type (s, m->type, 0, forwstr, m->gen_name);
386	    free (s);
387
388	    fprintf(codefile, "members |= (1 << %d);\n", memno);
389	    memno++;
390	    fprintf(codefile, "break;\n");
391	}
392	fprintf(codefile,
393		"default:\n"
394		"return ASN1_MISPLACED_FIELD;\n"
395		"break;\n");
396	fprintf(codefile, "}\n");
397	fprintf(codefile, "}\n");
398	memno = 0;
399	ASN1_TAILQ_FOREACH(m, t->members, members) {
400	    char *s;
401
402	    asprintf (&s, "%s->%s", name, m->gen_name);
403	    if (s == NULL)
404		errx(1, "malloc");
405	    fprintf(codefile, "if((members & (1 << %d)) == 0)\n", memno);
406	    if(m->optional)
407		fprintf(codefile, "%s = NULL;\n", s);
408	    else if(m->defval)
409		gen_assign_defval(s, m->defval);
410	    else
411		fprintf(codefile, "return ASN1_MISSING_FIELD;\n");
412	    free(s);
413	    memno++;
414	}
415	fprintf(codefile, "}\n");
416	break;
417    }
418    case TSetOf:
419    case TSequenceOf: {
420	char *n;
421	char *sname;
422
423	fprintf (codefile,
424		 "{\n"
425		 "size_t %s_origlen = len;\n"
426		 "size_t %s_oldret = ret;\n"
427		 "size_t %s_olen = 0;\n"
428		 "void *%s_tmp;\n"
429		 "ret = 0;\n"
430		 "(%s)->len = 0;\n"
431		 "(%s)->val = NULL;\n",
432		 tmpstr,
433		 tmpstr,
434		 tmpstr,
435		 tmpstr,
436		 name,
437		 name);
438
439	fprintf (codefile,
440		 "while(ret < %s_origlen) {\n"
441		 "size_t %s_nlen = %s_olen + sizeof(*((%s)->val));\n"
442		 "if (%s_olen > %s_nlen) { e = ASN1_OVERFLOW; %s; }\n"
443		 "%s_olen = %s_nlen;\n"
444		 "%s_tmp = realloc((%s)->val, %s_olen);\n"
445		 "if (%s_tmp == NULL) { e = ENOMEM; %s; }\n"
446		 "(%s)->val = %s_tmp;\n",
447		 tmpstr,
448		 tmpstr, tmpstr, name,
449		 tmpstr, tmpstr, forwstr,
450		 tmpstr, tmpstr,
451		 tmpstr, name, tmpstr,
452		 tmpstr, forwstr,
453		 name, tmpstr);
454
455	asprintf (&n, "&(%s)->val[(%s)->len]", name, name);
456	if (n == NULL)
457	    errx(1, "malloc");
458	asprintf (&sname, "%s_s_of", tmpstr);
459	if (sname == NULL)
460	    errx(1, "malloc");
461	decode_type (n, t->subtype, 0, forwstr, sname);
462	fprintf (codefile,
463		 "(%s)->len++;\n"
464		 "len = %s_origlen - ret;\n"
465		 "}\n"
466		 "ret += %s_oldret;\n"
467		 "}\n",
468		 name,
469		 tmpstr, tmpstr);
470	if (t->range)
471	    range_check(name, "len", forwstr, t->range);
472	free (n);
473	free (sname);
474	break;
475    }
476    case TGeneralizedTime:
477	decode_primitive ("generalized_time", name, forwstr);
478	break;
479    case TGeneralString:
480	decode_primitive ("general_string", name, forwstr);
481	break;
482    case TTag:{
483    	char *tname;
484
485	fprintf(codefile,
486		"{\n"
487		"size_t %s_datalen, %s_oldlen;\n",
488		tmpstr, tmpstr);
489	if(dce_fix)
490	    fprintf(codefile,
491		    "int dce_fix;\n");
492	fprintf(codefile, "e = der_match_tag_and_length(p, len, %s, %s, %s, "
493		"&%s_datalen, &l);\n",
494		classname(t->tag.tagclass),
495		is_primitive_type(t->subtype->type) ? "PRIM" : "CONS",
496		valuename(t->tag.tagclass, t->tag.tagvalue),
497		tmpstr);
498	if(optional) {
499	    fprintf(codefile,
500		    "if(e) {\n"
501		    "%s = NULL;\n"
502		    "} else {\n"
503		     "%s = calloc(1, sizeof(*%s));\n"
504		     "if (%s == NULL) { e = ENOMEM; %s; }\n",
505		     name, name, name, name, forwstr);
506	} else {
507	    fprintf(codefile, "if(e) %s;\n", forwstr);
508	}
509	fprintf (codefile,
510		 "p += l; len -= l; ret += l;\n"
511		 "%s_oldlen = len;\n",
512		 tmpstr);
513	if(dce_fix)
514	    fprintf (codefile,
515		     "if((dce_fix = _heim_fix_dce(%s_datalen, &len)) < 0)\n"
516		     "{ e = ASN1_BAD_FORMAT; %s; }\n",
517		     tmpstr, forwstr);
518	else
519	    fprintf(codefile,
520		    "if (%s_datalen > len) { e = ASN1_OVERRUN; %s; }\n"
521		    "len = %s_datalen;\n", tmpstr, forwstr, tmpstr);
522	asprintf (&tname, "%s_Tag", tmpstr);
523	if (tname == NULL)
524	    errx(1, "malloc");
525	decode_type (name, t->subtype, 0, forwstr, tname);
526	if(dce_fix)
527	    fprintf(codefile,
528		    "if(dce_fix){\n"
529		    "e = der_match_tag_and_length (p, len, "
530		    "(Der_class)0,(Der_type)0, UT_EndOfContent, "
531		    "&%s_datalen, &l);\n"
532		    "if(e) %s;\np += l; len -= l; ret += l;\n"
533		    "} else \n", tmpstr, forwstr);
534	fprintf(codefile,
535		"len = %s_oldlen - %s_datalen;\n",
536		tmpstr, tmpstr);
537	if(optional)
538	    fprintf(codefile,
539		    "}\n");
540	fprintf(codefile,
541		"}\n");
542	free(tname);
543	break;
544    }
545    case TChoice: {
546	Member *m, *have_ellipsis = NULL;
547	const char *els = "";
548
549	if (t->members == NULL)
550	    break;
551
552	ASN1_TAILQ_FOREACH(m, t->members, members) {
553	    const Type *tt = m->type;
554	    char *s;
555	    Der_class cl;
556	    Der_type  ty;
557	    unsigned  tag;
558
559	    if (m->ellipsis) {
560		have_ellipsis = m;
561		continue;
562	    }
563
564	    find_tag(tt, &cl, &ty, &tag);
565
566	    fprintf(codefile,
567		    "%sif (der_match_tag(p, len, %s, %s, %s, NULL) == 0) {\n",
568		    els,
569		    classname(cl),
570		    ty ? "CONS" : "PRIM",
571		    valuename(cl, tag));
572	    asprintf (&s, "%s(%s)->u.%s", m->optional ? "" : "&",
573		      name, m->gen_name);
574	    if (s == NULL)
575		errx(1, "malloc");
576	    decode_type (s, m->type, m->optional, forwstr, m->gen_name);
577	    fprintf(codefile,
578		    "(%s)->element = %s;\n",
579		    name, m->label);
580	    free(s);
581	    fprintf(codefile,
582		    "}\n");
583	    els = "else ";
584	}
585	if (have_ellipsis) {
586	    fprintf(codefile,
587		    "else {\n"
588		    "(%s)->u.%s.data = calloc(1, len);\n"
589		    "if ((%s)->u.%s.data == NULL) {\n"
590		    "e = ENOMEM; %s;\n"
591		    "}\n"
592		    "(%s)->u.%s.length = len;\n"
593		    "memcpy((%s)->u.%s.data, p, len);\n"
594		    "(%s)->element = %s;\n"
595		    "p += len;\n"
596		    "ret += len;\n"
597		    "len -= len;\n"
598		    "}\n",
599		    name, have_ellipsis->gen_name,
600		    name, have_ellipsis->gen_name,
601		    forwstr,
602		    name, have_ellipsis->gen_name,
603		    name, have_ellipsis->gen_name,
604		    name, have_ellipsis->label);
605	} else {
606	    fprintf(codefile,
607		    "else {\n"
608		    "e = ASN1_PARSE_ERROR;\n"
609		    "%s;\n"
610		    "}\n",
611		    forwstr);
612	}
613	break;
614    }
615    case TUTCTime:
616	decode_primitive ("utctime", name, forwstr);
617	break;
618    case TUTF8String:
619	decode_primitive ("utf8string", name, forwstr);
620	break;
621    case TPrintableString:
622	decode_primitive ("printable_string", name, forwstr);
623	break;
624    case TIA5String:
625	decode_primitive ("ia5_string", name, forwstr);
626	break;
627    case TBMPString:
628	decode_primitive ("bmp_string", name, forwstr);
629	break;
630    case TUniversalString:
631	decode_primitive ("universal_string", name, forwstr);
632	break;
633    case TVisibleString:
634	decode_primitive ("visible_string", name, forwstr);
635	break;
636    case TNull:
637	fprintf (codefile, "/* NULL */\n");
638	break;
639    case TOID:
640	decode_primitive ("oid", name, forwstr);
641	break;
642    default :
643	abort ();
644    }
645    return 0;
646}
647
648void
649generate_type_decode (const Symbol *s)
650{
651    int preserve = preserve_type(s->name) ? TRUE : FALSE;
652
653    fprintf (headerfile,
654	     "int    "
655	     "decode_%s(const unsigned char *, size_t, %s *, size_t *);\n",
656	     s->gen_name, s->gen_name);
657
658    fprintf (codefile, "int\n"
659	     "decode_%s(const unsigned char *p,"
660	     " size_t len, %s *data, size_t *size)\n"
661	     "{\n",
662	     s->gen_name, s->gen_name);
663
664    switch (s->type->type) {
665    case TInteger:
666    case TBoolean:
667    case TOctetString:
668    case TOID:
669    case TGeneralizedTime:
670    case TGeneralString:
671    case TUTF8String:
672    case TPrintableString:
673    case TIA5String:
674    case TBMPString:
675    case TUniversalString:
676    case TVisibleString:
677    case TUTCTime:
678    case TNull:
679    case TEnumerated:
680    case TBitString:
681    case TSequence:
682    case TSequenceOf:
683    case TSet:
684    case TSetOf:
685    case TTag:
686    case TType:
687    case TChoice:
688	fprintf (codefile,
689		 "size_t ret = 0;\n"
690		 "size_t l;\n"
691		 "int e;\n");
692	if (preserve)
693	    fprintf (codefile, "const unsigned char *begin = p;\n");
694
695	fprintf (codefile, "\n");
696	fprintf (codefile, "memset(data, 0, sizeof(*data));\n"); /* hack to avoid `unused variable' */
697
698	decode_type ("data", s->type, 0, "goto fail", "Top");
699	if (preserve)
700	    fprintf (codefile,
701		     "data->_save.data = calloc(1, ret);\n"
702		     "if (data->_save.data == NULL) { \n"
703		     "e = ENOMEM; goto fail; \n"
704		     "}\n"
705		     "data->_save.length = ret;\n"
706		     "memcpy(data->_save.data, begin, ret);\n");
707	fprintf (codefile,
708		 "if(size) *size = ret;\n"
709		 "return 0;\n");
710	fprintf (codefile,
711		 "fail:\n"
712		 "free_%s(data);\n"
713		 "return e;\n",
714		 s->gen_name);
715	break;
716    default:
717	abort ();
718    }
719    fprintf (codefile, "}\n\n");
720}
721