1 /*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <webrtc/MyWebSocketHandler.h>
18
19 #include "Utils.h"
20
21 #include <json/json.h>
22
23 #include <netdb.h>
24 #include <openssl/rand.h>
25
26 #include <webrtc/Keyboard.h>
27
28 namespace {
29
30 // helper method to ensure a json object has the required fields convertible
31 // to the appropriate types.
validateJsonObject(const Json::Value & obj,const std::string & type,const std::vector<std::pair<std::string,Json::ValueType>> & fields,std::function<void (const std::string &)> onError)32 bool validateJsonObject(
33 const Json::Value &obj, const std::string &type,
34 const std::vector<std::pair<std::string, Json::ValueType>> &fields,
35 std::function<void(const std::string&)> onError) {
36 for (const auto &field_spec : fields) {
37 const auto &field_name = field_spec.first;
38 auto field_type = field_spec.second;
39 if (!(obj.isMember(field_name) &&
40 obj[field_name].isConvertibleTo(field_type))) {
41 std::string error_msg = "Expected a field named '";
42 error_msg += field_name + "' of type '";
43 error_msg += std::to_string(field_type);
44 error_msg += "'";
45 if (!type.empty()) {
46 error_msg += " in message of type '" + type + "'";
47 }
48 error_msg += ".";
49 LOG(WARNING) << error_msg;
50 onError(error_msg);
51 return false;
52 }
53 }
54 return true;
55 }
56
57 } // namespace
58
MyWebSocketHandler(std::shared_ptr<RunLoop> runLoop,std::shared_ptr<ServerState> serverState,size_t handlerId)59 MyWebSocketHandler::MyWebSocketHandler(
60 std::shared_ptr<RunLoop> runLoop,
61 std::shared_ptr<ServerState> serverState,
62 size_t handlerId)
63 : mRunLoop(runLoop),
64 mServerState(serverState),
65 mId(handlerId),
66 mOptions(OptionBits::useSingleCertificateForAllTracks),
67 mTouchSink(mServerState->getTouchSink()),
68 mKeyboardSink(mServerState->getKeyboardSink()) {
69 }
70
~MyWebSocketHandler()71 MyWebSocketHandler::~MyWebSocketHandler() {
72 mServerState->releaseHandlerId(mId);
73 }
74
handleMessage(uint8_t,const uint8_t * msg,size_t len)75 int MyWebSocketHandler::handleMessage(
76 uint8_t /* headerByte */, const uint8_t *msg, size_t len) {
77 // android::hexdump(msg, len);
78
79 Json::Value obj;
80 Json::Reader json_reader;
81 Json::FastWriter json_writer;
82 auto str = reinterpret_cast<const char *>(msg);
83 if (!json_reader.parse(str, str + len, obj) < 0) {
84 return -EINVAL;
85 }
86
87 LOG(VERBOSE) << obj.toStyledString();
88
89 auto sendMessageOnError =
90 [this](const std::string &error_msg) {
91 auto reply = "{\"error\": \"" + error_msg + "\"}";
92 sendMessage(reply.c_str(), reply.size());
93 };
94
95 if (!validateJsonObject(obj, "", {{"type", Json::ValueType::stringValue}},
96 sendMessageOnError)) {
97 return -EINVAL;
98 }
99 std::string type = obj["type"].asString();
100
101 if (type == "greeting") {
102 Json::Value reply;
103 reply["type"] = "hello";
104 reply["reply"] = "Right back at ya!";
105
106 auto replyAsString = json_writer.write(reply);
107 sendMessage(replyAsString.c_str(), replyAsString.size());
108
109 if (obj.isMember("path")) {
110 parseOptions(obj["path"].asString());
111 }
112
113 if (mOptions & OptionBits::useSingleCertificateForAllTracks) {
114 mCertificateAndKey = CreateDTLSCertificateAndKey();
115 }
116
117 prepareSessions();
118 } else if (type == "set-remote-desc") {
119 if (!validateJsonObject(obj, type,
120 {{"sdp", Json::ValueType::stringValue}},
121 sendMessageOnError)) {
122 return -EINVAL;
123 }
124
125 int err = mOfferedSDP.setTo(obj["sdp"].asString());
126
127 if (err) {
128 LOG(ERROR) << "Offered SDP could not be parsed (" << err << ")";
129 }
130
131 for (size_t i = 0; i < mSessions.size(); ++i) {
132 const auto &session = mSessions[i];
133
134 session->setRemoteParams(
135 getRemoteUFrag(i),
136 getRemotePassword(i),
137 getRemoteFingerprint(i));
138 }
139
140 return err;
141 } else if (type == "request-offer") {
142 std::stringstream ss;
143
144 ss <<
145 "v=0\r\n"
146 "o=- 7794515898627856655 2 IN IP4 127.0.0.1\r\n"
147 "s=-\r\n"
148 "t=0 0\r\n"
149 "a=msid-semantic: WMS pqWEULZNyLiJHA7lcwlUnbule9FJNk0pY0aw\r\n";
150
151 bool bundled = false;
152
153 if ((mOptions & OptionBits::bundleTracks) && countTracks() > 1) {
154 bundled = true;
155
156 ss << "a=group:BUNDLE 0";
157
158 if (!(mOptions & OptionBits::disableAudio)) {
159 ss << " 1";
160 }
161
162 if (mOptions & OptionBits::enableData) {
163 ss << " 2";
164 }
165
166 ss << "\r\n";
167
168 emitTrackIceOptionsAndFingerprint(ss, 0 /* mlineIndex */);
169 }
170
171 size_t mlineIndex = 0;
172
173 // Video track (mid = 0)
174
175 std::string videoEncodingSpecific = "a=rtpmap:96 VP8/90000\r\n";
176
177 videoEncodingSpecific +=
178 "a=rtcp-fb:96 ccm fir\r\n"
179 "a=rtcp-fb:96 nack\r\n"
180 "a=rtcp-fb:96 nack pli\r\n";
181
182 ss <<
183 "m=video 9 "
184 << ((mOptions & OptionBits::useTCP) ? "TCP" : "UDP")
185 << "/TLS/RTP/SAVPF 96 97\r\n"
186 "c=IN IP4 0.0.0.0\r\n"
187 "a=rtcp:9 IN IP4 0.0.0.0\r\n";
188
189 if (!bundled) {
190 emitTrackIceOptionsAndFingerprint(ss, mlineIndex++);
191 }
192
193 ss <<
194 "a=setup:actpass\r\n"
195 "a=mid:0\r\n"
196 "a=sendonly\r\n"
197 "a=rtcp-mux\r\n"
198 "a=rtcp-rsize\r\n"
199 "a=rtcp-xr:rcvr-rtt=all\r\n";
200
201 ss << videoEncodingSpecific <<
202 "a=rtpmap:97 rtx/90000\r\n"
203 "a=fmtp:97 apt=96\r\n"
204 "a=ssrc-group:FID 3735928559 3405689008\r\n"
205 "a=ssrc:3735928559 cname:myWebRTP\r\n"
206 "a=ssrc:3735928559 msid:pqWEULZNyLiJHA7lcwlUnbule9FJNk0pY0aw 61843855-edd7-4ca9-be79-4e3ccc6cc035\r\n"
207 "a=ssrc:3735928559 mslabel:pqWEULZNyLiJHA7lcwlUnbule9FJNk0pY0aw\r\n"
208 "a=ssrc:3735928559 label:61843855-edd7-4ca9-be79-4e3ccc6cc035\r\n"
209 "a=ssrc:3405689008 cname:myWebRTP\r\n"
210 "a=ssrc:3405689008 msid:pqWEULZNyLiJHA7lcwlUnbule9FJNk0pY0aw 61843855-edd7-4ca9-be79-4e3ccc6cc035\r\n"
211 "a=ssrc:3405689008 mslabel:pqWEULZNyLiJHA7lcwlUnbule9FJNk0pY0aw\r\n"
212 "a=ssrc:3405689008 label:61843855-edd7-4ca9-be79-4e3ccc6cc035\r\n";
213
214 if (!(mOptions & OptionBits::disableAudio)) {
215 ss <<
216 "m=audio 9 "
217 << ((mOptions & OptionBits::useTCP) ? "TCP" : "UDP")
218 << "/TLS/RTP/SAVPF 98\r\n"
219 "c=IN IP4 0.0.0.0\r\n"
220 "a=rtcp:9 IN IP4 0.0.0.0\r\n";
221
222 if (!bundled) {
223 emitTrackIceOptionsAndFingerprint(ss, mlineIndex++);
224 }
225
226 ss <<
227 "a=setup:actpass\r\n"
228 "a=mid:1\r\n"
229 "a=sendonly\r\n"
230 "a=msid:pqWEULZNyLiJHA7lcwlUnbule9FJNk0pY0aw 61843856-edd7-4ca9-be79-4e3ccc6cc035\r\n"
231 "a=rtcp-mux\r\n"
232 "a=rtcp-rsize\r\n"
233 "a=rtpmap:98 opus/48000/2\r\n"
234 "a=fmtp:98 minptime=10;useinbandfec=1\r\n"
235 "a=ssrc-group:FID 2343432205\r\n"
236 "a=ssrc:2343432205 cname:myWebRTP\r\n"
237 "a=ssrc:2343432205 msid:pqWEULZNyLiJHA7lcwlUnbule9FJNk0pY0aw 61843856-edd7-4ca9-be79-4e3ccc6cc035\r\n"
238 "a=ssrc:2343432205 mslabel:pqWEULZNyLiJHA7lcwlUnbule9FJNk0pY0aw\r\n"
239 "a=ssrc:2343432205 label:61843856-edd7-4ca9-be79-4e3ccc6cc035\r\n";
240 }
241
242 if (mOptions & OptionBits::enableData) {
243 ss <<
244 "m=application 9 "
245 << ((mOptions & OptionBits::useTCP) ? "TCP" : "UDP")
246 << "/DTLS/SCTP webrtc-datachannel\r\n"
247 "c=IN IP4 0.0.0.0\r\n"
248 "a=sctp-port:5000\r\n";
249
250 if (!bundled) {
251 emitTrackIceOptionsAndFingerprint(ss, mlineIndex++);
252 }
253
254 ss <<
255 "a=setup:actpass\r\n"
256 "a=mid:2\r\n"
257 "a=sendrecv\r\n"
258 "a=fmtp:webrtc-datachannel max-message-size=65536\r\n";
259 }
260
261 Json::Value reply;
262 reply["type"] = "offer";
263 reply["sdp"] = ss.str();
264
265 auto replyAsString = json_writer.write(reply);
266 sendMessage(replyAsString.c_str(), replyAsString.size());
267 } else if (type == "get-ice-candidate") {
268 if (!validateJsonObject(obj, type, {{"mid", Json::ValueType::intValue}},
269 sendMessageOnError)) {
270 return -EINVAL;
271 }
272 int32_t mid = obj["mid"].asInt();
273
274 bool success = getCandidate(mid);
275
276 if (!success) {
277 Json::Value reply;
278 reply["type"] = "ice-candidate";
279
280 auto replyAsString = json_writer.write(reply);
281 sendMessage(replyAsString.c_str(), replyAsString.size());
282 }
283 } else if (type == "set-mouse-position") {
284 if (!validateJsonObject(obj, type, {{"down", Json::ValueType::intValue},
285 {"x", Json::ValueType::intValue},
286 {"y", Json::ValueType::intValue}},
287 sendMessageOnError)) {
288 return -EINVAL;
289 }
290 int32_t down = obj["down"].asInt();
291 int32_t x = obj["x"].asInt();
292 int32_t y = obj["y"].asInt();
293
294 LOG(VERBOSE)
295 << "set-mouse-position(" << down << ", " << x << ", " << y << ")";
296
297 mTouchSink->injectTouchEvent(x, y, down != 0);
298 } else if (type == "inject-multi-touch") {
299 if (!validateJsonObject(obj, type, {{"id", Json::ValueType::intValue},
300 {"initialDown", Json::ValueType::intValue},
301 {"x", Json::ValueType::intValue},
302 {"y", Json::ValueType::intValue},
303 {"slot", Json::ValueType::intValue}},
304 sendMessageOnError)) {
305 return -EINVAL;
306 }
307 int32_t id = obj["id"].asInt();
308 int32_t initialDown = obj["initialDown"].asInt();
309 int32_t x = obj["x"].asInt();
310 int32_t y = obj["y"].asInt();
311 int32_t slot = obj["slot"].asInt();
312
313 LOG(VERBOSE)
314 << "inject-multi-touch id="
315 << id
316 << ", initialDown="
317 << initialDown
318 << ", x="
319 << x
320 << ", y="
321 << y
322 << ", slot="
323 << slot;
324
325 mTouchSink->injectMultiTouchEvent(id, slot, x, y, initialDown);
326 } else if (type == "key-event") {
327 if (!validateJsonObject(obj, type, {{"event_type", Json::ValueType::stringValue},
328 {"keycode", Json::ValueType::stringValue}},
329 sendMessageOnError)) {
330 return -EINVAL;
331 }
332 auto down = obj["event_type"].asString() == std::string("keydown");
333 auto code = DomKeyCodeToLinux(obj["keycode"].asString());
334 mKeyboardSink->injectEvent(down, code);
335 }
336
337 return 0;
338 }
339
countTracks() const340 size_t MyWebSocketHandler::countTracks() const {
341 size_t n = 1; // We always have a video track.
342
343 if (!(mOptions & OptionBits::disableAudio)) {
344 ++n;
345 }
346
347 if (mOptions & OptionBits::enableData) {
348 ++n;
349 }
350
351 return n;
352 }
353
mlineIndexForMid(int32_t mid) const354 ssize_t MyWebSocketHandler::mlineIndexForMid(int32_t mid) const {
355 switch (mid) {
356 case 0:
357 return 0;
358
359 case 1:
360 if (mOptions & OptionBits::disableAudio) {
361 return -1;
362 }
363
364 return 1;
365
366 case 2:
367 if (!(mOptions & OptionBits::enableData)) {
368 return -1;
369 }
370
371 if (mOptions & OptionBits::disableAudio) {
372 return 1;
373 }
374
375 return 2;
376
377 default:
378 return -1;
379 }
380 }
381
getCandidate(int32_t mid)382 bool MyWebSocketHandler::getCandidate(int32_t mid) {
383 auto mlineIndex = mlineIndexForMid(mid);
384
385 if (mlineIndex < 0) {
386 return false;
387 }
388
389 if (!(mOptions & OptionBits::bundleTracks) || mRTPs.empty()) {
390 // Only allocate a local port once if we bundle tracks.
391
392 size_t sessionIndex = mlineIndex;
393
394 uint32_t trackMask = 0;
395 if (mOptions & OptionBits::bundleTracks) {
396 sessionIndex = 0; // One session for all tracks.
397
398 trackMask = RTPSocketHandler::TRACK_VIDEO;
399
400 if (!(mOptions & OptionBits::disableAudio)) {
401 trackMask |= RTPSocketHandler::TRACK_AUDIO;
402 }
403
404 if (mOptions & OptionBits::enableData) {
405 trackMask |= RTPSocketHandler::TRACK_DATA;
406 }
407 } else if (mid == 0) {
408 trackMask = RTPSocketHandler::TRACK_VIDEO;
409 } else if (mid == 1) {
410 trackMask = RTPSocketHandler::TRACK_AUDIO;
411 } else {
412 trackMask = RTPSocketHandler::TRACK_DATA;
413 }
414
415 const auto &session = mSessions[sessionIndex];
416
417 auto rtp = std::make_shared<RTPSocketHandler>(
418 mRunLoop,
419 mServerState,
420 (mOptions & OptionBits::useTCP)
421 ? RTPSocketHandler::TransportType::TCP
422 : RTPSocketHandler::TransportType::UDP,
423 PF_INET,
424 trackMask,
425 session);
426
427 rtp->run();
428
429 mRTPs.push_back(rtp);
430 }
431
432 auto rtp = mRTPs.back();
433
434 Json::Value reply;
435 reply["type"] = "ice-candidate";
436
437 auto localIPString = rtp->getLocalIPString();
438
439 std::stringstream ss;
440 ss << "candidate:0 1 ";
441
442 if (mOptions & OptionBits::useTCP) {
443 ss << "tcp";
444 } else {
445 ss << "UDP";
446 }
447
448 // see rfc8445, 5.1.2.1. for the derivation of "2122121471" below.
449 ss << " 2122121471 " << localIPString << " " << rtp->getLocalPort() << " typ host ";
450
451 if (mOptions & OptionBits::useTCP) {
452 ss << "tcptype passive ";
453 }
454
455 ss << "generation 0 ufrag " << rtp->getLocalUFrag();
456
457 reply["candidate"] = ss.str();
458 reply["mlineIndex"] = static_cast<Json::UInt64>(mlineIndex);
459
460 Json::FastWriter json_writer;
461 auto replyAsString = json_writer.write(reply);
462 sendMessage(replyAsString.c_str(), replyAsString.size());
463
464 return true;
465 }
466
getSDPValue(ssize_t targetMediaIndex,std::string_view key,bool fallthroughToGeneralSection) const467 std::optional<std::string> MyWebSocketHandler::getSDPValue(
468 ssize_t targetMediaIndex,
469 std::string_view key,
470 bool fallthroughToGeneralSection) const {
471
472 CHECK_GE(targetMediaIndex, -1);
473
474 if (targetMediaIndex + 1 >= mOfferedSDP.countSections()) {
475 LOG(ERROR)
476 << "getSDPValue: targetMediaIndex "
477 << targetMediaIndex
478 << " out of range (countSections()="
479 << mOfferedSDP.countSections()
480 << ")";
481
482 return std::nullopt;
483 }
484
485 const std::string prefix = "a=" + std::string(key) + ":";
486
487 auto sectionIndex = 1 + targetMediaIndex;
488 auto rangeEnd = mOfferedSDP.section_end(sectionIndex);
489
490 auto it = std::find_if(
491 mOfferedSDP.section_begin(sectionIndex),
492 rangeEnd,
493 [prefix](const auto &line) {
494 return StartsWith(line, prefix);
495 });
496
497 if (it == rangeEnd) {
498 if (fallthroughToGeneralSection) {
499 CHECK_NE(targetMediaIndex, -1);
500
501 // Oh no, scary recursion ahead.
502 return getSDPValue(
503 -1 /* targetMediaIndex */,
504 key,
505 false /* fallthroughToGeneralSection */);
506 }
507
508 LOG(WARNING)
509 << "Unable to find '"
510 << prefix
511 << "' with targetMediaIndex="
512 << targetMediaIndex;
513
514 return std::nullopt;
515 }
516
517 return (*it).substr(prefix.size());
518 }
519
getRemotePassword(size_t mlineIndex) const520 std::string MyWebSocketHandler::getRemotePassword(size_t mlineIndex) const {
521 auto value = getSDPValue(
522 mlineIndex, "ice-pwd", true /* fallthroughToGeneralSection */);
523
524 return value ? *value : std::string();
525 }
526
getRemoteUFrag(size_t mlineIndex) const527 std::string MyWebSocketHandler::getRemoteUFrag(size_t mlineIndex) const {
528 auto value = getSDPValue(
529 mlineIndex, "ice-ufrag", true /* fallthroughToGeneralSection */);
530
531 return value ? *value : std::string();
532 }
533
getRemoteFingerprint(size_t mlineIndex) const534 std::string MyWebSocketHandler::getRemoteFingerprint(size_t mlineIndex) const {
535 auto value = getSDPValue(
536 mlineIndex, "fingerprint", true /* fallthroughToGeneralSection */);
537
538 return value ? *value : std::string();
539 }
540
541 // static
542 std::pair<std::shared_ptr<X509>, std::shared_ptr<EVP_PKEY>>
CreateDTLSCertificateAndKey()543 MyWebSocketHandler::CreateDTLSCertificateAndKey() {
544 // Modeled after "https://stackoverflow.com/questions/256405/
545 // programmatically-create-x509-certificate-using-openssl".
546
547 std::shared_ptr<EVP_PKEY> pkey(EVP_PKEY_new(), EVP_PKEY_free);
548
549 std::unique_ptr<RSA, std::function<void(RSA *)>> rsa(
550 RSA_new(), RSA_free);
551
552 BIGNUM exponent;
553 BN_init(&exponent);
554 BN_set_word(&exponent, RSA_F4);
555
556 int res = RSA_generate_key_ex(
557 rsa.get() /* rsa */, 2048, &exponent, nullptr /* callback */);
558
559 CHECK_EQ(res, 1);
560
561 EVP_PKEY_assign_RSA(pkey.get(), rsa.release());
562
563 std::shared_ptr<X509> x509(X509_new(), X509_free);
564
565 ASN1_INTEGER_set(X509_get_serialNumber(x509.get()), 1);
566
567 X509_gmtime_adj(X509_get_notBefore(x509.get()), 0);
568 X509_gmtime_adj(X509_get_notAfter(x509.get()), 60 * 60 * 24 * 7); // 7 days.
569
570 X509_set_pubkey(x509.get(), pkey.get());
571
572 X509_NAME *name = X509_get_subject_name(x509.get());
573
574 X509_NAME_add_entry_by_txt(
575 name, "C", MBSTRING_ASC, (unsigned char *)"US", -1, -1, 0);
576
577 X509_NAME_add_entry_by_txt(
578 name,
579 "O",
580 MBSTRING_ASC,
581 (unsigned char *)"Beyond Aggravated",
582 -1,
583 -1,
584 0);
585
586 X509_NAME_add_entry_by_txt(
587 name, "CN", MBSTRING_ASC, (unsigned char *)"localhost", -1, -1, 0);
588
589 X509_set_issuer_name(x509.get(), name);
590
591 auto digest = EVP_sha256();
592
593 X509_sign(x509.get(), pkey.get(), digest);
594
595 return std::make_pair(x509, pkey);
596 }
597
parseOptions(const std::string & pathAndQuery)598 void MyWebSocketHandler::parseOptions(const std::string &pathAndQuery) {
599 auto separatorPos = pathAndQuery.find('?');
600
601 if (separatorPos == std::string::npos) {
602 return;
603 }
604
605 auto components = SplitString(pathAndQuery.substr(separatorPos + 1), '&');
606 for (auto name : components) {
607 bool boolValue = true;
608
609 separatorPos = name.find('=');
610 if (separatorPos != std::string::npos) {
611 boolValue = false;
612
613 auto value = name.substr(separatorPos + 1);
614 name.erase(separatorPos);
615
616 boolValue =
617 !strcasecmp("true", value.c_str())
618 || !strcasecmp("yes", value.c_str())
619 || value == "1";
620 }
621
622 if (name == "disable_audio") {
623 auto mask = OptionBits::disableAudio;
624 mOptions = (mOptions & ~mask) | (boolValue ? mask : 0);
625 } else if (name == "bundle_tracks" && boolValue) {
626 auto mask = OptionBits::bundleTracks;
627 mOptions = (mOptions & ~mask) | (boolValue ? mask : 0);
628 } else if (name == "enable_data" && boolValue) {
629 auto mask = OptionBits::enableData;
630 mOptions = (mOptions & ~mask) | (boolValue ? mask : 0);
631 } else if (name == "use_tcp" && boolValue) {
632 auto mask = OptionBits::useTCP;
633 mOptions = (mOptions & ~mask) | (boolValue ? mask : 0);
634 }
635 }
636 }
637
638 // static
CreateRandomIceCharSequence(char * dst,size_t size)639 void MyWebSocketHandler::CreateRandomIceCharSequence(char *dst, size_t size) {
640 // Per RFC 5245 an ice-char is alphanumeric, '+' or '/', i.e. 64 distinct
641 // character values (6 bit).
642
643 CHECK_EQ(1, RAND_bytes(reinterpret_cast<unsigned char *>(dst), size));
644
645 for (size_t i = 0; i < size; ++i) {
646 char x = dst[i] & 0x3f;
647 if (x < 26) {
648 x += 'a';
649 } else if (x < 52) {
650 x += 'A' - 26;
651 } else if (x < 62) {
652 x += '0' - 52;
653 } else if (x < 63) {
654 x = '+';
655 } else {
656 x = '/';
657 }
658
659 dst[i] = x;
660 }
661 }
662
663 std::pair<std::string, std::string>
createUniqueUFragAndPassword()664 MyWebSocketHandler::createUniqueUFragAndPassword() {
665 // RFC 5245, section 15.4 mandates that uFrag is at least 4 and password
666 // at least 22 ice-chars long.
667
668 char uFragChars[4];
669
670 for (;;) {
671 CreateRandomIceCharSequence(uFragChars, sizeof(uFragChars));
672
673 std::string uFrag(uFragChars, sizeof(uFragChars));
674
675 auto it = std::find_if(
676 mSessions.begin(), mSessions.end(),
677 [uFrag](const auto &session) {
678 return session->localUFrag() == uFrag;
679 });
680
681 if (it == mSessions.end()) {
682 // This uFrag is not in use yet.
683 break;
684 }
685 }
686
687 char passwordChars[22];
688 CreateRandomIceCharSequence(passwordChars, sizeof(passwordChars));
689
690 return std::make_pair(
691 std::string(uFragChars, sizeof(uFragChars)),
692 std::string(passwordChars, sizeof(passwordChars)));
693 }
694
prepareSessions()695 void MyWebSocketHandler::prepareSessions() {
696 size_t numSessions =
697 (mOptions & OptionBits::bundleTracks) ? 1 : countTracks();
698
699 for (size_t i = 0; i < numSessions; ++i) {
700 auto [ufrag, password] = createUniqueUFragAndPassword();
701
702 auto [certificate, key] =
703 (mOptions & OptionBits::useSingleCertificateForAllTracks)
704 ? mCertificateAndKey : CreateDTLSCertificateAndKey();
705
706 mSessions.push_back(
707 std::make_shared<RTPSession>(
708 ufrag, password, certificate, key));
709 }
710 }
711
emitTrackIceOptionsAndFingerprint(std::stringstream & ss,size_t mlineIndex) const712 void MyWebSocketHandler::emitTrackIceOptionsAndFingerprint(
713 std::stringstream &ss, size_t mlineIndex) const {
714 CHECK_LT(mlineIndex, mSessions.size());
715 const auto &session = mSessions[mlineIndex];
716
717 ss << "a=ice-ufrag:" << session->localUFrag() << "\r\n";
718 ss << "a=ice-pwd:" << session->localPassword() << "\r\n";
719 ss << "a=ice-options:trickle\r\n";
720 ss << "a=fingerprint:" << session->localFingerprint() << "\r\n";
721 }
722