Embedded SDK
Embedded SDK
Loading...
Searching...
No Matches
u2f_transport.c
Go to the documentation of this file.
1
2/*******************************************************************************
3 * Ledger Nano S - Secure firmware
4 * (c) 2022 Ledger
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 ********************************************************************************/
18
19#ifdef HAVE_IO_U2F
20
21#include <stdint.h>
22#include <string.h>
23#include "u2f_service.h"
24#include "u2f_transport.h"
25#include "u2f_processing.h"
26#include "u2f_io.h"
27
28#include "lcx_rng.h"
29#include "lcx_crc.h"
30#include "os.h"
31#include "os_io_seproxyhal.h"
32
33#define U2F_MASK_COMMAND 0x80
34#define U2F_COMMAND_HEADER_SIZE 3
35
36static const uint8_t BROADCAST_CHANNEL[] = {0xff, 0xff, 0xff, 0xff};
37static const uint8_t FORBIDDEN_CHANNEL[] = {0x00, 0x00, 0x00, 0x00};
38
39/* TODO: take into account the INIT during SEGMENTED message correctly.
40 * Avoid erasing the first part of the apdu buffer when doing so)
41 */
42
43// init
44void u2f_transport_reset(u2f_service_t *service)
45{
46 service->transportState = U2F_IDLE;
47 service->transportOffset = 0;
48 service->transportMedia = 0;
49 service->transportPacketIndex = 0;
51 service->fakeChannelTransportOffset = 0;
53 service->sending = false;
55 // reset the receive buffer to allow for a new message to be received again (in case
56 // transmission of a CODE buffer the previous reply)
57 service->transportBuffer = service->transportReceiveBuffer;
58 cx_rng(service->channel, U2F_CHANNEL_ID_SIZE);
59}
60
65 uint8_t *message_buffer,
66 uint16_t message_buffer_length)
67{
68 service->next_channel = 1;
69 service->transportReceiveBuffer = message_buffer;
70 service->transportReceiveBufferLength = message_buffer_length;
71 u2f_transport_reset(service);
72}
73
77static void u2f_transport_error(u2f_service_t *service, char errorCode)
78{
79 // u2f_transport_reset(service); // warning reset first to allow for U2F_io sent call to
80 // u2f_transport_sent internally on eventless platforms
81 G_io_usb_ep_buffer[8] = errorCode;
82
83 // ensure the state is set to error sending to allow for special treatment in case reply is not
84 // read by the receiver
86 service->transportPacketIndex = 0;
87 service->transportBuffer = G_io_usb_ep_buffer + 8;
88 service->transportOffset = 0;
89 service->transportLength = 1;
90 service->sendCmd = U2F_STATUS_ERROR;
91 // pump the first message, with the reception media
92 u2f_transport_sent(service, service->media);
93}
94
100{
101 // don't process when replying to anti timeout requests
102 if (!u2f_message_repliable(service)) {
103 // previous mark packet as sent
104 service->sending = false;
105 return;
106 }
107
108 // previous mark packet as sent
109 service->sending = false;
110
111 // if idle (possibly after an error), then only await for a transmission
113 && service->transportState != U2F_SENDING_ERROR) {
114 // absorb the error, transport is erroneous but that won't hurt in the end.
115 // also absorb the fake channel user presence check reply ack
116 // THROW(INVALID_STATE);
117 return;
118 }
119 if (service->transportOffset < service->transportLength) {
120 uint16_t mtu = (media == U2F_MEDIA_USB) ? USB_SEGMENT_SIZE : BLE_SEGMENT_SIZE;
121 uint16_t channelHeader = (media == U2F_MEDIA_USB ? 4 : 0);
122 uint8_t headerSize
123 = (service->transportPacketIndex == 0 ? (channelHeader + 3) : (channelHeader + 1));
124 uint16_t blockSize
125 = ((service->transportLength - service->transportOffset) > (mtu - headerSize)
126 ? (mtu - headerSize)
127 : service->transportLength - service->transportOffset);
128 uint16_t dataSize = blockSize + headerSize;
129 uint16_t offset = 0;
130 // Fragment
131 if (media == U2F_MEDIA_USB) {
132 memcpy(G_io_usb_ep_buffer, service->channel, U2F_CHANNEL_ID_SIZE);
133 offset += 4;
134 }
135 if (service->transportPacketIndex == 0) {
136 G_io_usb_ep_buffer[offset++] = service->sendCmd;
137 G_io_usb_ep_buffer[offset++] = (service->transportLength >> 8);
138 G_io_usb_ep_buffer[offset++] = (service->transportLength & 0xff);
139 }
140 else {
141 G_io_usb_ep_buffer[offset++] = (service->transportPacketIndex - 1);
142 }
143 if (service->transportBuffer != NULL) {
144 memmove(G_io_usb_ep_buffer + headerSize,
145 service->transportBuffer + service->transportOffset,
146 blockSize);
147 }
148 service->transportOffset += blockSize;
149 service->transportPacketIndex++;
150 u2f_io_send(G_io_usb_ep_buffer, dataSize, media);
151 }
152 // last part sent
153 else if (service->transportOffset == service->transportLength) {
154 u2f_transport_reset(service);
155 // we sent the whole response (even if we haven't yet received the ack for the last sent usb
156 // in packet)
157 G_io_app.apdu_state = APDU_IDLE;
158 }
159}
160
161void u2f_transport_send_usb_user_presence_required(u2f_service_t *service)
162{
163 uint16_t offset = 0;
164 service->sending = true;
165 memcpy(G_io_usb_ep_buffer, service->channel, U2F_CHANNEL_ID_SIZE);
166 offset += 4;
167 G_io_usb_ep_buffer[offset++] = U2F_CMD_MSG;
168 G_io_usb_ep_buffer[offset++] = 0;
169 G_io_usb_ep_buffer[offset++] = 2;
170 G_io_usb_ep_buffer[offset++] = 0x69;
171 G_io_usb_ep_buffer[offset++] = 0x85;
172 u2f_io_send(G_io_usb_ep_buffer, offset, U2F_MEDIA_USB);
173}
174
175void u2f_transport_send_wink(u2f_service_t *service)
176{
177 uint16_t offset = 0;
178 service->sending = true;
179 memcpy(G_io_usb_ep_buffer, service->channel, U2F_CHANNEL_ID_SIZE);
180 offset += 4;
181 G_io_usb_ep_buffer[offset++] = U2F_CMD_WINK;
182 G_io_usb_ep_buffer[offset++] = 0;
183 G_io_usb_ep_buffer[offset++] = 0;
184 u2f_io_send(G_io_usb_ep_buffer, offset, U2F_MEDIA_USB);
185}
186
187#ifdef HAVE_FIDO2
188
190{
191 uint16_t offset = 0;
192 service->sending = true;
193 memcpy(G_io_usb_ep_buffer, service->channel, U2F_CHANNEL_ID_SIZE);
194 offset += 4;
195 G_io_usb_ep_buffer[offset++] = CTAP2_STATUS_KEEPALIVE;
196 G_io_usb_ep_buffer[offset++] = 0;
197 G_io_usb_ep_buffer[offset++] = 1;
198 G_io_usb_ep_buffer[offset++] = reason;
199 u2f_io_send(G_io_usb_ep_buffer, offset, U2F_MEDIA_USB);
200}
201
202#endif
203
204bool u2f_transport_receive_fakeChannel(u2f_service_t *service, uint8_t *buffer, uint16_t size)
205{
207 return false;
208 }
209 if (memcmp(service->channel, buffer, U2F_CHANNEL_ID_SIZE) != 0) {
210 goto error;
211 }
212 if (service->fakeChannelTransportOffset == 0) {
213 uint16_t commandLength = U2BE(buffer, 4 + 1) + U2F_COMMAND_HEADER_SIZE;
214 // Some buggy implementations can send a WINK here, reply it gently
215 if (buffer[4] == U2F_CMD_WINK) {
216 u2f_transport_send_wink(service);
217 return true;
218 }
219
220 if (commandLength != service->transportLength) {
221 goto error;
222 }
223 if (buffer[4] != U2F_CMD_MSG) {
224 goto error;
225 }
226 service->fakeChannelTransportOffset = MIN(size - 4, service->transportLength);
228 service->fakeChannelCrc
229 = cx_crc16_update(0, buffer + 4, service->fakeChannelTransportOffset);
230 }
231 else {
232 if (buffer[4] != service->fakeChannelTransportPacketIndex) {
233 goto error;
234 }
235 uint16_t xfer_len
236 = MIN(size - 5, service->transportLength - service->fakeChannelTransportOffset);
238 service->fakeChannelTransportOffset += xfer_len;
239 service->fakeChannelCrc = cx_crc16_update(service->fakeChannelCrc, buffer + 5, xfer_len);
240 }
241 if (service->fakeChannelTransportOffset >= service->transportLength) {
242 if (service->fakeChannelCrc != service->commandCrc) {
243 goto error;
244 }
246 service->fakeChannelTransportOffset = 0;
247 // reply immediately when the asynch response is not yet ready
249 u2f_transport_send_usb_user_presence_required(service);
250 // response sent
252 }
253 }
254 return true;
255error:
257 // don't hesitate here, the user will have to exit/rerun the app otherwise.
258 THROW(EXCEPTION_IO_RESET);
259 return false;
260}
261
267 uint8_t *buffer,
268 uint16_t size,
270{
271 uint16_t channelHeader = (media == U2F_MEDIA_USB ? 4 : 0);
272 uint16_t xfer_len;
273 service->media = media;
274
275 // PRINTF("recv %d %d %d %d %d\n", size, service->waitAsynchronousResponse,
276 // service->transportState, service->transportOffset, buffer[4]);
277
278 // Handle a busy channel and avoid reentry
279 if (service->transportState == U2F_SENDING_RESPONSE) {
280 u2f_transport_error(service, ERROR_CHANNEL_BUSY);
281 goto error;
282 }
284 // TODO : this is an error for FIDO 2
285 if (!u2f_transport_receive_fakeChannel(service, buffer, size)) {
286 u2f_transport_error(service, ERROR_CHANNEL_BUSY);
287 goto error;
288 }
289 return;
290 }
291
292 // SENDING_ERROR is accepted, and triggers a reset => means the host hasn't consumed the error.
293 if (service->transportState == U2F_SENDING_ERROR) {
294 u2f_transport_reset(service);
295 }
296
297 if (size < (1 + channelHeader)) {
298 // Message to short, abort
299 u2f_transport_error(service, ERROR_PROP_MESSAGE_TOO_SHORT);
300 goto error;
301 }
302 if (media == U2F_MEDIA_USB) {
303 // hold the current channel value to reply to, for example, INIT commands within flow of
304 // segments.
305 memcpy(service->channel, buffer, U2F_CHANNEL_ID_SIZE);
306 }
307
308#ifdef HAVE_FIDO2
309
310 // Handle a cancel request if received
311
312 if ((buffer[channelHeader] == CTAP2_CMD_CANCEL)
313 && (((media == U2F_MEDIA_USB)
314 && (memcmp(service->transportChannel, service->channel, U2F_CHANNEL_ID_SIZE) == 0))
315 || (media != U2F_MEDIA_USB))) {
316 // Drop the cancel request if there's no command to be processed, otherwise pass it to the
317 // upper layer immediately
318 if (service->transportState != U2F_PROCESSING_COMMAND) {
319 return;
320 }
321 uint16_t commandLength = U2BE(buffer, channelHeader + 1);
322 ctap2_handle_cmd_cancel(service, buffer + channelHeader + 1 + 2, commandLength);
323 return;
324 }
325
326#endif
327
328 // no previous chunk processed for the current message
329 if (service->transportOffset == 0
330 // on USB we could get an INIT within a flow of segments.
331 || (media == U2F_MEDIA_USB
332 && memcmp(service->transportChannel, service->channel, U2F_CHANNEL_ID_SIZE) != 0)
333 // CTAP2 transport test (HID-1)
334 || (buffer[channelHeader] == U2F_CMD_INIT)) {
335 if (size < (channelHeader + 3)) {
336 // Message to short, abort
337 u2f_transport_error(service, ERROR_PROP_MESSAGE_TOO_SHORT);
338 goto error;
339 }
340 // check this is a command, cannot accept continuation without previous command
341 if ((buffer[channelHeader + 0] & U2F_MASK_COMMAND) == 0) {
342 // Not a command packet, abort
343 // CTAP2 transport test : do not send back an error in this case (HID-1)
344 // u2f_transport_error(service, ERROR_INVALID_SEQ);
345 goto error;
346 }
347
348 // If waiting for a continuation on a different channel, reply BUSY
349 // immediately
350 if (media == U2F_MEDIA_USB) {
351 if ((service->transportState == U2F_HANDLE_SEGMENTED)
352 && (memcmp(service->channel, service->transportChannel, U2F_CHANNEL_ID_SIZE) != 0)
353 && (buffer[channelHeader] != U2F_CMD_INIT)) {
354 // special error case, we reply but don't change the current state of the transport
355 // (ongoing message for example)
356 // u2f_transport_error_no_reset(service, ERROR_CHANNEL_BUSY);
357 uint16_t offset = 0;
358 // Fragment
359 if (media == U2F_MEDIA_USB) {
360 memcpy(G_io_usb_ep_buffer, service->channel, U2F_CHANNEL_ID_SIZE);
361 offset += 4;
362 }
363 G_io_usb_ep_buffer[offset++] = U2F_STATUS_ERROR;
364 G_io_usb_ep_buffer[offset++] = 0;
365 G_io_usb_ep_buffer[offset++] = 1;
366 G_io_usb_ep_buffer[offset++] = ERROR_CHANNEL_BUSY;
367 u2f_io_send(G_io_usb_ep_buffer, offset, media);
368 goto error;
369 }
370 }
371 // If a command was already sent, and we are not processing a INIT
372 // command, abort
373 if ((service->transportState == U2F_HANDLE_SEGMENTED)
374 && !((media == U2F_MEDIA_USB) && (buffer[channelHeader] == U2F_CMD_INIT))) {
375 // Unexpected continuation at this stage, abort
376 u2f_transport_error(service, ERROR_INVALID_SEQ);
377 goto error;
378 }
379 // Check the length
380 uint16_t commandLength = U2BE(buffer, channelHeader + 1);
381 if (commandLength > (service->transportReceiveBufferLength - 3)) {
382 // Overflow in message size, abort
383 u2f_transport_error(service, ERROR_INVALID_LEN);
384 goto error;
385 }
386 // Check if the command is supported
387 switch (buffer[channelHeader]) {
388 case U2F_CMD_PING:
389 case U2F_CMD_MSG:
390#ifdef HAVE_FIDO2
391 case CTAP2_CMD_CBOR:
392 case CTAP2_CMD_CANCEL:
393#endif
394 if (media == U2F_MEDIA_USB) {
396 || u2f_is_channel_forbidden(service->channel)) {
397 u2f_transport_error(service, ERROR_INVALID_CID);
398 goto error;
399 }
400 }
401 // no channel for BLE
402 break;
403 case U2F_CMD_INIT:
404 if (media != U2F_MEDIA_USB) {
405 // Unknown command, abort
406 u2f_transport_error(service, ERROR_INVALID_CMD);
407 goto error;
408 }
409
410 if (u2f_is_channel_forbidden(service->channel)) {
411 u2f_transport_error(service, ERROR_INVALID_CID);
412 goto error;
413 }
414
415 break;
416 default:
417 // Unknown command, abort
418 u2f_transport_error(service, ERROR_INVALID_CMD);
419 goto error;
420 }
421
422 // Ok, initialize the buffer
423 // if (buffer[channelHeader] != U2F_CMD_INIT)
424 {
425 xfer_len = MIN(size - (channelHeader), U2F_COMMAND_HEADER_SIZE + commandLength);
426 memmove(service->transportBuffer, buffer + channelHeader, xfer_len);
427 if (media == U2F_MEDIA_USB) {
428 service->commandCrc = cx_crc16_update(0, service->transportBuffer, xfer_len);
429 }
430 service->transportOffset = xfer_len;
431 service->transportLength = U2F_COMMAND_HEADER_SIZE + commandLength;
432 service->transportMedia = media;
433 // initialize the response
434 service->transportPacketIndex = 0;
435 memcpy(service->transportChannel, service->channel, U2F_CHANNEL_ID_SIZE);
436 }
437 }
438 else {
439 // Continuation
440 if (size < (channelHeader + 2)) {
441 // Message to short, abort
442 u2f_transport_error(service, ERROR_PROP_MESSAGE_TOO_SHORT);
443 goto error;
444 }
445 if (media != service->transportMedia) {
446 // Mixed media
447 u2f_transport_error(service, ERROR_PROP_MEDIA_MIXED);
448 goto error;
449 }
450 if (service->transportState != U2F_HANDLE_SEGMENTED) {
451 // Unexpected continuation at this stage, abort
452 // TODO : review the behavior is HID only
453 if (media == U2F_MEDIA_USB) {
454 u2f_transport_reset(service);
455 goto error;
456 }
457 else {
458 u2f_transport_error(service, ERROR_INVALID_SEQ);
459 goto error;
460 }
461 }
462 if (media == U2F_MEDIA_USB) {
463 // Check the channel
464 if (memcmp(buffer, service->channel, U2F_CHANNEL_ID_SIZE) != 0) {
465 u2f_transport_error(service, ERROR_CHANNEL_BUSY);
466 goto error;
467 }
468 }
469 // also discriminate invalid command sent instead of a continuation
470 if (buffer[channelHeader] != service->transportPacketIndex) {
471 // Bad continuation packet, abort
472 u2f_transport_error(service, ERROR_INVALID_SEQ);
473 goto error;
474 }
475 xfer_len
476 = MIN(size - (channelHeader + 1), service->transportLength - service->transportOffset);
477 memmove(service->transportBuffer + service->transportOffset,
478 buffer + channelHeader + 1,
479 xfer_len);
480 if (media == U2F_MEDIA_USB) {
481 service->commandCrc = cx_crc16_update(
482 service->commandCrc, service->transportBuffer + service->transportOffset, xfer_len);
483 }
484 service->transportOffset += xfer_len;
485 service->transportPacketIndex++;
486 }
487 // See if we can process the command
488 if ((media != U2F_MEDIA_USB)
489 && (service->transportOffset > (service->transportLength + U2F_COMMAND_HEADER_SIZE))) {
490 // Overflow, abort
491 u2f_transport_error(service, ERROR_INVALID_LEN);
492 goto error;
493 }
494 else if (service->transportOffset >= service->transportLength) {
495 // switch before the handler gets the opportunity to change it again
497 // internal notification of a complete message received
498 u2f_message_complete(service);
499 }
500 else {
501 // new segment received, reset the timeout for the current piece
502 service->seqTimeout = 0;
504 }
505error:
506 return;
507}
508
510{
511 return (memcmp(channel, BROADCAST_CHANNEL, 4) == 0);
512}
513
515{
516 return (memcmp(channel, FORBIDDEN_CHANNEL, 4) == 0);
517}
518
523{
524 // TODO : this only works for U2F
525
526 if (enabled) {
527 // start replying placeholder until user presence validated
530 u2f_transport_send_usb_user_presence_required(service);
531 }
532 }
533 // don't set to REPLY_READY when it has not been enabled beforehand
534 else if (service->waitAsynchronousResponse == U2F_WAIT_ASYNCH_ON) {
536 }
537}
538
540{
541 // no more asynch replies
542 // finished receiving the command
543 // and not sending a user presence required status
547 && service->sending == false);
548}
549
550void u2f_message_reply(u2f_service_t *service, uint8_t cmd, uint8_t *buffer, uint16_t len)
551{
552 // if U2F is not ready to reply, then gently avoid replying
553 if (u2f_message_repliable(service)) {
555 service->transportPacketIndex = 0;
556 service->transportBuffer = buffer;
557 service->transportOffset = 0;
558 service->transportLength = len;
559 service->sendCmd = cmd;
560 if (service->transportMedia != U2F_MEDIA_BLE) {
561 // pump the first message
562 u2f_transport_sent(service, service->transportMedia);
563 }
564 else {
565 while (G_io_app.apdu_state != APDU_IDLE) {
566 u2f_transport_sent(service, service->transportMedia);
567 }
568 }
569 }
570}
571
572#endif
CRC (Cyclic Redundancy Check).
Random Number Generation.
#define MIN(x, y)
Definition nbgl_types.h:98
uint16_t commandCrc
Definition u2f_service.h:84
uint32_t next_channel
Definition u2f_service.h:62
uint8_t * transportReceiveBuffer
Definition u2f_service.h:68
uint8_t waitAsynchronousResponse
Definition u2f_service.h:88
uint8_t * transportBuffer
Definition u2f_service.h:76
uint8_t fakeChannelTransportPacketIndex
Definition u2f_service.h:82
uint32_t seqTimeout
Definition u2f_service.h:95
uint16_t transportReceiveBufferLength
Definition u2f_service.h:70
uint8_t sendCmd
Definition u2f_service.h:99
uint16_t fakeChannelCrc
Definition u2f_service.h:85
uint8_t channel[U2F_CHANNEL_ID_SIZE]
Definition u2f_service.h:64
u2f_transport_state_t transportState
Definition u2f_service.h:77
uint16_t transportLength
Definition u2f_service.h:74
uint8_t transportPacketIndex
Definition u2f_service.h:75
uint16_t fakeChannelTransportOffset
Definition u2f_service.h:81
u2f_transport_media_t media
Definition u2f_service.h:65
uint16_t transportOffset
Definition u2f_service.h:73
u2f_transport_media_t transportMedia
Definition u2f_service.h:78
uint8_t transportChannel[4]
Definition u2f_service.h:72
u2f_transport_state_t fakeChannelTransportState
Definition u2f_service.h:83
void u2f_io_send(uint8_t *buffer, uint16_t length, u2f_transport_media_t media)
void u2f_message_set_autoreply_wait_user_presence(u2f_service_t *service, bool enabled)
void u2f_message_complete(u2f_service_t *service)
void u2f_message_reply(u2f_service_t *service, uint8_t cmd, uint8_t *buffer, uint16_t length)
bool u2f_message_repliable(u2f_service_t *service)
u2f_transport_media_t
Definition u2f_service.h:47
@ U2F_MEDIA_BLE
Definition u2f_service.h:51
@ U2F_MEDIA_USB
Definition u2f_service.h:49
@ U2F_WAIT_ASYNCH_ON
Definition u2f_service.h:56
@ U2F_WAIT_ASYNCH_REPLY_READY
Definition u2f_service.h:57
@ U2F_WAIT_ASYNCH_IDLE
Definition u2f_service.h:55
void ctap2_handle_cmd_cancel(u2f_service_t *service, uint8_t *buffer, uint16_t length)
@ U2F_HANDLE_SEGMENTED
Definition u2f_service.h:39
@ U2F_FAKE_RECEIVED
Definition u2f_service.h:44
@ U2F_IDLE
Definition u2f_service.h:38
@ U2F_PROCESSING_COMMAND
Definition u2f_service.h:40
@ U2F_SENDING_ERROR
Definition u2f_service.h:42
@ U2F_INTERNAL_ERROR
Definition u2f_service.h:43
@ U2F_SENDING_RESPONSE
Definition u2f_service.h:41
#define U2F_CHANNEL_ID_SIZE
Definition u2f_service.h:27
#define ERROR_PROP_MEDIA_MIXED
#define CTAP2_CMD_CANCEL
#define ERROR_INVALID_LEN
#define CTAP2_CMD_CBOR
void u2f_transport_sent(u2f_service_t *service, u2f_transport_media_t media)
#define ERROR_INVALID_CID
bool u2f_is_channel_forbidden(uint8_t *channel)
void u2f_transport_init(u2f_service_t *service, uint8_t *message_buffer, uint16_t message_buffer_length)
#define U2F_CMD_INIT
#define U2F_CMD_MSG
#define U2F_STATUS_ERROR
#define ERROR_CHANNEL_BUSY
bool u2f_is_channel_broadcast(uint8_t *channel)
#define U2F_CMD_PING
#define U2F_CMD_WINK
void u2f_transport_received(u2f_service_t *service, uint8_t *buffer, uint16_t size, u2f_transport_media_t media)
#define ERROR_PROP_MESSAGE_TOO_SHORT
#define CTAP2_STATUS_KEEPALIVE
void u2f_transport_ctap2_send_keepalive(u2f_service_t *service, uint8_t reason)
#define ERROR_INVALID_SEQ
#define ERROR_INVALID_CMD
unsigned short uint16_t
Definition usbd_conf.h:54
unsigned char uint8_t
Definition usbd_conf.h:53