11package core
22
33import (
4+ "encoding/base64"
45 "encoding/hex"
6+ "encoding/json"
57 "fmt"
68 "time"
79
810 "github.com/SimplyPrint/nfc-agent/internal/logging"
11+ "github.com/SimplyPrint/nfc-agent/internal/openprinttag"
912 "github.com/ebfe/scard"
1013)
1114
@@ -317,8 +320,19 @@ func WriteDataWithURL(readerName string, data []byte, dataType string, url strin
317320 ndefMessage = createNDEFMimeRecord ("application/octet-stream" , data )
318321 case "url" :
319322 ndefMessage = createNDEFURIRecord (string (data ))
323+ case "openprinttag" :
324+ // Parse JSON input and encode to CBOR
325+ var input openprinttag.Input
326+ if err := json .Unmarshal (data , & input ); err != nil {
327+ return fmt .Errorf ("invalid openprinttag JSON: %w" , err )
328+ }
329+ cborPayload , err := input .Encode ()
330+ if err != nil {
331+ return fmt .Errorf ("failed to encode openprinttag: %w" , err )
332+ }
333+ ndefMessage = createNDEFMimeRecord (openprinttag .MIMEType , cborPayload )
320334 default :
321- return fmt .Errorf ("unsupported data type: %s (use 'json', 'text', 'binary', or 'url ')" , dataType )
335+ return fmt .Errorf ("unsupported data type: %s (use 'json', 'text', 'binary', 'url', or 'openprinttag ')" , dataType )
322336 }
323337 }
324338
@@ -357,6 +371,9 @@ func createMultiRecordNDEF(url string, data []byte, dataType string) []byte {
357371 dataRecord = createNDEFRecordRaw (0x01 , []byte ("T" ), textPayload , false , true )
358372 case "binary" :
359373 dataRecord = createNDEFRecordRaw (0x02 , []byte ("application/octet-stream" ), data , false , true )
374+ case "openprinttag" :
375+ // Data is already CBOR-encoded at this point
376+ dataRecord = createNDEFRecordRaw (0x02 , []byte (openprinttag .MIMEType ), data , false , true )
360377 default :
361378 textPayload := []byte {0x02 }
362379 textPayload = append (textPayload , []byte ("en" )... )
@@ -821,6 +838,43 @@ func readNDEFData(card *scard.Card, cardInfo *Card) {
821838 }
822839 }
823840
841+ // Check if we have complete NDEF message
842+ if len (allData ) > 2 && allData [0 ] == 0x03 {
843+ var ndefLength , ndefStart int
844+ if allData [1 ] == 0xFF && len (allData ) >= 4 {
845+ ndefLength = int (allData [2 ])<< 8 | int (allData [3 ])
846+ ndefStart = 4
847+ } else if allData [1 ] != 0xFF {
848+ ndefLength = int (allData [1 ])
849+ ndefStart = 2
850+ }
851+ if ndefStart > 0 && len (allData ) >= ndefStart + ndefLength + 1 {
852+ break
853+ }
854+ }
855+ }
856+ } else if cardInfo .Type == "ISO 15693" {
857+ // ISO 15693 (Type 5) tags: NDEF starts at block 1 (after CC at block 0)
858+ maxBlocks := 79 // 80 blocks total, skip CC at block 0
859+ for blockNum := 1 ; blockNum < 1 + maxBlocks ; blockNum ++ {
860+ blockData , err := readNTAGPage (card , blockNum )
861+ if err != nil {
862+ logging .Debug (logging .CatCard , "NDEF read failed" , map [string ]any {
863+ "block" : blockNum ,
864+ "error" : err .Error (),
865+ })
866+ break
867+ }
868+
869+ allData = append (allData , blockData ... )
870+
871+ // Check for NDEF terminator
872+ for _ , b := range blockData {
873+ if b == 0xFE {
874+ goto done
875+ }
876+ }
877+
824878 // Check if we have complete NDEF message
825879 if len (allData ) > 2 && allData [0 ] == 0x03 {
826880 var ndefLength , ndefStart int
@@ -981,6 +1035,19 @@ done:
9811035 if mimeType == "application/json" {
9821036 cardInfo .Data = string (payload )
9831037 cardInfo .DataType = "json"
1038+ } else if mimeType == openprinttag .MIMEType {
1039+ // OpenPrintTag format (application/vnd.openprinttag)
1040+ opt , err := openprinttag .Decode (payload )
1041+ if err == nil {
1042+ resp := opt .ToResponse ()
1043+ jsonData , _ := json .Marshal (resp )
1044+ cardInfo .Data = string (jsonData )
1045+ cardInfo .DataType = "openprinttag"
1046+ } else {
1047+ // Fallback to binary if CBOR decode fails
1048+ cardInfo .Data = hex .EncodeToString (payload )
1049+ cardInfo .DataType = "binary"
1050+ }
9841051 } else if mimeType == "application/octet-stream" {
9851052 cardInfo .Data = hex .EncodeToString (payload )
9861053 cardInfo .DataType = "binary"
@@ -1324,8 +1391,10 @@ func RemovePassword(readerName string, password []byte) error {
13241391
13251392// WriteMultipleRecords writes multiple NDEF records to a card
13261393type NDEFRecord struct {
1327- Type string `json:"type"` // "url", "text", "json", "binary"
1328- Data string `json:"data"` // Data content (base64 for binary)
1394+ Type string `json:"type"` // "url", "text", "json", "binary", "mime"
1395+ Data string `json:"data"` // Data content
1396+ MimeType string `json:"mimeType,omitempty"` // For generic mime records (e.g., "application/vnd.openprinttag")
1397+ DataType string `json:"dataType,omitempty"` // "binary" for base64-encoded data
13291398}
13301399
13311400func WriteMultipleRecords (readerName string , records []NDEFRecord ) error {
@@ -1345,6 +1414,14 @@ func WriteMultipleRecords(readerName string, records []NDEFRecord) error {
13451414 }
13461415 defer card .Disconnect (scard .LeaveCard )
13471416
1417+ // Get ATR to detect card type
1418+ status , err := card .Status ()
1419+ if err != nil {
1420+ return fmt .Errorf ("failed to get card status: %w" , err )
1421+ }
1422+ atr := hex .EncodeToString (status .Atr )
1423+ isISO15693 := contains (atr , "03060b" )
1424+
13481425 // Build multi-record NDEF message
13491426 var ndefRecords []byte
13501427 for i , rec := range records {
@@ -1366,12 +1443,28 @@ func WriteMultipleRecords(readerName string, records []NDEFRecord) error {
13661443 case "json" :
13671444 recordBytes = createNDEFRecordRaw (0x02 , []byte ("application/json" ), []byte (rec .Data ), isFirst , isLast )
13681445 case "binary" :
1369- // Decode base64
1446+ // Decode hex
13701447 decoded , err := hex .DecodeString (rec .Data )
13711448 if err != nil {
13721449 return fmt .Errorf ("invalid binary data in record %d: %w" , i , err )
13731450 }
13741451 recordBytes = createNDEFRecordRaw (0x02 , []byte ("application/octet-stream" ), decoded , isFirst , isLast )
1452+ case "mime" :
1453+ if rec .MimeType == "" {
1454+ return fmt .Errorf ("mimeType required for mime record type in record %d" , i )
1455+ }
1456+ var payload []byte
1457+ if rec .DataType == "binary" {
1458+ // Data is base64 encoded
1459+ decoded , err := base64 .StdEncoding .DecodeString (rec .Data )
1460+ if err != nil {
1461+ return fmt .Errorf ("invalid base64 data in mime record %d: %w" , i , err )
1462+ }
1463+ payload = decoded
1464+ } else {
1465+ payload = []byte (rec .Data )
1466+ }
1467+ recordBytes = createNDEFRecordRaw (0x02 , []byte (rec .MimeType ), payload , isFirst , isLast )
13751468 default :
13761469 return fmt .Errorf ("unsupported record type: %s" , rec .Type )
13771470 }
@@ -1390,8 +1483,29 @@ func WriteMultipleRecords(readerName string, records []NDEFRecord) error {
13901483 tlv = append (tlv , ndefRecords ... )
13911484 tlv = append (tlv , 0xFE )
13921485
1393- if err := writeNTAGPages (card , 4 , tlv ); err != nil {
1394- return fmt .Errorf ("failed to write NDEF records: %w" , err )
1486+ if isISO15693 {
1487+ // ISO 15693 (Type 5) tags: CC at block 0, NDEF at block 1
1488+ // CC format: E1 [version/access] [size/8] [features]
1489+ // - 0xE1: Magic number
1490+ // - 0x40: Version 1.0 (4), read/write access (0)
1491+ // - Size: Available memory / 8 (we'll use 0x40 = 512 bytes, conservative)
1492+ // - 0x00: No special features
1493+ cc := []byte {0xE1 , 0x40 , 0x40 , 0x00 }
1494+
1495+ // Write CC at block 0
1496+ if err := writeNTAGPages (card , 0 , cc ); err != nil {
1497+ return fmt .Errorf ("failed to write CC block: %w" , err )
1498+ }
1499+
1500+ // Write NDEF TLV starting at block 1
1501+ if err := writeNTAGPages (card , 1 , tlv ); err != nil {
1502+ return fmt .Errorf ("failed to write NDEF records: %w" , err )
1503+ }
1504+ } else {
1505+ // NTAG (Type 2) tags: NDEF at page 4
1506+ if err := writeNTAGPages (card , 4 , tlv ); err != nil {
1507+ return fmt .Errorf ("failed to write NDEF records: %w" , err )
1508+ }
13951509 }
13961510
13971511 return nil
0 commit comments