0
0
mirror of https://github.com/zeromq/libzmq.git synced 2024-12-29 08:39:42 +08:00

Merge pull request #3670 from somdoron/ws_host_path

problem: ws_engine doesn't send correct host and path
This commit is contained in:
Luca Boccassi 2019-09-05 20:57:33 +01:00 committed by GitHub
commit df1bec03c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 317 additions and 113 deletions

View File

@ -785,6 +785,7 @@ set(cxx-sources
gather.cpp
ip_resolver.cpp
zap_client.cpp
ws_address.cpp
ws_connecter.cpp
ws_decoder.cpp
ws_encoder.cpp
@ -923,6 +924,7 @@ set(cxx-sources
vmci_listener.hpp
windows.hpp
wire.hpp
ws_address.hpp
ws_connecter.hpp
ws_decoder.hpp
ws_encoder.hpp

View File

@ -250,6 +250,8 @@ src_libzmq_la_SOURCES = \
src/vmci_listener.hpp \
src/windows.hpp \
src/wire.hpp \
src/ws_address.cpp \
src/ws_address.hpp \
src/ws_connecter.cpp \
src/ws_connecter.hpp \
src/ws_decoder.cpp \

View File

@ -36,6 +36,7 @@
#include "udp_address.hpp"
#include "ipc_address.hpp"
#include "tipc_address.hpp"
#include "ws_address.hpp"
#if defined ZMQ_HAVE_VMCI
#include "vmci_address.hpp"
@ -56,10 +57,12 @@ zmq::address_t::address_t (const std::string &protocol_,
zmq::address_t::~address_t ()
{
if (protocol == protocol_name::tcp || protocol == protocol_name::ws) {
if (protocol == protocol_name::tcp) {
LIBZMQ_DELETE (resolved.tcp_addr);
} else if (protocol == protocol_name::udp) {
LIBZMQ_DELETE (resolved.udp_addr);
} else if (protocol == protocol_name::ws) {
LIBZMQ_DELETE (resolved.ws_addr);
}
#if !defined ZMQ_HAVE_WINDOWS && !defined ZMQ_HAVE_OPENVMS \
&& !defined ZMQ_HAVE_VXWORKS
@ -85,6 +88,8 @@ int zmq::address_t::to_string (std::string &addr_) const
return resolved.tcp_addr->to_string (addr_);
if (protocol == protocol_name::udp && resolved.udp_addr)
return resolved.udp_addr->to_string (addr_);
if (protocol == protocol_name::ws && resolved.ws_addr)
return resolved.ws_addr->to_string (addr_);
#if !defined ZMQ_HAVE_WINDOWS && !defined ZMQ_HAVE_OPENVMS \
&& !defined ZMQ_HAVE_VXWORKS
if (protocol == protocol_name::ipc && resolved.ipc_addr)

View File

@ -45,6 +45,7 @@ namespace zmq
class ctx_t;
class tcp_address_t;
class udp_address_t;
class ws_address_t;
#if !defined ZMQ_HAVE_WINDOWS && !defined ZMQ_HAVE_OPENVMS
class ipc_address_t;
#endif
@ -92,6 +93,7 @@ struct address_t
void *dummy;
tcp_address_t *tcp_addr;
udp_address_t *udp_addr;
ws_address_t *ws_addr;
#if !defined ZMQ_HAVE_WINDOWS && !defined ZMQ_HAVE_OPENVMS \
&& !defined ZMQ_HAVE_VXWORKS
ipc_address_t *ipc_addr;

View File

@ -97,7 +97,8 @@ zmq::ip_resolver_options_t::ip_resolver_options_t () :
_nic_name_allowed (false),
_ipv6_wanted (false),
_port_expected (false),
_dns_allowed (false)
_dns_allowed (false),
_path_allowed (false)
{
}
@ -141,6 +142,13 @@ zmq::ip_resolver_options_t &zmq::ip_resolver_options_t::allow_dns (bool allow_)
return *this;
}
zmq::ip_resolver_options_t &zmq::ip_resolver_options_t::allow_path (bool allow_)
{
_path_allowed = allow_;
return *this;
}
bool zmq::ip_resolver_options_t::bindable ()
{
return _bindable_wanted;
@ -166,6 +174,11 @@ bool zmq::ip_resolver_options_t::allow_dns ()
return _dns_allowed;
}
bool zmq::ip_resolver_options_t::allow_path ()
{
return _path_allowed;
}
zmq::ip_resolver_t::ip_resolver_t (ip_resolver_options_t opts_) :
_options (opts_)
{
@ -214,6 +227,13 @@ int zmq::ip_resolver_t::resolve (ip_addr_t *ip_addr_, const char *name_)
port = 0;
}
// Check if path is allowed in ip address, if allowed it must be truncated
if (_options.allow_path ()) {
size_t pos = addr.find ('/');
if (pos != std::string::npos)
addr = addr.substr (0, pos);
}
// Trim any square brackets surrounding the address. Used for
// IPv6 addresses to remove the confusion with the port
// delimiter.

View File

@ -68,12 +68,14 @@ class ip_resolver_options_t
ip_resolver_options_t &ipv6 (bool ipv6_);
ip_resolver_options_t &expect_port (bool expect_);
ip_resolver_options_t &allow_dns (bool allow_);
ip_resolver_options_t &allow_path (bool allow_);
bool bindable ();
bool allow_nic_name ();
bool ipv6 ();
bool expect_port ();
bool allow_dns ();
bool allow_path ();
private:
bool _bindable_wanted;
@ -81,6 +83,7 @@ class ip_resolver_options_t
bool _ipv6_wanted;
bool _port_expected;
bool _dns_allowed;
bool _path_allowed;
};
class ip_resolver_t

View File

@ -65,6 +65,7 @@
#include "address.hpp"
#include "ipc_address.hpp"
#include "tcp_address.hpp"
#include "ws_address.hpp"
#include "udp_address.hpp"
#include "tipc_address.hpp"
#include "mailbox.hpp"
@ -889,46 +890,14 @@ int zmq::socket_base_t::connect (const char *endpoint_uri_)
// Defer resolution until a socket is opened
paddr->resolved.tcp_addr = NULL;
} else if (protocol == protocol_name::ws) {
// Do some basic sanity checks on ws:// address syntax
// - hostname starts with digit or letter, with embedded '-' or '.'
// - IPv6 address may contain hex chars and colons.
// - IPv6 link local address may contain % followed by interface name / zone_id
// (Reference: https://tools.ietf.org/html/rfc4007)
// - IPv4 address may contain decimal digits and dots.
// - Address must end in ":port" where port is *, or numeric
// - Address may contain two parts separated by ':'
// Following code is quick and dirty check to catch obvious errors,
// without trying to be fully accurate.
const char *check = address.c_str ();
if (isalnum (*check) || isxdigit (*check) || *check == '['
|| *check == ':') {
check++;
while (isalnum (*check) || isxdigit (*check) || *check == '.'
|| *check == '-' || *check == ':' || *check == '%'
|| *check == ';' || *check == '[' || *check == ']'
|| *check == '_' || *check == '*') {
check++;
}
}
// Assume the worst, now look for success
rc = -1;
// Did we reach the end of the address safely?
if (*check == 0) {
// Do we have a valid port string? (cannot be '*' in connect
check = strrchr (address.c_str (), ':');
if (check) {
check++;
if (*check && (isdigit (*check)))
rc = 0; // Valid
}
}
if (rc == -1) {
errno = EINVAL;
paddr->resolved.ws_addr = new (std::nothrow) ws_address_t ();
alloc_assert (paddr->resolved.ws_addr);
rc = paddr->resolved.ws_addr->resolve (address.c_str (), false,
options.ipv6);
if (rc != 0) {
LIBZMQ_DELETE (paddr);
return -1;
}
// Defer resolution until a socket is opened
paddr->resolved.tcp_addr = NULL;
}
#if !defined ZMQ_HAVE_WINDOWS && !defined ZMQ_HAVE_OPENVMS \
&& !defined ZMQ_HAVE_VXWORKS

159
src/ws_address.cpp Normal file
View File

@ -0,0 +1,159 @@
/*
Copyright (c) 2007-2016 Contributors as noted in the AUTHORS file
This file is part of libzmq, the ZeroMQ core engine in C++.
libzmq is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License (LGPL) as published
by the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, the Contributors give you permission to link
this library with independent modules to produce an executable,
regardless of the license terms of these independent modules, and to
copy and distribute the resulting executable under terms of your choice,
provided that you also meet, for each linked independent module, the
terms and conditions of the license of that module. An independent
module is a module which is not derived from or based on this library.
If you modify this library, you must extend this exception to your
version of the library.
libzmq is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "precompiled.hpp"
#include <string>
#include <sstream>
#include "macros.hpp"
#include "ws_address.hpp"
#include "stdint.hpp"
#include "err.hpp"
#include "ip.hpp"
#ifndef ZMQ_HAVE_WINDOWS
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/tcp.h>
#include <net/if.h>
#include <netdb.h>
#include <ctype.h>
#include <unistd.h>
#include <stdlib.h>
#endif
#include <limits.h>
zmq::ws_address_t::ws_address_t ()
{
memset (&_address, 0, sizeof (_address));
}
zmq::ws_address_t::ws_address_t (const sockaddr *sa_, socklen_t sa_len_)
{
zmq_assert (sa_ && sa_len_ > 0);
memset (&_address, 0, sizeof (_address));
if (sa_->sa_family == AF_INET
&& sa_len_ >= static_cast<socklen_t> (sizeof (_address.ipv4)))
memcpy (&_address.ipv4, sa_, sizeof (_address.ipv4));
else if (sa_->sa_family == AF_INET6
&& sa_len_ >= static_cast<socklen_t> (sizeof (_address.ipv6)))
memcpy (&_address.ipv6, sa_, sizeof (_address.ipv6));
_path = std::string ("/");
char hbuf[NI_MAXHOST];
const int rc = getnameinfo (addr (), addrlen (), hbuf, sizeof (hbuf), NULL,
0, NI_NUMERICHOST);
if (rc != 0)
_host = std::string ("localhost");
std::ostringstream os;
if (_address.family () == AF_INET6)
os << std::string ("[");
os << std::string (hbuf);
if (_address.family () == AF_INET6)
os << std::string ("]");
_host = os.str ();
}
int zmq::ws_address_t::resolve (const char *name_, bool local_, bool ipv6_)
{
// find the host part, It's important to use str*r*chr to only get
// the latest colon since IPv6 addresses use colons as delemiters.
const char *delim = strrchr (name_, ':');
if (delim == NULL) {
errno = EINVAL;
return -1;
}
_host = std::string (name_, delim - name_);
// find the path part, which is optional
delim = strrchr (name_, '/');
if (delim)
_path = std::string (delim);
else
_path = std::string ("/");
ip_resolver_options_t resolver_opts;
resolver_opts.bindable (local_)
.allow_dns (!local_)
.allow_nic_name (local_)
.ipv6 (ipv6_)
.allow_path (true)
.expect_port (true);
ip_resolver_t resolver (resolver_opts);
return resolver.resolve (&_address, name_);
}
int zmq::ws_address_t::to_string (std::string &addr_) const
{
std::ostringstream os;
os << std::string ("ws://") << host () << std::string (":")
<< _address.port ();
addr_ = os.str ();
return 0;
}
const sockaddr *zmq::ws_address_t::addr () const
{
return _address.as_sockaddr ();
}
socklen_t zmq::ws_address_t::addrlen () const
{
return _address.sockaddr_len ();
}
const char *zmq::ws_address_t::host () const
{
return _host.c_str ();
}
const char *zmq::ws_address_t::path () const
{
return _path.c_str ();
}
#if defined ZMQ_HAVE_WINDOWS
unsigned short zmq::ws_address_t::family () const
#else
sa_family_t zmq::ws_address_t::family () const
#endif
{
return _address.family ();
}

75
src/ws_address.hpp Normal file
View File

@ -0,0 +1,75 @@
/*
Copyright (c) 2007-2016 Contributors as noted in the AUTHORS file
This file is part of libzmq, the ZeroMQ core engine in C++.
libzmq is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License (LGPL) as published
by the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, the Contributors give you permission to link
this library with independent modules to produce an executable,
regardless of the license terms of these independent modules, and to
copy and distribute the resulting executable under terms of your choice,
provided that you also meet, for each linked independent module, the
terms and conditions of the license of that module. An independent
module is a module which is not derived from or based on this library.
If you modify this library, you must extend this exception to your
version of the library.
libzmq is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __ZMQ_WS_ADDRESS_HPP_INCLUDED__
#define __ZMQ_WS_ADDRESS_HPP_INCLUDED__
#if !defined ZMQ_HAVE_WINDOWS
#include <sys/socket.h>
#include <netinet/in.h>
#endif
#include "ip_resolver.hpp"
namespace zmq
{
class ws_address_t
{
public:
ws_address_t ();
ws_address_t (const sockaddr *sa_, socklen_t sa_len_);
// This function translates textual WS address into an address
// structure. If 'local' is true, names are resolved as local interface
// names. If it is false, names are resolved as remote hostnames.
// If 'ipv6' is true, the name may resolve to IPv6 address.
int resolve (const char *name_, bool local_, bool ipv6_);
// The opposite to resolve()
int to_string (std::string &addr_) const;
#if defined ZMQ_HAVE_WINDOWS
unsigned short family () const;
#else
sa_family_t family () const;
#endif
const sockaddr *addr () const;
socklen_t addrlen () const;
const char *host () const;
const char *path () const;
private:
ip_addr_t _address;
std::string _host;
std::string _path;
};
}
#endif

View File

@ -38,7 +38,7 @@
#include "ip.hpp"
#include "tcp.hpp"
#include "address.hpp"
#include "tcp_address.hpp"
#include "ws_address.hpp"
#include "session_base.hpp"
#include "ws_engine.hpp"
@ -111,7 +111,7 @@ void zmq::ws_connecter_t::out_event ()
return;
}
create_engine (fd, get_socket_name<tcp_address_t> (fd, socket_end_local));
create_engine (fd, get_socket_name<ws_address_t> (fd, socket_end_local));
}
void zmq::ws_connecter_t::timer_event (int id_)
@ -167,63 +167,20 @@ int zmq::ws_connecter_t::open ()
{
zmq_assert (_s == retired_fd);
// Resolve the address
if (_addr->resolved.tcp_addr != NULL) {
LIBZMQ_DELETE (_addr->resolved.tcp_addr);
}
_addr->resolved.tcp_addr = new (std::nothrow) tcp_address_t ();
alloc_assert (_addr->resolved.tcp_addr);
tcp_address_t tcp_addr;
_s = tcp_open_socket (_addr->address.c_str (), options, false, true,
_addr->resolved.tcp_addr);
if (_s == retired_fd) {
// TODO we should emit some event in this case!
LIBZMQ_DELETE (_addr->resolved.tcp_addr);
&tcp_addr);
if (_s == retired_fd)
return -1;
}
zmq_assert (_addr->resolved.tcp_addr != NULL);
// Set the socket to non-blocking mode so that we get async connect().
unblock_socket (_s);
const tcp_address_t *const tcp_addr = _addr->resolved.tcp_addr;
int rc;
// Set a source address for conversations
if (tcp_addr->has_src_addr ()) {
// Allow reusing of the address, to connect to different servers
// using the same source port on the client.
int flag = 1;
#ifdef ZMQ_HAVE_WINDOWS
rc = setsockopt (_s, SOL_SOCKET, SO_REUSEADDR,
reinterpret_cast<const char *> (&flag), sizeof (int));
wsa_assert (rc != SOCKET_ERROR);
#elif defined ZMQ_HAVE_VXWORKS
rc = setsockopt (_s, SOL_SOCKET, SO_REUSEADDR, (char *) &flag,
sizeof (int));
errno_assert (rc == 0);
#else
rc = setsockopt (_s, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof (int));
errno_assert (rc == 0);
#endif
#if defined ZMQ_HAVE_VXWORKS
rc = ::bind (_s, (sockaddr *) tcp_addr->src_addr (),
tcp_addr->src_addrlen ());
#else
rc = ::bind (_s, tcp_addr->src_addr (), tcp_addr->src_addrlen ());
#endif
if (rc == -1)
return -1;
}
// Connect to the remote peer.
#if defined ZMQ_HAVE_VXWORKS
rc = ::connect (_s, (sockaddr *) tcp_addr->addr (), tcp_addr->addrlen ());
int rc = ::connect (_s, (sockaddr *) tcp_addr.addr (), tcp_addr.addrlen ());
#else
rc = ::connect (_s, tcp_addr->addr (), tcp_addr->addrlen ());
int rc = ::connect (_s, tcp_addr.addr (), tcp_addr.addrlen ());
#endif
// Connect was successful immediately.
if (rc == 0) {
@ -307,8 +264,8 @@ void zmq::ws_connecter_t::create_engine (fd_t fd,
endpoint_type_connect);
// Create the engine object for this connection.
ws_engine_t *engine =
new (std::nothrow) ws_engine_t (fd, options, endpoint_pair, true);
ws_engine_t *engine = new (std::nothrow)
ws_engine_t (fd, options, endpoint_pair, *_addr->resolved.ws_addr, true);
alloc_assert (engine);
// Attach the engine to the corresponding session object.

View File

@ -69,9 +69,11 @@ encode_base64 (const unsigned char *in, int in_len, char *out, int out_len);
zmq::ws_engine_t::ws_engine_t (fd_t fd_,
const options_t &options_,
const endpoint_uri_pair_t &endpoint_uri_pair_,
ws_address_t &address_,
bool client_) :
stream_engine_base_t (fd_, options_, endpoint_uri_pair_),
_client (client_),
_address (address_),
_client_handshake_state (client_handshake_initial),
_server_handshake_state (handshake_initial),
_header_name_position (0),
@ -109,16 +111,15 @@ void zmq::ws_engine_t::plug_internal ()
encode_base64 (nonce, 16, _websocket_key, MAX_HEADER_VALUE_LENGTH);
assert (size > 0);
size = snprintf (
(char *) _write_buffer, WS_BUFFER_SIZE,
"GET / HTTP/1.1\r\n"
"Host: server.example.com\r\n" // TODO: we need the address here
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Key: %s\r\n"
"Sec-WebSocket-Protocol: ZWS2.0\r\n"
"Sec-WebSocket-Version: 13\r\n\r\n",
_websocket_key);
size = snprintf ((char *) _write_buffer, WS_BUFFER_SIZE,
"GET %s HTTP/1.1\r\n"
"Host: %s\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Key: %s\r\n"
"Sec-WebSocket-Protocol: ZWS2.0\r\n"
"Sec-WebSocket-Version: 13\r\n\r\n",
_address.path (), _address.host (), _websocket_key);
assert (size > 0 && size < WS_BUFFER_SIZE);
_outpos = _write_buffer;
_outsize = size;

View File

@ -34,6 +34,7 @@
#include "address.hpp"
#include "msg.hpp"
#include "stream_engine_base.hpp"
#include "ws_address.hpp"
#define WS_BUFFER_SIZE 8192
@ -130,6 +131,7 @@ class ws_engine_t : public stream_engine_base_t
ws_engine_t (fd_t fd_,
const options_t &options_,
const endpoint_uri_pair_t &endpoint_uri_pair_,
ws_address_t &address_,
bool client_);
~ws_engine_t ();
@ -145,6 +147,7 @@ class ws_engine_t : public stream_engine_base_t
bool server_handshake ();
bool _client;
ws_address_t _address;
ws_client_handshake_state_t _client_handshake_state;
ws_server_handshake_state_t _server_handshake_state;

View File

@ -100,7 +100,8 @@ std::string zmq::ws_listener_t::get_socket_name (zmq::fd_t fd_,
int zmq::ws_listener_t::create_socket (const char *addr_)
{
_s = tcp_open_socket (addr_, options, true, true, &_address);
tcp_address_t address;
_s = tcp_open_socket (addr_, options, true, true, &address);
if (_s == retired_fd) {
return -1;
}
@ -133,7 +134,7 @@ int zmq::ws_listener_t::create_socket (const char *addr_)
#if defined ZMQ_HAVE_VXWORKS
rc = bind (_s, (sockaddr *) _address.addr (), _address.addrlen ());
#else
rc = bind (_s, _address.addr (), _address.addrlen ());
rc = bind (_s, address.addr (), address.addrlen ());
#endif
#ifdef ZMQ_HAVE_WINDOWS
if (rc == SOCKET_ERROR) {
@ -173,6 +174,10 @@ int zmq::ws_listener_t::set_local_address (const char *addr_)
// socket was already created by the application
_s = options.use_fd;
} else {
int rc = _address.resolve (addr_, true, options.ipv6);
if (rc != 0)
return -1;
if (create_socket (addr_) == -1)
return -1;
}
@ -251,8 +256,8 @@ void zmq::ws_listener_t::create_engine (fd_t fd)
get_socket_name (fd, socket_end_local),
get_socket_name (fd, socket_end_remote), endpoint_type_bind);
ws_engine_t *engine =
new (std::nothrow) ws_engine_t (fd, options, endpoint_pair, false);
ws_engine_t *engine = new (std::nothrow)
ws_engine_t (fd, options, endpoint_pair, _address, false);
alloc_assert (engine);
// Choose I/O thread to run connecter in. Given that we are already

View File

@ -31,7 +31,7 @@
#define __ZMQ_WS_LISTENER_HPP_INCLUDED__
#include "fd.hpp"
#include "tcp_address.hpp"
#include "ws_address.hpp"
#include "stream_listener_base.hpp"
namespace zmq
@ -63,7 +63,7 @@ class ws_listener_t : public stream_listener_base_t
int create_socket (const char *addr_);
// Address to listen on.
tcp_address_t _address;
ws_address_t _address;
ws_listener_t (const ws_listener_t &);
const ws_listener_t &operator= (const ws_listener_t &);

View File

@ -35,10 +35,11 @@ SETUP_TEARDOWN_TESTCONTEXT
void test_roundtrip ()
{
void *sb = test_context_socket (ZMQ_REP);
TEST_ASSERT_SUCCESS_ERRNO (zmq_bind (sb, "ws://*:5556"));
TEST_ASSERT_SUCCESS_ERRNO (zmq_bind (sb, "ws://*:5556/roundtrip"));
void *sc = test_context_socket (ZMQ_REQ);
TEST_ASSERT_SUCCESS_ERRNO (zmq_connect (sc, "ws://127.0.0.1:5556"));
TEST_ASSERT_SUCCESS_ERRNO (
zmq_connect (sc, "ws://127.0.0.1:5556/roundtrip"));
bounce (sb, sc);
@ -49,10 +50,10 @@ void test_roundtrip ()
void test_short_message ()
{
void *sb = test_context_socket (ZMQ_REP);
TEST_ASSERT_SUCCESS_ERRNO (zmq_bind (sb, "ws://*:5557"));
TEST_ASSERT_SUCCESS_ERRNO (zmq_bind (sb, "ws://*:5557/short"));
void *sc = test_context_socket (ZMQ_REQ);
TEST_ASSERT_SUCCESS_ERRNO (zmq_connect (sc, "ws://127.0.0.1:5557"));
TEST_ASSERT_SUCCESS_ERRNO (zmq_connect (sc, "ws://127.0.0.1:5557/short"));
zmq_msg_t msg;
TEST_ASSERT_SUCCESS_ERRNO (zmq_msg_init_size (&msg, 255));
@ -78,10 +79,10 @@ void test_short_message ()
void test_large_message ()
{
void *sb = test_context_socket (ZMQ_REP);
TEST_ASSERT_SUCCESS_ERRNO (zmq_bind (sb, "ws://*:5557"));
TEST_ASSERT_SUCCESS_ERRNO (zmq_bind (sb, "ws://*:5557/large"));
void *sc = test_context_socket (ZMQ_REQ);
TEST_ASSERT_SUCCESS_ERRNO (zmq_connect (sc, "ws://127.0.0.1:5557"));
TEST_ASSERT_SUCCESS_ERRNO (zmq_connect (sc, "ws://127.0.0.1:5557/large"));
zmq_msg_t msg;
TEST_ASSERT_SUCCESS_ERRNO (zmq_msg_init_size (&msg, 65536));