智能时代-书摘

  • 大数据的特点:3V。大量vast,及时性velocity,多样性variety
  • 自动问答领域7WH问题:what,when,where,which,who,why,how
  • 计算机产生智能的三个要素是数据、数学模型和硬件基础
  • 世界上很多看似人工智能的问题都可以变为分类问题
  • 摩尔定律:戈登摩尔-集成电路的能力每18个月翻一番
  • 计算机获得智能的方式,它并非模仿人的思维方式产生,而是建立在大数据、摩尔定律和数学模型基础之上,通过将问题转化成计算问题,效果上达到人的水平。
  • 经典方法论的核心是:大胆假设,小心求证
  • 经典机械思维的核心:确定性和因果关系
  • 当一个系统完全达到恒温时,就无法做功了,这时熵最大。
  • 熵:在一个封闭的系统中,熵永远是朝着不断增加的方向发展的。也就是说,从微观上将,这个系统越来越无序;从宏观上看,他趋于恒温。
  • 用不确定性的眼光看世界,再用信息消除不确定性
  • 互信息:信息相关性的度量
  • 香农第一定律:任何信息都存在一种编码方法,使得平均编码长度可以非常接近它的信息熵,但是你不能找到一个编码方法,让平均编码的长度小于信息熵
  • 将最好的资源用在出现频率最高的地方,同时兼顾一定的资源防止黑天鹅。
  • 香农第二定律:信息的传播速率不可能超过信道的容量
  • 人脉就是人与人交往的带宽
  • 很多智能问题从根本上来讲就是消除不确定性的问题
  • 搜索质量的竞争其实是浏览器或者其他客户端软件市场占有量的竞争
  • 当一项技术普及之后,人类很难回到没有它的状态
  • 原有产业+新技术=新产业
  • What Andy gives,Bill takes away
  • 技术革命导致商业模式变化
  • 生产会越来越过剩,人们对服务的需求会越来越强烈
  • 提供服务虽然不像销售产品一次能挣很多的钱,但是细水长流的技术服务最终会给这些服务的提供者带来更长久的生意,更多的利润
  • 新技术不断出现,这些具体的技术知识术的进步,只是“术”的进步,而思维方式和做事方式才是“道”的提升
  • 专利数量和创造力、竞争力并没有强关联性
  • 当一家企业能够有机会为每一个家庭提供服务时,他的生意就不会中断
  • 今天,厂商之间的核心竞争力不再是商品本身,而是看谁能整体把握住这个机会。
  • 思维方式的好与坏、先进与落后,决定了一个人能否利用得好技术革命的成就,使自己成为时代的主人
  • 并行计算中,使用的处理器越多,并行计算的效率越低
  • 只有权力才能制约权力
  • 任何能够采集数据的设备都是传感器
  • 人工智能是大脑,iot是神经系统。iot中数量巨大的传感器和设备扮演着众多感官细胞的角色,而5G相当于周围神经。区块链承载着生物信号
  • IOT的两大方向:操作系统,通信标准
  • 企业的基因决定论:老的公司必须由新的公司完成更新换代的革命,这是由企业的基因决定的
  • 物联网市场巨大,远比今天的互联网市场大的多
  • 数据资产的两个问题:所有权问题,容易被复制的问题
  • 未来,由政府主导的区块链大数据平台是方向
  • 智能社会愿景:人类能够更好地了解自己;解决商业纠纷;提高社会运行效率;能够把人从重复性的工作解放出来
  • 未来热点领域:人工智能,iot,5g,区块链
  • 守规矩并非人生来具有的本性,甚至有点违背人性
  • 期权制度最大的威力在于将过去企业在利益分配上的零和游戏,变成了一种非零和游戏。期权制度最大的好处在于它不是零和游戏,如果企业办得好,大家是从资本市场上获利,都有利可图;如果办得不好,大家只能团结起来扭亏为盈,重新得到市场的认可。
  • 大数据隐私问题:1984-big brother
  • 智能时代,产能过剩将是全球所有工业化国家都要面临的一个问题
  • 社会公平只能反映在机会的平等上,而不是结果的公平
  • 挣当2%的人,与时俱进

认知自我是很难的事情

欲望,本身是一个中性词,他可以代指世间一切的追求和念想。

