HiPi

 view release on metacpan or  search on metacpan

lib/HiPi/Interface/MFRC522.pm  view on Meta::CPAN

	
	#// Transceive the data, store the reply in cmdBuffer[]
	my $waitIRq = 0x30;		#// RxIRq and IdleIRq
	my $validBits = 0;
    my $getlen = 16;
    my ($status, $piccdata, $validbitsout) = $self->pcd_communicate_with_picc(
        MFRC522_TRANSCEIVE, $waitIRq, \@sendbuffer, $getlen, $validBits 
    );
    
	if ($accepttimeout && $status == MFRC522_STATUS_TIMEOUT) {
		return (MFRC522_STATUS_OK, [] );
	}
	if ($status != MFRC522_STATUS_OK) {
		return ( $status, undef );
	}
	#// The PICC must reply with a 4 bit ACK
    
    my $returnbuffersize = scalar @$piccdata;
    
	if ($returnbuffersize != 1 || $validbitsout != 4) {
        
		return ( MFRC522_STATUS_ERROR, undef );
	}
	if ($piccdata->[0] != MIFARE_MF_ACK) {
		return ( MFRC522_STATUS_MIFARE_NACK, undef );
	}
    
	return ($status, $piccdata, $validbitsout);
    
}

sub picc_read_tag_serial {
    my $self = shift;
    my( $status, $uid ) = $self->picc_select;
    
    my $serialstring = '';
    if( $status == MFRC522_STATUS_OK ) {
        for (my $i = 0; $i < $uid->{'size'}; $i ++ ) {
            $serialstring .= '-' if $serialstring;
            $serialstring .= sprintf('%02X', $uid->{'data'}->[$i]);
        }
    }
    
    return ( $status, $uid, $serialstring );
}

sub picc_select {
    my ($self, $uid, $validbits ) = @_;
    
    $uid //= {
        size => 0,
        data => [],
        sak  => 0,
    };
    
    $validbits ||= 0;
    
