diff --git a/wish/cpp/CMakeLists.txt b/wish/cpp/CMakeLists.txt index 066c9ca..8062985 100644 --- a/wish/cpp/CMakeLists.txt +++ b/wish/cpp/CMakeLists.txt @@ -1,3 +1,17 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + cmake_minimum_required(VERSION 3.14) project(wish_cpp) diff --git a/wish/cpp/benchmark/CMakeLists.txt b/wish/cpp/benchmark/CMakeLists.txt index a5dfea4..9ece969 100644 --- a/wish/cpp/benchmark/CMakeLists.txt +++ b/wish/cpp/benchmark/CMakeLists.txt @@ -1,3 +1,17 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + add_executable(plain_benchmark_client plain_client.cc ) diff --git a/wish/cpp/benchmark/benchmark.py b/wish/cpp/benchmark/benchmark.py index ef0b803..d51c25c 100644 --- a/wish/cpp/benchmark/benchmark.py +++ b/wish/cpp/benchmark/benchmark.py @@ -1,3 +1,17 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + import argparse import shlex import subprocess diff --git a/wish/cpp/benchmark/high_qps_client.cc b/wish/cpp/benchmark/high_qps_client.cc index be4b6cc..692f317 100644 --- a/wish/cpp/benchmark/high_qps_client.cc +++ b/wish/cpp/benchmark/high_qps_client.cc @@ -1,3 +1,17 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include #include #include diff --git a/wish/cpp/benchmark/plain_client.cc b/wish/cpp/benchmark/plain_client.cc index 4f9e041..e0765ce 100644 --- a/wish/cpp/benchmark/plain_client.cc +++ b/wish/cpp/benchmark/plain_client.cc @@ -1,3 +1,17 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include #include #include diff --git a/wish/cpp/benchmark/tls_client.cc b/wish/cpp/benchmark/tls_client.cc index 8c6aef3..58c79c2 100644 --- a/wish/cpp/benchmark/tls_client.cc +++ b/wish/cpp/benchmark/tls_client.cc @@ -1,3 +1,17 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include #include #include diff --git a/wish/cpp/deployments/benchmark.yaml b/wish/cpp/deployments/benchmark.yaml index 6b8414e..3f9ce72 100644 --- a/wish/cpp/deployments/benchmark.yaml +++ b/wish/cpp/deployments/benchmark.yaml @@ -1,3 +1,17 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + apiVersion: batch/v1 kind: Job metadata: diff --git a/wish/cpp/deployments/echo-server.yaml b/wish/cpp/deployments/echo-server.yaml index 5a19ea7..bce8830 100644 --- a/wish/cpp/deployments/echo-server.yaml +++ b/wish/cpp/deployments/echo-server.yaml @@ -1,3 +1,17 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + apiVersion: apps/v1 kind: Deployment metadata: diff --git a/wish/cpp/deployments/stress.yaml b/wish/cpp/deployments/stress.yaml index f9c740e..4dc1625 100644 --- a/wish/cpp/deployments/stress.yaml +++ b/wish/cpp/deployments/stress.yaml @@ -1,3 +1,17 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + apiVersion: batch/v1 kind: Job metadata: diff --git a/wish/cpp/deployments/tls-echo-server.yaml b/wish/cpp/deployments/tls-echo-server.yaml index 249f869..7dc3e3e 100644 --- a/wish/cpp/deployments/tls-echo-server.yaml +++ b/wish/cpp/deployments/tls-echo-server.yaml @@ -1,3 +1,17 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + apiVersion: apps/v1 kind: Deployment metadata: diff --git a/wish/cpp/examples/CMakeLists.txt b/wish/cpp/examples/CMakeLists.txt index 9e9aeec..5dafa98 100644 --- a/wish/cpp/examples/CMakeLists.txt +++ b/wish/cpp/examples/CMakeLists.txt @@ -1,3 +1,17 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + add_executable(plain_echo_server plain_echo_server.cc ) diff --git a/wish/cpp/examples/h2_echo_server.cc b/wish/cpp/examples/h2_echo_server.cc index de9bdd8..5a5bf02 100644 --- a/wish/cpp/examples/h2_echo_server.cc +++ b/wish/cpp/examples/h2_echo_server.cc @@ -1,3 +1,17 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include #include #include @@ -14,56 +28,69 @@ int main(int argc, char** argv) { const int port = absl::GetFlag(FLAGS_port); - H2Server server(port); - - if (!server.Init()) { - LOG(ERROR) << "Init() failed"; + event_base* base = event_base_new(); + if (!base) { + LOG(ERROR) << "Failed to create event_base"; return 1; } - server.SetOnStream([](WebStream* stream) { - LOG(INFO) << "OnStream"; - - stream->SetOnMessage([stream](uint8_t opcode, const std::string& msg) { - std::string type; - switch (opcode) { - case WEB_STREAM_OPCODE_TEXT: - type = "TEXT"; - break; - case WEB_STREAM_OPCODE_BINARY: - type = "BINARY"; - break; - case WEB_STREAM_OPCODE_METADATA: - type = "METADATA"; - break; - default: - type = "UNKNOWN(" + std::to_string(opcode) + ")"; - break; - } - - LOG(INFO) << "OnMessage (opcode: " << type << ", message: " << msg << ")"; - - // Echo back - if (opcode == WEB_STREAM_OPCODE_TEXT) { - stream->SendText(msg); - } else if (opcode == WEB_STREAM_OPCODE_BINARY) { - stream->SendBinary(msg); - } else if (opcode == WEB_STREAM_OPCODE_METADATA) { - stream->SendMetadata(msg); - } else { - LOG(WARNING) << "Unknown opcode, cannot echo."; - } - }); + { + H2Server server(base, port); + + if (!server.Init()) { + LOG(ERROR) << "Init() failed"; + + event_base_free(base); + + return 1; + } - stream->SetOnClose([stream]() { - LOG(INFO) << "OnClose"; + server.SetOnStream([](WebStream* stream) { + LOG(INFO) << "OnStream"; - stream->Close(); + stream->SetOnMessage([stream](uint8_t opcode, const std::string& msg) { + std::string type; + switch (opcode) { + case WEB_STREAM_OPCODE_TEXT: + type = "TEXT"; + break; + case WEB_STREAM_OPCODE_BINARY: + type = "BINARY"; + break; + case WEB_STREAM_OPCODE_METADATA: + type = "METADATA"; + break; + default: + type = "UNKNOWN(" + std::to_string(opcode) + ")"; + break; + } + + LOG(INFO) << "OnMessage (opcode: " << type << ", message: " << msg << ")"; + + // Echo back + if (opcode == WEB_STREAM_OPCODE_TEXT) { + stream->SendText(msg); + } else if (opcode == WEB_STREAM_OPCODE_BINARY) { + stream->SendBinary(msg); + } else if (opcode == WEB_STREAM_OPCODE_METADATA) { + stream->SendMetadata(msg); + } else { + LOG(WARNING) << "Unknown opcode, cannot echo."; + } + }); + + stream->SetOnClose([stream]() { + LOG(INFO) << "OnClose"; + + stream->Close(); + }); }); - }); - server.Run(); + server.Run(); + } + + event_base_free(base); return 0; } diff --git a/wish/cpp/examples/h2_hello_client.cc b/wish/cpp/examples/h2_hello_client.cc index d57e032..33aa183 100644 --- a/wish/cpp/examples/h2_hello_client.cc +++ b/wish/cpp/examples/h2_hello_client.cc @@ -1,3 +1,17 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include #include #include @@ -18,48 +32,63 @@ int main(int argc, char** argv) { const std::string host = absl::GetFlag(FLAGS_host); const int port = absl::GetFlag(FLAGS_port); - H2Client client(host, port); - - if (!client.Init()) { - LOG(INFO) << "Init() failed"; + event_base* base = event_base_new(); + if (!base) { + LOG(ERROR) << "Failed to create event_base"; return 1; } - client.SetOnOpen([](WebStream* stream) { - LOG(INFO) << "OnOpen"; - - stream->SetOnMessage([](uint8_t opcode, const std::string& msg) { - std::string type; - switch (opcode) { - case WEB_STREAM_OPCODE_TEXT: - type = "TEXT"; - break; - case WEB_STREAM_OPCODE_BINARY: - type = "BINARY"; - break; - case WEB_STREAM_OPCODE_METADATA: - type = "METADATA"; - break; - default: - type = "UNKNOWN(" + std::to_string(opcode) + ")"; - break; - } - - LOG(INFO) << "Message (opcode: " << type << ", message: " << msg << ")"; - }); + { + H2Client client(base, host, port); + + if (!client.Init()) { + LOG(INFO) << "Init() failed"; + + event_base_free(base); + + return 1; + } - stream->SetOnClose([]() { - LOG(INFO) << "OnClose"; + client.SetOnOpen([&client](WebStream* stream) { + LOG(INFO) << "OnOpen"; + + stream->SetOnMessage([](uint8_t opcode, const std::string& msg) { + std::string type; + switch (opcode) { + case WEB_STREAM_OPCODE_TEXT: + type = "TEXT"; + break; + case WEB_STREAM_OPCODE_BINARY: + type = "BINARY"; + break; + case WEB_STREAM_OPCODE_METADATA: + type = "METADATA"; + break; + default: + type = "UNKNOWN(" + std::to_string(opcode) + ")"; + break; + } + + LOG(INFO) << "Message (opcode: " << type << ", message: " << msg << ")"; + }); + + stream->SetOnClose([&client]() { + LOG(INFO) << "OnClose"; + + client.Stop(); + }); + + stream->SendText("Hello web-stream text over HTTP/2!"); + stream->SendBinary("Hello web-stream binary over HTTP/2!"); + stream->SendMetadata("Hello web-stream metadata over HTTP/2!"); + stream->Close(); }); - stream->SendText("Hello web-stream text over HTTP/2!"); - stream->SendBinary("Hello web-stream binary over HTTP/2!"); - stream->SendMetadata("Hello web-stream metadata over HTTP/2!"); - stream->Close(); - }); + client.Run(); + } - client.Run(); + event_base_free(base); return 0; } diff --git a/wish/cpp/examples/h2_tls_echo_server.cc b/wish/cpp/examples/h2_tls_echo_server.cc index bd96ed7..8176d34 100644 --- a/wish/cpp/examples/h2_tls_echo_server.cc +++ b/wish/cpp/examples/h2_tls_echo_server.cc @@ -1,3 +1,17 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include #include #include @@ -31,56 +45,69 @@ int main(int argc, char** argv) { const std::string server_cert = absl::GetFlag(FLAGS_server_cert); const std::string server_key = absl::GetFlag(FLAGS_server_key); - H2TlsServer server(ca_cert, server_cert, server_key, port); - - if (!server.Init()) { - LOG(ERROR) << "Init() failed"; + event_base* base = event_base_new(); + if (!base) { + LOG(ERROR) << "Failed to create event_base"; return 1; } - server.SetOnStream([](WebStream* stream) { - LOG(INFO) << "OnStream"; - - stream->SetOnMessage([stream](uint8_t opcode, const std::string& msg) { - std::string type; - switch (opcode) { - case WEB_STREAM_OPCODE_TEXT: - type = "TEXT"; - break; - case WEB_STREAM_OPCODE_BINARY: - type = "BINARY"; - break; - case WEB_STREAM_OPCODE_METADATA: - type = "METADATA"; - break; - default: - type = "UNKNOWN(" + std::to_string(opcode) + ")"; - break; - } - - LOG(INFO) << "OnMessage (opcode: " << type << ", message: " << msg << ")"; - - // Echo back - if (opcode == WEB_STREAM_OPCODE_TEXT) { - stream->SendText(msg); - } else if (opcode == WEB_STREAM_OPCODE_BINARY) { - stream->SendBinary(msg); - } else if (opcode == WEB_STREAM_OPCODE_METADATA) { - stream->SendMetadata(msg); - } else { - LOG(WARNING) << "Unknown opcode, cannot echo."; - } + { + H2TlsServer server(base, ca_cert, server_cert, server_key, port); + + if (!server.Init()) { + LOG(ERROR) << "Init() failed"; + + event_base_free(base); + + return 1; + } + + server.SetOnStream([](WebStream* stream) { + LOG(INFO) << "OnStream"; + + stream->SetOnMessage([stream](uint8_t opcode, const std::string& msg) { + std::string type; + switch (opcode) { + case WEB_STREAM_OPCODE_TEXT: + type = "TEXT"; + break; + case WEB_STREAM_OPCODE_BINARY: + type = "BINARY"; + break; + case WEB_STREAM_OPCODE_METADATA: + type = "METADATA"; + break; + default: + type = "UNKNOWN(" + std::to_string(opcode) + ")"; + break; + } + + LOG(INFO) << "OnMessage (opcode: " << type << ", message: " << msg << ")"; + + // Echo back + if (opcode == WEB_STREAM_OPCODE_TEXT) { + stream->SendText(msg); + } else if (opcode == WEB_STREAM_OPCODE_BINARY) { + stream->SendBinary(msg); + } else if (opcode == WEB_STREAM_OPCODE_METADATA) { + stream->SendMetadata(msg); + } else { + LOG(WARNING) << "Unknown opcode, cannot echo."; + } + }); + + stream->SetOnClose([stream]() { + LOG(INFO) << "OnClose"; + + stream->Close(); + }); }); - stream->SetOnClose([stream]() { - LOG(INFO) << "OnClose"; - - stream->Close(); - }); - }); + server.Run(); + } - server.Run(); + event_base_free(base); return 0; } diff --git a/wish/cpp/examples/h2_tls_hello_client.cc b/wish/cpp/examples/h2_tls_hello_client.cc index 26cbe10..13602a6 100644 --- a/wish/cpp/examples/h2_tls_hello_client.cc +++ b/wish/cpp/examples/h2_tls_hello_client.cc @@ -1,3 +1,17 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include #include #include @@ -35,52 +49,68 @@ int main(int argc, char** argv) { const std::string client_cert = absl::GetFlag(FLAGS_client_cert); const std::string client_key = absl::GetFlag(FLAGS_client_key); - H2TlsClient client(host, - port, - ca_cert, - client_cert, - client_key); - - if (!client.Init()) { - LOG(INFO) << "Init() failed"; + event_base* base = event_base_new(); + if (!base) { + LOG(ERROR) << "Failed to create event_base"; return 1; } - client.SetOnOpen([](WebStream* stream) { - LOG(INFO) << "OnOpen"; - - stream->SetOnMessage([](uint8_t opcode, const std::string& msg) { - std::string type; - switch (opcode) { - case WEB_STREAM_OPCODE_TEXT: - type = "TEXT"; - break; - case WEB_STREAM_OPCODE_BINARY: - type = "BINARY"; - break; - case WEB_STREAM_OPCODE_METADATA: - type = "METADATA"; - break; - default: - type = "UNKNOWN(" + std::to_string(opcode) + ")"; - break; - } - - LOG(INFO) << "Message (opcode: " << type << ", message: " << msg << ")"; + { + H2TlsClient client(base, + host, + port, + ca_cert, + client_cert, + client_key); + + if (!client.Init()) { + LOG(INFO) << "Init() failed"; + + event_base_free(base); + + return 1; + } + + client.SetOnOpen([&client](WebStream* stream) { + LOG(INFO) << "OnOpen"; + + stream->SetOnMessage([](uint8_t opcode, const std::string& msg) { + std::string type; + switch (opcode) { + case WEB_STREAM_OPCODE_TEXT: + type = "TEXT"; + break; + case WEB_STREAM_OPCODE_BINARY: + type = "BINARY"; + break; + case WEB_STREAM_OPCODE_METADATA: + type = "METADATA"; + break; + default: + type = "UNKNOWN(" + std::to_string(opcode) + ")"; + break; + } + + LOG(INFO) << "Message (opcode: " << type << ", message: " << msg << ")"; + }); + + stream->SetOnClose([&client]() { + LOG(INFO) << "OnClose"; + + client.Stop(); + }); + + stream->SendText("Hello web-stream text over HTTP/2+TLS!"); + stream->SendBinary("Hello web-stream binary over HTTP/2+TLS!"); + stream->SendMetadata("Hello web-stream metadata over HTTP/2+TLS!"); + stream->Close(); }); - stream->SetOnClose([]() { - LOG(INFO) << "OnClose"; - }); - - stream->SendText("Hello web-stream text over HTTP/2+TLS!"); - stream->SendBinary("Hello web-stream binary over HTTP/2+TLS!"); - stream->SendMetadata("Hello web-stream metadata over HTTP/2+TLS!"); - stream->Close(); - }); + client.Run(); + } - client.Run(); + event_base_free(base); return 0; } diff --git a/wish/cpp/examples/plain_echo_server.cc b/wish/cpp/examples/plain_echo_server.cc index 719b8d7..e9b889f 100644 --- a/wish/cpp/examples/plain_echo_server.cc +++ b/wish/cpp/examples/plain_echo_server.cc @@ -1,3 +1,17 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include #include #include @@ -16,56 +30,69 @@ int main(int argc, char** argv) { const int port = absl::GetFlag(FLAGS_port); - PlainServer server(port); - - if (!server.Init()) { - LOG(ERROR) << "Init() failed"; + event_base* base = event_base_new(); + if (!base) { + LOG(ERROR) << "Failed to create event_base"; return 1; } - server.SetOnStream([](WebStream* stream) { - LOG(INFO) << "OnStream"; - - stream->SetOnMessage([stream](uint8_t opcode, const std::string& msg) { - std::string type; - switch (opcode) { - case WEB_STREAM_OPCODE_TEXT: - type = "TEXT"; - break; - case WEB_STREAM_OPCODE_BINARY: - type = "BINARY"; - break; - case WEB_STREAM_OPCODE_METADATA: - type = "METADATA"; - break; - default: - type = "UNKNOWN(" + std::to_string(opcode) + ")"; - break; - } - - LOG(INFO) << "OnMessage (opcode: " << type << ", message: " << msg << ")"; - - // Echo back - if (opcode == WEB_STREAM_OPCODE_TEXT) { - stream->SendText(msg); - } else if (opcode == WEB_STREAM_OPCODE_BINARY) { - stream->SendBinary(msg); - } else if (opcode == WEB_STREAM_OPCODE_METADATA) { - stream->SendMetadata(msg); - } else { - LOG(WARNING) << "Unknown opcode, cannot echo."; - } + { + PlainServer server(base, port); + + if (!server.Init()) { + LOG(ERROR) << "Init() failed"; + + event_base_free(base); + + return 1; + } + + server.SetOnStream([](WebStream* stream) { + LOG(INFO) << "OnStream"; + + stream->SetOnMessage([stream](uint8_t opcode, const std::string& msg) { + std::string type; + switch (opcode) { + case WEB_STREAM_OPCODE_TEXT: + type = "TEXT"; + break; + case WEB_STREAM_OPCODE_BINARY: + type = "BINARY"; + break; + case WEB_STREAM_OPCODE_METADATA: + type = "METADATA"; + break; + default: + type = "UNKNOWN(" + std::to_string(opcode) + ")"; + break; + } + + LOG(INFO) << "OnMessage (opcode: " << type << ", message: " << msg << ")"; + + // Echo back + if (opcode == WEB_STREAM_OPCODE_TEXT) { + stream->SendText(msg); + } else if (opcode == WEB_STREAM_OPCODE_BINARY) { + stream->SendBinary(msg); + } else if (opcode == WEB_STREAM_OPCODE_METADATA) { + stream->SendMetadata(msg); + } else { + LOG(WARNING) << "Unknown opcode, cannot echo."; + } + }); + + stream->SetOnClose([stream]() { + LOG(INFO) << "OnClose"; + + stream->Close(); + }); }); - stream->SetOnClose([stream]() { - LOG(INFO) << "OnClose"; - - stream->Close(); - }); - }); + server.Run(); + } - server.Run(); + event_base_free(base); return 0; } diff --git a/wish/cpp/examples/plain_hello_client.cc b/wish/cpp/examples/plain_hello_client.cc index 747cd98..b005b9e 100644 --- a/wish/cpp/examples/plain_hello_client.cc +++ b/wish/cpp/examples/plain_hello_client.cc @@ -1,3 +1,17 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include #include #include @@ -18,48 +32,63 @@ int main(int argc, char** argv) { const std::string host = absl::GetFlag(FLAGS_host); const int port = absl::GetFlag(FLAGS_port); - PlainClient client(host, port); - - if (!client.Init()) { - LOG(INFO) << "Init() failed"; + event_base* base = event_base_new(); + if (!base) { + LOG(ERROR) << "Failed to create event_base"; return 1; } - client.SetOnOpen([](WebStream* stream) { - LOG(INFO) << "OnOpen"; - - stream->SetOnMessage([](uint8_t opcode, const std::string& msg) { - std::string type; - switch (opcode) { - case WEB_STREAM_OPCODE_TEXT: - type = "TEXT"; - break; - case WEB_STREAM_OPCODE_BINARY: - type = "BINARY"; - break; - case WEB_STREAM_OPCODE_METADATA: - type = "METADATA"; - break; - default: - type = "UNKNOWN(" + std::to_string(opcode) + ")"; - break; - } - - LOG(INFO) << "OnMessage (opcode: " << type << ", message: " << msg << ")"; - }); + { + PlainClient client(base, host, port); + + if (!client.Init()) { + LOG(INFO) << "Init() failed"; + + event_base_free(base); + + return 1; + } - stream->SetOnClose([]() { - LOG(INFO) << "OnClose"; + client.SetOnOpen([&client](WebStream* stream) { + LOG(INFO) << "OnOpen"; + + stream->SetOnMessage([](uint8_t opcode, const std::string& msg) { + std::string type; + switch (opcode) { + case WEB_STREAM_OPCODE_TEXT: + type = "TEXT"; + break; + case WEB_STREAM_OPCODE_BINARY: + type = "BINARY"; + break; + case WEB_STREAM_OPCODE_METADATA: + type = "METADATA"; + break; + default: + type = "UNKNOWN(" + std::to_string(opcode) + ")"; + break; + } + + LOG(INFO) << "OnMessage (opcode: " << type << ", message: " << msg << ")"; + }); + + stream->SetOnClose([&client]() { + LOG(INFO) << "OnClose"; + + client.Stop(); + }); + + stream->SendText("Hello web-stream text!"); + stream->SendBinary("Hello web-stream binary!"); + stream->SendMetadata("Hello web-stream metadata!"); + stream->Close(); }); - stream->SendText("Hello web-stream text!"); - stream->SendBinary("Hello web-stream binary!"); - stream->SendMetadata("Hello web-stream metadata!"); - stream->Close(); - }); + client.Run(); + } - client.Run(); + event_base_free(base); return 0; } diff --git a/wish/cpp/examples/tls_echo_server.cc b/wish/cpp/examples/tls_echo_server.cc index 8129184..1079d61 100644 --- a/wish/cpp/examples/tls_echo_server.cc +++ b/wish/cpp/examples/tls_echo_server.cc @@ -1,3 +1,17 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include #include #include @@ -33,59 +47,73 @@ int main(int argc, char** argv) { const std::string server_cert = absl::GetFlag(FLAGS_server_cert); const std::string server_key = absl::GetFlag(FLAGS_server_key); - TlsServer server(port, - ca_cert, - server_cert, - server_key); - - if (!server.Init()) { - LOG(ERROR) << "Init() failed"; + event_base* base = event_base_new(); + if (!base) { + LOG(ERROR) << "Failed to create event_base"; return 1; } - server.SetOnStream([](WebStream* stream) { - LOG(INFO) << "OnStream"; - - stream->SetOnMessage([stream](uint8_t opcode, const std::string& msg) { - std::string type; - switch (opcode) { - case WEB_STREAM_OPCODE_TEXT: - type = "TEXT"; - break; - case WEB_STREAM_OPCODE_BINARY: - type = "BINARY"; - break; - case WEB_STREAM_OPCODE_METADATA: - type = "METADATA"; - break; - default: - type = "UNKNOWN(" + std::to_string(opcode) + ")"; - break; - } - - LOG(INFO) << "OnMessage (opcode: " << type << ", message: " << msg << ")"; - - // Echo back - if (opcode == WEB_STREAM_OPCODE_TEXT) { - stream->SendText(msg); - } else if (opcode == WEB_STREAM_OPCODE_BINARY) { - stream->SendBinary(msg); - } else if (opcode == WEB_STREAM_OPCODE_METADATA) { - stream->SendMetadata(msg); - } else { - LOG(WARNING) << "Unknown opcode, cannot echo."; - } + { + TlsServer server(base, + port, + ca_cert, + server_cert, + server_key); + + if (!server.Init()) { + LOG(ERROR) << "Init() failed"; + + event_base_free(base); + + return 1; + } + + server.SetOnStream([](WebStream* stream) { + LOG(INFO) << "OnStream"; + + stream->SetOnMessage([stream](uint8_t opcode, const std::string& msg) { + std::string type; + switch (opcode) { + case WEB_STREAM_OPCODE_TEXT: + type = "TEXT"; + break; + case WEB_STREAM_OPCODE_BINARY: + type = "BINARY"; + break; + case WEB_STREAM_OPCODE_METADATA: + type = "METADATA"; + break; + default: + type = "UNKNOWN(" + std::to_string(opcode) + ")"; + break; + } + + LOG(INFO) << "OnMessage (opcode: " << type << ", message: " << msg << ")"; + + // Echo back + if (opcode == WEB_STREAM_OPCODE_TEXT) { + stream->SendText(msg); + } else if (opcode == WEB_STREAM_OPCODE_BINARY) { + stream->SendBinary(msg); + } else if (opcode == WEB_STREAM_OPCODE_METADATA) { + stream->SendMetadata(msg); + } else { + LOG(WARNING) << "Unknown opcode, cannot echo."; + } + }); + + stream->SetOnClose([stream]() { + LOG(INFO) << "OnClose"; + + stream->Close(); + }); }); - stream->SetOnClose([stream]() { - LOG(INFO) << "OnClose"; - - stream->Close(); - }); - }); + server.Run(); + } - server.Run(); + event_base_free(base); return 0; } diff --git a/wish/cpp/examples/tls_hello_client.cc b/wish/cpp/examples/tls_hello_client.cc index 7d28fbe..2f33dca 100644 --- a/wish/cpp/examples/tls_hello_client.cc +++ b/wish/cpp/examples/tls_hello_client.cc @@ -1,3 +1,17 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include #include #include @@ -35,52 +49,68 @@ int main(int argc, char** argv) { const std::string client_cert = absl::GetFlag(FLAGS_client_cert); const std::string client_key = absl::GetFlag(FLAGS_client_key); - TlsClient client(host, - port, - ca_cert, - client_cert, - client_key); - - if (!client.Init()) { - LOG(INFO) << "Init() failed"; + event_base* base = event_base_new(); + if (!base) { + LOG(ERROR) << "Failed to create event_base"; return 1; } - client.SetOnOpen([](WebStream* stream) { - LOG(INFO) << "OnOpen"; - - stream->SetOnMessage([](uint8_t opcode, const std::string& msg) { - std::string type; - switch (opcode) { - case WEB_STREAM_OPCODE_TEXT: - type = "TEXT"; - break; - case WEB_STREAM_OPCODE_BINARY: - type = "BINARY"; - break; - case WEB_STREAM_OPCODE_METADATA: - type = "METADATA"; - break; - default: - type = "UNKNOWN(" + std::to_string(opcode) + ")"; - break; - } - - LOG(INFO) << "OnMessage (opcode: " << type << ", message: " << msg << ")"; - }); - - stream->SetOnClose([]() { - LOG(INFO) << "OnClose"; + { + TlsClient client(base, + host, + port, + ca_cert, + client_cert, + client_key); + + if (!client.Init()) { + LOG(INFO) << "Init() failed"; + + event_base_free(base); + + return 1; + } + + client.SetOnOpen([&client](WebStream* stream) { + LOG(INFO) << "OnOpen"; + + stream->SetOnMessage([](uint8_t opcode, const std::string& msg) { + std::string type; + switch (opcode) { + case WEB_STREAM_OPCODE_TEXT: + type = "TEXT"; + break; + case WEB_STREAM_OPCODE_BINARY: + type = "BINARY"; + break; + case WEB_STREAM_OPCODE_METADATA: + type = "METADATA"; + break; + default: + type = "UNKNOWN(" + std::to_string(opcode) + ")"; + break; + } + + LOG(INFO) << "OnMessage (opcode: " << type << ", message: " << msg << ")"; + }); + + stream->SetOnClose([&client]() { + LOG(INFO) << "OnClose"; + + client.Stop(); + }); + + stream->SendText("Hello web-stream text!"); + stream->SendBinary("Hello web-stream binary!"); + stream->SendMetadata("Hello web-stream metadata!"); + stream->Close(); }); - stream->SendText("Hello web-stream text!"); - stream->SendBinary("Hello web-stream binary!"); - stream->SendMetadata("Hello web-stream metadata!"); - stream->Close(); - }); + client.Run(); + } - client.Run(); + event_base_free(base); return 0; } diff --git a/wish/cpp/scripts/build-and-push.sh b/wish/cpp/scripts/build-and-push.sh index 42f57f3..eb45bc4 100755 --- a/wish/cpp/scripts/build-and-push.sh +++ b/wish/cpp/scripts/build-and-push.sh @@ -1,4 +1,18 @@ #!/usr/bin/env bash +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + # build-and-push.sh # Builds wish-server and wish-bench-client images, pushes them to Artifact Registry. # diff --git a/wish/cpp/src/buffer_event_web_stream.cc b/wish/cpp/src/buffer_event_web_stream.cc index 6549614..2e8fd89 100644 --- a/wish/cpp/src/buffer_event_web_stream.cc +++ b/wish/cpp/src/buffer_event_web_stream.cc @@ -1,3 +1,17 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "buffer_event_web_stream.h" #include @@ -18,34 +32,48 @@ BufferEventWebStream::BufferEventWebStream(bufferevent* bev, : bev_(bev), is_server_(is_server), ctx_(nullptr), - state_(OPEN) { + state_(OPEN) {} + +bool BufferEventWebStream::Init() { wslay_event_callbacks callbacks = { WslayRecvCallback, WslaySendCallback, WslayGenmaskCallback, - WslayOnFrameRecvStartCallback, // on_frame_recv_start_callback - nullptr, // on_frame_recv_chunk_callback - nullptr, // on_frame_recv_end_callback + WslayOnFrameRecvStartCallback, + nullptr, // on_frame_recv_chunk_callback + nullptr, // on_frame_recv_end_callback WslayOnMsgRecvCallback}; + int rv; if (is_server_) { - wslay_event_context_server_init(&ctx_, - &callbacks, - this); + rv = wslay_event_context_server_init(&ctx_, + &callbacks, + this); } else { - wslay_event_context_client_init(&ctx_, - &callbacks, - this); + rv = wslay_event_context_client_init(&ctx_, + &callbacks, + this); } + return rv == 0; } BufferEventWebStream::~BufferEventWebStream() { wslay_event_context_free(ctx_); + if (bev_) { + bufferevent_setcb(bev_, + nullptr, + nullptr, + nullptr, + nullptr); bufferevent_free(bev_); } } +void BufferEventWebStream::SetCleanupCallback(CleanupCallback cb) { + cleanup_cb_ = std::move(cb); +} + void BufferEventWebStream::Start() { bufferevent_setcb(bev_, ReadCallback, @@ -55,7 +83,7 @@ void BufferEventWebStream::Start() { int enable_rv = bufferevent_enable(bev_, EV_READ | EV_WRITE); if (enable_rv != 0) { - LOG(ERROR) << "bufferevent_enable() failed"; + VLOG(1) << "bufferevent_enable() failed"; } // If there is already data in the input buffer, process it immediately. @@ -95,7 +123,8 @@ int BufferEventWebStream::Close() { static constexpr char kTerminalChunk[] = "0\r\n\r\n"; int rv = bufferevent_write(bev_, kTerminalChunk, sizeof(kTerminalChunk) - 1); if (rv != 0) { - LOG(ERROR) << "bufferevent_write() failed"; + VLOG(3) << "bufferevent_write() failed"; + return -1; } @@ -112,7 +141,7 @@ void BufferEventWebStream::ReadCallback(bufferevent* bev, void* ctx) { case OPEN: { int rv = wslay_event_recv(stream->ctx_); if (rv != 0) { - LOG(ERROR) << "wslay_event_recv() failed: " << rv; + VLOG(2) << "wslay_event_recv() failed: " << rv; return; } @@ -122,8 +151,11 @@ void BufferEventWebStream::ReadCallback(bufferevent* bev, void* ctx) { evbuffer* input = bufferevent_get_input(stream->bev_); size_t extra_len = evbuffer_get_length(input); if (extra_len > 0) { - LOG(WARNING) << "Warning: received " << extra_len << " bytes of extra data after stream close."; - evbuffer_drain(input, extra_len); + VLOG(2) << "Warning: received " << extra_len << " bytes of extra data after stream close."; + + if (evbuffer_drain(input, extra_len) != 0) { + VLOG(2) << "evbuffer_drain failed"; + } } // Keep the read callback as ReadCallback; receive direction is done. @@ -158,7 +190,10 @@ void BufferEventWebStream::ReadCallback(bufferevent* bev, void* ctx) { stream); } else { stream->state_ = CLOSED; - delete stream; + auto cleanup = std::move(stream->cleanup_cb_); + if (cleanup) { + cleanup(stream); + } } return; } @@ -169,8 +204,11 @@ void BufferEventWebStream::ReadCallback(bufferevent* bev, void* ctx) { evbuffer* input = bufferevent_get_input(stream->bev_); size_t len = evbuffer_get_length(input); if (len > 0) { - LOG(WARNING) << "Warning: received " << len << " bytes of extra data after stream close."; - evbuffer_drain(input, len); + VLOG(2) << "Warning: received " << len << " bytes of extra data after stream close."; + + if (evbuffer_drain(input, len) != 0) { + VLOG(2) << "evbuffer_drain failed"; + } } return; } @@ -190,7 +228,10 @@ void BufferEventWebStream::DrainCallback(bufferevent* bev, size_t output_len = evbuffer_get_length(bufferevent_get_output(bev)); if (output_len == 0) { stream->state_ = CLOSED; - delete stream; + auto cleanup = std::move(stream->cleanup_cb_); + if (cleanup) { + cleanup(stream); + } } } @@ -200,9 +241,9 @@ void BufferEventWebStream::EventCallback(bufferevent* bev, if (what & BEV_EVENT_ERROR) { int err = EVUTIL_SOCKET_ERROR(); if (err != 0) { - LOG(ERROR) << "Error on socket: " << evutil_socket_error_to_string(err); + VLOG(2) << "Error on socket: " << evutil_socket_error_to_string(err); } else { - LOG(ERROR) << "Error on bufferevent"; + VLOG(2) << "Error on bufferevent"; } } @@ -217,7 +258,10 @@ void BufferEventWebStream::EventCallback(bufferevent* bev, stream->on_error_(); } } - delete stream; + auto cleanup = std::move(stream->cleanup_cb_); + if (cleanup) { + cleanup(stream); + } } } @@ -254,7 +298,7 @@ ssize_t BufferEventWebStream::WslaySendCallback(wslay_event_context* ctx, header, static_cast(header_len)); if (write_header_rv != 0) { - LOG(ERROR) << "bufferevent_write() failed"; + VLOG(3) << "bufferevent_write() failed"; wslay_event_set_error(ctx, WSLAY_ERR_CALLBACK_FAILURE); @@ -265,7 +309,7 @@ ssize_t BufferEventWebStream::WslaySendCallback(wslay_event_context* ctx, data, len); if (write_data_rv != 0) { - LOG(ERROR) << "bufferevent_write() failed"; + VLOG(3) << "bufferevent_write() failed"; wslay_event_set_error(ctx, WSLAY_ERR_CALLBACK_FAILURE); @@ -276,7 +320,7 @@ ssize_t BufferEventWebStream::WslaySendCallback(wslay_event_context* ctx, "\r\n", 2); if (write_trailer_rv != 0) { - LOG(ERROR) << "bufferevent_write() failed"; + VLOG(3) << "bufferevent_write() failed"; wslay_event_set_error(ctx, WSLAY_ERR_CALLBACK_FAILURE); @@ -342,26 +386,40 @@ ssize_t BufferEventWebStream::ReadChunkedBytes(uint8_t* buf, size_t len) { evbuffer_ptr pos = evbuffer_search(input, "\r\n", 2, nullptr); if (pos.pos < 0) { wslay_event_set_error(ctx_, WSLAY_ERR_WOULDBLOCK); + return -1; // Wait for more data. } if (pos.pos == 0) { - LOG(ERROR) << "ReadChunkedBytes: empty chunk-size line"; + VLOG(3) << "ReadChunkedBytes: empty chunk-size line"; + wslay_event_set_error(ctx_, WSLAY_ERR_CALLBACK_FAILURE); + return -1; } // Read the entire "\r\n" line into a temporary buffer. size_t line_len = static_cast(pos.pos) + 2; // include \r\n std::vector line(line_len + 1, '\0'); - evbuffer_remove(input, line.data(), line_len); + + int remove_rv = evbuffer_remove(input, line.data(), line_len); + if (remove_rv < 0 || static_cast(remove_rv) != line_len) { + VLOG(3) << "ReadChunkedBytes: evbuffer_remove() failed"; + + wslay_event_set_error(ctx_, WSLAY_ERR_CALLBACK_FAILURE); + + return -1; + } + // Null-terminate at the \r so strtoul sees only hex digits. line[pos.pos] = '\0'; char* end = nullptr; unsigned long chunk_size = std::strtoul(line.data(), &end, 16); if (end == line.data()) { - LOG(ERROR) << "ReadChunkedBytes: malformed chunk size"; + VLOG(3) << "ReadChunkedBytes: malformed chunk size"; + wslay_event_set_error(ctx_, WSLAY_ERR_CALLBACK_FAILURE); + return -1; } @@ -383,14 +441,17 @@ ssize_t BufferEventWebStream::ReadChunkedBytes(uint8_t* buf, size_t len) { size_t avail = evbuffer_get_length(input); if (avail == 0) { wslay_event_set_error(ctx_, WSLAY_ERR_WOULDBLOCK); + return -1; } size_t n = std::min({len, chunk_remaining_, avail}); int rv = evbuffer_remove(input, buf, n); if (rv < 0) { - LOG(ERROR) << "ReadChunkedBytes: evbuffer_remove() failed"; + VLOG(3) << "ReadChunkedBytes: evbuffer_remove() failed"; + wslay_event_set_error(ctx_, WSLAY_ERR_CALLBACK_FAILURE); + return -1; } chunk_remaining_ -= n; @@ -404,16 +465,20 @@ ssize_t BufferEventWebStream::ReadChunkedBytes(uint8_t* buf, size_t len) { size_t input_len = evbuffer_get_length(input); if (input_len < 2) { wslay_event_set_error(ctx_, WSLAY_ERR_WOULDBLOCK); + return -1; } - evbuffer_drain(input, 2); // Discard "\r\n". + if (evbuffer_drain(input, 2) != 0) { + VLOG(2) << "evbuffer_drain failed"; + } chunk_state_ = ChunkState::HEADER; if (terminal_chunk_seen_) { receive_closed_ = true; // Terminal chunk fully consumed; let ReadCallback close the stream. wslay_event_set_error(ctx_, WSLAY_ERR_WOULDBLOCK); + return -1; } diff --git a/wish/cpp/src/buffer_event_web_stream.h b/wish/cpp/src/buffer_event_web_stream.h index 0d84dd7..14d17c9 100644 --- a/wish/cpp/src/buffer_event_web_stream.h +++ b/wish/cpp/src/buffer_event_web_stream.h @@ -1,3 +1,19 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #ifndef WISH_CPP_SRC_BUFFER_EVENT_WEB_STREAM_H_ #define WISH_CPP_SRC_BUFFER_EVENT_WEB_STREAM_H_ @@ -31,6 +47,11 @@ class BufferEventWebStream : public WebStream { bool is_server); ~BufferEventWebStream() override; + bool Init(); + + using CleanupCallback = std::function; + void SetCleanupCallback(CleanupCallback cb); + // Start the handler (sets up callbacks and enables events) void Start(); @@ -87,6 +108,8 @@ class BufferEventWebStream : public WebStream { CloseCallback on_close_; ErrorCallback on_error_; + CleanupCallback cleanup_cb_; + // libevent callbacks static void ReadCallback(bufferevent* bev, void* ctx); diff --git a/wish/cpp/src/buffer_event_web_stream_test.cc b/wish/cpp/src/buffer_event_web_stream_test.cc index d45a363..2c1649f 100644 --- a/wish/cpp/src/buffer_event_web_stream_test.cc +++ b/wish/cpp/src/buffer_event_web_stream_test.cc @@ -1,3 +1,17 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "buffer_event_web_stream.h" #include "handshake.h" @@ -31,8 +45,8 @@ TEST_F(BufferEventWebStreamTest, HandshakeAndSimpleExchange) { pair); ASSERT_EQ(rv, 0); - BufferEventWebStream* server = nullptr; - BufferEventWebStream* client = nullptr; + std::unique_ptr server; + std::unique_ptr client; bool server_opened = false; bool client_opened = false; @@ -45,10 +59,11 @@ TEST_F(BufferEventWebStreamTest, HandshakeAndSimpleExchange) { } }; - auto* server_handshake = new ServerHandshake( + auto server_handshake = std::make_unique( pair[0], [&](bufferevent* bev) { - server = new BufferEventWebStream(bev, true); + server = std::make_unique(bev, true); + ASSERT_TRUE(server->Init()); server_opened = true; server->SetOnMessage([&](uint8_t opcode, const std::string& msg) { if (opcode == WEB_STREAM_OPCODE_TEXT) { @@ -65,7 +80,8 @@ TEST_F(BufferEventWebStreamTest, HandshakeAndSimpleExchange) { auto client_handshake = std::make_unique( pair[1], [&](bufferevent* bev) { - client = new BufferEventWebStream(bev, false); + client = std::make_unique(bev, false); + ASSERT_TRUE(client->Init()); client_opened = true; client->SetOnMessage([&](uint8_t opcode, const std::string& msg) { if (opcode == WEB_STREAM_OPCODE_TEXT) { @@ -85,9 +101,6 @@ TEST_F(BufferEventWebStreamTest, HandshakeAndSimpleExchange) { EXPECT_TRUE(server_opened); EXPECT_EQ(received_from_client, "Hello, Server!"); EXPECT_EQ(received_from_server, "Hello, Client!"); - - delete server; - delete client; } // Helper: drain all bytes from a bufferevent's input buffer. @@ -104,11 +117,12 @@ TEST_F(BufferEventWebStreamTest, ClientSendsUnmasked) { ASSERT_EQ(rv, 0); bufferevent_enable(pair[1], EV_READ | EV_WRITE); - BufferEventWebStream* client = nullptr; + std::unique_ptr client; auto client_handshake = std::make_unique( pair[0], [&](bufferevent* bev) { - client = new BufferEventWebStream(bev, false); + client = std::make_unique(bev, false); + ASSERT_TRUE(client->Init()); client->Start(); }, []() { ADD_FAILURE() << "Client handshake failed"; }); @@ -148,8 +162,6 @@ TEST_F(BufferEventWebStreamTest, ClientSendsUnmasked) { bool is_masked = (bytes[4] & 0x80) != 0; EXPECT_FALSE(is_masked) << "Client sent a masked frame! web-stream must not use masking."; - - delete client; bufferevent_free(pair[1]); } @@ -160,11 +172,12 @@ TEST_F(BufferEventWebStreamTest, ServerSendsUnmasked) { ASSERT_EQ(rv, 0); bufferevent_enable(pair[1], EV_READ | EV_WRITE); - BufferEventWebStream* server = nullptr; - auto* server_handshake = new ServerHandshake( + std::unique_ptr server; + auto server_handshake = std::make_unique( pair[0], [&](bufferevent* bev) { - server = new BufferEventWebStream(bev, true); + server = std::make_unique(bev, true); + ASSERT_TRUE(server->Init()); server->Start(); }, []() { ADD_FAILURE() << "Server handshake failed"; }); @@ -196,8 +209,6 @@ TEST_F(BufferEventWebStreamTest, ServerSendsUnmasked) { bool is_masked = (bytes[4] & 0x80) != 0; EXPECT_FALSE(is_masked) << "Server sent a masked frame! web-stream must not use masking."; - - delete server; bufferevent_free(pair[1]); } @@ -210,14 +221,15 @@ TEST_F(BufferEventWebStreamTest, CloseSignalsEOF) { pair); ASSERT_EQ(rv, 0); - BufferEventWebStream* server = nullptr; - BufferEventWebStream* client = nullptr; + std::unique_ptr server; + std::unique_ptr client; bool client_close_fired = false; - auto* server_handshake = new ServerHandshake( + auto server_handshake = std::make_unique( pair[0], [&](bufferevent* bev) { - server = new BufferEventWebStream(bev, true); + server = std::make_unique(bev, true); + ASSERT_TRUE(server->Init()); server->Start(); EXPECT_EQ(server->Close(), 0); EXPECT_EQ(server->Close(), -1); @@ -228,7 +240,8 @@ TEST_F(BufferEventWebStreamTest, CloseSignalsEOF) { auto client_handshake = std::make_unique( pair[1], [&](bufferevent* bev) { - client = new BufferEventWebStream(bev, false); + client = std::make_unique(bev, false); + ASSERT_TRUE(client->Init()); client->SetOnClose([&]() { client_close_fired = true; event_base_loopbreak(base_); @@ -241,7 +254,6 @@ TEST_F(BufferEventWebStreamTest, CloseSignalsEOF) { event_base_dispatch(base_); EXPECT_TRUE(client_close_fired); - delete server; } // Verify that metadata frames (opcode = 3) can be sent and received correctly by both client and server. @@ -252,8 +264,8 @@ TEST_F(BufferEventWebStreamTest, HandshakeAndMetadataExchange) { pair); ASSERT_EQ(rv, 0); - BufferEventWebStream* server = nullptr; - BufferEventWebStream* client = nullptr; + std::unique_ptr server; + std::unique_ptr client; bool server_opened = false; bool client_opened = false; @@ -266,10 +278,11 @@ TEST_F(BufferEventWebStreamTest, HandshakeAndMetadataExchange) { } }; - auto* server_handshake = new ServerHandshake( + auto server_handshake = std::make_unique( pair[0], [&](bufferevent* bev) { - server = new BufferEventWebStream(bev, true); + server = std::make_unique(bev, true); + ASSERT_TRUE(server->Init()); server_opened = true; server->SetOnMessage([&](uint8_t opcode, const std::string& msg) { if (opcode == WEB_STREAM_OPCODE_METADATA) { @@ -286,7 +299,8 @@ TEST_F(BufferEventWebStreamTest, HandshakeAndMetadataExchange) { auto client_handshake = std::make_unique( pair[1], [&](bufferevent* bev) { - client = new BufferEventWebStream(bev, false); + client = std::make_unique(bev, false); + ASSERT_TRUE(client->Init()); client_opened = true; client->SetOnMessage([&](uint8_t opcode, const std::string& msg) { if (opcode == WEB_STREAM_OPCODE_METADATA) { @@ -306,9 +320,6 @@ TEST_F(BufferEventWebStreamTest, HandshakeAndMetadataExchange) { EXPECT_TRUE(server_opened); EXPECT_EQ(metadata_from_client, "Client Metadata"); EXPECT_EQ(metadata_from_server, "Server Metadata"); - - delete server; - delete client; } // Verify that the local side can still receive messages from the peer after calling Close() (half-closed). @@ -319,17 +330,18 @@ TEST_F(BufferEventWebStreamTest, ReceiveMessageAfterClose) { pair); ASSERT_EQ(rv, 0); - BufferEventWebStream* server = nullptr; - BufferEventWebStream* client = nullptr; + std::unique_ptr server; + std::unique_ptr client; bool server_received_msg_after_close = false; bool server_close_fired = false; bool client_close_fired = false; - auto* server_handshake = new ServerHandshake( + auto server_handshake = std::make_unique( pair[0], [&](bufferevent* bev) { - server = new BufferEventWebStream(bev, true); + server = std::make_unique(bev, true); + ASSERT_TRUE(server->Init()); server->SetOnMessage([&](uint8_t opcode, const std::string& msg) { if (opcode == WEB_STREAM_OPCODE_TEXT && msg == "Hello after server close") { server_received_msg_after_close = true; @@ -347,7 +359,8 @@ TEST_F(BufferEventWebStreamTest, ReceiveMessageAfterClose) { auto client_handshake = std::make_unique( pair[1], [&](bufferevent* bev) { - client = new BufferEventWebStream(bev, false); + client = std::make_unique(bev, false); + ASSERT_TRUE(client->Init()); client->SetOnClose([&]() { client_close_fired = true; client->SendText("Hello after server close"); @@ -374,16 +387,17 @@ TEST_F(BufferEventWebStreamTest, WarningOnExtraDataPostClose) { pair); ASSERT_EQ(rv, 0); - BufferEventWebStream* server = nullptr; - BufferEventWebStream* client = nullptr; + std::unique_ptr server; + std::unique_ptr client; bool server_close_fired = false; bool client_close_fired = false; - auto* server_handshake = new ServerHandshake( + auto server_handshake = std::make_unique( pair[0], [&](bufferevent* bev) { - server = new BufferEventWebStream(bev, true); + server = std::make_unique(bev, true); + ASSERT_TRUE(server->Init()); server->SetOnClose([&]() { server_close_fired = true; }); @@ -396,7 +410,8 @@ TEST_F(BufferEventWebStreamTest, WarningOnExtraDataPostClose) { auto client_handshake = std::make_unique( pair[1], [&](bufferevent* bev) { - client = new BufferEventWebStream(bev, false); + client = std::make_unique(bev, false); + ASSERT_TRUE(client->Init()); client->SetOnClose([&]() { client_close_fired = true; client->Close(); @@ -423,14 +438,15 @@ TEST_F(BufferEventWebStreamTest, OnErrorOnMidMessageEOF) { pair); ASSERT_EQ(rv, 0); - BufferEventWebStream* server = nullptr; + std::unique_ptr server; bool server_close_fired = false; bool server_error_fired = false; - auto* server_handshake = new ServerHandshake( + auto server_handshake = std::make_unique( pair[0], [&](bufferevent* bev) { - server = new BufferEventWebStream(bev, true); + server = std::make_unique(bev, true); + ASSERT_TRUE(server->Init()); server->SetOnClose([&]() { server_close_fired = true; }); @@ -477,14 +493,15 @@ TEST_F(BufferEventWebStreamTest, OnErrorOnConnectionClosedMidMessage) { pair); ASSERT_EQ(rv, 0); - BufferEventWebStream* server = nullptr; + std::unique_ptr server; bool server_close_fired = false; bool server_error_fired = false; - auto* server_handshake = new ServerHandshake( + auto server_handshake = std::make_unique( pair[0], [&](bufferevent* bev) { - server = new BufferEventWebStream(bev, true); + server = std::make_unique(bev, true); + ASSERT_TRUE(server->Init()); server->SetOnClose([&]() { server_close_fired = true; }); diff --git a/wish/cpp/src/h2_client.cc b/wish/cpp/src/h2_client.cc index b21646f..22e8549 100644 --- a/wish/cpp/src/h2_client.cc +++ b/wish/cpp/src/h2_client.cc @@ -1,3 +1,17 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "h2_client.h" #include @@ -12,18 +26,16 @@ { \ (uint8_t*)(name), (uint8_t*)(value), strlen(name), strlen(value), NGHTTP2_NV_FLAG_NONE} -H2Client::H2Client(const std::string& host, int port) - : host_(host), +H2Client::H2Client(event_base* base, + const std::string& host, + int port) + : base_(base), + host_(host), port_(port), - base_(nullptr), dns_base_(nullptr), session_(nullptr) {} H2Client::~H2Client() { - if (base_) { - event_base_loopbreak(base_); - } - if (session_) { if (session_->web_stream) { delete session_->web_stream; @@ -41,15 +53,11 @@ H2Client::~H2Client() { if (dns_base_) { evdns_base_free(dns_base_, 0); } - if (base_) { - event_base_free(base_); - } } bool H2Client::Init() { - base_ = event_base_new(); if (!base_) { - LOG(ERROR) << "event_base_new() failed"; + VLOG(1) << "event_base is null"; return false; } @@ -57,7 +65,7 @@ bool H2Client::Init() { dns_base_ = evdns_base_new(base_, 1); if (!dns_base_) { - LOG(ERROR) << "evdns_base_new() failed"; + VLOG(1) << "evdns_base_new() failed"; return false; } @@ -66,7 +74,7 @@ bool H2Client::Init() { -1, BEV_OPT_CLOSE_ON_FREE); if (!bev) { - LOG(ERROR) << "bufferevent_socket_new() failed"; + VLOG(1) << "bufferevent_socket_new() failed"; return false; } @@ -88,7 +96,7 @@ bool H2Client::Init() { int enable_rv = bufferevent_enable(bev, EV_READ | EV_WRITE); if (enable_rv != 0) { - LOG(ERROR) << "bufferevent_enable() failed"; + VLOG(1) << "bufferevent_enable() failed"; return false; } @@ -99,7 +107,7 @@ bool H2Client::Init() { host_.c_str(), port_); if (connect_rv < 0) { - LOG(ERROR) << "bufferevent_socket_connect_hostname() failed"; + VLOG(1) << "bufferevent_socket_connect_hostname() failed"; return false; } @@ -109,14 +117,15 @@ bool H2Client::Init() { void H2Client::SetOnOpen(OpenCallback cb) { on_open_ = cb; } -void H2Client::Run() { - event_base_dispatch(base_); +int H2Client::Run() { + return event_base_dispatch(base_); } -void H2Client::Stop() { +int H2Client::Stop() { if (base_) { - event_base_loopexit(base_, nullptr); + return event_base_loopexit(base_, nullptr); } + return -1; } // ---- libevent bufferevent callbacks ---- @@ -142,8 +151,8 @@ void H2Client::ReadCallback(bufferevent* bev, data, len); if (recv_len < 0) { - LOG(ERROR) << "nghttp2_session_mem_recv() failed: " - << nghttp2_strerror(static_cast(recv_len)); + VLOG(1) << "nghttp2_session_mem_recv() failed: " + << nghttp2_strerror(static_cast(recv_len)); sess->client->HandleSessionError(sess); @@ -152,7 +161,7 @@ void H2Client::ReadCallback(bufferevent* bev, int drain_rv = evbuffer_drain(input, static_cast(recv_len)); if (drain_rv != 0) { - LOG(ERROR) << "evbuffer_drain() failed"; + VLOG(3) << "evbuffer_drain() failed"; sess->client->HandleSessionError(sess); @@ -166,8 +175,8 @@ void H2Client::ReadCallback(bufferevent* bev, // SendCallback. int send_rv = nghttp2_session_send(sess->h2session); if (send_rv < 0) { - LOG(ERROR) << "nghttp2_session_send() failed: " - << nghttp2_strerror(send_rv); + VLOG(1) << "nghttp2_session_send() failed: " + << nghttp2_strerror(send_rv); sess->client->HandleSessionError(sess); @@ -191,7 +200,7 @@ void H2Client::EventCallback(bufferevent* bev, &one, sizeof(one)); if (rv != 0) { - LOG(ERROR) << "H2Client: setsockopt(TCP_NODELAY) failed"; + VLOG(1) << "H2Client: setsockopt(TCP_NODELAY) failed"; } } @@ -201,7 +210,7 @@ void H2Client::EventCallback(bufferevent* bev, } if (what & BEV_EVENT_ERROR) { - LOG(ERROR) << "BEV_EVENT_ERROR event"; + VLOG(2) << "BEV_EVENT_ERROR event"; } if (what & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) { @@ -216,7 +225,9 @@ void H2Client::EventCallback(bufferevent* bev, sess->h2session = nullptr; } - sess->client->Stop(); + if (sess->client->Stop() != 0) { + VLOG(2) << "H2Client::Stop failed"; + } bufferevent_free(bev); @@ -244,7 +255,9 @@ void H2Client::HandleSessionError(Session* sess) { sess->bev = nullptr; } - Stop(); + if (Stop() != 0) { + VLOG(2) << "H2Client::Stop failed"; + } if (session_ == sess) { session_ = nullptr; @@ -265,7 +278,7 @@ nghttp2_ssize H2Client::SendCallback(nghttp2_session* /*session*/, data, length); if (rv != 0) { - LOG(ERROR) << "bufferevent_write() failed"; + VLOG(3) << "bufferevent_write() failed"; return NGHTTP2_ERR_CALLBACK_FAILURE; } @@ -320,8 +333,8 @@ int H2Client::OnFrameRecvCallback(nghttp2_session* /*session*/, } } } else { - LOG(ERROR) << "H2Client: Server rejected stream with status: " - << sess->response_status; + VLOG(2) << "H2Client: Server rejected stream with status: " + << sess->response_status; // nghttp2_on_frame_recv_callback spec: any nonzero value signals a fatal error. return -1; @@ -353,8 +366,8 @@ int H2Client::OnDataChunkRecvCallback(nghttp2_session* session, int send_rv = nghttp2_session_send(session); if (send_rv < 0) { - LOG(ERROR) << "nghttp2_session_send() failed: " - << nghttp2_strerror(send_rv); + VLOG(1) << "nghttp2_session_send() failed: " + << nghttp2_strerror(send_rv); return NGHTTP2_ERR_CALLBACK_FAILURE; } @@ -371,7 +384,8 @@ int H2Client::OnStreamCloseCallback(nghttp2_session* /*session*/, if (sess->web_stream && stream_id == sess->h2_stream_id) { if (error_code != NGHTTP2_NO_ERROR) { - LOG(ERROR) << "H2Client: Stream closed with error code: " << error_code; + VLOG(2) << "H2Client: Stream closed with error code: " << error_code; + sess->web_stream->OnError(); } else { sess->web_stream->OnClose(); @@ -409,7 +423,16 @@ nghttp2_ssize H2Client::DataSourceReadCallback(nghttp2_session* session, void H2Client::InitH2Session(Session* sess) { nghttp2_session_callbacks* cbs; - nghttp2_session_callbacks_new(&cbs); + + int cb_new_rv = nghttp2_session_callbacks_new(&cbs); + if (cb_new_rv != 0) { + VLOG(1) << "nghttp2_session_callbacks_new() failed: " << nghttp2_strerror(cb_new_rv); + + HandleSessionError(sess); + + return; + } + nghttp2_session_callbacks_set_send_callback2(cbs, SendCallback); nghttp2_session_callbacks_set_on_header_callback(cbs, @@ -421,21 +444,42 @@ void H2Client::InitH2Session(Session* sess) { nghttp2_session_callbacks_set_on_stream_close_callback(cbs, OnStreamCloseCallback); - nghttp2_session_client_new(&sess->h2session, - cbs, - sess); + int session_new_rv = nghttp2_session_client_new(&sess->h2session, + cbs, + sess); nghttp2_session_callbacks_del(cbs); + if (session_new_rv != 0) { + VLOG(1) << "nghttp2_session_client_new() failed: " << nghttp2_strerror(session_new_rv); + + HandleSessionError(sess); + + return; + } nghttp2_settings_entry iv[] = { {NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, 1 << 20}}; - nghttp2_submit_settings(sess->h2session, - NGHTTP2_FLAG_NONE, - iv, - 1); - nghttp2_session_set_local_window_size(sess->h2session, - NGHTTP2_FLAG_NONE, - 0, - 1 << 20); + int submit_settings_rv = nghttp2_submit_settings(sess->h2session, + NGHTTP2_FLAG_NONE, + iv, + 1); + if (submit_settings_rv != 0) { + VLOG(1) << "nghttp2_submit_settings failed: " << nghttp2_strerror(submit_settings_rv); + + HandleSessionError(sess); + + return; + } + int set_local_window_size_rv = nghttp2_session_set_local_window_size(sess->h2session, + NGHTTP2_FLAG_NONE, + 0, + 1 << 20); + if (set_local_window_size_rv != 0) { + VLOG(1) << "nghttp2_session_set_local_window_size failed: " << nghttp2_strerror(set_local_window_size_rv); + + HandleSessionError(sess); + + return; + } // Construct the :authority pseudo-header value (variable-length host:port). std::string authority = host_ + ":" + std::to_string(port_); @@ -467,9 +511,11 @@ void H2Client::InitH2Session(Session* sess) { &data_prd, nullptr); if (stream_id < 0) { - LOG(ERROR) << "H2Client: nghttp2_submit_request2() failed: " - << nghttp2_strerror(stream_id); + VLOG(1) << "H2Client: nghttp2_submit_request2() failed: " + << nghttp2_strerror(stream_id); + HandleSessionError(sess); + return; } sess->h2_stream_id = stream_id; @@ -479,16 +525,32 @@ void H2Client::InitH2Session(Session* sess) { stream_id, false); + if (!sess->web_stream->Init()) { + VLOG(1) << "H2Client: NGHTTP2WebStream::Init() failed"; + + HandleSessionError(sess); + + return; + } + // Register the stream object as stream user-data so DataSourceReadCallback // can find it. This must happen before nghttp2_session_send(). - nghttp2_session_set_stream_user_data(sess->h2session, - stream_id, - sess->web_stream); + int set_stream_user_data_rv = nghttp2_session_set_stream_user_data(sess->h2session, + stream_id, + sess->web_stream); + if (set_stream_user_data_rv != 0) { + VLOG(1) << "nghttp2_session_set_stream_user_data failed: " << nghttp2_strerror(set_stream_user_data_rv); + + HandleSessionError(sess); + + return; + } int send_rv = nghttp2_session_send(sess->h2session); if (send_rv < 0) { - LOG(ERROR) << "H2Client: nghttp2_session_send() failed: " - << nghttp2_strerror(send_rv); + VLOG(1) << "H2Client: nghttp2_session_send() failed: " + << nghttp2_strerror(send_rv); + HandleSessionError(sess); } } diff --git a/wish/cpp/src/h2_client.h b/wish/cpp/src/h2_client.h index f8c69f8..163ff67 100644 --- a/wish/cpp/src/h2_client.h +++ b/wish/cpp/src/h2_client.h @@ -1,3 +1,19 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #ifndef WISH_CPP_SRC_H2_CLIENT_H_ #define WISH_CPP_SRC_H2_CLIENT_H_ @@ -24,15 +40,17 @@ class H2Client { using OpenCallback = std::function; using CloseCallback = std::function; - H2Client(const std::string& host, int port); + H2Client(event_base* base, + const std::string& host, + int port); ~H2Client(); bool Init(); void SetOnOpen(OpenCallback cb); - void Run(); - void Stop(); + int Run(); + int Stop(); private: // Per-connection state (heap-allocated, owned by the callbacks). @@ -99,10 +117,11 @@ class H2Client { void InitH2Session(Session* sess); void HandleSessionError(Session* sess); + event_base* base_; + std::string host_; int port_; - event_base* base_; evdns_base* dns_base_; Session* session_; diff --git a/wish/cpp/src/h2_server.cc b/wish/cpp/src/h2_server.cc index 15dff6a..296f371 100644 --- a/wish/cpp/src/h2_server.cc +++ b/wish/cpp/src/h2_server.cc @@ -1,3 +1,17 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "h2_server.h" #include @@ -11,24 +25,21 @@ { \ (uint8_t*)(name), (uint8_t*)(value), strlen(name), strlen(value), NGHTTP2_NV_FLAG_NONE} -H2Server::H2Server(int port) - : port_(port), - base_(nullptr), +H2Server::H2Server(event_base* base, + int port) + : base_(base), + port_(port), listener_(nullptr) {} H2Server::~H2Server() { if (listener_) { evconnlistener_free(listener_); } - if (base_) { - event_base_free(base_); - } } bool H2Server::Init() { - base_ = event_base_new(); if (!base_) { - LOG(ERROR) << "H2Server: event_base_new() failed"; + VLOG(1) << "H2Server: event_base is null"; return false; } @@ -47,7 +58,7 @@ bool H2Server::Init() { reinterpret_cast(&sin), sizeof(sin)); if (!listener_) { - LOG(ERROR) << "H2Server: evconnlistener_new_bind() failed"; + VLOG(1) << "H2Server: evconnlistener_new_bind() failed"; return false; } @@ -59,8 +70,8 @@ bool H2Server::Init() { void H2Server::SetOnStream(StreamCallback cb) { on_stream_ = cb; } -void H2Server::Run() { - event_base_dispatch(base_); +int H2Server::Run() { + return event_base_dispatch(base_); } // ---- libevent listener callbacks ---- @@ -81,8 +92,12 @@ void H2Server::AcceptConnCb(evconnlistener* listener, &one, sizeof(one)); if (set_rv != 0) { - LOG(ERROR) << "H2Server: setsockopt(TCP_NODELAY) failed"; - evutil_closesocket(fd); + VLOG(1) << "H2Server: setsockopt(TCP_NODELAY) failed"; + + if (evutil_closesocket(fd) != 0) { + VLOG(2) << "evutil_closesocket failed"; + } + return; } @@ -90,8 +105,12 @@ void H2Server::AcceptConnCb(evconnlistener* listener, fd, BEV_OPT_CLOSE_ON_FREE); if (!bev) { - LOG(ERROR) << "H2Server: bufferevent_socket_new() failed"; - evutil_closesocket(fd); + VLOG(1) << "H2Server: bufferevent_socket_new() failed"; + + if (evutil_closesocket(fd) != 0) { + VLOG(2) << "evutil_closesocket failed"; + } + return; } @@ -99,6 +118,19 @@ void H2Server::AcceptConnCb(evconnlistener* listener, sess->server = server; sess->bev = bev; sess->h2session = CreateH2Session(sess); + if (!sess->h2session) { + VLOG(1) << "H2Server: CreateH2Session() failed"; + + bufferevent_free(bev); + + if (evutil_closesocket(fd) != 0) { + VLOG(2) << "evutil_closesocket failed"; + } + + delete sess; + + return; + } // Send server connection preface (SETTINGS frame). nghttp2_settings_entry iv[] = { @@ -109,8 +141,8 @@ void H2Server::AcceptConnCb(evconnlistener* listener, iv, 2); if (submit_settings_rv != 0) { - LOG(ERROR) << "H2Server: nghttp2_submit_settings() failed: " - << nghttp2_strerror(submit_settings_rv); + VLOG(1) << "H2Server: nghttp2_submit_settings() failed: " + << nghttp2_strerror(submit_settings_rv); nghttp2_session_del(sess->h2session); bufferevent_free(bev); @@ -124,8 +156,8 @@ void H2Server::AcceptConnCb(evconnlistener* listener, 0, 1 << 20); if (set_local_window_size_rv != 0) { - LOG(ERROR) << "H2Server: nghttp2_session_set_local_window_size() failed: " - << nghttp2_strerror(set_local_window_size_rv); + VLOG(1) << "H2Server: nghttp2_session_set_local_window_size() failed: " + << nghttp2_strerror(set_local_window_size_rv); nghttp2_session_del(sess->h2session); bufferevent_free(bev); @@ -136,8 +168,8 @@ void H2Server::AcceptConnCb(evconnlistener* listener, int send_rv = nghttp2_session_send(sess->h2session); if (send_rv != 0) { - LOG(ERROR) << "H2Server: nghttp2_session_send() failed: " - << nghttp2_strerror(send_rv); + VLOG(1) << "H2Server: nghttp2_session_send() failed: " + << nghttp2_strerror(send_rv); nghttp2_session_del(sess->h2session); bufferevent_free(bev); @@ -155,7 +187,7 @@ void H2Server::AcceptConnCb(evconnlistener* listener, int enable_rv = bufferevent_enable(bev, EV_READ | EV_WRITE); if (enable_rv != 0) { - LOG(ERROR) << "H2Server: bufferevent_enable() failed"; + VLOG(1) << "H2Server: bufferevent_enable() failed"; nghttp2_session_del(sess->h2session); bufferevent_free(bev); @@ -166,12 +198,18 @@ void H2Server::AcceptConnCb(evconnlistener* listener, } void H2Server::AcceptErrorCb(evconnlistener* listener, - void* /*ctx*/) { + void* ctx) { + (void)ctx; + event_base* base = evconnlistener_get_base(listener); int err = EVUTIL_SOCKET_ERROR(); - LOG(ERROR) << "H2Server: listener error " << err << " (" - << evutil_socket_error_to_string(err) << ")"; - event_base_loopexit(base, nullptr); + VLOG(1) << "H2Server: Got an error " << err << " (" + << evutil_socket_error_to_string(err) + << ") on the listener. Shutting down."; + + if (event_base_loopexit(base, nullptr) != 0) { + VLOG(2) << "event_base_loopexit failed"; + } } // ---- libevent bufferevent callbacks ---- @@ -191,22 +229,26 @@ void H2Server::ReadCallback(bufferevent* bev, void* ctx) { data, len); if (readlen < 0) { - LOG(ERROR) << "H2Server: nghttp2_session_mem_recv() failed: " - << nghttp2_strerror(static_cast(readlen)); + VLOG(1) << "H2Server: nghttp2_session_mem_recv() failed: " + << nghttp2_strerror(static_cast(readlen)); + bufferevent_free(bev); + return; } int drain_rv = evbuffer_drain(input, static_cast(readlen)); if (drain_rv != 0) { - LOG(ERROR) << "H2Server: evbuffer_drain() failed"; + VLOG(3) << "H2Server: evbuffer_drain() failed"; + bufferevent_free(bev); + return; } int session_send_rv = nghttp2_session_send(sess->h2session); if (session_send_rv < 0) { - LOG(ERROR) << "H2Server: nghttp2_session_send() failed: " - << nghttp2_strerror(session_send_rv); + VLOG(1) << "H2Server: nghttp2_session_send() failed: " + << nghttp2_strerror(session_send_rv); } } @@ -216,7 +258,7 @@ void H2Server::EventCallback(bufferevent* bev, Session* sess = static_cast(ctx); if (what & BEV_EVENT_ERROR) { - LOG(ERROR) << "H2Server: connection error"; + VLOG(2) << "H2Server: connection error"; } if (what & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) { @@ -320,8 +362,8 @@ int H2Server::OnFrameRecvCallback(nghttp2_session* session, 1, nullptr); if (submit_response_rv != 0) { - LOG(ERROR) << "H2Server: nghttp2_submit_response2() failed: " - << nghttp2_strerror(submit_response_rv); + VLOG(1) << "H2Server: nghttp2_submit_response2() failed: " + << nghttp2_strerror(submit_response_rv); // nghttp2_on_frame_recv_callback spec: any nonzero value signals a fatal error. return -1; @@ -329,8 +371,8 @@ int H2Server::OnFrameRecvCallback(nghttp2_session* session, int session_send_rv = nghttp2_session_send(session); if (session_send_rv != 0) { - LOG(ERROR) << "H2Server: nghttp2_session_send() failed: " - << nghttp2_strerror(session_send_rv); + VLOG(1) << "H2Server: nghttp2_session_send() failed: " + << nghttp2_strerror(session_send_rv); // nghttp2_on_frame_recv_callback spec: any nonzero value signals a fatal error. return -1; @@ -341,6 +383,15 @@ int H2Server::OnFrameRecvCallback(nghttp2_session* session, // Create the web-stream stream object. web_stream = new NGHTTP2WebStream(session, stream_id, true); + + if (!web_stream->Init()) { + VLOG(1) << "H2Server: NGHTTP2WebStream::Init() failed"; + + delete web_stream; + + return -1; + } + sess->incoming_streams[stream_id].web_stream = web_stream; nghttp2_data_provider2 data_prd; @@ -356,8 +407,8 @@ int H2Server::OnFrameRecvCallback(nghttp2_session* session, 2, &data_prd); if (submit_response_rv != 0) { - LOG(ERROR) << "H2Server: nghttp2_submit_response2() failed: " - << nghttp2_strerror(submit_response_rv); + VLOG(1) << "H2Server: nghttp2_submit_response2() failed: " + << nghttp2_strerror(submit_response_rv); delete web_stream; sess->incoming_streams.erase(stream_id); @@ -368,8 +419,8 @@ int H2Server::OnFrameRecvCallback(nghttp2_session* session, int session_send_rv = nghttp2_session_send(session); if (session_send_rv != 0) { - LOG(ERROR) << "H2Server: nghttp2_session_send() failed: " - << nghttp2_strerror(session_send_rv); + VLOG(1) << "H2Server: nghttp2_session_send() failed: " + << nghttp2_strerror(session_send_rv); delete web_stream; sess->incoming_streams.erase(stream_id); @@ -406,8 +457,8 @@ int H2Server::OnDataChunkRecvCallback(nghttp2_session* session, int rv = nghttp2_session_send(session); if (rv != 0) { - LOG(ERROR) << "H2Server: nghttp2_session_send() failed: " - << nghttp2_strerror(rv); + VLOG(1) << "H2Server: nghttp2_session_send() failed: " + << nghttp2_strerror(rv); } } @@ -460,7 +511,7 @@ nghttp2_session* H2Server::CreateH2Session(Session* sess) { nghttp2_session_callbacks* cbs; int callbacks_new_rv = nghttp2_session_callbacks_new(&cbs); if (callbacks_new_rv != 0) { - LOG(ERROR) << "H2Server: nghttp2_session_callbacks_new() failed"; + VLOG(1) << "H2Server: nghttp2_session_callbacks_new() failed"; return nullptr; } @@ -482,7 +533,7 @@ nghttp2_session* H2Server::CreateH2Session(Session* sess) { sess); nghttp2_session_callbacks_del(cbs); if (session_new_rv != 0) { - LOG(ERROR) << "H2Server: nghttp2_session_server_new() failed"; + VLOG(1) << "H2Server: nghttp2_session_server_new() failed"; return nullptr; } diff --git a/wish/cpp/src/h2_server.h b/wish/cpp/src/h2_server.h index 0e596b1..1ae508b 100644 --- a/wish/cpp/src/h2_server.h +++ b/wish/cpp/src/h2_server.h @@ -1,3 +1,19 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #ifndef WISH_CPP_SRC_H2_SERVER_H_ #define WISH_CPP_SRC_H2_SERVER_H_ @@ -20,12 +36,13 @@ class H2Server { // the stream is deleted by H2Server when the HTTP/2 stream closes. using StreamCallback = std::function; - explicit H2Server(int port); + H2Server(event_base* base, + int port); ~H2Server(); bool Init(); void SetOnStream(StreamCallback cb); - void Run(); + int Run(); private: // Per-connection state. @@ -101,9 +118,10 @@ class H2Server { // Helper: initialise an nghttp2 server session for a new connection. static nghttp2_session* CreateH2Session(Session* sess); + event_base* base_; + int port_; - event_base* base_; evconnlistener* listener_; StreamCallback on_stream_; diff --git a/wish/cpp/src/h2_tls_client.cc b/wish/cpp/src/h2_tls_client.cc index f85dca0..ef8f30f 100644 --- a/wish/cpp/src/h2_tls_client.cc +++ b/wish/cpp/src/h2_tls_client.cc @@ -1,3 +1,17 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "h2_tls_client.h" #include @@ -14,25 +28,22 @@ { \ (uint8_t*)(name), (uint8_t*)(value), strlen(name), strlen(value), NGHTTP2_NV_FLAG_NONE} -H2TlsClient::H2TlsClient(const std::string& host, +H2TlsClient::H2TlsClient(event_base* base, + const std::string& host, int port, const std::string& ca_file, const std::string& cert_file, const std::string& key_file) - : host_(host), + : base_(base), + host_(host), port_(port), ca_file_(ca_file), cert_file_(cert_file), key_file_(key_file), - base_(nullptr), dns_base_(nullptr), session_(nullptr) {} H2TlsClient::~H2TlsClient() { - if (base_) { - event_base_loopbreak(base_); - } - if (session_) { if (session_->web_stream) { delete session_->web_stream; @@ -50,9 +61,6 @@ H2TlsClient::~H2TlsClient() { if (dns_base_) { evdns_base_free(dns_base_, 0); } - if (base_) { - event_base_free(base_); - } } bool H2TlsClient::Init() { @@ -64,18 +72,21 @@ bool H2TlsClient::Init() { tls_ctx_.set_private_key_file(key_file_); if (!tls_ctx_.Init(false)) { - LOG(ERROR) << "H2TlsClient: failed to init TLS context"; + VLOG(1) << "H2TlsClient: failed to init TLS context"; + + return false; + } + + if (!base_) { + VLOG(1) << "H2TlsClient: event_base is null"; return false; } // Advertise "h2" via ALPN so the server can negotiate HTTP/2. static const unsigned char kAlpnH2[] = "\x02h2"; - SSL_CTX_set_alpn_protos(tls_ctx_.ssl_ctx(), kAlpnH2, sizeof(kAlpnH2) - 1); - - base_ = event_base_new(); - if (!base_) { - LOG(ERROR) << "event_base_new() failed"; + if (SSL_CTX_set_alpn_protos(tls_ctx_.ssl_ctx(), kAlpnH2, sizeof(kAlpnH2) - 1) != 0) { + VLOG(1) << "H2TlsClient: failed to set ALPN protocols"; return false; } @@ -83,7 +94,7 @@ bool H2TlsClient::Init() { dns_base_ = evdns_base_new(base_, 1); if (!dns_base_) { - LOG(ERROR) << "evdns_base_new() failed"; + VLOG(1) << "evdns_base_new() failed"; return false; } @@ -95,7 +106,7 @@ bool H2TlsClient::Init() { BUFFEREVENT_SSL_CONNECTING, BEV_OPT_CLOSE_ON_FREE); if (!bev) { - LOG(ERROR) << "bufferevent_openssl_socket_new() failed"; + VLOG(1) << "bufferevent_openssl_socket_new() failed"; SSL_free(ssl); @@ -121,7 +132,7 @@ bool H2TlsClient::Init() { int enable_rv = bufferevent_enable(bev, EV_READ | EV_WRITE); if (enable_rv != 0) { - LOG(ERROR) << "bufferevent_enable() failed"; + VLOG(1) << "bufferevent_enable() failed"; return false; } @@ -132,7 +143,7 @@ bool H2TlsClient::Init() { host_.c_str(), port_); if (connect_rv < 0) { - LOG(ERROR) << "bufferevent_socket_connect_hostname() failed"; + VLOG(1) << "bufferevent_socket_connect_hostname() failed"; return false; } @@ -142,14 +153,15 @@ bool H2TlsClient::Init() { void H2TlsClient::SetOnOpen(OpenCallback cb) { on_open_ = cb; } -void H2TlsClient::Run() { - event_base_dispatch(base_); +int H2TlsClient::Run() { + return event_base_dispatch(base_); } -void H2TlsClient::Stop() { +int H2TlsClient::Stop() { if (base_) { - event_base_loopexit(base_, nullptr); + return event_base_loopexit(base_, nullptr); } + return -1; } // ---- libevent bufferevent callbacks ---- @@ -175,8 +187,8 @@ void H2TlsClient::ReadCallback(bufferevent* bev, data, len); if (recv_len < 0) { - LOG(ERROR) << "nghttp2_session_mem_recv() failed: " - << nghttp2_strerror(static_cast(recv_len)); + VLOG(1) << "nghttp2_session_mem_recv() failed: " + << nghttp2_strerror(static_cast(recv_len)); sess->client->HandleSessionError(sess); @@ -185,7 +197,7 @@ void H2TlsClient::ReadCallback(bufferevent* bev, int drain_rv = evbuffer_drain(input, static_cast(recv_len)); if (drain_rv != 0) { - LOG(ERROR) << "evbuffer_drain() failed"; + VLOG(3) << "evbuffer_drain() failed"; sess->client->HandleSessionError(sess); @@ -199,8 +211,8 @@ void H2TlsClient::ReadCallback(bufferevent* bev, // SendCallback. int send_rv = nghttp2_session_send(sess->h2session); if (send_rv < 0) { - LOG(ERROR) << "nghttp2_session_send() failed: " - << nghttp2_strerror(send_rv); + VLOG(1) << "nghttp2_session_send() failed: " + << nghttp2_strerror(send_rv); sess->client->HandleSessionError(sess); @@ -223,7 +235,7 @@ void H2TlsClient::EventCallback(bufferevent* bev, &one, sizeof(one)); if (rv != 0) { - LOG(ERROR) << "H2TlsClient: setsockopt(TCP_NODELAY) failed"; + VLOG(1) << "H2TlsClient: setsockopt(TCP_NODELAY) failed"; } } @@ -233,7 +245,7 @@ void H2TlsClient::EventCallback(bufferevent* bev, } if (what & BEV_EVENT_ERROR) { - LOG(ERROR) << "BEV_EVENT_ERROR event"; + VLOG(2) << "BEV_EVENT_ERROR event"; } if (what & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) { @@ -249,7 +261,9 @@ void H2TlsClient::EventCallback(bufferevent* bev, sess->h2session = nullptr; } - sess->client->Stop(); + if (sess->client->Stop() != 0) { + VLOG(2) << "H2TlsClient::Stop failed"; + } bufferevent_free(bev); @@ -277,7 +291,9 @@ void H2TlsClient::HandleSessionError(Session* sess) { sess->bev = nullptr; } - Stop(); + if (Stop() != 0) { + VLOG(2) << "H2TlsClient::Stop failed"; + } if (session_ == sess) { session_ = nullptr; @@ -298,7 +314,7 @@ nghttp2_ssize H2TlsClient::SendCallback(nghttp2_session* /*session*/, data, length); if (rv != 0) { - LOG(ERROR) << "bufferevent_write() failed"; + VLOG(3) << "bufferevent_write() failed"; return NGHTTP2_ERR_CALLBACK_FAILURE; } @@ -353,8 +369,8 @@ int H2TlsClient::OnFrameRecvCallback(nghttp2_session* /*session*/, } } } else { - LOG(ERROR) << "H2TlsClient: Server rejected stream with status: " - << sess->response_status; + VLOG(2) << "H2TlsClient: Server rejected stream with status: " + << sess->response_status; // nghttp2_on_frame_recv_callback spec: any nonzero value signals a fatal error. return -1; @@ -386,8 +402,9 @@ int H2TlsClient::OnDataChunkRecvCallback(nghttp2_session* session, int send_rv = nghttp2_session_send(session); if (send_rv < 0) { - LOG(ERROR) << "nghttp2_session_send() failed: " - << nghttp2_strerror(send_rv); + VLOG(1) << "nghttp2_session_send() failed: " + << nghttp2_strerror(send_rv); + return NGHTTP2_ERR_CALLBACK_FAILURE; } } @@ -403,7 +420,8 @@ int H2TlsClient::OnStreamCloseCallback(nghttp2_session* /*session*/, if (sess->web_stream && stream_id == sess->h2_stream_id) { if (error_code != NGHTTP2_NO_ERROR) { - LOG(ERROR) << "H2TlsClient: Stream closed with error code: " << error_code; + VLOG(2) << "H2TlsClient: Stream closed with error code: " << error_code; + sess->web_stream->OnError(); } else { sess->web_stream->OnClose(); @@ -437,7 +455,16 @@ nghttp2_ssize H2TlsClient::DataSourceReadCallback(nghttp2_session* session, void H2TlsClient::InitH2Session(Session* sess) { nghttp2_session_callbacks* cbs; - nghttp2_session_callbacks_new(&cbs); + + int cb_new_rv = nghttp2_session_callbacks_new(&cbs); + if (cb_new_rv != 0) { + VLOG(1) << "nghttp2_session_callbacks_new failed: " << nghttp2_strerror(cb_new_rv); + + HandleSessionError(sess); + + return; + } + nghttp2_session_callbacks_set_send_callback2(cbs, SendCallback); nghttp2_session_callbacks_set_on_header_callback(cbs, @@ -449,21 +476,43 @@ void H2TlsClient::InitH2Session(Session* sess) { nghttp2_session_callbacks_set_on_stream_close_callback(cbs, OnStreamCloseCallback); - nghttp2_session_client_new(&sess->h2session, - cbs, - sess); + int session_new_rv = nghttp2_session_client_new(&sess->h2session, + cbs, + sess); nghttp2_session_callbacks_del(cbs); + if (session_new_rv != 0) { + VLOG(1) << "nghttp2_session_client_new failed: " << nghttp2_strerror(session_new_rv); + + HandleSessionError(sess); + + return; + } nghttp2_settings_entry iv[] = { {NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, 1 << 20}}; - nghttp2_submit_settings(sess->h2session, - NGHTTP2_FLAG_NONE, - iv, - 1); - nghttp2_session_set_local_window_size(sess->h2session, - NGHTTP2_FLAG_NONE, - 0, - 1 << 20); + int submit_settings_rv = nghttp2_submit_settings(sess->h2session, + NGHTTP2_FLAG_NONE, + iv, + 1); + if (submit_settings_rv != 0) { + VLOG(1) << "nghttp2_submit_settings failed: " << nghttp2_strerror(submit_settings_rv); + + HandleSessionError(sess); + + return; + } + + int set_local_window_size_rv = nghttp2_session_set_local_window_size(sess->h2session, + NGHTTP2_FLAG_NONE, + 0, + 1 << 20); + if (set_local_window_size_rv != 0) { + VLOG(1) << "nghttp2_session_set_local_window_size failed: " << nghttp2_strerror(set_local_window_size_rv); + + HandleSessionError(sess); + + return; + } std::string authority = host_ + ":" + std::to_string(port_); nghttp2_nv authority_nv = { @@ -492,9 +541,11 @@ void H2TlsClient::InitH2Session(Session* sess) { &data_prd, nullptr); if (stream_id < 0) { - LOG(ERROR) << "H2TlsClient: nghttp2_submit_request2() failed: " - << nghttp2_strerror(stream_id); + VLOG(1) << "H2TlsClient: nghttp2_submit_request2() failed: " + << nghttp2_strerror(stream_id); + HandleSessionError(sess); + return; } sess->h2_stream_id = stream_id; @@ -502,17 +553,32 @@ void H2TlsClient::InitH2Session(Session* sess) { sess->web_stream = new NGHTTP2WebStream(sess->h2session, stream_id, false); + if (!sess->web_stream->Init()) { + VLOG(1) << "H2TlsClient: NGHTTP2WebStream::Init() failed"; + + HandleSessionError(sess); + + return; + } // Register the stream object as stream user-data so DataSourceReadCallback // can find it. This must happen before nghttp2_session_send(). - nghttp2_session_set_stream_user_data(sess->h2session, - stream_id, - sess->web_stream); + int set_stream_user_data_rv = nghttp2_session_set_stream_user_data(sess->h2session, + stream_id, + sess->web_stream); + if (set_stream_user_data_rv != 0) { + VLOG(1) << "nghttp2_session_set_stream_user_data failed: " << nghttp2_strerror(set_stream_user_data_rv); + + HandleSessionError(sess); + + return; + } int send_rv = nghttp2_session_send(sess->h2session); if (send_rv < 0) { - LOG(ERROR) << "H2TlsClient: nghttp2_session_send() failed: " - << nghttp2_strerror(send_rv); + VLOG(1) << "H2TlsClient: nghttp2_session_send() failed: " + << nghttp2_strerror(send_rv); + HandleSessionError(sess); } } diff --git a/wish/cpp/src/h2_tls_client.h b/wish/cpp/src/h2_tls_client.h index 0b23e02..60e1517 100644 --- a/wish/cpp/src/h2_tls_client.h +++ b/wish/cpp/src/h2_tls_client.h @@ -1,3 +1,19 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #ifndef WISH_CPP_SRC_H2_TLS_CLIENT_H_ #define WISH_CPP_SRC_H2_TLS_CLIENT_H_ @@ -20,7 +36,8 @@ class H2TlsClient { using OpenCallback = std::function; using CloseCallback = std::function; - H2TlsClient(const std::string& host, + H2TlsClient(event_base* base, + const std::string& host, int port, const std::string& ca_file, const std::string& cert_file, @@ -31,8 +48,8 @@ class H2TlsClient { void SetOnOpen(OpenCallback cb); - void Run(); - void Stop(); + int Run(); + int Stop(); private: struct Session { @@ -94,6 +111,8 @@ class H2TlsClient { void InitH2Session(Session* sess); void HandleSessionError(Session* sess); + event_base* base_; + std::string host_; int port_; @@ -101,7 +120,6 @@ class H2TlsClient { std::string cert_file_; std::string key_file_; - event_base* base_; evdns_base* dns_base_; TlsContext tls_ctx_; diff --git a/wish/cpp/src/h2_tls_server.cc b/wish/cpp/src/h2_tls_server.cc index 091d618..938fa89 100644 --- a/wish/cpp/src/h2_tls_server.cc +++ b/wish/cpp/src/h2_tls_server.cc @@ -1,3 +1,17 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "h2_tls_server.h" #include @@ -28,23 +42,21 @@ static int AlpnSelectCb(SSL* /*ssl*/, return SSL_TLSEXT_ERR_OK; } -H2TlsServer::H2TlsServer(const std::string& ca_file, +H2TlsServer::H2TlsServer(event_base* base, + const std::string& ca_file, const std::string& cert_file, const std::string& key_file, int port) - : ca_file_(ca_file), + : base_(base), + ca_file_(ca_file), cert_file_(cert_file), key_file_(key_file), port_(port), - base_(nullptr), listener_(nullptr) {} H2TlsServer::~H2TlsServer() { if (listener_) { evconnlistener_free(listener_); } - if (base_) { - event_base_free(base_); - } } bool H2TlsServer::Init() { @@ -56,7 +68,7 @@ bool H2TlsServer::Init() { tls_ctx_.set_private_key_file(key_file_); if (!tls_ctx_.Init(true)) { - LOG(ERROR) << "H2TlsServer: failed to init TLS context"; + VLOG(1) << "H2TlsServer: failed to init TLS context"; return false; } @@ -64,9 +76,8 @@ bool H2TlsServer::Init() { // Advertise "h2" via ALPN. SSL_CTX_set_alpn_select_cb(tls_ctx_.ssl_ctx(), AlpnSelectCb, nullptr); - base_ = event_base_new(); if (!base_) { - LOG(ERROR) << "H2TlsServer: event_base_new() failed"; + VLOG(1) << "H2TlsServer: event_base is null"; return false; } @@ -85,7 +96,7 @@ bool H2TlsServer::Init() { reinterpret_cast(&sin), sizeof(sin)); if (!listener_) { - LOG(ERROR) << "H2TlsServer: evconnlistener_new_bind() failed"; + VLOG(1) << "H2TlsServer: evconnlistener_new_bind() failed"; return false; } @@ -96,8 +107,8 @@ bool H2TlsServer::Init() { void H2TlsServer::SetOnStream(StreamCallback cb) { on_stream_ = cb; } -void H2TlsServer::Run() { - event_base_dispatch(base_); +int H2TlsServer::Run() { + return event_base_dispatch(base_); } // ---- libevent listener callbacks ---- @@ -118,9 +129,11 @@ void H2TlsServer::AcceptConnCb(evconnlistener* listener, &one, sizeof(one)); if (set_rv != 0) { - LOG(ERROR) << "H2TlsServer: setsockopt(TCP_NODELAY) failed"; + VLOG(1) << "H2TlsServer: setsockopt(TCP_NODELAY) failed"; - evutil_closesocket(fd); + if (evutil_closesocket(fd) != 0) { + VLOG(2) << "evutil_closesocket failed"; + } return; } @@ -132,10 +145,13 @@ void H2TlsServer::AcceptConnCb(evconnlistener* listener, BUFFEREVENT_SSL_ACCEPTING, BEV_OPT_CLOSE_ON_FREE); if (!bev) { - LOG(ERROR) << "H2TlsServer: bufferevent_openssl_socket_new() failed"; + VLOG(1) << "H2TlsServer: bufferevent_openssl_socket_new() failed"; SSL_free(ssl); - evutil_closesocket(fd); + + if (evutil_closesocket(fd) != 0) { + VLOG(2) << "evutil_closesocket failed"; + } return; } @@ -146,6 +162,19 @@ void H2TlsServer::AcceptConnCb(evconnlistener* listener, sess->server = server; sess->bev = bev; sess->h2session = CreateH2Session(sess); + if (!sess->h2session) { + VLOG(1) << "H2TlsServer: CreateH2Session() failed"; + + bufferevent_free(bev); + + if (evutil_closesocket(fd) != 0) { + VLOG(2) << "evutil_closesocket failed"; + } + + delete sess; + + return; + } nghttp2_settings_entry iv[] = { {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}, @@ -155,8 +184,8 @@ void H2TlsServer::AcceptConnCb(evconnlistener* listener, iv, 2); if (submit_settings_rv != 0) { - LOG(ERROR) << "H2TlsServer: nghttp2_submit_settings() failed: " - << nghttp2_strerror(submit_settings_rv); + VLOG(1) << "H2TlsServer: nghttp2_submit_settings() failed: " + << nghttp2_strerror(submit_settings_rv); nghttp2_session_del(sess->h2session); bufferevent_free(bev); @@ -169,8 +198,8 @@ void H2TlsServer::AcceptConnCb(evconnlistener* listener, 0, 1 << 20); if (set_local_window_size_rv != 0) { - LOG(ERROR) << "H2TlsServer: nghttp2_session_set_local_window_size() failed: " - << nghttp2_strerror(set_local_window_size_rv); + VLOG(1) << "H2TlsServer: nghttp2_session_set_local_window_size() failed: " + << nghttp2_strerror(set_local_window_size_rv); nghttp2_session_del(sess->h2session); bufferevent_free(bev); @@ -181,8 +210,8 @@ void H2TlsServer::AcceptConnCb(evconnlistener* listener, int send_rv = nghttp2_session_send(sess->h2session); if (send_rv != 0) { - LOG(ERROR) << "H2TlsServer: nghttp2_session_send() failed: " - << nghttp2_strerror(send_rv); + VLOG(1) << "H2TlsServer: nghttp2_session_send() failed: " + << nghttp2_strerror(send_rv); nghttp2_session_del(sess->h2session); bufferevent_free(bev); @@ -200,7 +229,7 @@ void H2TlsServer::AcceptConnCb(evconnlistener* listener, int enable_rv = bufferevent_enable(bev, EV_READ | EV_WRITE); if (enable_rv != 0) { - LOG(ERROR) << "H2TlsServer: bufferevent_enable() failed"; + VLOG(1) << "H2TlsServer: bufferevent_enable() failed"; nghttp2_session_del(sess->h2session); bufferevent_free(bev); @@ -211,12 +240,18 @@ void H2TlsServer::AcceptConnCb(evconnlistener* listener, } void H2TlsServer::AcceptErrorCb(evconnlistener* listener, - void* /*ctx*/) { + void* ctx) { + (void)ctx; + event_base* base = evconnlistener_get_base(listener); int err = EVUTIL_SOCKET_ERROR(); - LOG(ERROR) << "H2TlsServer: listener error " << err << " (" - << evutil_socket_error_to_string(err) << ")"; - event_base_loopexit(base, nullptr); + VLOG(1) << "H2TlsServer: Got an error " << err << " (" + << evutil_socket_error_to_string(err) + << ") on the listener. Shutting down."; + + if (event_base_loopexit(base, nullptr) != 0) { + VLOG(2) << "event_base_loopexit failed"; + } } // ---- libevent bufferevent callbacks ---- @@ -236,8 +271,8 @@ void H2TlsServer::ReadCallback(bufferevent* bev, void* ctx) { data, len); if (recv_len < 0) { - LOG(ERROR) << "H2TlsServer: nghttp2_session_mem_recv() failed: " - << nghttp2_strerror(static_cast(recv_len)); + VLOG(1) << "H2TlsServer: nghttp2_session_mem_recv() failed: " + << nghttp2_strerror(static_cast(recv_len)); bufferevent_free(bev); return; @@ -245,7 +280,7 @@ void H2TlsServer::ReadCallback(bufferevent* bev, void* ctx) { int drain_rv = evbuffer_drain(input, static_cast(recv_len)); if (drain_rv != 0) { - LOG(ERROR) << "H2TlsServer: evbuffer_drain() failed"; + VLOG(3) << "H2TlsServer: evbuffer_drain() failed"; bufferevent_free(bev); return; @@ -253,8 +288,8 @@ void H2TlsServer::ReadCallback(bufferevent* bev, void* ctx) { int session_send_rv = nghttp2_session_send(sess->h2session); if (session_send_rv < 0) { - LOG(ERROR) << "H2TlsServer: nghttp2_session_send() failed: " - << nghttp2_strerror(session_send_rv); + VLOG(1) << "H2TlsServer: nghttp2_session_send() failed: " + << nghttp2_strerror(session_send_rv); } } @@ -264,7 +299,7 @@ void H2TlsServer::EventCallback(bufferevent* bev, Session* sess = static_cast(ctx); if (what & BEV_EVENT_ERROR) { - LOG(ERROR) << "H2TlsServer: connection error"; + VLOG(2) << "H2TlsServer: connection error"; } if (what & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) { @@ -368,8 +403,8 @@ int H2TlsServer::OnFrameRecvCallback(nghttp2_session* session, 1, nullptr); if (submit_response_rv != 0) { - LOG(ERROR) << "H2TlsServer: nghttp2_submit_response2() failed: " - << nghttp2_strerror(submit_response_rv); + VLOG(1) << "H2TlsServer: nghttp2_submit_response2() failed: " + << nghttp2_strerror(submit_response_rv); // nghttp2_on_frame_recv_callback spec: any nonzero value signals a fatal error. return -1; @@ -377,8 +412,8 @@ int H2TlsServer::OnFrameRecvCallback(nghttp2_session* session, int session_send_rv = nghttp2_session_send(session); if (session_send_rv != 0) { - LOG(ERROR) << "H2TlsServer: nghttp2_session_send() failed: " - << nghttp2_strerror(session_send_rv); + VLOG(1) << "H2TlsServer: nghttp2_session_send() failed: " + << nghttp2_strerror(session_send_rv); // nghttp2_on_frame_recv_callback spec: any nonzero value signals a fatal error. return -1; @@ -388,6 +423,15 @@ int H2TlsServer::OnFrameRecvCallback(nghttp2_session* session, } web_stream = new NGHTTP2WebStream(session, stream_id, true); + + if (!web_stream->Init()) { + VLOG(1) << "H2TlsServer: NGHTTP2WebStream::Init() failed"; + + delete web_stream; + + return -1; + } + sess->incoming_streams[stream_id].web_stream = web_stream; nghttp2_data_provider2 data_prd; @@ -403,8 +447,8 @@ int H2TlsServer::OnFrameRecvCallback(nghttp2_session* session, 2, &data_prd); if (submit_response_rv != 0) { - LOG(ERROR) << "H2TlsServer: nghttp2_submit_response2() failed: " - << nghttp2_strerror(submit_response_rv); + VLOG(1) << "H2TlsServer: nghttp2_submit_response2() failed: " + << nghttp2_strerror(submit_response_rv); delete web_stream; sess->incoming_streams.erase(stream_id); @@ -415,8 +459,8 @@ int H2TlsServer::OnFrameRecvCallback(nghttp2_session* session, int session_send_rv = nghttp2_session_send(session); if (session_send_rv != 0) { - LOG(ERROR) << "H2TlsServer: nghttp2_session_send() failed: " - << nghttp2_strerror(session_send_rv); + VLOG(1) << "H2TlsServer: nghttp2_session_send() failed: " + << nghttp2_strerror(session_send_rv); delete web_stream; sess->incoming_streams.erase(stream_id); @@ -451,8 +495,8 @@ int H2TlsServer::OnDataChunkRecvCallback(nghttp2_session* session, int rv = nghttp2_session_send(session); if (rv != 0) { - LOG(ERROR) << "H2TlsServer: nghttp2_session_send() failed: " - << nghttp2_strerror(rv); + VLOG(1) << "H2TlsServer: nghttp2_session_send() failed: " + << nghttp2_strerror(rv); } } @@ -505,7 +549,7 @@ nghttp2_session* H2TlsServer::CreateH2Session(Session* sess) { nghttp2_session_callbacks* cbs; int callbacks_new_rv = nghttp2_session_callbacks_new(&cbs); if (callbacks_new_rv != 0) { - LOG(ERROR) << "H2TlsServer: nghttp2_session_callbacks_new() failed"; + VLOG(1) << "H2TlsServer: nghttp2_session_callbacks_new() failed"; return nullptr; } @@ -527,7 +571,7 @@ nghttp2_session* H2TlsServer::CreateH2Session(Session* sess) { sess); nghttp2_session_callbacks_del(cbs); if (server_new_rv != 0) { - LOG(ERROR) << "H2TlsServer: nghttp2_session_server_new() failed"; + VLOG(1) << "H2TlsServer: nghttp2_session_server_new() failed"; return nullptr; } diff --git a/wish/cpp/src/h2_tls_server.h b/wish/cpp/src/h2_tls_server.h index ffb3833..a537bbe 100644 --- a/wish/cpp/src/h2_tls_server.h +++ b/wish/cpp/src/h2_tls_server.h @@ -1,3 +1,19 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #ifndef WISH_CPP_SRC_H2_TLS_SERVER_H_ #define WISH_CPP_SRC_H2_TLS_SERVER_H_ @@ -20,13 +36,16 @@ class H2TlsServer { public: using StreamCallback = std::function; - H2TlsServer(const std::string& ca_file, const std::string& cert_file, - const std::string& key_file, int port); + H2TlsServer(event_base* base, + const std::string& ca_file, + const std::string& cert_file, + const std::string& key_file, + int port); ~H2TlsServer(); bool Init(); void SetOnStream(StreamCallback cb); - void Run(); + int Run(); private: struct Session { @@ -98,13 +117,14 @@ class H2TlsServer { static nghttp2_session* CreateH2Session(Session* sess); + event_base* base_; + int port_; std::string ca_file_; std::string cert_file_; std::string key_file_; - event_base* base_; evconnlistener* listener_; TlsContext tls_ctx_; diff --git a/wish/cpp/src/handshake.cc b/wish/cpp/src/handshake.cc index a1ba57a..07c3702 100644 --- a/wish/cpp/src/handshake.cc +++ b/wish/cpp/src/handshake.cc @@ -1,3 +1,17 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "handshake.h" #include @@ -116,14 +130,15 @@ bool ValidateTransferEncoding(const phr_header* headers, size_t num_headers) { std::string_view value(headers[i].value, headers[i].value_len); std::vector tokens = SplitAndTrim(value); if (tokens.empty()) { - LOG(ERROR) << "Empty Transfer-Encoding header value"; + VLOG(2) << "Empty Transfer-Encoding header value"; + return false; } for (std::string_view token : tokens) { if (EqualsIgnoreCase(token, "chunked")) { // Valid chunked token } else { - LOG(ERROR) << "Unsupported Transfer-Encoding token: " << token; + VLOG(2) << "Unsupported Transfer-Encoding token: " << token; return false; } @@ -136,31 +151,36 @@ bool ValidateTransferEncoding(const phr_header* headers, size_t num_headers) { bool ValidateHeaders(const phr_header* headers, size_t num_headers) { bool has_content_encoding = HasHeader(headers, num_headers, "content-encoding"); if (has_content_encoding) { - LOG(ERROR) << "Content-Encoding support is not implemented yet"; + VLOG(2) << "Content-Encoding support is not implemented yet"; + return false; } bool has_content_length = HasHeader(headers, num_headers, "content-length"); if (has_content_length) { - LOG(ERROR) << "Content-Length support is not implemented yet"; + VLOG(2) << "Content-Length support is not implemented yet"; + return false; } bool has_connection_close = CheckHeaderContains(headers, num_headers, "connection", "close"); if (has_connection_close) { - LOG(ERROR) << "Connection: close support is not implemented yet"; + VLOG(2) << "Connection: close support is not implemented yet"; + return false; } bool has_connection_upgrade = CheckHeaderContains(headers, num_headers, "connection", "upgrade"); if (has_connection_upgrade) { - LOG(ERROR) << "Connection: upgrade is not supported by web-stream"; + VLOG(2) << "Connection: upgrade is not supported by web-stream"; + return false; } bool has_upgrade = HasHeader(headers, num_headers, "upgrade"); if (has_upgrade) { - LOG(ERROR) << "Upgrade header is not supported by web-stream"; + VLOG(2) << "Upgrade header is not supported by web-stream"; + return false; } @@ -194,7 +214,7 @@ void ClientHandshake::Start() { int enable_rv = bufferevent_enable(bev_, EV_READ | EV_WRITE); if (enable_rv != 0) { - LOG(ERROR) << "bufferevent_enable() failed"; + VLOG(1) << "bufferevent_enable() failed"; InvokeError(); @@ -210,7 +230,7 @@ void ClientHandshake::Start() { std::string data = ss.str(); int write_rv = bufferevent_write(bev_, data.c_str(), data.length()); if (write_rv != 0) { - LOG(ERROR) << "bufferevent_write() failed"; + VLOG(3) << "bufferevent_write() failed"; InvokeError(); } @@ -246,7 +266,7 @@ void ClientHandshake::HandleRead() { int parse_rv = phr_parse_response(data, len, &minor_version, &status, &msg, &msg_len, headers, &num_headers, 0); if (parse_rv == -1) { - LOG(ERROR) << "Failed to parse client handshake HTTP response"; + VLOG(2) << "Failed to parse client handshake HTTP response"; InvokeError(); @@ -257,7 +277,7 @@ void ClientHandshake::HandleRead() { } if (status != 200) { - LOG(ERROR) << "Bad client handshake response status: " << status; + VLOG(2) << "Bad client handshake response status: " << status; InvokeError(); @@ -265,7 +285,7 @@ void ClientHandshake::HandleRead() { } if (minor_version < 1) { - LOG(ERROR) << "HTTP version must be at least 1.1, got 1." << minor_version; + VLOG(2) << "HTTP version must be at least 1.1, got 1." << minor_version; InvokeError(); @@ -274,7 +294,7 @@ void ClientHandshake::HandleRead() { bool has_valid_ct = CheckHeader(headers, num_headers, "content-type", "application/web-stream"); if (!has_valid_ct) { - LOG(ERROR) << "Client handshake response missing web-stream Content-Type!"; + VLOG(2) << "Client handshake response missing web-stream Content-Type!"; InvokeError(); @@ -290,7 +310,7 @@ void ClientHandshake::HandleRead() { int drain_rv = evbuffer_drain(input, parse_rv); if (drain_rv != 0) { - LOG(ERROR) << "evbuffer_drain() failed"; + VLOG(3) << "evbuffer_drain() failed"; InvokeError(); @@ -316,10 +336,10 @@ void ClientHandshake::HandleEvent(short what) { if (what & BEV_EVENT_ERROR) { int err = EVUTIL_SOCKET_ERROR(); if (err != 0) { - LOG(ERROR) << "Error during client handshake: " - << evutil_socket_error_to_string(err); + VLOG(2) << "Error during client handshake: " + << evutil_socket_error_to_string(err); } else { - LOG(ERROR) << "Error during client handshake"; + VLOG(2) << "Error during client handshake"; } } @@ -339,10 +359,12 @@ void ClientHandshake::InvokeError() { ServerHandshake::ServerHandshake(bufferevent* bev, OnOpenCallback on_open, - OnErrorCallback on_error) + OnErrorCallback on_error, + CleanupCallback cleanup) : bev_(bev), on_open_(std::move(on_open)), - on_error_(std::move(on_error)) {} + on_error_(std::move(on_error)), + cleanup_(std::move(cleanup)) {} ServerHandshake::~ServerHandshake() { if (bev_) { @@ -355,7 +377,7 @@ void ServerHandshake::Start() { int enable_rv = bufferevent_enable(bev_, EV_READ | EV_WRITE); if (enable_rv != 0) { - LOG(ERROR) << "bufferevent_enable() failed"; + VLOG(1) << "bufferevent_enable() failed"; InvokeError(); @@ -394,7 +416,7 @@ void ServerHandshake::HandleRead() { int parse_rv = phr_parse_request(data, len, &method, &method_len, &path, &path_len, &minor_version, headers, &num_headers, 0); if (parse_rv == -1) { - LOG(ERROR) << "Failed to parse server handshake HTTP request"; + VLOG(2) << "Failed to parse server handshake HTTP request"; InvokeError(); @@ -405,7 +427,7 @@ void ServerHandshake::HandleRead() { } if (minor_version < 1) { - LOG(ERROR) << "HTTP version must be at least 1.1, got 1." << minor_version; + VLOG(2) << "HTTP version must be at least 1.1, got 1." << minor_version; InvokeError(); @@ -414,7 +436,7 @@ void ServerHandshake::HandleRead() { bool has_valid_ct = CheckHeader(headers, num_headers, "content-type", "application/web-stream"); if (!has_valid_ct) { - LOG(ERROR) << "Missing web-stream Content-Type!"; + VLOG(2) << "Missing web-stream Content-Type!"; InvokeError(); @@ -430,7 +452,7 @@ void ServerHandshake::HandleRead() { int drain_rv = evbuffer_drain(input, parse_rv); if (drain_rv != 0) { - LOG(ERROR) << "evbuffer_drain() failed"; + VLOG(3) << "evbuffer_drain() failed"; InvokeError(); @@ -446,7 +468,7 @@ void ServerHandshake::HandleRead() { std::string response_data = ss.str(); int write_rv = bufferevent_write(bev_, response_data.c_str(), response_data.length()); if (write_rv != 0) { - LOG(ERROR) << "bufferevent_write() failed"; + VLOG(3) << "bufferevent_write() failed"; InvokeError(); @@ -461,19 +483,22 @@ void ServerHandshake::HandleRead() { bufferevent_setcb(bev, nullptr, nullptr, nullptr, nullptr); auto on_open = std::move(on_open_); - // Save callback, delete this, then call callback - delete this; + auto cleanup = std::move(cleanup_); + auto* raw_ptr = this; on_open(bev); + if (cleanup) { + cleanup(raw_ptr); + } } void ServerHandshake::HandleEvent(short what) { if (what & BEV_EVENT_ERROR) { int err = EVUTIL_SOCKET_ERROR(); if (err != 0) { - LOG(ERROR) << "Error during server handshake: " - << evutil_socket_error_to_string(err); + VLOG(2) << "Error during server handshake: " + << evutil_socket_error_to_string(err); } else { - LOG(ERROR) << "Error during server handshake"; + VLOG(2) << "Error during server handshake"; } } @@ -484,8 +509,12 @@ void ServerHandshake::HandleEvent(short what) { void ServerHandshake::InvokeError() { auto on_error = std::move(on_error_); - delete this; + auto cleanup = std::move(cleanup_); + auto* raw_ptr = this; if (on_error) { on_error(); } + if (cleanup) { + cleanup(raw_ptr); + } } diff --git a/wish/cpp/src/handshake.h b/wish/cpp/src/handshake.h index ff73d4b..10decb5 100644 --- a/wish/cpp/src/handshake.h +++ b/wish/cpp/src/handshake.h @@ -1,3 +1,19 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #ifndef WISH_CPP_SRC_HANDSHAKE_H_ #define WISH_CPP_SRC_HANDSHAKE_H_ @@ -36,8 +52,9 @@ class ServerHandshake { public: using OnOpenCallback = std::function; using OnErrorCallback = std::function; + using CleanupCallback = std::function; - ServerHandshake(bufferevent* bev, OnOpenCallback on_open, OnErrorCallback on_error); + ServerHandshake(bufferevent* bev, OnOpenCallback on_open, OnErrorCallback on_error, CleanupCallback cleanup = nullptr); ~ServerHandshake(); void Start(); @@ -54,6 +71,7 @@ class ServerHandshake { OnOpenCallback on_open_; OnErrorCallback on_error_; + CleanupCallback cleanup_; }; #endif // WISH_CPP_SRC_HANDSHAKE_H_ diff --git a/wish/cpp/src/handshake_test.cc b/wish/cpp/src/handshake_test.cc index 8165ba7..8d73b30 100644 --- a/wish/cpp/src/handshake_test.cc +++ b/wish/cpp/src/handshake_test.cc @@ -1,3 +1,17 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "handshake.h" #include @@ -194,7 +208,7 @@ TEST_F(HandshakeTest, ServerHandshakeSuccess) { bool error_called = false; bufferevent* server_bev = nullptr; - auto* server = new ServerHandshake( + auto server = std::make_unique( pair[0], [&](bufferevent* bev) { open_called = true; @@ -247,7 +261,7 @@ TEST_F(HandshakeTest, ServerHandshakeFailureBadContentType) { bool open_called = false; bool error_called = false; - auto* server = new ServerHandshake( + auto server = std::make_unique( pair[0], [&](bufferevent* bev) { open_called = true; @@ -287,7 +301,7 @@ TEST_F(HandshakeTest, ServerHandshakeEventError) { bool open_called = false; bool error_called = false; - auto* server = new ServerHandshake( + auto server = std::make_unique( pair[0], [&](bufferevent* bev) { open_called = true; @@ -531,7 +545,7 @@ TEST_F(HandshakeTest, ServerHandshakeRejectsContentEncoding) { bool open_called = false; bool error_called = false; - auto* server = new ServerHandshake( + auto server = std::make_unique( pair[0], [&](bufferevent* bev) { open_called = true; bufferevent_free(bev); event_base_loopbreak(base_); }, [&]() { error_called = true; event_base_loopbreak(base_); }); @@ -554,7 +568,7 @@ TEST_F(HandshakeTest, ServerHandshakeRejectsContentLength) { bool open_called = false; bool error_called = false; - auto* server = new ServerHandshake( + auto server = std::make_unique( pair[0], [&](bufferevent* bev) { open_called = true; bufferevent_free(bev); event_base_loopbreak(base_); }, [&]() { error_called = true; event_base_loopbreak(base_); }); @@ -577,7 +591,7 @@ TEST_F(HandshakeTest, ServerHandshakeRejectsConnectionClose) { bool open_called = false; bool error_called = false; - auto* server = new ServerHandshake( + auto server = std::make_unique( pair[0], [&](bufferevent* bev) { open_called = true; bufferevent_free(bev); event_base_loopbreak(base_); }, [&]() { error_called = true; event_base_loopbreak(base_); }); @@ -600,7 +614,7 @@ TEST_F(HandshakeTest, ServerHandshakeRejectsNonChunkedTransferEncoding) { bool open_called = false; bool error_called = false; - auto* server = new ServerHandshake( + auto server = std::make_unique( pair[0], [&](bufferevent* bev) { open_called = true; bufferevent_free(bev); event_base_loopbreak(base_); }, [&]() { error_called = true; event_base_loopbreak(base_); }); @@ -623,7 +637,7 @@ TEST_F(HandshakeTest, ServerHandshakeRejectsHTTP10) { bool open_called = false; bool error_called = false; - auto* server = new ServerHandshake( + auto server = std::make_unique( pair[0], [&](bufferevent* bev) { open_called = true; bufferevent_free(bev); event_base_loopbreak(base_); }, [&]() { error_called = true; event_base_loopbreak(base_); }); diff --git a/wish/cpp/src/nghttp2_web_stream.cc b/wish/cpp/src/nghttp2_web_stream.cc index 7f39156..3f32442 100644 --- a/wish/cpp/src/nghttp2_web_stream.cc +++ b/wish/cpp/src/nghttp2_web_stream.cc @@ -1,3 +1,17 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "nghttp2_web_stream.h" #include @@ -19,25 +33,29 @@ NGHTTP2WebStream::NGHTTP2WebStream(nghttp2_session* session, ctx_(nullptr), close_fired_(false), input_buf_(evbuffer_new()), - output_buf_(evbuffer_new()) { + output_buf_(evbuffer_new()) {} + +bool NGHTTP2WebStream::Init() { wslay_event_callbacks callbacks = { WslayRecvCallback, WslaySendCallback, WslayGenmaskCallback, - WslayOnFrameRecvStartCallback, // on_frame_recv_start_callback - nullptr, // on_frame_recv_chunk_callback - nullptr, // on_frame_recv_end_callback + WslayOnFrameRecvStartCallback, + nullptr, // on_frame_recv_chunk_callback + nullptr, // on_frame_recv_end_callback WslayOnMsgRecvCallback}; + int rv; if (is_server_) { - wslay_event_context_server_init(&ctx_, - &callbacks, - this); + rv = wslay_event_context_server_init(&ctx_, + &callbacks, + this); } else { - wslay_event_context_client_init(&ctx_, - &callbacks, - this); + rv = wslay_event_context_client_init(&ctx_, + &callbacks, + this); } + return rv == 0; } NGHTTP2WebStream::~NGHTTP2WebStream() { @@ -76,16 +94,16 @@ int NGHTTP2WebStream::Close() { // (data provider already active), which is not an error here. int resume_rv = nghttp2_session_resume_data(h2session_, stream_id_); if (resume_rv < 0 && resume_rv != NGHTTP2_ERR_INVALID_ARGUMENT) { - LOG(ERROR) << "NGHTTP2WebStream::Close: nghttp2_session_resume_data() failed: " - << nghttp2_strerror(resume_rv); + VLOG(1) << "NGHTTP2WebStream::Close: nghttp2_session_resume_data() failed: " + << nghttp2_strerror(resume_rv); return resume_rv; } int send_rv = nghttp2_session_send(h2session_); if (send_rv < 0) { - LOG(ERROR) << "NGHTTP2WebStream::Close: nghttp2_session_send() failed: " - << nghttp2_strerror(send_rv); + VLOG(1) << "NGHTTP2WebStream::Close: nghttp2_session_send() failed: " + << nghttp2_strerror(send_rv); return send_rv; } @@ -96,11 +114,14 @@ int NGHTTP2WebStream::Close() { // ---- Session callbacks (called by H2Server / H2Client) ---- void NGHTTP2WebStream::OnDataChunk(const uint8_t* data, size_t len) { - evbuffer_add(input_buf_, data, len); + int add_rv = evbuffer_add(input_buf_, data, len); + if (add_rv != 0) { + VLOG(2) << "NGHTTP2WebStream: evbuffer_add() failed"; + } - int rv = wslay_event_recv(ctx_); - if (rv != 0) { - LOG(ERROR) << "NGHTTP2WebStream: wslay_event_recv() failed: " << rv; + int recv_rv = wslay_event_recv(ctx_); + if (recv_rv != 0) { + VLOG(2) << "NGHTTP2WebStream: wslay_event_recv() failed: " << recv_rv; } } @@ -149,7 +170,7 @@ nghttp2_ssize NGHTTP2WebStream::ReadSendData(uint8_t* buf, int rv = evbuffer_remove(output_buf_, buf, send_len); if (rv < 0) { - LOG(ERROR) << "evbuffer_remove() failed"; + VLOG(3) << "evbuffer_remove() failed"; // TODO(nlattice): Consider using `NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE` instead of `NGHTTP2_ERR_CALLBACK_FAILURE`. return NGHTTP2_ERR_CALLBACK_FAILURE; @@ -179,9 +200,10 @@ ssize_t NGHTTP2WebStream::WslayRecvCallback(wslay_event_context* ctx, size_t copy_len = std::min(len, data_len); int rv = evbuffer_remove(input, buf, copy_len); if (rv < 0) { - LOG(ERROR) << "evbuffer_remove() failed"; + VLOG(3) << "evbuffer_remove() failed"; wslay_event_set_error(ctx, WSLAY_ERR_CALLBACK_FAILURE); + return -1; } return static_cast(copy_len); @@ -196,9 +218,10 @@ ssize_t NGHTTP2WebStream::WslaySendCallback(wslay_event_context* /*ctx*/, int rv = evbuffer_add(s->output_buf_, data, len); if (rv != 0) { - LOG(ERROR) << "evbuffer_add() failed"; + VLOG(3) << "evbuffer_add() failed"; wslay_event_set_error(s->ctx_, WSLAY_ERR_CALLBACK_FAILURE); + return -1; } @@ -263,16 +286,16 @@ int NGHTTP2WebStream::SendMessage(uint8_t opcode, int resume_data_rv = nghttp2_session_resume_data(h2session_, stream_id_); if (resume_data_rv < 0 && resume_data_rv != NGHTTP2_ERR_INVALID_ARGUMENT) { - LOG(ERROR) << "NGHTTP2WebStream: nghttp2_session_resume_data() failed: " - << nghttp2_strerror(resume_data_rv); + VLOG(1) << "NGHTTP2WebStream: nghttp2_session_resume_data() failed: " + << nghttp2_strerror(resume_data_rv); return resume_data_rv; } int h2_send_rv = nghttp2_session_send(h2session_); if (h2_send_rv < 0) { - LOG(ERROR) << "NGHTTP2WebStream: nghttp2_session_send() failed: " - << nghttp2_strerror(h2_send_rv); + VLOG(1) << "NGHTTP2WebStream: nghttp2_session_send() failed: " + << nghttp2_strerror(h2_send_rv); return h2_send_rv; } diff --git a/wish/cpp/src/nghttp2_web_stream.h b/wish/cpp/src/nghttp2_web_stream.h index 7176cba..aa9b3d0 100644 --- a/wish/cpp/src/nghttp2_web_stream.h +++ b/wish/cpp/src/nghttp2_web_stream.h @@ -1,3 +1,19 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #ifndef WISH_CPP_SRC_NGHTTP2_WEB_STREAM_H_ #define WISH_CPP_SRC_NGHTTP2_WEB_STREAM_H_ @@ -35,6 +51,8 @@ class NGHTTP2WebStream : public WebStream { bool is_server); ~NGHTTP2WebStream() override; + bool Init(); + void SetOnMessage(MessageCallback cb) override; void SetOnClose(CloseCallback cb) override; void SetOnError(ErrorCallback cb) override; diff --git a/wish/cpp/src/nghttp2_web_stream_test.cc b/wish/cpp/src/nghttp2_web_stream_test.cc index be064e6..d43ea33 100644 --- a/wish/cpp/src/nghttp2_web_stream_test.cc +++ b/wish/cpp/src/nghttp2_web_stream_test.cc @@ -1,3 +1,17 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "nghttp2_web_stream.h" #include @@ -58,7 +72,9 @@ class NGHTTP2WebStreamTest : public ::testing::Test { TEST_F(NGHTTP2WebStreamTest, HandshakeAndSimpleExchange) { NGHTTP2WebStream server(server_session_, 1, true /* is_server */); + ASSERT_TRUE(server.Init()); NGHTTP2WebStream client(client_session_, 1, false /* is_server */); + ASSERT_TRUE(client.Init()); std::string received_from_client; std::string received_from_server; @@ -91,6 +107,7 @@ TEST_F(NGHTTP2WebStreamTest, HandshakeAndSimpleExchange) { // web-stream doesn't use masking (unlike WebSocket over TCP). TEST_F(NGHTTP2WebStreamTest, ClientSendsUnmasked) { NGHTTP2WebStream client(client_session_, 1, false /* is_server */); + ASSERT_TRUE(client.Init()); client.SendText("Hello"); uint8_t buf[256]; @@ -111,6 +128,7 @@ TEST_F(NGHTTP2WebStreamTest, ClientSendsUnmasked) { // Tests that the server does NOT mask frames when sending. TEST_F(NGHTTP2WebStreamTest, ServerSendsUnmasked) { NGHTTP2WebStream server(server_session_, 1, true /* is_server */); + ASSERT_TRUE(server.Init()); server.SendText("Hello"); uint8_t buf[256]; @@ -131,7 +149,9 @@ TEST_F(NGHTTP2WebStreamTest, ServerSendsUnmasked) { // calling Close() a second time returns -1. TEST_F(NGHTTP2WebStreamTest, CloseSignalsEOF) { NGHTTP2WebStream server(server_session_, 1, true /* is_server */); + ASSERT_TRUE(server.Init()); NGHTTP2WebStream client(client_session_, 1, false /* is_server */); + ASSERT_TRUE(client.Init()); bool client_close_fired = false; client.SetOnClose([&]() { client_close_fired = true; }); @@ -167,6 +187,7 @@ TEST_F(NGHTTP2WebStreamTest, CloseSignalsEOF) { // in the middle of receiving a message on HTTP/2. TEST_F(NGHTTP2WebStreamTest, OnErrorOnMidMessageEOF) { NGHTTP2WebStream client(client_session_, 1, false /* is_server */); + ASSERT_TRUE(client.Init()); bool client_close_fired = false; bool client_error_fired = false; @@ -195,6 +216,7 @@ TEST_F(NGHTTP2WebStreamTest, OnErrorOnMidMessageEOF) { // Verify that calling OnError() directly fires SetOnError callback. TEST_F(NGHTTP2WebStreamTest, OnErrorMethodFiresCallback) { NGHTTP2WebStream client(client_session_, 1, false /* is_server */); + ASSERT_TRUE(client.Init()); bool client_close_fired = false; bool client_error_fired = false; diff --git a/wish/cpp/src/plain_client.cc b/wish/cpp/src/plain_client.cc index ccbdf1f..f914bd6 100644 --- a/wish/cpp/src/plain_client.cc +++ b/wish/cpp/src/plain_client.cc @@ -1,38 +1,49 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "plain_client.h" #include -PlainClient::PlainClient(const std::string& host, int port) - : host_(host), +PlainClient::PlainClient(event_base* base, + const std::string& host, + int port) + : base_(base), + host_(host), port_(port), - base_(nullptr), dns_base_(nullptr), stream_(nullptr) {} PlainClient::~PlainClient() { - if (base_) { - event_base_loopbreak(base_); - } + handshake_.reset(); + stream_.reset(); if (dns_base_) { evdns_base_free(dns_base_, 0); } - if (base_) { - event_base_free(base_); - } } bool PlainClient::Init() { - base_ = event_base_new(); if (!base_) { - LOG(ERROR) << "event_base_new() failed"; + VLOG(1) << "event_base is null"; return false; } dns_base_ = evdns_base_new(base_, 1); if (!dns_base_) { - LOG(ERROR) << "evdns_base_new() failed"; + VLOG(1) << "evdns_base_new() failed"; return false; } @@ -41,7 +52,7 @@ bool PlainClient::Init() { -1, BEV_OPT_CLOSE_ON_FREE); if (!bev) { - LOG(ERROR) << "bufferevent_socket_new() failed"; + VLOG(1) << "bufferevent_socket_new() failed"; return false; } @@ -52,7 +63,7 @@ bool PlainClient::Init() { host_.c_str(), port_); if (connect_rv < 0) { - LOG(ERROR) << "bufferevent_socket_connect_hostname() failed"; + VLOG(1) << "bufferevent_socket_connect_hostname() failed"; bufferevent_free(bev); @@ -62,17 +73,30 @@ bool PlainClient::Init() { handshake_ = std::make_unique( bev, [this](bufferevent* bev) { - stream_ = new BufferEventWebStream(bev, false); + auto s = std::make_unique(bev, false); + + if (!s->Init()) { + VLOG(1) << "BufferEventWebStream::Init() failed"; + + handshake_.reset(); + + return; + } + + stream_ = std::move(s); + stream_->SetCleanupCallback([this](BufferEventWebStream* s) { + stream_.reset(); + }); if (on_open_) { - on_open_(stream_); + on_open_(stream_.get()); } stream_->Start(); handshake_.reset(); }, [this]() { - LOG(ERROR) << "Client handshake failed"; + VLOG(1) << "Client handshake failed"; handshake_.reset(); }); @@ -85,12 +109,13 @@ void PlainClient::SetOnOpen(OpenCallback cb) { on_open_ = cb; } -void PlainClient::Run() { - event_base_dispatch(base_); +int PlainClient::Run() { + return event_base_dispatch(base_); } -void PlainClient::Stop() { +int PlainClient::Stop() { if (base_) { - event_base_loopexit(base_, nullptr); + return event_base_loopexit(base_, nullptr); } + return -1; } diff --git a/wish/cpp/src/plain_client.h b/wish/cpp/src/plain_client.h index eccf2d5..9662325 100644 --- a/wish/cpp/src/plain_client.h +++ b/wish/cpp/src/plain_client.h @@ -1,3 +1,19 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #ifndef WISH_CPP_SRC_PLAIN_CLIENT_H_ #define WISH_CPP_SRC_PLAIN_CLIENT_H_ @@ -6,9 +22,8 @@ #include #include -#include - #include +#include #include "buffer_event_web_stream.h" #include "handshake.h" @@ -19,7 +34,8 @@ class PlainClient { using MessageCallback = std::function; using CloseCallback = std::function; - PlainClient(const std::string& host, + PlainClient(event_base* base, + const std::string& host, int port); ~PlainClient(); @@ -27,18 +43,19 @@ class PlainClient { void SetOnOpen(OpenCallback cb); - void Run(); - void Stop(); + int Run(); + int Stop(); private: + event_base* base_; + std::string host_; int port_; - event_base* base_; evdns_base* dns_base_; std::unique_ptr handshake_; - BufferEventWebStream* stream_; + std::unique_ptr stream_; OpenCallback on_open_; }; diff --git a/wish/cpp/src/plain_server.cc b/wish/cpp/src/plain_server.cc index 3f3ffae..14e106e 100644 --- a/wish/cpp/src/plain_server.cc +++ b/wish/cpp/src/plain_server.cc @@ -1,3 +1,17 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "plain_server.h" #include @@ -5,30 +19,31 @@ #include #include +#include #include #include #include "buffer_event_web_stream.h" #include "handshake.h" -PlainServer::PlainServer(int port) - : port_(port), - base_(nullptr), +PlainServer::PlainServer(event_base* base, + int port) + : base_(base), + port_(port), listener_(nullptr) {} PlainServer::~PlainServer() { + active_handshakes_.clear(); + active_streams_.clear(); + if (listener_) { evconnlistener_free(listener_); } - if (base_) { - event_base_free(base_); - } } bool PlainServer::Init() { - base_ = event_base_new(); if (!base_) { - LOG(ERROR) << "Could not initialize libevent!"; + VLOG(1) << "event_base is null"; return false; } @@ -47,7 +62,7 @@ bool PlainServer::Init() { reinterpret_cast(&sin), sizeof(sin)); if (!listener_) { - LOG(ERROR) << "Could not create a listener!"; + VLOG(1) << "Could not create a listener!"; return false; } @@ -60,8 +75,8 @@ void PlainServer::SetOnStream(StreamCallback cb) { on_stream_ = cb; } -void PlainServer::Run() { - event_base_dispatch(base_); +int PlainServer::Run() { + return event_base_dispatch(base_); } void PlainServer::AcceptConnCb(evconnlistener* listener, @@ -82,37 +97,58 @@ void PlainServer::AcceptConnCb(evconnlistener* listener, &one, sizeof(one)); if (set_opt_rv < 0) { - LOG(ERROR) << "setsockopt(TCP_NODELAY) failed: " << strerror(errno); + VLOG(1) << "setsockopt(TCP_NODELAY) failed: " << strerror(errno); } bufferevent* bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE); if (!bev) { - LOG(ERROR) << "bufferevent_socket_new() failed"; + VLOG(1) << "bufferevent_socket_new() failed"; + + if (evutil_closesocket(fd) != 0) { + VLOG(2) << "evutil_closesocket failed"; + } - evutil_closesocket(fd); return; } - auto* handshake = new ServerHandshake( + auto handshake = std::make_unique( bev, [server](bufferevent* bev) { - BufferEventWebStream* stream = new BufferEventWebStream(bev, true); + auto stream = std::make_unique(bev, true); + + if (!stream->Init()) { + VLOG(1) << "BufferEventWebStream::Init() failed"; + + return; + } + + auto* raw_stream = stream.get(); + server->active_streams_.push_back(std::move(stream)); + + raw_stream->SetCleanupCallback([server](BufferEventWebStream* s) { + server->RemoveStream(s); + }); if (server->on_stream_) { - server->on_stream_(stream); + server->on_stream_(raw_stream); } else { - LOG(WARNING) << "Warning: No stream handler registered."; + VLOG(2) << "Warning: No stream handler registered."; } - stream->Start(); + raw_stream->Start(); }, []() { - LOG(ERROR) << "Server handshake failed"; + VLOG(1) << "Server handshake failed"; + }, + [server](ServerHandshake* h) { + server->RemoveHandshake(h); }); - handshake->Start(); + auto* raw_handshake = handshake.get(); + server->active_handshakes_.push_back(std::move(handshake)); + raw_handshake->Start(); } void PlainServer::AcceptErrorCb(evconnlistener* listener, void* ctx) { @@ -121,8 +157,27 @@ void PlainServer::AcceptErrorCb(evconnlistener* listener, void* ctx) { event_base* base = evconnlistener_get_base(listener); int err = EVUTIL_SOCKET_ERROR(); - LOG(ERROR) << "Got an error " << err << " (" - << evutil_socket_error_to_string(err) - << ") on the listener. Shutting down."; - event_base_loopexit(base, nullptr); + VLOG(1) << "Got an error " << err << " (" + << evutil_socket_error_to_string(err) + << ") on the listener. Shutting down."; + + if (event_base_loopexit(base, nullptr) != 0) { + VLOG(2) << "event_base_loopexit failed"; + } +} + +void PlainServer::RemoveHandshake(ServerHandshake* handshake) { + auto it = std::find_if(active_handshakes_.begin(), active_handshakes_.end(), [handshake](const auto& ptr) { return ptr.get() == handshake; }); + if (it != active_handshakes_.end()) { + auto ptr = std::move(*it); + active_handshakes_.erase(it); + } +} + +void PlainServer::RemoveStream(BufferEventWebStream* stream) { + auto it = std::find_if(active_streams_.begin(), active_streams_.end(), [stream](const auto& ptr) { return ptr.get() == stream; }); + if (it != active_streams_.end()) { + auto ptr = std::move(*it); + active_streams_.erase(it); + } } diff --git a/wish/cpp/src/plain_server.h b/wish/cpp/src/plain_server.h index 0897576..899d941 100644 --- a/wish/cpp/src/plain_server.h +++ b/wish/cpp/src/plain_server.h @@ -1,3 +1,19 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #ifndef WISH_CPP_SRC_PLAIN_SERVER_H_ #define WISH_CPP_SRC_PLAIN_SERVER_H_ @@ -6,19 +22,25 @@ #include #include +#include +#include #include "web_stream.h" +class ServerHandshake; +class BufferEventWebStream; + class PlainServer { public: using StreamCallback = std::function; - explicit PlainServer(int port); + PlainServer(event_base* base, + int port); ~PlainServer(); bool Init(); void SetOnStream(StreamCallback cb); - void Run(); + int Run(); private: static void AcceptConnCb(evconnlistener* listener, @@ -29,12 +51,19 @@ class PlainServer { static void AcceptErrorCb(evconnlistener* listener, void* ctx); - int port_; + void RemoveHandshake(ServerHandshake* handshake); + void RemoveStream(BufferEventWebStream* stream); event_base* base_; + + int port_; + evconnlistener* listener_; StreamCallback on_stream_; + + std::vector> active_handshakes_; + std::vector> active_streams_; }; #endif // WISH_CPP_SRC_PLAIN_SERVER_H_ diff --git a/wish/cpp/src/tls_client.cc b/wish/cpp/src/tls_client.cc index 9db345a..fd18ed2 100644 --- a/wish/cpp/src/tls_client.cc +++ b/wish/cpp/src/tls_client.cc @@ -1,37 +1,45 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "tls_client.h" #include #include #include -TlsClient::TlsClient(const std::string& host, +TlsClient::TlsClient(event_base* base, + const std::string& host, int port, const std::string& ca_file, const std::string& cert_file, const std::string& key_file) - : host_(host), + : base_(base), + host_(host), port_(port), ca_file_(ca_file), cert_file_(cert_file), key_file_(key_file), - base_(nullptr), dns_base_(nullptr), stream_(nullptr) {} TlsClient::~TlsClient() { - // Signal the event loop to exit before freeing it. If Run() is still - // executing in another thread (e.g. the caller forgot to call Stop()), - // event_base_loopbreak wakes it up immediately so event_base_free is safe. - if (base_) { - event_base_loopbreak(base_); - } + handshake_.reset(); + stream_.reset(); if (dns_base_) { evdns_base_free(dns_base_, 0); } - if (base_) { - event_base_free(base_); - } } bool TlsClient::Init() { @@ -43,21 +51,20 @@ bool TlsClient::Init() { tls_ctx_.set_private_key_file(key_file_); if (!tls_ctx_.Init(false)) { - LOG(ERROR) << "Failed to init TLS context"; + VLOG(1) << "Failed to init TLS context"; return false; } - base_ = event_base_new(); if (!base_) { - LOG(ERROR) << "event_base_new() failed"; + VLOG(1) << "event_base is null"; return false; } dns_base_ = evdns_base_new(base_, 1); if (!dns_base_) { - LOG(ERROR) << "evdns_base_new() failed"; + VLOG(1) << "evdns_base_new() failed"; return false; } @@ -69,7 +76,7 @@ bool TlsClient::Init() { BUFFEREVENT_SSL_CONNECTING, BEV_OPT_CLOSE_ON_FREE); if (!bev) { - LOG(ERROR) << "bufferevent_openssl_socket_new() failed"; + VLOG(1) << "bufferevent_openssl_socket_new() failed"; SSL_free(ssl); @@ -84,7 +91,7 @@ bool TlsClient::Init() { host_.c_str(), port_); if (connect_rv < 0) { - LOG(ERROR) << "bufferevent_socket_connect_hostname() failed"; + VLOG(1) << "bufferevent_socket_connect_hostname() failed"; bufferevent_free(bev); return false; @@ -93,17 +100,31 @@ bool TlsClient::Init() { handshake_ = std::make_unique( bev, [this](bufferevent* bev) { - stream_ = new BufferEventWebStream(bev, false); + auto s = std::make_unique(bev, false); + + if (!s->Init()) { + VLOG(1) << "BufferEventWebStream::Init() failed"; + + handshake_.reset(); + + return; + } + + stream_ = std::move(s); + stream_->SetCleanupCallback([this](BufferEventWebStream* s) { + stream_.reset(); + }); if (on_open_) { - on_open_(stream_); + on_open_(stream_.get()); } stream_->Start(); handshake_.reset(); }, [this]() { - LOG(ERROR) << "Client handshake failed"; + VLOG(1) << "Client handshake failed"; + handshake_.reset(); }); @@ -116,12 +137,13 @@ void TlsClient::SetOnOpen(OpenCallback cb) { on_open_ = cb; } -void TlsClient::Run() { - event_base_dispatch(base_); +int TlsClient::Run() { + return event_base_dispatch(base_); } -void TlsClient::Stop() { +int TlsClient::Stop() { if (base_) { - event_base_loopexit(base_, nullptr); + return event_base_loopexit(base_, nullptr); } + return -1; } diff --git a/wish/cpp/src/tls_client.h b/wish/cpp/src/tls_client.h index 5c8e3c8..82e25bf 100644 --- a/wish/cpp/src/tls_client.h +++ b/wish/cpp/src/tls_client.h @@ -1,3 +1,19 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #ifndef WISH_CPP_SRC_TLS_CLIENT_H_ #define WISH_CPP_SRC_TLS_CLIENT_H_ @@ -6,9 +22,8 @@ #include #include -#include - #include +#include #include "buffer_event_web_stream.h" #include "handshake.h" @@ -20,7 +35,8 @@ class TlsClient { using MessageCallback = std::function; using CloseCallback = std::function; - TlsClient(const std::string& host, + TlsClient(event_base* base, + const std::string& host, int port, const std::string& ca_file, const std::string& cert_file, @@ -31,10 +47,12 @@ class TlsClient { void SetOnOpen(OpenCallback cb); - void Run(); - void Stop(); + int Run(); + int Stop(); private: + event_base* base_; + std::string host_; int port_; @@ -42,13 +60,12 @@ class TlsClient { std::string cert_file_; std::string key_file_; - event_base* base_; evdns_base* dns_base_; TlsContext tls_ctx_; std::unique_ptr handshake_; - BufferEventWebStream* stream_; + std::unique_ptr stream_; OpenCallback on_open_; }; diff --git a/wish/cpp/src/tls_context.cc b/wish/cpp/src/tls_context.cc index 91cebb7..b38ead4 100644 --- a/wish/cpp/src/tls_context.cc +++ b/wish/cpp/src/tls_context.cc @@ -1,3 +1,17 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "tls_context.h" #include @@ -29,7 +43,7 @@ bool TlsContext::Init(bool is_server) { ssl_ctx_ = SSL_CTX_new(method); if (!ssl_ctx_) { - LOG(ERROR) << "Failed to create SSL_CTX"; + VLOG(1) << "Failed to create SSL_CTX"; return false; } @@ -39,7 +53,7 @@ bool TlsContext::Init(bool is_server) { ca_file_.c_str(), nullptr); if (load_rv != 1) { - LOG(ERROR) << "Error loading CA file: " << ca_file_; + VLOG(1) << "Error loading CA file: " << ca_file_; return false; } @@ -60,7 +74,7 @@ bool TlsContext::Init(bool is_server) { certificate_file_.c_str(), SSL_FILETYPE_PEM); if (cert_rv <= 0) { - LOG(ERROR) << "Error loading certificate file: " << certificate_file_; + VLOG(1) << "Error loading certificate file: " << certificate_file_; return false; } @@ -69,19 +83,19 @@ bool TlsContext::Init(bool is_server) { private_key_file_.c_str(), SSL_FILETYPE_PEM); if (key_rv <= 0) { - LOG(ERROR) << "Error loading key file: " << private_key_file_; + VLOG(1) << "Error loading key file: " << private_key_file_; return false; } int check_rv = SSL_CTX_check_private_key(ssl_ctx_); if (check_rv != 1) { - LOG(ERROR) << "Private key does not match the certificate public key"; + VLOG(1) << "Private key does not match the certificate public key"; return false; } } else { - LOG(WARNING) << "Warning: cert_path or key_path is empty. mTLS may fail."; + VLOG(1) << "Warning: cert_path or key_path is empty. mTLS may fail."; } return true; diff --git a/wish/cpp/src/tls_context.h b/wish/cpp/src/tls_context.h index 71be9bb..b018654 100644 --- a/wish/cpp/src/tls_context.h +++ b/wish/cpp/src/tls_context.h @@ -1,3 +1,19 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #ifndef WISH_CPP_SRC_TLS_CONTEXT_H_ #define WISH_CPP_SRC_TLS_CONTEXT_H_ diff --git a/wish/cpp/src/tls_server.cc b/wish/cpp/src/tls_server.cc index 72235e6..c520bc3 100644 --- a/wish/cpp/src/tls_server.cc +++ b/wish/cpp/src/tls_server.cc @@ -1,3 +1,17 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "tls_server.h" #include @@ -6,29 +20,31 @@ #include #include +#include #include #include "buffer_event_web_stream.h" #include "handshake.h" -TlsServer::TlsServer(int port, +TlsServer::TlsServer(event_base* base, + int port, const std::string& ca_file, const std::string& cert_file, const std::string& key_file) - : port_(port), + : base_(base), + port_(port), ca_file_(ca_file), cert_file_(cert_file), key_file_(key_file), - base_(nullptr), listener_(nullptr) {} TlsServer::~TlsServer() { + active_handshakes_.clear(); + active_streams_.clear(); + if (listener_) { evconnlistener_free(listener_); } - if (base_) { - event_base_free(base_); - } } bool TlsServer::Init() { @@ -40,14 +56,13 @@ bool TlsServer::Init() { tls_ctx_.set_private_key_file(key_file_); if (!tls_ctx_.Init(true)) { - LOG(ERROR) << "Failed to init TLS context"; + VLOG(1) << "Failed to init TLS context"; return false; } - base_ = event_base_new(); if (!base_) { - LOG(ERROR) << "Could not initialize libevent!"; + VLOG(1) << "event_base is null"; return false; } @@ -69,7 +84,7 @@ bool TlsServer::Init() { sizeof(sin)); if (!listener_) { - LOG(ERROR) << "Could not create a listener!"; + VLOG(1) << "Could not create a listener!"; return false; } @@ -79,8 +94,8 @@ bool TlsServer::Init() { return true; } -void TlsServer::Run() { - event_base_dispatch(base_); +int TlsServer::Run() { + return event_base_dispatch(base_); } void TlsServer::SetOnStream(StreamCallback cb) { @@ -102,7 +117,7 @@ void TlsServer::AcceptConnCb(evconnlistener* listener, &one, sizeof(one)); if (set_opt_rv < 0) { - LOG(ERROR) << "setsockopt(TCP_NODELAY) failed: " << strerror(errno); + VLOG(1) << "setsockopt(TCP_NODELAY) failed: " << strerror(errno); } SSL* ssl = SSL_new(server->tls_ctx_.ssl_ctx()); @@ -112,41 +127,84 @@ void TlsServer::AcceptConnCb(evconnlistener* listener, BUFFEREVENT_SSL_ACCEPTING, BEV_OPT_CLOSE_ON_FREE); if (!bev) { - LOG(ERROR) << "bufferevent_openssl_socket_new() failed"; + VLOG(1) << "bufferevent_openssl_socket_new() failed"; SSL_free(ssl); - evutil_closesocket(fd); + + if (evutil_closesocket(fd) != 0) { + VLOG(2) << "evutil_closesocket failed"; + } return; } bufferevent_openssl_set_allow_dirty_shutdown(bev, 1); - auto* handshake = new ServerHandshake( + auto handshake = std::make_unique( bev, [server](bufferevent* bev) { - BufferEventWebStream* stream = new BufferEventWebStream(bev, true); + auto stream = std::make_unique(bev, true); + + if (!stream->Init()) { + VLOG(1) << "BufferEventWebStream::Init() failed"; + + return; + } + + auto* raw_stream = stream.get(); + server->active_streams_.push_back(std::move(stream)); + + raw_stream->SetCleanupCallback([server](BufferEventWebStream* s) { + server->RemoveStream(s); + }); if (server->on_stream_) { - server->on_stream_(stream); + server->on_stream_(raw_stream); } else { - LOG(WARNING) << "Warning: No stream handler registered."; + VLOG(2) << "Warning: No stream handler registered."; } - stream->Start(); + raw_stream->Start(); }, []() { - LOG(ERROR) << "Server handshake failed"; + VLOG(1) << "Server handshake failed"; + }, + [server](ServerHandshake* h) { + server->RemoveHandshake(h); }); - handshake->Start(); + auto* raw_handshake = handshake.get(); + server->active_handshakes_.push_back(std::move(handshake)); + raw_handshake->Start(); } -void TlsServer::AcceptErrorCb(evconnlistener* listener, void* ctx) { +void TlsServer::AcceptErrorCb(evconnlistener* listener, + void* ctx) { + (void)ctx; + event_base* base = evconnlistener_get_base(listener); int err = EVUTIL_SOCKET_ERROR(); - LOG(ERROR) << "Got an error " << err << " (" - << evutil_socket_error_to_string(err) - << ") on the listener. Shutting down."; - event_base_loopexit(base, nullptr); + VLOG(1) << "Got an error " << err << " (" + << evutil_socket_error_to_string(err) + << ") on the listener. Shutting down."; + + if (event_base_loopexit(base, nullptr) != 0) { + VLOG(2) << "event_base_loopexit failed"; + } +} + +void TlsServer::RemoveHandshake(ServerHandshake* handshake) { + auto it = std::find_if(active_handshakes_.begin(), active_handshakes_.end(), [handshake](const auto& ptr) { return ptr.get() == handshake; }); + if (it != active_handshakes_.end()) { + auto ptr = std::move(*it); + active_handshakes_.erase(it); + } +} + +void TlsServer::RemoveStream(BufferEventWebStream* stream) { + auto it = std::find_if(active_streams_.begin(), active_streams_.end(), [stream](const auto& ptr) { return ptr.get() == stream; }); + if (it != active_streams_.end()) { + auto ptr = std::move(*it); + active_streams_.erase(it); + } } diff --git a/wish/cpp/src/tls_server.h b/wish/cpp/src/tls_server.h index f429c65..62786f7 100644 --- a/wish/cpp/src/tls_server.h +++ b/wish/cpp/src/tls_server.h @@ -1,3 +1,19 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #ifndef WISH_CPP_SRC_TLS_SERVER_H_ #define WISH_CPP_SRC_TLS_SERVER_H_ @@ -6,16 +22,22 @@ #include #include +#include #include +#include #include "tls_context.h" #include "web_stream.h" +class ServerHandshake; +class BufferEventWebStream; + class TlsServer { public: using StreamCallback = std::function; - TlsServer(int port, + TlsServer(event_base* base, + int port, const std::string& ca_file, const std::string& cert_file, const std::string& key_file); @@ -23,7 +45,7 @@ class TlsServer { bool Init(); void SetOnStream(StreamCallback cb); - void Run(); + int Run(); private: static void AcceptConnCb(evconnlistener* listener, @@ -34,18 +56,25 @@ class TlsServer { static void AcceptErrorCb(evconnlistener* listener, void* ctx); + void RemoveHandshake(ServerHandshake* handshake); + void RemoveStream(BufferEventWebStream* stream); + + event_base* base_; + int port_; std::string ca_file_; std::string cert_file_; std::string key_file_; - event_base* base_; evconnlistener* listener_; TlsContext tls_ctx_; StreamCallback on_stream_; + + std::vector> active_handshakes_; + std::vector> active_streams_; }; #endif // WISH_CPP_SRC_TLS_SERVER_H_ diff --git a/wish/cpp/src/web_stream.h b/wish/cpp/src/web_stream.h index f2018cd..79857cc 100644 --- a/wish/cpp/src/web_stream.h +++ b/wish/cpp/src/web_stream.h @@ -1,6 +1,23 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #ifndef WISH_CPP_SRC_WEB_STREAM_H_ #define WISH_CPP_SRC_WEB_STREAM_H_ +#include #include #include diff --git a/wish/cpp/src/wish_opcodes.h b/wish/cpp/src/wish_opcodes.h index 8e765fe..47f5889 100644 --- a/wish/cpp/src/wish_opcodes.h +++ b/wish/cpp/src/wish_opcodes.h @@ -1,3 +1,19 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #ifndef WISH_CPP_SRC_WISH_OPCODES_H_ #define WISH_CPP_SRC_WISH_OPCODES_H_