人是被欲望支配的动物,一个没有欲望的人是会死掉的。我们日常的各种行为和决策,从根本上讲,其实都是围绕我们的欲望开展的。

认知自我是最难的事情,因为要客观认知,就意味着要否定和肯定并存,而否定自我是大部分人做不到的,进一步说大部分人无法找到否定自我后进一步的进取对策,找到它的积极意义。

我们要正确认知自我,才能保证我们对自己做出正确的评估和人生决策,才能指导我们对现实世界的事务目标做出正确决策和评估,才能保证我们有能力落地实施,最终达成目标,创造美好生活。

这种认知不仅需要对自我经历的思考,也需要结合社会科技人文的动向和社会需求来进行。能正确理解当今的社会现状和事实,具备基本的知识,以此为标准对自我进行分析和反思。

认知自我是我们制定人生目标的前提,是我们行动的参考,在我们做任何决策和行动前,我们都应该先清楚认知自我的内心,能力和欲望。

认知自我也是我们克服焦虑的唯一方法。

pixel3无法访问网络

新拿到的pixel3原系统,发现wifi连接后受限,4g lte也无法打开网页,提示SSl相关的问题。

找到的解决方法:

  1. 将手机开启到开发者模式
  2. 开启USB调试
  3. 启用adb来进行设置:
    没有ROOT情况 (adb命令要自行安装与配置)
    没有 ROOT 的安卓机可以借助 ADB 命令来修改,首先下载ADB工具包,然后手机开启USB调试模式,接着运行 CMD 输入下面的命令就可以了。
    # 删除默认的地址
    adb shell settings delete global captive_portal_https_url
    adb shell settings delete global captive_portal_http_url
    # 修改新的地址
    adb shell settings put global captive_portal_http_url http://captive.v2ex.co/generate_204
    adb shell settings put global captive_portal_https_url https://captive.v2ex.co/generate_204
    改完同样把手机切换飞行模式,再切换回来就可以了。如果需要其它服务器地址,自行修改,如 MIUI 的是 http://connect.rom.miui.com/generate_204 地址

EFR32BG22系列-windows开发环境搭建及出厂例程验证

最近对ThreadX很感兴趣,TreadX官方文档中有Silicon Labs EFR32MG12的代码支持,我手头没有这块板子,但我之前有幸拿到了朋友送的配置可谓极致的silicon labs BG22开发板,所以想尝试下这个板子是否也可以用。

这个板子可谓精致,还有配套的演示app,可玩性很高,但是一直忙于其他项目,没有时间研究。所以趁国庆有空,拿出来吃灰的板子研究下,希望以后能有机会拿它在高端和超低功耗场景拿来做产品,同时也算是对朋友的一个交代。

万事开头难,我们要怎么快速入门silicon labs的系列产品呢?对于c类型项目来说,最难的入门门槛就是开发环境和开发流程,搞定这些,到了纯编码部分就是很快的事情了。

本文内容分为以下几个部分

  • EFR32BG22开发板硬件配置介绍
  • EFR32BG22 windows开发环境搭建
  • 出厂例程的配套APP功能介绍

EFR32BG22开发板硬件配置介绍

开发板图片

file

规格

小封装 Thunderboard

  • 兼容 EXP 的外接头
    目标设备
  • EFR32BG22
    • 适用于大容量产品的安全蓝牙 5.2 SoC
    • 76.8 MHz、具有 512 kB 闪存和 32 kB RAM 的 ARM Cortex-M33 内核
    • 蓝牙 5.2 无线电,支持测向和 LE 编码 PHY
  • 38.4 MHz HFXO 晶体
  • 32.768 kHz LFXO 晶体
  • 2.4 GHz 匹配网络和贴片天线
    板载板控制器
  • J-Link 调试器
    • SWD 物理层
  • UART 数据包追踪/异步协议
  • 带硬件流控制功能的虚拟 COM
    用于调试连接的 USB Micro-B 连接器
    用户接口功能:
  • 1x 按钮(带 EM2 唤醒功能)
  • 1x LED
    数据存储 / OTA 支持
  • 8 Mbit SPI 闪存
    节能特性
  • 适用于传感器的可控独立电源域
    Android 和 iOS 移动应用程序
  • 查看传感器数据、控制 LED 灯和检测按钮操作
  • iOS 应用程序得到快速实施
  • 以原代码形式实现的 Android 应用程序
  • GitHub 上可用的源代码
    传感器
  • 相对湿度和温度传感器:Si7021
  • UV 和环境光传感器:Si1133
  • 霍尔效应传感器:Si7210
  • 6 轴 IMU:Invensense ICM-20648
    Mini Simplicity 调试连接器(兼容 SLSDA001A),可接入:
  • AEM
  • PTI
  • VCOM
  • SWD