#   bool uidComplete;
#	bool selectDone;
#	bool useCascadeTag;
#	byte cascadeLevel = 1;
#	MFRC522::StatusCode result;
#	byte count;
#	byte checkBit;
#	byte index;
#	byte uidIndex;					// The first index in uid->uidByte[] that is used in the current Cascade Level.
#	int8_t currentLevelKnownBits;		// The number of known UID bits in the current Cascade Level.
#	byte buffer[9];					// The SELECT/ANTICOLLISION commands uses a 7 byte standard frame + 2 bytes CRC_A
#	byte bufferUsed;				// The number of bytes used in the buffer, ie the number of bytes to transfer to the FIFO.
#	byte rxAlign;					// Used in BitFramingReg. Defines the bit position for the first bit received.
#	byte txLastBits;				// Used in BitFramingReg. The number of valid bits in the last transmitted byte. 
#	byte *responseBuffer;
#	byte responseLength;
#	
#	// Description of buffer structure:
#	//		Byte 0: SEL 				Indicates the Cascade Level: PICC_CMD_SEL_CL1, PICC_CMD_SEL_CL2 or PICC_CMD_SEL_CL3
#	//		Byte 1: NVB					Number of Valid Bits (in complete command, not just the UID): High nibble: complete bytes, Low nibble: Extra bits. 
#	//		Byte 2: UID-data or CT		See explanation below. CT means Cascade Tag.
#	//		Byte 3: UID-data
#	//		Byte 4: UID-data
#	//		Byte 5: UID-data
#	//		Byte 6: BCC					Block Check Character - XOR of bytes 2-5
#	//		Byte 7: CRC_A
#	//		Byte 8: CRC_A
#	// The BCC and CRC_A are only transmitted if we know all the UID bits of the current Cascade Level.
#	//
#	// Description of bytes 2-5: (Section 6.5.4 of the ISO/IEC 14443-3 draft: UID contents and cascade levels)
#	//		UID size	Cascade level	Byte2	Byte3	Byte4	Byte5
#	//		========	=============	=====	=====	=====	=====
#	//		 4 bytes		1			uid0	uid1	uid2	uid3
#	//		 7 bytes		1			CT		uid0	uid1	uid2
#	//						2			uid3	uid4	uid5	uid6
#	//		10 bytes		1			CT		uid0	uid1	uid2
#	//						2			CT		uid3	uid4	uid5
#	//						3			uid6	uid7	uid8	uid9
	
	# // Sanity checks
	if ($validbits > 80) {
		return ( MFRC522_STATUS_INVALID );
	}
	
	# // Prepare MFRC522
	$self->clear_bit_mask(MFRC522_REG_CollReg, 0x80);		#// ValuesAfterColl=1 => Bits received after collision are cleared.
	
	#// Repeat Cascade Level loop until we have a complete UID.
	my $uidComplete = 0;
    my $cascadeLevel = 1;
    
    my( $uidIndex, $useCascadeTag, $currentLevelKnownBits, $index, $selectDone, $txLastBits, $bufferUsed, $rxAlign );
    
    my ($respstatus, $respdata, $respvalidbits);
    my ( $crcstatus, $cbuffer1, $cbuffer2 );
    
    my ( $responseIndex, $responseLength );
    
    my @buffer = ();
    
	while (!$uidComplete) {
		#// Set the Cascade Level in the SEL byte, find out if we need to use the Cascade Tag in byte 2.
        
        $selectDone = 0;
        
        if( $cascadeLevel == 1 ) {
            
            $buffer[0] = MIFARE_SELECT_CL1;
            $uidIndex = 0,
            $useCascadeTag = ( $validbits && $uid->{'size'} > 4 ) ? 1 : 0;
        } elsif( $cascadeLevel == 2 ) {
			            
            $buffer[0] = MIFARE_SELECT_CL2;
            $uidIndex = 3,
            $useCascadeTag = ( $validbits && $uid->{'size'} > 7 ) ? 1 : 0;
		
        } elsif( $cascadeLevel == 3 ) {
			            
            $buffer[0] = MIFARE_SELECT_CL3;
            $uidIndex = 6,
            $useCascadeTag = 0
			
		} else {
            # should not get here
            # warn qq( cascade level $cascadeLevel);
            return ( MFRC522_STATUS_INTERNAL_ERROR );
        }
		
		# // How many UID bits are known in this Cascade Level?
		$currentLevelKnownBits = $validbits - (8 * $uidIndex);
        
		if ($currentLevelKnownBits < 0) {
			$currentLevelKnownBits = 0;
		}
                
		# // Copy the known bits from uid->uidByte[] to buffer[]
		$index = 2; #// destination index in buffer[]
		if ($useCascadeTag) {
			$buffer[$index++] = MIFARE_CASCADE;
		}
        
		my $bytesToCopy = int($currentLevelKnownBits / 8) + ($currentLevelKnownBits % 8 ? 1 : 0); # // The number of bytes needed to represent the known bits for this level.
		
        if ($bytesToCopy) {
			my $maxBytes = $useCascadeTag ? 3 : 4; #// Max 4 bytes in each Cascade Level. Only 3 left if we use the Cascade Tag
			if ($bytesToCopy > $maxBytes) {
				$bytesToCopy = $maxBytes;
			}
			for (my $count = 0; $count < $bytesToCopy; $count++) {
				$buffer[$index++] = $uid->{'data'}->[$uidIndex + $count] || 0;
			}
		}
		# // Now that the data has been copied we need to include the 8 bits in CT in currentLevelKnownBits
		if ($useCascadeTag) {
			$currentLevelKnownBits += 8;
		}
		
		# // Repeat anti collision loop until we can transmit all UID bits + BCC and receive a SAK - max 32 iterations.
		
		while (!$selectDone) {
			# // Find out how many bits and bytes to send and receive.
			if ($currentLevelKnownBits >= 32) { # // All UID bits in this Cascade Level are known. This is a SELECT.
				
				$buffer[1] = 0x70; #// NVB - Number of Valid Bits: Seven whole bytes
				#// Calculate BCC - Block Check Character
                
                for( 2,3,4,5) {
                    $buffer[$_] //= 0;
                }
                
				$buffer[6] = $buffer[2] ^ $buffer[3] ^ $buffer[4] ^ $buffer[5];
				# // Calculate CRC_A
                
                my @crcdata = @buffer[0..6];
                
                ( $crcstatus, $cbuffer1, $cbuffer2 ) = $self->pcd_calculate_crc( \@crcdata );
                
				if ($crcstatus != MFRC522_STATUS_OK) {
					return ( $crcstatus, undef, undef );
				}
                
                # set the crc result
                $buffer[7] = $cbuffer1;
                $buffer[8] = $cbuffer2;
                

lib/HiPi/Interface/MFRC522.pm  view on Meta::CPAN

				}
				my $collisionPos = $valueOfCollReg & 0x1F; # // Values 0-31, 0 means bit 32.
				if ($collisionPos == 0) {
					$collisionPos = 32;
				}
				if ($collisionPos <= $currentLevelKnownBits) { #// No progress - should not happen
					return ( MFRC522_STATUS_INTERNAL_ERROR, undef, undef );
				}
				#// Choose the PICC with the bit set.
				$currentLevelKnownBits	= $collisionPos;
				my $count			= $currentLevelKnownBits % 8; #// The bit to modify
				my $checkBit		= ($currentLevelKnownBits - 1) % 8;
				$index			= 1 + int($currentLevelKnownBits / 8) + ($count ? 1 : 0); # // First byte is index 0.
				$buffer[$index]	|= (1 << $checkBit);
			} elsif ($respstatus != MFRC522_STATUS_OK) {
				return ( $respstatus, undef, undef );
			} else { # // MFRC522_STATUS_OK
                
				if ($currentLevelKnownBits >= 32) { #// This was a SELECT.
					$selectDone = 1; #// No more anticollision 
					#// We continue below outside the while.
				} else { #// This was an ANTICOLLISION.
					#/ We now have all 32 bits of the UID in this Cascade Level
					$currentLevelKnownBits = 32;
					#// Run loop again to do the SELECT.
				}
			}
		} 
		
		#// We do not check the CBB - it was constructed by us above.
		
		#// Copy the found UID bytes from buffer[] to uid->uidByte[]
		$index			= ($buffer[2] == MIFARE_CASCADE) ? 3 : 2; #// source index in buffer[]
		$bytesToCopy	= ($buffer[2] == MIFARE_CASCADE) ? 3 : 4;
		for (my $count = 0; $count < $bytesToCopy; $count++) {
            $uid->{'data'}->[$uidIndex + $count] = $buffer[$index++];
		}
		
		#// Check response SAK (Select Acknowledge)
        
        my $resplen = scalar @$respdata;
        
		if ($resplen != 3 || $respvalidbits != 0) { # // SAK must be exactly 24 bits (1 byte + CRC_A).
			return ( MFRC522_STATUS_ERROR, undef, undef );
		}
		#// Verify CRC_A - do our own calculation and store the control in buffer[2..3] - those bytes are not needed anymore.
         
        my @crcdata = @$respdata;
        pop @crcdata; pop @crcdata;
        
        ( $crcstatus, $cbuffer1, $cbuffer2 ) = $self->pcd_calculate_crc( \@crcdata );
        
		if ($crcstatus != MFRC522_STATUS_OK) {
			return ( $crcstatus, undef, undef );
		}
		if (($cbuffer1 != $respdata->[-2]) || ($cbuffer2 != $respdata->[-1])) {
			return ( MFRC522_STATUS_CRC_WRONG, undef, undef );
		}
        
		if ($respdata->[0] & 0x04) { #// Cascade bit set - UID not complete yes
			$cascadeLevel++;
		} else {
			$uidComplete = 1;
			$uid->{'sak'} = $respdata->[0];
		}
	} 
	
	#// Set correct uid->size
	$uid->{'size'} = 3 * $cascadeLevel + 1;

	return ( MFRC522_STATUS_OK, $uid, $respvalidbits );
}

