mpi: Support printing of negative numbers.
authorWerner Koch <wk@gnupg.org>
Sat, 7 Sep 2013 08:06:46 +0000 (10:06 +0200)
committerWerner Koch <wk@gnupg.org>
Tue, 17 Sep 2013 13:22:15 +0000 (15:22 +0200)
* mpi/mpicoder.c (twocompl, onecompl): New.
(gcry_mpi_print): Use it for STD and SSH.
(gcry_mpi_scan): Use it for STD and SSH.  Always set NSCANNED.
(gcry_mpi_aprint): Clear the extra allocated byte.
* tests/t-convert.c (showhex, showmpi): New.
(mpi2bitstr_nlz): New.
(check_formats): New.
(main): Call new test.

Signed-off-by: Werner Koch <wk@gnupg.org>
NEWS
doc/gcrypt.texi
mpi/mpicoder.c
tests/t-convert.c

diff --git a/NEWS b/NEWS
index e5ea856..46b5297 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -28,6 +28,9 @@ Noteworthy changes in version 1.6.0 (unreleased)
 
  * Added several MPI helper functions.
 
+ * Added support for negative numbers to gcry_mpi_print,
+   gcry_mpi_aprint and gcry_mpi_scan.
+
  * Interface changes relative to the 1.5.0 release:
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  gcry_ac_*              REMOVED.
index afef7f7..63fd596 100644 (file)
@@ -3826,7 +3826,8 @@ bytes actually scanned unless @var{nscanned} was given as
 
 @table @code
 @item GCRYMPI_FMT_STD
-2-complement stored without a length header.
+2-complement stored without a length header.  Note that
+@code{gcry_mpi_print} stores a @code{0} as a string of zero length.
 
 @item GCRYMPI_FMT_PGP
 As used by OpenPGP (only defined as unsigned). This is basically
@@ -3837,8 +3838,10 @@ As used in the Secure Shell protocol.  This is @code{GCRYMPI_FMT_STD}
 with a 4 byte big endian header.
 
 @item GCRYMPI_FMT_HEX
-Stored as a C style string with each byte of the MPI encoded as 2 hex
-digits.  When using this format, @var{buflen} must be zero.
+Stored as a string with each byte of the MPI encoded as 2 hex digits.
+Negative numbers are prefix with a minus sign and in addition the
+high bit is always zero to make clear that an explicit sign ist used.
+When using this format, @var{buflen} must be zero.
 
 @item GCRYMPI_FMT_USG
 Simple unsigned integer.
@@ -3866,6 +3869,12 @@ Convert the MPI @var{a} into an external representation described by
 address will be stored in the variable @var{buffer} points to.  The
 number of bytes stored in this buffer will be stored in the variable
 @var{nbytes} points to, unless @var{nbytes} is @code{NULL}.
+
+Even if @var{nbytes} is zero, the function allocates at least one byte
+and store a zero there.  Thus with formats @code{GCRYMPI_FMT_STD} and
+@code{GCRYMPI_FMT_USG} the caller may safely set a returned length of
+0 to 1 to represent a zero as a 1 byte string.
+
 @end deftypefun
 
 @deftypefun void gcry_mpi_dump (@w{const gcry_mpi_t @var{a}})
index 19c8de9..07e91c6 100644 (file)
@@ -353,6 +353,65 @@ _gcry_mpi_set_buffer (gcry_mpi_t a, const void *buffer_arg,
 }
 
 
