Skip to content

Commit b6320ce

Browse files
Add binary argument support (#66)
1 parent 6e997d5 commit b6320ce

File tree

10 files changed

+209
-2
lines changed

10 files changed

+209
-2
lines changed

include/signalrclient/signalr_value.h

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ namespace signalr
2222
string,
2323
float64,
2424
null,
25-
boolean
25+
boolean,
26+
binary
2627
};
2728

2829
/**
@@ -96,6 +97,16 @@ namespace signalr
9697
*/
9798
SIGNALRCLIENT_API value(std::map<std::string, value>&& map);
9899

100+
/**
101+
* Create an object representing a value_type::binary with the given array of byte's.
102+
*/
103+
SIGNALRCLIENT_API value(const std::vector<uint8_t>& bin);
104+
105+
/**
106+
* Create an object representing a value_type::binary with the given array of byte's.
107+
*/
108+
SIGNALRCLIENT_API value(std::vector<uint8_t>&& bin);
109+
99110
/**
100111
* Copies an existing value.
101112
*/
@@ -151,6 +162,11 @@ namespace signalr
151162
*/
152163
SIGNALRCLIENT_API bool is_bool() const;
153164

165+
/**
166+
* True if the object stored is a binary blob.
167+
*/
168+
SIGNALRCLIENT_API bool is_binary() const;
169+
154170
/**
155171
* Returns the stored object as a double. This will throw if the underlying object is not a signalr::type::float64.
156172
*/
@@ -176,6 +192,11 @@ namespace signalr
176192
*/
177193
SIGNALRCLIENT_API const std::map<std::string, value>& as_map() const;
178194

195+
/**
196+
* Returns the stored object as an array of bytes. This will throw if the underlying object is not a signalr::type::binary.
197+
*/
198+
SIGNALRCLIENT_API const std::vector<uint8_t>& as_binary() const;
199+
179200
/**
180201
* Returns the signalr::type that represents the stored object.
181202
*/
@@ -191,6 +212,7 @@ namespace signalr
191212
std::vector<value> array;
192213
double number;
193214
std::map<std::string, value> map;
215+
std::vector<uint8_t> binary;
194216

195217
// constructor of types in union are not implicitly called
196218
// this is expected as we only construct a single type in the union once we know

src/signalrclient/json_helpers.cpp

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,68 @@ namespace signalr
4545
}
4646
}
4747

48+
char getBase64Value(uint32_t i)
49+
{
50+
char index = (char)i;
51+
if (index < 26)
52+
{
53+
return 'A' + index;
54+
}
55+
if (index < 52)
56+
{
57+
return 'a' + (index - 26);
58+
}
59+
if (index < 62)
60+
{
61+
return '0' + (index - 52);
62+
}
63+
if (index == 62)
64+
{
65+
return '+';
66+
}
67+
if (index == 63)
68+
{
69+
return '/';
70+
}
71+
72+
throw std::out_of_range("base64 table index out of range: " + std::to_string(index));
73+
}
74+
75+
std::string base64Encode(const std::vector<uint8_t>& data)
76+
{
77+
std::string base64result;
78+
79+
size_t i = 0;
80+
while (i <= data.size() - 3)
81+
{
82+
uint32_t b = ((uint32_t)data[i] << 16) | ((uint32_t)data[i + 1] << 8) | (uint32_t)data[i + 2];
83+
base64result.push_back(getBase64Value((b >> 18) & 0x3F));
84+
base64result.push_back(getBase64Value((b >> 12) & 0x3F));
85+
base64result.push_back(getBase64Value((b >> 6) & 0x3F));
86+
base64result.push_back(getBase64Value(b & 0x3F));
87+
88+
i += 3;
89+
}
90+
if (data.size() - i == 2)
91+
{
92+
uint32_t b = ((uint32_t)data[i] << 8) | (uint32_t)data[i + 1];
93+
base64result.push_back(getBase64Value((b >> 10) & 0x3F));
94+
base64result.push_back(getBase64Value((b >> 4) & 0x3F));
95+
base64result.push_back(getBase64Value((b << 2) & 0x3F));
96+
base64result.push_back('=');
97+
}
98+
else if (data.size() - i == 1)
99+
{
100+
uint32_t b = (uint32_t)data[i];
101+
base64result.push_back(getBase64Value((b >> 2) & 0x3F));
102+
base64result.push_back(getBase64Value((b << 4) & 0x3F));
103+
base64result.push_back('=');
104+
base64result.push_back('=');
105+
}
106+
107+
return base64result;
108+
}
109+
48110
Json::Value createJson(const signalr::value& v)
49111
{
50112
switch (v.type())
@@ -110,6 +172,11 @@ namespace signalr
110172
}
111173
return object;
112174
}
175+
case signalr::value_type::binary:
176+
{
177+
const auto& binary = v.as_binary();
178+
return Json::Value(base64Encode(binary));
179+
}
113180
case signalr::value_type::null:
114181
default:
115182
return Json::Value(Json::ValueType::nullValue);