sub get_status_code_name {
    my($self, $code) = @_;
    $code //= 0xEE;
    if($code == MFRC522_STATUS_OK ) {
        return 'Success.';
    } elsif($code == MFRC522_STATUS_ERROR ) {
        return 'Error in communication.';
    } elsif($code == MFRC522_STATUS_COLLISION ) {
        return 'Collision detected.';
    } elsif($code == MFRC522_STATUS_TIMEOUT ) {
        return 'Timeout in communication.';
    } elsif($code == MFRC522_STATUS_NO_ROOM ) {
        return 'A buffer is not big enough.';
    } elsif($code == MFRC522_STATUS_INTERNAL_ERROR ) {
        return 'Internal error in the code. Should not happen.';
    } elsif($code == MFRC522_STATUS_INVALID ) {
        return 'Invalid argument.';
    } elsif($code == MFRC522_STATUS_CRC_WRONG ) {
        return 'The CRC_A does not match.';
    } elsif($code == MFRC522_STATUS_MIFARE_NACK ) {
        return 'A MIFARE PICC responded with NAK.';
    } elsif($code == MFRC522_STATUS_UNSUPPORTED_TYPE ) {
        return 'Unsupported command for this PICC type.';
    } elsif($code == MFRC522_STATUS_BLOCK_NOT_ALLOWED ) {
        return 'Command not allowed for this block.';
    } elsif($code == MFRC522_STATUS_BAD_PARAM ) {
        return 'Bad parameter.';
    } else {
        return 'Unknown Error.';
    }
}

