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 )