User Tools

Site Tools


cvend

This is an old revision of the document!


cVEND NFC Reader

cVEND is the NFC reader on the bottom half of the PM3

cvend_plug.pdf

The associated serial device appears to be at /dev/ttymxc3 @ 115200 8n1 in Linux.

cVEND protocol notes

Boot-up / initialization logic analyzer trace (open with the Saleae app):

cvend-bootup.sal

IPP packet structure

offset length name description
0 1 always 0xBC
1 1 seq monotonically increasing from 1; skips 0 when rolling over
2 1 replyTo seq of the message that this is replying to, or 0 if not a reply
3 1 msgType
4 2 msgLen length of msgData
6 1 hdrCRC CRC-8-Dallas/Maxim over previous 6 bytes
7 msgLen msgData message payload, structure depends on msgType
7+msgLen 4 msgCRC little endian bit inverted IEEE CRC-32/JAMCRC over msgData, only present when msgType >= 0x80

IPP message types

msgType dir name description
0x02 Version
0x03 VersionReply (device ID from rockchip otp, firmware version, cwd) as length-prefixed strings
e.g. “\x18\x00\xdd\xf5\x14cS01.05.46-49.00-2-2\x0502.35\n/home/app0”
0x04 Status
0x05 StatusReply e.g. “\x00\x00”
0x07 Heartbeat sent periodically by reader
0x0f Startup sent by reader after startup, approx 1 minute after power on, e.g. “\x00”
0x10 Reset first byte selects subcommand (2 = restart application, 3 = reboot, 4 = poweroff, 33-36 = reset sam 1-4, others = Error: Reset Code not implemented yet. [IppBaseTelegram.cpp:157])
0x11 ResetReply e.g. “\x00\x00”
0x20 LEDs sets LED status, u32 bitmap; the only two externally visible LEDs on the PM3 are “\x00\x00\x00\x40” and “\x00\x00\x00\x80”
0x22 Buzzer makes the cvend beep; u16 frequency, u16 duration. e.g. “\x06\x00\x01\x00”
0x32 CardRelease registered in ProxCardCtrl::ProxCardCtrl(RFIDReader&)
0x46 AbortCardHandling registered in ProxCardCtrl::ProxCardCtrl(RFIDReader&)
0x96 PutFile
0x97 PutFileReply
0x98 GetFile
0x99 GetFileReply
0x9a DeleteFile
0x9b DeleteFileReply
0x9c FileInfo
0x9d FileInfoReply
0xa4 FileList Seems to list files on the cVEND. First 2 bytes are the path length, remaining bytes are the path to list
0xa5 FileListReply First 4 bytes are reply length, remaining bytes seem to be null seperated modification timestamps and file/folder names
0xaa SetTime
0xab SetTimeReply
0xac ITSOData conditionally registered in ItsoIppHandler::_handleItsoCtrlReq(CommBuffer&, IppHeader const&, unsigned char const*)
0xad ITSODataReply
0xae ITSOCtrl registered in ItsoIppHandler::ItsoIppHandler(ProxCardItso&)
0xaf ITSOCtrlReply
0xb1 ISORead sent by reader when ISO14443A card presented, after enabling Iso with 0xe4
card UID at offset 2
0xb3 ISOCardReleased sent by reader after ISO1443A card released with 0x32 or 0x46
0xb4 APDUProx DeviceSelect(0x00), CLA, INS, P1, P2, Lc (2 bytes), Data, [magic], Le (2 bytes), I don't know what magic is, but put 0x04 if you have data or 0x01 if you don't
0xb5 APDUProxReply DeviceSelect(0x00), Echo (?), Status (0x00=success, 0x6c=no session, 0x70=no card, 0x76=retry), SW1, SW2, Data
0xb6 SAMCtrl registered in SamCtrl::SamCtrl()
0xb7 SAMCtrlReply
0xb9 DESFireRead sent by reader when DESFire card presented, after enabling DESFire with 0xe4
0xba unknown, registered in ProxCardDesfire::ProxCardDesfire(RFIDReader&)
0xbb DESFireCardRemoved sent by reader when DESFire card removed from field
0xbc DESFireCommand sends desfire command, documented in m075031_desfire.pdf
0xbd DESFireCommandReply response to command, documented above
0xbe UnhandledCard sent by reader when a card is presented that is not supported by any enabled ProxCardFunction, containing UID, historical bytes, and other data. First two bytes seem to be card type
0xce unknown, registered in IppHandling::IppHandling()
0xd0 EMV first byte selects subcommand (0 = load config, 1 = preprocess, 2 = toggle polling)
0xd1 EMVStatus sent by reader after startup and certain nfc state changes, format and semantics not yet understood
0xd4 UltralightCommand Sends a subcommand to the UltralightC/NTAG card in the field. First byte is subcmd. See table below.
0xd5 UltralightReply Unsolicited card event (replyTo = 0), or reply to a 0xD4 command (replyTo = seq of the 0xD4). See tables below.
0xe4 ProxCardFunction first 2 bytes specify function (4=VdvKa, 5=MifareClassic, 6=Iso, 7=Desfire, 8=Girogo, 9=Itso, 10=UltralightC), next byte must be 1, next byte (0=disable, 1=enable)
00070101 sent by PM3 to enable DESFire reading at startup
0xe5 ProxCardFunctionReply Returns 4 byte payload: 0x00, the function number (as above), 0x00, 0x00
0xe6 unknown, registered in GirogoIppHandler::GirogoIppHandler(RFIDReader&, ProxCardGirogo*)
0xe8 SecurityServices first byte selects subcommand (0=GetVersionOfKey, 1=RemoveX509Cert, 2=RemoveKeyBlock, 3=ImportX509Cert, 4=ExportX509Cert, 5=ImportKeyBlock, 6=ExportKeyBlock, 7=GenerateKSKPair), remaining bytes unknown
0xe9 SecurityServicesReply
0xea unknown, registered in ProxCardMifareClassic::ProxCardMifareClassic(RFIDReader&)
0xed Log first byte specifies log level (1=INFO, 2=WARNING, 3=ERROR) followed by null-terminated human-readable log message