sub get_default_key {
    my $self = shift;
    my @key = (0xFF) x 6;
    return \@key;
}

sub picc_type_is_classic {
    my($self, $picctype, $sak) = @_;
    
    if(!defined($picctype) && defined($sak)) {
        $picctype = $self->picc_get_type( $sak );
    }
    
    if( $picctype && ( $picctype == MFRC522_PICC_TYPE_MIFARE_MINI
        || $picctype == MFRC522_PICC_TYPE_MIFARE_1K
        || $picctype == MFRC522_PICC_TYPE_MIFARE_4K ) ) {
        return 1;
    } else {
        return 0;
    }
}

sub picc_type_is_ultralight {
    my($self, $picctype) = @_;

lib/HiPi/Interface/MFRC522.pm  view on Meta::CPAN

    } elsif( $self->picc_type_is_ultralight( $picctype ) ) {
        $key = $self->get_default_key unless(defined($key));
        $output .= $self->picc_dump_ultralight_memory( $uid, $picctype, $key );
    } else {
        my $piccname = $self->picc_get_type_name( $picctype );
        $output = qq(Dumping memory contents not implemented for $piccname\n);
    }
    
    return $output . qq(\n);
}

sub picc_dump_ultralight_memory {
    my($self, $uid, $picctype, $key) = @_;
    my $piccname = $self->picc_get_type_name( $picctype );
    my $output = qq(Dumping memory contents not implemented for $piccname\n);
    return $output;
}

sub picc_dump_classic_memory {
    my($self, $uid, $picctype, $key) = @_;
    
    my $output = '';
    my $no_of_sectors = 0;
    if( $picctype == MFRC522_PICC_TYPE_MIFARE_MINI ) {
        $no_of_sectors = 5;
    } elsif( $picctype == MFRC522_PICC_TYPE_MIFARE_1K ) {
        $no_of_sectors = 16;
    } elsif( $picctype == MFRC522_PICC_TYPE_MIFARE_4K ) {
        $no_of_sectors = 40;
    }
    
	#// Dump sectors, highest address first.
	if ($no_of_sectors) {
        $output .= qq(Sector Block   0  1  2  3   4  5  6  7   8  9 10 11  12 13 14 15  AccessBits\n);
        for (my $i = $no_of_sectors -1; $i >= 0; $i-- ) {
            $output .= $self->picc_dump_classic_sector( $uid, $key, $i );
		}
	}
    
    my $haltstatus = $self->picc_halt_active;
    unless($haltstatus == MFRC522_STATUS_OK) {
        $output .= $self->get_status_code_name( $haltstatus ) . qq(\n);
    }
    $self->pcd_stop_crypto1;
    return $output;
}

