//
// read_until.cpp
// ~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2023 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//

// Disable autolinking for unit tests.
#if !defined(BOOST_ALL_NO_LIB)
#define BOOST_ALL_NO_LIB 1
#endif // !defined(BOOST_ALL_NO_LIB)

// Test that header file is self-contained.
#include "asio/read_until.hpp"

#include <cstring>
#include "archetypes/async_result.hpp"
#include "asio/io_context.hpp"
#include "asio/post.hpp"
#include "asio/streambuf.hpp"
#include "unit_test.hpp"

#if defined(ASIO_HAS_BOOST_BIND)
# include <boost/bind/bind.hpp>
#else // defined(ASIO_HAS_BOOST_BIND)
# include <functional>
#endif // defined(ASIO_HAS_BOOST_BIND)

class test_stream
{
public:
  typedef asio::io_context::executor_type executor_type;

  test_stream(asio::io_context& io_context)
    : io_context_(io_context),
      length_(0),
      position_(0),
      next_read_length_(0)
  {
  }

  executor_type get_executor() ASIO_NOEXCEPT
  {
    return io_context_.get_executor();
  }

  void reset(const void* data, size_t length)
  {
    using namespace std; // For memcpy.

    ASIO_CHECK(length <= max_length);

    memcpy(data_, data, length);
    length_ = length;
    position_ = 0;
    next_read_length_ = length;
  }

  void next_read_length(size_t length)
  {
    next_read_length_ = length;
  }

  template <typename Mutable_Buffers>
  size_t read_some(const Mutable_Buffers& buffers)
  {
    size_t n = asio::buffer_copy(buffers,
        asio::buffer(data_, length_) + position_,
        next_read_length_);
    position_ += n;
    return n;
  }

  template <typename Mutable_Buffers>
  size_t read_some(const Mutable_Buffers& buffers,
      asio::error_code& ec)
  {
    ec = asio::error_code();
    return read_some(buffers);
  }

  template <typename Mutable_Buffers, typename Handler>
  void async_read_some(const Mutable_Buffers& buffers, Handler handler)
  {
    size_t bytes_transferred = read_some(buffers);
    asio::post(get_executor(),
        asio::detail::bind_handler(
          ASIO_MOVE_CAST(Handler)(handler),
          asio::error_code(), bytes_transferred));
  }

private:
  asio::io_context& io_context_;
  enum { max_length = 8192 };
  char data_[max_length];
  size_t length_;
  size_t position_;
  size_t next_read_length_;
};

static const char read_data[]
  = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

void test_dynamic_string_read_until_char()
{
  asio::io_context ioc;
  test_stream s(ioc);
  std::string data1, data2;
  asio::dynamic_string_buffer<char, std::string::traits_type,
    std::string::allocator_type> sb1 = asio::dynamic_buffer(data1);
  asio::dynamic_string_buffer<char, std::string::traits_type,
    std::string::allocator_type> sb2 = asio::dynamic_buffer(data2, 25);
  asio::error_code ec;

  s.reset(read_data, sizeof(read_data));
  sb1.consume(sb1.size());
  std::size_t length = asio::read_until(s, sb1, 'Z');
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  sb1.consume(sb1.size());
  length = asio::read_until(s, sb1, 'Z');
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  sb1.consume(sb1.size());
  length = asio::read_until(s, sb1, 'Z');
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  sb1.consume(sb1.size());
  length = asio::read_until(s, sb1, 'Z', ec);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  sb1.consume(sb1.size());
  length = asio::read_until(s, sb1, 'Z', ec);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  sb1.consume(sb1.size());
  length = asio::read_until(s, sb1, 'Z', ec);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  sb2.consume(sb2.size());
  length = asio::read_until(s, sb2, 'Z', ec);
  ASIO_CHECK(ec == asio::error::not_found);
  ASIO_CHECK(length == 0);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  sb2.consume(sb2.size());
  length = asio::read_until(s, sb2, 'Z', ec);
  ASIO_CHECK(ec == asio::error::not_found);
  ASIO_CHECK(length == 0);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  sb2.consume(sb2.size());
  length = asio::read_until(s, sb2, 'Z', ec);
  ASIO_CHECK(ec == asio::error::not_found);
  ASIO_CHECK(length == 0);

  s.reset(read_data, sizeof(read_data));
  sb2.consume(sb2.size());
  length = asio::read_until(s, sb2, 'Y', ec);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 25);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  sb2.consume(sb2.size());
  length = asio::read_until(s, sb2, 'Y', ec);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 25);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  sb2.consume(sb2.size());
  length = asio::read_until(s, sb2, 'Y', ec);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 25);
}

