23namespace proto_fuzzer {
29constexpr unsigned char kMqttConnect = 0x10;
30constexpr unsigned char kMqttPublish = 0x30;
31constexpr unsigned char kMqttPuback = 0x40;
32constexpr unsigned char kMqttSubscribe = 0x80;
33constexpr unsigned char kMqttSuback = 0x90;
34constexpr unsigned char kMqttPingreq = 0xc0;
35constexpr unsigned char kMqttPingresp = 0xd0;
36constexpr unsigned char kMqttDisconnect = 0xe0;
37constexpr std::size_t kMqttProbePublishBytes = 16 * 1024;
38constexpr std::size_t kOversizedMqttCredentialBytes = 0x10000;
40unsigned char ByteAt(std::string_view bytes, std::size_t index) {
return static_cast<unsigned char>(bytes[index]); }
51 server_chunk_count_(0),
52 next_server_chunk_(0),
57 qos1_publish_count_(0),
58 active_pollset_probe_count_(0),
59 release_script_chunks_(false),
60 protocol_error_(false),
61 socket_opened_(false) {}
102void MqttMockServer::ResetForScenario(
const curl::fuzzer::proto::Scenario& scenario) {
104 scenario_ = &scenario;
106 static_cast<std::size_t
>(scenario.connection().on_readable_size()));
107 next_server_chunk_ = 0;
111 subscribe_count_ = 0;
112 qos1_publish_count_ = 0;
113 active_pollset_probe_count_ = 0;
114 pending_client_bytes_.clear();
115 packet_transcript_.clear();
116 published_topic_.clear();
117 published_payload_.clear();
118 release_script_chunks_ =
false;
119 protocol_error_ =
false;
120 socket_opened_ =
false;
128void MqttMockServer::ApplyScriptBackpressure(MockConnection* connection,
129 const curl::fuzzer::proto::Connection& script) {
134 const std::uint32_t int_max =
static_cast<std::uint32_t
>(std::numeric_limits<int>::max());
135 const auto& backpressure = script.backpressure();
136 const int receive_buffer =
static_cast<int>(std::min(backpressure.recv_buf_bytes(), int_max));
137 connection->ApplyBackpressure(receive_buffer,
static_cast<std::size_t
>(backpressure.drain_limit()));
151 if (data ==
nullptr || size == 0) {
154 pending_client_bytes_.append(
reinterpret_cast<const char*
>(data), size);
165 if (scenario_ ==
nullptr || purpose != CURLSOCKTYPE_IPCXN || socket_opened_) {
166 return CURL_SOCKET_BAD;
172 return CURL_SOCKET_BAD;
176 ApplyScriptBackpressure(
connection_.get(), scenario_->connection());
177 const std::string& greeting = scenario_->connection().initial_response();
178 if (!greeting.empty() &&
179 !
connection_->WriteAll(
reinterpret_cast<const unsigned char*
>(greeting.data()), greeting.size())) {
181 return CURL_SOCKET_BAD;
184 socket_opened_ =
true;
195MqttMockServer::ParseResult MqttMockServer::ParseRemainingLength(std::string_view bytes, std::size_t* header_size,
196 std::size_t* remaining_length) {
197 std::size_t value = 0;
198 std::size_t multiplier = 1;
199 for (std::size_t index = 1; index <= 4; ++index) {
200 if (bytes.size() <= index) {
201 return ParseResult::kNeedMore;
203 const unsigned char encoded = ByteAt(bytes, index);
204 value +=
static_cast<std::size_t
>(encoded & 0x7fU) * multiplier;
205 if ((encoded & 0x80U) == 0) {
206 *header_size = index + 1;
207 *remaining_length = value;
208 return ParseResult::kComplete;
212 return ParseResult::kInvalid;
221void MqttMockServer::CapturePrefix(std::string_view source, std::size_t limit, std::string* destination) {
222 if (destination ==
nullptr || destination->size() >= limit) {
225 const std::size_t available = limit - destination->size();
226 destination->append(source.data(), std::min(source.size(), available));
234bool MqttMockServer::WriteServerBytes(std::string_view bytes) {
236 connection_->WriteAll(
reinterpret_cast<const unsigned char*
>(bytes.data()), bytes.size()));
244void MqttMockServer::HandleClientPacket(
unsigned char first_byte, std::string_view body) {
245 const unsigned char type =
static_cast<unsigned char>(first_byte & 0xf0U);
253 if (body.size() < 2) {
256 const std::size_t topic_length =
257 (
static_cast<std::size_t
>(ByteAt(body, 0)) << 8U) |
static_cast<std::size_t
>(ByteAt(body, 1));
258 std::size_t cursor = 2;
259 const std::size_t available = body.size() - cursor;
260 const std::size_t captured_topic = std::min(topic_length, available);
261 CapturePrefix(body.substr(cursor, captured_topic), scenario_limits::kMaxMetadataBytes, &published_topic_);
262 cursor += captured_topic;
263 if (captured_topic < topic_length) {
267 const unsigned qos = (
static_cast<unsigned>(first_byte) >> 1U) & 0x03U;
269 if (body.size() - cursor < 2) {
272 const unsigned char packet_id_high = ByteAt(body, cursor);
273 const unsigned char packet_id_low = ByteAt(body, cursor + 1);
279 const unsigned char puback[] = {kMqttPuback, 0x02, packet_id_high, packet_id_low};
280 (void)WriteServerBytes(std::string_view(
reinterpret_cast<const char*
>(puback),
sizeof(puback)));
281 ++qos1_publish_count_;
284 CapturePrefix(body.substr(cursor), kMaxCapturedPayloadBytes, &published_payload_);
288 case kMqttSubscribe: {
290 const unsigned char packet_id_high = body.empty() ? 0 : ByteAt(body, 0);
291 const unsigned char packet_id_low = body.size() < 2 ? 0 : ByteAt(body, 1);
295 const unsigned char suback[] = {kMqttSuback, 0x03, packet_id_high, packet_id_low, 0x00};
296 (void)WriteServerBytes(std::string_view(
reinterpret_cast<const char*
>(suback),
sizeof(suback)));
299 release_script_chunks_ =
true;
304 const unsigned char pingresp[] = {kMqttPingresp, 0x00};
305 (void)WriteServerBytes(std::string_view(
reinterpret_cast<const char*
>(pingresp),
sizeof(pingresp)));
309 case kMqttDisconnect:
327bool MqttMockServer::ReleaseNextServerChunk() {
328 if (!release_script_chunks_ || scenario_ ==
nullptr ||
connection_ ==
nullptr ||
329 next_server_chunk_ >= server_chunk_count_) {
333 const std::string& chunk = scenario_->connection().on_readable(
static_cast<int>(next_server_chunk_++));
336 release_script_chunks_ =
false;
340 (void)WriteServerBytes(chunk);
341 if (next_server_chunk_ >= server_chunk_count_) {
355bool MqttMockServer::ServiceClientConnection() {
360 const std::size_t size_before_read = pending_client_bytes_.size();
361 const std::size_t transport_progress =
connection_->DrainIncoming();
362 bool made_progress = pending_client_bytes_.size() != size_before_read || transport_progress != 0;
364 std::size_t consumed = 0;
366 while (consumed < pending_client_bytes_.size() && packets < kMaxPacketsPerService) {
367 const std::string_view available(pending_client_bytes_.data() + consumed, pending_client_bytes_.size() - consumed);
368 if (available.size() < 2) {
372 std::size_t header_size = 0;
373 std::size_t remaining_length = 0;
374 const ParseResult parsed = ParseRemainingLength(available, &header_size, &remaining_length);
375 if (parsed == ParseResult::kNeedMore) {
378 if (parsed == ParseResult::kInvalid || remaining_length > kMaxPacketBytes) {
379 protocol_error_ =
true;
383 const std::size_t packet_size = header_size + remaining_length;
384 if (available.size() < packet_size) {
388 const std::string_view packet = available.substr(0, packet_size);
389 CapturePrefix(packet, kMaxCapturedPacketBytes, &packet_transcript_);
391 HandleClientPacket(ByteAt(packet, 0), packet.substr(header_size));
392 consumed += packet_size;
394 made_progress =
true;
397 if (protocol_error_) {
398 pending_client_bytes_.clear();
403 release_script_chunks_ =
false;
404 }
else if (consumed != 0) {
405 pending_client_bytes_.erase(0, consumed);
407 return made_progress;
412void MqttMockServer::FinishConnections() {
422void MqttMockServer::ApplyProbeOptions(CURL* easy)
const {
423 if (easy ==
nullptr) {
428 case curl::fuzzer::proto::MQTT_PROBE_SEND_BACKPRESSURE: {
429 static const std::string publish_body(kMqttProbePublishBytes,
'p');
430 (void)curl_easy_setopt(easy, CURLOPT_POSTFIELDSIZE_LARGE,
static_cast<curl_off_t
>(publish_body.size()));
431 (void)curl_easy_setopt(easy, CURLOPT_POSTFIELDS, publish_body.c_str());
435 case curl::fuzzer::proto::MQTT_PROBE_OVERSIZED_USERNAME: {
436 static const std::string username(kOversizedMqttCredentialBytes,
'u');
437 (void)curl_easy_setopt(easy, CURLOPT_USERNAME, username.c_str());
438 (void)curl_easy_setopt(easy, CURLOPT_PASSWORD,
"");
442 case curl::fuzzer::proto::MQTT_PROBE_OVERSIZED_PASSWORD: {
443 static const std::string password(kOversizedMqttCredentialBytes,
'p');
444 (void)curl_easy_setopt(easy, CURLOPT_USERNAME,
"");
445 (void)curl_easy_setopt(easy, CURLOPT_PASSWORD, password.c_str());
449 case curl::fuzzer::proto::MQTT_PROBE_DEFAULT:
464 ApplyProbeOptions(easy);
465 ResetForScenario(scenario);
467 int still_running = 1;
468 int idle_iterations = 0;
469 int drive_iterations = 0;
470 CURLMcode result = CURLM_OK;
472 const int running_before = still_running;
473 result = curl_multi_perform(multi, &still_running);
474 if (result != CURLM_OK) {
481 if (scenario.probe_multi_pollset() && still_running && socket_opened_) {
483 ++active_pollset_probe_count_;
486 bool made_progress = still_running != running_before;
487 made_progress = ServiceClientConnection() || made_progress;
488 made_progress = ReleaseNextServerChunk() || made_progress;
498 (void)ServiceClientConnection();
499 (void)ReleaseNextServerChunk();
506#if defined(PROTO_FUZZER_OBSERVE_STATE)
510 (void)mqtt_observation;
std::unique_ptr< MockConnection > connection_
The per-scenario MockConnection, lazily created by HandleOpenSocket().
MockConnection * connection()
static void ProbeMultiPollset(CURLM *multi)
Ask curl to construct and inspect its connection-filter pollset without waiting.
virtual void ObserveState()
Default no-op for peers without protocol-specific observations.
static constexpr int kMaxDriveIterations
Hard operation budget for one scenario.
static constexpr int kMaxIdleIterations
Consecutive no-progress budget for ordinary scenarios.
void RunLoop(CURLM *multi, CURL *easy, const curl::fuzzer::proto::Scenario &scenario) override
Alternate curl transitions with immediate peer service until the transfer completes or deterministic ...
std::size_t packet_count() const
const std::string & published_topic() const
std::size_t qos1_publish_count() const
std::size_t subscribe_count() const
void ObserveIncomingData(const unsigned char *data, std::size_t size) override
Append decoded application bytes to the pending parser buffer.
void ObserveState() override
Retain protocol outcomes in coverage and introspector builds.
std::size_t connect_count() const
virtual std::unique_ptr< MockConnection > CreateConnection()
Create the default plaintext transport for the MQTT packet exchange.
const std::string & packet_transcript() const
curl_socket_t HandleOpenSocket(curlsocktype purpose=CURLSOCKTYPE_IPCXN, struct curl_sockaddr *address=nullptr) override
Create the single MQTT socketpair and queue the scenario's first server bytes.
MqttMockServer(curl::fuzzer::proto::MqttProbeMode mode=curl::fuzzer::proto::MQTT_PROBE_DEFAULT)
Begin without a borrowed protobuf or open socketpair.
std::size_t active_pollset_probe_count() const
Report how many requested zero-timeout multi-poll probes ran during the most recent drive.
std::size_t publish_count() const
const std::string & published_payload() const
~MqttMockServer() override
Release the server-owned socketpair half after curl has returned its client descriptor.
MockConnection and MockServer — the in-process peer that feeds canned response bytes to libcurl over ...
Packet-aware in-process peer for curl's binary MQTT state machine.
Shared complexity limits for structured scenarios.
constexpr std::size_t kMaxResponseChunks
Enough response boundaries to exercise incremental parsers while keeping per-perform delivery work fi...