→ - Host to Reader
← - Reader to Host

0xD5 UltralightReply — unsolicited card events

replyTo = 0 for both forms.

byte 0 meaning payload
0x00 Card entered field byte 1 = 0x00 (purpose unknown); bytes 2–8 = 7-byte UID (cascade / double UID format, e.g. 04 21 B9 01 10 04 03 for NTAG 215)
0x01 Card left field byte 1 = 0x00

0xD4 UltralightCommand / 0xD5 UltralightReply — subcommands

Host sends 0xD4 with msgData[0] = subcmd. Reader replies with 0xD5 where replyTo = seq of the 0xD4.

Reply format: [subcmd_echo, status, payload…]

Status codes: 0x00 = success, 0x01 = operation error (e.g. no card in field), 0x02 = invalid parameters.

subcmd name request payload (after subcmd byte) reply payload (after subcmd + status)
0x02 Read [start_page: u8, num_pages: u8] — exactly 3 bytes total page data, 4 bytes per page (e.g. 135 pages × 4 = 540 bytes for NTAG 215)
0x03 Write [page_addr: u8, data: 4 bytes] — 5 bytes total empty
0x04 Authenticate (raw key) [key: 16 bytes] — 17 bytes total empty
0x05 Authenticate (key index) [key_idx: BE u16] — 3 bytes total empty
0x06 Authenticate (index + key) [key_idx: BE u16, key: 16 bytes] — 19 bytes total empty
0x07 Abort none — exactly 1 byte total empty
0x00–0x01, 0x08+ (invalid) no reply frame; device logs “Unknown subcommand”

Notes:

  • Subcmds 0x02–0x07 are registered in ProxCardUltralightC::ProxCardUltralightC(RFIDReader&) (confirmed by disassembly of feig ARM binary from V0200.C72 firmware).
  • If no card is in the field, the device echoes [subcmd, 0x01] immediately without attempting the operation.
  • To read all pages of an NTAG 215: send subcmd 0x02 with start_page=0x00, num_pages=135.