sub pcd_authenticate {
    my($self, $command, $blockAddr, $key, $uid ) = @_;
    
    my $waitIRq = 0x10;
	    
    my @sendData = ( $command, $blockAddr );
    
	for (my $i = 0; $i < 6; $i++) {	# // 6 key bytes
		$sendData[2 + $i] = $key->[$i];
	}
	#// Use the last uid bytes as specified in http://cache.nxp.com/documents/application_note/AN10927.pdf
	#// section 3.2.5 "MIFARE Classic Authentication".
	#// The only missed case is the MF1Sxxxx shortcut activation,
	#// but it requires cascade tag (CT) byte, that is not part of uid.
    
	for (my $i = 0; $i < 4; $i++) {				#// The last 4 bytes of the UID
		$sendData[8 + $i] = $uid->{'data'}->[$i + $uid->{'size'} -4];
	}
	
    my ($piccstatus, $piccdata, $piccvalidbits) = $self->pcd_communicate_with_picc( MFRC522_AUTHENT, $waitIRq, \@sendData  );
	return $piccstatus;
}

sub picc_dump_classic_sector {
    my($self, $uid, $key, $sector) = @_;
    
    my $output = '';
    
    my ( $status, $firstBlock, $no_of_blocks, $isSectorTrailer );
	#byte firstBlock;		// Address of lowest address to dump actually last block dumped)
	#byte no_of_blocks;		// Number of blocks in sector
	#bool isSectorTrailer;	// Set to true while handling the "last" (ie highest address) in the sector.
	
	#// The access bits are stored in a peculiar fashion.
	#// There are four groups:
	#//		g[3]	Access bits for the sector trailer, block 3 (for sectors 0-31) or block 15 (for sectors 32-39)
	#//		g[2]	Access bits for block 2 (for sectors 0-31) or blocks 10-14 (for sectors 32-39)
	#//		g[1]	Access bits for block 1 (for sectors 0-31) or blocks 5-9 (for sectors 32-39)
	#//		g[0]	Access bits for block 0 (for sectors 0-31) or blocks 0-4 (for sectors 32-39)
	#// Each group has access bits [C1 C2 C3]. In this code C1 is MSB and C3 is LSB.
	#// The four CX bits are stored together in a nible cx and an inverted nible cx_.
	#byte c1, c2, c3;		// Nibbles
	#byte c1_, c2_, c3_;		// Inverted nibbles
    
    my ( $c1, $c2, $c3, $c1x, $c2x, $c3x );
    
	#bool invertedError;		// True if one of the inverted nibbles did not match
	#byte g[4];				// Access bits for each of the four groups.
	#byte group;				// 0-3 - active group for access bits
	#bool firstInGroup;		// True for the first block dumped in the group
    
    my( $invertedError, $group, $firstInGroup );
    my @g = (0,0,0,0);
	
	#// Determine position and size of sector.
	if ($sector < 32) { #// Sectors 0..31 has 4 blocks each
		$no_of_blocks = 4;
		$firstBlock = $sector * $no_of_blocks;
	} elsif ($sector < 40) { #// Sectors 32-39 has 16 blocks each
		$no_of_blocks = 16;
		$firstBlock = 128 + ($sector - 32) * $no_of_blocks;
	}  else { #// Illegal input, no MIFARE Classic PICC has more than 40 sectors.
		return qq(Illegal input, no MIFARE Classic PICC has more than 40 sectors\n);
	}
		
	#// Dump blocks, highest address first.
	my $byteCount;
	my @buffer = ( 0 ) x 18;
	my $blockAddr;
	$isSectorTrailer = 1;
	$invertedError = 0;	# // Avoid "unused variable" warning.
	for (my $blockOffset = $no_of_blocks - 1; $blockOffset >= 0; $blockOffset-- ) {
		$blockAddr = $firstBlock + $blockOffset;
		#// Sector number - only on first line



( run in 1.189 second using v1.01-cache-2.11-cpan-39bf76dae61 )