src/signalrclient/json_helpers.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ namespace signalr
1616

1717
Json::Value createJson(const signalr::value& v);
1818

19+
std::string base64Encode(const std::vector<uint8_t>& data);
20+
1921
Json::StreamWriterBuilder getJsonWriter();
2022
std::unique_ptr<Json::CharReader> getJsonReader();
2123
}

src/signalrclient/messagepack_hub_protocol.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,10 @@ namespace signalr
6363
return signalr::value(std::move(map));
6464
}
6565
case msgpack::type::object_type::BIN:
66-
throw signalr_exception("messagepack type 'BIN' not supported");
66+
{
67+
std::vector<uint8_t> vec = std::vector<uint8_t>(v.via.bin.ptr, v.via.bin.ptr + v.via.bin.size);
68+
return signalr::value(std::move(vec));
69+
}
6770
case msgpack::type::object_type::EXT:
6871
throw signalr_exception("messagepack type 'EXT' not supported");
6972
case msgpack::type::object_type::NIL:
@@ -159,6 +162,13 @@ namespace signalr
159162
}
160163
return;
161164
}
165+
case signalr::value_type::binary:
166+
{
167+
const auto& bin = v.as_binary();
168+
packer.pack_bin((uint32_t)bin.size());
169+
packer.pack_bin_body((char*)bin.data(), (uint32_t)bin.size());
170+
return;
171+
}
162172
case signalr::value_type::null:
163173
default:
164174
{

src/signalrclient/signalr_value.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ namespace signalr
2525
return "null";
2626
case signalr::value_type::boolean:
2727
return "boolean";
28+
case signalr::value_type::binary:
29+
return "binary";
2830
default:
2931
return std::to_string((int)v);
3032
}
@@ -53,6 +55,9 @@ namespace signalr
5355
case value_type::map:
5456
new (&mStorage.map) std::map<std::string, value>();
5557
break;
58+
case value_type::binary:
59+
new (&mStorage.binary) std::vector<uint8_t>();
60+
break;
5661
case value_type::null:
5762
default:
5863
break;
@@ -109,6 +114,16 @@ namespace signalr
109114
new (&mStorage.map) std::map<std::string, value>(std::move(map));
110115
}
111116

117+
value::value(const std::vector<uint8_t>& bin) : mType(value_type::binary)
118+
{
119+
new (&mStorage.binary) std::vector<uint8_t>(bin);
120+
}
121+
122+
value::value(std::vector<uint8_t>&& bin) : mType(value_type::binary)
123+
{
124+
new (&mStorage.binary) std::vector<uint8_t>(std::move(bin));
125+
}
126+
112127
value::value(const value& rhs)
113128
{
114129
mType = rhs.mType;
@@ -129,6 +144,9 @@ namespace signalr
129144
case value_type::map:
130145
new (&mStorage.map) std::map<std::string, value>(rhs.mStorage.map);
131146
break;
147+
case value_type::binary:
148+
new (&mStorage.binary) std::vector<uint8_t>(rhs.mStorage.binary);
149+
break;
132150
case value_type::null:
133151
default:
134152
break;
@@ -155,6 +173,9 @@ namespace signalr
155173
case value_type::map:
156174
new (&mStorage.map) std::map<std::string, signalr::value>(std::move(rhs.mStorage.map));
157175
break;
176+
case value_type::binary:
177+
new (&mStorage.binary) std::vector<uint8_t>(std::move(rhs.mStorage.binary));
178+
break;
158179
case value_type::null:
159180
default:
160181
break;
@@ -179,6 +200,9 @@ namespace signalr
179200
case value_type::map:
180201
mStorage.map.~map();
181202
break;
203+
case value_type::binary:
204+
mStorage.binary.~vector();
205+
break;
182206
case value_type::null:
183207
case value_type::float64:
184208
case value_type::boolean:
@@ -209,6 +233,9 @@ namespace signalr
209233
case value_type::map:
210234
new (&mStorage.map) std::map<std::string, value>(rhs.mStorage.map);
211235
break;
236+
case value_type::binary:
237+
new (&mStorage.binary) std::vector<uint8_t>(rhs.mStorage.binary);
238+
break;
212239
case value_type::null:
213240
default:
214241
break;
@@ -239,6 +266,9 @@ namespace signalr
239266
case value_type::map:
240267
new (&mStorage.map) std::map<std::string, value>(std::move(rhs.mStorage.map));
241268
break;
269+
case value_type::binary:
270+
new (&mStorage.binary) std::vector<uint8_t>(std::move(rhs.mStorage.binary));
271+
break;
242272
case value_type::null:
243273
default:
244274
break;
@@ -277,6 +307,11 @@ namespace signalr
277307
return mType == signalr::value_type::boolean;
278308
}
279309

