2 Commits
v1.0.0 ... main

Author SHA1 Message Date
Kevin Wong
ee7590a79d 代码优化 2026-01-04 18:03:29 +08:00
Kevin Wong
0886e83365 Update README: 更新说明文件 2025-12-31 16:04:00 +08:00
6 changed files with 260 additions and 315 deletions

438
README.md
View File

@@ -1,346 +1,210 @@
# Avaota F1 开发与集成
# NaviGlass Client 📱
> 面向仓库根目录的总览文档,整合 Day1Day9 日志、任务清单与完成总结。
<div align="center">
**面向视障人士的智能导航与辅助系统 - 客户端**
基于 Avaota F1 (全志 V821 RISC-V) 的嵌入式终端
[功能特性](#功能特性) • [硬件配置](#硬件配置) • [编译部署](#编译部署) • [程序架构](#程序架构)
</div>
---
## 1. 项目简介
## ✨ 功能特性
本项目面向 **Avaota F1 AI 机器人**,基于 **全志 V821 / 32-bit RISCV** SoC目标是在 Tina Linux 固件上实现一套「可量产」的机器人终端固件,集成:
- 音频采集(板载模拟麦克风)与播放(I2S + MAX98357A
- IMU 六轴姿态传感(ICM42688P
- GC2083 MIPI 摄像头 JPEG 图像采集
- UDP / HTTP / WebSocket 网络通讯
- 多线程主程序与静态链接交叉编译流程
文档与代码组织按「Day1Day9 开发日志 + 任务清单 + 完成总结」推进,可作为以后移植到其他 Tina Linux / 全志平台的模板工程。
目前对话的位置是本地Windows 11系统的主机用于开发的环境是局域网中的Ubuntu服务器。
- **摄像头采集**: GC2083 MIPI (1280×720 @20fps, JPEG 硬件编码)
- **音频采集**: 板载模拟麦克风 (16kHz/mono)
- **音频播放**: I2S + MAX98357A 功放
- **IMU 传感**: ICM-42688-P 六轴 (GPIO 模拟 SPI)
- **网络通信**: WebSocket (音视频) + UDP (IMU)
- **自动启动**: 开机自动连接服务器
---
## 2. 硬件与开发环境
## 🔧 硬件配置
### 2.1 目标硬件(板端)
### 核心硬件
- **SoC**:全志 V82132bit RISCV 架构
- **板载外设**
- 模拟麦克风 → 内置 Audio Codecaudiocodec
- I2S 数字功放MAX98357ABCLK/LRCK/DOUTPD12/PD13/PD15
- 摄像头GC2083MIPICSI2典型输出 1280×720 @ 20fps
- IMUICM42688PGPIO 模拟 SPI
- **操作系统**Tina Linux基于 OpenWrt 的 Allwinner SDK
| 组件 | 型号 | 接口 |
|------|------|------|
| SoC | 全志 V821 | 32-bit RISC-V |
| 摄像头 | GC2083 | MIPI-CSI2 |
| 功放 | MAX98357A | I2S (PD12/PD13/PD15) |
| 麦克风 | 板载模拟 | Audio Codec |
| IMU | ICM-42688-P | GPIO SPI |
### 2.2 主机开发环境PC
### 引脚配置
- **系统**Ubuntu 24.04 LTS同时也有 Windows + WSL 的混合开发)
- **工具链 & SDK**
- Tina SDK`tina-v821-release`
- `/home/rongye/ProgramFiles/AvaotaF1/avaota_sdk/tina-v821-release` 这是SDK位置
- 交叉编译工具链:
- **当前使用**`prebuilt/rootfsbuilt/riscv/nds32le-linux-musl-v5d`**musl 工具链(与开发板兼容)**
- 编译器前缀:`riscv32-linux-musl-`
- ~~已废弃~~`out/toolchain/nds32le-linux-glibc-v5d` (glibc 工具链,与板端 musl libc 1.2.4 不兼容)
- `/home/rongye/ProgramFiles/AvaotaF1/avaota_app_demo` 这是交叉编译的位置
- Python通过 `python-is-python3` 或为遗留脚本装 python2 并软链到 `python`
### 2.3 开发主机准备要点Ubuntu 24.04
- 开启 i386 架构以兼容旧版 32 位库
- 手动安装被移除的 `libncurses5` / `libtinfo5`
- 安装 bison / flex / 交叉编译依赖,修复 `lunch` / `make` 时报错
- 注意 `make -j` 一定要限制并行度(如 `-j8`),避免 OOM
| 功能 | 引脚 | 说明 |
|------|------|------|
| I2S BCLK | PD12 | 音频位时钟 |
| I2S LRCK | PD13 | 左右声道时钟 |
| I2S DOUT | PD15 | 音频数据输出 |
| SPI SCLK | PD3 | IMU 时钟 |
| SPI MOSI | PD2 | IMU 数据输入 |
| SPI MISO | PD4 | IMU 数据输出 |
| SPI CS | PD5 | IMU 片选 |
---
## 3. 仓库结构建议
## 💻 开发环境
实际仓库结构可按以下思路组织(示例):
### 主机环境
```text
.
├── ../Docs/DevLogs/ # (位于上级目录) 每日开发日志
│ ├── Day1.md … Day18.md
├── docs/ # 本地硬件/SDK文档
│ ├── 1.png … 2.png
│ ├── AvaotaF1.md
│ ├── 引脚.md
│ ├── TinaSDK-Docs/
│ ├── tina_files_clean.csv
├── ESP32S3/ # 可参考的ESP32S3固件
├── src/
│ ├── audio/ # AudioCapture / AudioPlayer
│ ├── camera/ # Camera 类 / MPP 封装
│ ├── imu/ # ICM42688 SPI 驱动与测试
│ ├── network/ # UDP / HTTP / WebSocket 客户端
│ ├── utils/
│ ├── main.cpp # 主程序入口(多线程集成)
│ ├── main_test.cpp # 本地硬件自检程序
│ ├── Makefile # 交叉编译配置
│ └── build_*.sh # 构建脚本main/test/phaseX
├── build_main.sh
├── logs.md # 编译时的日志(实时填写)
└── README.md # 本文档
- **系统**: Ubuntu 24.04 LTS
- **SDK**: Tina SDK (tina-v821-release)
- **工具链**: musl (`riscv32-linux-musl-`)
### SDK 路径
```
/home/rongye/ProgramFiles/AvaotaF1/avaota_sdk/tina-v821-release
├── prebuilt/rootfsbuilt/riscv/nds32le-linux-musl-v5d/ ← 工具链
└── ...
```
你可以直接把 Day1Day7、任务清单与完成总结放到 `../docs/DevLogs/` 目录,并用当前 README 作为入口索引。
> ⚠️ 必须使用 **musl 工具链**glibc 工具链与开发板不兼容
---
## 4. 功能模块概览
## 🚀 编译部署
### 4.1 音频系统
**输入:板载模拟麦克风**
- 通过 SoC 内部 **Audio Codec (audiocodec)** 采集
- ALSA 设备:`hw:audiocodec``hw:0,0`(录音推荐 `plughw:0,0`
- 运行时配置:
- `MIC Switch` 开启
- `MIC Gain` 建议值 25在音量与底噪之间平衡
- `adc-vol``lineout-gain` 使用 DTS 默认值或适度调整
- 采样参数:
- 16 kHz, S16_LE, Mono
- 典型链路:
- `setup_mic.sh``arecord``/tmp/test_mic.wav``aplay` / 上行网络
**输出I2S + MAX98357A**
- I2S0 接 MAX98357A扬声器输出
- Device Tree 为每个引脚创建独立 pinctrl 节点:
- `i2s0_bclk_pin`PD12
- `i2s0_lrck_pin`PD13
- `i2s0_dout0_pin`PD15
- I2S 平台设备 `&i2s0_plat` 绑定这些引脚,`status = "okay"`
- 通过 ALSA `aplay` / 自己的 `AudioPlayer` 类进行播放
### 4.2 IMU 传感器ICM42688P
- 最终采用 **GPIO 模拟 SPI**
- SCLK: PD3
- MOSI: PD2
- MISO: PD4
- CS : PD5
- SPI Mode0软件 bitbang速率约 500 kHz可按需提升
- 主要特性:
- WHO_AM_I = 0x47 识别验证
- 加速度计±16g1 kHz ODR
- 陀螺仪±2000 °/s1 kHz ODR
- 温度通道:用于环境监控与漂移补偿
- 静止状态验证:
- 合加速度 ≈ 9.8 m/s²
- 陀螺仪接近 0 °/s
- 上层封装:
- `ICM42688` 类提供采样与单位转换
- 独立 `test_imu` 自检程序
- 在主程序中通过 UDP 周期性上报 JSON/结构体数据
### 4.3 摄像头系统GC2083 + MPP
- 使用 Allwinner EyeseeMPP 框架SYS → VI → ISP → VENC
- 流水线:
- VI接 MIPI 摄像头)
- ISP自动曝光、白平衡、降噪
- VENCJPEG 编码)
- 关键配置:
- 分辨率1280×720 @ 20fps可调
- JPEG 质量80在质量与码率之间折中
- VI Buffer5 帧
- VBV Buffer4 MB避免 `VBV FULL` 错误
- `Camera` 类职责:
- 完成 MPP 初始化 / 绑定 / 销毁
- 按需抓拍单帧 JPEG用于 WebSocket 发送或本地保存)
- 提供阻塞式 `capture_frame()` 接口与重试机制
- 测试程序:
- `test_camera` 抓拍多张 JPEG 保存到 SD 卡
- 实测成功率 100%,文件大小 2080 KB 之间,画面曝光正常
### 4.4 网络通讯
**UDP**
- 轻量 IMU 数据上报通道
- 使用 POSIX socket静态链接无额外依赖
- 典型用法:
- `UDPSender` 初始化目标 IP/Port
- 周期性发送 IMU/状态数据
**HTTPlibcurl 或轻量封装)**
- 主要用途:
- 从服务器拉取 TTS 音频流(如 `stream.wav`
- 拉取配置文件或诊断信息
- 流式下载接口已预留,可边下边写入 `AudioPlayer` 播放
**WebSocket**
- 早期方案SDK 内置 `libuwsc`(后期为规避依赖,可用自实现轻量 WS 客户端)
- 主要用途:
- 上行:摄像头 JPEG 帧、音频 PCM 片段
- 下行:控制指令 / 状态同步
- 接口设计:
- `WSClient` 负责 TCP 连接、握手、帧收发、心跳与重连
- 主线程中,每个子模块持有各自的 WSClient 实例或共享连接
---
## 5. 编译与构建流程
### 5.1 Tina SDK 环境准备(概要)
1. 解压 SDK
```bash
mkdir -p ~/ProgramFiles/avaota_sdk
tar -xvf tina-v821-*.tar.xz -C ~/ProgramFiles/avaota_sdk
```
2. 初始化环境:
```bash
cd ~/ProgramFiles/avaota_sdk/tina-v821-release
source build/envsetup.sh
lunch # 选择 avaota_f1 / v821 相关配置
```
3. 全量编译:
```bash
make -j8
pack # 需要时打包固件
```
### 5.2 应用程序交叉编译
在 `src/` 目录中提供一个或多个构建脚本,例如:
### 1. 环境初始化
```bash
#!/bin/bash
set -e
SDK_ROOT=~/ProgramFiles/AvaotaF1/avaota_sdk/tina-v821-release
# ⚠️ 使用 musl 工具链(与开发板兼容)
TOOLCHAIN=${SDK_ROOT}/prebuilt/rootfsbuilt/riscv/nds32le-linux-musl-v5d/bin
export PATH=${TOOLCHAIN}:$PATH
make clean
make all -j4 # 或按目标拆分make main / make test_audio / make test_imu
cd ~/ProgramFiles/AvaotaF1/avaota_sdk/tina-v821-release
source build/envsetup.sh
lunch # 选择 avaota_f1
```
Makefile 要点:
### 2. 交叉编译
- 使用 `riscv32-linux-musl-g++` 编译器musl 工具链)
- 动态链接器:`/lib32/ld.so.1`(板端需创建符号链接:`ln -s /lib32/ilp32d/libc.so /lib32/ld.so.1`
- 链接静态或最小依赖的动态库
- 注意链接顺序:业务库 → MPP/ISP/cedarx → `-lpthread -lrt -lm -ldl -lstdc++`
```bash
cd ~/ProgramFiles/AvaotaF1/avaota_app_demo
./build_main.sh
```
### 5.3 部署与运行
### 3. 部署到开发板
1. 将交叉编译好的可执行文件拷贝到 SD 卡:
```bash
cp avaota_client /media/$USER/SDCARD/
```
2. 板端挂载 SD 卡并复制到 `/tmp`
```bash
mount /dev/mmcblk0p1 /mnt/extsd
cp /mnt/extsd/avaota_client /tmp/
chmod +x /tmp/avaota_client
```
3. 运行:
```bash
/tmp/avaota_client
```
```bash
# 复制到 SD 卡
cp avaota_client /media/$USER/SDCARD/
> 推荐所有测试程序(`test_audio` / `test_imu` / `test_camera` / `avaota_test`)都统一走上述流程,避免执行权限与只读分区问题。
# 板端执行
mount /dev/mmcblk0p1 /mnt/extsd
cp /mnt/extsd/avaota_client /mnt/UDISK/
chmod +x /mnt/UDISK/avaota_client
/mnt/UDISK/avaota_client
```
### 4. 自动启动配置
程序已配置开机自启动:
- 位置: `/mnt/UDISK/avaota_client`
- 方式: `/etc/init.d/avaota` + `load_script.conf`
- 延迟: 15 秒 (等待音频系统就绪)
---
## 6. 主程序(集成)架构示意
主程序建议使用多线程模型,将各个模块解耦:
## 🏗️ 程序架构
```cpp
// 伪代码示例
int main() {
init_logging();
init_network_config(); // 服务器 IP / 端口 / 路径等
init_signal_handler(); // Ctrl+C 安全退出
std::thread cam_thread(run_camera_loop);
std::thread mic_thread(run_audio_capture_loop);
std::thread spk_thread(run_audio_play_loop);
std::thread imu_thread(run_imu_udp_loop);
// 可选:主线程处理下行指令 / 状态机
run_main_control_loop();
// 等待退出
init_signal_handler(); // Ctrl+C 安全退出
std::thread cam_thread(run_camera_loop); // 摄像头 → WebSocket
std::thread mic_thread(run_audio_capture); // 麦克风 → WebSocket
std::thread spk_thread(run_audio_play); // WebSocket → 扬声器
std::thread imu_thread(run_imu_udp); // IMU → UDP
cam_thread.join();
mic_thread.join();
spk_thread.join();
imu_thread.join();
return 0;
}
```
每个线程内部:
### 核心模块
- 初始化各自的硬件和网络客户端
- 进入循环:
- 采集 → 编码(可选) → 通过 UDP/WS 发送
- 或从 HTTP/WS 接收 → 解码(可选) → 播放/控制硬件
- 捕获异常并尝试重连 / 恢复,必要时上报主线程
| 目录 | 模块 | 功能 |
|------|------|------|
| `src/audio/` | AudioCapture | 麦克风采集 (ALSA) |
| `src/audio/` | AudioPlayer | 扬声器播放 (I2S) |
| `src/camera/` | Camera | MPP 框架封装 |
| `src/imu/` | ICM42688 | GPIO SPI 驱动 |
| `src/network/` | WSClient | WebSocket 客户端 |
| `src/network/` | UDPSender | UDP 发送 |
---
## 7. 日志与文档索引
## 📡 通信协议
可以在 README 中给出所有开发日志与计划文档的入口,方便回溯:
### WebSocket 连接
- `../Docs/DevLogs/Day1.md`SDK 编译与 32 位环境踩坑
- `../Docs/DevLogs/Day2.md`UDP 通信、网络库编译、libuwsc / libcurl 准备
- `../Docs/DevLogs/Day3.md`:音频采集 & 播放模块ALSA + I2S
- `../Docs/DevLogs/Day4.md`:模拟麦克风配置 + IMU SPI 驱动与验证
- `../Docs/DevLogs/Day5.md`GC2083 摄像头 MPP 集成与 JPEG 捕获
- `../Docs/DevLogs/Day6.md`:硬件全功能验证、本地测试程序、网络库诊断
- `../Docs/DevLogs/Day7.md`:交叉编译 Makefile 收敛、工具链配置
- `../Docs/DevLogs/Day8.md`整体编译成功、Cedar 库链接完成
- `../Docs/DevLogs/Day9.md`:⭐ **musl 工具链修复 + 板上测试通过**
- `../Docs/task_complete.md`完整任务清单与进度条97% 完成)
- `../Docs/implementation_plan_complete.md`:实现计划 & 各阶段目标拆解
| 端点 | 用途 | 数据格式 |
|------|------|---------|
| `/ws/camera` | 摄像头上传 | Binary (JPEG) |
| `/ws_audio` | 音频双向 | Binary (PCM16 16kHz) |
### UDP 连接
| 端口 | 用途 | 数据格式 |
|------|------|---------|
| 12345 | IMU 数据 | JSON |
### 服务器地址
```cpp
// main.cpp
#define SERVER_IP "8.148.25.142" // 公网 IP (frp 穿透)
#define SERVER_PORT 8081
```
---
## 8. 任务完成度与后续工作
## 📁 目录结构
### 8.1 当前完成度(参考任务清单与总结)
- SDK/工具链/固件:✅
- musl 工具链修复:✅ **Day 9 关键突破)**
- 音频采集 & 播放:✅
- IMU 采集:✅ **(板上测试通过)**
- 摄像头采集:✅ **(板上测试通过)**
- UDP / HTTP / WebSocket 基础:✅(库与客户端实现已完成)
- WiFi 网络配置:✅ **192.168.110.132**
- 板端硬件测试:✅ **(所有模块通过)**
- 网络服务器通信测试:⏳(服务器已部署,待测试)
- 性能评估与稳定性验证:⏳(待完成)
整体项目 **97%** 完成,核心功能全部验证通过!🎉
### 8.2 建议的后续 TODO
- 将服务器 IP / 端口、音频参数、帧率等抽象为配置文件JSON / INI
- 丰富错误日志并加上日志轮转
- 对 WebSocket / HTTP 加入重连与指数退避策略
- 若后续量产,考虑:
- 用硬件 SPI 替换 GPIO bitbang
- 对功耗与休眠策略进行优化
- 引入简单看门狗机制,防止长期运行卡死
```
NaviGlassClient/
├── avaota_app_demo/
│ ├── src/
│ │ ├── audio/ # 音频模块
│ │ ├── camera/ # 摄像头模块
│ │ ├── imu/ # IMU 模块
│ │ ├── network/ # 网络模块
│ │ ├── main.cpp # 主程序
│ │ └── Makefile
│ └── build_main.sh
├── Device_Tree/ # DTS 配置
├── docs/ # 硬件文档
└── README.md
```
---
## 9. 如何使用本 README
## 📊 性能参数
- **新成员上手**:从本 README 入手,结合 `../Docs/DevLogs/DayX.md` 逐步了解每个子系统的设计与坑点。
- **以后复用到新项目**:可以直接复制「编译流程」「外设接线 + DTS 配置」「主程序多线程架构」这几部分,稍作修改即可移植到其他全志 / RISCV 设备。
- **代码导航入口**:按模块查找 `src/audio`, `src/imu`, `src/camera`, `src/network` 目录,结合对应的 DayX 日志阅读。
| 指标 | 数值 |
|------|------|
| 视频帧率 | 10 FPS (上传) |
| 音频采样 | 16kHz / 16bit / Mono |
| IMU 采样 | 10 Hz |
| 启动时间 | ~15 秒 (音频系统就绪) |
| 功耗 | 待测 |
祝使用愉快 🎉,也欢迎在后续开发阶段继续补充和更新本 README。
---
## 🔗 相关文档
- [服务器代码](../NaviGlassServer/) - Python 后端
- [开发日志](../Docs/DevLogs/) - Day 1 ~ Day 25
- [任务清单](../Docs/task_complete.md)
- [Avaota F1 文档](./docs/)
---
## 📄 许可证
MIT License

View File

@@ -46,8 +46,8 @@ check_lib() {
}
all_libs_ok=true
check_lib "libssl.so" || all_libs_ok=false
check_lib "libcrypto.so" || all_libs_ok=false
# check_lib "libssl.so" || all_libs_ok=false (Removed in optimization)
# check_lib "libcrypto.so" || all_libs_ok=false (Removed in optimization)
check_lib "libcurl.so" || all_libs_ok=false
check_lib "libasound.so" || all_libs_ok=false

View File

@@ -132,8 +132,8 @@ LDFLAGS += -lcedarxrender -lhwdisplay
# ALSA 音频库(静态库不存在,且音频播放已禁用)
# LDFLAGS += -lasound
# OpenSSL (用于 WebSocket 握手)
LDFLAGS += -lssl -lcrypto
# OpenSSL (Removed in Day 26 optimization)
# LDFLAGS += -lssl -lcrypto
# 结束静态库组
LDFLAGS += -Wl,--end-group

View File

@@ -52,7 +52,11 @@ Camera::Camera()
, m_fps(0.0f)
, m_frame_count(0)
, m_last_time(0)
, m_current_buf_idx(0)
{
// Initialize buffer pointers to nullptr
m_frame_buffers[0] = nullptr;
m_frame_buffers[1] = nullptr;
LOGD("Camera constructor");
}
@@ -102,6 +106,14 @@ void Camera::deinit()
// 退出系统
AW_MPI_SYS_Exit();
// Day 26: Free memory pool
for (int i = 0; i < 2; i++) {
if (m_frame_buffers[i]) {
delete[] m_frame_buffers[i];
m_frame_buffers[i] = nullptr;
}
}
// 重置统计
m_fps = 0.0f;
m_frame_count = 0;
@@ -113,6 +125,18 @@ void Camera::deinit()
bool Camera::init()
{
LOGI("Initializing camera: %dx%d @%dfps", m_width, m_height, DEFAULT_FPS);
// Day 26: Pre-allocate memory pool
for (int i = 0; i < 2; i++) {
if (!m_frame_buffers[i]) {
m_frame_buffers[i] = new uint8_t[MAX_FRAME_SIZE];
if (!m_frame_buffers[i]) {
LOGE("Failed to allocate frame buffer %d", i);
return false;
}
LOGD("Allocated frame buffer %d (%zu bytes)", i, MAX_FRAME_SIZE);
}
}
// 1. 初始化 MPP 系统
MPP_SYS_CONF_S sys_conf;
@@ -332,16 +356,25 @@ bool Camera::capture_frame(uint8_t** jpeg_data, size_t* jpeg_size)
return false;
}
// 计算总大小
// Day 26: Use Memory Pool instead of dynamic allocation
size_t total_size = stream.mpPack[0].mLen0 + stream.mpPack[0].mLen1 + stream.mpPack[0].mLen2;
if (total_size == 0) {
LOGE("Empty JPEG frame");
AW_MPI_VENC_ReleaseStream(m_venc_chn, &stream);
return false;
}
// Safety check for buffer overflow
if (total_size > MAX_FRAME_SIZE) {
LOGE("Frame size (%zu) exceeds MAX_FRAME_SIZE (%zu), dropping frame", total_size, MAX_FRAME_SIZE);
AW_MPI_VENC_ReleaseStream(m_venc_chn, &stream);
return false;
}
// 分配内存并拷贝数据
uint8_t* buffer = new uint8_t[total_size];
// Get current buffer
uint8_t* buffer = m_frame_buffers[m_current_buf_idx];
// Copy data
size_t offset = 0;
if (stream.mpPack[0].mLen0 > 0 && stream.mpPack[0].mpAddr0) {
@@ -360,6 +393,9 @@ bool Camera::capture_frame(uint8_t** jpeg_data, size_t* jpeg_size)
// 释放流
AW_MPI_VENC_ReleaseStream(m_venc_chn, &stream);
// Switch buffer index for next frame
m_current_buf_idx = (m_current_buf_idx + 1) % 2;
// 更新 FPS 统计
update_fps_stats();
@@ -373,9 +409,8 @@ bool Camera::capture_frame(uint8_t** jpeg_data, size_t* jpeg_size)
void Camera::release_frame(uint8_t* jpeg_data)
{
if (jpeg_data) {
delete[] jpeg_data;
}
// No-op for memory pool
// Buffer will be overwritten when it's turn comes again
}
bool Camera::set_framesize(const std::string& size)

View File

@@ -95,6 +95,11 @@ private:
bool init_venc(); // Video Encoder
void update_fps_stats();
// Day 26 Optimization: Fixed Memory Pool (Double Buffering)
static const size_t MAX_FRAME_SIZE = 512 * 1024; // 512KB (Sufficient for 720p MJPEG)
uint8_t* m_frame_buffers[2];
int m_current_buf_idx;
};
#endif // CAMERA_H

View File

@@ -12,10 +12,61 @@
#include <arpa/inet.h>
#include <netdb.h>
#include <fcntl.h>
#include <openssl/sha.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/buffer.h>
#include <vector>
#include <string>
// Day 26: Remove OpenSSL dependency
// #include <openssl/sha.h>
// #include <openssl/bio.h>
// #include <openssl/evp.h>
// #include <openssl/buffer.h>
// Custom Base64 Encoding Table
static const char base64_chars[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
// Custom helper: base64 encode
static std::string base64_encode(const unsigned char* bytes_to_encode, unsigned int in_len) {
std::string ret;
int i = 0;
int j = 0;
unsigned char char_array_3[3];
unsigned char char_array_4[4];
while (in_len--) {
char_array_3[i++] = *(bytes_to_encode++);
if (i == 3) {
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for(i = 0; (i <4) ; i++)
ret += base64_chars[char_array_4[i]];
i = 0;
}
}
if (i) {
for(j = i; j < 3; j++)
char_array_3[j] = '\0';
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for (j = 0; (j < i + 1); j++)
ret += base64_chars[char_array_4[j]];
while((i++ < 3))
ret += '=';
}
return ret;
}
// WebSocket 魔术字符串(用于握手验证,当前未使用)
// static const char* WS_MAGIC_STRING = "258EAFAA-5914-47DA-95CA-C5AB0DC85B11";
@@ -178,21 +229,11 @@ bool WSClient::perform_handshake() {
random_bytes[i] = rand() % 256;
}
// Base64 编码
BIO *bio, *b64;
BUF_MEM *bufferPtr;
// Day 26: Use custom Base64 implementation instead of OpenSSL
std::string ws_key = base64_encode(random_bytes, 16);
b64 = BIO_new(BIO_f_base64());
bio = BIO_new(BIO_s_mem());
bio = BIO_push(b64, bio);
BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL);
BIO_write(bio, random_bytes, 16);
BIO_flush(bio);
BIO_get_mem_ptr(bio, &bufferPtr);
std::string ws_key(bufferPtr->data, bufferPtr->length);
BIO_free_all(bio);
// Debug log key (optional)
// LOG_DEBUG("[WS] Generated Sec-WebSocket-Key: %s", ws_key.c_str());
// 构造 HTTP 升级请求
char request[1024];