void test_streambuf_read_until_char()
{
#if !defined(ASIO_NO_DYNAMIC_BUFFER_V1)
  asio::io_context ioc;
  test_stream s(ioc);
  asio::streambuf sb1;
  asio::streambuf sb2(25);
  asio::error_code ec;

  s.reset(read_data, sizeof(read_data));
  sb1.consume(sb1.size());
  std::size_t length = asio::read_until(s, sb1, 'Z');
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  sb1.consume(sb1.size());
  length = asio::read_until(s, sb1, 'Z');
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  sb1.consume(sb1.size());
  length = asio::read_until(s, sb1, 'Z');
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  sb1.consume(sb1.size());
  length = asio::read_until(s, sb1, 'Z', ec);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  sb1.consume(sb1.size());
  length = asio::read_until(s, sb1, 'Z', ec);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  sb1.consume(sb1.size());
  length = asio::read_until(s, sb1, 'Z', ec);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  sb2.consume(sb2.size());
  length = asio::read_until(s, sb2, 'Z', ec);
  ASIO_CHECK(ec == asio::error::not_found);
  ASIO_CHECK(length == 0);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  sb2.consume(sb2.size());
  length = asio::read_until(s, sb2, 'Z', ec);
  ASIO_CHECK(ec == asio::error::not_found);
  ASIO_CHECK(length == 0);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  sb2.consume(sb2.size());
  length = asio::read_until(s, sb2, 'Z', ec);
  ASIO_CHECK(ec == asio::error::not_found);
  ASIO_CHECK(length == 0);

  s.reset(read_data, sizeof(read_data));
  sb2.consume(sb2.size());
  length = asio::read_until(s, sb2, 'Y', ec);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 25);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  sb2.consume(sb2.size());
  length = asio::read_until(s, sb2, 'Y', ec);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 25);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  sb2.consume(sb2.size());
  length = asio::read_until(s, sb2, 'Y', ec);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 25);
#endif // !defined(ASIO_NO_DYNAMIC_BUFFER_V1)
}

void test_dynamic_string_read_until_string()
{
  asio::io_context ioc;
  test_stream s(ioc);
  std::string data1, data2;
  asio::dynamic_string_buffer<char, std::string::traits_type,
    std::string::allocator_type> sb1 = asio::dynamic_buffer(data1);
  asio::dynamic_string_buffer<char, std::string::traits_type,
    std::string::allocator_type> sb2 = asio::dynamic_buffer(data2, 25);
  asio::error_code ec;

  s.reset(read_data, sizeof(read_data));
  sb1.consume(sb1.size());
  std::size_t length = asio::read_until(s, sb1, "XYZ");
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  sb1.consume(sb1.size());
  length = asio::read_until(s, sb1, "XYZ");
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  sb1.consume(sb1.size());
  length = asio::read_until(s, sb1, "XYZ");
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  sb1.consume(sb1.size());
  length = asio::read_until(s, sb1, "XYZ", ec);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  sb1.consume(sb1.size());
  length = asio::read_until(s, sb1, "XYZ", ec);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  sb1.consume(sb1.size());
  length = asio::read_until(s, sb1, "XYZ", ec);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  sb2.consume(sb2.size());
  length = asio::read_until(s, sb2, "XYZ", ec);
  ASIO_CHECK(ec == asio::error::not_found);
  ASIO_CHECK(length == 0);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  sb2.consume(sb2.size());
  length = asio::read_until(s, sb2, "XYZ", ec);
  ASIO_CHECK(ec == asio::error::not_found);
  ASIO_CHECK(length == 0);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  sb2.consume(sb2.size());
  length = asio::read_until(s, sb2, "XYZ", ec);
  ASIO_CHECK(ec == asio::error::not_found);
  ASIO_CHECK(length == 0);

  s.reset(read_data, sizeof(read_data));
  sb2.consume(sb2.size());
  length = asio::read_until(s, sb2, "WXY", ec);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 25);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  sb2.consume(sb2.size());
  length = asio::read_until(s, sb2, "WXY", ec);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 25);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  sb2.consume(sb2.size());
  length = asio::read_until(s, sb2, "WXY", ec);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 25);
}

