LCOV - code coverage report
Current view: top level - src - der-encoder.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 0 241 0.0 %
Date: 2016-12-09 14:04:45 Functions: 0 19 0.0 %

          Line data    Source code
       1             : /* der-decoder.c - Distinguished Encoding Rules Encoder
       2             :  * Copyright (C) 2001, 2004, 2008, 2012 g10 Code GmbH
       3             :  *
       4             :  * This file is part of KSBA.
       5             :  *
       6             :  * KSBA is free software; you can redistribute it and/or modify
       7             :  * it under the terms of either
       8             :  *
       9             :  *   - the GNU Lesser General Public License as published by the Free
      10             :  *     Software Foundation; either version 3 of the License, or (at
      11             :  *     your option) any later version.
      12             :  *
      13             :  * or
      14             :  *
      15             :  *   - the GNU General Public License as published by the Free
      16             :  *     Software Foundation; either version 2 of the License, or (at
      17             :  *     your option) any later version.
      18             :  *
      19             :  * or both in parallel, as here.
      20             :  *
      21             :  * KSBA is distributed in the hope that it will be useful, but WITHOUT
      22             :  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
      23             :  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
      24             :  * License for more details.
      25             :  *
      26             :  * You should have received a copies of the GNU General Public License
      27             :  * and the GNU Lesser General Public License along with this program;
      28             :  * if not, see <http://www.gnu.org/licenses/>.
      29             :  */
      30             : 
      31             : #include <config.h>
      32             : #include <stdio.h>
      33             : #include <stdlib.h>
      34             : #include <string.h>
      35             : #include <assert.h>
      36             : #include "util.h"
      37             : 
      38             : #include "ksba.h"
      39             : #include "asn1-func.h"
      40             : #include "ber-help.h"
      41             : #include "der-encoder.h"
      42             : #include "convert.h"
      43             : 
      44             : struct der_encoder_s {
      45             :   AsnNode module;    /* the ASN.1 structure */
      46             :   ksba_writer_t writer;
      47             :   const char *last_errdesc; /* string with the error description */
      48             :   AsnNode root;   /* of the expanded parse tree */
      49             :   int debug;
      50             : };
      51             : 
      52             : /* To be useful for the DER encoder we store all data direct as the
      53             :    binary image, so we use the VALTYPE_MEM */
      54             : static gpg_error_t
      55           0 : store_value (AsnNode node, const void *buffer, size_t length)
      56             : {
      57           0 :   _ksba_asn_set_value (node, VALTYPE_MEM, buffer, length);
      58           0 :   return 0;
      59             : }
      60             : 
      61             : static void
      62           0 : clear_value (AsnNode node)
      63             : {
      64           0 :   _ksba_asn_set_value (node, VALTYPE_NULL, NULL, 0);
      65           0 : }
      66             : 
      67             : 
      68             : 
      69             : 
      70             : DerEncoder
      71           0 : _ksba_der_encoder_new (void)
      72             : {
      73             :   DerEncoder d;
      74             : 
      75           0 :   d = xtrycalloc (1, sizeof *d);
      76           0 :   if (!d)
      77           0 :     return NULL;
      78             : 
      79           0 :   return d;
      80             : }
      81             : 
      82             : void
      83           0 : _ksba_der_encoder_release (DerEncoder d)
      84             : {
      85           0 :   xfree (d);
      86           0 : }
      87             : 
      88             : 
      89             : /**
      90             :  * _ksba_der_encoder_set_module:
      91             :  * @d: Decoder object
      92             :  * @module: ASN.1 Parse tree
      93             :  *
      94             :  * Initialize the decoder with the ASN.1 module.  Note, that this is a
      95             :  * shallow copy of the module.  Fixme: What about ref-counting of
      96             :  * AsnNodes?
      97             :  *
      98             :  * Return value: 0 on success or an error code
      99             :  **/
     100             : gpg_error_t
     101           0 : _ksba_der_encoder_set_module (DerEncoder d, ksba_asn_tree_t module)
     102             : {
     103           0 :   if (!d || !module)
     104           0 :     return gpg_error (GPG_ERR_INV_VALUE);
     105           0 :   if (d->module)
     106           0 :     return gpg_error (GPG_ERR_CONFLICT); /* module already set */
     107             : 
     108           0 :   d->module = module->parse_tree;
     109           0 :   return 0;
     110             : }
     111             : 
     112             : 
     113             : gpg_error_t
     114           0 : _ksba_der_encoder_set_writer (DerEncoder d, ksba_writer_t w)
     115             : {
     116           0 :   if (!d || !w)
     117           0 :     return gpg_error (GPG_ERR_INV_VALUE);
     118           0 :   if (d->writer)
     119           0 :     return gpg_error (GPG_ERR_CONFLICT); /* reader already set */
     120             : 
     121           0 :   d->writer = w;
     122           0 :   return 0;
     123             : }
     124             : 
     125             : 
     126             : 
     127             : /*
     128             :   Helpers to construct and write out objects
     129             : */
     130             : 
     131             : 
     132             : /* Create and write a
     133             : 
     134             :   AlgorithmIdentifier ::= SEQUENCE {
     135             :       algorithm    OBJECT IDENTIFIER,
     136             :       parameters   ANY DEFINED BY algorithm OPTIONAL
     137             :   }
     138             : 
     139             :   where parameters will be set to NULL if parm is NULL or to an octet
     140             :   string with the given parm.  As a special hack parameter will not be
     141             :   written if PARM is given but parmlen is 0.  */
     142             : gpg_error_t
     143           0 : _ksba_der_write_algorithm_identifier (ksba_writer_t w, const char *oid,
     144             :                                       const void *parm, size_t parmlen)
     145             : {
     146             :   gpg_error_t err;
     147             :   unsigned char *buf;
     148             :   size_t len;
     149           0 :   int no_null = (parm && !parmlen);
     150             : 
     151           0 :   err = ksba_oid_from_str (oid, &buf, &len);
     152           0 :   if (err)
     153           0 :     return err;
     154             : 
     155             :   /* write the sequence */
     156             :   /* fixme: the the length to encode the TLV values are actually not
     157             :      just 2 byte each but depend on the length of the values - for
     158             :      our purposes the static values do work.  */
     159           0 :   err = _ksba_ber_write_tl (w, TYPE_SEQUENCE, CLASS_UNIVERSAL, 1,
     160           0 :                             (no_null? 2:4) + len + (parm? parmlen:0));
     161           0 :   if (err)
     162           0 :     goto leave;
     163             : 
     164             :   /* the OBJECT ID header and the value */
     165           0 :   err = _ksba_ber_write_tl (w, TYPE_OBJECT_ID, CLASS_UNIVERSAL, 0, len);
     166           0 :   if (!err)
     167           0 :     err = ksba_writer_write (w, buf, len);
     168           0 :   if (err)
     169           0 :     goto leave;
     170             : 
     171             :   /* Write the parameter */
     172           0 :   if (no_null)
     173             :     ;
     174           0 :   else if (parm)
     175             :     {
     176           0 :       err = _ksba_ber_write_tl (w, TYPE_OCTET_STRING, CLASS_UNIVERSAL,
     177             :                                 0, parmlen);
     178           0 :       if (!err)
     179           0 :         err = ksba_writer_write (w, parm, parmlen);
     180             :     }
     181             :   else
     182             :     {
     183           0 :       err = _ksba_ber_write_tl (w, TYPE_NULL, CLASS_UNIVERSAL, 0, 0);
     184             :     }
     185             : 
     186             :  leave:
     187           0 :   xfree (buf);
     188           0 :   return err;
     189             : }
     190             : 
     191             : 
     192             : 
     193             : 
     194             : 
     195             : /*************************************************
     196             :  ***  Copy data from a tree image to the tree  ***
     197             :  *************************************************/
     198             : 
     199             : /* Copy all values from the tree SRC (with values store in SRCIMAGE)
     200             :    to the tree DST */
     201             : gpg_error_t
     202           0 : _ksba_der_copy_tree (AsnNode dst_root,
     203             :                      AsnNode src_root, const unsigned char *src_image)
     204             : {
     205             :   AsnNode s, d;
     206             : 
     207           0 :   s = src_root;
     208           0 :   d = dst_root;
     209             :   /* note: we use the is_any flags becuase an inserted copy may have
     210             :      already changed the any tag to the actual type */
     211           0 :   while (s && d && (s->type == d->type || d->flags.is_any))
     212             :     {
     213           0 :       if (d->flags.is_any)
     214           0 :         d->type = s->type;
     215             : 
     216           0 :       if (s->flags.in_array && s->right)
     217             :         {
     218           0 :           if (!_ksba_asn_insert_copy (d))
     219           0 :             return gpg_error (GPG_ERR_ENOMEM);
     220             :         }
     221             : 
     222           0 :       if ( !_ksba_asn_is_primitive (s->type) )
     223             :         ;
     224           0 :       else if (s->off == -1)
     225           0 :         clear_value (d);
     226             :       else
     227           0 :         store_value (d, src_image + s->off + s->nhdr, s->len);
     228             : 
     229           0 :       s = _ksba_asn_walk_tree (src_root, s);
     230           0 :       d = _ksba_asn_walk_tree (dst_root, d);
     231             :     }
     232             : 
     233           0 :   if (s || d)
     234             :     {
     235             : /*        fputs ("ksba_der_copy_tree: trees don't match\nSOURCE TREE:\n", stderr); */
     236             : /*        _ksba_asn_node_dump_all (src_root, stderr); */
     237             : /*        fputs ("DESTINATION TREE:\n", stderr); */
     238             : /*        _ksba_asn_node_dump_all (dst_root, stderr); */
     239           0 :       return gpg_error (GPG_ERR_ENCODING_PROBLEM);
     240             :     }
     241           0 :   return 0;
     242             : }
     243             : 
     244             : 
     245             : 
     246             : /*********************************************
     247             :  ********** Store data in a tree *************
     248             :  *********************************************/
     249             : 
     250             : 
     251             : gpg_error_t
     252           0 : _ksba_der_store_time (AsnNode node, const ksba_isotime_t atime)
     253             : {
     254             :   char buf[50], *p;
     255             :   int need_gen;
     256             :   gpg_error_t err;
     257             : 
     258             :   /* First check that ATIME is indeed as formatted as expected. */
     259           0 :   err = _ksba_assert_time_format (atime);
     260           0 :   if (err)
     261           0 :     return err;
     262             : 
     263           0 :   memcpy (buf, atime, 8);
     264           0 :   memcpy (buf+8, atime+9, 6);
     265           0 :   strcpy (buf+14, "Z");
     266             : 
     267             :   /* We need to use generalized time beginning with the year 2050. */
     268           0 :   need_gen = (_ksba_cmp_time (atime, "20500101T000000") >= 0);
     269             : 
     270           0 :   if (node->type == TYPE_ANY)
     271           0 :     node->type = need_gen? TYPE_GENERALIZED_TIME : TYPE_UTC_TIME;
     272           0 :   else if (node->type == TYPE_CHOICE)
     273             :     { /* find a suitable choice to store the value */
     274             :       AsnNode n;
     275             : 
     276           0 :       for (n=node->down; n; n=n->right)
     277             :         {
     278           0 :           if ( (need_gen && n->type == TYPE_GENERALIZED_TIME)
     279           0 :                || (!need_gen && n->type == TYPE_UTC_TIME))
     280             :             {
     281           0 :               node = n;
     282           0 :               break;
     283             :             }
     284             :         }
     285             :     }
     286             : 
     287           0 :   if (node->type == TYPE_GENERALIZED_TIME
     288           0 :       || node->type == TYPE_UTC_TIME)
     289             :     {
     290           0 :       p = node->type == TYPE_UTC_TIME? (buf+2):buf;
     291           0 :       return store_value (node, p, strlen (p));
     292             :     }
     293             :   else
     294           0 :     return gpg_error (GPG_ERR_INV_VALUE);
     295             : }
     296             : 
     297             : /* Store the utf-8 STRING in NODE. */
     298             : gpg_error_t
     299           0 : _ksba_der_store_string (AsnNode node, const char *string)
     300             : {
     301           0 :   if (node->type == TYPE_CHOICE)
     302             :     {
     303             :       /* find a suitable choice to store the value */
     304             :     }
     305             : 
     306             : 
     307           0 :   if (node->type == TYPE_PRINTABLE_STRING)
     308             :     {
     309           0 :       return store_value (node, string, strlen (string));
     310             :     }
     311             :   else
     312           0 :     return gpg_error (GPG_ERR_INV_VALUE);
     313             : }
     314             : 
     315             : 
     316             : /* Store the integer VALUE in NODE.  VALUE is assumed to be a DER
     317             :    encoded integer prefixed with 4 bytes given its length in network
     318             :    byte order. */
     319             : gpg_error_t
     320           0 : _ksba_der_store_integer (AsnNode node, const unsigned char *value)
     321             : {
     322           0 :   if (node->type == TYPE_INTEGER)
     323             :     {
     324             :       size_t len;
     325             : 
     326           0 :       len = (value[0] << 24) | (value[1] << 16) | (value[2] << 8) | value[3];
     327           0 :       return store_value (node, value+4, len);
     328             :     }
     329             :   else
     330           0 :     return gpg_error (GPG_ERR_INV_VALUE);
     331             : }
     332             : 
     333             : gpg_error_t
     334           0 : _ksba_der_store_oid (AsnNode node, const char *oid)
     335             : {
     336             :   gpg_error_t err;
     337             : 
     338           0 :   if (node->type == TYPE_ANY)
     339           0 :     node->type = TYPE_OBJECT_ID;
     340             : 
     341           0 :   if (node->type == TYPE_OBJECT_ID)
     342             :     {
     343             :       unsigned char *buf;
     344             :       size_t len;
     345             : 
     346           0 :       err = ksba_oid_from_str (oid, &buf, &len);
     347           0 :       if (err)
     348           0 :         return err;
     349           0 :       err = store_value (node, buf, len);
     350           0 :       xfree (buf);
     351           0 :       return err;
     352             :     }
     353             :   else
     354           0 :     return gpg_error (GPG_ERR_INV_VALUE);
     355             : }
     356             : 
     357             : 
     358             : gpg_error_t
     359           0 : _ksba_der_store_octet_string (AsnNode node, const char *buf, size_t len)
     360             : {
     361           0 :   if (node->type == TYPE_ANY)
     362           0 :     node->type = TYPE_OCTET_STRING;
     363             : 
     364           0 :   if (node->type == TYPE_OCTET_STRING)
     365             :     {
     366           0 :       return store_value (node, buf, len);
     367             :     }
     368             :   else
     369           0 :     return gpg_error (GPG_ERR_INV_VALUE);
     370             : }
     371             : 
     372             : 
     373             : gpg_error_t
     374           0 : _ksba_der_store_sequence (AsnNode node, const unsigned char *buf, size_t len)
     375             : {
     376           0 :   if (node->type == TYPE_ANY)
     377           0 :     node->type = TYPE_PRE_SEQUENCE;
     378             : 
     379           0 :   if (node->type == TYPE_SEQUENCE || node->type == TYPE_PRE_SEQUENCE)
     380             :     {
     381           0 :       return store_value (node, buf, len);
     382             :     }
     383             :   else
     384           0 :     return gpg_error (GPG_ERR_INV_VALUE);
     385             : }
     386             : 
     387             : 
     388             : gpg_error_t
     389           0 : _ksba_der_store_null (AsnNode node)
     390             : {
     391           0 :   if (node->type == TYPE_ANY)
     392           0 :     node->type = TYPE_NULL;
     393             : 
     394           0 :   if (node->type == TYPE_NULL)
     395             :     {
     396           0 :       clear_value (node);
     397           0 :       return 0;
     398             :     }
     399             :   else
     400           0 :     return gpg_error (GPG_ERR_INV_VALUE);
     401             : }
     402             : 
     403             : 
     404             : /*
     405             :    Actual DER encoder
     406             : */
     407             : 
     408             : /* We have a value for this node.  Calculate the length of the header
     409             :    and store it in node->nhdr and store the length of the value in
     410             :    node->value. We assume that this is a primitive node and has a
     411             :    value of type VALTYPE_MEM. */
     412             : static void
     413           0 : set_nhdr_and_len (AsnNode node, unsigned long length)
     414             : {
     415           0 :   int buflen = 0;
     416             : 
     417           0 :   if (node->type == TYPE_SET_OF || node->type == TYPE_SEQUENCE_OF)
     418           0 :     buflen++;
     419           0 :   else if (node->type == TYPE_TAG)
     420           0 :     buflen++;
     421           0 :   else if (node->type < 0x1f || node->type == TYPE_PRE_SEQUENCE)
     422           0 :     buflen++;
     423             :   else
     424             :     {
     425           0 :       never_reached ();
     426             :       /* Fixme: tags with values above 31 are not yet implemented */
     427             :     }
     428             : 
     429           0 :   if (!node->type /*&& !class*/)
     430           0 :     buflen++; /* end tag */
     431           0 :   else if (node->type == TYPE_NULL /*&& !class*/)
     432           0 :     buflen++; /* NULL tag */
     433           0 :   else if (!length)
     434           0 :     buflen++; /* indefinite length */
     435           0 :   else if (length < 128)
     436           0 :     buflen++;
     437             :   else
     438             :     {
     439           0 :       buflen += (length <= 0xff ? 2:
     440           0 :                  length <= 0xffff ? 3:
     441           0 :                  length <= 0xffffff ? 4: 5);
     442             :     }
     443             : 
     444           0 :   node->len = length;
     445           0 :   node->nhdr = buflen;
     446           0 : }
     447             : 
     448             : /* Like above but put now put it into buffer.  return the number of
     449             :    bytes copied.  There is no need to do length checking here */
     450             : static size_t
     451           0 : copy_nhdr_and_len (unsigned char *buffer, AsnNode node)
     452             : {
     453           0 :   unsigned char *p = buffer;
     454             :   int tag, class;
     455             :   unsigned long length;
     456             : 
     457           0 :   tag = node->type;
     458           0 :   class = CLASS_UNIVERSAL;
     459           0 :   length = node->len;
     460             : 
     461           0 :   if (tag == TYPE_SET_OF)
     462           0 :     tag = TYPE_SET;
     463           0 :   else if (tag == TYPE_SEQUENCE_OF)
     464           0 :     tag = TYPE_SEQUENCE;
     465           0 :   else if (tag == TYPE_PRE_SEQUENCE)
     466           0 :     tag = TYPE_SEQUENCE;
     467           0 :   else if (tag == TYPE_TAG)
     468             :     {
     469           0 :       class = CLASS_CONTEXT;  /* Hmmm: we no way to handle other classes */
     470           0 :       tag = node->value.v_ulong;
     471             :     }
     472           0 :   if (tag < 0x1f)
     473             :     {
     474           0 :       *p = (class << 6) | tag;
     475           0 :       if (!_ksba_asn_is_primitive (tag))
     476           0 :         *p |= 0x20;
     477           0 :       p++;
     478             :     }
     479             :   else
     480             :     {
     481             :       /* fixme: Not_Implemented*/
     482             :     }
     483             : 
     484           0 :   if (!tag && !class)
     485           0 :     *p++ = 0; /* end tag */
     486           0 :   else if (tag == TYPE_NULL && !class)
     487           0 :     *p++ = 0; /* NULL tag */
     488           0 :   else if (!length)
     489           0 :     *p++ = 0x80; /* indefinite length - can't happen! */
     490           0 :   else if (length < 128)
     491           0 :     *p++ = length;
     492             :   else
     493             :     {
     494             :       int i;
     495             : 
     496             :       /* fixme: if we know the sizeof an ulong we could support larger
     497             :          objects - however this is pretty ridiculous */
     498           0 :       i = (length <= 0xff ? 1:
     499           0 :            length <= 0xffff ? 2:
     500           0 :            length <= 0xffffff ? 3: 4);
     501             : 
     502           0 :       *p++ = (0x80 | i);
     503           0 :       if (i > 3)
     504           0 :         *p++ = length >> 24;
     505           0 :       if (i > 2)
     506           0 :         *p++ = length >> 16;
     507           0 :       if (i > 1)
     508           0 :         *p++ = length >> 8;
     509           0 :       *p++ = length;
     510             :     }
     511             : 
     512           0 :   return p - buffer;
     513             : }
     514             : 
     515             : 
     516             : 
     517             : static unsigned long
     518           0 : sum_up_lengths (AsnNode root)
     519             : {
     520             :   AsnNode n;
     521           0 :   unsigned long len = 0;
     522             : 
     523           0 :   if (root->type == TYPE_NULL)
     524           0 :     return root->nhdr;
     525             : 
     526           0 :   if (!(n=root->down) || _ksba_asn_is_primitive (root->type))
     527           0 :     len = root->len;
     528             :   else
     529             :     {
     530           0 :       for (; n; n = n->right)
     531           0 :         len += sum_up_lengths (n);
     532             :     }
     533           0 :   if ( !_ksba_asn_is_primitive (root->type)
     534           0 :        && root->type != TYPE_CHOICE
     535           0 :        && len
     536           0 :        && !root->flags.is_implicit)
     537             :     { /* this is a constructed one */
     538           0 :       set_nhdr_and_len (root, len);
     539             :     }
     540             : 
     541           0 :   return len? (len + root->nhdr):0;
     542             : }
     543             : 
     544             : /* Create a DER encoding from the value tree ROOT and return an
     545             :    allocated image of appropriate length in r_image and r_imagelen.
     546             :    The value tree is modified so that it can be used the same way as a
     547             :    parsed one, i.e the elements off, and len are set to point into
     548             :    image.  */
     549             : gpg_error_t
     550           0 : _ksba_der_encode_tree (AsnNode root,
     551             :                        unsigned char **r_image, size_t *r_imagelen)
     552             : {
     553             :   AsnNode n;
     554             :   unsigned char *image;
     555             :   size_t imagelen, len;
     556             : 
     557             :   /* clear out all fields */
     558           0 :   for (n=root; n ; n = _ksba_asn_walk_tree (root, n))
     559             :     {
     560           0 :       n->off = -1;
     561           0 :       n->len = 0;
     562           0 :       n->nhdr = 0;
     563             :     }
     564             : 
     565             :   /* Set default values */
     566             :   /* FIXME */
     567             : 
     568             :   /* calculate the length of the headers.  These are the tag and
     569             :      length fields of all primitive elements */
     570           0 :   for (n=root; n ; n = _ksba_asn_walk_tree (root, n))
     571             :     {
     572           0 :       if (_ksba_asn_is_primitive (n->type)
     573           0 :           && !n->flags.is_implicit
     574           0 :           && ((n->valuetype == VALTYPE_MEM && n->value.v_mem.len)
     575           0 :               || n->type == TYPE_NULL))
     576           0 :         set_nhdr_and_len (n, n->value.v_mem.len);
     577             :     }
     578             : 
     579             :   /* Now calculate the length of all constructed types */
     580           0 :   imagelen = sum_up_lengths (root);
     581             : 
     582             : #if 0
     583             :   /* set off to zero, so that it can be dumped */
     584             :   for (n=root; n ; n = _ksba_asn_walk_tree (root, n))
     585             :       n->off = 0;
     586             :   fputs ("DER encoded value Tree:\n", stderr);
     587             :   _ksba_asn_node_dump_all (root, stderr);
     588             :   for (n=root; n ; n = _ksba_asn_walk_tree (root, n))
     589             :       n->off = -1;
     590             : #endif
     591             : 
     592             :   /* now we can create an encoding in image */
     593           0 :   image = xtrymalloc (imagelen);
     594           0 :   if (!image)
     595           0 :     return gpg_error (GPG_ERR_ENOMEM);
     596           0 :   len = 0;
     597           0 :   for (n=root; n ; n = _ksba_asn_walk_tree (root, n))
     598             :     {
     599             :       size_t nbytes;
     600             : 
     601           0 :       if (!n->nhdr)
     602           0 :         continue;
     603           0 :       assert (n->off == -1);
     604           0 :       assert (len < imagelen);
     605           0 :       n->off = len;
     606           0 :       nbytes = copy_nhdr_and_len (image+len, n);
     607           0 :       len += nbytes;
     608           0 :       if ( _ksba_asn_is_primitive (n->type)
     609           0 :            && n->valuetype == VALTYPE_MEM
     610           0 :            && n->value.v_mem.len )
     611             :         {
     612           0 :           nbytes = n->value.v_mem.len;
     613           0 :           assert (len + nbytes <= imagelen);
     614           0 :           memcpy (image+len, n->value.v_mem.buf, nbytes);
     615           0 :           len += nbytes;
     616             :         }
     617             :     }
     618             : 
     619           0 :   assert (len == imagelen);
     620             : 
     621           0 :   *r_image = image;
     622           0 :   if (r_imagelen)
     623           0 :     *r_imagelen = imagelen;
     624           0 :   return 0;
     625             : }

Generated by: LCOV version 1.12