opal-up 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +209 -0
  3. data/README.md +81 -28
  4. data/bin/up_ruby +4 -0
  5. data/bin/up_ruby_cluster +4 -0
  6. data/ext/up_ext/App.h +606 -0
  7. data/ext/up_ext/AsyncSocket.h +355 -0
  8. data/ext/up_ext/AsyncSocketData.h +87 -0
  9. data/ext/up_ext/BloomFilter.h +83 -0
  10. data/ext/up_ext/ChunkedEncoding.h +236 -0
  11. data/ext/up_ext/ClientApp.h +36 -0
  12. data/ext/up_ext/HttpContext.h +502 -0
  13. data/ext/up_ext/HttpContextData.h +56 -0
  14. data/ext/up_ext/HttpErrors.h +53 -0
  15. data/ext/up_ext/HttpParser.h +680 -0
  16. data/ext/up_ext/HttpResponse.h +578 -0
  17. data/ext/up_ext/HttpResponseData.h +95 -0
  18. data/ext/up_ext/HttpRouter.h +380 -0
  19. data/ext/up_ext/Loop.h +204 -0
  20. data/ext/up_ext/LoopData.h +112 -0
  21. data/ext/up_ext/MoveOnlyFunction.h +377 -0
  22. data/ext/up_ext/PerMessageDeflate.h +315 -0
  23. data/ext/up_ext/ProxyParser.h +163 -0
  24. data/ext/up_ext/QueryParser.h +120 -0
  25. data/ext/up_ext/TopicTree.h +363 -0
  26. data/ext/up_ext/Utilities.h +66 -0
  27. data/ext/up_ext/WebSocket.h +381 -0
  28. data/ext/up_ext/WebSocketContext.h +434 -0
  29. data/ext/up_ext/WebSocketContextData.h +109 -0
  30. data/ext/up_ext/WebSocketData.h +86 -0
  31. data/ext/up_ext/WebSocketExtensions.h +256 -0
  32. data/ext/up_ext/WebSocketHandshake.h +145 -0
  33. data/ext/up_ext/WebSocketProtocol.h +506 -0
  34. data/ext/up_ext/bsd.c +767 -0
  35. data/ext/up_ext/bsd.h +109 -0
  36. data/ext/up_ext/context.c +524 -0
  37. data/ext/up_ext/epoll_kqueue.c +458 -0
  38. data/ext/up_ext/epoll_kqueue.h +67 -0
  39. data/ext/up_ext/extconf.rb +5 -0
  40. data/ext/up_ext/internal.h +224 -0
  41. data/ext/up_ext/libusockets.h +350 -0
  42. data/ext/up_ext/libuwebsockets.cpp +1374 -0
  43. data/ext/up_ext/libuwebsockets.h +260 -0
  44. data/ext/up_ext/loop.c +386 -0
  45. data/ext/up_ext/loop_data.h +38 -0
  46. data/ext/up_ext/socket.c +231 -0
  47. data/ext/up_ext/up_ext.c +278 -0
  48. data/lib/up/node/rack_env.rb +2 -2
  49. data/lib/up/ruby/cluster_cli.rb +10 -0
  50. data/lib/up/ruby/rack_cluster.rb +26 -0
  51. data/lib/up/ruby/rack_env.rb +97 -0
  52. data/lib/up/ruby/rack_server.rb +26 -0
  53. data/lib/up/ruby/server_cli.rb +10 -0
  54. data/lib/up/u_web_socket/rack_env.rb +1 -1
  55. data/lib/up/version.rb +1 -1
  56. metadata +71 -18
  57. data/.gitignore +0 -5
  58. data/Gemfile +0 -2
  59. data/example_rack_app/Gemfile +0 -3
  60. data/example_rack_app/config.ru +0 -6
  61. data/example_rack_app/rack_app.rb +0 -5
  62. data/example_roda_app/Gemfile +0 -6
  63. data/example_roda_app/config.ru +0 -6
  64. data/example_roda_app/roda_app.rb +0 -37
  65. data/example_sinatra_app/Gemfile +0 -6
  66. data/example_sinatra_app/config.ru +0 -6
  67. data/example_sinatra_app/sinatra_app.rb +0 -7
  68. data/opal-up.gemspec +0 -27
  69. data/up_logo.svg +0 -256
