blob: 0b79ca3a330d9a15820c70da2110264ed197530c [file] [log] [blame]
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -08001#include <webrtc/MyWebSocketHandler.h>
2
3#include "Utils.h"
4
5#include <media/stagefright/foundation/ABuffer.h>
6#include <media/stagefright/foundation/hexdump.h>
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -08007#include <media/stagefright/Utils.h>
8
Jorge E. Moreira2fce64d2019-12-04 16:50:10 -08009#include <json/json.h>
10
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -080011#include <netdb.h>
12#include <openssl/rand.h>
13
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -080014using android::ABuffer;
15
16MyWebSocketHandler::MyWebSocketHandler(
17 std::shared_ptr<RunLoop> runLoop,
18 std::shared_ptr<ServerState> serverState,
19 size_t handlerId)
20 : mRunLoop(runLoop),
21 mServerState(serverState),
22 mId(handlerId),
23 mOptions(OptionBits::useSingleCertificateForAllTracks),
24 mTouchSink(mServerState->getTouchSink()) {
25}
26
27MyWebSocketHandler::~MyWebSocketHandler() {
28 for (auto rtp : mRTPs) {
29 mServerState->releasePort(rtp->getLocalPort());
30 }
31
32 mServerState->releaseHandlerId(mId);
33}
34
35int MyWebSocketHandler::handleMessage(
36 uint8_t /* headerByte */, const uint8_t *msg, size_t len) {
37 // android::hexdump(msg, len);
38
Jorge E. Moreira2fce64d2019-12-04 16:50:10 -080039 Json::Value obj;
40 Json::Reader json_reader;
41 Json::FastWriter json_writer;
42 auto str = reinterpret_cast<const char *>(msg);
43 if (!json_reader.parse(str, str + len, obj) < 0) {
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -080044 return -EINVAL;
45 }
46
Jorge E. Moreira2fce64d2019-12-04 16:50:10 -080047 LOG(VERBOSE) << obj.toStyledString();
48
49 if (!obj.isMember("type")) {
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -080050 return -EINVAL;
51 }
Jorge E. Moreira2fce64d2019-12-04 16:50:10 -080052 std::string type = obj["type"].asString();
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -080053
54 if (type == "greeting") {
Jorge E. Moreira2fce64d2019-12-04 16:50:10 -080055 Json::Value reply;
56 reply["type"] = "hello";
57 reply["reply"] = "Right back at ya!";
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -080058
Jorge E. Moreira2fce64d2019-12-04 16:50:10 -080059 auto replyAsString = json_writer.write(reply);
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -080060 sendMessage(replyAsString.c_str(), replyAsString.size());
61
Jorge E. Moreira2fce64d2019-12-04 16:50:10 -080062 if (obj.isMember("path")) {
63 parseOptions(obj["path"].asString());
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -080064 }
65
66 if (mOptions & OptionBits::useSingleCertificateForAllTracks) {
67 mCertificateAndKey = CreateDTLSCertificateAndKey();
68 }
69
70 prepareSessions();
71 } else if (type == "set-remote-desc") {
Jorge E. Moreira2fce64d2019-12-04 16:50:10 -080072 if (!obj.isMember("sdp")) {
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -080073 return -EINVAL;
74 }
75
Jorge E. Moreira2fce64d2019-12-04 16:50:10 -080076 int err = mOfferedSDP.setTo(obj["sdp"].asString());
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -080077
78 if (err) {
79 LOG(ERROR) << "Offered SDP could not be parsed (" << err << ")";
80 }
81
82 for (size_t i = 0; i < mSessions.size(); ++i) {
83 const auto &session = mSessions[i];
84
85 session->setRemoteParams(
86 getRemoteUFrag(i),
87 getRemotePassword(i),
88 getRemoteFingerprint(i));
89 }
90
91 return err;
92 } else if (type == "request-offer") {
93 std::stringstream ss;
94
95 ss <<
96"v=0\r\n"
97"o=- 7794515898627856655 2 IN IP4 127.0.0.1\r\n"
98"s=-\r\n"
99"t=0 0\r\n"
100"a=msid-semantic: WMS pqWEULZNyLiJHA7lcwlUnbule9FJNk0pY0aw\r\n";
101
102 bool bundled = false;
103
104 if ((mOptions & OptionBits::bundleTracks) && countTracks() > 1) {
105 bundled = true;
106
107 ss << "a=group:BUNDLE 0";
108
109 if (!(mOptions & OptionBits::disableAudio)) {
110 ss << " 1";
111 }
112
113 if (mOptions & OptionBits::enableData) {
114 ss << " 2";
115 }
116
117 ss << "\r\n";
118
119 emitTrackIceOptionsAndFingerprint(ss, 0 /* mlineIndex */);
120 }
121
122 size_t mlineIndex = 0;
123
124 // Video track (mid = 0)
125
126 std::string videoEncodingSpecific =
127 (mServerState->videoFormat() == ServerState::VideoFormat::H264) ?
128"a=rtpmap:96 H264/90000\r\n"
129"a=fmtp:96 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f\r\n"
130 :
131"a=rtpmap:96 VP8/90000\r\n";
132
133 videoEncodingSpecific +=
134"a=rtcp-fb:96 ccm fir\r\n"
135"a=rtcp-fb:96 nack\r\n"
136"a=rtcp-fb:96 nack pli\r\n";
137
138 ss <<
139"m=video 9 UDP/TLS/RTP/SAVPF 96 97\r\n"
140"c=IN IP4 0.0.0.0\r\n"
141"a=rtcp:9 IN IP4 0.0.0.0\r\n";
142
143 if (!bundled) {
144 emitTrackIceOptionsAndFingerprint(ss, mlineIndex++);
145 }
146
147 ss <<
148"a=setup:actpass\r\n"
149"a=mid:0\r\n"
150"a=sendonly\r\n"
151"a=rtcp-mux\r\n"
152"a=rtcp-rsize\r\n"
153"a=rtcp-xr:rcvr-rtt=all\r\n";
154
155 ss << videoEncodingSpecific <<
156"a=rtpmap:97 rtx/90000\r\n"
157"a=fmtp:97 apt=96\r\n"
158"a=ssrc-group:FID 3735928559 3405689008\r\n"
159"a=ssrc:3735928559 cname:myWebRTP\r\n"
160"a=ssrc:3735928559 msid:pqWEULZNyLiJHA7lcwlUnbule9FJNk0pY0aw 61843855-edd7-4ca9-be79-4e3ccc6cc035\r\n"
161"a=ssrc:3735928559 mslabel:pqWEULZNyLiJHA7lcwlUnbule9FJNk0pY0aw\r\n"
162"a=ssrc:3735928559 label:61843855-edd7-4ca9-be79-4e3ccc6cc035\r\n"
163"a=ssrc:3405689008 cname:myWebRTP\r\n"
164"a=ssrc:3405689008 msid:pqWEULZNyLiJHA7lcwlUnbule9FJNk0pY0aw 61843855-edd7-4ca9-be79-4e3ccc6cc035\r\n"
165"a=ssrc:3405689008 mslabel:pqWEULZNyLiJHA7lcwlUnbule9FJNk0pY0aw\r\n"
166"a=ssrc:3405689008 label:61843855-edd7-4ca9-be79-4e3ccc6cc035\r\n";
167
168 if (!(mOptions & OptionBits::disableAudio)) {
169 ss <<
170"m=audio 9 UDP/TLS/RTP/SAVPF 98\r\n"
171"c=IN IP4 0.0.0.0\r\n"
172"a=rtcp:9 IN IP4 0.0.0.0\r\n";
173
174 if (!bundled) {
175 emitTrackIceOptionsAndFingerprint(ss, mlineIndex++);
176 }
177
178 ss <<
179"a=setup:actpass\r\n"
180"a=mid:1\r\n"
181"a=sendonly\r\n"
182"a=msid:pqWEULZNyLiJHA7lcwlUnbule9FJNk0pY0aw 61843856-edd7-4ca9-be79-4e3ccc6cc035\r\n"
183"a=rtcp-mux\r\n"
184"a=rtcp-rsize\r\n"
185"a=rtpmap:98 opus/48000/2\r\n"
186"a=fmtp:98 minptime=10;useinbandfec=1\r\n"
187"a=ssrc-group:FID 2343432205\r\n"
188"a=ssrc:2343432205 cname:myWebRTP\r\n"
189"a=ssrc:2343432205 msid:pqWEULZNyLiJHA7lcwlUnbule9FJNk0pY0aw 61843856-edd7-4ca9-be79-4e3ccc6cc035\r\n"
190"a=ssrc:2343432205 mslabel:pqWEULZNyLiJHA7lcwlUnbule9FJNk0pY0aw\r\n"
191"a=ssrc:2343432205 label:61843856-edd7-4ca9-be79-4e3ccc6cc035\r\n";
192 }
193
194 if (mOptions & OptionBits::enableData) {
195 ss <<
196"m=application 9 UDP/DTLS/SCTP webrtc-datachannel\r\n"
197"c=IN IP4 0.0.0.0\r\n"
198"a=sctp-port:5000\r\n";
199
200 if (!bundled) {
201 emitTrackIceOptionsAndFingerprint(ss, mlineIndex++);
202 }
203
204 ss <<
205"a=setup:actpass\r\n"
206"a=mid:2\r\n"
207"a=sendrecv\r\n"
208"a=fmtp:webrtc-datachannel max-message-size=65536\r\n";
209 }
210
Jorge E. Moreira2fce64d2019-12-04 16:50:10 -0800211 Json::Value reply;
212 reply["type"] = "offer";
213 reply["sdp"] = ss.str();
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -0800214
Jorge E. Moreira2fce64d2019-12-04 16:50:10 -0800215 auto replyAsString = json_writer.write(reply);
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -0800216 sendMessage(replyAsString.c_str(), replyAsString.size());
217 } else if (type == "get-ice-candidate") {
Jorge E. Moreira2fce64d2019-12-04 16:50:10 -0800218 CHECK(obj.isMember("mid"));
219 int32_t mid = obj["mid"].asInt();
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -0800220
221 bool success = getCandidate(mid);
222
223 if (!success) {
Jorge E. Moreira2fce64d2019-12-04 16:50:10 -0800224 Json::Value reply;
225 reply["type"] = "ice-candidate";
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -0800226
Jorge E. Moreira2fce64d2019-12-04 16:50:10 -0800227 auto replyAsString = json_writer.write(reply);
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -0800228 sendMessage(replyAsString.c_str(), replyAsString.size());
229 }
230 } else if (type == "set-mouse-position") {
Jorge E. Moreira2fce64d2019-12-04 16:50:10 -0800231 CHECK(obj.isMember("down"));
232 int32_t down = obj["down"].asInt();
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -0800233
Jorge E. Moreira2fce64d2019-12-04 16:50:10 -0800234 CHECK(obj.isMember("x"));
235 CHECK(obj.isMember("y"));
236 int32_t x = obj["x"].asInt();
237 int32_t y = obj["y"].asInt();
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -0800238
239 LOG(VERBOSE)
240 << "set-mouse-position(" << down << ", " << x << ", " << y << ")";
241
Jorge E. Moreira392833b2019-12-03 16:53:29 -0800242 std::shared_ptr<ABuffer> accessUnit(new ABuffer(3 * sizeof(int32_t)));
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -0800243 int32_t *data = reinterpret_cast<int32_t *>(accessUnit->data());
244 data[0] = down;
Jorge E. Moreira19bfe072019-11-20 15:25:52 -0800245 data[1] = x;
246 data[2] = y;
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -0800247
248 mTouchSink->onAccessUnit(accessUnit);
249 } else if (type == "inject-multi-touch") {
Jorge E. Moreira2fce64d2019-12-04 16:50:10 -0800250 CHECK(obj.isMember("id"));
251 CHECK(obj.isMember("initialDown"));
252 CHECK(obj.isMember("x"));
253 CHECK(obj.isMember("y"));
254 CHECK(obj.isMember("slot"));
255 int32_t id = obj["id"].asInt();
256 int32_t initialDown = obj["initialDown"].asInt();
257 int32_t x = obj["x"].asInt();
258 int32_t y = obj["y"].asInt();
259 int32_t slot = obj["slot"].asInt();
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -0800260
261 LOG(VERBOSE)
262 << "inject-multi-touch id="
263 << id
264 << ", initialDown="
265 << initialDown
266 << ", x="
267 << x
268 << ", y="
269 << y
270 << ", slot="
271 << slot;
272
Jorge E. Moreira392833b2019-12-03 16:53:29 -0800273 std::shared_ptr<ABuffer> accessUnit(new ABuffer(5 * sizeof(int32_t)));
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -0800274 int32_t *data = reinterpret_cast<int32_t *>(accessUnit->data());
275 data[0] = id;
276 data[1] = (initialDown != 0);
Jorge E. Moreira19bfe072019-11-20 15:25:52 -0800277 data[2] = x;
278 data[3] = y;
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -0800279 data[4] = slot;
280
281 mTouchSink->onAccessUnit(accessUnit);
282 }
283
284 return 0;
285}
286
287size_t MyWebSocketHandler::countTracks() const {
288 size_t n = 1; // We always have a video track.
289
290 if (!(mOptions & OptionBits::disableAudio)) {
291 ++n;
292 }
293
294 if (mOptions & OptionBits::enableData) {
295 ++n;
296 }
297
298 return n;
299}
300
301ssize_t MyWebSocketHandler::mlineIndexForMid(int32_t mid) const {
302 switch (mid) {
303 case 0:
304 return 0;
305
306 case 1:
307 if (mOptions & OptionBits::disableAudio) {
308 return -1;
309 }
310
311 return 1;
312
313 case 2:
314 if (!(mOptions & OptionBits::enableData)) {
315 return -1;
316 }
317
318 if (mOptions & OptionBits::disableAudio) {
319 return 1;
320 }
321
322 return 2;
323
324 default:
325 return -1;
326 }
327}
328
329bool MyWebSocketHandler::getCandidate(int32_t mid) {
330 auto mlineIndex = mlineIndexForMid(mid);
331
332 if (mlineIndex < 0) {
333 return false;
334 }
335
336 if (!(mOptions & OptionBits::bundleTracks) || mRTPs.empty()) {
337 // Only allocate a local port once if we bundle tracks.
338
339 auto localPort = mServerState->acquirePort();
340
341 if (!localPort) {
342 return false;
343 }
344
345 size_t sessionIndex = mlineIndex;
346
347 uint32_t trackMask = 0;
348 if (mOptions & OptionBits::bundleTracks) {
349 sessionIndex = 0; // One session for all tracks.
350
351 trackMask = RTPSocketHandler::TRACK_VIDEO;
352
353 if (!(mOptions & OptionBits::disableAudio)) {
354 trackMask |= RTPSocketHandler::TRACK_AUDIO;
355 }
356
357 if (mOptions & OptionBits::enableData) {
358 trackMask |= RTPSocketHandler::TRACK_DATA;
359 }
360 } else if (mid == 0) {
361 trackMask = RTPSocketHandler::TRACK_VIDEO;
362 } else if (mid == 1) {
363 trackMask = RTPSocketHandler::TRACK_AUDIO;
364 } else {
365 trackMask = RTPSocketHandler::TRACK_DATA;
366 }
367
368 const auto &session = mSessions[sessionIndex];
369
370 auto rtp = std::make_shared<RTPSocketHandler>(
371 mRunLoop,
372 mServerState,
373 PF_INET,
374 localPort,
375 trackMask,
376 session);
377
378 rtp->run();
379
380 mRTPs.push_back(rtp);
381 }
382
383 auto rtp = mRTPs.back();
384
Jorge E. Moreira2fce64d2019-12-04 16:50:10 -0800385 Json::Value reply;
386 reply["type"] = "ice-candidate";
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -0800387
388 auto localIPString = rtp->getLocalIPString();
389
390 // see rfc8445, 5.1.2.1. for the derivation of "2122121471" below.
Jorge E. Moreira2fce64d2019-12-04 16:50:10 -0800391 reply["candidate"] =
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -0800392 "candidate:0 1 UDP 2122121471 "
393 + localIPString
394 + " "
395 + std::to_string(rtp->getLocalPort())
396 + " typ host generation 0 ufrag "
Jorge E. Moreira2fce64d2019-12-04 16:50:10 -0800397 + rtp->getLocalUFrag();
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -0800398
Jorge E. Moreira2fce64d2019-12-04 16:50:10 -0800399 reply["mlineIndex"] = static_cast<Json::UInt64>(mlineIndex);
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -0800400
Jorge E. Moreira2fce64d2019-12-04 16:50:10 -0800401 Json::FastWriter json_writer;
402 auto replyAsString = json_writer.write(reply);
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -0800403 sendMessage(replyAsString.c_str(), replyAsString.size());
404
405 return true;
406}
407
408std::optional<std::string> MyWebSocketHandler::getSDPValue(
409 ssize_t targetMediaIndex,
410 std::string_view key,
411 bool fallthroughToGeneralSection) const {
412
413 CHECK_GE(targetMediaIndex, -1);
414
415 if (targetMediaIndex + 1 >= mOfferedSDP.countSections()) {
416 LOG(ERROR)
417 << "getSDPValue: targetMediaIndex "
418 << targetMediaIndex
419 << " out of range (countSections()="
420 << mOfferedSDP.countSections()
421 << ")";
422
423 return std::nullopt;
424 }
425
426 const std::string prefix = "a=" + std::string(key) + ":";
427
428 auto sectionIndex = 1 + targetMediaIndex;
429 auto rangeEnd = mOfferedSDP.section_end(sectionIndex);
430
431 auto it = std::find_if(
432 mOfferedSDP.section_begin(sectionIndex),
433 rangeEnd,
434 [prefix](const auto &line) {
435 return StartsWith(line, prefix);
436 });
437
438 if (it == rangeEnd) {
439 if (fallthroughToGeneralSection) {
440 CHECK_NE(targetMediaIndex, -1);
441
442 // Oh no, scary recursion ahead.
443 return getSDPValue(
444 -1 /* targetMediaIndex */,
445 key,
446 false /* fallthroughToGeneralSection */);
447 }
448
449 LOG(WARNING)
450 << "Unable to find '"
451 << prefix
452 << "' with targetMediaIndex="
453 << targetMediaIndex;
454
455 return std::nullopt;
456 }
457
458 return (*it).substr(prefix.size());
459}
460
461std::string MyWebSocketHandler::getRemotePassword(size_t mlineIndex) const {
462 auto value = getSDPValue(
463 mlineIndex, "ice-pwd", true /* fallthroughToGeneralSection */);
464
465 return value ? *value : std::string();
466}
467
468std::string MyWebSocketHandler::getRemoteUFrag(size_t mlineIndex) const {
469 auto value = getSDPValue(
470 mlineIndex, "ice-ufrag", true /* fallthroughToGeneralSection */);
471
472 return value ? *value : std::string();
473}
474
475std::string MyWebSocketHandler::getRemoteFingerprint(size_t mlineIndex) const {
476 auto value = getSDPValue(
477 mlineIndex, "fingerprint", true /* fallthroughToGeneralSection */);
478
479 return value ? *value : std::string();
480}
481
482// static
483std::pair<std::shared_ptr<X509>, std::shared_ptr<EVP_PKEY>>
484MyWebSocketHandler::CreateDTLSCertificateAndKey() {
485 // Modeled after "https://stackoverflow.com/questions/256405/
486 // programmatically-create-x509-certificate-using-openssl".
487
488 std::shared_ptr<EVP_PKEY> pkey(EVP_PKEY_new(), EVP_PKEY_free);
489
490 std::unique_ptr<RSA, std::function<void(RSA *)>> rsa(
491 RSA_new(), RSA_free);
492
493 BIGNUM exponent;
494 BN_init(&exponent);
495 BN_set_word(&exponent, RSA_F4);
496
497 int res = RSA_generate_key_ex(
498 rsa.get() /* rsa */, 2048, &exponent, nullptr /* callback */);
499
500 CHECK_EQ(res, 1);
501
502 EVP_PKEY_assign_RSA(pkey.get(), rsa.release());
503
504 std::shared_ptr<X509> x509(X509_new(), X509_free);
505
506 ASN1_INTEGER_set(X509_get_serialNumber(x509.get()), 1);
507
508 X509_gmtime_adj(X509_get_notBefore(x509.get()), 0);
509 X509_gmtime_adj(X509_get_notAfter(x509.get()), 60 * 60 * 24 * 7); // 7 days.
510
511 X509_set_pubkey(x509.get(), pkey.get());
512
513 X509_NAME *name = X509_get_subject_name(x509.get());
514
515 X509_NAME_add_entry_by_txt(
516 name, "C", MBSTRING_ASC, (unsigned char *)"US", -1, -1, 0);
517
518 X509_NAME_add_entry_by_txt(
519 name,
520 "O",
521 MBSTRING_ASC,
522 (unsigned char *)"Beyond Aggravated",
523 -1,
524 -1,
525 0);
526
527 X509_NAME_add_entry_by_txt(
528 name, "CN", MBSTRING_ASC, (unsigned char *)"localhost", -1, -1, 0);
529
530 X509_set_issuer_name(x509.get(), name);
531
532 auto digest = EVP_sha256();
533
534 X509_sign(x509.get(), pkey.get(), digest);
535
536 return std::make_pair(x509, pkey);
537}
538
539void MyWebSocketHandler::parseOptions(const std::string &pathAndQuery) {
540 auto separatorPos = pathAndQuery.find("?");
541
542 if (separatorPos == std::string::npos) {
543 return;
544 }
545
546 auto components = SplitString(pathAndQuery.substr(separatorPos + 1), '&');
547 for (auto name : components) {
548 bool boolValue = true;
549
550 separatorPos = name.find("=");
551 if (separatorPos != std::string::npos) {
552 boolValue = false;
553
554 auto value = name.substr(separatorPos + 1);
555 name.erase(separatorPos);
556
557 boolValue =
558 !strcasecmp("true", value.c_str())
559 || !strcasecmp("yes", value.c_str())
560 || value == "1";
561 }
562
563 if (name == "disable_audio") {
564 auto mask = OptionBits::disableAudio;
565 mOptions = (mOptions & ~mask) | (boolValue ? mask : 0);
566 } else if (name == "bundle_tracks" && boolValue) {
567 auto mask = OptionBits::bundleTracks;
568 mOptions = (mOptions & ~mask) | (boolValue ? mask : 0);
569 } else if (name == "enable_data" && boolValue) {
570 auto mask = OptionBits::enableData;
571 mOptions = (mOptions & ~mask) | (boolValue ? mask : 0);
572 }
573 }
574}
575
576// static
577void MyWebSocketHandler::CreateRandomIceCharSequence(char *dst, size_t size) {
578 // Per RFC 5245 an ice-char is alphanumeric, '+' or '/', i.e. 64 distinct
579 // character values (6 bit).
580
581 CHECK_EQ(1, RAND_bytes(reinterpret_cast<unsigned char *>(dst), size));
582
583 for (size_t i = 0; i < size; ++i) {
584 char x = dst[i] & 0x3f;
585 if (x < 26) {
586 x += 'a';
587 } else if (x < 52) {
588 x += 'A' - 26;
589 } else if (x < 62) {
590 x += '0' - 52;
591 } else if (x < 63) {
592 x = '+';
593 } else {
594 x = '/';
595 }
596
597 dst[i] = x;
598 }
599}
600
601std::pair<std::string, std::string>
602MyWebSocketHandler::createUniqueUFragAndPassword() {
603 // RFC 5245, section 15.4 mandates that uFrag is at least 4 and password
604 // at least 22 ice-chars long.
605
606 char uFragChars[4];
607
608 for (;;) {
609 CreateRandomIceCharSequence(uFragChars, sizeof(uFragChars));
610
611 std::string uFrag(uFragChars, sizeof(uFragChars));
612
613 auto it = std::find_if(
614 mSessions.begin(), mSessions.end(),
615 [uFrag](const auto &session) {
616 return session->localUFrag() == uFrag;
617 });
618
619 if (it == mSessions.end()) {
620 // This uFrag is not in use yet.
621 break;
622 }
623 }
624
625 char passwordChars[22];
626 CreateRandomIceCharSequence(passwordChars, sizeof(passwordChars));
627
628 return std::make_pair(
629 std::string(uFragChars, sizeof(uFragChars)),
630 std::string(passwordChars, sizeof(passwordChars)));
631}
632
633void MyWebSocketHandler::prepareSessions() {
634 size_t numSessions =
635 (mOptions & OptionBits::bundleTracks) ? 1 : countTracks();
636
637 for (size_t i = 0; i < numSessions; ++i) {
638 auto [ufrag, password] = createUniqueUFragAndPassword();
639
640 auto [certificate, key] =
641 (mOptions & OptionBits::useSingleCertificateForAllTracks)
642 ? mCertificateAndKey : CreateDTLSCertificateAndKey();
643
644 mSessions.push_back(
645 std::make_shared<RTPSession>(
646 ufrag, password, certificate, key));
647 }
648}
649
650void MyWebSocketHandler::emitTrackIceOptionsAndFingerprint(
651 std::stringstream &ss, size_t mlineIndex) const {
652 CHECK_LT(mlineIndex, mSessions.size());
653 const auto &session = mSessions[mlineIndex];
654
655 ss << "a=ice-ufrag:" << session->localUFrag() << "\r\n";
656 ss << "a=ice-pwd:" << session->localPassword() << "\r\n";
657 ss << "a=ice-options:trickle\r\n";
658 ss << "a=fingerprint:" << session->localFingerprint() << "\r\n";
659}