cancel
Showing results for 
Search instead for 
Did you mean: 

ST25DV Mailbox: exact RF sequence to Enable + Write + Read (Android NfcV) — code and logs included

Additional Details: I am attaching the files where i have the impletation code.
Please first refer this post where I sent all the details about this: 
https://community.st.com/t5/st25-nfc-rfid-tags-and-readers/how-to-enable-the-mailbox-and-perform-write-read-on-st25dv-via/td-p/852648

import android.nfc.Tag
import android.nfc.tech.NfcV
import java.io.IOException
import kotlin.experimental.or

class NfcMailboxManager(private val logCallback: (String) -> Unit) {

    data class OperationResult(val success: Boolean, val summary: String, val detail: String = "")
    data class MailboxStatus(val summary: String, val mbCtrl: Int, val mbLen: Int)
    data class ReadResult(val payload: ByteArray?, val summary: String) {
        override fun equals(other: Any?): Boolean {
            if (this === other) return true
            if (javaClass != other?.javaClass) return false

            other as ReadResult

            if (!payload.contentEquals(other.payload)) return false
            if (summary != other.summary) return false

            return true
        }

        override fun hashCode(): Int {
            var result = payload?.contentHashCode() ?: 0
            result = 31 * result + summary.hashCode()
            return result
        }
    }

    private fun log(s: String) = logCallback("[NfcMailboxManager] $s")

    // ST opcodes (datasheet)
    companion object {
        const val FLAGS_DEFAULT: Byte = 0x02
        const val OPC_READ_CONFIG: Byte = 0xA0.toByte()
        const val OPC_WRITE_CONFIG: Byte = 0xA1.toByte()
        const val OPC_WRITE_MSG: Byte = 0xAA.toByte()
        const val OPC_READ_MSG_LEN: Byte = 0xAB.toByte()
        const val OPC_READ_MSG: Byte = 0xAC.toByte()
        const val OPC_READ_DYN: Byte = 0xAD.toByte()
        const val OPC_WRITE_DYN: Byte = 0xAE.toByte()
        const val OPC_PRESENT_PWD: Byte = 0xB3.toByte()

        // Pointers / register ptrs (RF view)
        const val PTR_STATIC_FTM = 0x0D
        const val PTR_MB_CTRL_DYN = 0x0D

        private const val FTM_MB_MODE_MASK = 0x01  // bit1

    }

    // ------- High level ops -------