310+
bool value::is_binary() const
311+
{
312+
return mType == signalr::value_type::binary;
313+
}
314+
280315
double value::as_double() const
281316
{
282317
if (!is_double())
@@ -327,6 +362,16 @@ namespace signalr
327362
return mStorage.map;
328363
}
329364

365+
const std::vector<uint8_t>& value::as_binary() const
366+
{
367+
if (!is_binary())
368+
{
369+
throw signalr_exception("object is a '" + value_type_to_string(mType) + "' expected it to be a 'binary'");
370+
}
371+
372+
return mStorage.binary;
373+
}
374+
330375
value_type value::type() const
331376
{
332377
return mType;

test/signalrclienttests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
set (SOURCES
2+
base64_tests.cpp
23
callback_manager_tests.cpp
34
cancellation_token_source_tests.cpp
45
case_insensitive_comparison_utils_tests.cpp
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
#include "stdafx.h"
6+
#include "json_helpers.h"
7+
8+
using namespace signalr;
9+
10+
std::vector<std::pair<std::vector<uint8_t>, std::string>> test_data
11+
{
12+
{ { 49, 48, 51, 57 },
13+
"MTAzOQ==" },
14+
15+
{ { 83, 101, 99, 114, 101, 116, 84, 117, 110, 110, 101, 108 },
16+
"U2VjcmV0VHVubmVs" },
17+
18+
{ { 69, 97, 115, 116, 101, 114, 69, 103, 103, 48, 49 },
19+
"RWFzdGVyRWdnMDE=" },
20+
21+
{ { 255, 201, 193, 55, 90, 199 },
22+
"/8nBN1rH" },
23+
24+
{ { 251, 201, 193, 255 },
25+
"+8nB/w==" }
26+
};
27+
28+
TEST(base_encode, encodes_binary_data)
29+
{
30+
for (auto& data : test_data)
31+
{
32+
auto output = base64Encode(data.first);
33+
34+
ASSERT_STREQ(data.second.data(), output.data());
35+
}
36+
}

test/signalrclienttests/json_hub_protocol_tests.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,15 @@ TEST(json_hub_protocol, parsing_field_order_does_not_matter)
9898
assert_hub_message_equality(&message, output[0].get());
9999
}
100100

101+
TEST(json_hub_protocol, can_serialize_binary)
102+
{
103+
auto output = json_hub_protocol().write_message(
104+
std::shared_ptr<hub_message>(new invocation_message("", "Target", std::vector<value>{ value(std::vector<uint8_t>{ 0x67, 0x6F, 0x6F, 0x64, 0x20, 0x64, 0x61, 0x79 }) })).get());
105+
106+
auto expected = "{\"arguments\":[\"Z29vZCBkYXk=\"],\"target\":\"Target\",\"type\":1}\x1e";
107+
ASSERT_STREQ(expected, output.data());
108+
}
109+
101110
TEST(json_hub_protocol, can_parse_multiple_messages)
102111
{
103112
auto output = json_hub_protocol().parse_messages(std::string("{\"arguments\":[],\"target\":\"Target\",\"type\":1}\x1e") +

test/signalrclienttests/messagepack_hub_protocol_tests.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ namespace
5151
{ string_from_bytes({0x10, 0x96, 0x01, 0x80, 0xC0, 0xA6, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x91, 0x92, 0x01, 0x05, 0x90}),
5252
std::shared_ptr<hub_message>(new invocation_message("", "Target", std::vector<value>{ value(std::vector<value>{value(1.f), value(5.f)}) })) },
5353

54+
// invocation message with binary argument
55+
{ string_from_bytes({0x14, 0x96, 0x01, 0x80, 0xC0, 0xA6, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x91, 0xC4, 0x05, 0x17, 0x36, 0x45, 0x6D, 0xC8, 0x90}),
56+
std::shared_ptr<hub_message>(new invocation_message("", "Target", std::vector<value>{ value(std::vector<uint8_t>{23, 54, 69, 109, 200}) })) },
57+
5458
// ping message
5559
{ string_from_bytes({0x02, 0x91, 0x06}),
5660
std::shared_ptr<hub_message>(new ping_message()) },

test/signalrclienttests/test_utils.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,17 @@ void assert_signalr_value_equality(const signalr::value& expected, const signalr
137137
}
138138
break;
139139
}
140+
case value_type::binary:
141+
{
142+
auto& expected_binary = expected.as_binary();
143+
auto& actual_binary = actual.as_binary();
144+
ASSERT_EQ(expected_binary.size(), actual_binary.size());
145+
for (auto i = 0; i < expected_binary.size(); ++i)
146+
{
147+
ASSERT_EQ(expected_binary[i], actual_binary[i]);
148+
}
149+
break;
150+
}
140151
case value_type::null:
141152
break;
142153
default:

0 commit comments

Comments
 (0)