Alexander Krämer
Alexander Krämer

Reputation: 396

NfcA.transceive(byte[] data) throws TagLostException

It all started here. The problem over there was solved, now I'm facing problems with using the transceive method.

My code looks like this:

private void writeAndProtectTag(final Intent intent, final String message) {
    // Run the entire process in its own thread as NfcA.transceive(byte[] data);
    // Should not be run in main thread according to <https://developer.android.com/reference/android/nfc/tech/NfcA.html#transceive(byte[])>
    (new Thread(new Runnable() {
        // Password has to be 4 characters
        // Password Acknowledge has to be 2 characters
        byte[] pwd      = "-_bA".getBytes();
        byte[] pack     = "cC".getBytes();

        @Override
        public void run() {
            // Store tag object for use in NfcA and Ndef
            Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
            // Using NfcA instead of MifareUltralight should make no difference in this method
            NfcA nfca = null;

            // Whole process is put into a big try-catch trying to catch the transceive's IOException
            try {
                nfca = NfcA.get(tag);

                nfca.connect();

                byte[] response;

                // Authenticate with the tag first
                // In case it's already been locked
                try {
                    response = nfca.transceive(new byte[]{
                            (byte) 0x1B, // PWD_AUTH
                            pwd[0], pwd[1], pwd[2], pwd[3]
                    });

                    // Check if PACK is matching expected PACK
                    // This is a (not that) secure method to check if tag is genuine
                    if ((response != null) && (response.length >= 2)) {
                        byte[] packResponse = Arrays.copyOf(response, 2);
                        if (!(pack[0] == packResponse[0] && pack[1] == packResponse[1])) {
                            Toast.makeText(ctx, "Tag could not be authenticated:\n" + packResponse.toString() + "≠" + pack.toString(), Toast.LENGTH_LONG).show();
                        }
                    }
                }catch(TagLostException e){
                    e.printStackTrace();
                }

                // Get Page 2Ah
                response = nfca.transceive(new byte[] {
                        (byte) 0x30, // READ
                        (byte) 0x2A  // page address
                });
                // configure tag as write-protected with unlimited authentication tries
                if ((response != null) && (response.length >= 16)) {    // read always returns 4 pages
                    boolean prot = false;                               // false = PWD_AUTH for write only, true = PWD_AUTH for read and write
                    int authlim = 0;                                    // 0 = unlimited tries
                    nfca.transceive(new byte[] {
                            (byte) 0xA2, // WRITE
                            (byte) 0x2A, // page address
                            (byte) ((response[0] & 0x078) | (prot ? 0x080 : 0x000) | (authlim & 0x007)),    // set ACCESS byte according to our settings
                            0, 0, 0                                                                         // fill rest as zeros as stated in datasheet (RFUI must be set as 0b)
                    });
                }
                // Get page 29h
                response = nfca.transceive(new byte[] {
                        (byte) 0x30, // READ
                        (byte) 0x29  // page address
                });
                // Configure tag to protect entire storage (page 0 and above)
                if ((response != null) && (response.length >= 16)) {  // read always returns 4 pages
                    int auth0 = 0;                                    // first page to be protected
                    nfca.transceive(new byte[] {
                            (byte) 0xA2, // WRITE
                            (byte) 0x29, // page address
                            response[0], 0, response[2],              // Keep old mirror values and write 0 in RFUI byte as stated in datasheet
                            (byte) (auth0 & 0x0ff)
                    });
                }

                // Send PACK and PWD
                // set PACK:
                nfca.transceive(new byte[] {
                        (byte)0xA2,
                        (byte)0x2C,
                        pack[0], pack[1], 0, 0  // Write PACK into first 2 Bytes and 0 in RFUI bytes
                });
                // set PWD:
                nfca.transceive(new byte[] {
                        (byte)0xA2,
                        (byte)0x2B,
                        pwd[0], pwd[1], pwd[2], pwd[3] // Write all 4 PWD bytes into Page 43
                });

                // Generate NdefMessage to be written onto the tag
                NdefMessage msg = null;
                try {
                    NdefRecord r1 = NdefRecord.createMime("text/plain", message.getBytes("UTF-8"));
                    NdefRecord r2 = NdefRecord.createApplicationRecord("com.example.alex.nfcapppcekunde");
                    msg = new NdefMessage(r1, r2);
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }

                byte[] ndefMessage = msg.toByteArray();

                nfca.transceive(new byte[] {
                        (byte)0xA2, // WRITE
                        (byte)3,    // block address
                        (byte)0xE1, (byte)0x10, (byte)0x12, (byte)0x00
                });

                // wrap into TLV structure
                byte[] tlvEncodedData = null;

                tlvEncodedData = new byte[ndefMessage.length + 3];
                tlvEncodedData[0] = (byte)0x03;  // NDEF TLV tag
                tlvEncodedData[1] = (byte)(ndefMessage.length & 0x0FF);  // NDEF TLV length (1 byte)
                System.arraycopy(ndefMessage, 0, tlvEncodedData, 2, ndefMessage.length);
                tlvEncodedData[2 + ndefMessage.length] = (byte)0xFE;  // Terminator TLV tag

                // fill up with zeros to block boundary:
                tlvEncodedData = Arrays.copyOf(tlvEncodedData, (tlvEncodedData.length / 4 + 1) * 4);
                for (int i = 0; i < tlvEncodedData.length; i += 4) {
                    byte[] command = new byte[] {
                            (byte)0xA2, // WRITE
                            (byte)((4 + i / 4) & 0x0FF), // block address
                            0, 0, 0, 0
                    };
                    System.arraycopy(tlvEncodedData, i, command, 2, 4);
                    try {
                        response = nfca.transceive(command);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        //UI related things, not important for NFC
                        btn.setImageResource(R.drawable.arrow_red);
                        tv.setText("");
                    }
                });
                curAction = "handle";

                try {
                    nfca.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            } catch (IOException e) {
                //Trying to catch any ioexception that may be thrown
                e.printStackTrace();
            } catch (Exception e) {
                //Trying to catch any exception that may be thrown
                e.printStackTrace();
            }

        }
    })).start();
}

