curl-fuzzer C++ reference
Loading...
Searching...
No Matches
ldap_mock_server.cc
Go to the documentation of this file.
1/*
2 * Copyright (C) Max Dymond, <cmeister2@gmail.com>, et al.
3 *
4 * SPDX-License-Identifier: curl
5 */
6
9
11
12#include <algorithm>
13#include <cstddef>
14#include <cstdint>
15#include <limits>
16#include <memory>
17#include <string>
18#include <string_view>
19
22
23namespace proto_fuzzer {
24
25namespace {
26
27// RFC 4511 protocol operation tags used by this peer.
28constexpr unsigned char kSequenceTag = 0x30;
29constexpr unsigned char kBooleanTag = 0x01;
30constexpr unsigned char kIntegerTag = 0x02;
31constexpr unsigned char kEnumeratedTag = 0x0a;
32constexpr unsigned char kOctetStringTag = 0x04;
33constexpr unsigned char kUnbindRequest = 0x42;
34constexpr unsigned char kBindRequest = 0x60;
35constexpr unsigned char kBindResponse = 0x61;
36constexpr unsigned char kSearchRequest = 0x63;
37constexpr unsigned char kSearchResultEntry = 0x64;
38constexpr unsigned char kSearchResultDone = 0x65;
39constexpr unsigned char kAbandonRequest = 0x50;
40constexpr unsigned char kExtendedRequest = 0x77;
41constexpr unsigned char kExtendedResponse = 0x78;
42constexpr unsigned char kSetTag = 0x31;
43constexpr unsigned char kPresentFilter = 0x87;
44constexpr int kLdapSuccess = 0;
45constexpr std::string_view kObjectClass = "objectclass";
46constexpr std::string_view kSupportedSaslMechanisms = "supportedSASLMechanisms";
47constexpr std::string_view kPlainMechanism = "PLAIN";
48
49unsigned char ByteAt(std::string_view bytes, std::size_t index) { return static_cast<unsigned char>(bytes[index]); }
50
51} // namespace
52
57 : scenario_(nullptr),
58 server_chunk_count_(0),
59 next_server_chunk_(0),
60 bind_request_count_(0),
61 search_request_count_(0),
62 mechanism_search_request_count_(0),
63 starttls_request_count_(0),
64 unbind_request_count_(0),
65 server_message_count_(0),
66 scripted_message_count_(0),
67 synthesized_done_count_(0),
68 scripted_bind_response_count_(0),
69 search_message_id_(0),
70 search_active_(false),
71 search_done_sent_(false),
72 scripted_bind_consumed_(false),
73 release_script_chunks_(false),
74 peer_closed_(false),
75 protocol_error_(false),
76 socket_opened_(false) {}
77
82
84const std::string& LdapMockServer::packet_transcript() const { return packet_transcript_; }
85
91void LdapMockServer::ObserveIncomingData(const unsigned char* data, std::size_t size) {
92 if (data == nullptr || size == 0) {
93 return;
94 }
95 pending_client_bytes_.append(reinterpret_cast<const char*>(data), size);
96}
97
99std::size_t LdapMockServer::bind_request_count() const { return bind_request_count_; }
100
102std::size_t LdapMockServer::search_request_count() const { return search_request_count_; }
103
105std::size_t LdapMockServer::mechanism_search_request_count() const { return mechanism_search_request_count_; }
106
108std::size_t LdapMockServer::starttls_request_count() const { return starttls_request_count_; }
109
111std::size_t LdapMockServer::unbind_request_count() const { return unbind_request_count_; }
112
114std::size_t LdapMockServer::server_message_count() const { return server_message_count_; }
115
117std::size_t LdapMockServer::scripted_message_count() const { return scripted_message_count_; }
118
120std::size_t LdapMockServer::synthesized_done_count() const { return synthesized_done_count_; }
121
123std::size_t LdapMockServer::scripted_bind_response_count() const { return scripted_bind_response_count_; }
124
134LdapMockServer::ParseResult LdapMockServer::ParseTlv(std::string_view bytes, std::size_t offset, TlvView* view) {
135 if (view == nullptr) {
136 return ParseResult::kInvalid;
137 }
138 if (offset >= bytes.size()) {
139 return ParseResult::kNeedMore;
140 }
141
142 const unsigned char tag = ByteAt(bytes, offset);
143 if (tag == 0) {
144 // End-of-contents is only valid for indefinite lengths, which LDAP forbids
145 // in the commands this peer accepts.
146 return ParseResult::kInvalid;
147 }
148
149 std::size_t cursor = offset + 1;
150 if (cursor >= bytes.size()) {
151 return ParseResult::kNeedMore;
152 }
153
154 const unsigned char length_byte = ByteAt(bytes, cursor++);
155 std::size_t length = 0;
156 if ((length_byte & 0x80U) == 0) {
157 length = static_cast<std::size_t>(length_byte);
158 } else {
159 const std::size_t length_bytes = static_cast<std::size_t>(length_byte & 0x7fU);
160 if (length_bytes == 0 || length_bytes > 4) {
161 return ParseResult::kInvalid;
162 }
163 if (bytes.size() - cursor < length_bytes) {
164 return ParseResult::kNeedMore;
165 }
166 for (std::size_t index = 0; index < length_bytes; ++index) {
167 length = (length << 8U) | static_cast<std::size_t>(ByteAt(bytes, cursor + index));
168 }
169 cursor += length_bytes;
170 }
171
172 if (length > kMaxClientMessageBytes) {
173 return ParseResult::kInvalid;
174 }
175 if (bytes.size() - cursor < length) {
176 return ParseResult::kNeedMore;
177 }
178
179 view->tag = tag;
180 view->value_offset = cursor;
181 view->value_length = length;
182 view->total_length = cursor - offset + length;
183 return ParseResult::kComplete;
184}
185
194bool LdapMockServer::IsSaslMechanismSearch(std::string_view value) {
195 std::size_t cursor = 0;
196 TlvView field;
197 const auto parse_field = [&value, &cursor](unsigned char tag, TlvView* parsed) {
198 if (ParseTlv(value, cursor, parsed) != ParseResult::kComplete || parsed->tag != tag) {
199 return false;
200 }
201 cursor = parsed->value_offset + parsed->value_length;
202 return true;
203 };
204 const auto is_zero = [&value](const TlvView& parsed) {
205 return parsed.value_length == 1 && ByteAt(value, parsed.value_offset) == 0;
206 };
207
208 if (!parse_field(kOctetStringTag, &field) || field.value_length != 0 || !parse_field(kEnumeratedTag, &field) ||
209 !is_zero(field) || !parse_field(kEnumeratedTag, &field) || !is_zero(field) || !parse_field(kIntegerTag, &field) ||
210 !is_zero(field) || !parse_field(kIntegerTag, &field) || !is_zero(field) || !parse_field(kBooleanTag, &field) ||
211 !is_zero(field) || !parse_field(kPresentFilter, &field) ||
212 value.substr(field.value_offset, field.value_length) != kObjectClass || !parse_field(kSequenceTag, &field) ||
213 cursor != value.size()) {
214 return false;
215 }
216
217 const std::size_t attributes_end = field.value_offset + field.value_length;
218 TlvView attribute;
219 if (ParseTlv(value, field.value_offset, &attribute) != ParseResult::kComplete || attribute.tag != kOctetStringTag ||
220 attribute.value_offset + attribute.value_length != attributes_end) {
221 return false;
222 }
223 return value.substr(attribute.value_offset, attribute.value_length) == kSupportedSaslMechanisms;
224}
225
233LdapMockServer::ParseResult LdapMockServer::ParseMessage(std::string_view bytes, MessageView* message) {
234 if (message == nullptr) {
235 return ParseResult::kInvalid;
236 }
237
238 TlvView outer;
239 ParseResult parsed = ParseTlv(bytes, 0, &outer);
240 if (parsed != ParseResult::kComplete) {
241 return parsed;
242 }
243 if (outer.tag != kSequenceTag || outer.value_length < 2) {
244 return ParseResult::kInvalid;
245 }
246
247 const std::size_t outer_end = outer.value_offset + outer.value_length;
248 TlvView integer;
249 parsed = ParseTlv(bytes, outer.value_offset, &integer);
250 if (parsed != ParseResult::kComplete) {
251 return parsed;
252 }
253 if (integer.tag != kIntegerTag || integer.value_length == 0 || integer.value_length > 4 ||
254 integer.value_offset + integer.value_length > outer_end) {
255 return ParseResult::kInvalid;
256 }
257
258 std::uint32_t message_id = 0;
259 for (std::size_t index = 0; index < integer.value_length; ++index) {
260 message_id = (message_id << 8U) | static_cast<std::uint32_t>(ByteAt(bytes, integer.value_offset + index));
261 }
262
263 const std::size_t operation_offset = integer.value_offset + integer.value_length;
264 if (operation_offset >= outer_end) {
265 return ParseResult::kInvalid;
266 }
267 TlvView operation;
268 parsed = ParseTlv(bytes, operation_offset, &operation);
269 if (parsed != ParseResult::kComplete) {
270 return parsed;
271 }
272 if (operation.value_offset + operation.value_length > outer_end) {
273 return ParseResult::kInvalid;
274 }
275
276 message->op_tag = operation.tag;
277 message->message_id = message_id;
278 message->total_length = outer.total_length;
279 message->sasl_mechanism_query = operation.tag == kSearchRequest &&
280 IsSaslMechanismSearch(bytes.substr(operation.value_offset, operation.value_length));
281 return ParseResult::kComplete;
282}
283
290void LdapMockServer::AppendBerLength(std::string* out, std::size_t length) {
291 if (out == nullptr) {
292 return;
293 }
294 if (length < 128) {
295 out->push_back(static_cast<char>(length));
296 return;
297 }
298
299 unsigned char encoded[sizeof(std::size_t)];
300 std::size_t count = 0;
301 while (length != 0) {
302 encoded[count++] = static_cast<unsigned char>(length & 0xffU);
303 length >>= 8U;
304 }
305 out->push_back(static_cast<char>(0x80U | count));
306 while (count != 0) {
307 out->push_back(static_cast<char>(encoded[--count]));
308 }
309}
310
315void LdapMockServer::AppendTlv(std::string* out, unsigned char tag, std::string_view value) {
316 if (out == nullptr) {
317 return;
318 }
319 out->push_back(static_cast<char>(tag));
320 AppendBerLength(out, value.size());
321 out->append(value.data(), value.size());
322}
323
327void LdapMockServer::AppendBerInteger(std::string* out, std::uint32_t value) {
328 unsigned char encoded[5];
329 std::size_t count = 0;
330 do {
331 encoded[count++] = static_cast<unsigned char>(value & 0xffU);
332 value >>= 8U;
333 } while (value != 0);
334
335 std::string bytes;
336 if ((encoded[count - 1] & 0x80U) != 0) {
337 bytes.push_back('\0');
338 }
339 while (count != 0) {
340 bytes.push_back(static_cast<char>(encoded[--count]));
341 }
342 AppendTlv(out, kIntegerTag, bytes);
343}
344
352void LdapMockServer::BuildResultMessage(std::uint32_t message_id, unsigned char op_tag, int result_code,
353 std::string* out) {
354 if (out == nullptr) {
355 return;
356 }
357
358 std::string result;
359 const int bounded_code = std::max(0, std::min(result_code, 255));
360 const char code_byte = static_cast<char>(bounded_code);
361 AppendTlv(&result, kEnumeratedTag, std::string_view(&code_byte, 1));
362 AppendTlv(&result, kOctetStringTag, std::string_view());
363 AppendTlv(&result, kOctetStringTag, std::string_view());
364
365 std::string content;
366 AppendBerInteger(&content, message_id);
367 AppendTlv(&content, op_tag, result);
368
369 out->clear();
370 AppendTlv(out, kSequenceTag, content);
371}
372
377void LdapMockServer::BuildSaslMechanismsEntry(std::uint32_t message_id, std::string* out) {
378 if (out == nullptr) {
379 return;
380 }
381
382 std::string values;
383 AppendTlv(&values, kOctetStringTag, kPlainMechanism);
384
385 std::string attribute;
386 AppendTlv(&attribute, kOctetStringTag, kSupportedSaslMechanisms);
387 AppendTlv(&attribute, kSetTag, values);
388
389 std::string attributes;
390 AppendTlv(&attributes, kSequenceTag, attribute);
391
392 std::string entry;
393 AppendTlv(&entry, kOctetStringTag, std::string_view());
394 AppendTlv(&entry, kSequenceTag, attributes);
395
396 std::string content;
397 AppendBerInteger(&content, message_id);
398 AppendTlv(&content, kSearchResultEntry, entry);
399
400 out->clear();
401 AppendTlv(out, kSequenceTag, content);
402}
403
412bool LdapMockServer::RewriteMessageId(std::string_view message, std::uint32_t message_id, std::string* out) {
413 if (out == nullptr) {
414 return false;
415 }
416
417 TlvView outer;
418 if (ParseTlv(message, 0, &outer) != ParseResult::kComplete || outer.tag != kSequenceTag ||
419 outer.total_length != message.size()) {
420 return false;
421 }
422
423 TlvView integer;
424 if (ParseTlv(message, outer.value_offset, &integer) != ParseResult::kComplete || integer.tag != kIntegerTag ||
425 integer.value_offset + integer.value_length > outer.value_offset + outer.value_length) {
426 return false;
427 }
428
429 std::string content;
430 AppendBerInteger(&content, message_id);
431 const std::size_t remainder_offset = integer.value_offset + integer.value_length;
432 const std::size_t outer_end = outer.value_offset + outer.value_length;
433 content.append(message.data() + remainder_offset, outer_end - remainder_offset);
434
435 out->clear();
436 AppendTlv(out, kSequenceTag, content);
437 return true;
438}
439
444bool LdapMockServer::MessageOpTag(std::string_view message, unsigned char* op_tag) {
445 if (op_tag == nullptr) {
446 return false;
447 }
448 MessageView decoded;
449 if (ParseMessage(message, &decoded) != ParseResult::kComplete || decoded.total_length != message.size()) {
450 return false;
451 }
452 *op_tag = decoded.op_tag;
453 return true;
454}
455
460void LdapMockServer::ResetForScenario(const curl::fuzzer::proto::Scenario& scenario) {
461 connection_.reset();
462 scenario_ = &scenario;
463 server_chunk_count_ = std::min<std::size_t>(scenario_limits::kMaxResponseChunks,
464 static_cast<std::size_t>(scenario.connection().on_readable_size()));
465 next_server_chunk_ = 0;
466 bind_request_count_ = 0;
467 search_request_count_ = 0;
468 mechanism_search_request_count_ = 0;
469 starttls_request_count_ = 0;
470 unbind_request_count_ = 0;
471 server_message_count_ = 0;
472 scripted_message_count_ = 0;
473 synthesized_done_count_ = 0;
474 scripted_bind_response_count_ = 0;
475 pending_client_bytes_.clear();
476 packet_transcript_.clear();
477 search_message_id_ = 0;
478 search_active_ = false;
479 search_done_sent_ = false;
480 scripted_bind_consumed_ = false;
481 release_script_chunks_ = false;
482 peer_closed_ = false;
483 protocol_error_ = false;
484 socket_opened_ = false;
485}
486
492void LdapMockServer::ApplyScriptBackpressure(MockConnection* connection,
493 const curl::fuzzer::proto::Connection& script) {
494 if (connection == nullptr) {
495 return;
496 }
497
498 const std::uint32_t int_max = static_cast<std::uint32_t>(std::numeric_limits<int>::max());
499 const auto& backpressure = script.backpressure();
500 const int receive_buffer = static_cast<int>(std::min(backpressure.recv_buf_bytes(), int_max));
501 connection->ApplyBackpressure(receive_buffer, static_cast<std::size_t>(backpressure.drain_limit()));
502}
503
507std::unique_ptr<MockConnection> LdapMockServer::CreateConnection() { return std::make_unique<MockConnection>(); }
508
517curl_socket_t LdapMockServer::HandleOpenSocket(curlsocktype purpose, struct curl_sockaddr* /*address*/) {
518 if (scenario_ == nullptr || purpose != CURLSOCKTYPE_IPCXN || socket_opened_) {
519 return CURL_SOCKET_BAD;
520 }
521
523 if (!connection_ || !connection_->ok()) {
524 connection_.reset();
525 return CURL_SOCKET_BAD;
526 }
527 connection_->SetIncomingDataObserver(this);
528
529 ApplyScriptBackpressure(connection_.get(), scenario_->connection());
530 socket_opened_ = true;
531 return connection_->take_client_fd();
532}
533
540void LdapMockServer::CapturePrefix(std::string_view source, std::size_t limit, std::string* destination) {
541 if (destination == nullptr || destination->size() >= limit) {
542 return;
543 }
544 const std::size_t available = limit - destination->size();
545 destination->append(source.data(), std::min(source.size(), available));
546}
547
553bool LdapMockServer::WriteServerBytes(std::string_view bytes) {
554 if (bytes.empty()) {
555 return true;
556 }
557 const bool written = connection_ != nullptr &&
558 connection_->WriteAll(reinterpret_cast<const unsigned char*>(bytes.data()), bytes.size());
559 if (written) {
560 ++server_message_count_;
561 }
562 return written;
563}
564
570void LdapMockServer::HandleBindRequest(std::uint32_t message_id) {
571 ++bind_request_count_;
572
573 std::string response;
574 if (!scripted_bind_consumed_ && scenario_ != nullptr && !scenario_->connection().initial_response().empty()) {
575 const std::string& scripted = scenario_->connection().initial_response();
576 if (!RewriteMessageId(scripted, message_id, &response)) {
577 response = scripted;
578 }
579 scripted_bind_consumed_ = true;
580 ++scripted_bind_response_count_;
581 } else {
582 BuildResultMessage(message_id, kBindResponse, kLdapSuccess, &response);
583 }
584 (void)WriteServerBytes(response);
585}
586
591void LdapMockServer::HandleSearchRequest(std::uint32_t message_id) {
592 ++search_request_count_;
593 search_message_id_ = message_id;
594 search_active_ = true;
595 search_done_sent_ = false;
596 release_script_chunks_ = true;
597 next_server_chunk_ = 0;
598}
599
604void LdapMockServer::HandleMechanismSearchRequest(std::uint32_t message_id) {
605 ++search_request_count_;
606 ++mechanism_search_request_count_;
607
608 std::string entry;
609 BuildSaslMechanismsEntry(message_id, &entry);
610 (void)WriteServerBytes(entry);
611
612 std::string done;
613 BuildResultMessage(message_id, kSearchResultDone, kLdapSuccess, &done);
614 ++synthesized_done_count_;
615 (void)WriteServerBytes(done);
616}
617
625void LdapMockServer::HandleStartTlsRequest(std::uint32_t message_id) {
626 ++starttls_request_count_;
627 std::string response;
628 BuildResultMessage(message_id, kExtendedResponse, kLdapSuccess, &response);
629 (void)WriteServerBytes(response);
630}
631
636void LdapMockServer::HandleClientMessage(const MessageView& message) {
637 switch (message.op_tag) {
638 case kBindRequest:
639 HandleBindRequest(message.message_id);
640 return;
641
642 case kSearchRequest:
643 if (message.sasl_mechanism_query && bind_request_count_ == 0 && mechanism_search_request_count_ == 0) {
644 HandleMechanismSearchRequest(message.message_id);
645 } else {
646 HandleSearchRequest(message.message_id);
647 }
648 return;
649
650 case kExtendedRequest:
651 HandleStartTlsRequest(message.message_id);
652 return;
653
654 case kUnbindRequest:
655 ++unbind_request_count_;
656 return;
657
658 case kAbandonRequest:
659 return;
660
661 default:
662 return;
663 }
664}
665
672bool LdapMockServer::ReleaseNextServerChunk() {
673 if (!release_script_chunks_ || scenario_ == nullptr || connection_ == nullptr ||
674 next_server_chunk_ >= server_chunk_count_) {
675 return false;
676 }
677
678 const std::string& chunk = scenario_->connection().on_readable(static_cast<int>(next_server_chunk_));
679 ++next_server_chunk_;
680
681 if (chunk.empty()) {
682 connection_->ShutdownWrite();
683 peer_closed_ = true;
684 release_script_chunks_ = false;
685 return true;
686 }
687
688 unsigned char op_tag = 0;
689 const bool has_op_tag = MessageOpTag(chunk, &op_tag);
690 std::string rewritten;
691 const bool renumbered = RewriteMessageId(chunk, search_message_id_, &rewritten);
692 const std::string_view bytes = renumbered ? std::string_view(rewritten) : std::string_view(chunk);
693
694 ++scripted_message_count_;
695 (void)WriteServerBytes(bytes);
696 if (has_op_tag && op_tag == kSearchResultDone) {
697 search_done_sent_ = true;
698 release_script_chunks_ = false;
699 }
700 return true;
701}
702
707bool LdapMockServer::FinishSearchIfReady() {
708 if (!search_active_ || search_done_sent_ || peer_closed_ || connection_ == nullptr) {
709 return false;
710 }
711 if (release_script_chunks_ && next_server_chunk_ < server_chunk_count_) {
712 return false;
713 }
714
715 std::string done;
716 BuildResultMessage(search_message_id_, kSearchResultDone, kLdapSuccess, &done);
717 ++synthesized_done_count_;
718 search_done_sent_ = true;
719 release_script_chunks_ = false;
720 return WriteServerBytes(done);
721}
722
729bool LdapMockServer::ServiceClientConnection() {
730 if (connection_ == nullptr || protocol_error_ || peer_closed_) {
731 return false;
732 }
733
734 const std::size_t size_before_read = pending_client_bytes_.size();
735 const std::size_t transport_progress = connection_->DrainIncoming();
736 bool made_progress = pending_client_bytes_.size() != size_before_read || transport_progress != 0;
737
738 std::size_t consumed = 0;
739 int messages = 0;
740 while (consumed < pending_client_bytes_.size() && messages < kMaxMessagesPerService) {
741 const std::string_view available(pending_client_bytes_.data() + consumed, pending_client_bytes_.size() - consumed);
742 MessageView message;
743 const ParseResult parsed = ParseMessage(available, &message);
744 if (parsed == ParseResult::kNeedMore) {
745 break;
746 }
747 if (parsed == ParseResult::kInvalid) {
748 protocol_error_ = true;
749 break;
750 }
751
752 const std::string_view complete = available.substr(0, message.total_length);
753 CapturePrefix(complete, kMaxCapturedMessageBytes, &packet_transcript_);
754 HandleClientMessage(message);
755 consumed += message.total_length;
756 ++messages;
757 made_progress = true;
758 }
759
760 if (protocol_error_) {
761 pending_client_bytes_.clear();
762 // Close the write half immediately so a bogus length cannot wait on the
763 // idle budget for an impossible body.
764 connection_->ShutdownWrite();
765 release_script_chunks_ = false;
766 } else if (consumed != 0) {
767 pending_client_bytes_.erase(0, consumed);
768 }
769 return made_progress;
770}
771
774void LdapMockServer::FinishConnections() {
775 if (connection_ != nullptr) {
776 connection_->ShutdownWrite();
777 }
778}
779
788void LdapMockServer::RunLoop(CURLM* multi, CURL* easy, const curl::fuzzer::proto::Scenario& scenario) {
789 (void)easy;
790 ResetForScenario(scenario);
791
792 int still_running = 1;
793 int idle_iterations = 0;
794 int drive_iterations = 0;
795 CURLMcode result = CURLM_OK;
796 while (still_running && idle_iterations < kMaxIdleIterations && drive_iterations++ < kMaxDriveIterations) {
797 const int running_before = still_running;
798 result = curl_multi_perform(multi, &still_running);
799 if (result != CURLM_OK) {
800 break;
801 }
802
803 bool made_progress = still_running != running_before;
804 made_progress = ServiceClientConnection() || made_progress;
805 made_progress = ReleaseNextServerChunk() || made_progress;
806 made_progress = FinishSearchIfReady() || made_progress;
807 if (made_progress) {
808 idle_iterations = 0;
809 } else {
810 ++idle_iterations;
811 }
812 }
813
814 // The final perform may have emitted a request immediately before marking
815 // the transfer done. Drain those bytes before cleanup.
816 (void)ServiceClientConnection();
817 (void)ReleaseNextServerChunk();
818 (void)FinishSearchIfReady();
819 FinishConnections();
820 scenario_ = nullptr;
821}
822
825#if defined(PROTO_FUZZER_OBSERVE_STATE)
827 const volatile std::size_t ldap_observation[] = {packet_transcript().size(), bind_request_count(),
830 (void)ldap_observation;
831#endif
832}
833
834} // namespace proto_fuzzer
void ObserveState() override
Retain protocol outcomes in coverage and introspector builds.
LdapMockServer()
Begin without a borrowed protobuf or open socketpair.
std::size_t scripted_bind_response_count() const
std::size_t synthesized_done_count() const
std::size_t starttls_request_count() const
std::size_t search_request_count() const
std::size_t mechanism_search_request_count() const
std::size_t unbind_request_count() const
curl_socket_t HandleOpenSocket(curlsocktype purpose=CURLSOCKTYPE_IPCXN, struct curl_sockaddr *address=nullptr) override
Create the single LDAP socketpair.
~LdapMockServer() override
Release the server-owned socketpair half after curl has returned its client descriptor.
std::size_t scripted_message_count() const
void ObserveIncomingData(const unsigned char *data, std::size_t size) override
Append one fragment after the transport has decoded it.
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 server_message_count() const
virtual std::unique_ptr< MockConnection > CreateConnection()
Create the default plaintext transport.
std::size_t bind_request_count() const
const std::string & packet_transcript() const
Owns one half of a socketpair used to feed canned responses to libcurl.
Definition mock_server.h:29
std::unique_ptr< MockConnection > connection_
The per-scenario MockConnection, lazily created by HandleOpenSocket().
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.
BER/ASN.1-aware in-process peer for curl's OpenLDAP-backed LDAP client.
MockConnection and MockServer — the in-process peer that feeds canned response bytes to libcurl over ...
Shared complexity limits for structured scenarios.