除了左右两个MIC,以上的传感器和按键以及led都在APP中可以互通。

EFR32BG22 windows开发环境搭建

  1. 确保你的系统是win10,因为最新的simplicity5主要是针对win10兼容的。我自己得老爷本是win7,特此升级到了win10,废了不少时间。
  2. 从官网下载Simplicity Studio并运行安装程序,一切按照指导操作就可以。
  3. 环境变量的配置,主要是silicon相关工具的path配置,这个可以放到后面实际使用时再针对性的来配置。
  4. 在simplicity中根据开发板类型下载正确的SDK。这一步需要连接开发板,要确认正确的usb线,开发板链接电脑后,在设备管理器里面应该出现正确的j-link和com端口。Tools->Simplicity Commander,然后选择正确的开发板。
  5. 克隆出厂例程,出厂例程有完整的测试app和驱动,方便我们后继开发使用。

  6. 编译:project->build project。
  7. 编译成功。
  8. 烧录下载:操作flash programmer按钮。因为boot的原因,默认我们的例程是不包含bootloader的,所以下载时要注意首地址,系统默认boot中应用的首地址是0x6000。如果不小心boot被擦除了,就要重新下载bootloader。如果直接不小心下载到0地址,boot会被覆盖,而且程序也不会正常运行。
  9. 下载成成功。
  10. 复位运行,正确下载后,开机后VCOM会有log输出,同时黄色LED会闪烁。
    [I] Thuderboard demo initialised
    [I]  sv = 3.031  svl = 3.031   i = 0.003   r = 0.072
    [I] Bluetooth stack booted: v3.2.3-b273
    [I] Bluetooth public device address: 84:2E:14:31:CA:5D

注意事项

通过simplicity 的下载按钮下载的只是应用,是不包含bootloader的,如果之前全片erase过,下载后是无法正常运行的。更新boot的方法就是重新下载一个带boot的,然后再重新更新应用,或者独立下载系统内置的默认bootloader。

问题1

在我们尝试 erase时,出现下面的问题:

DP write failed
Could not access Debug challenge interface

解决
参考官方论坛的解答,断开重连问题得以解决: https://silabs-prod.adobecqms.net/community/software/simplicity-studio/forum.topic.html/can_t_program_bgm220pc22hnamoduledpwritefaile-j6BP

进一步的原因,其实官方的文档里面有描述:
https://www.silabs.com/documents/public/training/wireless/bg22-thunderboard-workshop-out-of-the-box-thunderboard-example-project.pdf

Error: DP Write Failed - Press the Reset button on Thunderboard or unplug/replug then Flash again within 30 seconds.
The Thunderboard demo app which ships on the boards goes into a low energy mode (EM2) after 30 seconds. When the device is in EM2,
the debug interface is unavailable, and DP write fails. We can wake the device by resetting Thunderboard

出厂例程的配套APP功能介绍

在烧录下载了我们自己编译的嵌入式版本后,我们接下来要用原厂配套的APP来测试各个功能,以确保我们手头的代码版本功能正常。

  1. 下载app。可以从github直接下载 https://github.com/SiliconLabs/thunderboard-ios https://github.com/SiliconLabs/thunderboard-android ,也可以从各手机商店下载。iphone 可以直接在商店搜索“Thunderboard”。
  2. 点开APP,复位开发板,因为板子可能进入睡眠模式,无法连接。复位开机的30s内是可以正常连接的(经测试,出厂的原装版本会蓝牙休眠,我们新的编译的版本不会,可以随时连接)。正常的情况下,app下方会显示开发板蓝牙的名称。
  3. APP功能主界面
  4. Motion功能,主要是测试的6轴IMU,当我们旋转开发板时,APP的仿真目标会根据三轴的角度,同步变化。
  5. 环境传感器数据,会显示实际的温湿度,环境光,UV,霍尔开关状态,地磁参数。其中霍尔开关和地磁是同一个芯片Si7210出来的数据。
  6. IO控制,这个主要演示的是通过app控制载LED,以及板载按键开关的反向状态同步到APP。

