| #include "http2/adapter/nghttp2_callbacks.h" |
| |
| #include <cstdint> |
| #include <cstring> |
| |
| #include "absl/strings/string_view.h" |
| #include "http2/adapter/http2_protocol.h" |
| #include "http2/adapter/http2_visitor_interface.h" |
| #include "http2/adapter/nghttp2_data_provider.h" |
| #include "http2/adapter/nghttp2_util.h" |
| #include "third_party/nghttp2/nghttp2.h" |
| #include "third_party/nghttp2/src/lib/includes/nghttp2/nghttp2.h" |
| #include "common/platform/api/quiche_logging.h" |
| #include "common/quiche_endian.h" |
| |
| namespace http2 { |
| namespace adapter { |
| namespace callbacks { |
| |
| ssize_t OnReadyToSend(nghttp2_session* /* session */, |
| const uint8_t* data, |
| size_t length, |
| int flags, |
| void* user_data) { |
| auto* visitor = static_cast<Http2VisitorInterface*>(user_data); |
| const ssize_t result = visitor->OnReadyToSend(ToStringView(data, length)); |
| if (result < 0) { |
| return NGHTTP2_ERR_CALLBACK_FAILURE; |
| } else if (result == 0) { |
| return NGHTTP2_ERR_WOULDBLOCK; |
| } else { |
| return result; |
| } |
| } |
| |
| int OnBeginFrame(nghttp2_session* /* session */, |
| const nghttp2_frame_hd* header, |
| void* user_data) { |
| auto* visitor = static_cast<Http2VisitorInterface*>(user_data); |
| visitor->OnFrameHeader(header->stream_id, header->length, header->type, |
| header->flags); |
| if (header->type == NGHTTP2_DATA) { |
| visitor->OnBeginDataForStream(header->stream_id, header->length); |
| } |
| return 0; |
| } |
| |
| int OnFrameReceived(nghttp2_session* /* session */, |
| const nghttp2_frame* frame, |
| void* user_data) { |
| auto* visitor = static_cast<Http2VisitorInterface*>(user_data); |
| const Http2StreamId stream_id = frame->hd.stream_id; |
| QUICHE_VLOG(2) << "Frame " << static_cast<int>(frame->hd.type) |
| << " for stream " << stream_id; |
| switch (frame->hd.type) { |
| // The beginning of the DATA frame is handled in OnBeginFrame(), and the |
| // beginning of the header block is handled in client/server-specific |
| // callbacks. This callback handles the point at which the entire logical |
| // frame has been received and processed. |
| case NGHTTP2_DATA: |
| if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { |
| visitor->OnEndStream(stream_id); |
| } |
| break; |
| case NGHTTP2_HEADERS: { |
| if (frame->hd.flags & NGHTTP2_FLAG_END_HEADERS) { |
| visitor->OnEndHeadersForStream(stream_id); |
| } |
| if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { |
| visitor->OnEndStream(stream_id); |
| } |
| break; |
| } |
| case NGHTTP2_PRIORITY: { |
| nghttp2_priority_spec priority_spec = frame->priority.pri_spec; |
| visitor->OnPriorityForStream(stream_id, priority_spec.stream_id, |
| priority_spec.weight, |
| priority_spec.exclusive != 0); |
| break; |
| } |
| case NGHTTP2_RST_STREAM: { |
| visitor->OnRstStream(stream_id, |
| ToHttp2ErrorCode(frame->rst_stream.error_code)); |
| break; |
| } |
| case NGHTTP2_SETTINGS: |
| if (frame->hd.flags & NGHTTP2_FLAG_ACK) { |
| visitor->OnSettingsAck(); |
| } else { |
| visitor->OnSettingsStart(); |
| for (int i = 0; i < frame->settings.niv; ++i) { |
| nghttp2_settings_entry entry = frame->settings.iv[i]; |
| // The nghttp2_settings_entry uses int32_t for the ID; we must cast. |
| visitor->OnSetting(Http2Setting{ |
| .id = static_cast<Http2SettingsId>(entry.settings_id), |
| .value = entry.value}); |
| } |
| visitor->OnSettingsEnd(); |
| } |
| break; |
| case NGHTTP2_PUSH_PROMISE: |
| // This case is handled by headers-related callbacks: |
| // 1. visitor->OnPushPromiseForStream() is invoked in the client-side |
| // OnHeadersStart() adapter callback, as nghttp2 only allows clients |
| // to receive PUSH_PROMISE frames. |
| // 2. visitor->OnHeaderForStream() is invoked for each server push |
| // request header in the PUSH_PROMISE header block. |
| // 3. This switch statement is reached once all server push request |
| // headers have been parsed. |
| break; |
| case NGHTTP2_PING: { |
| Http2PingId ping_id; |
| std::memcpy(&ping_id, frame->ping.opaque_data, sizeof(Http2PingId)); |
| visitor->OnPing(quiche::QuicheEndian::NetToHost64(ping_id), |
| (frame->hd.flags & NGHTTP2_FLAG_ACK) != 0); |
| break; |
| } |
| case NGHTTP2_GOAWAY: { |
| absl::string_view opaque_data( |
| reinterpret_cast<const char*>(frame->goaway.opaque_data), |
| frame->goaway.opaque_data_len); |
| visitor->OnGoAway(frame->goaway.last_stream_id, |
| ToHttp2ErrorCode(frame->goaway.error_code), |
| opaque_data); |
| break; |
| } |
| case NGHTTP2_WINDOW_UPDATE: { |
| visitor->OnWindowUpdate(stream_id, |
| frame->window_update.window_size_increment); |
| break; |
| } |
| case NGHTTP2_CONTINUATION: |
| // This frame type should not be passed to any callbacks, according to |
| // https://nghttp2.org/documentation/enums.html#c.NGHTTP2_CONTINUATION. |
| QUICHE_LOG(ERROR) << "Unexpected receipt of NGHTTP2_CONTINUATION type!"; |
| break; |
| case NGHTTP2_ALTSVC: |
| break; |
| case NGHTTP2_ORIGIN: |
| break; |
| } |
| |
| return 0; |
| } |
| |
| int OnBeginHeaders(nghttp2_session* /* session */, |
| const nghttp2_frame* frame, |
| void* user_data) { |
| auto* visitor = static_cast<Http2VisitorInterface*>(user_data); |
| visitor->OnBeginHeadersForStream(frame->hd.stream_id); |
| return 0; |
| } |
| |
| int OnHeader(nghttp2_session* /* session */, |
| const nghttp2_frame* frame, |
| nghttp2_rcbuf* name, |
| nghttp2_rcbuf* value, |
| uint8_t flags, |
| void* user_data) { |
| auto* visitor = static_cast<Http2VisitorInterface*>(user_data); |
| visitor->OnHeaderForStream(frame->hd.stream_id, ToStringView(name), |
| ToStringView(value)); |
| return 0; |
| } |
| |
| int OnDataChunk(nghttp2_session* /* session */, |
| uint8_t flags, |
| Http2StreamId stream_id, |
| const uint8_t* data, |
| size_t len, |
| void* user_data) { |
| auto* visitor = static_cast<Http2VisitorInterface*>(user_data); |
| visitor->OnDataForStream( |
| stream_id, absl::string_view(reinterpret_cast<const char*>(data), len)); |
| return 0; |
| } |
| |
| int OnStreamClosed(nghttp2_session* /* session */, |
| Http2StreamId stream_id, |
| uint32_t error_code, |
| void* user_data) { |
| auto* visitor = static_cast<Http2VisitorInterface*>(user_data); |
| visitor->OnCloseStream(stream_id, ToHttp2ErrorCode(error_code)); |
| return 0; |
| } |
| |
| ssize_t OnReadyToReadDataForStream(nghttp2_session* /* session */, |
| Http2StreamId stream_id, |
| uint8_t* dest_buffer, |
| size_t max_length, |
| uint32_t* data_flags, |
| nghttp2_data_source* source, |
| void* user_data) { |
| auto* visitor = static_cast<Http2VisitorInterface*>(source->ptr); |
| ssize_t bytes_to_send = 0; |
| bool end_stream = false; |
| visitor->OnReadyToSendDataForStream(stream_id, |
| reinterpret_cast<char*>(dest_buffer), |
| max_length, &bytes_to_send, &end_stream); |
| if (bytes_to_send >= 0 && end_stream) { |
| *data_flags |= NGHTTP2_DATA_FLAG_EOF; |
| } |
| return bytes_to_send; |
| } |
| |
| nghttp2_session_callbacks_unique_ptr Create() { |
| nghttp2_session_callbacks* callbacks; |
| nghttp2_session_callbacks_new(&callbacks); |
| |
| nghttp2_session_callbacks_set_send_callback(callbacks, &OnReadyToSend); |
| nghttp2_session_callbacks_set_on_begin_frame_callback(callbacks, |
| &OnBeginFrame); |
| nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, |
| &OnFrameReceived); |
| nghttp2_session_callbacks_set_on_begin_headers_callback(callbacks, |
| &OnBeginHeaders); |
| nghttp2_session_callbacks_set_on_header_callback2(callbacks, &OnHeader); |
| nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, |
| &OnDataChunk); |
| nghttp2_session_callbacks_set_on_stream_close_callback(callbacks, |
| &OnStreamClosed); |
| nghttp2_session_callbacks_set_send_data_callback( |
| callbacks, &DataFrameSourceSendCallback); |
| return MakeCallbacksPtr(callbacks); |
| } |
| |
| } // namespace callbacks |
| } // namespace adapter |
| } // namespace http2 |