In my old code version that used Ndef class to write onto the tag I didn't have this problem. Now that I changed a few things I am getting two TagLostExceptions:

After the second TagLostException the method ends of course. When reading the configuration I should not get a TagLostException. Does anyone know what is going on there?

Here's the exceptions:

    android.nfc.TagLostException: Tag was lost.
                             at android.nfc.TransceiveResult.getResponseOrThrow(TransceiveResult.java:48)
                             at android.nfc.tech.BasicTagTechnology.transceive(BasicTagTechnology.java:151)
                             at android.nfc.tech.NfcA.transceive(NfcA.java:129)
                             at com.example.alex.nfcapppce.MainActivity$2.run(MainActivity.java:134)
                             at java.lang.Thread.run(Thread.java:761)
    android.nfc.TagLostException: Tag was lost.
                             at android.nfc.TransceiveResult.getResponseOrThrow(TransceiveResult.java:48)
                             at android.nfc.tech.BasicTagTechnology.transceive(BasicTagTechnology.java:151)
                             at android.nfc.tech.NfcA.transceive(NfcA.java:129)
                             at com.example.alex.nfcapppce.MainActivity$2.run(MainActivity.java:155)
                             at java.lang.Thread.run(Thread.java:761)

Upvotes: 2

Views: 3819

Answers (2)

Michael Roland
Michael Roland

Reputation: 40869