至此,我们编译的版本,在APP的各项测试中都正常通过,跟出厂的原装版本表现一致,也就证明了我们这套代码以及环境的可用性和正确性,接下来我们就可以放心基于这套驱动和参考代码来构建我们自己的功能产品了。

结论

不论是从芯片支持的外围传感器的驱动丰富性,还是从开发IDE Simplicity和配套APP的易用和完备角度来看,EFR32BG22芯片及其开发SDK套件都是低功耗蓝牙物联网解决方案的极具竞争力选择,其芯片价格在一线品牌中也很有竞争力,具有极高性价比,值得我们深入挖掘和研究。

EFR32BG22如何烧录内置bootloader

BG22带蓝牙协议栈的版本是需要bootloader的。如果开发过程中我们不小心把bootloader覆盖了或者擦除了,怎么重新烧录?

有2种方法:

  • 烧录已经自带boot的目标文件,比如出厂默认的例子:Bluetooth - SoC Thunderboard EFR32BG22 (BRD4184A)。然后再单独更新应用部分,注意确认好起始地址,不要搞错了。目前内置默认的boot的应用首地址是0x6000。
  • 烧录独立的bootloader,可以是自己编译的,也可以是内置的。

第1种在simplicity中可以一键完成,所以我们不再赘述。我们接下来重点详细介绍第2种方法。

EFR32BG22单独更新boot的步骤

  1. 在当前工程的生成目录下打开命令行
    file
  2. 执行命令行:
    commander flash D:\SiliconLabs\SimplicityStudio\v5\developer\sdks\gecko_sdk_suite\v3.2\platform\bootloader\sample-apps\bootloader-storage-internal-single-512k\efr32mg22c224f512im40-brd4182a\bootloader-storage-internal-single-512k.s37

    这里需要需要注意环境变量的问题,如果提示不能识别commander,就需要自己手动配置。我们这里使用的是系统默认的boot,如果用自己的,注意改成实际boot项目的路径。
    成功后提示如下:
    file

Thunderboard EFR32BG22 summary

VCOM

The serial format is 115200 bps, 8 bits, no parity, and 1 stop bit by default.

Recover bricked Device

实际是先整片擦除,然后自动重新烧录了bootloader

合并bootloader和应用

commander convert bootloader-uart-bgapi_BG21_test.s37 your_application.s37 -o app+bootloader.s37

合并bootloader和应用的命令在UG162文档描述如下:

5.5.1 Combine Two Files
Converts two files with different file formats into one specified output file. Command Line Syntax:
$ commander convert <filename> <filename> [--address <address>] --outfile <filename>

注意:需要指定转换文件的路径,如果不指定路径,需要把转换的文件放到commander软件同一路径下。

php readfile无法访问七牛图片的问题

今天国庆有空,所以抽空定位下之前发现的七牛头像文件接口访问失效的问题。接口以前是正常的,中间无代码修改,最近发现有错误日志,同时app头像加载异常。

经过服务器业务日志分析,是php 的readfile函数失效。

在出问题的服务器用wget 和curl测试都无法很快下载,需要等非常长时间,但是我本地的浏览器正常,能很快访问。所以接口失败就应该是超时导致的。刚开始没有头绪,以为是七牛的安全相关的问题,后面根据wget的日志,发现每次都优先解析的是ipv6地址,于是怀疑可能是ipv6的问题。

于是禁用服务器的ipv6:

编辑文件/etc/sysctl.conf,
vi /etc/sysctl.conf

添加下面的行:
net.ipv6.conf.all.disable_ipv6 =1
net.ipv6.conf.default.disable_ipv6 =1

如果你想要为特定的网卡禁止IPv6,比如,对于enp0s3,添加下面的行。
net.ipv6.conf.enp0s3.disable_ipv6 =1

保存并退出文件。

执行下面的命令来使设置生效。
sysctl -p

再次尝试wget,curl,皆正常,于是测试php接口,恢复正常。

ios map:zero map Length edge on polygon boundary

