WebRTC源码分析——随机值(数、字符串)生成系统

1 引言

WebRTC的rtc_base/helpers.h 和 rtc_base/helpers.cc提供了生成随机值的各种方法:

  • CreateRandomString:生成一定长度的随机字符串;
  • CreateRandomUuid:生成UUID字符串,"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"格式,其中’x’ is a hex digit, and ‘y’ is 8, 9, a or b;
  • CreateRandomId && CreateRandomId64 && CreateRandomNonZeroId:生成随机整数ID值;
  • CreateRandomDouble:创建随机浮点值。

这些多样随机值产生的方法都离不开RandomGenerator接口提供的基础功能。在rtc_base/helpers.cc文件中定义了RandomGenerator接口,也提供了两个实现类SecureRandomGenerator、TestRandomGenerator。由于后者是做测试用的,因此本文将不做分析。

2 RandomGenerator && SecureRandomGenerator

class RandomGenerator {
 public:
  virtual ~RandomGenerator() {}
  virtual bool Init(const void* seed, size_t len) = 0;
  virtual bool Generate(void* buf, size_t len) = 0;
};

// The OpenSSL RNG.
class SecureRandomGenerator : public RandomGenerator {
 public:
  SecureRandomGenerator() {}
  ~SecureRandomGenerator() override {}
  bool Init(const void* seed, size_t len) override { return true; }
  bool Generate(void* buf, size_t len) override {
    return (RAND_bytes(reinterpret_cast<unsigned char*>(buf), len) > 0);
  }
};

由上源码可知:随机数生成器提供了两个基本方法,初始化方法Init、生成一定长度随机值方法Generate。SecureRandomGenerator类的定义上方有一行英文注释:// The OpenSSL RNG. ,也即告知SecureRandomGenerator实现随机值的生成是利用的OpenSSL库中的方法。

  • SecureRandomGenerator.Init():什么也做,也不会保存传入的初始种子。因为在OpenSSL库中会有跨平台的种子获取函数重新获取,不依赖于外部传入的初始种子。
  • SecureRandomGenerator.Generate():利用RAND_bytes()来生成随机值。注意reinterpret_cast<unsigned char*>强制性地将一块无具体类型的数据块转为unsigned char*类型的数据块。

2.1 RAND_bytes——boringSSL库中的随机值生成函数

看本节标题会发现RAND_bytes()是boringSSL库的方法,说好的OpenSSL呢?其实,boringSSL脱胎于OpenSSL,是OpenSSL的一个分支,由Google开发的。可能是因为OpenSSL出过几次安全泄漏BUG,Google转而开分支自己维护从而形成了boringSSL库。Google很多项目包括WebRTC和Chrome都使用了boringSSL。比如RAND_bytes()就位于third_party/boringssl/src/crypto/fipsmodule/rand/rand.c文件,即WebRTC的第三方库boringssl中。

按理说,想要研究透随机值产生的原理需要更深的挖掘RAND_bytes()方法的代码以及其中包含的数学原理。但是,每个人的精力是有限的,或许以后有时间会这么做,当前姑且先放过自己吧。

RAND_bytes() 源码如下:

int RAND_bytes(uint8_t *out, size_t out_len) {
  static const uint8_t kZeroAdditionalData[32] = {0};
  RAND_bytes_with_additional_data(out, out_len, kZeroAdditionalData);
  return 1;
}

3 InitRandom——初始化

我们的计算机系统是无法提供真随机数的,都是利用算法提供的伪随机序列(给同样的初值,后续将生成同样的随机值序列),因此,并不真正的随机。但是可以通过初始种子的随机性 和 伪随机数算法让产生的数看起来像是随机的。

算法就是上面boringSSL库RAND_bytes()提供的。而初始种子是调用InitRandom方法时外部提供的。其中的Rng()返回的就是一个全局唯一的SecureRandomGenerator对象,根据上面对SecureRandomGenerator的源码分析:外部传入的这个种子没有得到使用的。