You try to authenticate to the tag before you read any data. Depending on the specific Android device that you use, if authentication fails, you will either receive a NACK response or an IOException (typically, but not always(?), a TagLostException). In both cases you currently ignore this error (you drop the exception and you only process successful results ((response != null) && (response.length >= 2)). Consequently, your tag is (and remains) in error state when authentication failed.

Therefore, you need to properly handle these error conditions. Typically, you would want to close and re-open the connection upon every error (NACK response, null response, and IOException). You could easily implement this in your code:

nfca.connect();

byte[] response;
boolean authError = true;

// Authenticate with the tag first
try {
    response = nfca.transceive(new byte[]{
            (byte) 0x1B, // PWD_AUTH
            pwd[0], pwd[1], pwd[2], pwd[3]
    });

    // Check if PACK is matching expected PACK
    if ((response != null) && (response.length >= 2)) {
        authError = false;

        [...]
    }
} catch (IOException e) {
    e.printStackTrace();
}

if (authError) {
    try {
        nfca.close();
    } catch (Exception ignored) {}
    nfca.connect();
}

// Get Page 2Ah
response = nfca.transceive(new byte[] {
        (byte) 0x30, // READ
        (byte) 0x2A  // page address
});

[...]

Upvotes: 1

Alexander Kr&#228;mer
Alexander Kr&#228;mer

Reputation: 396

SOLVED:

When trying to authenticate with a tag that does not have a protection yet the communication just crashes instead of throwing a proper Exception to catch, because I am not catching an exception the application continues trying to transceive data, but the tag "was lost" as the now called Exception is telling me.

To know if the tag is already protected, check Auth0 for set protection:

byte[] response;

//Read page 41 on NTAG213, will be different for other tags
response = mifare.transceive(new byte[] {
        (byte) 0x30, // READ
        41           // page address
});
// Authenticate with the tag first
// only if the Auth0 byte is not 0xFF,
// which is the default value meaning unprotected
if(response[3] != (byte)0xFF) {
    try {
        response = mifare.transceive(new byte[]{
                (byte) 0x1B, // PWD_AUTH
                pwd[0], pwd[1], pwd[2], pwd[3]
        });
        // Check if PACK is matching expected PACK
        // This is a (not that) secure method to check if tag is genuine
        if ((response != null) && (response.length >= 2)) {
            final byte[] packResponse = Arrays.copyOf(response, 2);
            if (!(pack[0] == packResponse[0] && pack[1] == packResponse[1])) {runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(ctx, "Tag could not be authenticated:\n" + packResponse.toString() + "≠" + pack.toString(), Toast.LENGTH_LONG).show();
                }
            });
            }else{
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(ctx, "Tag successfully authenticated!", Toast.LENGTH_SHORT).show();
                    }
                });
            }
        }
    } catch (TagLostException e) {
        e.printStackTrace();
    }
}else{
    // Protect tag with your password in case
    // it's not protected yet

    // Get Page 2Ah
    response = mifare.transceive(new byte[] {
            (byte) 0x30, // READ
            (byte) 0x2A  // page address
    });
    // configure tag as write-protected with unlimited authentication tries
    if ((response != null) && (response.length >= 16)) {    // read always returns 4 pages
        boolean prot = false;                               // false = PWD_AUTH for write only, true = PWD_AUTH for read and write
        int authlim = 0;                                    // 0 = unlimited tries
        mifare.transceive(new byte[] {
                (byte) 0xA2, // WRITE
                (byte) 0x2A, // page address
                (byte) ((response[0] & 0x078) | (prot ? 0x080 : 0x000) | (authlim & 0x007)),    // set ACCESS byte according to our settings
                0, 0, 0                                                                         // fill rest as zeros as stated in datasheet (RFUI must be set as 0b)
        });
    }
    // Get page 29h
    response = mifare.transceive(new byte[] {
            (byte) 0x30, // READ
            (byte) 0x29  // page address
    });
    // Configure tag to protect entire storage (page 0 and above)
    if ((response != null) && (response.length >= 16)) {  // read always returns 4 pages
        int auth0 = 0;                                    // first page to be protected
        mifare.transceive(new byte[] {
                (byte) 0xA2, // WRITE
                (byte) 0x29, // page address
                response[0], 0, response[2],              // Keep old mirror values and write 0 in RFUI byte as stated in datasheet
                (byte) (auth0 & 0x0ff)
        });
    }

    // Send PACK and PWD
    // set PACK:
    mifare.transceive(new byte[] {
            (byte)0xA2,
            (byte)0x2C,
            pack[0], pack[1], 0, 0  // Write PACK into first 2 Bytes and 0 in RFUI bytes
    });
    // set PWD:
    mifare.transceive(new byte[] {
            (byte)0xA2,
            (byte)0x2B,
            pwd[0], pwd[1], pwd[2], pwd[3] // Write all 4 PWD bytes into Page 43
    });
}

Upvotes: 1

Related Questions