Sample reader flow

  1. Host enables desired card type with ProxCardFunction.
  2. Reader acknowledges with ProxCardFunctionReply.
  3. Reader waits for card, seems like it eventually goes to sleep without any stimuli. Might have to keep it awake by sending packets occasionally (e.g. Status).
  4. When card is scanned, reader sends the corresponding read packet if the card type is enabled (e.g. DESFireRead for DESFire). If type is not enabled, sends UnhandledCard.
  5. Card data can then be queried by sending the equivalent command packets. DESFire commands are documented in m075031_desfire.pdf. Example for reading a page off a DESFire ( all of the following packets are of type DESFireCommand and DESFireCommandReply):
  6. Host sets DESFire application (e.g. packet type DESFireCommand with body 0x5AF210E0 for application ID 0xE010F2). NOTE: The application ID will vary between issuer/agency. For example, the stock software uses application ID F9C32B while Portland's TriMet uses the one in the example. You can check this for your card with an NFC reader or app.
  7. Reader responds with a status code in accordance with the documentation. This comes in the form of a DESFireCommandReply packet.
  8. Host sends read command. To read the full contents of file 0x00, the body is 0xBD00000000000000.
  9. Reader responds with the status code and file data.

Reader -> Host

packet type 0xED seems to be a log message:

\xBC\xB9\0\xED\0h\xB6\x0306:48:13 Error: feclr_transceive() failed: error: 0, status: 0x00000008 

0xB9 - card read?

Host -> reader

0xBC - appears after every read (ack of some sort?)

Card read flow

R: BCB600B9000BBB0C000000048A6EF29B149079CB8CF0
H: BC2200BC000158602A714F60           # DESFire GetVersion (60)
R: BCB722BD001E3D00000401013300160504010103001605048A6EF29B149020487330305022C98C7E9B
H: BC2300BC0004AA5A2BC3F911A8A56A     # DESFire SelectApplication (5A 2BC3F9)
R: BCB823BD0002E301FDE0523164

Another one (same payloads as previous but with different seq/hdrCRC):

R:  BCBF00B9000B480C000000048A6EF29B149079CB8CF0
H:  BC2400BC0001C4602A714F60
R:  BCC024BD001E0800000401013300160504010103001605048A6EF29B149020487330305022C98C7E9B
H:  BC2500BC0004365A2BC3F911A8A56A
R:  BCC125BD00027401FDE0523164

Sample log message:

BCB000ED0068450330363A34383A3038204572726F723A206665636C725F7472616E7363656976652829206661696C65643A206572726F723A20302C207374617475733A2030783030303030303038200A205B524649445265616465724D756C74694170702E6370703A3135395D0002CD1533BCB100ED0046B40130363A34383A3038207472616E7363656976652065
00000000  bc b0 00 ed 00 68 45 03 30 36 3a 34 38 3a 30 38  |¼°.í.hE.06:48:08|
00000010  20 45 72 72 6f 72 3a 20 66 65 63 6c 72 5f 74 72  | Error: feclr_tr|
00000020  61 6e 73 63 65 69 76 65 28 29 20 66 61 69 6c 65  |ansceive() faile|
00000030  64 3a 20 65 72 72 6f 72 3a 20 30 2c 20 73 74 61  |d: error: 0, sta|
00000040  74 75 73 3a 20 30 78 30 30 30 30 30 30 30 38 20  |tus: 0x00000008 |
00000050  0a 20 5b 52 46 49 44 52 65 61 64 65 72 4d 75 6c  |. [RFIDReaderMul|
00000060  74 69 41 70 70 2e 63 70 70 3a 31 35 39 5d 00 02  |tiApp.cpp:159]..|
00000070  cd 15 33 bc b1 00 ed 00 46 b4 01 30 36 3a 34 38  |Í.3¼±.í.F´.06:48|
00000080  3a 30 38 20 74 72 61 6e 73 63 65 69 76 65 20 65  |:08 transceive e|

There are 4 bytes appended at the end of some messages (presumably those with ID LSB=1, if the Rust struct description is accurate?) which is not a CRC32 with any polynomial I recognize, but a CRC32 of (message + CRC) is always 0xFFFFFFFF. These 4 bytes are NOT included in the length.

The checksum is _appended_, as can be seen by it following the log message (after the final \0).

is it a _little endian_, _bit negated_ CRC32. Which is weird because the length is big endian?..

* Host only sends two types of messages after initialization - both of type 0xBC. One is length 1 and the body is always 0x60 (+ negated CRC 2A714F60), the other is of length 4 and the body is always 5A2BC3F9 (+ negated-CRC 11A8A56A)

cvend.1779619159.txt.gz · Last modified: by doof