void test_streambuf_read_until_string()
{
#if !defined(ASIO_NO_DYNAMIC_BUFFER_V1)
  asio::io_context ioc;
  test_stream s(ioc);
  asio::streambuf sb1;
  asio::streambuf sb2(25);
  asio::error_code ec;

  s.reset(read_data, sizeof(read_data));
  sb1.consume(sb1.size());
  std::size_t length = asio::read_until(s, sb1, "XYZ");
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  sb1.consume(sb1.size());
  length = asio::read_until(s, sb1, "XYZ");
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  sb1.consume(sb1.size());
  length = asio::read_until(s, sb1, "XYZ");
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  sb1.consume(sb1.size());
  length = asio::read_until(s, sb1, "XYZ", ec);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  sb1.consume(sb1.size());
  length = asio::read_until(s, sb1, "XYZ", ec);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  sb1.consume(sb1.size());
  length = asio::read_until(s, sb1, "XYZ", ec);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  sb2.consume(sb2.size());
  length = asio::read_until(s, sb2, "XYZ", ec);
  ASIO_CHECK(ec == asio::error::not_found);
  ASIO_CHECK(length == 0);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  sb2.consume(sb2.size());
  length = asio::read_until(s, sb2, "XYZ", ec);
  ASIO_CHECK(ec == asio::error::not_found);
  ASIO_CHECK(length == 0);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  sb2.consume(sb2.size());
  length = asio::read_until(s, sb2, "XYZ", ec);
  ASIO_CHECK(ec == asio::error::not_found);
  ASIO_CHECK(length == 0);

  s.reset(read_data, sizeof(read_data));
  sb2.consume(sb2.size());
  length = asio::read_until(s, sb2, "WXY", ec);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 25);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  sb2.consume(sb2.size());
  length = asio::read_until(s, sb2, "WXY", ec);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 25);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  sb2.consume(sb2.size());
  length = asio::read_until(s, sb2, "WXY", ec);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 25);
#endif // !defined(ASIO_NO_DYNAMIC_BUFFER_V1)
}

class match_char
{
public:
  explicit match_char(char c) : c_(c) {}

  template <typename Iterator>
  std::pair<Iterator, bool> operator()(
      Iterator begin, Iterator end) const
  {
    Iterator i = begin;
    while (i != end)
      if (c_ == *i++)
        return std::make_pair(i, true);
    return std::make_pair(i, false);
  }

private:
  char c_;
};

namespace asio {
  template <> struct is_match_condition<match_char>
  {
    enum { value = true };
  };
} // namespace asio

void test_dynamic_string_read_until_match_condition()
{
  asio::io_context ioc;
  test_stream s(ioc);
  std::string data1, data2;
  asio::dynamic_string_buffer<char, std::string::traits_type,
    std::string::allocator_type> sb1 = asio::dynamic_buffer(data1);
  asio::dynamic_string_buffer<char, std::string::traits_type,
    std::string::allocator_type> sb2 = asio::dynamic_buffer(data2, 25);
  asio::error_code ec;

  s.reset(read_data, sizeof(read_data));
  sb1.consume(sb1.size());
  std::size_t length = asio::read_until(s, sb1, match_char('Z'));
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  sb1.consume(sb1.size());
  length = asio::read_until(s, sb1, match_char('Z'));
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  sb1.consume(sb1.size());
  length = asio::read_until(s, sb1, match_char('Z'));
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  sb1.consume(sb1.size());
  length = asio::read_until(s, sb1, match_char('Z'), ec);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  sb1.consume(sb1.size());
  length = asio::read_until(s, sb1, match_char('Z'), ec);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  sb1.consume(sb1.size());
  length = asio::read_until(s, sb1, match_char('Z'), ec);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  sb2.consume(sb2.size());
  length = asio::read_until(s, sb2, match_char('Z'), ec);
  ASIO_CHECK(ec == asio::error::not_found);
  ASIO_CHECK(length == 0);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  sb2.consume(sb2.size());
  length = asio::read_until(s, sb2, match_char('Z'), ec);
  ASIO_CHECK(ec == asio::error::not_found);
  ASIO_CHECK(length == 0);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  sb2.consume(sb2.size());
  length = asio::read_until(s, sb2, match_char('Z'), ec);
  ASIO_CHECK(ec == asio::error::not_found);
  ASIO_CHECK(length == 0);

  s.reset(read_data, sizeof(read_data));
  sb2.consume(sb2.size());
  length = asio::read_until(s, sb2, match_char('Y'), ec);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 25);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  sb2.consume(sb2.size());
  length = asio::read_until(s, sb2, match_char('Y'), ec);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 25);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  sb2.consume(sb2.size());
  length = asio::read_until(s, sb2, match_char('Y'), ec);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 25);
}

