text
[RRG-proxmark3.git] / doc / new_frame_format.md
blobc5e7d542d7709965aaf0608dd36e9511c9027a2c
1 # New frame format documentation
3 This document is primarily intended for developers only.
5 A major change is the support of variable length frames between host and Proxmark3.  
6 This is a step especially important for usage over FPC/USART/BT.
8 ## Old format
10 Previously, frames were, in both directions like this:
12     uint64_t cmd;
13     uint64_t arg[3];
14     union {
15         uint8_t  asBytes[PM3_CMD_DATA_SIZE];
16         uint32_t asDwords[PM3_CMD_DATA_SIZE / 4];
17     } d;
19 with PM3_CMD_DATA_SIZE = 512 and there was no API abstraction, everybody was forging/parsing these frames.  
20 So the frame size was fixed, 544 bytes, even for simple ACKs.  
21 When snooping the USB transfers, we can observe the host is sending 544b Bulk USB frames while the Proxmark3 is limited by its internal buffers and is sending 128b, 128b, 128b, 128b, 32b, so in total 5 packets.
23 ## New format
25 Even if we make the payload part variable in the old format, we've still a minimum of 32 bytes per frame with fields arbitrarily large.
26 So we designed a new format from scratch:
28 For commands being sent to the Proxmark3:
30     uint32_t magic;
31     uint16_t length : 15;
32     bool ng : 1;
33     uint16_t cmd;
34     uint8_t  data[length];
35     uint16_t crc;
37 * `magic`:  arbitrary magic (`PM3a`) to help re-sync if needed
38 * `length`: length of the variable payload, 0 if none, max 512 (PM3_CMD_DATA_SIZE) for now.
39 * `ng`:     flag to tell if the data is following the new format (ng) or the old one, see transition notes below
40 * `cmd`:    as previously, on 16b as it's enough
41 * `data`:   variable length payload
42 * `crc`:    either an actual CRC (crc14a) or a Magic placeholder (`a3`)
44 For responses from the Proxmark:
46     uint32_t magic;
47     uint16_t length : 15;
48     bool ng : 1;
49     int16_t  status;
50     uint16_t cmd;
51     uint8_t  data[length];
52     uint16_t crc;
54 * `magic`:  arbitrary magic (`PM3b`) to help re-sync if needed
55 * `length`: length of the variable payload, 0 if none, max 512 (PM3_CMD_DATA_SIZE) for now.
56 * `ng`:     flag to tell if the data is following the new format (ng) or the old one, see transition notes below
57 * `status`: a field to send back the status of the command execution
58 * `cmd`:    as previously, on 16b as it's enough
59 * `data`:   variable length payload
60 * `crc`:    either an actual CRC (crc14a) or a Magic placeholder (`a3`)
62 We used to send an anonymous ACK, now we're replying with the corresponding command name and a status.
63 CRC is optional and on reception, the magic `a3` is accepted as placeholder. If it's different then it's checked as a CRC.
64 By default CRC is user over USART and is disabled over USB, on both directions.
66 Internal structures used to handle these packets are:
67 * PacketCommandNGPreamble
68 * PacketCommandNGPostamble
69 * PacketCommandNGRaw
70 * PacketResponseNGPreamble
71 * PacketResponseNGPostamble
72 * PacketResponseNGRaw
74 But they are abstracted from the developer view with a new API. See below.
76 ## Transition
78 Because it's a long transition to clean all the code from the old format and because we don't want to break stuffs when flashing the bootloader, the old frames are still supported together with the new frames. The old structure is now called `PacketCommandOLD` and `PacketResponseOLD` and it's also abstracted from the developer view with the new API.
80 ## New format API
82 So the new API is a merge of the old and the new frame formats, to ensure a smooth transition.
84 The boolean `ng` indicates if the structure is storing data from the old or the new format.
85 Old format can come from either old 544b frames or mixed frames (variable length but still with oldargs).
86 After the full transition, we might remove the fields `oldarg` and `ng`.
87 `PacketCommandNG` and `PacketResponseNG` are the structs used by the API, as seen in a previous section, there are other variable-sized packed structs specifically for the data transmission.
89     typedef struct {
90         uint16_t cmd;
91         uint16_t length;
92         uint32_t magic;      //  NG
93         uint16_t crc;        //  NG
94         uint64_t oldarg[3];  //  OLD
95         union {
96             uint8_t  asBytes[PM3_CMD_DATA_SIZE];
97             uint32_t asDwords[PM3_CMD_DATA_SIZE / 4];
98         } data;
99         bool ng;             // does it store NG data or OLD data?
100     } PacketCommandNG;
102     typedef struct {
103         uint16_t cmd;
104         uint16_t length;
105         uint32_t magic;      //  NG
106         int16_t  status;     //  NG
107         uint16_t crc;        //  NG
108         uint64_t oldarg[3];  //  OLD
109         union {
110             uint8_t  asBytes[PM3_CMD_DATA_SIZE];
111             uint32_t asDwords[PM3_CMD_DATA_SIZE / 4];
112         } data;
113         bool ng;             // does it store NG data or OLD data?
114     } PacketResponseNG;
117 ### On the client, for sending frames:
119 (`client/comms.c`)
121     void SendCommandNG(uint16_t cmd, uint8_t *data, size_t len);
122     void SendCommandBL(uint64_t cmd, uint64_t arg0, uint64_t arg1, uint64_t arg2, void *data, size_t len);
123     void SendCommandOLD(uint64_t cmd, uint64_t arg0, uint64_t arg1, uint64_t arg2, void *data, size_t len);
124     void SendCommandMIX(uint64_t cmd, uint64_t arg0, uint64_t arg1, uint64_t arg2, void *data, size_t len);
126 So cmds should make the transition from `SendCommandOLD` to `SendCommandNG` to benefit from smaller frames (and armsrc handlers adjusted accordingly of course).  
127 `SendCommandBL` is for Bootloader-related activities, see Bootrom section.  
128 `SendCommandMIX` is a transition fct: it uses the same API as `SendCommandOLD` but benefits somehow from variable length frames. It occupies at least 24b of data for the oldargs and real data is therefore limited to PM3_CMD_DATA_SIZE - 24 (defined as PM3_CMD_DATA_SIZE_MIX). Besides the size limitation, the receiver handler doesn't know if this was an OLD frame or a MIX frame, it gets its oldargs and data as usual.  
129 Warning : it makes sense to move from `SendCommandOLD` to `SendCommandMIX` only for *commands with small payloads*.
130 * otherwise both have about the same size
131 * `SendCommandMIX` has a smaller payload (PM3_CMD_DATA_SIZE_MIX < PM3_CMD_DATA_SIZE) so it's risky to blindly move from OLD to MIX if there is a large payload.
133 Internally these functions prepare the new or old frames and call `uart_communication` which calls `uart_send`.
135 ### On the Proxmark3, for receiving frames:
137 (`armsrc/appmain.c`)
139     PacketCommandNG
141 `AppMain` calls `receive_ng`(`common/cmd.c`) which calls `usb_read_ng`/`usart_read_ng` to get a `PacketCommandNG`, then passes it to `PacketReceived`.
142 (no matter if it's an old frame or a new frame, check `PacketCommandNG.ng` field to know if there are `oldargs`)  
143 `PacketReceive` is the commands broker.  
144 Old handlers will still find their stuff in `PacketCommandNG.oldarg` field.
146 ### On the Proxmark3, for sending frames:
148 (`common/cmd.c`)
150     int16_t reply_ng(uint16_t cmd, int16_t status, uint8_t *data, size_t len)
151     int16_t reply_old(uint64_t cmd, uint64_t arg0, uint64_t arg1, uint64_t arg2, void *data, size_t len)
152     int16_t reply_mix(uint64_t cmd, uint64_t arg0, uint64_t arg1, uint64_t arg2, void *data, size_t len)
154 So replies should make the transition from `reply_old` to `reply_ng` to benefit from smaller frames (and client reception adjusted accordingly of course).  
155 `reply_mix` is a transition fct: it uses the same API as reply_old but benefits somehow from variable length frames. It occupies at least 24b of data for the oldargs and real data is therefore limited to PM3_CMD_DATA_SIZE - 24. Besides the size limitation, the client command doesn't know if this was an OLD frame or a MIX frame, it gets its oldargs and data as usual.
157 Example of a handler that supports both OLD/MIX and NG command styles and replies with the new frame format when it receives new command format:
159     if (packet->ng) {
160         reply_ng(CMD_FOOBAR, PM3_SUCCESS, packet->data.asBytes, packet->length);
161     } else {
162         // reply_old(CMD_ACK, 0, 0, 0, packet->data.asBytes, packet->length);
163         reply_mix(CMD_ACK, 0, 0, 0, packet->data.asBytes, packet->length);
164     }
166 ### On the client, for receiving frames:
168 (`client/comms.c`)
170     WaitForResponseTimeout ⇒ PacketResponseNG
172 `uart_communication` calls `uart_receive` and create a `PacketResponseNG`, then passes it to `PacketResponseReceived`.
173 `PacketResponseReceived` treats it immediately (prints) or stores it with `storeReply`.
174 Commands do `WaitForResponseTimeoutW` (or `dl_it`) which uses `getReply` to fetch responses.
176 ## API transition
178 In short, to move from one format to the other, we need for each command:
180 * (client TX) `SendCommandOLD` ⇒ `SendCommandNG` (with all stuff in ad-hoc PACKED structs in `data` field)
181 * (pm3 RX) `PacketCommandNG` parsing, from `oldarg` to only the `data` field
182 * (pm3 TX) `reply_old` ⇒ `reply_ng` (with all stuff in ad-hoc PACKED structs in `data` field)
183 * (client RX) `PacketResponseNG` parsing, from `oldarg` to only the `data` field
185 Meanwhile, a fast transition to MIX frames can be done with:
187 * (client TX) `SendCommandOLD` ⇒ `SendCommandMIX` (but check the limited data size PM3_CMD_DATA_SIZE ⇒ PM3_CMD_DATA_SIZE_MIX)
188 * (pm3 TX) `reply_old` ⇒ `reply_mix` (but check the limited data size PM3_CMD_DATA_SIZE ⇒ PM3_CMD_DATA_SIZE_MIX)
190 ## Bootrom
192 Bootrom code will still use the old frame format to remain compatible with other repos supporting the old format and because it would hardly gain anything from the new format:
193 * almost all frames convey 512b of payload, so difference in overhead is negligible
194 * bringing flash over usart sounds risky and would be terribly slow anyway (115200 bauds vs. 7M bauds).
196 `SendCommandBL` is the same as `SendCommandOLD` with a different name to be sure not to migrate it.
198 ### On the Proxmark3, for receiving frames:
200 (`bootrom/bootrom.c`)
202     usb_read (common/usb_cdc.c) ⇒ UsbPacketReceived (bootrom.c)
203       ⇒ CMD_DEVICE_INFO / CMD_START_FLASH / CMD_FINISH_WRITE / CMD_HARDWARE_RESET
205 also `usb_enable`, `usb_disable` (`common/usb_cdc.c`)
207 ### On the Proxmark3, for sending frames:
209 (`bootrom/bootrom.c`)
211     reply_old (bootrom.c) ⇒ usb_write (common/usb_cdc.c)
213 also `usb_enable`, `usb_disable` (`common/usb_cdc.c`)
215 ### On the client, for sending frames:
217 Therefore, the flasher client (`client/flasher.c` + `client/flash.c`) must still use these old frames.  
218 It uses a few commands in common with current client code:
220     OpenProxmark
221     CloseProxmark
222     SendCommandOLD
223       ⇒ CMD_DEVICE_INFO / CMD_START_FLASH / CMD_FINISH_WRITE / CMD_HARDWARE_RESET
225 ### On the client, for receiving frames:
227 As usual, old frames are still supported
229     WaitForResponseTimeout ⇒ PacketResponseNG
231 ## New usart RX FIFO
233 USART code has been rewritten to cope with unknown size packets.
234 * using USART full duplex with double DMA buffer on RX & TX
235 * using internal FIFO for RX
237 `usart_init`:
238 * USART is activated all way long from usart_init(), no need to touch it in RX/TX routines: `pUS1->US_PTCR = AT91C_PDC_RXTEN | AT91C_PDC_TXTEN`
240 `usart_writebuffer_sync`:
241 * still using DMA but accepts arbitrary packet sizes
242 * removed unneeded memcpy
243 * wait for DMA buffer to be treated before returning, therefore "sync"
244 * we could make an async version but caller must be sure the DMA buffer remains available!
245 * as it's sync, no need for next DMA buffer
247 `usart_read_ng`:
248 * user tells expected packet length
249 * relies on usart_rxdata_available to know if there is data in our FIFO buffer
250 * fetches data from our FIFO
251 * dynamic number of tries (depending on FPC speed) to wait for asked data
253 `usart_rxdata_available`:
254 * polls usart_fill_rxfifo
255 * returns number of bytes available in our FIFO
257 `usart_fill_rxfifo`:
258 * if next DMA buffer got moved to current buffer (`US_RNCR == 0`), it means one DMA buffer is full
259   * transfer current DMA buffer data to our FIFO
260   * swap to the other DMA buffer
261   * provide the emptied DMA buffer as next DMA buffer
262 * if current DMA buffer is partially filled
263   * transfer available data to our FIFO
264   * remember how many bytes we already copied to our FIFO
266 ## Timings
268 Reference (before new format):
270     linux usb: #db#   USB Transfer Speed PM3 ⇒ Client = 545109 Bytes/s
271 On a Windows VM:
273     proxspace usb: #db#   USB Transfer Speed PM3 ⇒ Client = 233998 Bytes/s
275 Over USART:
277 (`common/usart.h`)  
278 USART_BAUD_RATE defined there
280          9600: #db#   USB Transfer Speed PM3 ⇒ Client =    934 Bytes/s
281        115200: #db#   USB Transfer Speed PM3 ⇒ Client =  11137 Bytes/s
282        460800: #db#   USB Transfer Speed PM3 ⇒ Client =  43119 Bytes/s
283     linux usb: #db#   USB Transfer Speed PM3 ⇒ Client = 666624 Bytes/s (equiv. to ~7Mbaud)
286 (`pm3_cmd.h`)
288 Receiving from USART need more than 30ms as we used on USB
289 else we get errors about partial packet reception
291     FTDI   9600 hw status                   ⇒ we need 20ms
292     FTDI 115200 hw status                   ⇒ we need 50ms
293     FTDI 460800 hw status                   ⇒ we need 30ms
294     BT   115200 hf mf fchk --1k -f file.dic ⇒ we need 140ms
296     # define UART_FPC_CLIENT_RX_TIMEOUT_MS  170
297     # define UART_USB_CLIENT_RX_TIMEOUT_MS  20
298     # define UART_TCP_CLIENT_RX_TIMEOUT_MS  300
300 This goes to `uart_posix.c` `timeval` struct
301 and `uart_win32.c` `serial_port_windows` struct
303 It starts at UART_FPC_CLIENT_RX_TIMEOUT_MS and once we detect we're working over USB
304 it's reduced to UART_USB_CLIENT_RX_TIMEOUT_MS.
308 Add automatically some communication delay in the `WaitForResponseTimeout` & `dl_it` timeouts.  
309 Only when using FPC, timeout = 2* empirically measured delay (FTDI cable).  
310 Empirically measured delay (FTDI cable) with "hw ping -l 512" :
312        usb ⇒    6..  32ms
313     460800 ⇒   40..  70ms
314       9600 ⇒ 1100..1150ms
316 (`client/comms.c`)
318     static size_t communication_delay(void) {
319         if (conn.send_via_fpc_usart)  // needed also for Windows USB USART??
320             return 2 * (12000000 / uart_speed);
321         return 100;
322     }
324 Because some commands send a lot of frames before finishing (hw status, lf read,...),
325 `WaitForResponseTimeout` & `dl_it` timeouts are reset at each packet reception,
326 so timeout is actually counted after latest received packet,
327 it doesn't depend anymore on the number of received packets.
329 It was needed to tune pm3 RX usart `maxtry` :
331 (`common/usart.c`)
333     uint32_t usart_read_ng(uint8_t *data, size_t len) {
334     // Empirical max try observed: 3000000 / USART_BAUD_RATE
335     // Let's take 10x
336     uint32_t tryconstant = 0;
337     #ifdef USART_SLOW_LINK
338         // Experienced up to 13200 tries on BT link even at 460800
339         tryconstant = 50000;
340     #endif
341         uint32_t maxtry = 10 * (3000000 / USART_BAUD_RATE) + tryconstant;
344 `DbpStringEx` using `reply_old`:
346     time client/proxmark3 -p /dev/ttyACM0 -c "hw status"
347     2.52s
348     time client/proxmark3 -p /dev/ttyUSB0 -b 460800 -c "hw status"
349     3.03s
350     time client/proxmark3 -p /dev/ttyUSB0 -b 115200 -c "hw status"
351     4.88s
352     time client/proxmark3 -p /dev/ttyUSB0 -b 9600 -c "hw status"
353     26.5s
355 `DbpStringEx` using `reply_mix`:
357     time client/proxmark3 -p /dev/ttyUSB0 -b 9600 -c "hw status"
358     7.08s
360 `DbpStringEx` using `reply_ng`:
362     time client/proxmark3 -p /dev/ttyACM0 -c "hw status"
363     2.10s
364     time client/proxmark3 -p /dev/ttyUSB0 -b 460800 -c "hw status"
365     2.22s
366     time client/proxmark3 -p /dev/ttyUSB0 -b 115200 -c "hw status"
367     2.43s
368     time client/proxmark3 -p /dev/ttyUSB0 -b 9600 -c "hw status"
369     5.75s
371     time client/proxmark3 -p /dev/ttyUSB0 -b 9600 -c "lf read"
372     50.38s
373     time client/proxmark3 -p /dev/ttyUSB0 -b 115200 -c "lf read"
374     6.28s
376     time client/proxmark3 -p /dev/ttyACM0 -c "mem dump -f foo_usb"
377     1.48s
378     time client/proxmark3 -p /dev/ttyUSB0 -b 115200 -c "mem dump -f foo_fpc"
379     25.34s
382 Sending multiple commands can still be slow because it waits regularly for incoming RX frames and the timings are quite conservative because of BT (see struct timeval timeout in uart_posix.c, now at 200ms). When one knows there is no response to wait before the next command, he can use the same trick as in the flasher:
384     // fast push mode
385     conn.block_after_ACK = true;
386     some loop {
387         if (sending_last_command)
388             // Disable fast mode
389             conn.block_after_ACK = false;
390         SendCommandOLD / SendCommandMix
391         if (!WaitForResponseTimeout(CMD_ACK, &resp, some_timeout)) {
392             ....
393             conn.block_after_ACK = false;
394             return PM3_ETIMEOUT;
395         }
396     }
397     return PM3_SUCCESS;
399 Or if it's too complex to determine when we're sending the last command:
401     // fast push mode
402     conn.block_after_ACK = true;
403     some loop {
404         SendCommandOLD / SendCommandMIX
405         if (!WaitForResponseTimeout(CMD_ACK, &resp, some_timeout)) {
406             ....
407             conn.block_after_ACK = false;
408             return PM3_ETIMEOUT;
409         }
410     }
411     // Disable fast mode and send a dummy command to make it effective
412     conn.block_after_ACK = false;
413     SendCommandNG(CMD_PING, NULL, 0);
414     WaitForResponseTimeout(CMD_ACK, NULL, 1000);
415     return PM3_SUCCESS;
418 ## Reference frames
421 For helping debug...
423 * OLD command and reply packets are 544 bytes.
424 * NG & MIX command packets are between 10 and 522 bytes.
425 * NG & MIX reply packets are between 12 and 524 bytes.
427 On linux USB
428 * sent packets can be 544
429 * received packets are max 128, so 544 = 128+128+128+128+32
431 On linux UART (FTDI)
432 * sent packets are max 256, so 544 = 256+256+32
433 * received packets are max 512, so 544 = 512+32
435 `hw ping` (old version, mix reply)
437     TestProxmark: SendCommandOLD(CMD_PING, 0, 0, 0, NULL, 0);
438     ->544=0901000000000000000000000000000000000000000000000000000000000000  -> OLD
439     CMD_PING: reply_mix(CMD_ACK, reply_via_fpc, 0, 0, 0, 0);
440     <-36=504d336218000000ff0000000000000000000000000000000000000000000000   <- MIX
442 `hw ping` (intermediate version using MIX)
444     CmdPing  SendCommandMIX(CMD_PING, 0, 0, 0, NULL, 0);
445     ->34=504d336118000901000000000000000000000000000000000000000000000000   -> MIX
446     CMD_PING reply_mix(CMD_ACK, reply_via_fpc, 0, 0, 0, 0);
447     <-36=504d336218000000ff0000000000000000000000000000000000000000000000   <- MIX
449 `hw ping` (current NG version)
451     CmdPing  SendCommandNG(CMD_PING, data, len);
452     ->10=504d3361008009016133                                               -> NG
453     CMD_PING reply_ng(CMD_PING, PM3_SUCCESS, packet->data.asBytes, packet->length);
454     <-12=504d33620080000009016233                                           <- NG
456 `hw ping -l 512` (NG)
458     CmdPing  SendCommandNG(CMD_PING, data, len);
459     ->522=504d336100820901000102030405060708090a0b0c0d0e0f1011121314151617  -> NG
460     CMD_PING reply_ng(CMD_PING, PM3_SUCCESS, packet->data.asBytes, packet->length);
461     <-128=504d3362008200000901000102030405060708090a0b0c0d0e0f101112131415  <- NG
462     <-128=767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495
463     <-128=f6f7f8f9fafbfcfdfeff000102030405060708090a0b0c0d0e0f101112131415
464     <-128=767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495
465     <-12=f6f7f8f9fafbfcfdfeff6233