diff options
Diffstat (limited to 'pw_rpc/fuzz/client_fuzzer.cc')
-rw-r--r-- | pw_rpc/fuzz/client_fuzzer.cc | 111 |
1 files changed, 111 insertions, 0 deletions
diff --git a/pw_rpc/fuzz/client_fuzzer.cc b/pw_rpc/fuzz/client_fuzzer.cc new file mode 100644 index 000000000..c5086becf --- /dev/null +++ b/pw_rpc/fuzz/client_fuzzer.cc @@ -0,0 +1,111 @@ +// Copyright 2023 The Pigweed Authors +// +// 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 +// +// https://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. + +// clang-format off +#include "pw_rpc/internal/log_config.h" // PW_LOG_* macros must be first. +// clang-format on + +#include <sys/socket.h> + +#include <cstring> + +#include "pw_log/log.h" +#include "pw_rpc/fuzz/argparse.h" +#include "pw_rpc/fuzz/engine.h" +#include "pw_rpc/integration_testing.h" + +namespace pw::rpc::fuzz { +namespace { + +// This client configures a socket read timeout to allow the RPC dispatch thread +// to exit gracefully. +constexpr timeval kSocketReadTimeout = {.tv_sec = 1, .tv_usec = 0}; + +int FuzzClient(int argc, char** argv) { + // TODO(aarongreen): Incorporate descriptions into usage message. + Vector<ArgParserVariant, 5> parsers{ + // Enables additional logging. + BoolParser("-v", "--verbose").set_default(false), + + // The number of actions to perform as part of the test. A value of 0 runs + // indefinitely. + UnsignedParser<size_t>("-n", "--num-actions").set_default(256), + + // The seed value for the PRNG. A value of 0 generates a seed. + UnsignedParser<uint64_t>("-s", "--seed").set_default(0), + + // The time, in milliseconds, that can elapse without triggering an error. + UnsignedParser<size_t>("-t", "--timeout").set_default(5000), + + // The port use to connect to the `test_rpc_server`. + UnsignedParser<uint16_t>("port").set_default(48000)}; + + if (!ParseArgs(parsers, argc, argv).ok()) { + PrintUsage(parsers, argv[0]); + return 1; + } + + bool verbose; + size_t num_actions; + uint64_t seed; + size_t timeout_ms; + uint16_t port; + if (!GetArg(parsers, "--verbose", &verbose).ok() || + !GetArg(parsers, "--num-actions", &num_actions).ok() || + !GetArg(parsers, "--seed", &seed).ok() || + !GetArg(parsers, "--timeout", &timeout_ms).ok() || + !GetArg(parsers, "port", &port).ok()) { + return 1; + } + + if (!seed) { + seed = chrono::SystemClock::now().time_since_epoch().count(); + } + + if (auto status = integration_test::InitializeClient(port); !status.ok()) { + PW_LOG_ERROR("Failed to initialize client: %s", pw_StatusString(status)); + return 1; + } + + // Set read timout on socket to allow + // pw::rpc::integration_test::TerminateClient() to complete. + int fd = integration_test::GetClientSocketFd(); + if (setsockopt(fd, + SOL_SOCKET, + SO_RCVTIMEO, + &kSocketReadTimeout, + sizeof(kSocketReadTimeout)) != 0) { + PW_LOG_ERROR("Failed to configure socket receive timeout with errno=%d", + errno); + return 1; + } + + if (num_actions == 0) { + num_actions = std::numeric_limits<size_t>::max(); + } + + Fuzzer fuzzer(integration_test::client(), integration_test::kChannelId); + fuzzer.set_verbose(verbose); + fuzzer.set_timeout(std::chrono::milliseconds(timeout_ms)); + fuzzer.Run(seed, num_actions); + integration_test::TerminateClient(); + return 0; +} + +} // namespace +} // namespace pw::rpc::fuzz + +int main(int argc, char** argv) { + return pw::rpc::fuzz::FuzzClient(argc, argv); +} |