Skip to content

Commit ddba667

Browse files
Properly handle unknown hub message types (#83)
1 parent d5b57ad commit ddba667

File tree

7 files changed

+200
-5
lines changed

7 files changed

+200
-5
lines changed

src/signalrclient/hub_connection_impl.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,12 @@ namespace signalr
364364

365365
for (const auto& val : messages)
366366
{
367+
// Protocol received an unknown message type and gave us a null object, close the connection like we do in other client implementations
368+
if (val == nullptr)
369+
{
370+
throw std::runtime_error("null message received");
371+
}
372+
367373
switch (val->message_type)
368374
{
369375
case message_type::invocation:
@@ -405,14 +411,17 @@ namespace signalr
405411
case message_type::close:
406412
// TODO
407413
break;
414+
default:
415+
throw std::runtime_error("unknown message type '" + std::to_string(static_cast<int>(val->message_type)) + "' received");
416+
break;
408417
}
409418
}
410419
}
411420
catch (const std::exception &e)
412421
{
413422
if (m_logger.is_enabled(trace_level::error))
414423
{
415-
m_logger.log(trace_level::error, std::string("error occured when parsing response: ")
424+
m_logger.log(trace_level::error, std::string("error occurred when parsing response: ")
416425
.append(e.what())
417426
.append(". response: ")
418427
.append(response));

src/signalrclient/json_helpers.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
namespace signalr
1111
{
12+
char record_separator = '\x1e';
13+
1214
signalr::value createValue(const Json::Value& v)
1315
{
1416
switch (v.type())

src/signalrclient/json_helpers.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
namespace signalr
1212
{
13-
static constexpr char record_separator = '\x1e';
13+
extern char record_separator;
1414

1515
signalr::value createValue(const Json::Value& v);
1616

src/signalrclient/json_hub_protocol.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,10 @@ namespace signalr
7070
while (pos != std::string::npos)
7171
{
7272
auto hub_message = parse_message(message.c_str() + offset, pos - offset);
73-
vec.emplace_back(std::move(hub_message));
73+
if (hub_message != nullptr)
74+
{
75+
vec.push_back(std::move(hub_message));
76+
}
7477

7578
offset = pos + 1;
7679
pos = message.find(record_separator, offset);

test/signalrclienttests/hub_connection_tests.cpp

Lines changed: 163 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1112,7 +1112,7 @@ TEST(hub_invocation, hub_connection_closes_when_invocation_response_missing_argu
11121112
auto log_entries = std::dynamic_pointer_cast<memory_log_writer>(writer)->get_log_entries();
11131113
ASSERT_EQ(2, log_entries.size()) << dump_vector(log_entries);
11141114

1115-
ASSERT_TRUE(has_log_entry("[error ] error occured when parsing response: Field 'arguments' not found for 'invocation' message. response: { \"type\": 1, \"target\": \"broadcast\" }\x1e\n", log_entries)) << dump_vector(log_entries);
1115+
ASSERT_TRUE(has_log_entry("[error ] error occurred when parsing response: Field 'arguments' not found for 'invocation' message. response: { \"type\": 1, \"target\": \"broadcast\" }\x1e\n", log_entries)) << dump_vector(log_entries);
11161116
ASSERT_TRUE(has_log_entry("[error ] connection closed with error: Field 'arguments' not found for 'invocation' message\n", log_entries)) << dump_vector(log_entries);
11171117
}
11181118

@@ -1156,7 +1156,7 @@ TEST(hub_invocation, hub_connection_closes_when_invocation_response_missing_targ
11561156
auto log_entries = std::dynamic_pointer_cast<memory_log_writer>(writer)->get_log_entries();
11571157
ASSERT_EQ(2, log_entries.size()) << dump_vector(log_entries);
11581158

1159-
ASSERT_TRUE(has_log_entry("[error ] error occured when parsing response: Field 'target' not found for 'invocation' message. response: { \"type\": 1, \"arguments\": [] }\x1e\n", log_entries)) << dump_vector(log_entries);
1159+
ASSERT_TRUE(has_log_entry("[error ] error occurred when parsing response: Field 'target' not found for 'invocation' message. response: { \"type\": 1, \"arguments\": [] }\x1e\n", log_entries)) << dump_vector(log_entries);
11601160
ASSERT_TRUE(has_log_entry("[error ] connection closed with error: Field 'target' not found for 'invocation' message\n", log_entries)) << dump_vector(log_entries);
11611161
}
11621162

@@ -1891,6 +1891,87 @@ TEST(send, throws_if_protocol_fails)
18911891
ASSERT_EQ(connection_state::connected, hub_connection->get_connection_state());
18921892
}
18931893

1894+
class empty_hub_protocol : public hub_protocol
1895+
{
1896+
virtual std::string write_message(const hub_message*) const override
1897+
{
1898+
return std::string { };
1899+
}
1900+
virtual std::vector<std::unique_ptr<hub_message>> parse_messages(const std::string& str) const override
1901+
{
1902+
auto vec = std::vector<std::unique_ptr<hub_message>>();
1903+
if (str.find("\"target\"") != std::string::npos)
1904+
{
1905+
vec.push_back(std::unique_ptr<hub_message>(new invocation_message("1", "target", std::vector<signalr::value>())));
1906+
}
1907+
else
1908+
{
1909+
vec.push_back(std::unique_ptr<hub_message>());
1910+
}
1911+
return vec;
1912+
}
1913+
1914+
virtual const std::string& name() const override
1915+
{
1916+
return m_protocol_name;
1917+
}
1918+
1919+
virtual int version() const override
1920+
{
1921+
return 1;
1922+
}
1923+
1924+
virtual signalr::transfer_format transfer_format() const override
1925+
{
1926+
return signalr::transfer_format::text;
1927+
}
1928+
1929+
private:
1930+
std::string m_protocol_name = "json";
1931+
};
1932+
1933+
TEST(receive, close_connection_on_null_hub_message)
1934+
{
1935+
auto websocket_client = create_test_websocket_client();
1936+
1937+
std::shared_ptr<log_writer> writer(std::make_shared<memory_log_writer>());
1938+
auto hub_connection = hub_connection_impl::create("", std::move(std::unique_ptr<hub_protocol>(new empty_hub_protocol())), signalr::trace_level::info, writer, nullptr, [websocket_client](const signalr_client_config& config)
1939+
{
1940+
websocket_client->set_config(config);
1941+
return websocket_client;
1942+
}, true);
1943+
1944+
auto close_mre = manual_reset_event<void>();
1945+
hub_connection->set_disconnected([&close_mre](std::exception_ptr exception)
1946+
{
1947+
close_mre.set(exception);
1948+
});
1949+
1950+
auto mre = manual_reset_event<void>();
1951+
hub_connection->start([&mre](std::exception_ptr exception)
1952+
{
1953+
mre.set(exception);
1954+
});
1955+
1956+
ASSERT_FALSE(websocket_client->receive_loop_started.wait(5000));
1957+
ASSERT_FALSE(websocket_client->handshake_sent.wait(5000));
1958+
websocket_client->receive_message("{ }\x1e");
1959+
1960+
mre.get();
1961+
1962+
websocket_client->receive_message("{ \"type\": 134 }\x1e");
1963+
1964+
try
1965+
{
1966+
close_mre.get();
1967+
ASSERT_TRUE(false);
1968+
}
1969+
catch (const std::exception& ex)
1970+
{
1971+
ASSERT_STREQ("null message received", ex.what());
1972+
}
1973+
}
1974+
18941975
TEST(keepalive, sends_ping_messages)
18951976
{
18961977
signalr_client_config config;
@@ -2022,3 +2103,83 @@ TEST(keepalive, resets_server_timeout_timer_on_any_message_from_server)
20222103
}
20232104
ASSERT_EQ(connection_state::disconnected, hub_connection.get_connection_state());
20242105
}
2106+
2107+
class unknown_message_type_hub_protocol : public hub_protocol
2108+
{
2109+
class custom_hub_message : public hub_message
2110+
{
2111+
public:
2112+
custom_hub_message() : hub_message(static_cast<signalr::message_type>(100)) {}
2113+
};
2114+
virtual std::string write_message(const hub_message*) const override
2115+
{
2116+
return std::string{ };
2117+
}
2118+
virtual std::vector<std::unique_ptr<hub_message>> parse_messages(const std::string& str) const override
2119+
{
2120+
auto vec = std::vector<std::unique_ptr<hub_message>>();
2121+
vec.push_back(std::unique_ptr<hub_message>(new custom_hub_message()));
2122+
return vec;
2123+
}
2124+
2125+
virtual const std::string& name() const override
2126+
{
2127+
return m_protocol_name;
2128+
}
2129+
2130+
virtual int version() const override
2131+
{
2132+
return 1;
2133+
}
2134+
2135+
virtual signalr::transfer_format transfer_format() const override
2136+
{
2137+
return signalr::transfer_format::text;
2138+
}
2139+
2140+
private:
2141+
std::string m_protocol_name = "json";
2142+
};
2143+
2144+
TEST(receive, unknown_message_type_closes_connection)
2145+
{
2146+
auto websocket_client = create_test_websocket_client();
2147+
2148+
std::shared_ptr<log_writer> writer(std::make_shared<memory_log_writer>());
2149+
auto hub_connection = hub_connection_impl::create("", std::move(std::unique_ptr<hub_protocol>(new unknown_message_type_hub_protocol())), signalr::trace_level::info, writer,
2150+
nullptr, [websocket_client](const signalr_client_config& config)
2151+
{
2152+
websocket_client->set_config(config);
2153+
return websocket_client;
2154+
}, true);
2155+
2156+
auto disconnect_mre = manual_reset_event<void>();
2157+
hub_connection->set_disconnected([&disconnect_mre](std::exception_ptr ex)
2158+
{
2159+
disconnect_mre.set(ex);
2160+
});
2161+
2162+
auto mre = manual_reset_event<void>();
2163+
hub_connection->start([&mre](std::exception_ptr exception)
2164+
{
2165+
mre.set(exception);
2166+
});
2167+
2168+
ASSERT_FALSE(websocket_client->receive_loop_started.wait(5000));
2169+
ASSERT_FALSE(websocket_client->handshake_sent.wait(5000));
2170+
websocket_client->receive_message("{ }\x1e");
2171+
2172+
mre.get();
2173+
2174+
websocket_client->receive_message("{ \"type\": 101 }\x1e");
2175+
2176+
try
2177+
{
2178+
disconnect_mre.get();
2179+
ASSERT_TRUE(false);
2180+
}
2181+
catch (const std::exception& ex)
2182+
{
2183+
ASSERT_STREQ("unknown message type '100' received", ex.what());
2184+
}
2185+
}

test/signalrclienttests/json_hub_protocol_tests.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,15 @@ TEST(json_hub_protocol, extra_items_ignored_when_parsing)
128128
assert_hub_message_equality(&message, output[0].get());
129129
}
130130

131+
TEST(json_hub_protocol, unknown_message_type_returns_null)
132+
{
133+
ping_message message = ping_message();
134+
// adding ping message, just make sure other messages are still being parsed
135+
auto output = json_hub_protocol().parse_messages("{\"type\":142}\x1e{\"type\":6}\x1e");
136+
ASSERT_EQ(1, output.size());
137+
assert_hub_message_equality(&message, output[0].get());
138+
}
139+
131140
std::vector<std::pair<std::string, std::string>> invalid_messages
132141
{
133142
{ "\x1e", "* Line 1, Column 1\n Syntax error: value, object or array expected.\n* Line 1, Column 1\n A valid JSON document must be either an array or an object value.\n" },

test/signalrclienttests/messagepack_hub_protocol_tests.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,17 @@ TEST(messagepack_hub_protocol, extra_items_ignored_when_parsing)
119119
assert_hub_message_equality(&message, output[0].get());
120120
}
121121

122+
TEST(messagepack_hub_protocol, unknown_message_type_returns_null)
123+
{
124+
ping_message message = ping_message();
125+
auto payload = string_from_bytes({ 0x04, 0x93, 0x6E, 0x80, 0xC0,
126+
// adding ping message, just make sure other messages are still being parsed
127+
0x02, 0x91, 0x06 });
128+
auto output = messagepack_hub_protocol().parse_messages(payload);
129+
ASSERT_EQ(1, output.size());
130+
assert_hub_message_equality(&message, output[0].get());
131+
}
132+
122133
namespace
123134
{
124135
std::vector<std::pair<std::string, std::string>> invalid_messages

0 commit comments

Comments
 (0)