void test_streambuf_read_until_match_condition()
{
#if !defined(ASIO_NO_DYNAMIC_BUFFER_V1)
  asio::io_context ioc;
  test_stream s(ioc);
  asio::streambuf sb1;
  asio::streambuf sb2(25);
  asio::error_code ec;

  s.reset(read_data, sizeof(read_data));
  sb1.consume(sb1.size());
  std::size_t length = asio::read_until(s, sb1, match_char('Z'));
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  sb1.consume(sb1.size());
  length = asio::read_until(s, sb1, match_char('Z'));
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  sb1.consume(sb1.size());
  length = asio::read_until(s, sb1, match_char('Z'));
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  sb1.consume(sb1.size());
  length = asio::read_until(s, sb1, match_char('Z'), ec);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  sb1.consume(sb1.size());
  length = asio::read_until(s, sb1, match_char('Z'), ec);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  sb1.consume(sb1.size());
  length = asio::read_until(s, sb1, match_char('Z'), ec);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  sb2.consume(sb2.size());
  length = asio::read_until(s, sb2, match_char('Z'), ec);
  ASIO_CHECK(ec == asio::error::not_found);
  ASIO_CHECK(length == 0);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  sb2.consume(sb2.size());
  length = asio::read_until(s, sb2, match_char('Z'), ec);
  ASIO_CHECK(ec == asio::error::not_found);
  ASIO_CHECK(length == 0);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  sb2.consume(sb2.size());
  length = asio::read_until(s, sb2, match_char('Z'), ec);
  ASIO_CHECK(ec == asio::error::not_found);
  ASIO_CHECK(length == 0);

  s.reset(read_data, sizeof(read_data));
  sb2.consume(sb2.size());
  length = asio::read_until(s, sb2, match_char('Y'), ec);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 25);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  sb2.consume(sb2.size());
  length = asio::read_until(s, sb2, match_char('Y'), ec);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 25);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  sb2.consume(sb2.size());
  length = asio::read_until(s, sb2, match_char('Y'), ec);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 25);
#endif // !defined(ASIO_NO_DYNAMIC_BUFFER_V1)
}

void async_read_handler(
    const asio::error_code& err, asio::error_code* err_out,
    std::size_t bytes_transferred, std::size_t* bytes_out, bool* called)
{
  *err_out = err;
  *bytes_out = bytes_transferred;
  *called = true;
}

