Bring string descriptions of errors up to date
diff --git a/QCBOR.xcodeproj/project.pbxproj b/QCBOR.xcodeproj/project.pbxproj
index 6e69fbd..e43bf04 100644
--- a/QCBOR.xcodeproj/project.pbxproj
+++ b/QCBOR.xcodeproj/project.pbxproj
@@ -173,7 +173,7 @@
 		E776E094214AE09700E67947 /* UsefulBuf.h */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 3; lastKnownFileType = sourcecode.c.h; name = UsefulBuf.h; path = inc/UsefulBuf.h; sourceTree = "<group>"; tabWidth = 3; };
 		E776E096214AE0C700E67947 /* cmd_line_main.c */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 3; lastKnownFileType = sourcecode.c.c; path = cmd_line_main.c; sourceTree = "<group>"; tabWidth = 3; };
 		E776E161214EE19C00E67947 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
-		E7864765252CE63100A0C11B /* qcbor_err_to_str.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = qcbor_err_to_str.c; path = src/qcbor_err_to_str.c; sourceTree = "<group>"; };
+		E7864765252CE63100A0C11B /* qcbor_err_to_str.c */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 3; lastKnownFileType = sourcecode.c.c; name = qcbor_err_to_str.c; path = src/qcbor_err_to_str.c; sourceTree = "<group>"; tabWidth = 3; };
 		E78C91DE240C90C100F4CECE /* qcbor_decode.h */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 3; lastKnownFileType = sourcecode.c.h; name = qcbor_decode.h; path = inc/qcbor/qcbor_decode.h; sourceTree = "<group>"; tabWidth = 3; };
 		E78C91DF240C90C100F4CECE /* qcbor_common.h */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 3; lastKnownFileType = sourcecode.c.h; name = qcbor_common.h; path = inc/qcbor/qcbor_common.h; sourceTree = "<group>"; tabWidth = 3; };
 		E78C91E0240C90C100F4CECE /* qcbor_private.h */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 3; lastKnownFileType = sourcecode.c.h; name = qcbor_private.h; path = inc/qcbor/qcbor_private.h; sourceTree = "<group>"; tabWidth = 3; };
diff --git a/inc/qcbor/qcbor_common.h b/inc/qcbor/qcbor_common.h
index 752d149..4fabec7 100644
--- a/inc/qcbor/qcbor_common.h
+++ b/inc/qcbor/qcbor_common.h
@@ -547,6 +547,10 @@
  *
  * @return  NULL-terminated string describing error or "Unidentified
  *          error" if the error is not known.
+ *
+ * This is not thread-safe because it uses a static buffer
+ * for formatting, but this is only a diagnostic and the only
+ * consequence is the wrong description.
  */
 const char *qcbor_err_to_str(QCBORError err);
 
diff --git a/src/qcbor_err_to_str.c b/src/qcbor_err_to_str.c
index 4879f91..1aa1cea 100644
--- a/src/qcbor_err_to_str.c
+++ b/src/qcbor_err_to_str.c
@@ -1,69 +1,91 @@
-/*==============================================================================
- err_to_str.c -- strings names for errors
-
- Copyright (c) 2020, Patrick Uiterwijk. All rights reserved.
- Copyright (c) 2020, Laurence Lundblade.
- Copyright (c) 2021, Arm Limited. All rights reserved.
-
- SPDX-License-Identifier: BSD-3-Clause
-
- See BSD-3-Clause license in README.md
-
- Created on 3/21/20
- =============================================================================*/
+/* ==========================================================================
+ * err_to_str.c -- strings names for errors
+ *
+ * Copyright (c) 2020, Patrick Uiterwijk. All rights reserved.
+ * Copyright (c) 2020,2024, Laurence Lundblade.
+ * Copyright (c) 2021, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * See BSD-3-Clause license in README.md
+ *
+ * Created on 3/21/20
+ * ========================================================================== */
 
 #include "qcbor/qcbor_common.h"
+#include <string.h>
 
-#define _ERR_TO_STR(errpart) case QCBOR_##errpart: return "QCBOR_" #errpart;
+#define ERR_TO_STR_CASE(errpart) case errpart: return #errpart;
 