ios:15.0.1
xcode:13.0

在苹果地图页面手势缩放时,会显示:

[VKDefault] Zero Length edge on polygon boundary

然后多次反复缩放,一直触发这个日志,然后系统就会提示内存泄露 memory leak,然后崩溃。

==14901==ERROR: AddressSanitizer: allocator is out of memory trying to allocate 0x110 bytes
==14901==FATAL: AddressSanitizer: internal allocator is out of memory trying to allocate 0x50 bytes
warning: could not execute support code to read Objective-C class data in the process. This may reduce the quality of type information available.
AddressSanitizer report breakpoint hit. Use 'thread info -s' to get extended information about the report.

暂时没有办法,只能等新版本再观察了,如果有解决方案的朋友希望留言,谢谢

区块链杂记

DAO: Decentralized Autonomous Organization(去中心化自治组织)

瑞波币(XRP): 是瑞波(Ripple)系统内的流动性工具,是一个桥梁货币,是各类货币之间兑换的中间品

TRX: 波场币发行 波场币是由波场基金会发行的基于波场协议的主网货币,简称TRX。 TRX是TRON区块链上账户的基本单位,所有其他代币的价值均从TRON价值衍生出来,TRX也是所有基于TRC标准代币的天然桥梁货币。

TRC20:类似ERC20,是一种基于波场的转帐协议或者通道.

DXO: DeepSpace Token

L1,L2: https://www.jianshu.com/p/2c957b67fa19

其中第 0 层对应 OSI 模型的底层协议,大致包括物理层、数据链路层、网络层和传输层。第一层(Layer 1)大致包括数据层、共识层和激励层。
而第 2 层(Layer 2)则主要包括合约层和应用层。
按照这个维度来划分,像我们所熟悉的比特币网络、以太坊主网等主流公链都属于 Layer 1 的范畴。只不过,由于在当前众多的公链项目中,以太坊是运行智能合约、DAPP 最多的公链,也是锁仓资产价值和日均交易量最大的公链,所以在有关以太坊网络 Layer1 和 Layer2 不同扩容方案的讨论也是最多的,所以在本文中,如没有特殊说明,所提到的 Layer1 和 Layer2 一般以以太坊为主。
通俗来说,在以太坊网络中,Layer 1 的主要作用就是确保网络安全、去中心化及最终状态确认,做到状态共识,并作为一条公链网络中可信的“加密法院”,通过智能合约设计的规则进行仲裁,以经济激励的形式将信任传递到 Layer2 上;而 Layer2 则以追求更高效的性能为终极目标,从上面区块链技术逻辑架构示意图中,我们可以看到,作为第二层网络,可以替 Layer1 承担大部分计算工作,近年来,不少项目都是基于 Layer2 搭建的,从而将交易行为从主链上分离出来,降低一层网络的负担,提高业务处理效率,从而实现扩容。在这个过程中,Layer2 虽然只做到了局部共识,但是基本可以满足各类场景的需求。
目前行业内比较贴切的是将 Layer1 和 Layer2 的关系和中央银行与商业银行的关系来类比:把 Layer1 承担着中央银行的角色,而 layer2 则是各大商业银行。
在现行主流的金融系统中,所有的资产都必须在中央银行结算,而具体的流通过程可以同时发生在中央银行和商业银行。因为如果所有人都去央行结算的话,势必会发生业务拥堵的情况,更好的解决办法当然是由商业银行来先处理大量交易业务,然后由各个商业银行和中央银行结算一次整体业务,这样才能使得整个金融系统更加高效有序的运转起来。
所以从中我们能够得到的启示就是,对于在以太坊网络中存在的交易拥堵、手续费居高不下的问题,一个可行的解决方案就出炉了——将以太坊的资产存入 Layer2,之后的资产流动交易环节都在 Layer2 上进行,只把最终结算过程放到 Layer1 上就可以了.

区块链杂记

celr: celer network 代币,属于l2技术,主打链下链上结合,发挥链下方案的性能优势
ren: republic protocol 代币,主打私密撮合交易
rune: THOR chain的代币,主打快速结算
dydx:基于ERC20的dydx exchange平台的token,主打deFi和低手续费

unraid中安装qbittorrent

  1. 创建应用需要映射的目录