+static void
+onecompl (gcry_mpi_t a)
+{
+  mpi_ptr_t ap;
+  mpi_size_t n;
+  unsigned int i;
+  unsigned int nbits = mpi_get_nbits (a);
+
+  if (mpi_is_immutable (a))
+    {
+      mpi_immutable_failed ();
+      return;
+    }
+
+  mpi_normalize (a);
+  ap = a->d;
+  n = a->nlimbs;
+
+  for( i = 0; i < n; i++ )
+    ap[i] ^= (mpi_limb_t)(-1);
+
+  a->sign = 0;
+  mpi_clear_highbit (a, nbits-1);
+}
+
+
+/* Perform a two's complement operation on buffer P of size N bytes.  */
+static void
+twocompl (unsigned char *p, unsigned int n)
+{
+  int i;
+
+  for (i=n-1; i >= 0 && !p[i]; i--)
+    ;
+  if (i >= 0)
+    {
+      if ((p[i] & 0x01))
+        p[i] = (((p[i] ^ 0xfe) | 0x01) & 0xff);
+      else if ((p[i] & 0x02))
+        p[i] = (((p[i] ^ 0xfc) | 0x02) & 0xfe);
+      else if ((p[i] & 0x04))
+        p[i] = (((p[i] ^ 0xf8) | 0x04) & 0xfc);
+      else if ((p[i] & 0x08))
+        p[i] = (((p[i] ^ 0xf0) | 0x08) & 0xf8);
+      else if ((p[i] & 0x10))
+        p[i] = (((p[i] ^ 0xe0) | 0x10) & 0xf0);
+      else if ((p[i] & 0x20))
+        p[i] = (((p[i] ^ 0xc0) | 0x20) & 0xe0);
+      else if ((p[i] & 0x40))
+        p[i] = (((p[i] ^ 0x80) | 0x40) & 0xc0);
+      else
+        p[i] = 0x80;
+
+      for (i--; i >= 0; i--)
+        p[i] ^= 0xff;
+    }
+}
+
+
 /* Convert the external representation of an integer stored in BUFFER
    with a length of BUFLEN into a newly create MPI returned in
    RET_MPI.  If NBYTES is not NULL, it will receive the number of
@@ -380,15 +439,14 @@ gcry_mpi_scan (struct gcry_mpi **ret_mpi, enum gcry_mpi_format format,
                 : mpi_alloc ((len+BYTES_PER_MPI_LIMB-1)/BYTES_PER_MPI_LIMB);
       if (len)
         {
+          _gcry_mpi_set_buffer (a, s, len, 0);
           a->sign = !!(*s & 0x80);
           if (a->sign)
             {
-              /* FIXME: we have to convert from 2compl to magnitude format */
-              mpi_free (a);
-              return gcry_error (GPG_ERR_INTERNAL);
+              onecompl (a);
+              mpi_add_ui (a, a, 1);
+              a->sign = 1;
            }
-          else
-            _gcry_mpi_set_buffer (a, s, len, 0);
        }
       if (ret_mpi)
         {
@@ -397,6 +455,8 @@ gcry_mpi_scan (struct gcry_mpi **ret_mpi, enum gcry_mpi_format format,
        }
       else
         mpi_free(a);
+      if (nscanned)
+        *nscanned = len;
       return 0;
     }
   else if (format == GCRYMPI_FMT_USG)
@@ -414,6 +474,8 @@ gcry_mpi_scan (struct gcry_mpi **ret_mpi, enum gcry_mpi_format format,
        }
       else
         mpi_free(a);
+      if (nscanned)
+        *nscanned = len;
       return 0;
     }
   else if (format == GCRYMPI_FMT_PGP)
@@ -458,14 +520,13 @@ gcry_mpi_scan (struct gcry_mpi **ret_mpi, enum gcry_mpi_format format,
       if (n)
         {
           a->sign = !!(*s & 0x80);
+          _gcry_mpi_set_buffer( a, s, n, 0 );
           if (a->sign)
             {
-              /* FIXME: we have to convert from 2compl to magnitude format */
-              mpi_free(a);
-              return gcry_error (GPG_ERR_INTERNAL);
+              onecompl (a);
+              mpi_add_ui (a, a, 1);
+              a->sign = 1;
            }
-          else
-            _gcry_mpi_set_buffer( a, s, n, 0 );
        }
       if (nscanned)
         *nscanned = n+4;
@@ -497,6 +558,8 @@ gcry_mpi_scan (struct gcry_mpi **ret_mpi, enum gcry_mpi_format format,
        }
       else
         mpi_free(a);
+      if (nscanned)
+        *nscanned = strlen ((const char*)buffer);
       return 0;
     }
   else