-const char *qcbor_err_to_str(QCBORError err) {
-    switch (err) {
-    _ERR_TO_STR(SUCCESS)
-    _ERR_TO_STR(ERR_BUFFER_TOO_SMALL)
-    _ERR_TO_STR(ERR_ENCODE_UNSUPPORTED)
-    _ERR_TO_STR(ERR_BUFFER_TOO_LARGE)
-    _ERR_TO_STR(ERR_ARRAY_NESTING_TOO_DEEP)
-    _ERR_TO_STR(ERR_CLOSE_MISMATCH)
-    _ERR_TO_STR(ERR_ARRAY_TOO_LONG)
-    _ERR_TO_STR(ERR_TOO_MANY_CLOSES)
-    _ERR_TO_STR(ERR_ARRAY_OR_MAP_STILL_OPEN)
-    _ERR_TO_STR(ERR_BAD_TYPE_7)
-    _ERR_TO_STR(ERR_EXTRA_BYTES)
-    _ERR_TO_STR(ERR_UNSUPPORTED)
-    _ERR_TO_STR(ERR_ARRAY_OR_MAP_UNCONSUMED)
-    _ERR_TO_STR(ERR_BAD_INT)
-    _ERR_TO_STR(ERR_INDEFINITE_STRING_CHUNK)
-    _ERR_TO_STR(ERR_HIT_END)
-    _ERR_TO_STR(ERR_BAD_BREAK)
-    _ERR_TO_STR(ERR_INPUT_TOO_LARGE)
-    _ERR_TO_STR(ERR_ARRAY_DECODE_NESTING_TOO_DEEP)
-    _ERR_TO_STR(ERR_ARRAY_DECODE_TOO_LONG)
-    _ERR_TO_STR(ERR_STRING_TOO_LONG)
-    _ERR_TO_STR(ERR_BAD_EXP_AND_MANTISSA)
-    _ERR_TO_STR(ERR_NO_STRING_ALLOCATOR)
-    _ERR_TO_STR(ERR_STRING_ALLOCATE)
-    _ERR_TO_STR(ERR_TOO_MANY_TAGS)
-    _ERR_TO_STR(ERR_MAP_LABEL_TYPE)
-    _ERR_TO_STR(ERR_UNEXPECTED_TYPE)
-    _ERR_TO_STR(ERR_BAD_OPT_TAG)
-    _ERR_TO_STR(ERR_DUPLICATE_LABEL)
-    _ERR_TO_STR(ERR_MEM_POOL_SIZE)
-    _ERR_TO_STR(ERR_INT_OVERFLOW)
-    _ERR_TO_STR(ERR_DATE_OVERFLOW)
-    _ERR_TO_STR(ERR_EXIT_MISMATCH)
-    _ERR_TO_STR(ERR_NO_MORE_ITEMS)
-    _ERR_TO_STR(ERR_LABEL_NOT_FOUND)
-    _ERR_TO_STR(ERR_NUMBER_SIGN_CONVERSION)
-    _ERR_TO_STR(ERR_CONVERSION_UNDER_OVER_FLOW)
-    _ERR_TO_STR(ERR_MAP_NOT_ENTERED)
-    _ERR_TO_STR(ERR_CALLBACK_FAIL)
-    _ERR_TO_STR(ERR_FLOAT_DATE_DISABLED)
-    _ERR_TO_STR(ERR_HALF_PRECISION_DISABLED)
-    _ERR_TO_STR(ERR_HW_FLOAT_DISABLED)
-    _ERR_TO_STR(ERR_FLOAT_EXCEPTION)
-    _ERR_TO_STR(ERR_ALL_FLOAT_DISABLED)
+
+const char *
+qcbor_err_to_str(QCBORError uErr) {
+    switch (uErr) {
+    ERR_TO_STR_CASE(QCBOR_SUCCESS)
+    ERR_TO_STR_CASE(QCBOR_ERR_BUFFER_TOO_SMALL)
+    ERR_TO_STR_CASE(QCBOR_ERR_ENCODE_UNSUPPORTED)
+    ERR_TO_STR_CASE(QCBOR_ERR_BUFFER_TOO_LARGE)
+    ERR_TO_STR_CASE(QCBOR_ERR_ARRAY_NESTING_TOO_DEEP)
+    ERR_TO_STR_CASE(QCBOR_ERR_CLOSE_MISMATCH)
+    ERR_TO_STR_CASE(QCBOR_ERR_ARRAY_TOO_LONG)
+    ERR_TO_STR_CASE(QCBOR_ERR_TOO_MANY_CLOSES)
+    ERR_TO_STR_CASE(QCBOR_ERR_ARRAY_OR_MAP_STILL_OPEN)
+    ERR_TO_STR_CASE(QCBOR_ERR_OPEN_BYTE_STRING)
+    ERR_TO_STR_CASE(QCBOR_ERR_CANNOT_CANCEL)
+    ERR_TO_STR_CASE(QCBOR_ERR_BAD_TYPE_7)
+    ERR_TO_STR_CASE(QCBOR_ERR_EXTRA_BYTES)
+    ERR_TO_STR_CASE(QCBOR_ERR_UNSUPPORTED)
+    ERR_TO_STR_CASE(QCBOR_ERR_ARRAY_OR_MAP_UNCONSUMED)
+    ERR_TO_STR_CASE(QCBOR_ERR_BAD_INT)
+    ERR_TO_STR_CASE(QCBOR_ERR_INDEFINITE_STRING_CHUNK)
+    ERR_TO_STR_CASE(QCBOR_ERR_HIT_END)
+    ERR_TO_STR_CASE(QCBOR_ERR_BAD_BREAK)
+    ERR_TO_STR_CASE(QCBOR_ERR_INPUT_TOO_LARGE)
+    ERR_TO_STR_CASE(QCBOR_ERR_ARRAY_DECODE_NESTING_TOO_DEEP)
+    ERR_TO_STR_CASE(QCBOR_ERR_ARRAY_DECODE_TOO_LONG)
+    ERR_TO_STR_CASE(QCBOR_ERR_STRING_TOO_LONG)
+    ERR_TO_STR_CASE(QCBOR_ERR_BAD_EXP_AND_MANTISSA)
+    ERR_TO_STR_CASE(QCBOR_ERR_NO_STRING_ALLOCATOR)
+    ERR_TO_STR_CASE(QCBOR_ERR_STRING_ALLOCATE)
+    ERR_TO_STR_CASE(QCBOR_ERR_MAP_LABEL_TYPE)
+    ERR_TO_STR_CASE(QCBOR_ERR_UNRECOVERABLE_TAG_CONTENT)
+    ERR_TO_STR_CASE(QCBOR_ERR_INDEF_LEN_STRINGS_DISABLED)
+    ERR_TO_STR_CASE(QCBOR_ERR_TAGS_DISABLED)
+    ERR_TO_STR_CASE(QCBOR_ERR_TOO_MANY_TAGS)
+    ERR_TO_STR_CASE(QCBOR_ERR_UNEXPECTED_TYPE)
+    ERR_TO_STR_CASE(QCBOR_ERR_DUPLICATE_LABEL)
+    ERR_TO_STR_CASE(QCBOR_ERR_MEM_POOL_SIZE)
+    ERR_TO_STR_CASE(QCBOR_ERR_INT_OVERFLOW)
+    ERR_TO_STR_CASE(QCBOR_ERR_DATE_OVERFLOW)
+    ERR_TO_STR_CASE(QCBOR_ERR_EXIT_MISMATCH)
+    ERR_TO_STR_CASE(QCBOR_ERR_NO_MORE_ITEMS)
+    ERR_TO_STR_CASE(QCBOR_ERR_LABEL_NOT_FOUND)
+    ERR_TO_STR_CASE(QCBOR_ERR_NUMBER_SIGN_CONVERSION)
+    ERR_TO_STR_CASE(QCBOR_ERR_CONVERSION_UNDER_OVER_FLOW)
+    ERR_TO_STR_CASE(QCBOR_ERR_MAP_NOT_ENTERED)
+    ERR_TO_STR_CASE(QCBOR_ERR_CALLBACK_FAIL)
+    ERR_TO_STR_CASE(QCBOR_ERR_FLOAT_DATE_DISABLED)
+    ERR_TO_STR_CASE(QCBOR_ERR_HALF_PRECISION_DISABLED)
+    ERR_TO_STR_CASE(QCBOR_ERR_HW_FLOAT_DISABLED)
+    ERR_TO_STR_CASE(QCBOR_ERR_FLOAT_EXCEPTION)
+    ERR_TO_STR_CASE(QCBOR_ERR_ALL_FLOAT_DISABLED)
+    ERR_TO_STR_CASE(QCBOR_ERR_RECOVERABLE_BAD_TAG_CONTENT)
+    ERR_TO_STR_CASE(QCBOR_ERR_CANNOT_ENTER_ALLOCATED_STRING)
 
     default:
-        return "Unidentified error";
+        if(uErr >= QCBOR_ERR_FIRST_USER_DEFINED && uErr <= QCBOR_ERR_LAST_USER_DEFINED) {
+            /* Static buffer is not thread safe, but this is only a diagnostic */
+            static char  buf[20];
+            strcpy(buf, "USER_DEFINED_");
+            size_t uEndOffset = strlen(buf);
+            buf[uEndOffset]   = (char)(uErr/100 + '0');
+            buf[uEndOffset+1] = (char)(((uErr/10) % 10) + '0');
+            buf[uEndOffset+2] = (char)((uErr % 10 )+ '0');
+            buf[uEndOffset+3] = '\0';
+            return buf;
+
+        } else {
+            return "Unidentified QCBOR error";
+        }
     }
 }
diff --git a/test/qcbor_decode_tests.c b/test/qcbor_decode_tests.c
index 1a83452..5dce02d 100644
--- a/test/qcbor_decode_tests.c
+++ b/test/qcbor_decode_tests.c
@@ -9240,6 +9240,41 @@
       return -23;
    }
 
+   /* Test error strings */
+   const char *szErrString;
+
+   szErrString = qcbor_err_to_str(QCBOR_ERR_ARRAY_DECODE_TOO_LONG);
+   if(szErrString == NULL) {
+      return -100;
+   }
+   if(strcmp(szErrString, "QCBOR_ERR_ARRAY_DECODE_TOO_LONG")) {
+      return -101;
+   }
+
+   szErrString = qcbor_err_to_str(QCBOR_SUCCESS);
+   if(szErrString == NULL) {
+      return -102;
+   }
+   if(strcmp(szErrString, "QCBOR_SUCCESS")) {
+      return -103;
+   }
+
+   szErrString = qcbor_err_to_str(100);
+   if(szErrString == NULL) {
+      return -104;
+   }
+   if(strcmp(szErrString, "Unidentified QCBOR error")) {
+      return -105;
+   }
+
+   szErrString = qcbor_err_to_str(200);
+   if(szErrString == NULL) {
+      return -106;
+   }
+   if(strcmp(szErrString, "USER_DEFINED_200")) {
+      return -107;
+   }
+
    return 0;
 }