0. 概述
项目目标:通过 libUSB 学习,动手制作你的身份认证专用U盘;
人生突破:摘掉 “只会吃软饭” 帽子,编程路上从此硬一回;
包含视频:5 段;
动手项目:两个可执行程序
-(1)USBScanner (谁?谁?谁?谁在插我)
-(2)USBIdentifying (制作“红花会”身份证U盘)
核心知识:
- USB (通用串行总线)基本概念;
- 银行 U 盾基本原理;
- libUSB 核心功能编程;
1. 简介、安装、快速使用
迅速了解 USB 基本概念,libUSB 的作用与使命(定位),十数行代码,列出当前插在你的电脑上的 USB 设备……
上机 :USBScanner 简陋版 → USBScanner 标准版本。
-
视频 1 :USBScanner 标准版开发
-
重点A:设备(及接口)类型
2. USB 设备列表 Ultra 版本
深入了解 USB 编程的逻辑层次:设备 → 配置 → 接口 → 接口描述 → 端点。以上层次组成如下图所示的 “USB 设备信息树”。
-
重点B:USB逻辑层次
-
视频 2 :USBScanner Ultra 版开发
-
重点C:设备传输分类
- 控制传输 (用于系统控制设备,所有USB设备必选实现)
- 等时传输(音频、音频等注重时间同步的流式传输,宁错勿迟)
- 批量传输(大容量存储数据的读与写,宁迟勿错)
- 中断传输(键盘、鼠标等中断式输入数据传输)
3. 裸读 U 盘容量-准备
最关键的准备工作:将 U 盘的驱动降级。
libUSB无法对一块正在接受 Windows (或其它操作系统)高级驱动管理的 USB 设备进行裸读裸写——那样太不安全了。视上层OS之不同,通常需要手工或编程自动(结合管理员权限)将指定USB设备与系统驱动“剥离”后,才能实现。
- 视频3: 裸读 U 盘容量准备工作
- 重点D:libUSB 库定位
以下是 libUSB 在 USB 设备开发分层中,所处位置。
- 重点E:libUSB 批量传输三板斧
① 发送命令与参数 → ② 传输数据(接收IN/或输出/OUT)→ ③ 询问状态
注意点:只要第 ① 步完成,则程序必须匹配完成第 ③ 步。
4. 裸读 U 盘容量-上机
- 视频4:裸读U盘容量-上机
5. 制作身份认证 U 盘
- 视频5:制作身份认证U盘
6. 附:关联知识
6.1 BCD 编码
BCD(Binary-Coded Decimal,二进制编码的十进制)是一种将十进制数字(0-9)转换为二进制形式的编码方式,其核心思想是用二进制数来表示每个十进制数位,而非直接将整个十进制数转换为纯二进制。这种编码在需要精确表示十进制数值的场景(如金融、会计、显示设备等)中广泛应用,避免了纯二进制转换时可能出现的精度误差。
十进制与BCD编程对应表(以 四位/bit 为 BCD码长):
十进制数 | BCD | 十进制数 | BCD |
---|---|---|---|
0 | 0000 | 1 | 0001 |
2 | 0010 | 3 | 0011 |
4 | 0100 | 5 | 0101 |
6 | 0110 | 7 | 0111 |
8 | 1000 | 9 | 1001 |
基于此,假设有一个十进制数 9230, 则可表达为二进制数(空格仅为方便阅读):
1001 0010 0011 0000
libUSB 正是使用如上表所示的 4 位(bit)码长的BCD编码,共使用4组,合计16位(uint16_t,两个字节)来表达 USB 和设备产品的版本号。由于一个字节(byte)包含8位,也就是一个字节中两个 BCD 码,这两个BCD码,谁在高位,谁在低位?比如:0010 0001 是在表达 21 还是在表达 12 ?在 libUSB 的版本号表达中,约定如下:
注:图中XXXX 均为16位进制表达,且“字”指字节(8个2进制位),“位”指半个字节(4个2进制位)。
6.2 大小端序
问一个问题,在 C/C 程序中,设有数据 char* s="123456"
,请问:
- 在计算机存储中,逻辑上, ‘1’ 所在内存位置(编号)大,还是 ‘6’ 所在内存位置(编号)大?
- 如果使用网络程序按字节发送该字符串,请问 ‘1’ 先发,还是 ‘6’ 先发出去?
- 以上答案和该程序所在主机的大小端序是否有相关性?
问题 1 答案:‘6’ 所在内存的编号大,因为 : s[0] 是’1’的位置,而s[6] 是’6’的位置,显然,6 比 0 大。
问题 2 答案:‘1’ 先发出去。
问题 3 答案:无关。
如果将数据从 C/C++ 字符串(char *)改为一个整数,事情就开始复杂了。比如数据 int32_t i=0x123456
,请问,此时是 1 在内存高位,还是 6 在内存高位?
首先,由于 i 的类型是 int32_t,因此它实际有 32 位 4 个字节,用16进制表达,它应该是: 0x00123456
。
然后,由于计算机存储的最小单位是1字节,因此,0x00123456
实际会被拆分为以下字节:0x00、0x12、0x34、0x56。因此,我们实际要考察的是:
- 0x00 和 0x56 这两个字节谁在内存中的逻辑高地址,谁在内存中的逻辑低地址?
- 网络发送时,0x00先被发送,还是 0x56 先被发送?
二者都没有标准答案,得看机型(具体实现),得看协议(遵循特定约定)。有两种常见的实现或约定:大端序(Big-Endian)和小端序(Little-Endian)。
首先,不管是大端还是小端,都认同:在程序的表达上,先保存的数据,应位于逻辑低地址上,后保存的数据,应位于逻辑高地址上。这正是字符串 "123456"
的保存和大小端主机无关的基础:对于C/C++字符系列,在逻辑上总是视’1’存储在’2’之前, ‘2’ 存储在’3’之前……
其次,在高低位表达上,二者也都采用人类原有理解来区分,即:不管什么进制的数,比如十进制 123, 1是高位(权值最大,百位),3 是低位(权值最小,个位)。
区别开始出现了:
大端序(Big-Endian):既然人类喜欢把高位的数放在前面,比如:一百二十三会被写成 123 而不是 321,所以,我们兼容这种写法,在计算机,也将高位上的数,写在内存地址靠前的位置上。结论:整数中的最高位,写在内存逻辑地址较小的位置上。
小端序(Little-Endian):我们是程序,干嘛要采用人类的生活习惯?再说,人类也有把“小”的东西写在前面的案例啊,比如英国人信封上地址,就是先有街道号,最后是国家,另外他们表达日期好像也是日月年……
大端序插话:但是伟大的中国人民就没这么乱,数字、时间、地址,都是从大到小……)
小端序:你别插话,反正,在这里照顾人类的习惯毫无作用,应该保持一致:位高的数据就在内存的高位地址,位低的数据就在内存的低位的地址……这多好记忆啊……结论:整数中的最高位,会被写在内存逻辑地址较大的位置上。
当我们使用 libusb_bulk_transfer()
函数,发送 READ(10)
或 WRITE(10)
指令时,此时,需要读或写的位置(lba-index / 块编号) ,就是以大端序的方式,被转换为字符系列(无符号字符系列)中去的:
// LBA地址 (大端序)
cbw.CBWCB[2] = (lba_index >> 24) & 0xFF;
cbw.CBWCB[3] = (lba_index >> 16) & 0xFF;
cbw.CBWCB[4] = (lba_index >> 8) & 0xFF;
cbw.CBWCB[5] = lba_index & 0xFF;
即:比较小的内存位置(比如下标 2 的元素),存放了 lba_index 这个整数中比较高(靠左)的字节,(注意 赋值左边的 2,3,4,5 和赋值右边的 右移 24、16、8、0 的关系)。
6.3 USB 端点错误处理
当 USB 设备遇到无法处理的请求时,负责执行该请求的端点,会向主机发送 “STALL” 握手包,表示“设备无法处理该请求,请停止操作”,在 libusb中,通常对应的错误编码为 LIBUSB_ERROR_PIPE 。
一旦端点进入 STALL 状态,后续对该端点的操作,都会失败——除非主机显式的清除该状态。因此,程序在检测到此类问题发生时,应及时在相应的端点上,调用 libusb_clear_halt()
函数,清除此状态。
stall 和 halt 在此都是英文单词本意。
另,当出现更为严重的错误时,可以考虑调用 libusb_reset_device()
对设备进行完全复位。
7. 源码
CMakeLists.txt 最终版本内容:
cmake_minimum_required(VERSION 3.10)
project(HelloUSB)
set(CMAKE_CXX_STANDARD 17) # 使用 C++17
# 以下三行对所有构建目标生效
link_libraries(usb-1.0) # 添加 libusb-1.0 库
link_directories(C:/msys64/ucrt64/lib) # 指定 库 所在目录
add_link_options(-static) # 使用GCC静态链接
# 第一个构建目标:
add_executable(USBScanner main.cpp)
# 第二个构建目标:
add_executable(USBIdentifying main2.cpp)
7.1 USBScanner (终版)
main.cpp
#include <cassert>
#include <cstdlib>
#include <iostream>
#include <iomanip>
#include <memory>
#include <map>
#include <libusb-1.0/libusb.h>
void handle_error(int r, char const* msg)
{
std::cerr << msg << " - " << libusb_error_name(r) << "\n";
}
std::string tab(int depth) // 产生指定个数的缩进符
{
return std::string(depth, '\t');
}
// BCD版本号转字符串
std::string bcd_to_version_string(uint16_t bcd)
{
// 0x XXXX
uint8_t major = (bcd >> 8) & 0xFF; // 主版本号,在第三个字节
uint8_t minor = (bcd >> 4) & 0x0F; // 次版本号,在第二个字节
uint8_t patch = bcd & 0x0F; // 补丁号
std::ostringstream oss;
oss << static_cast<int>(major) << "."
<< static_cast<int>(minor);
if (patch != 0)
{
oss << "." << static_cast<int>(patch);
}
return oss.str().c_str();
}
// USB 设备分类的枚举值,转成中文说明
std::string const& class_code_to_string(uint8_t code)
{
static std::string const unknown_class = "未知类型";
static std::map<uint8_t, std::string> const m =
{
{LIBUSB_CLASS_PER_INTERFACE, "接口层面定义"},
{LIBUSB_CLASS_AUDIO, "音频设备(扬声器、麦克风、MIDI输入等)"},
{LIBUSB_CLASS_COMM, "通信设备(调制解调器,网络适配器等)"},
{LIBUSB_CLASS_HID, "人机接口设备(键盘、鼠标、手柄等)"},
{LIBUSB_CLASS_PHYSICAL, "物理设备(力反馈、运动传感器等)"},
{LIBUSB_CLASS_IMAGE, "图像设备(数码相机、扫描仪等)"},
{LIBUSB_CLASS_PRINTER, "打印机"},
{LIBUSB_CLASS_MASS_STORAGE, "大容量存储类(U盘、移动硬盘、SD读卡器等)"},
{LIBUSB_CLASS_HUB, "集线器"},
{LIBUSB_CLASS_DATA, "数据类(通信设备的配套接口)"},
{LIBUSB_CLASS_SMART_CARD, "智能卡(安全令牌、读卡器等)"},
{LIBUSB_CLASS_CONTENT_SECURITY, "数据版权管理设备(内容安全)"},
{LIBUSB_CLASS_VIDEO, "视频设备(摄像头、视频采集卡等)"},
{LIBUSB_CLASS_PERSONAL_HEALTHCARE, "个人健康设备(心率监测器、血糖仪等)"},
{LIBUSB_CLASS_DIAGNOSTIC_DEVICE, "诊断设备(USB调试工具、协议分析仪等)"},
{LIBUSB_CLASS_WIRELESS, "无线控制器(蓝牙适配器、Wi-Fi网卡等)"},
{LIBUSB_CLASS_MISCELLANEOUS, "复合功能设备(多功能键盘等)"},
{LIBUSB_CLASS_APPLICATION, "特定应用设备(固件更新模式、设备测试模式等)"},
{LIBUSB_CLASS_VENDOR_SPEC, "厂商自定义设备(非标准设备,需专用驱动)"},
};
auto it = m.find(code);
return (it != m.cend() ? it->second : unknown_class);
}
// 传输类型的名称
std::string transfer_type_to_string(uint8_t transfer_type)
{
switch(transfer_type)
{
case LIBUSB_TRANSFER_TYPE_CONTROL:
return "控制传输";
case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS:
return "等时传输";
case LIBUSB_TRANSFER_TYPE_BULK:
return "批量传输";
case LIBUSB_TRANSFER_TYPE_INTERRUPT:
return "中断传输";
default:
return "未知传输类型";
}
}
// 打印端点描述信息
void print_endpoint_descriptor(libusb_endpoint_descriptor const& desc, int depth)
{
// 传输类型 desc.bmAttributes & 0x03 (LIBUSB_TRANSFER_TYPE_MASK)
uint8_t transfer_type = desc.bmAttributes
& LIBUSB_TRANSFER_TYPE_MASK;
std::cout << tab(depth) << "端点地址:0x" << std::hex << std::setw(2)
<< static_cast<int>(desc.bEndpointAddress)
<< " - " << transfer_type_to_string(transfer_type)
<< "(" << ((desc.bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK)? "IN" : "OUT")
<< ")\n";
}
// 打印接口信息
void print_interface_descriptor(libusb_interface_descriptor const& desc, int depth)
{
// 每个接口有自己的设备类型:
std::cout << tab(depth) << "设备类型:"
<< class_code_to_string(desc.bInterfaceClass) << "\n";
std::cout << std::hex;
std::cout << tab(depth) << "设备子类型:0x" << std::setw(2)
<< static_cast<int>(desc.bInterfaceSubClass) << "\n";
std::cout << tab(depth) << "接口协议:0x" << std::setw(2)
<< static_cast<int>(desc.bInterfaceProtocol) << "\n";
std::cout << tab(depth) << "包含端点个数:" << std::setw(4) << std::dec
<< static_cast<int>(desc.bNumEndpoints) << "\n";
for (int i=0; i<desc.bNumEndpoints; ++i)
{
std::cout << tab(depth) << "#" << std::dec << i << ":\n";
print_endpoint_descriptor(desc.endpoint[i], depth+1);
}
}
// 打印设备配置信息
void print_config_descriptor(libusb_config_descriptor const& desc, int depth)
{
// labmda : 设备功耗 = 电流 * 电压 (固定 5 伏)
auto toW = [](unsigned int mA) -> double { return mA * 5.0 / 1000; };
std::cout << tab(depth) << "最大功耗:" << std::setprecision(2)
<< toW(desc.MaxPower * 2) << " 瓦(high-speed-mode),"
<< toW(desc.MaxPower * 8) << "瓦(super-speed-mode)\n";
std::cout << tab(depth) << "包含接口个数:" << std::setw(4) << std::dec
<< static_cast<int>(desc.bNumInterfaces) << "\n";
for (int i=0; i<desc.bNumInterfaces; ++i)
{
std::cout << std::dec << tab(depth) << "接口 #" << i; // 接口从 0 起
// 取接口:
libusb_interface const* interface = &(desc.interface[i]);
if (interface->num_altsetting <= 0)
{
std::cout << "(无备用配置)\n";
continue;
}
if (interface->num_altsetting > 1)
{
std::cout << "(有 " << std::dec << interface->num_altsetting << " 个备用配置)\n";
}
else
{
std::cout << "\n";
}
// 取首选配置:
libusb_interface_descriptor const* interface_desc
= &(interface->altsetting[0]); // 第0个->首选!
// 打印接口描述(设置)信息
print_interface_descriptor(*interface_desc, depth + 1);
}
}
void print_device_descriptor(libusb_device* dev, libusb_device_descriptor const& desc, int depth = 1)
{
std::cout << tab(depth) << "USB 版本:" << bcd_to_version_string(desc.bcdUSB) << "\n";
std::cout << tab(depth) << "设备类型:" << class_code_to_string(desc.bDeviceClass) << "\n";
std::cout << tab(depth) << "设备版本:" << bcd_to_version_string(desc.bcdDevice) << "\n";
std::cout << std::hex;
std::cout << tab(depth) << "厂商 ID:0x" << std::setw(4) << desc.idVendor << "\n";
std::cout << tab(depth) << "产品 ID:0x" << std::setw(4) << desc.idProduct << "\n";
std::cout << tab(depth) << "包含配置数:" << std::dec << std::setw(2)
<< static_cast<int>(desc.bNumConfigurations) << "\n";
libusb_config_descriptor *config_desc = nullptr; // 配置描述
// 只取活动的配置
if (int r = libusb_get_active_config_descriptor(dev, &config_desc); r != LIBUSB_SUCCESS)
{
handle_error(r, "获取设备的活动配置失败!");
return;
}
assert(config_desc != nullptr);
// 安排哨兵
std::unique_ptr<libusb_config_descriptor, void(*)(libusb_config_descriptor *)>
guard(config_desc, libusb_free_config_descriptor);
// 打印配置描述
print_config_descriptor(*config_desc, depth+1);
}
int main()
{
std::system("chcp 65001 >nul"); // 设置程序运行的控制台编码为 UTF-8
// RAII的一种使用(自定义类型,非智能指针)
struct AutoPauser { ~AutoPauser() { std::system("pause"); }} auto_pauser;
// 初始化 libusb
libusb_context *ctx = nullptr;
if (int r = libusb_init(&ctx); r != LIBUSB_SUCCESS)
{
handle_error(r, "初始化失败!");
return -1;
}
// 定义哨兵-1
std::unique_ptr<libusb_context, void(*) (libusb_context*)> guard_1 (ctx, libusb_exit);
// 获取设备列表
libusb_device** devices; // 设备列表
ssize_t count = libusb_get_device_list(ctx, &devices);
if (count < 0)
{
handle_error(count, "获取设备列表失败!");
return -1;
}
std::cout << "发现 " << count << " 个 USB 设备。\n";
// 定义哨兵-2
std::unique_ptr<libusb_device*, void(*) (libusb_device**)> guard_2(devices
, [](libusb_device** p) { libusb_free_device_list(p, 1); });
std::cout << std::setfill('0');
for (int i=0; i<count; ++i)
{
std::cout << "设备 " << std::dec << std::setw(2) << i + 1 << ":\n";
libusb_device_descriptor desc; // 设备描述数据
if (int r = libusb_get_device_descriptor(devices[i], &desc); r != LIBUSB_SUCCESS)
{
handle_error(r, "获取设备描述失败!");
continue;
}
print_device_descriptor(devices[i], desc);
std::cout << "-----------------------------\n";
}
}
7.2 USBIdentifying (终版)
main2.cpp
#include <cassert>
#include <cstdlib>
#include <iostream>
#include <iomanip>
#include <string>
#include <sstream>
#include <memory>
#include <vector>
#include <algorithm>
#include <libusb-1.0/libusb.h>
#pragma pack(push, 1)
// 命令块
struct CommandBlockWrapper
{
uint32_t dCBWSignature = 0x43425355; // "USBC"
uint32_t dCBWTag = 0xCAFE1234;
uint32_t dCBWDataTransferLength;
uint8_t bmCBWFlags; // 0x00=OUT, 0x80=IN
uint8_t bCBWLUN = 0;
uint8_t bCBWCBLength;
uint8_t CBWCB[16] = {0}; // SCSI 指令区
};
// 命令执行状态块
struct CommandStatusWrapper
{
uint32_t dCSWSignature = 0x53425355; // "USBS"
uint32_t dCSWTag; // 匹配 CommandBlockWrapper.dCBWTag
uint32_t dCSWDataResidue;
uint8_t bCSWStatus;
};
#pragma pack(pop)
// 处理错误
void handle_error(char const* msg)
{
std::cerr << msg << "\n";
}
// 处理有(libusb库)出错代码的错误
void handle_error(int r, char const* msg)
{
std::cerr << msg << " - " << libusb_error_name(r) << "\n";
}
// 处理传输字节数不匹配的错误
void handle_error_transferred(char const* msg, int transferred, int expected)
{
std::cerr << msg << " - 传输字节数:" << transferred
<< ",期望字节数:" << expected << "\n";
}
// 处理错误的执行状态
void handle_error_status(char const* msg, uint8_t status, libusb_device_handle* dev_handle)
{
std::cerr << msg << " - 执行状态有误:"
<< std::hex << std::setw(2) << std::setfill('0')
<< static_cast<int>(status) << "\n";
if (status == 0x01)
{
std::cerr << "设备拒绝执行当前命令,请检查SCSI格式与内容\n";
return;
}
if (status == 0x02) // 命令时序阶段性错误(Phase Error),通常需要重置设备
{
if (!dev_handle)
{
std::cerr << "命令阶段错误,请重置设备\n";
}
else
{
std::cerr << "命令阶段错误,将自动重置设备\n";
int r = libusb_reset_device(dev_handle); // 重置!!!
if (r != LIBUSB_SUCCESS)
{
handle_error(r, "自动重置设备失败,请手工尝试");
}
}
}
}
// 处理命令执行“成功”,但有残留数据?
void handle_error_residue(char const* msg, uint32_t residue)
{
std::cerr << msg << " - 有残留数据:" << residue << " 字节\n";
}
// 检查指定的ID组合是否(在你现在的电脑上)唯一
bool verify_device_id_unique(uint16_t const vid, uint16_t const pid)
{
libusb_device** devices = nullptr;
ssize_t count = libusb_get_device_list(nullptr, &devices);
if (count < 0)
{
handle_error(count, "获取设备列表失败!");
return false;
}
// 安排负责释放设备列表的哨兵
std::unique_ptr<libusb_device*, void (*)(libusb_device **)>
guard (devices, +[](libusb_device** ptr)
{
libusb_free_device_list(ptr, 1);
});
int found = 0;
for (int i=0; i<count; ++i)
{
libusb_device* dev = devices[i];
libusb_device_descriptor desc;
if (int r = libusb_get_device_descriptor(dev, &desc); r != LIBUSB_SUCCESS)
{
handle_error(r, "获取设备描述符失败");
continue;
}
if (desc.idVendor == vid && desc.idProduct == pid)
{
++found;
if (found > 1)
{
handle_error("该ID组合的设备存在重复");
return false;
}
}
}
if (found == 0)
{
handle_error("未找到该ID组合的设备");
return false;
}
return true;
}
// 查找指定接口用于批量传输的输出与输入端点
bool find_bulk_endpoints(libusb_interface_descriptor const* interface_desc
, uint8_t* bulk_out_endpoint, uint8_t* bulk_in_endpoint)
{
assert(bulk_out_endpoint != nullptr && bulk_in_endpoint != nullptr);
for (int i=0; i < interface_desc->bNumEndpoints; ++i)
{
libusb_endpoint_descriptor const* endpoint_desc = &(interface_desc->endpoint[i]);
if ((endpoint_desc->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK)
== LIBUSB_TRANSFER_TYPE_BULK)
if ((endpoint_desc->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK)
== LIBUSB_ENDPOINT_OUT)
*bulk_out_endpoint = endpoint_desc->bEndpointAddress;
else if ((endpoint_desc->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK)
== LIBUSB_ENDPOINT_IN)
*bulk_in_endpoint = endpoint_desc->bEndpointAddress;
}
if (0 == *bulk_out_endpoint || 0 == *bulk_in_endpoint)
{
handle_error("未找到用于批量传输的输出或输入端点");
return false;
}
return true;
}
// 读取指令执行状态
bool read_command_status(libusb_device_handle* dev_handle
, uint8_t bulk_in_endpoint, unsigned int timeout)
{
// 定义 CSW 数据
CommandStatusWrapper csw;
// 读取 CSW
int transferred = 0;
if (int r = libusb_bulk_transfer(dev_handle, bulk_in_endpoint,
reinterpret_cast<unsigned char*>(&csw), sizeof(csw)
, &transferred, timeout); r != LIBUSB_SUCCESS)
{
handle_error(r, "读取 CSW 失败");
return false;
}
else if (static_cast<size_t>(transferred) != sizeof(csw))
{
handle_error_transferred("读取 CSW 失败", transferred, sizeof(csw));
return false;
}
else if (csw.bCSWStatus != 0)
{
handle_error_status("前一命令执行失败", csw.bCSWStatus, dev_handle);
return false;
}
else if (csw.dCSWDataResidue > 0)
{
handle_error_residue("前一命令执行“半成功”?", csw.dCSWDataResidue);
return false;
}
return true;
}
// U 盘容量
struct USBCapacity
{
uint32_t max_lba; // 该U盘存储单元共有多少 “块”
uint32_t block_size; // 每一“块”可存储多少字节
// 计算字段
uint64_t total_bytes; // max_lba * block_size 总容量
};
// 读 U 盘容量信息
bool read_usb_capacity(libusb_device_handle * dev_handle, uint8_t bulk_out_endpoint, uint8_t bulk_in_endpoint, USBCapacity* capacity)
{
assert(capacity != nullptr);
// 第一板斧:组装并输出指令块
CommandBlockWrapper cbw;
cbw.dCBWDataTransferLength = 8; // 期待传输(收到)的数据大小
cbw.bmCBWFlags = LIBUSB_ENDPOINT_IN; // 期待收到数据
cbw.bCBWCBLength = 10; // 本指令(+参数)的最大长度
cbw.CBWCB[0] = 0x25; // SCSI READ CAPACITY(10)
unsigned int const timeout = 1000; // ms
if (int transferred = 0, r = libusb_bulk_transfer(dev_handle
, bulk_out_endpoint
, reinterpret_cast<unsigned char*>(&cbw), sizeof(cbw)
, &transferred, timeout); r != LIBUSB_SUCCESS)
{
handle_error(r, "发送 READ CAPACITY(10) 命令块失败");
return false;
}
else if (static_cast<size_t>(transferred) != sizeof(cbw))
{
handle_error_transferred("写 READ CAPACITY(10) 指令,字节数有误"
, transferred, sizeof(cbw));
return false;
}
// 第二板斧:传输业务数据(此处为读)
bool data_transfer_fail = false; // 本步是否失败了?
uint8_t capacity_buffer [8] {}; // 接收缓存区
if (int transferred = 0, r = libusb_bulk_transfer(dev_handle
, bulk_in_endpoint, capacity_buffer, sizeof(capacity_buffer)
, &transferred, timeout); r != LIBUSB_SUCCESS)
{
handle_error(r, "读 READ CAPACITY(10) 执行状态失败!");
data_transfer_fail = true;
}
else if (transferred != sizeof(capacity_buffer))
{
handle_error_transferred("读 READ CAPACITY(10) 传输字节数错误"
, transferred, sizeof(capacity_buffer));
data_transfer_fail = true;
}
if (data_transfer_fail) // 数据传输过程发生错误
{
// 先清除 In 端点的状态
libusb_clear_halt(dev_handle, bulk_in_endpoint);
}
// 第三板斧:读状态 (当第一板成功,本板一定要使出)
if (!read_command_status(dev_handle, bulk_in_endpoint, timeout))
{
handle_error("READ CAPACITY(10) 指令执行结果状态有误");
return false;
}
if (data_transfer_fail)
{
return false;
}
// 解析读到的 8 字节,转成容量信息
capacity->max_lba = (capacity_buffer[0] << 24) | (capacity_buffer[1] << 16)
| (capacity_buffer[2] << 8) | capacity_buffer[3];
capacity->block_size = (capacity_buffer[4] << 24) | (capacity_buffer[5] << 16)
| (capacity_buffer[6] << 8) | capacity_buffer[7];
// 计算总容量 (字节)
capacity->total_bytes = static_cast<uint64_t>(capacity->max_lba + 1)
* capacity->block_size;
return true;
}
// 将块索引和需要占用的块数(固定1块),以大头序拆分到字节数组
void init_cbwcb_for_lba(CommandBlockWrapper& cbw , uint32_t const lba_index)
{
// LBA地址 (大端序)
cbw.CBWCB[2] = (lba_index >> 24) & 0xFF;
cbw.CBWCB[3] = (lba_index >> 16) & 0xFF;
cbw.CBWCB[4] = (lba_index >> 8) & 0xFF;
cbw.CBWCB[5] = lba_index & 0xFF;
// 块数 (写入1个块)
cbw.CBWCB[7] = 0x00;
cbw.CBWCB[8] = 0x01;
}
// 读取一块大小的字节数据到内存
bool read_bytes_from_lba(libusb_device_handle* dev_handle
, std::vector<uint8_t>& bytes
, uint8_t bulk_out_endpoint, uint8_t bulk_in_endpoint
, uint32_t lba_index, unsigned int timeout)
{
// 构造 READ(10) 的命令块
CommandBlockWrapper cbw;
cbw.dCBWDataTransferLength = bytes.size();
cbw.bmCBWFlags = LIBUSB_ENDPOINT_IN; // 期待输入
cbw.bCBWCBLength = 10; // 指令+数据长度标志
cbw.CBWCB[0] = 0x28; // READ(10) 指令操作码
// 数据位置(块索引)与大小(固定一块)
init_cbwcb_for_lba(cbw, lba_index);
// 发送 CBW:
if (int transferred = 0, r = libusb_bulk_transfer(dev_handle, bulk_out_endpoint
, reinterpret_cast<unsigned char*>(&cbw)
, sizeof(cbw), &transferred, timeout); r != LIBUSB_SUCCESS)
{
handle_error(r, "发送 READ(10) 命令块失败");
return false;
}
else if (static_cast<size_t>(transferred) != sizeof(cbw))
{
handle_error_transferred("写 READ(10) 指令,字节数有误", transferred, sizeof(cbw));
return false;
}
bool data_transfer_fail = false;
// 接收数据
if (int transferred = 0, r = libusb_bulk_transfer(dev_handle, bulk_in_endpoint
, bytes.data(), bytes.size(), &transferred, timeout); r != LIBUSB_SUCCESS)
{
handle_error(r, "读业务数据失败");
data_transfer_fail = true;
}
else if (static_cast<size_t>(transferred) != bytes.size())
{
handle_error_transferred ("实际读入业务数据字节数有误", transferred, bytes.size());
data_transfer_fail = true;
}
if (data_transfer_fail)
{
libusb_clear_halt(dev_handle, bulk_in_endpoint); // 清除 IN 端点 STALL 状态
}
// 接收CSW状态
if (!read_command_status(dev_handle, bulk_in_endpoint, timeout))
{
handle_error("READ(10) 指令执行结果状态有误");
return false;
}
return !data_transfer_fail;
}
std::string read_flag_from_usb(libusb_device_handle* dev_handle
, uint32_t lba_index, uint32_t block_size
, uint8_t bulk_out_endpoint, uint8_t bulk_in_endpoint)
{
assert(block_size > 0);
std::cout << "正在从 " << lba_index + 1 << " 块读取身份标志数据……\n";
std::vector<uint8_t> bytes(block_size, '\0'); // 接收缓存区
unsigned int const timeout = 1000; //ms
if (!read_bytes_from_lba(dev_handle, bytes
, bulk_out_endpoint, bulk_in_endpoint, lba_index, timeout))
{
return "";
}
std::cout << "读取成功!\n";
std::string flag;
auto pos = std::find(bytes.begin(), bytes.end(), '\0');
flag.assign(bytes.begin(), pos);
return flag;
}
// 写出一块大小的字节数据到U盘
bool write_bytes_to_lba(libusb_device_handle* dev_handle
, std::vector<uint8_t> const& bytes
, uint8_t bulk_out_endpoint, uint8_t bulk_in_endpoint
, uint32_t lba_index, unsigned int timeout)
{
// 构造 WRITE(10) 的命令块
CommandBlockWrapper cbw;
cbw.dCBWDataTransferLength = bytes.size();
cbw.bmCBWFlags = LIBUSB_ENDPOINT_OUT; // 期待输出
cbw.bCBWCBLength = 10; // 指令+数据长度标志
cbw.CBWCB[0] = 0x2A; // WRITE(10) 指令操作码
// 数据位置(块索引)与大小(固定一块)
init_cbwcb_for_lba(cbw, lba_index);
// 发送 CBW:
if (int transferred = 0, r = libusb_bulk_transfer(dev_handle, bulk_out_endpoint
, reinterpret_cast<unsigned char*>(&cbw), sizeof(cbw)
, &transferred, timeout); r != LIBUSB_SUCCESS)
{
handle_error(r, "发送 WRITE(10) 命令块失败");
return false;
}
else if (static_cast<size_t>(transferred) != sizeof(cbw))
{
handle_error_transferred("写 WRITE(10) 指令,字节数有误", transferred, sizeof(cbw));
return false;
}
bool data_transfer_fail = false;
// 输出数据
if (int transferred = 0, r = libusb_bulk_transfer(dev_handle, bulk_out_endpoint
, const_cast<unsigned char*>(bytes.data())
, bytes.size(), &transferred, timeout); r != LIBUSB_SUCCESS)
{
handle_error(r, "写业务数据失败");
data_transfer_fail = true;
}
else if (static_cast<size_t>(transferred) != bytes.size())
{
handle_error_transferred ("实际写出业务数据字节数有误", transferred, bytes.size());
data_transfer_fail = true;
}
if (data_transfer_fail)
{
libusb_clear_halt(dev_handle, bulk_out_endpoint); // 清除 OUT 端点 STALL 状态
}
// 接收CSW状态
if (!read_command_status(dev_handle, bulk_in_endpoint, timeout))
{
handle_error("WRITE(10) 指令执行结果状态有误");
return false;
}
return !data_transfer_fail;
}
// 将指定身份标志串,写入到U盘的指定位置
bool write_flag_to_usb(libusb_device_handle* dev_handle, uint32_t lba_index
, uint32_t block_size, uint8_t bulk_out_endpoint, uint8_t bulk_in_endpoint
, std::string const& flag)
{
assert(block_size > 0);
assert(flag.size() <= block_size);
std::vector<uint8_t> bytes(block_size, '\0');
std::copy(flag.begin(), flag.end(), bytes.begin());
unsigned int timeout = 1200; //ms
return write_bytes_to_lba(dev_handle, bytes
, bulk_out_endpoint, bulk_in_endpoint, lba_index, timeout);
}
int main(int argc, char const* argv[])
{
std::system("chcp 65001 >nul"); // 设备程序运行的控制台编码为 UTF-8
struct AutoPauser {~AutoPauser() { std::system("pause"); }} auto_pauser;
bool write = (argc > 1) && (std::string(argv[1]) == "--write");
if (int r = libusb_init(nullptr); r != LIBUSB_SUCCESS)
{
handle_error(r, "初始化 libusb 失败!");
return -1;
}
// 哨兵 1
std::unique_ptr<libusb_context, void(*)(libusb_context*)>
GUARD_1 (nullptr, &libusb_exit);
uint16_t const vid = 0x058F, pid = 0x6387;
if (!verify_device_id_unique(vid, pid)) // 俩ID是否合法:存在且唯一
{
return -1;
}
std::cout << std::hex << std::setfill('0');
std::cout << "将使用 VID: 0x" << std::setw(4) << vid
<< " PID: 0x" << std::setw(4) << pid << " 打开设备" << std::endl;
libusb_device_handle* dev_handle
= libusb_open_device_with_vid_pid(nullptr, vid, pid);
if (!dev_handle)
{
handle_error("打开指定ID组合的设备失败");
return -1;
}
// 哨兵 2
std::unique_ptr<libusb_device_handle, void(*)(libusb_device_handle*)>
GUARD_2 (dev_handle, &libusb_close);
// 取设备 (取配置描述需要)
libusb_device* dev = libusb_get_device(dev_handle);
assert(dev != nullptr);
// 取当前活动的配置描述数据
libusb_config_descriptor* config_desc = nullptr;
if (int r = libusb_get_active_config_descriptor(dev, &config_desc)
; r != LIBUSB_SUCCESS)
{
handle_error(r, "获取设备当前活动的配置描述数据失败!");
return -1;
}
assert(config_desc != nullptr);
// 哨兵 3
std::unique_ptr<libusb_config_descriptor, void(*)(libusb_config_descriptor*)>
GUARD_3 (config_desc, &libusb_free_config_descriptor);
// 该配置是否拥有接口?
if (config_desc->bNumInterfaces == 0)
{
std::cerr << "设备描述中,接口个数为0!" << std::endl;
return -1;
}
// 申请占用接口0 (接口0是U盘的标准接口)
if (int r = libusb_claim_interface(dev_handle, 0); r != LIBUSB_SUCCESS)
{
handle_error(r, "申请接口占用失败!");
return -1;
}
// 哨兵 4
std::unique_ptr<libusb_device_handle, void(*)(libusb_device_handle*)>
GUARD_4 (dev_handle, +[](libusb_device_handle* h)
{
libusb_release_interface(h, 0); // 释放指定设备的 0 号接口占用
});
// 取出接口
libusb_interface const * interface
= &(config_desc->interface[0]); // config_desc->interface + 0;
// 检查可选设置个数
if (interface->num_altsetting <= 0)
{
handle_error("接口可选设置个数为0!");
return -1;
}
// 取到接口的首个描述数据 (忽略后续的)
libusb_interface_descriptor const * interface_desc
= &(interface->altsetting[0]);
// 检查该接口是不是 “大容量存储”
if (interface_desc->bInterfaceClass != LIBUSB_CLASS_MASS_STORAGE)
{
handle_error("该接口不是大容量存储类型!");
return -1;
}
// 找到该接口下用于批量输出输入的 endpoint:
uint8_t bulk_out_endpoint = 0, bulk_in_endpoint = 0;
if ( !find_bulk_endpoints(interface_desc
, &bulk_out_endpoint, &bulk_in_endpoint))
{
return -1;
}
std::cout << std::hex << std::setfill('0');
std::cout << "将使用输出端点 0x" << std::hex
<< std::setw(2) << static_cast<int>(bulk_out_endpoint)
<< ", 输入端点 0x" << std::setw(2)
<< static_cast<int>(bulk_in_endpoint) << std::endl;
// 获取存储容量:
USBCapacity capacity = {};
if (!read_usb_capacity(dev_handle
, bulk_out_endpoint, bulk_in_endpoint, &capacity))
{
return -1;
}
std::cout << "该USB大容量存储设备的容量是:" << std::dec
<< capacity.total_bytes << " 字节,块数:"
<< capacity.max_lba + 1 << ",每块大小:"
<< capacity.block_size << " 字节" << std::endl;
assert(capacity.max_lba >= 9);
auto lba_index = capacity.max_lba - 9; // 取倒数第10块
std::string const flag_id = "d2school.com";
if (flag_id.size() > capacity.block_size)
{
handle_error("身份标志串长度超过了当前USB设备存储块大小!");
return -1;
}
if (write)
{
if (!write_flag_to_usb(dev_handle, lba_index, capacity.block_size
, bulk_out_endpoint, bulk_in_endpoint, flag_id))
{
return -1;
}
std::cout << "已经成功往专用盘写入您尊贵的身份标志串!请妥善保管!" << std::endl;
}
// 读取身份标志串:
std::string flag = read_flag_from_usb(dev_handle, lba_index
, capacity.block_size, bulk_out_endpoint, bulk_in_endpoint);
if (flag.empty())
{
std::cout << "不是合法的身份认证专用盘,请联系 d2school.com 在线购买!"
<< std::endl; // 示例,非真实商品
}
if (flag != flag_id)
{
std::cout << "你不是第2学堂的人!" << std::endl;
}
else
{
std::cout << "亲,欢迎回到组织!" << std::endl;
}
}