bool InitRandom(int seed) {
  return InitRandom(reinterpret_cast<const char*>(&seed), sizeof(seed));
}

bool InitRandom(const char* seed, size_t len) {
  if (!Rng().Init(seed, len)) {
    RTC_LOG(LS_ERROR) << "Failed to init random generator!";
    return false;
  }
  return true;
}

3.1 WebRTC随机系统初始化时机

虽然外部种子没有得到使用,但是我们还是来看下WebRTC中何时调用的InitRandom(),传入的种子一般会给什么样的值:

bool PeerConnectionFactory::Initialize() {
  RTC_DCHECK(signaling_thread_->IsCurrent());
  rtc::InitRandom(rtc::Time32());
  ...
}

由源码可知:

rtc::InitRandom()方法会在PeerConnectionFactory.Initialize()方法中第一时间被调用,但请注意一点,在调用rtc::InitRandom()之前,都会先调用rtc::InitializeSSL(),确保SSL库先初始化完毕。毕竟,随机值系统是依赖于SSL库的。

另外,传入的初始化种子由rtc::Time32()产生,也即当前的系统时间,记住这个常用的做法。

4 随机字符串的生成

4.1 CreateRandomString

源码如下:

std::string CreateRandomString(size_t len) {
  std::string str;
  RTC_CHECK(CreateRandomString(len, &str));
  return str;
}

bool CreateRandomString(size_t len, std::string* str) {
  return CreateRandomString(len, kBase64, 64, str);
}

bool CreateRandomString(size_t len,
                        const std::string& table,
                        std::string* str) {
  return CreateRandomString(len, table.c_str(), static_cast<int>(table.size()),
                            str);
}

static bool CreateRandomString(size_t len,
                               const char* table,
                               int table_size,
                               std::string* str) {
  str->clear();
  // table的长度必须能被256整除,比如base64,为什么是256?因为单个字符8bit,能表示的最大值就是256.
  // 为了避免后续生成的字符串映射到table中字符时有偏模除法,此处做这个检测。
  // Avoid biased modulo division below.
  if (256 % table_size) {
    RTC_LOG(LS_ERROR) << "Table size must divide 256 evenly!";
    return false;
  }
  // 利用RandomGenerator生成len长度的随机串
  std::unique_ptr<uint8_t[]> bytes(new uint8_t[len]);
  if (!Rng().Generate(bytes.get(), len)) {
    RTC_LOG(LS_ERROR) << "Failed to generate random string!";
    return false;
  }
  // 若str的容量capacity不够len,则扩展std::string的capacity以容纳len长度的字符串
  str->reserve(len);
  // 让生成的随机串的每个字符都映射到table的中的一员,直接取余映射到表中,呼应一开始被256整除
  for (size_t i = 0; i < len; ++i) {
    str->push_back(table[bytes[i] % table_size]);
  }
  return true;
}

由上4个源码可知,最终的实现在于最后一个静态CreateRandomString方法,该方法依赖一个字符串table的入参。提供如此多的CreateRandomString方法,就是为了给这个table参数提供一个的默认实现,让一般的用户使用更方便;也为了增强灵活性,让用户可以自行提供自己想要table的实现。注意这种设计思路。

默认的table,是Base64算法提供的64个 可打印的 基础字符。关于Base64算法和应用,可见 https://baijiahao.baidu.com/s?id=1644892102150918183&wfr=spider&for=pc

