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