事情已通过去快一周了吧,继上次修复 maixpy k210 的 esp8285 at 通讯后,忽然遇到泽畔大大问,要不要作 ussl 的支持?python
评估了一下各方的实现,想了一下本身也恰好在作网络层的优化和处理,何况 micropython 在 stm32 、 esp32 上的也有对应的实现,那就添加实现进去吧,选取了 mbedtls 版本的 ussl 模块,实现相关文件以下。git
这里说一下 ussl 的工做机制。github
首先创建在 micropython 的 network 架构下的 socket 模块,提供了关键的 steam->write 和 steam->read 基础接口,实际上就是继承一个抽象 steam 对象的接口。json
所以 ussl 提供了 wrap_socket 用来提高 socket 的功能,从而支持 https 的访问。网络
咱们看一下 micropython 的实例就知道了。架构
try: import usocket as _socket except: import _socket try: import ussl as ssl except: import ssl def main(use_stream=True): s = _socket.socket() ai = _socket.getaddrinfo("google.com", 443) print("Address infos:", ai) addr = ai[0][-1] print("Connect address:", addr) s.connect(addr) s = ssl.wrap_socket(s) print(s) if use_stream: # Both CPython and MicroPython SSLSocket objects support read() and # write() methods. s.write(b"GET / HTTP/1.0\r\n\r\n") print(s.read(4096)) else: # MicroPython SSLSocket objects implement only stream interface, not # socket interface s.send(b"GET / HTTP/1.0\r\n\r\n") print(s.recv(4096)) s.close() main()
实现的最终结果以下,不过目前的实测效果距离商业使用,保守来说,还有很大的优化空间,主要在核心函数和配置方面要改善性能。app
MaixPy k210 采用 components/micropython/CMakeLists.txt 来管理 micropython 的编译命令。dom
因此在不脱离主流的基础上,在 micropython-ulab 的配置后面继续添加以下配置。socket
if(1 OR CONFIG_MICROPY_SSL_MBEDTLS) list(APPEND ADD_INCLUDE "${mpy_core_dir}/lib/mbedtls/include") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DMBEDTLS_CONFIG_FILE='\"${mpy_port_dir}/src/mbedtls/include/mbedtls_config.h\"'") # message(${CMAKE_C_FLAGS}) append_srcs_dir(ADD_SRCS "port/src/mbedtls") append_srcs_dir(ADD_SRCS "core/lib/mbedtls/library") list(REMOVE_ITEM ADD_SRCS "${mpy_core_dir}/lib/mbedtls/library/net_sockets.c") endif()
稍微解释一下函数
#if !defined(MBEDTLS_CONFIG_FILE) #include "mbedtls/config.h" #else #include MBEDTLS_CONFIG_FILE #endif
因此如今把代码编译了进去,就完成了大部分的移植,是否是很简单?
固然,事情不会这么顺利的,在没有进行专门配置的时候,启动模块是能够的,如今开始实践发起一次 get https 网站的请求,测试 python code 以下:
wCli = MicroWebCli('https://tcc.taobao.com/cc/json/mobile_tel_segment.htm?tel=13631786501') # wCli = MicroWebCli('https://www.baifubao.com/callback?cmd=1059&callback=phone&phone=13631786501') # wCli = MicroWebCli('https://github.com') # wCli = MicroWebCli('https://ssl.logink.cn/') # wCli = MicroWebCli('https://cn.bing.com/?FORM=Z9FD1') # wCli = MicroWebCli('https://www.sojson.com') # wCli = MicroWebCli('https://www.baidu.com') while True: try: print('GET %s' % wCli.URL) wCli.OpenRequest() buf = memoryview(bytearray(1024)) resp = wCli.GetResponse() if resp.IsSuccess() : while not resp.IsClosed() : x = resp.ReadContentInto(buf) if x < len(buf) : buf = buf[:x] print(bytes(buf)) print('GET success with "%s" content type' % resp.GetContentType()) else : print('GET return %d code (%s)' % (resp.GetStatusCode(), resp.GetStatusMessage())) except Exception as E: print(E) time.sleep(2)
Gather entropy_len bytes of entropy to seed state
mbedtls_ctr_drbg_seed returned -52 或者-0x0034
错误解释:
MBEDTLS_ERR_CTR_DRBG_ENTROPY_SOURCE_FAILED -0x0034 /**< The entropy source failed. */
缘由:
给mbedtls提供的熵源不够混乱,应该用硬件随机数发生器。
解决:
mbedtls\config.h
#define MBEDTLS_ENTROPY_HARDWARE_ALT
解决方案能够参考
https://blog.csdn.net/liaofeifly/article/details/88899655
mbedtls_hardware_poll 实现能够参考 esp32 、stm32 的,以下是我后来实践到 k210 的,这个函数只会在发起连接的时候调用。
#include <stdlib.h> #include <stdio.h> #include "rng.h" #if !defined(MBEDTLS_CONFIG_FILE) #include "mbedtls/config.h" #else #include MBEDTLS_CONFIG_FILE #endif #include "mphalport.h" int os_get_random(unsigned char *buf, size_t len) { int i, j; unsigned long tmp; for (i = 0; i < ((len + 3) & ~3) / 4; i++) { tmp = rng_get() + systick_current_millis(); for (j = 0; j < 4; j++) { if ((i * 4 + j) < len) { buf[i * 4 + j] = (uint8_t)(tmp >> (j * 8)); } else { break; } } } return 0; } int mbedtls_hardware_poll( void *data, unsigned char *output, size_t len, size_t *olen ) { int res = os_get_random(output, len); *olen = len; return 0; } // int mbedtls_hardware_poll(void *data, unsigned char *output, size_t len, size_t *olen) { // uint32_t val; // int n = 0; // *olen = len; // while (len--) { // if (!n) { // val = rng_get(); // n = 4; // } // *output++ = val; // val >>= 8; // --n; // } // return 0; // }
错误类型是
#define MBEDTLS_ERR_SSL_CONN_EOF -0x7280 /**< The connection indicated an EOF. */
这个问题我看了好久,由于上来就给我当头一棒,任何 https 的访问都布星。
那就奇了怪了,而后我开了 debug 开关,开到等级 4 后。
#ifdef MBEDTLS_DEBUG_C // Debug level (0-4) mbedtls_debug_set_threshold(4); #endif
发现 TM 有在流动数据,并且是在工做的,并且看数据发现是正常工做的。
那么开了 debug 和没有开 debug 的区别在哪里呢?
能够想象的是 debug 确定会对程序执行产生一些细微的差距,最后经过几回 debug 的后定位到是这个函数的延时保障了程序的执行。
看起来是否是很奇怪?为何呢?若是在这里简单的延时就会产生下述的效果。
也就是请求时好时坏,若是知道是须要延时的话,那么又该延时多少呢?
此时交给❤名侦探登场❤,真相只有一个!
首先函数来自于 f_send 操做,这个确定是个回调,而后定位它。
void mbedtls_ssl_set_bio( mbedtls_ssl_context *ssl, void *p_bio, mbedtls_ssl_send_t *f_send, mbedtls_ssl_recv_t *f_recv, mbedtls_ssl_recv_timeout_t *f_recv_timeout ) { ssl->p_bio = p_bio; ssl->f_send = f_send; ssl->f_recv = f_recv; ssl->f_recv_timeout = f_recv_timeout; }
说明来自上层,那么继续,定位到 /home/junhuanchen/MaixPy/components/micropython/core/extmod/modussl_mbedtls.c 。
mbedtls_ssl_set_bio(&o->ssl, &o->sock, _mbedtls_ssl_send, _mbedtls_ssl_recv, NULL);
说明回调的是 _mbedtls_ssl_send 函数,以下。
STATIC int _mbedtls_ssl_send(void *ctx, const byte *buf, size_t len) { mp_obj_t sock = *(mp_obj_t*)ctx; const mp_stream_p_t *sock_stream = mp_get_stream(sock); int err; mp_uint_t out_sz = sock_stream->write(sock, buf, len, &err); if (out_sz == MP_STREAM_ERROR) { if (mp_is_nonblocking_error(err)) { return MBEDTLS_ERR_SSL_WANT_WRITE; } return -err; } else { return out_sz; } }
通过测试发现,延时不该该上在 sock_stream->write 以前,而是以后,那么这说明什么问题呢?
若是要解决问题,是能够在这里添加延时解决问题,可是这里是问题的源头吗?显然不是。
判断有二
那么说明问题并非这个地方致使的,在这个 micropython 的体系里,这时候只能说明一个问题,是网卡的 sock_stream->write 请求过快返回致使的问题。
而我在用的是 ESP8285 的 AT 网卡,这就能够联想到 esp_send 的工做机制不必定符合标准 socket 工做时序。
为何呢?
由于 AT 的请求只须要将数据发送过去便可完成传输,但传输正确与否请求这端不直接参与,这与其余芯片的工做到链路层发送数据的机制不一样,因此就会提早返回。
若是个人假设是正确的,那么我应该把问题定位到这里 components/micropython/port/src/standard_lib/network/esp8285/modesp8285.c 之中的 esp8285_socket_send 函数。
STATIC mp_uint_t esp8285_socket_send(mod_network_socket_obj_t *socket, const byte *buf, mp_uint_t len, int *_errno) { if((mp_obj_type_t*)&mod_network_nic_type_esp8285 != mp_obj_get_type(MP_OBJ_TO_PTR(socket->nic))) { *_errno = MP_EPIPE; return MP_STREAM_ERROR; } nic_obj_t* self = MP_OBJ_TO_PTR(socket->nic); if(socket->peer_closed) { *_errno = MP_ENOTCONN; return MP_STREAM_ERROR; } Buffer_Clear(&self->esp8285.buffer);//clear receive buffer socket->first_read_after_write = true; if(0 == esp_send(&self->esp8285,(const char*)buf,len, (uint32_t)(socket->timeout*1000) ) ) { *_errno = MP_EPIPE; return MP_STREAM_ERROR; } // printk("%s len %d\r\n", __func__, len); mp_hal_delay_us(len * 50); // maybe 50 us time required to send per byte // vTaskDelay(len / portTICK_PERIOD_MS); return len; }
在这里我作了一个假设性的 timing 测试,假设为了修复它与其余芯片同步的工做时序,那么我应该在发送数据后进行一段时间的延时,但这个延时是多久呢?
我如今通讯在用的波特率都是 921600 ,在这个假设可行的状况下,我假定每一个字节发送到对端须要的时间为 100 us 即 mp_hal_delay_us(len * 50);
,以此进行测试。
结论是实测 100 us 恢复正常工做,表示已经修复成功,那么就结束了吗?
还没,咱们应该还要继续肯定,真正预期的延时应该是多少?
接着二分法到 50 us 也成功。
继续二分法到 25 us 也成功。
继续二分法到 10 us 却不成功。// 虚伪二分
那么结论也有个大概了,考虑到 921600 的速率过快,我本来思考要么就 25 us 最优方案。
但后来发现,每次请求的数据并不会应该这个值而有所改善性能,反而可能会存在小几率请求失败,那既然没影响,应该保守设置到 50 us 比较合理。
此时修改的位置很是合理,一方面不破坏 esp32 spi 那端的网卡,另外一方面也不破坏 micropython core 的代码,从而比较优雅的解决问题。
实测 https 的网站都有以下,不过 github.com 的 hostname 常常获取不到,dns 炸裂
wCli = MicroWebCli('https://tcc.taobao.com/cc/json/mobile_tel_segment.htm?tel=13631786501')
wCli = MicroWebCli('https://www.baifubao.com/callback?cmd=1059&callback=phone&phone=13631786501')
wCli = MicroWebCli('https://github.com')
wCli = MicroWebCli('https://ssl.logink.cn/')
wCli = MicroWebCli('https://cn.bing.com/?FORM=Z9FD1')
wCli = MicroWebCli('https://www.sojson.com')
wCli = MicroWebCli('https://www.baidu.com')
另外 HTTPS 的访问速度没有想象的快,从创建连接到收发数据就须要差很少 20s,相比 HTTP 的 10 秒一次请求,这中间有很大的优化空间。
简单判断跟加密解密的软实现函数的效率有关,也可能跟目标网站的响应速度以及对应的 TLS 传输协议有关,能够进一步优化 mbedtls 的配置文件。
须要实测真实用户的 https 的使用环境才能进一步优化。
2020年5月11日 junhuanchen 留