@@ -541,18 +604,25 @@ gcry_mpi_print (enum gcry_mpi_format format,
       int extra = 0;
       unsigned int n;
 
-      if (negative)
-        return gcry_error (GPG_ERR_INTERNAL); /* Can't handle it yet. */
-
       tmp = _gcry_mpi_get_buffer (a, 0, &n, NULL);
       if (!tmp)
         return gpg_error_from_syserror ();
 
-      /* If the high bit of the returned buffer is set, we need to
-         print an extra leading 0x00 so that the output is interpreted
-         as a positive number.  */
-      if (n && (*tmp & 0x80))
+      if (negative)
         {
+          twocompl (tmp, n);
+          if (!(*tmp & 0x80))
+            {
+              /* Need to extend the sign.  */
+              n++;
+              extra = 2;
+            }
+        }
+      else if (n && (*tmp & 0x80))
+        {
+          /* Positive but the high bit of the returned buffer is set.
+             Thus we need to print an extra leading 0x00 so that the
+             output is interpreted as a positive number.  */
           n++;
           extra = 1;
        }
@@ -567,9 +637,11 @@ gcry_mpi_print (enum gcry_mpi_format format,
         {
           unsigned char *s = buffer;
 
-          if (extra)
+          if (extra == 1)
             *s++ = 0;
-          memcpy (s, tmp, n-extra);
+          else if (extra)
+            *s++ = 0xff;
+          memcpy (s, tmp, n-!!extra);
        }
       gcry_free(tmp);
       *nwritten = n;
@@ -632,13 +704,21 @@ gcry_mpi_print (enum gcry_mpi_format format,
       int extra = 0;
       unsigned int n;
 
-      if (negative)
-        return gcry_error (GPG_ERR_INTERNAL); /* Can't handle it yet.  */
-
       tmp = _gcry_mpi_get_buffer (a, 0, &n, NULL);
       if (!tmp)
         return gpg_error_from_syserror ();
-      if (n && (*tmp & 0x80))
+
+      if (negative)
+        {
+          twocompl (tmp, n);
+          if (!(*tmp & 0x80))
+            {
+              /* Need to extend the sign.  */
+              n++;
+              extra = 2;
+            }
+        }
+      else if (n && (*tmp & 0x80))
         {
           n++;
           extra=1;
@@ -658,10 +738,11 @@ gcry_mpi_print (enum gcry_mpi_format format,
           *s++ = n >> 16;
           *s++ = n >> 8;
           *s++ = n;
-          if (extra)
+          if (extra == 1)
             *s++ = 0;
-
-          memcpy (s, tmp, n-extra);
+          else if (extra)
+            *s++ = 0xff;
+          memcpy (s, tmp, n-!!extra);
        }
       gcry_free (tmp);
       *nwritten = 4+n;
@@ -741,6 +822,10 @@ gcry_mpi_aprint (enum gcry_mpi_format format,
   *buffer = mpi_is_secure(a) ? gcry_malloc_secure (n?n:1) : gcry_malloc (n?n:1);
   if (!*buffer)
     return gpg_error_from_syserror ();
+  /* If the returned buffer will have a length of 0, we nevertheless
+     allocated 1 byte (malloc needs it anyway) and store a 0.  */
+  if (!n)
+    **buffer = 0;
   rc = gcry_mpi_print( format, *buffer, n, &n, a );
   if (rc)
     {
index b7f97fa..d44c439 100644 (file)
 
 #include "../src/gcrypt-int.h"
 
-#define PGM "t-mpi-point"
+#define PGM "t-convert"
+
+#define DIM(v)              (sizeof(v)/sizeof((v)[0]))
+#define DIMof(type,member)   DIM(((type *)0)->member)
 
 static const char *wherestr;
 static int verbose;
@@ -55,6 +58,61 @@ show (const char *format, ...)
 }
 
 static void
+showhex (const char *prefix, const void *buffer, size_t buflen)
+{
+  const unsigned char *s;
+
+  if (!verbose)
+    return;
+  fprintf (stderr, "%s: %s ", PGM, prefix);
+  for (s= buffer; buflen; buflen--, s++)
+    fprintf (stderr, "%02x", *s);
+  putc ('\n', stderr);
+}
+
+
+/* Allocate a bit string consisting of '0' and '1' from the MPI A.  Do
+   not return any leading zero bits.  Caller needs to gcry_free the
+   result. */
+static char *
+mpi2bitstr_nlz (gcry_mpi_t a)
+{
+  char *p, *buf;
+  size_t length = gcry_mpi_get_nbits (a);
+
+  if (!length)
+    {
+      buf = p = xmalloc (3);
+      *p++ = ' ';
+      *p++ = '0';
+    }
+  else
+    {
+      buf = p = xmalloc (length + 1 + 1);
+      *p++ = gcry_mpi_is_neg (a)? '-':' ';
+      while (length-- > 1)
+        *p++ = gcry_mpi_test_bit (a, length) ? '1':'0';
+      *p++ = gcry_mpi_test_bit (a, 0) ? '1':'0';
+    }
+  *p = 0;
+  return buf;
+}
+
+
+static void
+showmpi (const char *prefix, gcry_mpi_t a)
+{
+  char *bitstr;
+
+  if (!verbose)
+    return;
+  bitstr = mpi2bitstr_nlz (a);
+  fprintf (stderr, "%s: %s%s\n", PGM, prefix, bitstr);
+  xfree (bitstr);
+}
+
+
+static void
 fail (const char *format, ...)
 {
   va_list arg_ptr;
@@ -95,10 +153,10 @@ negative_zero (void)
   void *bufaddr = &buf;
   struct { const char *name; enum gcry_mpi_format format; } fmts[] =
     {
-      { "STD", GCRYMPI_FMT_STD },
-      { "PGP", GCRYMPI_FMT_PGP },
-      { "SSH", GCRYMPI_FMT_SSH },
-      { "HEX", GCRYMPI_FMT_HEX },
+      /* { "STD", GCRYMPI_FMT_STD }, */
+      /* { "PGP", GCRYMPI_FMT_PGP }, */
+      /* { "SSH", GCRYMPI_FMT_SSH }, */
+      /* { "HEX", GCRYMPI_FMT_HEX }, */
       { "USG", GCRYMPI_FMT_USG },
       { NULL, 0 }
     };
@@ -134,6 +192,311 @@ negative_zero (void)
 }
 
 
+static void
+check_formats (void)
+{
+  static struct {
+    int value;
+    struct {
+      const char *hex;
+      size_t stdlen;
+      const char *std;
+      size_t sshlen;
+      const char *ssh;
+      size_t usglen;
+      const char *usg;
+    } a;
+  } data[] = {
+    {    0, { "00",
+              0, "",
+              4, "\x00\x00\x00\x00",
+              0, "" }
+    },
+    {    1, { "01",
+              1, "\x01",
+              5, "\x00\x00\x00\x01\x01",
+              1, "\x01" }
+    },
+    {    2, { "02",
+              1, "\x02",
+              5, "\x00\x00\x00\x01\x02",
+              1, "\x02",  }
+    },
+    {  127, { "7F",
+              1, "\x7f",
+              5, "\x00\x00\x00\x01\x7f",
+              1, "\x7f"  }
+    },
+    {  128, { "0080",
+              2, "\x00\x80",
+              6, "\x00\x00\x00\x02\x00\x80",
+              1, "\x80" }
+    },
+    {  129, { "0081",
+              2, "\x00\x81",
+              6, "\x00\x00\x00\x02\x00\x81",
+              1, "\x81"  }
+    },
+    {  255, { "00FF",
+              2, "\x00\xff",
+              6, "\x00\x00\x00\x02\x00\xff",
+              1, "\xff" }
+    },
+    {  256, { "0100",
+              2, "\x01\x00",
+              6, "\x00\x00\x00\x02\x01\x00",
+              2, "\x01\x00" }
+    },
+    {  257, { "0101",
+              2, "\x01\x01",
+              6, "\x00\x00\x00\x02\x01\x01",
+              2, "\x01\x01" }
+    },
+    {   -1, { "-01",
+              1, "\xff",
+              5, "\x00\x00\x00\x01\xff",
+              1,"\x01" }
+    },
+    {   -2, { "-02",
+              1, "\xfe",
+              5, "\x00\x00\x00\x01\xfe",
+              1, "\x02" }
+    },
+    { -127, { "-7F",
+              1, "\x81",
+              5, "\x00\x00\x00\x01\x81",
+              1, "\x7f" }
+    },
+    { -128, { "-0080",
+              1, "\x80",
+              5, "\x00\x00\x00\x01\x80",
+              1, "\x80" }
+    },
+    { -129, { "-0081",
+              2, "\xff\x7f",
+              6, "\x00\x00\x00\x02\xff\x7f",
+              1, "\x81" }
+    },
+    { -255, { "-00FF",
+              2, "\xff\x01",
+              6, "\x00\x00\x00\x02\xff\x01",
+              1, "\xff" }
+    },
+    { -256, { "-0100",
+              2, "\xff\x00",
+              6, "\x00\x00\x00\x02\xff\x00",
+              2, "\x01\x00" }
+    },
+    { -257, { "-0101",
+              2, "\xfe\xff",
+              6, "\x00\x00\x00\x02\xfe\xff",
+              2, "\x01\x01" }
+    },
+    {  65535, { "00FFFF",
+                3, "\x00\xff\xff",
+                7, "\x00\x00\x00\x03\x00\xff\xff",
+                2, "\xff\xff" }
+    },
+    {  65536, { "010000",
+                3, "\x01\00\x00",
+                7, "\x00\x00\x00\x03\x01\x00\x00",
+                3, "\x01\x00\x00" }
+    },
+    {  65537, { "010001",
+                3, "\x01\00\x01",
+                7, "\x00\x00\x00\x03\x01\x00\x01",
+                3, "\x01\x00\x01" }
+    },
+    { -65537, { "-010001",
+                3, "\xfe\xff\xff",
+                7, "\x00\x00\x00\x03\xfe\xff\xff",
+                3, "\x01\x00\x01" }
+    },
+    { -65536, { "-010000",
+                3, "\xff\x00\x00",
+                7, "\x00\x00\x00\x03\xff\x00\x00",
+                3, "\x01\x00\x00" }
+    },
+    { -65535, { "-00FFFF",
+                3, "\xff\x00\x01",
+                7, "\x00\x00\x00\x03\xff\x00\x01",
+                2, "\xff\xff" }
+    }
+  };
+  gpg_error_t err;
+  gcry_mpi_t a, b;
+  char *buf;
+  void *bufaddr = &buf;
+  int idx;
+  size_t buflen;
+
+  a = gcry_mpi_new (0);
+  for (idx=0; idx < DIM(data); idx++)
+    {
+      if (debug)
+        show ("print test %d\n", data[idx].value);
+
+      if (data[idx].value < 0)
+        {
+          gcry_mpi_set_ui (a, -data[idx].value);
+          gcry_mpi_neg (a, a);
+        }
+      else
+        gcry_mpi_set_ui (a, data[idx].value);
+
+      err = gcry_mpi_aprint (GCRYMPI_FMT_HEX, bufaddr, NULL, a);
+      if (err)
+        fail ("error printing value %d as %s: %s\n",
+              data[idx].value, "HEX", gpg_strerror (err));
+      else
+        {
+          if (strcmp (buf, data[idx].a.hex))
+            {
+              fail ("error printing value %d as %s: %s\n",
+                    data[idx].value, "HEX", "wrong result");
+              show ("expected: '%s'\n", data[idx].a.hex);
+              show ("     got: '%s'\n", buf);
+            }
+          gcry_free (buf);
+        }
+
+      err = gcry_mpi_aprint (GCRYMPI_FMT_STD, bufaddr, &buflen, a);
+      if (err)
+        fail ("error printing value %d as %s: %s\n",
+              data[idx].value, "STD", gpg_strerror (err));
+      else
+        {
+          if (buflen != data[idx].a.stdlen
+              || memcmp (buf, data[idx].a.std, data[idx].a.stdlen))
+            {
+              fail ("error printing value %d as %s: %s\n",
+                    data[idx].value, "STD", "wrong result");
+              showhex ("expected:", data[idx].a.std, data[idx].a.stdlen);
+              showhex ("     got:", buf, buflen);
+            }
+          gcry_free (buf);
+        }
+
+      err = gcry_mpi_aprint (GCRYMPI_FMT_SSH, bufaddr, &buflen, a);
+      if (err)
+        fail ("error printing value %d as %s: %s\n",
+              data[idx].value, "SSH", gpg_strerror (err));
+      else
+        {
+          if (buflen != data[idx].a.sshlen
+              || memcmp (buf, data[idx].a.ssh, data[idx].a.sshlen))
+            {
+              fail ("error printing value %d as %s: %s\n",
+                    data[idx].value, "SSH", "wrong result");
+              showhex ("expected:", data[idx].a.ssh, data[idx].a.sshlen);
+              showhex ("     got:", buf, buflen);
+            }
+          gcry_free (buf);
+        }
+
+      err = gcry_mpi_aprint (GCRYMPI_FMT_USG, bufaddr, &buflen, a);
+      if (err)
+        fail ("error printing value %d as %s: %s\n",
+              data[idx].value, "USG", gpg_strerror (err));
+      else
+        {
+          if (buflen != data[idx].a.usglen
+              || memcmp (buf, data[idx].a.usg, data[idx].a.usglen))
+            {
+              fail ("error printing value %d as %s: %s\n",
+                    data[idx].value, "USG", "wrong result");
+              showhex ("expected:", data[idx].a.usg, data[idx].a.usglen);
+              showhex ("     got:", buf, buflen);
+            }
+          gcry_free (buf);
+        }
+    }
+
+
+  /* Now for the other direction.  */
+  for (idx=0; idx < DIM(data); idx++)
+    {
+      if (debug)
+        show ("scan test %d\n", data[idx].value);
+
+      if (data[idx].value < 0)
+        {
+          gcry_mpi_set_ui (a, -data[idx].value);
+          gcry_mpi_neg (a, a);
+        }
+      else
+        gcry_mpi_set_ui (a, data[idx].value);
+
+      err = gcry_mpi_scan (&b, GCRYMPI_FMT_HEX, data[idx].a.hex, 0, &buflen);
+      if (err)
+        fail ("error scanning value %d from %s: %s\n",
+              data[idx].value, "HEX", gpg_strerror (err));
+      else
+        {
+          if (gcry_mpi_cmp (a, b))
+            {
+              fail ("error scanning value %d from %s: %s\n",
+                    data[idx].value, "HEX", "wrong result");
+              showmpi ("expected:", a);
+              showmpi ("     got:", b);
+            }
+          gcry_mpi_release (b);
+        }
+
+      err = gcry_mpi_scan (&b, GCRYMPI_FMT_STD,
+                           data[idx].a.std, data[idx].a.stdlen, &buflen);
+      if (err)
+        fail ("error scanning value %d as %s: %s\n",
+              data[idx].value, "STD", gpg_strerror (err));
+      else
+        {
+          if (gcry_mpi_cmp (a, b) || data[idx].a.stdlen != buflen)
+            {
+              fail ("error scanning value %d from %s: %s (%u)\n",
+                    data[idx].value, "STD", "wrong result", buflen);
+              showmpi ("expected:", a);
+              showmpi ("     got:", b);
+            }
+          gcry_mpi_release (b);
+        }
+
+      err = gcry_mpi_aprint (GCRYMPI_FMT_SSH, bufaddr, &buflen, a);
+      if (err)
+        fail ("error printing value %d as %s: %s\n",
+              data[idx].value, "SSH", gpg_strerror (err));
+      else
+        {
+          if (buflen != data[idx].a.sshlen
+              || memcmp (buf, data[idx].a.ssh, data[idx].a.sshlen))
+            {
+              fail ("error printing value %d as %s: %s\n",
+                    data[idx].value, "SSH", "wrong result");
+              showhex ("expected:", data[idx].a.ssh, data[idx].a.sshlen);
+              showhex ("     got:", buf, buflen);
+            }
+          gcry_free (buf);
+        }
+
+      err = gcry_mpi_aprint (GCRYMPI_FMT_USG, bufaddr, &buflen, a);
+      if (err)
+        fail ("error printing value %d as %s: %s\n",
+              data[idx].value, "USG", gpg_strerror (err));
+      else
+        {
+          if (buflen != data[idx].a.usglen
+              || memcmp (buf, data[idx].a.usg, data[idx].a.usglen))
+            {
+              fail ("error printing value %d as %s: %s\n",
+                    data[idx].value, "USG", "wrong result");
+              showhex ("expected:", data[idx].a.usg, data[idx].a.usglen);
+              showhex ("     got:", buf, buflen);
+            }
+          gcry_free (buf);
+        }
+    }
+
+  gcry_mpi_release (a);
+}
 
 
 int
@@ -154,6 +517,7 @@ main (int argc, char **argv)
   gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
 
   negative_zero ();
+  check_formats ();
 
   show ("All tests completed. Errors: %d\n", error_count);
   return error_count ? 1 : 0;