]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
http/3: add description for known server error codes
authorStefan Eissing <stefan@eissing.org>
Wed, 7 Jan 2026 11:12:29 +0000 (12:12 +0100)
committerDaniel Stenberg <daniel@haxx.se>
Wed, 7 Jan 2026 13:30:21 +0000 (14:30 +0100)
When a server resets a stream with an error code, list that code
and its known name in the failure message of the transfer.

Ref: #20195
Closes #20202

lib/vquic/curl_ngtcp2.c
lib/vquic/curl_osslq.c
lib/vquic/curl_quiche.c
lib/vquic/vquic.c
lib/vquic/vquic_int.h

index 97ef98b97165c43001ff13723af1489beef94149..43db734ef78511447f6c5573b7bad25b80d46454 100644 (file)
@@ -1372,7 +1372,9 @@ static CURLcode recv_closed_stream(struct Curl_cfilter *cf,
   (void)cf;
   *pnread = 0;
   if(stream->reset) {
-    failf(data, "HTTP/3 stream %" PRId64 " reset by server", stream->id);
+    failf(data, "HTTP/3 stream %" PRId64 " reset by server (error 0x%" PRIx64
+          " %s)", stream->id, stream->error3,
+          vquic_h3_err_str(stream->error3));
     return data->req.bytecount ? CURLE_PARTIAL_FILE : CURLE_HTTP3;
   }
   else if(!stream->resp_hds_complete) {
index 464879788c547f03b49ea825d956590bc4de20e4..bcf9ab9549d35a125ec50dacd42ff9bf9d270456 100644 (file)
@@ -2076,9 +2076,9 @@ static CURLcode recv_closed_stream(struct Curl_cfilter *cf,
   (void)cf;
   *pnread = 0;
   if(stream->reset) {
-    failf(data,
-          "HTTP/3 stream %" PRId64 " reset by server",
-          stream->s.id);
+    failf(data, "HTTP/3 stream %" PRId64 " reset by server (error 0x%" PRIx64
+          " %s)", stream->s.id, stream->error3,
+          vquic_h3_err_str(stream->error3));
     return data->req.bytecount ? CURLE_PARTIAL_FILE : CURLE_HTTP3;
   }
   else if(!stream->resp_hds_complete) {
index e4140e776d5ec48443ab76367ca9d296149cc0f1..01f1bf5edd23be5a03cc3bba8bb2a572472f5708 100644 (file)
@@ -855,7 +855,9 @@ static CURLcode recv_closed_stream(struct Curl_cfilter *cf,
   DEBUGASSERT(stream);
   *pnread = 0;
   if(stream->reset) {
-    failf(data, "HTTP/3 stream %" PRIu64 " reset by server", stream->id);
+    failf(data, "HTTP/3 stream %" PRId64 " reset by server (error 0x%" PRIx64
+          " %s)", stream->id, stream->error3,
+          vquic_h3_err_str(stream->error3));
     result = data->req.bytecount ? CURLE_PARTIAL_FILE : CURLE_HTTP3;
     CURL_TRC_CF(data, cf, "[%" PRIu64 "] cf_recv, was reset -> %d",
                 stream->id, result);
index d7bfa9910408ec5eda974084786bb3903c07982d..33ba12f476d04979c6ac7860490b80c6ad264077 100644 (file)
@@ -742,6 +742,56 @@ CURLcode Curl_conn_may_http3(struct Curl_easy *data,
   return CURLE_OK;
 }
 
+#ifndef CURL_DISABLE_VERBOSE_STRINGS
+const char *vquic_h3_err_str(uint64_t error_code)
+{
+  if(error_code <= UINT_MAX) {
+    switch((unsigned int)error_code) {
+    case CURL_H3_ERR_NO_ERROR:
+      return "NO_ERROR";
+    case CURL_H3_ERR_GENERAL_PROTOCOL_ERROR:
+      return "GENERAL_PROTOCOL_ERROR";
+    case CURL_H3_ERR_INTERNAL_ERROR:
+      return "INTERNAL_ERROR";
+    case CURL_H3_ERR_STREAM_CREATION_ERROR:
+      return "STREAM_CREATION_ERROR";
+    case CURL_H3_ERR_CLOSED_CRITICAL_STREAM:
+      return "CLOSED_CRITICAL_STREAM";
+    case CURL_H3_ERR_FRAME_UNEXPECTED:
+      return "FRAME_UNEXPECTED";
+    case CURL_H3_ERR_FRAME_ERROR:
+      return "FRAME_ERROR";
+    case CURL_H3_ERR_EXCESSIVE_LOAD:
+      return "EXCESSIVE_LOAD";
+    case CURL_H3_ERR_ID_ERROR:
+      return "ID_ERROR";
+    case CURL_H3_ERR_SETTINGS_ERROR:
+      return "SETTINGS_ERROR";
+    case CURL_H3_ERR_MISSING_SETTINGS:
+      return "MISSING_SETTINGS";
+    case CURL_H3_ERR_REQUEST_REJECTED:
+      return "REQUEST_REJECTED";
+    case CURL_H3_ERR_REQUEST_CANCELLED:
+      return "REQUEST_CANCELLED";
+    case CURL_H3_ERR_REQUEST_INCOMPLETE:
+      return "REQUEST_INCOMPLETE";
+    case CURL_H3_ERR_MESSAGE_ERROR:
+      return "MESSAGE_ERROR";
+    case CURL_H3_ERR_CONNECT_ERROR:
+      return "CONNECT_ERROR";
+    case CURL_H3_ERR_VERSION_FALLBACK:
+      return "VERSION_FALLBACK";
+    default:
+      break;
+    }
+  }
+  /* RFC 9114 ch. 8.1 + 9, reserved future error codes that are NO_ERROR */
+  if((error_code >= 0x21) && !((error_code - 0x21) % 0x1f))
+    return "NO_ERROR";
+  return "unknown";
+}
+#endif /* CURL_DISABLE_VERBOSE_STRINGS */
+
 #if defined(USE_NGTCP2) || defined(USE_NGHTTP3)
 
 static void *vquic_ngtcp2_malloc(size_t size, void *user_data)
index e602b7ee6d1782cd79c79eec5a28f95bef821076..a6d3d0ea5464598ce1db561870bca3c722598f89 100644 (file)
 #define MAX_PKT_BURST         10
 #define MAX_UDP_PAYLOAD_SIZE  1452
 
+/* definitions from RFC 9114, ch 8.1 */
+typedef enum {
+  CURL_H3_ERR_NO_ERROR = 0x0100,
+  CURL_H3_ERR_GENERAL_PROTOCOL_ERROR = 0x0101,
+  CURL_H3_ERR_INTERNAL_ERROR = 0x0102,
+  CURL_H3_ERR_STREAM_CREATION_ERROR = 0x0103,
+  CURL_H3_ERR_CLOSED_CRITICAL_STREAM = 0x0104,
+  CURL_H3_ERR_FRAME_UNEXPECTED = 0x0105,
+  CURL_H3_ERR_FRAME_ERROR = 0x0106,
+  CURL_H3_ERR_EXCESSIVE_LOAD = 0x0107,
+  CURL_H3_ERR_ID_ERROR = 0x0108,
+  CURL_H3_ERR_SETTINGS_ERROR = 0x0109,
+  CURL_H3_ERR_MISSING_SETTINGS = 0x010a,
+  CURL_H3_ERR_REQUEST_REJECTED = 0x010b,
+  CURL_H3_ERR_REQUEST_CANCELLED = 0x010c,
+  CURL_H3_ERR_REQUEST_INCOMPLETE = 0x010d,
+  CURL_H3_ERR_MESSAGE_ERROR = 0x010e,
+  CURL_H3_ERR_CONNECT_ERROR = 0x010f,
+  CURL_H3_ERR_VERSION_FALLBACK = 0x0110,
+} vquic_h3_error;
+
+#ifndef CURL_DISABLE_VERBOSE_STRINGS
+const char *vquic_h3_err_str(uint64_t error_code);
+#else
+#define vquic_h3_err_str(x)   ""
+#endif /* CURL_DISABLE_VERBOSE_STRINGS */
+
 struct cf_quic_ctx {
   curl_socket_t sockfd;               /* connected UDP socket */
   struct sockaddr_storage local_addr; /* address socket is bound to */