    fun enableMailbox(
        tag: Tag,
        presentPwdIfNeeded: Boolean = true,
        pwdNumber: Int = 0,          // must be 0 for configuration
        pwd: ByteArray? = null
    ): OperationResult {
        val nfcv = NfcV.get(tag) ?: return OperationResult(false, "NfcV not supported on tag")
        try {
            nfcv.connect()
            log("Connected for enableMailbox")

            // 1) Read static FTM (0x0D) and ensure MB_MODE=1 or MB_MODE must be bit1 (0x02)
            var ftm = tryReadConfiguration(nfcv, PTR_STATIC_FTM, tag.id)
            log("FTM static reg (0x0D) = 0x${ftm.toString(16)}")

            if ((ftm and FTM_MB_MODE_MASK) == 0) {
                if (presentPwdIfNeeded && pwd != null) {
                    // Always use CONFIG password #0 for FTM writes
                    val okPwd = presentPasswordWithOpenSession(
                        nfcv,
                        tag.id,
                        0,
                        pwd
                    )// CONFIG present password #0
                    log("PresentPassword(CONFIG#0) result: $okPwd")
                    if (!okPwd) return OperationResult(false, "PresentPassword failed (CONFIG#0)")
                }
                val newFtm = (ftm or FTM_MB_MODE_MASK).toByte()
                if (!writeConfiguration(nfcv, PTR_STATIC_FTM, newFtm, tag.id))
                    return OperationResult(false, "Failed to set MB_MODE (Write Configuration)")
                // Re-read to confirm
                ftm = tryReadConfiguration(nfcv, PTR_STATIC_FTM, tag.id)
                if ((ftm and FTM_MB_MODE_MASK) == 0) return OperationResult(
                    false,
                    "MB_MODE still 0 after write (check password/VCC)"
                )
                log("MB_MODE set to 1 in static FTM")
            } else {
                log("MB_MODE already 1 (FTM=0x${ftm.toString(16)})")
            }
            // 2) Set MB_EN in MB_CTRL_Dyn (0x0D)
            var mbCtrl = tryReadDynamic(nfcv, PTR_MB_CTRL_DYN, tag.id)
            val newMbCtrl = (mbCtrl or 0x01).toByte()
            if (!writeDynamic(nfcv, PTR_MB_CTRL_DYN, newMbCtrl, tag.id))
                return OperationResult(false, "Failed to set MB_EN (Write Dynamic)")
            // Re-read to confirm
            mbCtrl = tryReadDynamic(nfcv, PTR_MB_CTRL_DYN, tag.id)
            val mbLen = tryReadMessageLength(nfcv, tag.id)
            if ((mbCtrl and 0x01) == 0) {
                // force an addressed write once
                val req = ByteArray(1 + 1 + tag.id.size + 2)
                req[0] = (FLAGS_DEFAULT or 0x20)      // addressed
                req[1] = OPC_WRITE_DYN
                System.arraycopy(tag.id, 0, req, 2, tag.id.size)
                req[2 + tag.id.size] = PTR_MB_CTRL_DYN.toByte()
                req[3 + tag.id.size] = newMbCtrl
                val r = tryTransceive(nfcv, req)
                if (r == null) return OperationResult(false, "MB_EN write (addressed retry) failed")

                mbCtrl = tryReadDynamic(nfcv, PTR_MB_CTRL_DYN, tag.id)
            }
            val ok = (ftm and FTM_MB_MODE_MASK) != 0 && (mbCtrl and 0x01) != 0
            //val sum = "FTM(MB_MODE)=${(ftm and 0x01)!=0}, MB_CTRL=0x${mbCtrl.toString(16)}, MB_LEN=$mbLen"
            return OperationResult(
                ok,
                if (ok) "Mailbox enabled. FTM=0x${ftm.toString(16)} MB_CTRL=0x${mbCtrl.toString(16)}" else "Enable incomplete. FTM=0x${
                    ftm.toString(16)
                } MB_CTRL=0x${mbCtrl.toString(16)}"
            )
        } catch (ex: Exception) {
            return OperationResult(false, "Exception in enableMailbox", ex.message ?: "")
        } finally {
            try {
                nfcv.close()
            } catch (_: Exception) {
            }
        }
    }


    fun disableMailbox(tag: Tag): OperationResult {
        val nfcv = NfcV.get(tag) ?: return OperationResult(false, "NfcV not supported")
        try {
            nfcv.connect()
            val ok = writeDynamic(nfcv, PTR_MB_CTRL_DYN, 0x00, tag.id)
            return OperationResult(ok, if (ok) "MB_EN cleared" else "Failed to clear MB_EN")
        } catch (e: Exception) {
            return OperationResult(false, "Exception", e.message ?: "")
        } finally {
            try {
                nfcv.close()
            } catch (_: Exception) {
            }
        }
    }

    fun readMailboxStatus(tag: Tag): MailboxStatus {
        val nfcv = NfcV.get(tag) ?: throw IOException("NfcV not supported")
        try {
            nfcv.connect()
            val mbCtrl = tryReadDynamic(nfcv, PTR_MB_CTRL_DYN, tag.id)
            val mbLen = tryReadMessageLength(nfcv, tag.id)
            return MailboxStatus("MB_CTRL=0x${mbCtrl.toString(16)} MB_LEN=$mbLen", mbCtrl, mbLen)
        } finally {
            try {
                nfcv.close()
            } catch (_: Exception) {
            }
        }
    }

    fun writeMessage(tag: Tag, payload: ByteArray): OperationResult {
        val nfcv = NfcV.get(tag) ?: return OperationResult(false, "NfcV not supported")
        try {
            nfcv.connect()
            val mbCtrl = tryReadDynamic(nfcv, PTR_MB_CTRL_DYN, tag.id)
            if ((mbCtrl and 0x01) == 0) return OperationResult(false, "Mailbox disabled (MB_EN=0)")
            // ensure mailbox free (no HOST_PUT_MSG)
            if ((mbCtrl and 0x02) != 0) return OperationResult(
                false,
                "Mailbox not free: HOST_PUT_MSG=1"
            )
            val len = payload.size
            if (len < 1 || len > 256) return OperationResult(false, "Invalid payload size")
            val request = ByteArray(1 + 1 + 1 + len)
            request[0] = FLAGS_DEFAULT
            request[1] = OPC_WRITE_MSG
            request[2] = (len - 1).toByte()
            System.arraycopy(payload, 0, request, 3, len)
            val resp = transceiveWithUidFallback(nfcv, request, tag.id)
            return OperationResult(resp != null, if (resp != null) "Write OK" else "Write failed")
        } catch (e: Exception) {
            return OperationResult(false, "Exception", e.message ?: "")
        } finally {
            try {
                nfcv.close()
            } catch (_: Exception) {
            }
        }
    }

    fun readMessage(tag: Tag): ReadResult {
        val nfcv = NfcV.get(tag) ?: return ReadResult(null, "NfcV not supported")
        try {
            nfcv.connect()
            val mbLen = tryReadMessageLength(nfcv, tag.id)
            if (mbLen == 0) return ReadResult(ByteArray(0), "Mailbox empty")
            val request = byteArrayOf(FLAGS_DEFAULT, OPC_READ_MSG, 0x00, 0x00)
            val resp = transceiveWithUidFallback(nfcv, request, tag.id) ?: return ReadResult(
                null,
                "No response"
            )
            return ReadResult(resp, "OK")
        } catch (e: Exception) {
            return ReadResult(null, "Exception ${e.message}")
        } finally {
            try {
                nfcv.close()
            } catch (_: Exception) {
            }
        }
    }

    fun resetMailbox(tag: Tag): OperationResult {
        return disableMailbox(tag)
    }

    // ------- Low-level helpers -------

    private fun tryTransceive(nfcv: NfcV, data: ByteArray): ByteArray? {
        return try {
            log("REQ: ${toHex(data)}")
            val r = nfcv.transceive(data)
            log("RSP: ${r?.let { toHex(it) } ?: "null"}")
            r
        } catch (e: Exception) {
            log("transceive failed: ${e.message}")
            null
        }
    }

    private fun transceiveWithUidFallback(nfcv: NfcV, baseRequest: ByteArray, uid: ByteArray?): ByteArray? {
        var resp = tryTransceive(nfcv, baseRequest)
        if (resp != null) return resp
        if (uid == null || uid.isEmpty()) return null

        val flags = baseRequest[0]
        val opcode = baseRequest[1]
        val rest  = if (baseRequest.size > 2) baseRequest.copyOfRange(2, baseRequest.size) else ByteArray(0)

        // A) opcode + UID (same flags)
        run {
            val req = ByteArray(1 + 1 + uid.size + rest.size)
            req[0] = flags
            req[1] = opcode
            System.arraycopy(uid, 0, req, 2, uid.size)
            if (rest.isNotEmpty()) System.arraycopy(rest, 0, req, 2 + uid.size, rest.size)
            resp = tryTransceive(nfcv, req); if (resp != null) return resp
        }

        // B) UID + opcode (same flags)
        run {
            val req = ByteArray(1 + uid.size + (baseRequest.size - 1))
            req[0] = flags
            System.arraycopy(uid, 0, req, 1, uid.size)
            System.arraycopy(baseRequest, 1, req, 1 + uid.size, baseRequest.size - 1)
            resp = tryTransceive(nfcv, req); if (resp != null) return resp
        }

        // C) opcode + UID with **addressed** flag (0x20)
        run {
            val req = ByteArray(1 + 1 + uid.size + rest.size)
            req[0] = (flags or 0x20)    // 0x22 typical
            req[1] = opcode
            System.arraycopy(uid, 0, req, 2, uid.size)
            if (rest.isNotEmpty()) System.arraycopy(rest, 0, req, 2 + uid.size, rest.size)
            resp = tryTransceive(nfcv, req); if (resp != null) return resp
        }

        // D) UID + opcode with **addressed** flag
        run {
            val req = ByteArray(1 + uid.size + (baseRequest.size - 1))
            req[0] = (flags or 0x20)
            System.arraycopy(uid, 0, req, 1, uid.size)
            System.arraycopy(baseRequest, 1, req, 1 + uid.size, baseRequest.size - 1)
            resp = tryTransceive(nfcv, req); if (resp != null) return resp
        }

        return null
    }


    private fun tryReadConfiguration(nfcv: NfcV, addr: Int, uid: ByteArray?): Int {
        val req = byteArrayOf(FLAGS_DEFAULT, OPC_READ_CONFIG, addr.toByte())
        val r = transceiveWithUidFallback(nfcv, req, uid)
            ?: throw IOException("No response to ReadConfiguration")
        if (r.size < 2) throw IOException("Bad ReadConfiguration response: ${toHex(r)}")
        // r[0]=flags, r[1]=data
        return r[1].toInt() and 0xFF
    }

    private fun writeConfiguration(nfcv: NfcV, addr: Int, value: Byte, uid: ByteArray?): Boolean {
        val req = byteArrayOf(FLAGS_DEFAULT, OPC_WRITE_CONFIG, addr.toByte(), value)
        val r = transceiveWithUidFallback(nfcv, req, uid)
        return r != null
    }

    private fun tryReadDynamic(nfcv: NfcV, ptr: Int, uid: ByteArray?): Int {
        val req = byteArrayOf(FLAGS_DEFAULT, OPC_READ_DYN, ptr.toByte())
        val r = transceiveWithUidFallback(nfcv, req, uid)
            ?: throw IOException("No response to ReadDynamic")
        if (r.size < 2) throw IOException("Bad ReadDynamic response: ${toHex(r)}")
        return r[1].toInt() and 0xFF
    }

    private fun writeDynamic(nfcv: NfcV, ptr: Int, value: Byte, uid: ByteArray?): Boolean {
        val req = byteArrayOf(FLAGS_DEFAULT, OPC_WRITE_DYN, ptr.toByte(), value)
        val r = transceiveWithUidFallback(nfcv, req, uid)
        return r != null
    }

    private fun tryReadMessageLength(nfcv: NfcV, uid: ByteArray?): Int {
        val req = byteArrayOf(FLAGS_DEFAULT, OPC_READ_MSG_LEN)
        val r = transceiveWithUidFallback(nfcv, req, uid) ?: return 0
        if (r.size < 2) return 0
        return r[1].toInt() and 0xFF
    }

    /**
     * Present RF password (B3h).
     * pwdNumber: 0..3
     * pwd: 8-byte password
     */
    fun presentPassword(tag: Tag, pwdNumber: Int, pwd: ByteArray): Boolean {
        val nfcv = NfcV.get(tag) ?: return false
        try {
            nfcv.connect()
            if (pwd.size != 8) {
                log("presentPassword: password length must be 8")
                return false
            }
            val base = ByteArray(1 + 1 + 1 + 8)
            base[0] = FLAGS_DEFAULT
            base[1] = OPC_PRESENT_PWD
            base[2] = pwdNumber.toByte()
            System.arraycopy(pwd, 0, base, 3, 8)
            val resp = transceiveWithUidFallback(nfcv, base, tag.id)
            return resp != null
        } catch (e: Exception) {
            log("presentPassword exception: ${e.message}")
            return false
        } finally {
            try {
                nfcv.close()
            } catch (_: Exception) {
            }
        }
    }

    private fun toHex(b: ByteArray) = b.joinToString(" ") { "%02X".format(it) }

    private fun presentPasswordWithOpenSession(
        nfcv: NfcV,
        uid: ByteArray?,
        pwdNumber: Int,
        pwd: ByteArray
    ): Boolean {
        if (pwd.size != 8) {
            log("presentPassword: password length must be 8")
            return false
        }
        val base = ByteArray(1 + 1 + 1 + 8)
        base[0] = FLAGS_DEFAULT
        base[1] = OPC_PRESENT_PWD
        base[2] = pwdNumber.toByte()     // must be 0 for config
        System.arraycopy(pwd, 0, base, 3, 8)
        val resp = transceiveWithUidFallback(nfcv, base, uid)
        return resp != null
    }

}

 

0 REPLIES 0