mkdir /mnt/user/appdata/qbittorrent
cd /mnt/user/appdata/qbittorrent/
mkdir -p config data
  1. 创建docker
WEB_PORT=8082
BT_PORT=8999
docker run -d --name qbittorrent \
-e PUID=$UID \
-e PGID=$GID \
-e WEB_PORT=8082 \
-e BT_PORT=8999 \
--restart=always \
-p $WEB_PORT:$WEB_PORT -p $BT_PORT:$BT_PORT/tcp -p $BT_PORT:$BT_PORT/udp \
-v /mnt/user/appdata/qbittorrent/config:/config \
-v /mnt/user/appdata/qbittorrent/data:/data \
-v /mnt/user/downloads:/downloads \
80x86/qbittorrent:4.2.1-amd64

访问: http://192.168.x.x:8082/
账号: admin adminadmin

其他细节和设置可以参考:
https://post.smzdm.com/p/a992mwxo/

docsify maxLevel subMaxLevel参数说明

maxLevel,subMaxLevel是docsify中的两个重要的参数,用来控制整个文档站左侧导航菜单的层级。

作用

maxLevel:控制着左侧的菜单的总层级.
subMaxLevel:表示由文档内容中的标题自动生成菜单的级别,从第一级起算。例如subMaxLevel取2,表示一共两级,所以实际只显示第二级(##),因为第一级默认跟外层的入口同级,显示外层的名字。

数值关系

maxLevel>=1
subMaxLevel>=1 && subMaxLevel<maxLevel

source code illustration of TLS support in esp32

嵌入式下实现TLS支持原理

我们知道要实现https,MQTT等协议时,要求通讯安全,客户端就必须实现tls证书的支持。

但是日常我们打来电脑和手机浏览器访问https网站,好像并不需要关注tls的问题?

答案是因为浏览器厂商已经帮助我们兼容好了,但当你要通过嵌入式IOT设备或者单片机实现https访问的时候,你就需要处理TLS证书的问题了。

因为ESP32的开源代码比较清晰简洁,所以我们今天的讲解,以ESP32为硬件平台。其他硬件也是类似的原理,你只要实现自己的过程,替换底层的socket接口就可以实现类似的效果。

此文通过对关键代码的详细讲解,说明在ESP32下支持TLS证书的实现原理和过程。以此原理和过程作为参考,用户也可以实现其他嵌入式硬件和单片机的TLS证书支持,因为ESP的证书的内容和代码都是源码可见的,在弄懂了后就可以很好的移植和仿制。

ESP32 HTTPS demo

官方的例子见 \examples\protocols\https_request,举例了三种支持TLS的方式:

    https_get_request_using_crt_bundle();
    https_get_request_using_cacert_buf();
    https_get_request_using_global_ca_store();

第一种是cert bundle方式,这个是今天我们重点的讲解。后两种就是使用用户指定的证书的方式,代码非常简单,就不展开了,今天主要介绍 cert bundle的方式。把cert bundle方式弄清楚了,后面两种也就清楚了,因为就是等于是cert bundle 方式的简化,简化了证书的创建,匹配和校验过程。

cert bundle的优劣

  • 优点:cert bundle的优势是不需要用户关注证书和手动下载证书,自动实现全球几乎所有TLS根证书的支持。
  • 缺点:因为内置一百多种证书,所以占用空间要大写,经过实际代码测试验证,大概大了60K左右。

ESP32 x509 Certificate Bundle

esp32 针对TLS的场景提供了x509 Certificate Bundle的实现支持,其可以简单理解为 x509证书集合。

关于x509 Certificate Bundle的详细介绍,可以参看:https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/protocols/esp_crt_bundle.html

原文已经讲的非常清楚了,我们就不赘述了,今天只重点关注源码细节。

ESP32 TLS实现过程简述

  1. cert bundle 反序列化
  2. 创建空的证书
  3. 在https的访问回调中,根据cert name,查找对应的证书。
  4. 校验证书合法性
  5. 校验通过后,建立链接,进行基于tls的读写过程。
  6. 关闭链接,释放tls对象

源码分析

上面简述了整个过程,接下来我们将针对上面的步骤,对关键源码进行解析说明。

cert bundle 反序列化

外层调用:

if (s_crt_bundle.crts == NULL) {
        ret = esp_crt_bundle_init(x509_crt_imported_bundle_bin_start);
    }

参数 x509_crt_imported_bundle_bin_start 对应的值为 asm("_binary_x509_crt_bundle_start")

这个asm对象表示一段汇编代码,作用是返回x509对象数据地址,这个对象是通过python工具将cacrt_all.pem打包成二进制文件的,具体汇编的内容,可以自己参见 build目录下的 x509_crt_bundle.S汇编文件。

里层函数实现,解析注释:

static esp_err_t esp_crt_bundle_init(const uint8_t* x509_bundle)
{
    // 根据包头的两个字节获取证书个数
    s_crt_bundle.num_certs = (x509_bundle[0] << 8) | x509_bundle[1];
    s_crt_bundle.crts      = calloc(s_crt_bundle.num_certs, sizeof(x509_bundle));

    if (s_crt_bundle.crts == NULL) {
        ESP_LOGE(TAG, "Unable to allocate memory for bundle");
        return ESP_ERR_NO_MEM;
    }

    const uint8_t* cur_crt;
    cur_crt = x509_bundle + BUNDLE_HEADER_OFFSET;

    ESP_LOGW(TAG, "cert num:%d", s_crt_bundle.num_certs);

    // 根据个数一个一个的来偏移获取每个证书的内容
    for (int i = 0; i < s_crt_bundle.num_certs; i++) {
        s_crt_bundle.crts[i] = cur_crt;
        // 每个证书前面4个字节是长度信息
        size_t name_len = cur_crt[0] << 8 | cur_crt[1];
        size_t key_len  = cur_crt[2] << 8 | cur_crt[3];
        ESP_LOGW(TAG, "cert name len:%d,key len:%d", name_len, key_len);
        // 根据长度进行每个证书的偏移
        cur_crt = cur_crt + CRT_HEADER_OFFSET + name_len + key_len;
    }

    return ESP_OK;
}

这段代码的作用就是 将入参的cert bundle 地址内存,根据数据结构定义{BUNDLE_HEADER_OFFSET,{name_len,key_len, name,key}}反序列化成cert结构数组对象 s_crt_bundle。

截止本文撰写日期,esp32 实现的这个x509 cert bundle 支持135个根证书,几乎支持了全球所有的证书。

创建空的证书

mbedtls_x509_crt_init(&s_dummy_crt);
mbedtls_ssl_conf_ca_chain(ssl_conf, &s_dummy_crt, NULL);

这个空证书的作用主要是承载访问服务器时获取的服务器证书特征,这个特征会通过回调的方式传给 mbedtls_ssl_conf_verify 函数参数。

查找和校验证书

代码如下,解析见注释:

int esp_crt_verify_callback(void* buf, mbedtls_x509_crt* crt, int depth, uint32_t* flags)
{
    mbedtls_x509_crt* child = crt;

    /* It's OK for a trusted cert to have a weak signature hash alg.
       as we already trust this certificate */
    uint32_t flags_filtered = *flags & ~(MBEDTLS_X509_BADCERT_BAD_MD);

    if (flags_filtered != MBEDTLS_X509_BADCERT_NOT_TRUSTED) {
        return 0;
    }

    if (s_crt_bundle.crts == NULL) {
        ESP_LOGE(TAG, "No certificates in bundle");
        return MBEDTLS_ERR_X509_FATAL_ERROR;
    }

    ESP_LOGD(TAG, "%d certificates in bundle", s_crt_bundle.num_certs);

    size_t name_len = 0;
    const uint8_t* crt_name;

    // start 和 end 是证书数组的index。这里实现的是二分查找算法,说明cert bundle     
    // 数组的名字是增加了排序特征的,具体细节,需要查看python的打包工具的实现代码。
    bool crt_found = false;
    int start      = 0;
    int end        = s_crt_bundle.num_certs - 1;
    int middle     = (end - start) / 2;

    /* Look for the certificate using binary search on subject name */
    while (start <= end) {
        name_len = s_crt_bundle.crts[middle][0] << 8 | s_crt_bundle.crts[middle][1];
        crt_name = s_crt_bundle.crts[middle] + CRT_HEADER_OFFSET;

        int cmp_res = memcmp(child->issuer_raw.p, crt_name, name_len);
        if (cmp_res == 0) {
            crt_found = true;
            break;
        } else if (cmp_res < 0) {
            end = middle - 1;
        } else {
            start = middle + 1;
        }
        middle = (start + end) / 2;
    }
    // 二分查找结束

    // 校验证书合法性
    int ret = MBEDTLS_ERR_X509_FATAL_ERROR;
    if (crt_found) {
        size_t key_len = s_crt_bundle.crts[middle][2] << 8 | s_crt_bundle.crts[middle][3];
        ret = esp_crt_check_signature(child, s_crt_bundle.crts[middle] + CRT_HEADER_OFFSET + name_len, key_len);
    }

    if (ret == 0) {
        ESP_LOGI(TAG, "Certificate validated");
        *flags = 0;
        return 0;
    }

    ESP_LOGE(TAG, "Failed to verify certificate");
    return MBEDTLS_ERR_X509_FATAL_ERROR;
}

代码很简单,注释中已经说了,就是一个二分查找过程。

校验证书合法性

相关代码在上面的部分已经注释过了,就是esp_crt_check_signature函数。校验的细节,感兴趣的朋友可以自己查看此函数源码。

TLS读写过程

写请求过程:

do {
        ret = esp_tls_conn_write(tls, REQUEST + written_bytes, sizeof(REQUEST) - written_bytes);
        if (ret >= 0) {
            ESP_LOGI(TAG, "%d bytes written", ret);
            written_bytes += ret;
        } else if (ret != ESP_TLS_ERR_SSL_WANT_READ && ret != ESP_TLS_ERR_SSL_WANT_WRITE) {
            ESP_LOGE(TAG, "esp_tls_conn_write  returned: [0x%02X](%s)", ret, esp_err_to_name(ret));
            goto exit;
        }
    } while (written_bytes < sizeof(REQUEST));

esp_tls_conn_write根据长度来循环写。

读响应过程:

    do {
        len = sizeof(buf) - 1;
        bzero(buf, sizeof(buf));
        ret = esp_tls_conn_read(tls, (char*) buf, len);

        if (ret == ESP_TLS_ERR_SSL_WANT_WRITE || ret == ESP_TLS_ERR_SSL_WANT_READ) {
            continue;
        }

        if (ret < 0) {
            ESP_LOGE(TAG, "esp_tls_conn_read  returned [-0x%02X](%s)", -ret, esp_err_to_name(ret));
            break;
        }

        if (ret == 0) {
            ESP_LOGI(TAG, "connection closed");
            break;
        }

        len = ret;
        ESP_LOGD(TAG, "%d bytes read", len);
        /* Print response directly to stdout as it is read */
        for (int i = 0; i < len; i++) {
            putchar(buf[i]);
        }
        putchar('\n'); // JSON output doesn't have a newline at end
    } while (1);

esp_tls_conn_read 循环读,直到链接关闭为止,跳出循环

释放 tls对象:

esp_tls_conn_delete(tls);

结束

好了,整个代码过程还是封装的非常干净和清晰的,如果你需要在自己的嵌入式系统中实现TLS证书过程,那么也可以仿照以上的过程,实现自己的bundle和匹配校验过程,并且可以根据实际需要定制和优化。

希望以上的分析,能帮你快速实现自己的TLS访问功能。

letsencrypt root certification expire illustration

letsencrypt 跟证书过期更换说明

见官方的声明 https://letsencrypt.org/docs/dst-root-ca-x3-expiration-september-2021/

今年2021.9.30后,原有的DST RootCA X3就要过期了,改成ISRG Root X1。

对于浏览器用户基本不用担心,因为浏览器厂商自动做了支持。但是对于老旧的设备和一些嵌入式IOT设备等,就需要支持最新的证书,否则可能出现访问的问题。

use openssl client to download ssl certification

显示目标链中的所有证书:

openssl s_client -showcerts -connect www.xxx.com:443 </dev/null

当有多个子域名的时候,第一个不一定是想要的,所以自己根据CN来判断到底是哪组证书。然后自己截取 "-BEGIN CERTIFICATE-" "-END CERTIFICATE-" 之间的部分。

如果直接取第一个:

openssl s_client -showcerts -connect www.xxx.com:443 </dev/null |sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > test.cert