@@ -675,20 +675,127 @@ static void handle_fast_read_command(uint8_t block_num, uint8_t end_block_num) {
675675
676676 int block_max = get_block_max_by_tag_type (m_tag_type , true);
677677
678- if (block_num >= end_block_num || end_block_num >= block_max ) {
678+ if (block_num > end_block_num || end_block_num >= block_max ) {
679679 nfc_tag_14a_tx_nbit (NAK_INVALID_OPERATION_TBV , 4 );
680680 return ;
681681 }
682682
683683 NRF_LOG_INFO ("HANDLING FAST READ %02x %02x" , block_num , end_block_num );
684+ // FAST_READ is inclusive: read from block_num to end_block_num (both included)
685+ handle_any_read (block_num , end_block_num - block_num + 1 , block_max );
686+ }
687+
688+ // Call with:
689+ // - page: page number to check
690+ // - user_memory_end_page: page index of the first config page (end of user memory page index + 1), used to locate dynamic lock bytes
691+ // - dyn_lock_bit_page_cnt: 2 NTAG212/213, 16 for NTAG215/216
692+ static bool is_page_locked_ntag21x (uint16_t page ,
693+ uint16_t user_memory_end_page ,
694+ int dyn_lock_bit_page_cnt )
695+ {
696+ if (page < 3 ) {
697+ return true;
698+ }
699+
700+ if (page >= 3 && page <= 7 ) {
701+ uint8_t bit_index = (uint8_t )(page - 3 ); // 0 -> page3,4 -> page 7
702+ return (((m_tag_information -> memory [2 ][2 ] >> 4 ) >> bit_index ) & 0x1 ) != 0 ;
703+ }
704+ if (page >= 8 && page <= 15 ) {
705+ uint8_t bit_index = (uint8_t )(page - 8 ); // 0 -> page3, 12 -> page15
706+ return (((m_tag_information -> memory [2 ][3 ] ) >> bit_index ) & 0x1 ) != 0 ;
707+ }
708+
709+ // Determine the start and end of the dynamic-lock-covered user pages:
710+ const uint16_t dyn_start = 16 ;
711+ const uint16_t dyn_end = (uint16_t )(user_memory_end_page - 1 ); // inclusive
684712
685- handle_any_read (block_num , end_block_num - block_num , block_max );
713+ if (page >= dyn_start && page <= dyn_end ) {
714+
715+ // For NTAG21x the dynamic lock bytes are in bytes 0..2 of that page.
716+ // We'll interpret them as three bytes [b0, b1, b2].
717+ uint8_t b0 = m_tag_information -> memory [user_memory_end_page ][0 ];
718+ uint8_t b1 = m_tag_information -> memory [user_memory_end_page ][1 ];
719+ uint8_t b2 = m_tag_information -> memory [user_memory_end_page ][2 ];
720+ // Each dynamic lock bit covers 'dyn_lock_bit_page_cnt' pages.
721+ uint32_t relative = (uint32_t )(page - dyn_start );
722+ uint32_t block_index = relative / (uint32_t )dyn_lock_bit_page_cnt ; // which bit overall
723+
724+ // block_index maps into b0..b2 (bits 0..23)
725+ uint8_t which_byte = (uint8_t )(block_index / 8 );
726+ uint8_t which_bit = (uint8_t )(block_index % 8 );
727+
728+ uint8_t target = 0 ;
729+ if (which_byte == 0 ) target = b0 ;
730+ else if (which_byte == 1 ) target = b1 ;
731+ else if (which_byte == 2 ) target = b2 ;
732+ else target = 0 ; // out-of-range -> treated as unlocked
733+
734+ return (((target >> which_bit ) & 1 ) != 0 );
735+ }
736+ return false;
686737}
687738
688- static bool check_ro_lock_on_page (int block_num ) {
689- if (block_num < 3 ) return true;
690- else if (block_num == 3 ) return (m_tag_information -> memory [2 ][2 ] & 9 ) != 0 ; // bits 0 and 3
691- else if (block_num <= MF0ICU1_PAGES ) {
739+ // Get user memory end page for NTAG21x tags, returns 0 for non-NTAG21x tags
740+ static int get_user_memory_end_page_by_tag_type (tag_specific_type_t tag_type ) {
741+ switch (tag_type ) {
742+ case TAG_TYPE_NTAG_213 :
743+ return NTAG213_USER_MEMORY_END ;
744+ case TAG_TYPE_NTAG_215 :
745+ return NTAG215_USER_MEMORY_END ;
746+ case TAG_TYPE_NTAG_216 :
747+ return NTAG216_USER_MEMORY_END ;
748+ default :
749+ return 0 ;
750+ }
751+ }
752+
753+ // Handle special write cases for dynamic lock bytes and config pages
754+ // Returns ACK_VALUE if handled, 0 if not a special case
755+ static int handle_special_page_write (uint8_t block_num , uint8_t * p_data ) {
756+ int user_memory_end_page = get_user_memory_end_page_by_tag_type (m_tag_type );
757+ int first_cfg_page = get_first_cfg_page_by_tag_type (m_tag_type );
758+
759+ // Handle dynamic lock bytes page (user_memory_end page) for NTAG21x tags
760+ if (user_memory_end_page > 0 && block_num == user_memory_end_page ) {
761+ // Dynamic lock bytes are OR'ed just like static lock bytes
762+ // Only bytes 0-2 contain lock bits, byte 3 is RFU (reserved, typically 0x00)
763+ m_tag_information -> memory [block_num ][0 ] |= p_data [0 ];
764+ m_tag_information -> memory [block_num ][1 ] |= p_data [1 ];
765+ m_tag_information -> memory [block_num ][2 ] |= p_data [2 ];
766+ m_tag_information -> memory [block_num ][3 ] = p_data [3 ]; // Byte 3 is RFU, write it directly
767+ return ACK_VALUE ;
768+ }
769+
770+ // Handle PWD and PACK pages (always writable even when CFGLCK is set)
771+ // PWD and PACK remain writable to allow password changes
772+ if (first_cfg_page > 0 && (block_num == first_cfg_page + 2 || block_num == first_cfg_page + 3 )) {
773+ memcpy (m_tag_information -> memory [block_num ], p_data , NFC_TAG_MF0_NTAG_DATA_SIZE );
774+ return ACK_VALUE ;
775+ }
776+
777+ // Handle config pages (CFG0 and CFG1) - check CFGLCK protection
778+ if (first_cfg_page > 0 && (block_num == first_cfg_page || block_num == first_cfg_page + 1 )) {
779+ // Check CFGLCK bit (bit 6 of CFG0 byte 0)
780+ // Per spec: CFGLCK permanently write-protects configuration pages
781+ uint8_t cfglck = m_tag_information -> memory [first_cfg_page ][0 ] & 0x40 ;
782+ if (cfglck != 0 ) {
783+ // Config pages are locked
784+ return NAK_INVALID_OPERATION_TBV ;
785+ }
786+ // Not locked, allow write
787+ memcpy (m_tag_information -> memory [block_num ], p_data , NFC_TAG_MF0_NTAG_DATA_SIZE );
788+ return ACK_VALUE ;
789+ }
790+
791+ // Not a special case
792+ return 0 ;
793+ }
794+
795+ // Old v2.1.0 algorithm for MF0ICU1, MF0ICU2, MF0UL11, MF0UL21 tags
796+ static bool check_ro_lock_on_page_mf0 (int block_num ) {
797+ // This function assumes block_num > 3 (pages 0-3 are handled by caller)
798+ if (block_num <= MF0ICU1_PAGES ) {
692799 bool locked = false;
693800
694801 // check block locking bits
@@ -751,18 +858,6 @@ static bool check_ro_lock_on_page(int block_num) {
751858 user_memory_end = NTAG212_USER_MEMORY_END ;
752859 dyn_lock_bit_page_cnt = 2 ;
753860 break ;
754- case TAG_TYPE_NTAG_213 :
755- user_memory_end = NTAG213_USER_MEMORY_END ;
756- dyn_lock_bit_page_cnt = 2 ;
757- break ;
758- case TAG_TYPE_NTAG_215 :
759- user_memory_end = NTAG215_USER_MEMORY_END ;
760- dyn_lock_bit_page_cnt = 16 ;
761- break ;
762- case TAG_TYPE_NTAG_216 :
763- user_memory_end = NTAG216_USER_MEMORY_END ;
764- dyn_lock_bit_page_cnt = 16 ;
765- break ;
766861 default :
767862 ASSERT (false);
768863 break ;
@@ -790,14 +885,90 @@ static bool check_ro_lock_on_page(int block_num) {
790885 }
791886}
792887
888+ // Check if a page is read-only locked
889+ // NOTE: This function is only called for block_num >= 3
890+ // Blocks 0-2, dynamic locks, and config pages are handled elsewhere
891+ static bool check_ro_lock_on_page (int block_num ) {
892+ // Page 3 (OTP/CC page) - check lock bits
893+ if (block_num == 3 ) {
894+ return (m_tag_information -> memory [2 ][2 ] & 9 ) != 0 ; // bits 0 and 3
895+ }
896+
897+ // For MF0ICU1, MF0ICU2, MF0UL11, MF0UL21, NTAG_210 and NTAG_212 tags - use old v2.1.0 algorithm. TODO try to merge the two
898+ switch (m_tag_type ) {
899+ case TAG_TYPE_MF0ICU1 :
900+ case TAG_TYPE_MF0ICU2 :
901+ case TAG_TYPE_MF0UL11 :
902+ case TAG_TYPE_MF0UL21 :
903+ case TAG_TYPE_NTAG_210 :
904+ case TAG_TYPE_NTAG_212 :
905+ return check_ro_lock_on_page_mf0 (block_num );
906+ default :
907+ break ;
908+ }
909+
910+ // For NTAG21x tags
911+ int user_memory_end = get_user_memory_end_page_by_tag_type (m_tag_type );
912+ if (user_memory_end == 0 ) {
913+ ASSERT (false);
914+ return false;
915+ }
916+
917+ if (block_num < user_memory_end ) {
918+
919+ switch (m_tag_type ) {
920+ case TAG_TYPE_NTAG_213 : {
921+ return is_page_locked_ntag21x (block_num ,
922+ NTAG213_USER_MEMORY_END , 2 );
923+ }
924+ case TAG_TYPE_NTAG_215 : {
925+ return is_page_locked_ntag21x (block_num ,
926+ NTAG215_USER_MEMORY_END , 16 );
927+ }
928+ case TAG_TYPE_NTAG_216 : {
929+ return is_page_locked_ntag21x (block_num ,
930+ NTAG216_USER_MEMORY_END , 16 );
931+ }
932+ default :
933+ ASSERT (false);
934+ break ;
935+ }
936+ return false;
937+
938+ } else {
939+ // Pages beyond user memory end (password, pack, etc.) are always writable
940+ // Note: Config pages and dynamic lock bytes are handled by handle_special_page_write()
941+ // before this function is ever called
942+ return false;
943+ }
944+ }
945+
793946static int handle_write_command (uint8_t block_num , uint8_t * p_data ) {
794947 int block_max = get_block_max_by_tag_type (m_tag_type , false);
795-
796- if (block_num >= block_max ) {
948+ int first_cfg_page = get_first_cfg_page_by_tag_type (m_tag_type );
949+ uint8_t cfglck = m_tag_information -> memory [first_cfg_page ][0 ] & 0x40 ;
950+
951+ // Check if block_num is within valid range
952+ // Note: block_max already takes AUTH0 protection into account
953+ //TODO Check if other tag types need special handling here
954+ bool is_config_page = ((m_tag_type == TAG_TYPE_NTAG_213 )
955+ || (m_tag_type == TAG_TYPE_NTAG_215 ) || (m_tag_type == TAG_TYPE_NTAG_216 ))
956+ && (block_num >= first_cfg_page ) && (block_num <= first_cfg_page + 1 );
957+ bool is_beyond_user_memory = (block_num >= block_max );
958+ bool config_locked = (cfglck != 0 );
959+
960+ // Reject out-of-bounds writes (except config pages)
961+ if (is_beyond_user_memory && !is_config_page ) {
797962 NRF_LOG_ERROR ("Write failed: block_num %08x >= block_max %08x" , block_num , block_max );
798963 return NAK_INVALID_OPERATION_TBV ;
799964 }
800965
966+ // Reject writes to locked config pages
967+ if (config_locked && is_config_page ) {
968+ NRF_LOG_ERROR ("Write failed: config pages locked (cfglck=0x40)" );
969+ return NAK_INVALID_OPERATION_TBV ;
970+ }
971+
801972 // Handle writing based on the current write mode
802973 if (m_tag_information -> config .mode_block_write == NFC_TAG_MF0_NTAG_WRITE_DENIED ) {
803974 // In this mode, reject all write operations
@@ -816,6 +987,19 @@ static int handle_write_command(uint8_t block_num, uint8_t *p_data) {
816987 return ACK_VALUE ;
817988 }
818989
990+ switch (m_tag_type ) {
991+ case TAG_TYPE_NTAG_213 :
992+ case TAG_TYPE_NTAG_215 :
993+ case TAG_TYPE_NTAG_216 :
994+ // Check if this is a special page (dynamic lock bytes or config pages) and handle it
995+ int special_result = handle_special_page_write (block_num , p_data );
996+ if (special_result != 0 ) {
997+ return special_result ;
998+ }
999+ default :
1000+ break ;
1001+ }
1002+
8191003 switch (block_num ) {
8201004 case 0 :
8211005 case 1 :
@@ -872,6 +1056,13 @@ static void handle_read_cnt_command(uint8_t index) {
8721056static void handle_incr_cnt_command (uint8_t block_num , uint8_t * p_data ) {
8731057 uint8_t ctr_page_off ;
8741058 uint8_t ctr_page_end ;
1059+ uint8_t first_index ;
1060+ uint8_t * cnt_data ;
1061+ uint32_t incr_value ;
1062+ uint32_t cnt ;
1063+
1064+ first_index = 0 ;
1065+
8751066 switch (m_tag_type ) {
8761067 case TAG_TYPE_MF0UL11 :
8771068 ctr_page_off = MF0UL11_PAGES ;
@@ -881,20 +1072,36 @@ static void handle_incr_cnt_command(uint8_t block_num, uint8_t *p_data) {
8811072 ctr_page_off = MF0UL21_PAGES ;
8821073 ctr_page_end = ctr_page_off + MF0ULx1_NUM_CTRS ;
8831074 break ;
1075+ case TAG_TYPE_NTAG_213 :
1076+ ctr_page_off = NTAG213_PAGES ;
1077+ ctr_page_end = ctr_page_off + NTAG_NUM_CTRS ;
1078+ first_index = 2 ;
1079+ break ;
1080+ case TAG_TYPE_NTAG_215 :
1081+ ctr_page_off = NTAG215_PAGES ;
1082+ ctr_page_end = ctr_page_off + NTAG_NUM_CTRS ;
1083+ first_index = 2 ;
1084+ break ;
1085+ case TAG_TYPE_NTAG_216 :
1086+ ctr_page_off = NTAG216_PAGES ;
1087+ ctr_page_end = ctr_page_off + NTAG_NUM_CTRS ;
1088+ first_index = 2 ;
1089+ break ;
8841090 default :
8851091 if (is_ntag ()) nfc_tag_14a_tx_nbit (NAK_INVALID_OPERATION_TBV , 4 );
8861092 return ;
8871093 }
8881094
8891095 // check that counter index is in bounds
890- if (block_num >= (ctr_page_end - ctr_page_off )) {
1096+ // NTAG cards expect counter address 2, so adjust for internal storage at offset 0
1097+ if ((block_num < first_index ) || ((block_num - first_index ) >= (ctr_page_end - ctr_page_off ))) {
8911098 nfc_tag_14a_tx_nbit (NAK_INVALID_OPERATION_TBV , 4 );
8921099 return ;
8931100 }
8941101
895- uint8_t * cnt_data = m_tag_information -> memory [ctr_page_off + block_num ];
896- uint32_t incr_value = ((uint32_t )p_data [0 ] << 16 ) | ((uint32_t )p_data [1 ] << 8 ) | ((uint32_t )p_data [2 ]);
897- uint32_t cnt = ((uint32_t )cnt_data [0 ] << 16 ) | ((uint32_t )cnt_data [1 ] << 8 ) | ((uint32_t )cnt_data [2 ]);
1102+ cnt_data = m_tag_information -> memory [ctr_page_off + block_num - first_index ];
1103+ incr_value = ((uint32_t )p_data [0 ] << 16 ) | ((uint32_t )p_data [1 ] << 8 ) | ((uint32_t )p_data [2 ]);
1104+ cnt = ((uint32_t )cnt_data [0 ] << 16 ) | ((uint32_t )cnt_data [1 ] << 8 ) | ((uint32_t )cnt_data [2 ]);
8981105
8991106 if ((0xFFFFFF - cnt ) < incr_value ) {
9001107 // set tearing event flag
0 commit comments