static const char kBase64[64] = {
    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
    'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
    'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
    'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};

如同静态CreateRandomString源码上的注释所写,基本的思路就是利用RandomGenerator生成长度为目标长度的随机字符串,由于这些字符串的单个字符范围是0~255,在ASII表中并不都是可打印的字符,因此需要转为可打印的字符,也即此处提供的默认字符表——base64编码算法的基础字符表。这与WebRTC中CreateRandomString的用途有关系,很多时候都有打印这些随机串到日志的需求。

4.2 WebRTC中随机串的用途

  • Candidate.id_:Candidate的标识id值为8位长字符串,采用rtc::CreateRandomString(8)产生;
  • STUN协议的Bing Request请求的事务id:12位长随机字符串;
  • ICE协议中的身份验证信息,uflag和pwd:分别为4位、24位随机字符串;
  • 轨道id:8位随机串;
  • CNAME:16位随机串;
  • MediaTransportSettings.pre_shared_key:媒体传输设置的预设共享key长度为32位随机字符串。

5 随机UUID生成

5.1 CreateRandomUuid

源码如下:

// Version 4 UUID is of the form:
// xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
// Where 'x' is a hex digit, and 'y' is 8, 9, a or b.
std::string CreateRandomUuid() {
  std::string str;
  std::unique_ptr<uint8_t[]> bytes(new uint8_t[31]);
  RTC_CHECK(Rng().Generate(bytes.get(), 31));
  str.reserve(36);
  for (size_t i = 0; i < 8; ++i) {
    str.push_back(kHex[bytes[i] % 16]);
  }
  str.push_back('-');
  for (size_t i = 8; i < 12; ++i) {
    str.push_back(kHex[bytes[i] % 16]);
  }
  str.push_back('-');
  str.push_back('4');
  for (size_t i = 12; i < 15; ++i) {
    str.push_back(kHex[bytes[i] % 16]);
  }
  str.push_back('-');
  str.push_back(kUuidDigit17[bytes[15] % 4]);
  for (size_t i = 16; i < 19; ++i) {
    str.push_back(kHex[bytes[i] % 16]);
  }
  str.push_back('-');
  for (size_t i = 19; i < 31; ++i) {
    str.push_back(kHex[bytes[i] % 16]);
  }
  return str;
}

随机UUID的生成与随机串生成本质上没有区别,都是利用RandomGenerator来生成一定长度的串后,将串中的单个字符映射到对应的表中,从而形成自己想要的串。

随机UUID生成过程中用到两个表:kHex 和 kUuidDigit17

static const char kHex[16] = {'0', '1', '2', '3', '4', '5', '6', '7',
                              '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

static const char kUuidDigit17[4] = {'8', '9', 'a', 'b'};

PS: UUID长度36位的串,但其中有5个字符是固定的:4个’-’ 和 1个’4’。因此,生成随机串时,只需要生成31位的,然后按位映射到"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"这个格式即可。

5.2 WebRTC中UUID的用途

  • RtpSender && RtpReceiver的id标识;
  • MediaStream的id标识;

6 随机整数的生成

6.1 CreateRandomId

源码如下:

uint32_t CreateRandomId() {
  uint32_t id;
  RTC_CHECK(Rng().Generate(&id, sizeof(id)));
  return id;
}

相当于直接填充32位的buffer,然后强制按照无符号的32位整型进行解释。

6.2 CreateRandomId64

源码如下:

uint64_t CreateRandomId64() {
  return static_cast<uint64_t>(CreateRandomId()) << 32 | CreateRandomId();
}

生成32位无符号随机整数;然后使用static_cast<uint64_t>强制显示的转换为64位无符号整数,之前的32位随机数将占据64位整数的低32位;然后进行左移32后占据高32位;然后生成新的32位无符号随机整数,进行位或,新的32位无符号随机整数将占据低32位。从而得到的64位无符号整数都是随机产生的。
有个疑问:为什么要如此绕着处理得到64位的值,而不是直接一次性随机产生64位的值呢?

6.3 CreateRandomNonZeroId

uint32_t CreateRandomNonZeroId() {
  uint32_t id;
  do {
    id = CreateRandomId();
  } while (id == 0);
  return id;
}

显而易见,不做多余的解释

6.4 WebRTC中随机整数的用途

  • 发送STUN PING的ConnectionRequest请求所在的连接对象Connection的32位唯一标识id值,使用CreateRandomId()来产生。
  • WebRTC的64位会话id,即PeerConnection.session_id_使用CreateRandomId64()来产生。
  • WebRTC的内部jsep会话控制器对象的ice_tiebreaker_使用CreateRandomId64()来产生。
  • RTP时钟RtpClock的初始序列号以及初始时间值都是采用CreateRandomNonZeroId()产生的32位无符号随机值,然后在该随机值上进行递增的。

7 随机浮点数的生成

源码如下:

double CreateRandomDouble() {
  return CreateRandomId() / (std::numeric_limits<uint32_t>::max() +
                             std::numeric_limits<double>::epsilon());
}

获取32位无符号整型随机数、32位无符号整型最大值、double类型的比1.0略多的一个浮点数。
然后先都转换成浮点数,进行运算,得到随机浮点数double。

目前WebRTC源码中尚无使用。

PS: 生成64位随机无符号整数、生成double类型的随机浮点数时,为什么不直接生成64位的随机值,然后再进行转换了?当前做法是生成32位的无符号随机整数,然后再向64位的随机整数/double浮点数转换?是因为ssl库中直接生成64位随机数所耗性能 >两次生成32位随机数?——这个留待以后有时间去研究RAND_bytes()方法实现原理再来深究吧

8 总结

行文至此,大致将WebRTC的基础模块rtc_base/helpers.cc下的各类随机值生成方法介绍完毕了。回顾下要点:

  • RandomGenerator && SecureRandomGenerator是提供随机值的重要接口和实现类。本质上,随机值的产生依赖于底层的ssl库——boringSSL。因此,在初始化随机值系统之前,即调用rtc::InitRandom()之前需要确保调用了rtc::InitializeSSL();
  • 生成随机字符串 && UUID过程中,先利用RandomGenerator生成一定长度的uint8_t[len]数组,然后挨个将数组中的字符映射到某个字符表中,比如随机字符串生成默认映射到base64的字符表中。这样使得生成的字符串 && UUID称为可打印的。
  • 生成整数 && 浮点数过程中有个疑问:即在生成64位随机无符号整数、生成double类型的随机浮点数时,为什么不直接生成64位的随机值,然后再进行转换了?当前做法是生成32位的无符号随机整数,然后再向64位的随机整数/double浮点数转换?是因为ssl库中直接生成64位随机数所耗性能 >两次生成32位随机数?
WebRTC(Web Real-Time Communication)是一个开源项目,它提供了在浏览器中实现实时音视频通信的技术。下面是对WebRTC源码的简要分析WebRTC源码主要分为以下几个模块: 1. 信令(Signaling)模块:负责建立和维护通信的连接。它使用WebSocket或者其他协议进行通信,包括传输SDP(Session Description Protocol)和ICE(Interactive Connectivity Establishment)信息。 2. 媒体(Media)模块:处理音视频据的采集、编码、解码和传输。媒体模块使用WebRTC提供的API,通过WebRTC的PeerConnection建立点对点的媒体通信。 3. 网络(Networking)模块:处理网络传输相关的功能,例如NAT穿越、ICE候选地址的收集和选择、STUN和TURN服务器的使用等。 4. 安全(Security)模块:处理加密和身份验证相关的功能,确保通信过程的安全性和隐私性。 5. SDP解析(SDP Parsing)模块:解析和生成SDP信息,SDP包含了关于媒体会话的描述和参。 6. ICE代理(ICE Agent)模块:负责管理ICE协议的运行,处理候选地址的收集和选择,以及NAT穿越等功能。 7. RTP/RTCP模块:处理音视频的实时传输协议(RTP)和实时传输控制协议(RTCP),包括据包的发送和接收、丢包恢复、拥塞控制等。 8. 编解码器(Codec)模块:负责音视频据的编码和解码,WebRTC支持一系列开源编解码器,如VP8、VP9、H.264等。 这些模块之间相互协作,实现了基于浏览器的实时音视频通信。WebRTC源码使用C++语言编写,涉及到了底层的网络和媒体处理,同时也提供了一系列的API供开发者使用。 请注意,由于WebRTC源码较为庞大,这里只是简要地介绍了主要模块,实际的源码分析需要深入研究和阅读源码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值