Add a function to convert SSL_ERROR_* values to strings.

Unexpected SSL_ERROR_* values usually mean the caller didn't handle an
error case for some opt-in feature, but it still would be handy to
stringify them when logging.

Change-Id: If1c44a180b5c124a51ba61410ba02bd637f3429a
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/37188
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index d86ebf0..1ef9f84 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -560,6 +560,11 @@
 #define SSL_ERROR_HANDOFF 17
 #define SSL_ERROR_HANDBACK 18
 
+// SSL_error_description returns a string representation of |err|, where |err|
+// is one of the |SSL_ERROR_*| constants returned by |SSL_get_error|, or NULL
+// if the value is unrecognized.
+OPENSSL_EXPORT const char *SSL_error_description(int err);
+
 // SSL_set_mtu sets the |ssl|'s MTU in DTLS to |mtu|. It returns one on success
 // and zero on failure.
 OPENSSL_EXPORT int SSL_set_mtu(SSL *ssl, unsigned mtu);
diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc
index 9cf14d5..1186312 100644
--- a/ssl/ssl_lib.cc
+++ b/ssl/ssl_lib.cc
@@ -1400,6 +1400,49 @@
   return SSL_ERROR_SYSCALL;
 }
 
+const char *SSL_error_description(int err) {
+  switch (err) {
+    case SSL_ERROR_NONE:
+      return "NONE";
+    case SSL_ERROR_SSL:
+      return "SSL";
+    case SSL_ERROR_WANT_READ:
+      return "WANT_READ";
+    case SSL_ERROR_WANT_WRITE:
+      return "WANT_WRITE";
+    case SSL_ERROR_WANT_X509_LOOKUP:
+      return "WANT_X509_LOOKUP";
+    case SSL_ERROR_SYSCALL:
+      return "SYSCALL";
+    case SSL_ERROR_ZERO_RETURN:
+      return "ZERO_RETURN";
+    case SSL_ERROR_WANT_CONNECT:
+      return "WANT_CONNECT";
+    case SSL_ERROR_WANT_ACCEPT:
+      return "WANT_ACCEPT";
+    case SSL_ERROR_WANT_CHANNEL_ID_LOOKUP:
+      return "WANT_CHANNEL_ID_LOOKUP";
+    case SSL_ERROR_PENDING_SESSION:
+      return "PENDING_SESSION";
+    case SSL_ERROR_PENDING_CERTIFICATE:
+      return "PENDING_CERTIFICATE";
+    case SSL_ERROR_WANT_PRIVATE_KEY_OPERATION:
+      return "WANT_PRIVATE_KEY_OPERATION";
+    case SSL_ERROR_PENDING_TICKET:
+      return "PENDING_TICKET";
+    case SSL_ERROR_EARLY_DATA_REJECTED:
+      return "EARLY_DATA_REJECTED";
+    case SSL_ERROR_WANT_CERTIFICATE_VERIFY:
+      return "WANT_CERTIFICATE_VERIFY";
+    case SSL_ERROR_HANDOFF:
+      return "HANDOFF";
+    case SSL_ERROR_HANDBACK:
+      return "HANDBACK";
+    default:
+      return nullptr;
+  }
+}
+
 uint32_t SSL_CTX_set_options(SSL_CTX *ctx, uint32_t options) {
   ctx->options |= options;
   return ctx->options;
diff --git a/tool/transport_common.cc b/tool/transport_common.cc
index 8cc63d7..d04cb7d 100644
--- a/tool/transport_common.cc
+++ b/tool/transport_common.cc
@@ -661,7 +661,8 @@
       fprintf(file, "%s: received close_notify\n", msg);
       break;
     default:
-      fprintf(file, "%s: unknown error type (%d)\n", msg, ssl_err);
+      fprintf(file, "%s: unexpected error: %s\n", msg,
+              SSL_error_description(ssl_err));
   }
   ERR_print_errors_fp(file);
 }