mirror of
https://github.com/RfidResearchGroup/ChameleonUltra.git
synced 2026-09-11 18:30:14 -07:00
Merge branch 'main' into patch-iso14443-4-blockvals
This commit is contained in:
@@ -55,15 +55,7 @@ static nfc_tag_14a_4_information_t *m_tag_information = NULL;
|
|||||||
static nfc_tag_14a_coll_res_reference_t m_shadow_coll_res;
|
static nfc_tag_14a_coll_res_reference_t m_shadow_coll_res;
|
||||||
|
|
||||||
/* T=CL session state */
|
/* T=CL session state */
|
||||||
static uint8_t m_block_num = 0;
|
static nfc_tag_14a_4_tcl_state_t m_tcl_session_state;
|
||||||
static bool m_cid_supported = false;
|
|
||||||
static uint8_t m_cid = 0;
|
|
||||||
static uint8_t m_apdu_buf[NFC_14A_4_MAX_APDU];
|
|
||||||
static uint16_t m_apdu_len = 0;
|
|
||||||
static bool m_apdu_pending = false;
|
|
||||||
static uint8_t m_resp_buf[NFC_14A_4_MAX_APDU];
|
|
||||||
static uint16_t m_resp_len = 0;
|
|
||||||
static bool m_response_ready = false;
|
|
||||||
|
|
||||||
/* TX scratch buffer */
|
/* TX scratch buffer */
|
||||||
static uint8_t m_tx_buf[NFC_14A_4_MAX_APDU + 4];
|
static uint8_t m_tx_buf[NFC_14A_4_MAX_APDU + 4];
|
||||||
@@ -160,34 +152,35 @@ static bool find_static_response(const uint8_t *apdu, uint16_t apdu_len,
|
|||||||
/* TX helpers */
|
/* TX helpers */
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
static void send_iblock(const uint8_t *data, uint16_t len) {
|
static void send_iblock(nfc_tag_14a_4_tcl_state_t *m_tcl_session_state) {
|
||||||
uint8_t pcb = 0x02 | (m_block_num & 0x01);
|
uint16_t len = m_tcl_session_state->m_resp_len;
|
||||||
if (m_cid_supported) pcb |= PCB_CID_FOLLOWING;
|
uint8_t pcb = 0x02 | (m_tcl_session_state->m_block_num & 0x01);
|
||||||
|
if (m_tcl_session_state->m_cid_supported) pcb |= PCB_CID_FOLLOWING;
|
||||||
uint8_t off = 0;
|
uint8_t off = 0;
|
||||||
m_tx_buf[off++] = pcb;
|
m_tx_buf[off++] = pcb;
|
||||||
if (m_cid_supported) m_tx_buf[off++] = m_cid & 0x0F;
|
if (m_tcl_session_state->m_cid_supported) m_tx_buf[off++] = m_tcl_session_state->m_cid & 0x0F;
|
||||||
if (len > NFC_14A_4_MAX_APDU) len = NFC_14A_4_MAX_APDU;
|
if (len > NFC_14A_4_MAX_APDU) len = NFC_14A_4_MAX_APDU;
|
||||||
memcpy(&m_tx_buf[off], data, len);
|
memcpy(&m_tx_buf[off], m_tcl_session_state->m_resp_buf, len);
|
||||||
nfc_tag_14a_tx_bytes(m_tx_buf, off + len, true);
|
nfc_tag_14a_tx_bytes(m_tx_buf, off + len, true);
|
||||||
m_block_num ^= 1;
|
m_tcl_session_state->m_block_num ^= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void send_rack(void) {
|
static void send_rack(nfc_tag_14a_4_tcl_state_t *m_tcl_session_state) {
|
||||||
uint8_t pcb = PCB_RBLOCK_VAL | (m_block_num & 0x01);
|
uint8_t pcb = PCB_RBLOCK_VAL | (m_tcl_session_state->m_block_num & 0x01);
|
||||||
if (m_cid_supported) {
|
if (m_tcl_session_state->m_cid_supported) {
|
||||||
pcb |= PCB_CID_FOLLOWING;
|
pcb |= PCB_CID_FOLLOWING;
|
||||||
uint8_t buf[2] = { pcb, m_cid & 0x0F };
|
uint8_t buf[2] = { pcb, m_tcl_session_state->m_cid & 0x0F };
|
||||||
nfc_tag_14a_tx_bytes(buf, 2, true);
|
nfc_tag_14a_tx_bytes(buf, 2, true);
|
||||||
} else {
|
} else {
|
||||||
nfc_tag_14a_tx_bytes(&pcb, 1, true);
|
nfc_tag_14a_tx_bytes(&pcb, 1, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void send_wtx(void) {
|
static void send_wtx(nfc_tag_14a_4_tcl_state_t *m_tcl_session_state) {
|
||||||
uint8_t buf[3];
|
uint8_t buf[3];
|
||||||
uint8_t off = 0;
|
uint8_t off = 0;
|
||||||
buf[off++] = PCB_SBLOCK_WTX | (m_cid_supported ? PCB_CID_FOLLOWING : 0);
|
buf[off++] = PCB_SBLOCK_WTX | (m_tcl_session_state->m_cid_supported ? PCB_CID_FOLLOWING : 0);
|
||||||
if (m_cid_supported) buf[off++] = m_cid & 0x0F;
|
if (m_tcl_session_state->m_cid_supported) buf[off++] = m_tcl_session_state->m_cid & 0x0F;
|
||||||
buf[off++] = WTX_VALUE;
|
buf[off++] = WTX_VALUE;
|
||||||
nfc_tag_14a_tx_bytes(buf, off, true);
|
nfc_tag_14a_tx_bytes(buf, off, true);
|
||||||
}
|
}
|
||||||
@@ -196,8 +189,18 @@ static void send_wtx(void) {
|
|||||||
/* State handler (called from NFCT ISR on each received frame) */
|
/* State handler (called from NFCT ISR on each received frame) */
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
static void nfc_tag_14a_4_state_handler(uint8_t *data, uint16_t szBytes) {
|
void nfc_tag_14a_4_base_respond(nfc_tag_14a_4_tcl_state_t *m_tcl_session_state) {
|
||||||
if (szBytes == 0) return;
|
if (m_tcl_session_state->m_response_ready) {
|
||||||
|
m_tcl_session_state->m_response_ready = false;
|
||||||
|
send_iblock(m_tcl_session_state);
|
||||||
|
} else {
|
||||||
|
/* No response ready — keep reader alive with WTX */
|
||||||
|
send_wtx(m_tcl_session_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool nfc_tag_14a_4_base_handler(nfc_tag_14a_4_tcl_state_t *m_tcl_session_state, uint8_t *data, uint16_t szBytes) {
|
||||||
|
if (szBytes == 0) return false;
|
||||||
uint8_t pcb = data[0];
|
uint8_t pcb = data[0];
|
||||||
|
|
||||||
/* ---- S-block ---- */
|
/* ---- S-block ---- */
|
||||||
@@ -206,37 +209,37 @@ static void nfc_tag_14a_4_state_handler(uint8_t *data, uint16_t szBytes) {
|
|||||||
/* Echo DESELECT */
|
/* Echo DESELECT */
|
||||||
nfc_tag_14a_tx_bytes(data, szBytes, true);
|
nfc_tag_14a_tx_bytes(data, szBytes, true);
|
||||||
nfc_tag_14a_4_reset_handler();
|
nfc_tag_14a_4_reset_handler();
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
if ((pcb & 0x3F) == (PCB_SBLOCK_WTX & 0x3F)) {
|
if ((pcb & 0x3F) == (PCB_SBLOCK_WTX & 0x3F)) {
|
||||||
/* Reader sending WTX — echo back with our WTXM */
|
/* Reader sending WTX — echo back with our WTXM */
|
||||||
uint8_t wtxm = (szBytes > 1) ? data[szBytes - 1] & 0x3F : WTX_VALUE;
|
uint8_t wtxm = (szBytes > 1) ? data[szBytes - 1] & 0x3F : WTX_VALUE;
|
||||||
uint8_t resp[3];
|
uint8_t resp[3];
|
||||||
uint8_t off = 0;
|
uint8_t off = 0;
|
||||||
resp[off++] = PCB_SBLOCK_WTX | (m_cid_supported ? PCB_CID_FOLLOWING : 0);
|
resp[off++] = PCB_SBLOCK_WTX | (m_tcl_session_state->m_cid_supported ? PCB_CID_FOLLOWING : 0);
|
||||||
if (m_cid_supported) resp[off++] = m_cid & 0x0F;
|
if (m_tcl_session_state->m_cid_supported) resp[off++] = m_tcl_session_state->m_cid & 0x0F;
|
||||||
resp[off++] = wtxm;
|
resp[off++] = wtxm;
|
||||||
nfc_tag_14a_tx_bytes(resp, off, true);
|
nfc_tag_14a_tx_bytes(resp, off, true);
|
||||||
/* If we now have a response ready, send it next I-block */
|
/* If we now have a response ready, send it next I-block */
|
||||||
if (m_response_ready) {
|
if (m_tcl_session_state->m_response_ready) {
|
||||||
m_response_ready = false;
|
m_tcl_session_state->m_response_ready = false;
|
||||||
send_iblock(m_resp_buf, m_resp_len);
|
send_iblock(m_tcl_session_state);
|
||||||
}
|
}
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
if ((pcb & PCB_PPS) == PCB_PPS) {
|
if ((pcb & PCB_PPS) == PCB_PPS) {
|
||||||
/* Echo back with our own PPS */
|
/* Echo back with our own PPS */
|
||||||
uint8_t resp[1] = { PCB_PPS };
|
uint8_t resp[1] = { PCB_PPS };
|
||||||
nfc_tag_14a_tx_bytes(resp, 1, true);
|
nfc_tag_14a_tx_bytes(resp, 1, true);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- R-block ---- */
|
/* ---- R-block ---- */
|
||||||
if (is_rblock(pcb)) {
|
if (is_rblock(pcb)) {
|
||||||
send_rack();
|
send_rack(m_tcl_session_state);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- I-block ---- */
|
/* ---- I-block ---- */
|
||||||
@@ -248,14 +251,14 @@ static void nfc_tag_14a_4_state_handler(uint8_t *data, uint16_t szBytes) {
|
|||||||
|
|
||||||
uint8_t offset = 1;
|
uint8_t offset = 1;
|
||||||
if (has_cid) {
|
if (has_cid) {
|
||||||
m_cid_supported = true;
|
m_tcl_session_state->m_cid_supported = true;
|
||||||
offset++; /* skip CID byte */
|
offset++; /* skip CID byte */
|
||||||
}
|
}
|
||||||
if (has_nad) offset++;
|
if (has_nad) offset++;
|
||||||
|
|
||||||
if (offset >= szBytes) {
|
if (offset >= szBytes) {
|
||||||
send_rack();
|
send_rack(m_tcl_session_state);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t apdu_len = szBytes - offset;
|
uint16_t apdu_len = szBytes - offset;
|
||||||
@@ -263,95 +266,102 @@ static void nfc_tag_14a_4_state_handler(uint8_t *data, uint16_t szBytes) {
|
|||||||
|
|
||||||
m_dbg_iblocks_rx++;
|
m_dbg_iblocks_rx++;
|
||||||
m_dbg_last_rx_pcb = pcb;
|
m_dbg_last_rx_pcb = pcb;
|
||||||
NRF_LOG_INFO("14A4 I-block #%d: reader_blk=%d m_block_num=%d apdu_len=%d",
|
NRF_LOG_INFO("14A4 I-block #%d: reader_blk=%d m_tcl_session_state->m_block_num=%d apdu_len=%d",
|
||||||
m_dbg_iblocks_rx, reader_blknum, m_block_num, apdu_len);
|
m_dbg_iblocks_rx, reader_blknum, m_tcl_session_state->m_block_num, apdu_len);
|
||||||
|
|
||||||
/* Block number check per ISO14443-4 §7.5.3.3:
|
/* Block number check per ISO14443-4 §7.5.3.3:
|
||||||
* If block number matches expected, process new APDU.
|
* If block number matches expected, process new APDU.
|
||||||
* If block number does NOT match, it is a retransmit —
|
* If block number does NOT match, it is a retransmit —
|
||||||
* resend the last response without re-processing. */
|
* resend the last response without re-processing. */
|
||||||
if (reader_blknum != (m_block_num & 0x01)) {
|
if (reader_blknum != (m_tcl_session_state->m_block_num & 0x01)) {
|
||||||
/* Retransmit: resend last response */
|
/* Retransmit: resend last response */
|
||||||
if (m_resp_len > 0) {
|
if (m_tcl_session_state->m_resp_len > 0) {
|
||||||
/* Restore block num to what we sent last time and resend */
|
/* Restore block num to what we sent last time and resend */
|
||||||
m_block_num ^= 1; /* undo the increment from last send */
|
m_tcl_session_state->m_block_num ^= 1; /* undo the increment from last send */
|
||||||
send_iblock(m_resp_buf, m_resp_len);
|
send_iblock(m_tcl_session_state);
|
||||||
} else {
|
} else {
|
||||||
send_rack();
|
send_rack(m_tcl_session_state);
|
||||||
}
|
}
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(m_apdu_buf, &data[offset], apdu_len);
|
memcpy(m_tcl_session_state->m_apdu_buf, &data[offset], apdu_len);
|
||||||
m_apdu_len = apdu_len;
|
m_tcl_session_state->m_apdu_len = apdu_len;
|
||||||
m_apdu_pending = true;
|
m_tcl_session_state->m_apdu_pending = true;
|
||||||
m_response_ready = false;
|
m_tcl_session_state->m_response_ready = false;
|
||||||
|
|
||||||
if (more_chain) {
|
if (more_chain) {
|
||||||
send_rack();
|
send_rack(m_tcl_session_state);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
NRF_LOG_INFO("14A-4: unknown PCB 0x%02x", pcb);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* State handler (called from NFCT ISR on each received frame) */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
static void nfc_tag_14a_4_state_handler(uint8_t *data, uint16_t szBytes) {
|
||||||
|
if (!nfc_tag_14a_4_base_handler(&m_tcl_session_state, data, szBytes)) return;
|
||||||
|
|
||||||
/* APDU complete — check static table first, then WTX */
|
/* APDU complete — check static table first, then WTX */
|
||||||
{
|
|
||||||
uint8_t *static_resp = NULL;
|
uint8_t *static_resp = NULL;
|
||||||
uint16_t static_len = 0;
|
uint16_t static_len = 0;
|
||||||
bool _found = find_static_response(m_apdu_buf, apdu_len,
|
bool _found = find_static_response(m_tcl_session_state.m_apdu_buf, m_tcl_session_state.m_apdu_len,
|
||||||
&static_resp, &static_len);
|
&static_resp, &static_len);
|
||||||
m_dbg_last_match = _found ? 1 : 0;
|
m_dbg_last_match = _found ? 1 : 0;
|
||||||
NRF_LOG_INFO("14A4 find_static: found=%d static_len=%d resp_count=%d",
|
NRF_LOG_INFO("14A4 find_static: found=%d static_len=%d resp_count=%d",
|
||||||
_found, static_len, m_static_resp_count);
|
_found, static_len, m_static_resp_count);
|
||||||
if (_found) {
|
if (_found) {
|
||||||
m_dbg_iblocks_tx++;
|
m_dbg_iblocks_tx++;
|
||||||
memcpy(m_resp_buf, static_resp, static_len);
|
memcpy(m_tcl_session_state.m_resp_buf, static_resp, static_len);
|
||||||
m_resp_len = static_len;
|
m_tcl_session_state.m_resp_len = static_len;
|
||||||
send_iblock(m_resp_buf, m_resp_len);
|
m_tcl_session_state.m_response_ready = true;
|
||||||
} else if (m_response_ready) {
|
|
||||||
m_response_ready = false;
|
|
||||||
send_iblock(m_resp_buf, m_resp_len);
|
|
||||||
} else {
|
|
||||||
/* No response ready — keep reader alive with WTX */
|
|
||||||
send_wtx();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NRF_LOG_INFO("14A-4: unknown PCB 0x%02x", pcb);
|
nfc_tag_14a_4_base_respond(&m_tcl_session_state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
/* APDU relay API (for host-driven responses) */
|
/* APDU relay API (for host-driven responses) */
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
bool nfc_tag_14a_4_get_pending_apdu(uint8_t *buf, uint16_t *length) {
|
bool nfc_tag_14a_4_get_pending_apdu(uint8_t *buf, uint16_t *length) {
|
||||||
if (!m_apdu_pending) return false;
|
if (!m_tcl_session_state.m_apdu_pending) return false;
|
||||||
m_apdu_pending = false;
|
m_tcl_session_state.m_apdu_pending = false;
|
||||||
*length = m_apdu_len;
|
*length = m_tcl_session_state.m_apdu_len;
|
||||||
memcpy(buf, m_apdu_buf, m_apdu_len);
|
memcpy(buf, m_tcl_session_state.m_apdu_buf, m_tcl_session_state.m_apdu_len);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void nfc_tag_14a_4_set_response(const uint8_t *data, uint16_t length) {
|
void nfc_tag_14a_4_set_response(const uint8_t *data, uint16_t length) {
|
||||||
if (length > NFC_14A_4_MAX_APDU) length = NFC_14A_4_MAX_APDU;
|
if (length > NFC_14A_4_MAX_APDU) length = NFC_14A_4_MAX_APDU;
|
||||||
memcpy(m_resp_buf, data, length);
|
memcpy(m_tcl_session_state.m_resp_buf, data, length);
|
||||||
m_resp_len = length;
|
m_tcl_session_state.m_resp_len = length;
|
||||||
m_response_ready = true;
|
m_tcl_session_state.m_response_ready = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
/* Reset handler */
|
/* Reset handler */
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
void nfc_tag_14a_4_reset_state(nfc_tag_14a_4_tcl_state_t *m_tcl_session_state) {
|
||||||
|
m_tcl_session_state->m_block_num = 0;
|
||||||
|
m_tcl_session_state->m_cid_supported = false;
|
||||||
|
m_tcl_session_state->m_cid = 0;
|
||||||
|
m_tcl_session_state->m_apdu_pending = false;
|
||||||
|
m_tcl_session_state->m_response_ready = false;
|
||||||
|
m_tcl_session_state->m_apdu_len = 0;
|
||||||
|
m_tcl_session_state->m_resp_len = 0;
|
||||||
|
}
|
||||||
|
|
||||||
void nfc_tag_14a_4_reset_handler(void) {
|
void nfc_tag_14a_4_reset_handler(void) {
|
||||||
m_block_num = 0;
|
nfc_tag_14a_4_reset_state(&m_tcl_session_state);
|
||||||
m_cid_supported = false;
|
|
||||||
m_cid = 0;
|
|
||||||
m_apdu_pending = false;
|
|
||||||
m_response_ready = false;
|
|
||||||
m_apdu_len = 0;
|
|
||||||
m_resp_len = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void nfc_tag_14a_4_get_debug_counters(uint8_t *rx, uint8_t *tx,
|
void nfc_tag_14a_4_get_debug_counters(uint8_t *rx, uint8_t *tx,
|
||||||
|
|||||||
@@ -48,9 +48,27 @@ typedef struct __attribute__((packed)) {
|
|||||||
}
|
}
|
||||||
nfc_tag_14a_4_information_t;
|
nfc_tag_14a_4_information_t;
|
||||||
|
|
||||||
|
/* T=CL session state */
|
||||||
|
typedef struct __attribute__((packed)) {
|
||||||
|
uint8_t m_block_num;
|
||||||
|
bool m_cid_supported;
|
||||||
|
uint8_t m_cid;
|
||||||
|
uint8_t m_apdu_buf[NFC_14A_4_MAX_APDU];
|
||||||
|
uint16_t m_apdu_len;
|
||||||
|
bool m_apdu_pending;
|
||||||
|
uint8_t m_resp_buf[NFC_14A_4_MAX_APDU];
|
||||||
|
uint16_t m_resp_len;
|
||||||
|
bool m_response_ready;
|
||||||
|
}
|
||||||
|
nfc_tag_14a_4_tcl_state_t;
|
||||||
|
|
||||||
/* Anti-collision resource — used by get_coll_res_data in app_cmd.c */
|
/* Anti-collision resource — used by get_coll_res_data in app_cmd.c */
|
||||||
nfc_tag_14a_coll_res_reference_t *nfc_tag_14a_4_get_coll_res(void);
|
nfc_tag_14a_coll_res_reference_t *nfc_tag_14a_4_get_coll_res(void);
|
||||||
|
|
||||||
|
/* Handles the low-level ISO14443-4 communication. Returns true when a complete APDU has been read and is ready for response. */
|
||||||
|
bool nfc_tag_14a_4_base_handler(nfc_tag_14a_4_tcl_state_t *m_tcl_session_state, uint8_t *data, uint16_t szBytes);
|
||||||
|
void nfc_tag_14a_4_base_respond(nfc_tag_14a_4_tcl_state_t *m_tcl_session_state);
|
||||||
|
|
||||||
/* tag_base_map callbacks */
|
/* tag_base_map callbacks */
|
||||||
int nfc_tag_14a_4_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *buffer);
|
int nfc_tag_14a_4_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *buffer);
|
||||||
int nfc_tag_14a_4_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer);
|
int nfc_tag_14a_4_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer);
|
||||||
@@ -66,6 +84,7 @@ bool nfc_tag_14a_4_get_pending_apdu(uint8_t *buf, uint16_t *length);
|
|||||||
void nfc_tag_14a_4_set_response(const uint8_t *data, uint16_t length);
|
void nfc_tag_14a_4_set_response(const uint8_t *data, uint16_t length);
|
||||||
|
|
||||||
/* Reset handler */
|
/* Reset handler */
|
||||||
|
void nfc_tag_14a_4_reset_state(nfc_tag_14a_4_tcl_state_t *m_tcl_session_state);
|
||||||
void nfc_tag_14a_4_reset_handler(void);
|
void nfc_tag_14a_4_reset_handler(void);
|
||||||
|
|
||||||
#endif /* NFC_14A_4_H */
|
#endif /* NFC_14A_4_H */
|
||||||
|
|||||||
Reference in New Issue
Block a user