@@ -0,0 +1,163 @@
1
+ /*
2
+ * Authored by Alex Hultman, 2018-2020.
3
+ * Intellectual property of third-party.
4
+
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ /* This module implements The PROXY Protocol v2 */
19
+
20
+ #ifndef UWS_PROXY_PARSER_H
21
+ #define UWS_PROXY_PARSER_H
22
+
23
+ #ifdef UWS_WITH_PROXY
24
+
25
+ namespace uWS {
26
+
27
+ struct proxy_hdr_v2 {
28
+ uint8_t sig[12]; /* hex 0D 0A 0D 0A 00 0D 0A 51 55 49 54 0A */
29
+ uint8_t ver_cmd; /* protocol version and command */
30
+ uint8_t fam; /* protocol family and address */
31
+ uint16_t len; /* number of following bytes part of the header */
32
+ };
33
+
34
+ union proxy_addr {
35
+ struct { /* for TCP/UDP over IPv4, len = 12 */
36
+ uint32_t src_addr;
37
+ uint32_t dst_addr;
38
+ uint16_t src_port;
39
+ uint16_t dst_port;
40
+ } ipv4_addr;
41
+ struct { /* for TCP/UDP over IPv6, len = 36 */
42
+ uint8_t src_addr[16];
43
+ uint8_t dst_addr[16];
44
+ uint16_t src_port;
45
+ uint16_t dst_port;
46
+ } ipv6_addr;
47
+ };
48
+
49
+ /* Byte swap for little-endian systems */
50
+ /* Todo: This functions should be shared with the one in WebSocketProtocol.h! */
51
+ template <typename T>
52
+ T _cond_byte_swap(T value) {
53
+ uint32_t endian_test = 1;
54
+ if (*((char *)&endian_test)) {
55
+ union {
56
+ T i;
57
+ uint8_t b[sizeof(T)];
58
+ } src = { value }, dst;
59
+
60
+ for (unsigned int i = 0; i < sizeof(value); i++) {
61
+ dst.b[i] = src.b[sizeof(value) - 1 - i];
62
+ }
63
+
64
+ return dst.i;
65
+ }
66
+ return value;
67
+ }
68
+
69
+ struct ProxyParser {
70
+ private:
71
+ union proxy_addr addr;
72
+
73
+ /* Default family of 0 signals no proxy address */
74
+ uint8_t family = 0;
75
+
76
+ public:
77
+ /* Returns 4 or 16 bytes source address */
78
+ std::string_view getSourceAddress() {
79
+
80
+ // UNSPEC family and protocol
81
+ if (family == 0) {
82
+ return {};
83
+ }
84
+
85
+ if ((family & 0xf0) >> 4 == 1) {
86
+ /* Family 1 is INET4 */
87
+ return {(char *) &addr.ipv4_addr.src_addr, 4};
88
+ } else {
89
+ /* Family 2 is INET6 */
90
+ return {(char *) &addr.ipv6_addr.src_addr, 16};
91
+ }
92
+ }
93
+
94
+ /* Returns [done, consumed] where done = false on failure */
95
+ std::pair<bool, unsigned int> parse(std::string_view data) {
96
+
97
+ /* We require at least four bytes to determine protocol */
98
+ if (data.length() < 4) {
99
+ return {false, 0};
100
+ }
101
+
102
+ /* HTTP can never start with "\r\n\r\n", but PROXY always does */
103
+ if (memcmp(data.data(), "\r\n\r\n", 4)) {
104
+ /* This is HTTP, so be done */
105
+ return {true, 0};
106
+ }
107
+
108
+ /* We assume we are parsing PROXY V2 here */
109
+
110
+ /* We require 16 bytes here */
111
+ if (data.length() < 16) {
112
+ return {false, 0};
113
+ }
114
+
115
+ /* Header is 16 bytes */
116
+ struct proxy_hdr_v2 header;
117
+ memcpy(&header, data.data(), 16);
118
+
119
+ if (memcmp(header.sig, "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A", 12)) {
120
+ /* This is not PROXY protocol at all */
121
+ return {false, 0};
122
+ }
123
+
124
+ /* We only support version 2 */
125
+ if ((header.ver_cmd & 0xf0) >> 4 != 2) {
126
+ return {false, 0};
127
+ }
128
+
129
+ //printf("Version: %d\n", (header.ver_cmd & 0xf0) >> 4);
130
+ //printf("Command: %d\n", (header.ver_cmd & 0x0f));
131
+
132
+ /* We get length in network byte order (todo: share this function with the rest) */
133
+ uint16_t hostLength = _cond_byte_swap<uint16_t>(header.len);
134
+
135
+ /* We must have all the data available */
136
+ if (data.length() < 16u + hostLength) {
137
+ return {false, 0};
138
+ }
139
+
140
+ /* Payload cannot be more than sizeof proxy_addr */
141
+ if (sizeof(proxy_addr) < hostLength) {
142
+ return {false, 0};
143
+ }
144
+
145
+ //printf("Family: %d\n", (header.fam & 0xf0) >> 4);
146
+ //printf("Transport: %d\n", (header.fam & 0x0f));
147
+
148
+ /* We have 0 family by default, and UNSPEC is 0 as well */
149
+ family = header.fam;
150
+
151
+ /* Copy payload */
152
+ memcpy(&addr, data.data() + 16, hostLength);
153
+
154
+ /* We consumed everything */
155
+ return {true, 16 + hostLength};
156
+ }
157
+ };
158
+
159
+ }
160
+
161
+ #endif
162
+
163
+ #endif // UWS_PROXY_PARSER_H
@@ -0,0 +1,120 @@
1
+ /*
2
+ * Authored by Alex Hultman, 2018-2020.
3
+ * Intellectual property of third-party.
4
+
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ /* This module implements URI query parsing and retrieval of value given key */
19
+
20
+ #ifndef UWS_QUERYPARSER_H
21
+ #define UWS_QUERYPARSER_H
22
+
23
+ #include <string_view>
24
+
25
+ namespace uWS {
26
+
27
+ /* Takes raw query including initial '?' sign. Will inplace decode, so input will mutate */
28
+ static inline std::string_view getDecodedQueryValue(std::string_view key, std::string_view rawQuery) {
29
+
30
+ /* Can't have a value without a key */
31
+ if (!key.length()) {
32
+ return {};
33
+ }
34
+
35
+ /* Start with the whole querystring including initial '?' */
36
+ std::string_view queryString = rawQuery;
37
+
38
+ /* List of key, value could be cached for repeated fetches similar to how headers are, todo! */
39
+ while (queryString.length()) {
40
+ /* Find boundaries of this statement */
41
+ std::string_view statement = queryString.substr(1, queryString.find('&', 1) - 1);
42
+
43
+ /* Only bother if first char of key match (early exit) */
44
+ if (statement.length() && statement[0] == key[0]) {
45
+ /* Equal sign must be present and not in the end of statement */
46
+ auto equality = statement.find('=');
47
+ if (equality != std::string_view::npos) {
48
+
49
+ std::string_view statementKey = statement.substr(0, equality);
50
+ std::string_view statementValue = statement.substr(equality + 1);
51
+
52
+ /* String comparison */
53
+ if (key == statementKey) {
54
+
55
+ /* Decode value inplace, put null at end if before length of original */
56
+ char *in = (char *) statementValue.data();
57
+
58
+ /* Write offset */
59
+ unsigned int out = 0;
60
+
61
+ /* Walk over all chars until end or null char, decoding in place */
62
+ for (unsigned int i = 0; i < statementValue.length() && in[i]; i++) {
63
+ /* Only bother with '%' */
64
+ if (in[i] == '%') {
65
+ /* Do we have enough data for two bytes hex? */
66
+ if (i + 2 >= statementValue.length()) {
67
+ return {};
68
+ }
69
+
70
+ /* Two bytes hex */
71
+ int hex1 = in[i + 1] - '0';
72
+ if (hex1 > 9) {
73
+ hex1 &= 223;
74
+ hex1 -= 7;
75
+ }
76
+
77
+ int hex2 = in[i + 2] - '0';
78
+ if (hex2 > 9) {
79
+ hex2 &= 223;
80
+ hex2 -= 7;
81
+ }
82
+
83
+ *((unsigned char *) &in[out]) = (unsigned char) (hex1 * 16 + hex2);
84
+ i += 2;
85
+ } else {
86
+ /* Is this even a rule? */
87
+ if (in[i] == '+') {
88
+ in[out] = ' ';
89
+ } else {
90
+ in[out] = in[i];
91
+ }
92
+ }
93
+
94
+ /* We always only write one char */
95
+ out++;
96
+ }
97
+
98
+ /* If decoded string is shorter than original, put null char to stop next read */
99
+ if (out < statementValue.length()) {
100
+ in[out] = 0;
101
+ }
102
+
103
+ return statementValue.substr(0, out);
104
+ }
105
+ } else {
106
+ /* This querystring is invalid, cannot parse it */
107
+ return {nullptr, 0};
108
+ }
109
+ }
110
+
111
+ queryString.remove_prefix(statement.length() + 1);
112
+ }
113
+
114
+ /* Nothing found is given as nullptr, while empty string is given as some pointer to the given buffer */
115
+ return {nullptr, 0};
116
+ }
117
+
118
+ }
119
+
120
+ #endif
@@ -0,0 +1,363 @@
1
+ /*
2
+ * Authored by Alex Hultman, 2018-2021.
3
+ * Intellectual property of third-party.
4
+
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ #ifndef UWS_TOPICTREE_H
19
+ #define UWS_TOPICTREE_H
20
+
21
+ #include <map>
22
+ #include <list>
23
+ #include <iostream>
24
+ #include <unordered_set>
25
+ #include <utility>
26
+ #include <memory>
27
+ #include <unordered_map>
28
+ #include <vector>
29
+ #include <string_view>
30
+ #include <functional>
31
+ #include <set>
32
+ #include <string>
33
+
34
+ namespace uWS {
35
+
36
+ struct Subscriber;
37
+
38
+ struct Topic : std::unordered_set<Subscriber *> {
39
+
40
+ Topic(std::string_view topic) : name(topic) {
41
+
42
+ }
43
+
44
+ std::string name;
45
+ };
46
+
47
+ struct Subscriber {
48
+
49
+ template <typename, typename> friend struct TopicTree;
50
+
51
+ private:
52
+ /* We use a factory */
53
+ Subscriber() = default;
54
+
55
+ /* State of prev, next does not matter unless we are needsDrainage() since we are not in the list */
56
+ Subscriber *prev, *next;
57
+
58
+ /* Any one subscriber can be part of at most 32 publishes before it needs a drain,
59
+ * or whatever encoding of runs or whatever we might do in the future */
60
+ uint16_t messageIndices[32];
61
+
62
+ /* This one matters the most, if it is 0 we are not in the list of drainableSubscribers */
63
+ unsigned char numMessageIndices = 0;
64
+
65
+ public:
66
+
67
+ /* We have a list of topics we subscribe to (read by WebSocket::iterateTopics) */
68
+ std::set<Topic *> topics;
69
+
70
+ /* User data */
71
+ void *user;
72
+
73
+ bool needsDrainage() {
74
+ return numMessageIndices;
75
+ }
76
+ };
77
+
78
+ template <typename T, typename B>
79
+ struct TopicTree {
80
+
81
+ enum IteratorFlags {
82
+ LAST = 1,
83
+ FIRST = 2
84
+ };
85
+
86
+ /* Whomever is iterating this topic is locked to not modify its own list */
87
+ Subscriber *iteratingSubscriber = nullptr;
88
+
89
+ private:
90
+
91
+ /* The drain callback must not publish, unsubscribe or subscribe.
92
+ * It must only cork, uncork, send, write */
93
+ std::function<bool(Subscriber *, T &, IteratorFlags)> cb;
94
+
95
+ /* The topics */
96
+ std::unordered_map<std::string_view, std::unique_ptr<Topic>> topics;
97
+
98
+ /* List of subscribers that needs drainage */
99
+ Subscriber *drainableSubscribers = nullptr;
100
+
101
+ /* Palette of outgoing messages, up to 64k */
102
+ std::vector<T> outgoingMessages;
103
+
104
+ void checkIteratingSubscriber(Subscriber *s) {
105
+ /* Notify user that they are doing something wrong here */
106
+ if (iteratingSubscriber == s) {
107
+ std::cerr << "Error: WebSocket must not subscribe or unsubscribe to topics while iterating its topics!" << std::endl;
108
+ std::terminate();
109
+ }
110
+ }
111
+
112
+ /* Warning: does NOT unlink from drainableSubscribers or modify next, prev. */
113
+ void drainImpl(Subscriber *s) {
114
+ /* Before we call cb we need to make sure this subscriber will not report needsDrainage()
115
+ * since WebSocket::send will call drain from within the cb in that case.*/
116
+ int numMessageIndices = s->numMessageIndices;
117
+ s->numMessageIndices = 0;
118
+
119
+ /* Then we emit cb */
120
+ for (int i = 0; i < numMessageIndices; i++) {
121
+ T &outgoingMessage = outgoingMessages[s->messageIndices[i]];
122
+
123
+ int flags = (i == numMessageIndices - 1) ? LAST : 0;
124
+
125
+ /* Returning true will stop drainage short (such as when backpressure is too high) */
126
+ if (cb(s, outgoingMessage, (IteratorFlags)(flags | (i == 0 ? FIRST : 0)))) {
127
+ break;
128
+ }
129
+ }
130
+ }
131
+
132
+ void unlinkDrainableSubscriber(Subscriber *s) {
133
+ if (s->prev) {
134
+ s->prev->next = s->next;
135
+ }
136
+ if (s->next) {
137
+ s->next->prev = s->prev;
138
+ }
139
+ /* If we are the head, then we also need to reset the head */
140
+ if (drainableSubscribers == s) {
141
+ drainableSubscribers = s->next;
142
+ }
143
+ }
144
+
145
+ public:
146
+
147
+ TopicTree(std::function<bool(Subscriber *, T &, IteratorFlags)> cb) : cb(cb) {
148
+
149
+ }
150
+
151
+ /* Returns nullptr if not found */
152
+ Topic *lookupTopic(std::string_view topic) {
153
+ auto it = topics.find(topic);
154
+ if (it == topics.end()) {
155
+ return nullptr;
156
+ }
157
+ return it->second.get();
158
+ }
159
+
160
+ /* Subscribe fails if we already are subscribed */
161
+ Topic *subscribe(Subscriber *s, std::string_view topic) {
162
+ /* Notify user that they are doing something wrong here */
163
+ checkIteratingSubscriber(s);
164
+
165
+ /* Lookup or create new topic */
166
+ Topic *topicPtr = lookupTopic(topic);
167
+ if (!topicPtr) {
168
+ Topic *newTopic = new Topic(topic);
169
+ topics.insert({std::string_view(newTopic->name.data(), newTopic->name.length()), std::unique_ptr<Topic>(newTopic)});
170
+ topicPtr = newTopic;
171
+ }
172
+
173
+ /* Insert us in topic, insert topic in us */
174
+ auto [it, inserted] = s->topics.insert(topicPtr);
175
+ if (!inserted) {
176
+ return nullptr;
177
+ }
178
+ topicPtr->insert(s);
179
+
180
+ /* Success */
181
+ return topicPtr;
182
+ }
183
+
184
+ /* Returns ok, last, newCount */
185
+ std::tuple<bool, bool, int> unsubscribe(Subscriber *s, std::string_view topic) {
186
+ /* Notify user that they are doing something wrong here */
187
+ checkIteratingSubscriber(s);
188
+
189
+ /* Lookup topic */
190
+ Topic *topicPtr = lookupTopic(topic);
191
+ if (!topicPtr) {
192
+ /* If the topic doesn't exist we are assumed to still be subscribers of something */
193
+ return {false, false, -1};
194
+ }
195
+
196
+ /* Erase from our list first */
197
+ if (s->topics.erase(topicPtr) == 0) {
198
+ return {false, false, -1};
199
+ }
200
+
201
+ /* Remove us from topic */
202
+ topicPtr->erase(s);
203
+
204
+ int newCount = topicPtr->size();
205
+
206
+ /* If there is no subscriber to this topic, remove it */
207
+ if (!topicPtr->size()) {
208
+ /* Unique_ptr deletes the topic */
209
+ topics.erase(topic);
210
+ }
211
+
212
+ /* If we don't hold any topics we are to be freed altogether */
213
+ return {true, s->topics.size() == 0, newCount};
214
+ }
215
+
216
+ /* Factory function for creating a Subscriber */
217
+ Subscriber *createSubscriber() {
218
+ return new Subscriber();
219
+ }
220
+
221
+ /* This is used to end a Subscriber, before freeing it */
222
+ void freeSubscriber(Subscriber *s) {
223
+
224
+ /* I guess we call this one even if we are not subscribers */
225
+ if (!s) {
226
+ return;
227
+ }
228
+
229
+ /* For all topics, unsubscribe */
230
+ for (Topic *topicPtr : s->topics) {
231
+ /* If we are the last subscriber, simply remove the whole topic */
232
+ if (topicPtr->size() == 1) {
233
+ topics.erase(topicPtr->name);
234
+ } else {
235
+ /* Otherwise just remove us */
236
+ topicPtr->erase(s);
237
+ }
238
+ }
239
+
240
+ /* We also need to unlink us */
241
+ if (s->needsDrainage()) {
242
+ unlinkDrainableSubscriber(s);
243
+ }
244
+
245
+ delete s;
246
+ }
247
+
248
+ /* Mainly used by WebSocket::send to drain one socket before sending */
249
+ void drain(Subscriber *s) {
250
+ /* The list is undefined and cannot be touched unless needsDrainage(). */
251
+ if (s->needsDrainage()) {
252
+ /* This function differs from drainImpl by properly unlinking
253
+ * the subscriber from drainableSubscribers. drainImpl does not. */
254
+ unlinkDrainableSubscriber(s);
255
+
256
+ /* This one always resets needsDrainage before it calls any cb's.
257
+ * Otherwise we would stackoverflow when sending after publish but before drain. */
258
+ drainImpl(s);
259
+
260
+ /* If we drained last subscriber, also clear outgoingMessages */
261
+ if (!drainableSubscribers) {
262
+ outgoingMessages.clear();
263
+ }
264
+ }
265
+ }
266
+
267
+ /* Called everytime we call send, to drain published messages so to sync outgoing messages */
268
+ void drain() {
269
+ if (drainableSubscribers) {
270
+ /* Drain one socket a time */
271
+ for (Subscriber *s = drainableSubscribers; s; s = s->next) {
272
+ /* Instead of unlinking every single subscriber, we just leave the list undefined
273
+ * and reset drainableSubscribers ptr below. */
274
+ drainImpl(s);
275
+ }
276
+ /* Drain always clears drainableSubscribers and outgoingMessages */
277
+ drainableSubscribers = nullptr;
278
+ outgoingMessages.clear();
279
+ }
280
+ }
281
+
282
+ /* Big messages bypass all buffering and land directly in backpressure */
283
+ template <typename F>
284
+ bool publishBig(Subscriber *sender, std::string_view topic, B &&bigMessage, F cb) {
285
+ /* Do we even have this topic? */
286
+ auto it = topics.find(topic);
287
+ if (it == topics.end()) {
288
+ return false;
289
+ }
290
+
291
+ /* For all subscribers in topic */
292
+ for (Subscriber *s : *it->second) {
293
+
294
+ /* If we are sender then ignore us */
295
+ if (sender != s) {
296
+ cb(s, bigMessage);
297
+ }
298
+ }
299
+
300
+ return true;
301
+ }
302
+
303
+ /* Linear in number of affected subscribers */
304
+ bool publish(Subscriber *sender, std::string_view topic, T &&message) {
305
+ /* Do we even have this topic? */
306
+ auto it = topics.find(topic);
307
+ if (it == topics.end()) {
308
+ return false;
309
+ }
310
+
311
+ /* If we have more than 65k messages we need to drain every socket. */
312
+ if (outgoingMessages.size() == UINT16_MAX) {
313
+ /* If there is a socket that is currently corked, this will be ugly as all sockets will drain
314
+ * to their own backpressure */
315
+ drain();
316
+ }
317
+
318
+ /* If nobody references this message, don't buffer it */
319
+ bool referencedMessage = false;
320
+
321
+ /* For all subscribers in topic */
322
+ for (Subscriber *s : *it->second) {
323
+
324
+ /* If we are sender then ignore us */
325
+ if (sender != s) {
326
+
327
+ /* At least one subscriber wants this message */
328
+ referencedMessage = true;
329
+
330
+ /* If we already have too many outgoing messages on this subscriber, drain it now */
331
+ if (s->numMessageIndices == 32) {
332
+ /* This one does not need to check needsDrainage here but still does. */
333
+ drain(s);
334
+ }
335
+
336
+ /* Finally we can continue */
337
+ s->messageIndices[s->numMessageIndices++] = (uint16_t)outgoingMessages.size();
338
+ /* First message adds subscriber to list of drainable subscribers */
339
+ if (s->numMessageIndices == 1) {
340
+ /* Insert us in the head of drainable subscribers */
341
+ s->next = drainableSubscribers;
342
+ s->prev = nullptr;
343
+ if (s->next) {
344
+ s->next->prev = s;
345
+ }
346
+ drainableSubscribers = s;
347
+ }
348
+ }
349
+ }
350
+
351
+ /* Push this message and return with success */
352
+ if (referencedMessage) {
353
+ outgoingMessages.emplace_back(message);
354
+ }
355
+
356
+ /* Success if someone wants it */
357
+ return referencedMessage;
358
+ }
359
+ };
360
+
361
+ }
362
+
363
+ #endif