void test_dynamic_string_async_read_until_char()
{
#if defined(ASIO_HAS_BOOST_BIND)
  namespace bindns = boost;
#else // defined(ASIO_HAS_BOOST_BIND)
  namespace bindns = std;
#endif // defined(ASIO_HAS_BOOST_BIND)
  using bindns::placeholders::_1;
  using bindns::placeholders::_2;

  asio::io_context ioc;
  test_stream s(ioc);
  std::string data1, data2;
  asio::dynamic_string_buffer<char, std::string::traits_type,
    std::string::allocator_type> sb1 = asio::dynamic_buffer(data1);
  asio::dynamic_string_buffer<char, std::string::traits_type,
    std::string::allocator_type> sb2 = asio::dynamic_buffer(data2, 25);
  asio::error_code ec;
  std::size_t length;
  bool called;

  s.reset(read_data, sizeof(read_data));
  ec = asio::error_code();
  length = 0;
  called = false;
  sb1.consume(sb1.size());
  asio::async_read_until(s, sb1, 'Z',
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  ec = asio::error_code();
  length = 0;
  called = false;
  sb1.consume(sb1.size());
  asio::async_read_until(s, sb1, 'Z',
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  ec = asio::error_code();
  length = 0;
  called = false;
  sb1.consume(sb1.size());
  asio::async_read_until(s, sb1, 'Z',
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  ec = asio::error_code();
  length = 0;
  called = false;
  sb2.consume(sb2.size());
  asio::async_read_until(s, sb2, 'Z',
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(ec == asio::error::not_found);
  ASIO_CHECK(length == 0);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  ec = asio::error_code();
  length = 0;
  called = false;
  sb2.consume(sb2.size());
  asio::async_read_until(s, sb2, 'Z',
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(ec == asio::error::not_found);
  ASIO_CHECK(length == 0);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  ec = asio::error_code();
  length = 0;
  called = false;
  sb2.consume(sb2.size());
  asio::async_read_until(s, sb2, 'Z',
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(ec == asio::error::not_found);
  ASIO_CHECK(length == 0);

  s.reset(read_data, sizeof(read_data));
  ec = asio::error_code();
  length = 0;
  called = false;
  sb2.consume(sb2.size());
  asio::async_read_until(s, sb2, 'Y',
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 25);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  ec = asio::error_code();
  length = 0;
  called = false;
  sb2.consume(sb2.size());
  asio::async_read_until(s, sb2, 'Y',
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 25);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  ec = asio::error_code();
  length = 0;
  called = false;
  sb2.consume(sb2.size());
  asio::async_read_until(s, sb2, 'Y',
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 25);

  s.reset(read_data, sizeof(read_data));
  sb2.consume(sb2.size());
  int i = asio::async_read_until(s, sb2, 'Y',
      archetypes::lazy_handler());
  ASIO_CHECK(i == 42);
  ioc.restart();
  ioc.run();
}

void test_streambuf_async_read_until_char()
{
#if !defined(ASIO_NO_DYNAMIC_BUFFER_V1)
#if defined(ASIO_HAS_BOOST_BIND)
  namespace bindns = boost;
#else // defined(ASIO_HAS_BOOST_BIND)
  namespace bindns = std;
#endif // defined(ASIO_HAS_BOOST_BIND)
  using bindns::placeholders::_1;
  using bindns::placeholders::_2;

  asio::io_context ioc;
  test_stream s(ioc);
  asio::streambuf sb1;
  asio::streambuf sb2(25);
  asio::error_code ec;
  std::size_t length;
  bool called;

  s.reset(read_data, sizeof(read_data));
  ec = asio::error_code();
  length = 0;
  called = false;
  sb1.consume(sb1.size());
  asio::async_read_until(s, sb1, 'Z',
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  ec = asio::error_code();
  length = 0;
  called = false;
  sb1.consume(sb1.size());
  asio::async_read_until(s, sb1, 'Z',
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  ec = asio::error_code();
  length = 0;
  called = false;
  sb1.consume(sb1.size());
  asio::async_read_until(s, sb1, 'Z',
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  ec = asio::error_code();
  length = 0;
  called = false;
  sb2.consume(sb2.size());
  asio::async_read_until(s, sb2, 'Z',
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(ec == asio::error::not_found);
  ASIO_CHECK(length == 0);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  ec = asio::error_code();
  length = 0;
  called = false;
  sb2.consume(sb2.size());
  asio::async_read_until(s, sb2, 'Z',
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(ec == asio::error::not_found);
  ASIO_CHECK(length == 0);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  ec = asio::error_code();
  length = 0;
  called = false;
  sb2.consume(sb2.size());
  asio::async_read_until(s, sb2, 'Z',
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(ec == asio::error::not_found);
  ASIO_CHECK(length == 0);

  s.reset(read_data, sizeof(read_data));
  ec = asio::error_code();
  length = 0;
  called = false;
  sb2.consume(sb2.size());
  asio::async_read_until(s, sb2, 'Y',
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 25);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  ec = asio::error_code();
  length = 0;
  called = false;
  sb2.consume(sb2.size());
  asio::async_read_until(s, sb2, 'Y',
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 25);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  ec = asio::error_code();
  length = 0;
  called = false;
  sb2.consume(sb2.size());
  asio::async_read_until(s, sb2, 'Y',
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 25);

  s.reset(read_data, sizeof(read_data));
  sb2.consume(sb2.size());
  int i = asio::async_read_until(s, sb2, 'Y',
      archetypes::lazy_handler());
  ASIO_CHECK(i == 42);
  ioc.restart();
  ioc.run();
#endif // !defined(ASIO_NO_DYNAMIC_BUFFER_V1)
}

void test_dynamic_string_async_read_until_string()
{
#if defined(ASIO_HAS_BOOST_BIND)
  namespace bindns = boost;
#else // defined(ASIO_HAS_BOOST_BIND)
  namespace bindns = std;
#endif // defined(ASIO_HAS_BOOST_BIND)
  using bindns::placeholders::_1;
  using bindns::placeholders::_2;

  asio::io_context ioc;
  test_stream s(ioc);
  std::string data1, data2;
  asio::dynamic_string_buffer<char, std::string::traits_type,
    std::string::allocator_type> sb1 = asio::dynamic_buffer(data1);
  asio::dynamic_string_buffer<char, std::string::traits_type,
    std::string::allocator_type> sb2 = asio::dynamic_buffer(data2, 25);
  asio::error_code ec;
  std::size_t length;
  bool called;

  s.reset(read_data, sizeof(read_data));
  ec = asio::error_code();
  length = 0;
  called = false;
  sb1.consume(sb1.size());
  asio::async_read_until(s, sb1, "XYZ",
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  ec = asio::error_code();
  length = 0;
  called = false;
  sb1.consume(sb1.size());
  asio::async_read_until(s, sb1, "XYZ",
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  ec = asio::error_code();
  length = 0;
  called = false;
  sb1.consume(sb1.size());
  asio::async_read_until(s, sb1, "XYZ",
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  ec = asio::error_code();
  length = 0;
  called = false;
  sb2.consume(sb2.size());
  asio::async_read_until(s, sb2, "XYZ",
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(ec == asio::error::not_found);
  ASIO_CHECK(length == 0);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  ec = asio::error_code();
  length = 0;
  called = false;
  sb2.consume(sb2.size());
  asio::async_read_until(s, sb2, "XYZ",
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(ec == asio::error::not_found);
  ASIO_CHECK(length == 0);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  ec = asio::error_code();
  length = 0;
  called = false;
  sb2.consume(sb2.size());
  asio::async_read_until(s, sb2, "XYZ",
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(ec == asio::error::not_found);
  ASIO_CHECK(length == 0);

  s.reset(read_data, sizeof(read_data));
  ec = asio::error_code();
  length = 0;
  called = false;
  sb2.consume(sb2.size());
  asio::async_read_until(s, sb2, "WXY",
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 25);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  ec = asio::error_code();
  length = 0;
  called = false;
  sb2.consume(sb2.size());
  asio::async_read_until(s, sb2, "WXY",
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 25);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  ec = asio::error_code();
  length = 0;
  called = false;
  sb2.consume(sb2.size());
  asio::async_read_until(s, sb2, "WXY",
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 25);

  s.reset(read_data, sizeof(read_data));
  sb2.consume(sb2.size());
  int i = asio::async_read_until(s, sb2, "WXY",
      archetypes::lazy_handler());
  ASIO_CHECK(i == 42);
  ioc.restart();
  ioc.run();
}

void test_streambuf_async_read_until_string()
{
#if !defined(ASIO_NO_DYNAMIC_BUFFER_V1)
#if defined(ASIO_HAS_BOOST_BIND)
  namespace bindns = boost;
#else // defined(ASIO_HAS_BOOST_BIND)
  namespace bindns = std;
#endif // defined(ASIO_HAS_BOOST_BIND)
  using bindns::placeholders::_1;
  using bindns::placeholders::_2;

  asio::io_context ioc;
  test_stream s(ioc);
  asio::streambuf sb1;
  asio::streambuf sb2(25);
  asio::error_code ec;
  std::size_t length;
  bool called;

  s.reset(read_data, sizeof(read_data));
  ec = asio::error_code();
  length = 0;
  called = false;
  sb1.consume(sb1.size());
  asio::async_read_until(s, sb1, "XYZ",
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  ec = asio::error_code();
  length = 0;
  called = false;
  sb1.consume(sb1.size());
  asio::async_read_until(s, sb1, "XYZ",
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  ec = asio::error_code();
  length = 0;
  called = false;
  sb1.consume(sb1.size());
  asio::async_read_until(s, sb1, "XYZ",
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  ec = asio::error_code();
  length = 0;
  called = false;
  sb2.consume(sb2.size());
  asio::async_read_until(s, sb2, "XYZ",
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(ec == asio::error::not_found);
  ASIO_CHECK(length == 0);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  ec = asio::error_code();
  length = 0;
  called = false;
  sb2.consume(sb2.size());
  asio::async_read_until(s, sb2, "XYZ",
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(ec == asio::error::not_found);
  ASIO_CHECK(length == 0);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  ec = asio::error_code();
  length = 0;
  called = false;
  sb2.consume(sb2.size());
  asio::async_read_until(s, sb2, "XYZ",
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(ec == asio::error::not_found);
  ASIO_CHECK(length == 0);

  s.reset(read_data, sizeof(read_data));
  ec = asio::error_code();
  length = 0;
  called = false;
  sb2.consume(sb2.size());
  asio::async_read_until(s, sb2, "WXY",
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 25);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  ec = asio::error_code();
  length = 0;
  called = false;
  sb2.consume(sb2.size());
  asio::async_read_until(s, sb2, "WXY",
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 25);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  ec = asio::error_code();
  length = 0;
  called = false;
  sb2.consume(sb2.size());
  asio::async_read_until(s, sb2, "WXY",
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 25);

  s.reset(read_data, sizeof(read_data));
  sb2.consume(sb2.size());
  int i = asio::async_read_until(s, sb2, "WXY",
      archetypes::lazy_handler());
  ASIO_CHECK(i == 42);
  ioc.restart();
  ioc.run();
#endif // !defined(ASIO_NO_DYNAMIC_BUFFER_V1)
}

void test_dynamic_string_async_read_until_match_condition()
{
#if defined(ASIO_HAS_BOOST_BIND)
  namespace bindns = boost;
#else // defined(ASIO_HAS_BOOST_BIND)
  namespace bindns = std;
#endif // defined(ASIO_HAS_BOOST_BIND)
  using bindns::placeholders::_1;
  using bindns::placeholders::_2;

  asio::io_context ioc;
  test_stream s(ioc);
  std::string data1, data2;
  asio::dynamic_string_buffer<char, std::string::traits_type,
    std::string::allocator_type> sb1 = asio::dynamic_buffer(data1);
  asio::dynamic_string_buffer<char, std::string::traits_type,
    std::string::allocator_type> sb2 = asio::dynamic_buffer(data2, 25);
  asio::error_code ec;
  std::size_t length;
  bool called;

  s.reset(read_data, sizeof(read_data));
  ec = asio::error_code();
  length = 0;
  called = false;
  sb1.consume(sb1.size());
  asio::async_read_until(s, sb1, match_char('Z'),
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  ec = asio::error_code();
  length = 0;
  called = false;
  sb1.consume(sb1.size());
  asio::async_read_until(s, sb1, match_char('Z'),
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  ec = asio::error_code();
  length = 0;
  called = false;
  sb1.consume(sb1.size());
  asio::async_read_until(s, sb1, match_char('Z'),
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  ec = asio::error_code();
  length = 0;
  called = false;
  sb2.consume(sb2.size());
  asio::async_read_until(s, sb2, match_char('Z'),
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(ec == asio::error::not_found);
  ASIO_CHECK(length == 0);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  ec = asio::error_code();
  length = 0;
  called = false;
  sb2.consume(sb2.size());
  asio::async_read_until(s, sb2, match_char('Z'),
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(ec == asio::error::not_found);
  ASIO_CHECK(length == 0);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  ec = asio::error_code();
  length = 0;
  called = false;
  sb2.consume(sb2.size());
  asio::async_read_until(s, sb2, match_char('Z'),
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(ec == asio::error::not_found);
  ASIO_CHECK(length == 0);

  s.reset(read_data, sizeof(read_data));
  ec = asio::error_code();
  length = 0;
  called = false;
  sb2.consume(sb2.size());
  asio::async_read_until(s, sb2, match_char('Y'),
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 25);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  ec = asio::error_code();
  length = 0;
  called = false;
  sb2.consume(sb2.size());
  asio::async_read_until(s, sb2, match_char('Y'),
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 25);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  ec = asio::error_code();
  length = 0;
  called = false;
  sb2.consume(sb2.size());
  asio::async_read_until(s, sb2, match_char('Y'),
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 25);

  s.reset(read_data, sizeof(read_data));
  sb2.consume(sb2.size());
  int i = asio::async_read_until(s, sb2, match_char('Y'),
      archetypes::lazy_handler());
  ASIO_CHECK(i == 42);
  ioc.restart();
  ioc.run();
}

void test_streambuf_async_read_until_match_condition()
{
#if !defined(ASIO_NO_DYNAMIC_BUFFER_V1)
#if defined(ASIO_HAS_BOOST_BIND)
  namespace bindns = boost;
#else // defined(ASIO_HAS_BOOST_BIND)
  namespace bindns = std;
#endif // defined(ASIO_HAS_BOOST_BIND)
  using bindns::placeholders::_1;
  using bindns::placeholders::_2;

  asio::io_context ioc;
  test_stream s(ioc);
  asio::streambuf sb1;
  asio::streambuf sb2(25);
  asio::error_code ec;
  std::size_t length;
  bool called;

  s.reset(read_data, sizeof(read_data));
  ec = asio::error_code();
  length = 0;
  called = false;
  sb1.consume(sb1.size());
  asio::async_read_until(s, sb1, match_char('Z'),
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  ec = asio::error_code();
  length = 0;
  called = false;
  sb1.consume(sb1.size());
  asio::async_read_until(s, sb1, match_char('Z'),
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  ec = asio::error_code();
  length = 0;
  called = false;
  sb1.consume(sb1.size());
  asio::async_read_until(s, sb1, match_char('Z'),
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 26);

  s.reset(read_data, sizeof(read_data));
  ec = asio::error_code();
  length = 0;
  called = false;
  sb2.consume(sb2.size());
  asio::async_read_until(s, sb2, match_char('Z'),
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(ec == asio::error::not_found);
  ASIO_CHECK(length == 0);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  ec = asio::error_code();
  length = 0;
  called = false;
  sb2.consume(sb2.size());
  asio::async_read_until(s, sb2, match_char('Z'),
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(ec == asio::error::not_found);
  ASIO_CHECK(length == 0);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  ec = asio::error_code();
  length = 0;
  called = false;
  sb2.consume(sb2.size());
  asio::async_read_until(s, sb2, match_char('Z'),
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(ec == asio::error::not_found);
  ASIO_CHECK(length == 0);

  s.reset(read_data, sizeof(read_data));
  ec = asio::error_code();
  length = 0;
  called = false;
  sb2.consume(sb2.size());
  asio::async_read_until(s, sb2, match_char('Y'),
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 25);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(1);
  ec = asio::error_code();
  length = 0;
  called = false;
  sb2.consume(sb2.size());
  asio::async_read_until(s, sb2, match_char('Y'),
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 25);

  s.reset(read_data, sizeof(read_data));
  s.next_read_length(10);
  ec = asio::error_code();
  length = 0;
  called = false;
  sb2.consume(sb2.size());
  asio::async_read_until(s, sb2, match_char('Y'),
      bindns::bind(async_read_handler, _1, &ec,
        _2, &length, &called));
  ioc.restart();
  ioc.run();
  ASIO_CHECK(called);
  ASIO_CHECK(!ec);
  ASIO_CHECK(length == 25);

  s.reset(read_data, sizeof(read_data));
  sb2.consume(sb2.size());
  int i = asio::async_read_until(s, sb2, match_char('Y'),
      archetypes::lazy_handler());
  ASIO_CHECK(i == 42);
  ioc.restart();
  ioc.run();
#endif // !defined(ASIO_NO_DYNAMIC_BUFFER_V1)
}

ASIO_TEST_SUITE
(
  "read_until",
  ASIO_TEST_CASE(test_dynamic_string_read_until_char)
  ASIO_TEST_CASE(test_streambuf_read_until_char)
  ASIO_TEST_CASE(test_dynamic_string_read_until_string)
  ASIO_TEST_CASE(test_streambuf_read_until_string)
  ASIO_TEST_CASE(test_dynamic_string_read_until_match_condition)
  ASIO_TEST_CASE(test_streambuf_read_until_match_condition)
  ASIO_TEST_CASE(test_dynamic_string_async_read_until_char)
  ASIO_TEST_CASE(test_streambuf_async_read_until_char)
  ASIO_TEST_CASE(test_dynamic_string_async_read_until_string)
  ASIO_TEST_CASE(test_streambuf_async_read_until_string)
  ASIO_TEST_CASE(test_dynamic_string_async_read_until_match_condition)
  ASIO_TEST_CASE(test_streambuf_async_read_until_match_condition)
)
