- struct AES_ctx ctx;
- uint8_t KEY[32];
- uint8_t IV[16];
- memcpy(KEY, RT_FOTA_ALGO_AES_KEY, 32);
- memcpy(IV, RT_FOTA_ALGO_AES_IV, 16);
- AES_init_ctx_iv(&ctx, KEY, IV);
4.使用AES解密函数原型是- /**
- * 使用AES_CBC模式解密缓冲区。
- *
- * @param ctx 密钥上下文,包含解密所需的轮密钥和初始化向量。
- * @param buf 待解密的数据缓冲区。
- * @param length 要解密的数据长度,必须是AES块长度的倍数。
- *
- * 注意:这个函数假设输入数据长度是AES块长度的倍数,如果不是,可能会导致未被解密的数据被错误地处理。
- */
- void AES_CBC_decrypt_buffer(struct AES_ctx *ctx, uint8_t *buf, size_t length);
直接调用就可以了,AES_CBC_decrypt_buffer(&ctx, (u8 *)pBuf, 512);
每调用一次后,解密的IV都会变,不能从任意位置解密,必须从头开始,解密才能正确。
如需再次解密需要重新初始化 AES_init_ctx_iv(&ctx, KEY, IV);
三、接收解析文件头
定义一个共同体
- union RBL{
- struct
- {
- char type[4]; /* RBL 字符头 */
- uint16_t fota_algo; /* 算法配置: 表示是否加密或者使用了压缩算法 */
- uint8_t fm_time[6]; /* 原始 bin 文件的时间戳, 6 位时间戳, 使用了 4 字节, 包含年月日信息 */
- char app_part_name[16]; /* app 执行分区名 ,实际是作为固件型号使用*/
- char download_version[24]; /* 固件代码版本号 */
- char current_version[24]; /* 这个域在 rbl 文件生成时都是一样的,我用于表示 app
- 分区当前运行固件的版本号,判断是否固件需要升级 */
- uint32_t code_crc; /* 代码的 CRC32 校验值, 它是的打包后的校验值, 即 rbl 文件 96 字节后的数据 */
- uint32_t hash_val; /* hash校验值 */
原始的RBL文件头的char app_part_name是作为烧写的分区用的,我是用来当作固件型号来使用的。
初始化一个共同体RBL OTA_part,接收使用的是串口空闲中断或者接收超时模式,判断接收的是不是96字节,如果是的,赋值给共同体的数组,memcpy(&OTA_part.data[0], Recve_buffer, 96);获取到文件的大小,CRC等数据信息,先判断固件型号是否和本机一致,如一致则开始接收程序。
四、接收文件并写入备份区
当文件头判断都正确后就开始接收加密后的APP了,由于上位机是按照256字节发送的,直接按页写入就可以了,并累加写入的大小,并计算crc,直到写入的字节等于头文件中的文件大小,- body_crc = rt_fota_step_crc(body_crc, Recve_buffer, len); // 计算CRC
- // 快速解锁闪存以准备写入
- FLASH_Unlock_Fast();
- // 计算写入地址
- uint32_t address = (uint32_t)BACKUP_IMAGE_START_ADD + (file_count * FLASH_PAGE_SIZE);
- if (len >= 256)
- {
- // 写入一页数据
- FLASH_ProgramPage_Fast(address, Recve_buffer); // 写入一页数据
- // 写入完成后锁定闪存
- FLASH_Lock_Fast(); // 快速编程模式上锁
- file_count++; // 文件写入计数
- filesize += len;
- UART_IAP_SendData(0X03); // 发送确认
- }
- else
- {
- // 写入剩余的数据
- // 循环写入剩余的数据
- union flash_data {
- uint8_t buf[256]; /* data */
- uint32_t page[64];
- } flash;
- for (uint32_t i = 0; i < len; i++)
- {
- flash.buf[i] = Recve_buffer[i];
- }
- FLASH_ProgramPage_Fast(address, flash.page); // 写入数据
- // 写入完成后锁定闪存
- FLASH_Lock_Fast(); // 快速编程模式上锁
- filesize += len;
- if ((filesize) != OTA_part.fota_part_head.com_size)
- {
- printf("文件大小不匹配\r\n");
- Flag_UART_IAP = 0;
- UART_IAP_SendData(0X05); // 发送错误确认
- return;
- }
- }
判断CRC,固件版本,文件Hash等校验,检验无误后,重启写入APP区- // 从用户闪存区读取96字节数据到程序文件结构体,再次判断
- IAP_EEPROM_READ(FILE_FLAG_ADD, &OTA_part.data[0], 96);
- printf("文件头 = %s\r\n", OTA_part.data);
- printf("版本号 = %s\r\n", OTA_part.fota_part_head.download_version);
- printf("名称 = %s\r\n", OTA_part.fota_part_head.app_part_name);
- printf("代码大小 = %d\r\n", OTA_part.fota_part_head.raw_size);
- printf("hash_val = %x\r\n", OTA_part.fota_part_head.hash_val);
if (strcmp(OTA_part.fota_part_head.app_part_name, Model_name) == 0)
{
// 设置升级标志
u32 updateFlag = IMAGE_FLAG_UPDATE;
// 擦除存储升级标志的EEPROM区域
IAP_EEPROM_ERASE(UPDATA_FLAG_STORAGE_ADD, FLASH_PAGE_SIZE);
// 写入升级标志到EEPROM
IAP_EEPROM_WRITE(UPDATA_FLAG_STORAGE_ADD, (u8 *)&updateFlag, 4);
// 复位系统
NVIC_SystemReset();
}
五、把备份区文件写入APP区
重启后首先判断升级标志,如果有升级标志,就开始把备份区的数据写入到APP区,首先读取文件头里的大小,根据大小开始循环读取写入,
- // 循环复制数据,这样可以确保即使 com_size 不能被 READ_DATA_LEN 整除,循环次数也会多一次,从而覆盖所有数据。
- for (i = 0; i < ((OTA_part.fota_part_head.com_size + READ_DATA_LEN - 1) / READ_DATA_LEN);
- i++) // i < (BACKUP_IMAGE_MAX_SIZE / READ_DATA_LEN)
- {
- // 计算当前循环对应的闪存地址
- flashAddr = USER_IMAGE_START_ADD + i * READ_DATA_LEN;
- // 从用户闪存区读取数据到缓冲区
- IAP_EEPROM_READ((flashAddr + USER_IMAGE_MAX_SIZE), (u8 *)pBuf, READ_DATA_LEN);
- AES_CBC_decrypt_buffer(&ctx, (u8 *)pBuf, READ_DATA_LEN);
- // 将缓冲区的数据写入备份闪存区
- IAP_EEPROM_WRITE(flashAddr, (u8 *)pBuf, READ_DATA_LEN);
- // 检查写入的数据是否正确
- for (j = 0; j < (READ_DATA_LEN / 4); j++)
- {
- // 如果数据不匹配,返回操作失败状态
- if (pBuf[j] != *(u32 *)(flashAddr + 4 * j))
- return NoREADY;
- }
- }
写入完成后可以再次校验CRC32和HASH,判断是否一致,
- // 初始化flash地址
- flashAddr = USER_IMAGE_START_ADD;
- hash = FNV1A_32_INIT;
- // 循环计算数据,直到达到代码大小
- for (uint32_t i = 0; i < raw_size; i++)
- {
- hash ^= *(u8 *)(flashAddr + i);
- hash *= FNV_PRIME_32;
- }
- printf("hash_val = %x\r\n", hash);
- if (hash != OTA_part.fota_part_head.hash_val)
- {
- printf("hash is wrong!\r\n");
- return NoREADY;
- }
校验一致后就可以清除升级标志并跳转到APP运行了。
- printf("update success!\r\n");
- printf("Run APP1!\r\n");
- EEprom_Write_Byte(UPDATA_FLAG_STORAGE_ADD, 0); // 写入完成
- Delay_Ms(100);
- NVIC_EnableIRQ(Software_IRQn);
- NVIC_SetPendingIRQ(Software_IRQn);