Mifare Classic RFID tag dump: PHP parsing library

You can get a dump for testing by the link: https://github.com/zhovner/mfdread/raw/master/dump.mfd

File extension

mfd

KS implementation details

License: BSD-2-Clause
Minimal Kaitai Struct required: 0.9

References

This page hosts a formal specification of Mifare Classic RFID tag dump using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

PHP source code to parse Mifare Classic RFID tag dump

MifareClassic.php

<?php
// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

/**
 * You can get a dump for testing by the link: https://github.com/zhovner/mfdread/raw/master/dump.mfd
 */

namespace {
    class MifareClassic extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, \Kaitai\Struct\Struct $_parent = null, \MifareClassic $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_read();
        }

        private function _read() {
            $this->_m__raw_sectors = [];
            $this->_m_sectors = [];
            $i = 0;
            while (!$this->_io->isEof()) {
                $this->_m__raw_sectors[] = $this->_io->readBytes(((($i >= 32 ? 4 : 1) * 4) * 16));
                $_io__raw_sectors = new \Kaitai\Struct\Stream(end($this->_m__raw_sectors));
                $this->_m_sectors[] = new \MifareClassic\Sector($i == 0, $_io__raw_sectors, $this, $this->_root);
                $i++;
            }
        }
        protected $_m_sectors;
        protected $_m__raw_sectors;
        public function sectors() { return $this->_m_sectors; }
        public function _raw_sectors() { return $this->_m__raw_sectors; }
    }
}

namespace MifareClassic {
    class Key extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, \MifareClassic\Trailer $_parent = null, \MifareClassic $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_read();
        }

        private function _read() {
            $this->_m_key = $this->_io->readBytes(6);
        }
        protected $_m_key;
        public function key() { return $this->_m_key; }
    }
}

namespace MifareClassic {
    class Sector extends \Kaitai\Struct\Struct {
        public function __construct(bool $hasManufacturer, \Kaitai\Struct\Stream $_io, \MifareClassic $_parent = null, \MifareClassic $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_m_hasManufacturer = $hasManufacturer;
            $this->_read();
        }

        private function _read() {
            if ($this->hasManufacturer()) {
                $this->_m_manufacturer = new \MifareClassic\Manufacturer($this->_io, $this, $this->_root);
            }
            $this->_m__raw_dataFiller = $this->_io->readBytes((($this->_io()->size() - $this->_io()->pos()) - 16));
            $_io__raw_dataFiller = new \Kaitai\Struct\Stream($this->_m__raw_dataFiller);
            $this->_m_dataFiller = new \MifareClassic\Sector\Filler($_io__raw_dataFiller, $this, $this->_root);
            $this->_m_trailer = new \MifareClassic\Trailer($this->_io, $this, $this->_root);
        }
        protected $_m_blockSize;
        public function blockSize() {
            if ($this->_m_blockSize !== null)
                return $this->_m_blockSize;
            $this->_m_blockSize = 16;
            return $this->_m_blockSize;
        }
        protected $_m_data;
        public function data() {
            if ($this->_m_data !== null)
                return $this->_m_data;
            $this->_m_data = $this->dataFiller()->data();
            return $this->_m_data;
        }
        protected $_m_blocks;
        public function blocks() {
            if ($this->_m_blocks !== null)
                return $this->_m_blocks;
            $io = $this->dataFiller()->_io();
            $_pos = $io->pos();
            $io->seek(0);
            $this->_m_blocks = [];
            $i = 0;
            while (!$io->isEof()) {
                $this->_m_blocks[] = $io->readBytes($this->blockSize());
                $i++;
            }
            $io->seek($_pos);
            return $this->_m_blocks;
        }
        protected $_m_values;
        public function values() {
            if ($this->_m_values !== null)
                return $this->_m_values;
            $io = $this->dataFiller()->_io();
            $_pos = $io->pos();
            $io->seek(0);
            $this->_m_values = new \MifareClassic\Sector\Values($io, $this, $this->_root);
            $io->seek($_pos);
            return $this->_m_values;
        }
        protected $_m_manufacturer;
        protected $_m_dataFiller;
        protected $_m_trailer;
        protected $_m_hasManufacturer;
        protected $_m__raw_dataFiller;
        public function manufacturer() { return $this->_m_manufacturer; }
        public function dataFiller() { return $this->_m_dataFiller; }
        public function trailer() { return $this->_m_trailer; }
        public function hasManufacturer() { return $this->_m_hasManufacturer; }
        public function _raw_dataFiller() { return $this->_m__raw_dataFiller; }
    }
}

namespace MifareClassic\Sector {
    class Values extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, \MifareClassic\Sector $_parent = null, \MifareClassic $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_read();
        }

        private function _read() {
            $this->_m_values = [];
            $i = 0;
            while (!$this->_io->isEof()) {
                $this->_m_values[] = new \MifareClassic\Sector\Values\ValueBlock($this->_io, $this, $this->_root);
                $i++;
            }
        }
        protected $_m_values;
        public function values() { return $this->_m_values; }
    }
}

namespace MifareClassic\Sector\Values {
    class ValueBlock extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, \MifareClassic\Sector\Values $_parent = null, \MifareClassic $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_read();
        }

        private function _read() {
            $this->_m_valuez = [];
            $n = 3;
            for ($i = 0; $i < $n; $i++) {
                $this->_m_valuez[] = $this->_io->readU4le();
            }
            $this->_m_addrz = [];
            $n = 4;
            for ($i = 0; $i < $n; $i++) {
                $this->_m_addrz[] = $this->_io->readU1();
            }
        }
        protected $_m_addr;
        public function addr() {
            if ($this->_m_addr !== null)
                return $this->_m_addr;
            if ($this->valid()) {
                $this->_m_addr = $this->addrz()[0];
            }
            return $this->_m_addr;
        }
        protected $_m_addrValid;
        public function addrValid() {
            if ($this->_m_addrValid !== null)
                return $this->_m_addrValid;
            $this->_m_addrValid =  (($this->addrz()[0] == ~($this->addrz()[1])) && ($this->addrz()[0] == $this->addrz()[2]) && ($this->addrz()[1] == $this->addrz()[3])) ;
            return $this->_m_addrValid;
        }
        protected $_m_valid;
        public function valid() {
            if ($this->_m_valid !== null)
                return $this->_m_valid;
            $this->_m_valid =  (($this->valueValid()) && ($this->addrValid())) ;
            return $this->_m_valid;
        }
        protected $_m_valueValid;
        public function valueValid() {
            if ($this->_m_valueValid !== null)
                return $this->_m_valueValid;
            $this->_m_valueValid =  (($this->valuez()[0] == ~($this->valuez()[1])) && ($this->valuez()[0] == $this->valuez()[2])) ;
            return $this->_m_valueValid;
        }
        protected $_m_value;
        public function value() {
            if ($this->_m_value !== null)
                return $this->_m_value;
            if ($this->valid()) {
                $this->_m_value = $this->valuez()[0];
            }
            return $this->_m_value;
        }
        protected $_m_valuez;
        protected $_m_addrz;
        public function valuez() { return $this->_m_valuez; }
        public function addrz() { return $this->_m_addrz; }
    }
}

/**
 * only to create _io
 */

namespace MifareClassic\Sector {
    class Filler extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, \MifareClassic\Sector $_parent = null, \MifareClassic $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_read();
        }

        private function _read() {
            $this->_m_data = $this->_io->readBytes($this->_io()->size());
        }
        protected $_m_data;
        public function data() { return $this->_m_data; }
    }
}

namespace MifareClassic {
    class Manufacturer extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, \MifareClassic\Sector $_parent = null, \MifareClassic $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_read();
        }

        private function _read() {
            $this->_m_nuid = $this->_io->readU4le();
            $this->_m_bcc = $this->_io->readU1();
            $this->_m_sak = $this->_io->readU1();
            $this->_m_atqa = $this->_io->readU2le();
            $this->_m_manufacturer = $this->_io->readBytes(8);
        }
        protected $_m_nuid;
        protected $_m_bcc;
        protected $_m_sak;
        protected $_m_atqa;
        protected $_m_manufacturer;

        /**
         * beware for 7bytes UID it goes over next fields
         */
        public function nuid() { return $this->_m_nuid; }
        public function bcc() { return $this->_m_bcc; }
        public function sak() { return $this->_m_sak; }
        public function atqa() { return $this->_m_atqa; }

        /**
         * may contain manufacture date as BCD
         */
        public function manufacturer() { return $this->_m_manufacturer; }
    }
}

namespace MifareClassic {
    class Trailer extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, \MifareClassic\Sector $_parent = null, \MifareClassic $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_read();
        }

        private function _read() {
            $this->_m_keyA = new \MifareClassic\Key($this->_io, $this, $this->_root);
            $this->_m__raw_accessBits = $this->_io->readBytes(3);
            $_io__raw_accessBits = new \Kaitai\Struct\Stream($this->_m__raw_accessBits);
            $this->_m_accessBits = new \MifareClassic\Trailer\AccessConditions($_io__raw_accessBits, $this, $this->_root);
            $this->_m_userByte = $this->_io->readU1();
            $this->_m_keyB = new \MifareClassic\Key($this->_io, $this, $this->_root);
        }
        protected $_m_acBits;
        public function acBits() {
            if ($this->_m_acBits !== null)
                return $this->_m_acBits;
            $this->_m_acBits = 3;
            return $this->_m_acBits;
        }
        protected $_m_acsInSector;
        public function acsInSector() {
            if ($this->_m_acsInSector !== null)
                return $this->_m_acsInSector;
            $this->_m_acsInSector = 4;
            return $this->_m_acsInSector;
        }
        protected $_m_acCountOfChunks;
        public function acCountOfChunks() {
            if ($this->_m_acCountOfChunks !== null)
                return $this->_m_acCountOfChunks;
            $this->_m_acCountOfChunks = ($this->acBits() * 2);
            return $this->_m_acCountOfChunks;
        }
        protected $_m_keyA;
        protected $_m_accessBits;
        protected $_m_userByte;
        protected $_m_keyB;
        protected $_m__raw_accessBits;
        public function keyA() { return $this->_m_keyA; }
        public function accessBits() { return $this->_m_accessBits; }
        public function userByte() { return $this->_m_userByte; }
        public function keyB() { return $this->_m_keyB; }
        public function _raw_accessBits() { return $this->_m__raw_accessBits; }
    }
}

namespace MifareClassic\Trailer {
    class AccessConditions extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, \MifareClassic\Trailer $_parent = null, \MifareClassic $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_read();
        }

        private function _read() {
            $this->_m_rawChunks = [];
            $n = $this->_parent()->acCountOfChunks();
            for ($i = 0; $i < $n; $i++) {
                $this->_m_rawChunks[] = $this->_io->readBitsIntBe(4);
            }
        }
        protected $_m_dataAcs;
        public function dataAcs() {
            if ($this->_m_dataAcs !== null)
                return $this->_m_dataAcs;
            $_pos = $this->_io->pos();
            $this->_io->seek(0);
            $this->_m_dataAcs = [];
            $n = ($this->_parent()->acsInSector() - 1);
            for ($i = 0; $i < $n; $i++) {
                $this->_m_dataAcs[] = new \MifareClassic\Trailer\AccessConditions\DataAc($this->acsRaw()[$i], $this->_io, $this, $this->_root);
            }
            $this->_io->seek($_pos);
            return $this->_m_dataAcs;
        }
        protected $_m_remaps;
        public function remaps() {
            if ($this->_m_remaps !== null)
                return $this->_m_remaps;
            $_pos = $this->_io->pos();
            $this->_io->seek(0);
            $this->_m_remaps = [];
            $n = $this->_parent()->acBits();
            for ($i = 0; $i < $n; $i++) {
                $this->_m_remaps[] = new \MifareClassic\Trailer\AccessConditions\ChunkBitRemap($i, $this->_io, $this, $this->_root);
            }
            $this->_io->seek($_pos);
            return $this->_m_remaps;
        }
        protected $_m_acsRaw;
        public function acsRaw() {
            if ($this->_m_acsRaw !== null)
                return $this->_m_acsRaw;
            $_pos = $this->_io->pos();
            $this->_io->seek(0);
            $this->_m_acsRaw = [];
            $n = $this->_parent()->acsInSector();
            for ($i = 0; $i < $n; $i++) {
                $this->_m_acsRaw[] = new \MifareClassic\Trailer\AccessConditions\Ac($i, $this->_io, $this, $this->_root);
            }
            $this->_io->seek($_pos);
            return $this->_m_acsRaw;
        }
        protected $_m_trailerAc;
        public function trailerAc() {
            if ($this->_m_trailerAc !== null)
                return $this->_m_trailerAc;
            $_pos = $this->_io->pos();
            $this->_io->seek(0);
            $this->_m_trailerAc = new \MifareClassic\Trailer\AccessConditions\TrailerAc($this->acsRaw()[($this->_parent()->acsInSector() - 1)], $this->_io, $this, $this->_root);
            $this->_io->seek($_pos);
            return $this->_m_trailerAc;
        }
        protected $_m_chunks;
        public function chunks() {
            if ($this->_m_chunks !== null)
                return $this->_m_chunks;
            $_pos = $this->_io->pos();
            $this->_io->seek(0);
            $this->_m_chunks = [];
            $n = $this->_parent()->acBits();
            for ($i = 0; $i < $n; $i++) {
                $this->_m_chunks[] = new \MifareClassic\Trailer\AccessConditions\ValidChunk($this->rawChunks()[$this->remaps()[$i]->invChunkNo()], $this->rawChunks()[$this->remaps()[$i]->chunkNo()], $this->_io, $this, $this->_root);
            }
            $this->_io->seek($_pos);
            return $this->_m_chunks;
        }
        protected $_m_rawChunks;
        public function rawChunks() { return $this->_m_rawChunks; }
    }
}

namespace MifareClassic\Trailer\AccessConditions {
    class TrailerAc extends \Kaitai\Struct\Struct {
        public function __construct(\MifareClassic\Trailer\AccessConditions\Ac $ac, \Kaitai\Struct\Stream $_io, \MifareClassic\Trailer\AccessConditions $_parent = null, \MifareClassic $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_m_ac = $ac;
            $this->_read();
        }

        private function _read() {
        }
        protected $_m_canReadKeyB;

        /**
         * key A is required
         */
        public function canReadKeyB() {
            if ($this->_m_canReadKeyB !== null)
                return $this->_m_canReadKeyB;
            $this->_m_canReadKeyB = $this->ac()->invShiftVal() <= 2;
            return $this->_m_canReadKeyB;
        }
        protected $_m_canWriteKeys;
        public function canWriteKeys() {
            if ($this->_m_canWriteKeys !== null)
                return $this->_m_canWriteKeys;
            $this->_m_canWriteKeys =  ((\Kaitai\Struct\Stream::mod(($this->ac()->invShiftVal() + 1), 3) != 0) && ($this->ac()->invShiftVal() < 6)) ;
            return $this->_m_canWriteKeys;
        }
        protected $_m_canWriteAccessBits;
        public function canWriteAccessBits() {
            if ($this->_m_canWriteAccessBits !== null)
                return $this->_m_canWriteAccessBits;
            $this->_m_canWriteAccessBits = $this->ac()->bits()[2]->b();
            return $this->_m_canWriteAccessBits;
        }
        protected $_m_keyBControlsWrite;
        public function keyBControlsWrite() {
            if ($this->_m_keyBControlsWrite !== null)
                return $this->_m_keyBControlsWrite;
            $this->_m_keyBControlsWrite = !($this->canReadKeyB());
            return $this->_m_keyBControlsWrite;
        }
        protected $_m_ac;
        public function ac() { return $this->_m_ac; }
    }
}

namespace MifareClassic\Trailer\AccessConditions {
    class ChunkBitRemap extends \Kaitai\Struct\Struct {
        public function __construct(int $bitNo, \Kaitai\Struct\Stream $_io, \MifareClassic\Trailer\AccessConditions $_parent = null, \MifareClassic $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_m_bitNo = $bitNo;
            $this->_read();
        }

        private function _read() {
        }
        protected $_m_shiftValue;
        public function shiftValue() {
            if ($this->_m_shiftValue !== null)
                return $this->_m_shiftValue;
            $this->_m_shiftValue = ($this->bitNo() == 1 ? -1 : 1);
            return $this->_m_shiftValue;
        }
        protected $_m_chunkNo;
        public function chunkNo() {
            if ($this->_m_chunkNo !== null)
                return $this->_m_chunkNo;
            $this->_m_chunkNo = \Kaitai\Struct\Stream::mod((($this->invChunkNo() + $this->shiftValue()) + $this->_parent()->_parent()->acCountOfChunks()), $this->_parent()->_parent()->acCountOfChunks());
            return $this->_m_chunkNo;
        }
        protected $_m_invChunkNo;
        public function invChunkNo() {
            if ($this->_m_invChunkNo !== null)
                return $this->_m_invChunkNo;
            $this->_m_invChunkNo = ($this->bitNo() + $this->shiftValue());
            return $this->_m_invChunkNo;
        }
        protected $_m_bitNo;
        public function bitNo() { return $this->_m_bitNo; }
    }
}

namespace MifareClassic\Trailer\AccessConditions {
    class DataAc extends \Kaitai\Struct\Struct {
        public function __construct(\MifareClassic\Trailer\AccessConditions\Ac $ac, \Kaitai\Struct\Stream $_io, \MifareClassic\Trailer\AccessConditions $_parent = null, \MifareClassic $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_m_ac = $ac;
            $this->_read();
        }

        private function _read() {
        }
        protected $_m_readKeyARequired;
        public function readKeyARequired() {
            if ($this->_m_readKeyARequired !== null)
                return $this->_m_readKeyARequired;
            $this->_m_readKeyARequired = $this->ac()->val() <= 4;
            return $this->_m_readKeyARequired;
        }
        protected $_m_writeKeyBRequired;
        public function writeKeyBRequired() {
            if ($this->_m_writeKeyBRequired !== null)
                return $this->_m_writeKeyBRequired;
            $this->_m_writeKeyBRequired =  (( ((!($this->readKeyARequired())) || ($this->readKeyBRequired())) ) && (!($this->ac()->bits()[0]->b()))) ;
            return $this->_m_writeKeyBRequired;
        }
        protected $_m_writeKeyARequired;
        public function writeKeyARequired() {
            if ($this->_m_writeKeyARequired !== null)
                return $this->_m_writeKeyARequired;
            $this->_m_writeKeyARequired = $this->ac()->val() == 0;
            return $this->_m_writeKeyARequired;
        }
        protected $_m_readKeyBRequired;
        public function readKeyBRequired() {
            if ($this->_m_readKeyBRequired !== null)
                return $this->_m_readKeyBRequired;
            $this->_m_readKeyBRequired = $this->ac()->val() <= 6;
            return $this->_m_readKeyBRequired;
        }
        protected $_m_decrementAvailable;
        public function decrementAvailable() {
            if ($this->_m_decrementAvailable !== null)
                return $this->_m_decrementAvailable;
            $this->_m_decrementAvailable =  (( (($this->ac()->bits()[1]->b()) || (!($this->ac()->bits()[0]->b()))) ) && (!($this->ac()->bits()[2]->b()))) ;
            return $this->_m_decrementAvailable;
        }
        protected $_m_incrementAvailable;
        public function incrementAvailable() {
            if ($this->_m_incrementAvailable !== null)
                return $this->_m_incrementAvailable;
            $this->_m_incrementAvailable =  (( ((!($this->ac()->bits()[0]->b())) && (!($this->readKeyARequired())) && (!($this->readKeyBRequired()))) ) || ( ((!($this->ac()->bits()[0]->b())) && ($this->readKeyARequired()) && ($this->readKeyBRequired())) )) ;
            return $this->_m_incrementAvailable;
        }
        protected $_m_ac;
        public function ac() { return $this->_m_ac; }
    }
}

namespace MifareClassic\Trailer\AccessConditions {
    class Ac extends \Kaitai\Struct\Struct {
        public function __construct(int $index, \Kaitai\Struct\Stream $_io, \MifareClassic\Trailer\AccessConditions $_parent = null, \MifareClassic $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_m_index = $index;
            $this->_read();
        }

        private function _read() {
        }
        protected $_m_bits;
        public function bits() {
            if ($this->_m_bits !== null)
                return $this->_m_bits;
            $_pos = $this->_io->pos();
            $this->_io->seek(0);
            $this->_m_bits = [];
            $n = $this->_parent()->_parent()->acBits();
            for ($i = 0; $i < $n; $i++) {
                $this->_m_bits[] = new \MifareClassic\Trailer\AccessConditions\Ac\AcBit($this->index(), $this->_parent()->chunks()[$i]->chunk(), $this->_io, $this, $this->_root);
            }
            $this->_io->seek($_pos);
            return $this->_m_bits;
        }
        protected $_m_val;

        /**
         * c3 c2 c1
         */
        public function val() {
            if ($this->_m_val !== null)
                return $this->_m_val;
            $this->_m_val = ((($this->bits()[2]->n() << 2) | ($this->bits()[1]->n() << 1)) | $this->bits()[0]->n());
            return $this->_m_val;
        }
        protected $_m_invShiftVal;
        public function invShiftVal() {
            if ($this->_m_invShiftVal !== null)
                return $this->_m_invShiftVal;
            $this->_m_invShiftVal = ((($this->bits()[0]->n() << 2) | ($this->bits()[1]->n() << 1)) | $this->bits()[2]->n());
            return $this->_m_invShiftVal;
        }
        protected $_m_index;
        public function index() { return $this->_m_index; }
    }
}

namespace MifareClassic\Trailer\AccessConditions\Ac {
    class AcBit extends \Kaitai\Struct\Struct {
        public function __construct(int $i, int $chunk, \Kaitai\Struct\Stream $_io, \MifareClassic\Trailer\AccessConditions\Ac $_parent = null, \MifareClassic $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_m_i = $i;
            $this->_m_chunk = $chunk;
            $this->_read();
        }

        private function _read() {
        }
        protected $_m_n;
        public function n() {
            if ($this->_m_n !== null)
                return $this->_m_n;
            $this->_m_n = (($this->chunk() >> $this->i()) & 1);
            return $this->_m_n;
        }
        protected $_m_b;
        public function b() {
            if ($this->_m_b !== null)
                return $this->_m_b;
            $this->_m_b = $this->n() == 1;
            return $this->_m_b;
        }
        protected $_m_i;
        protected $_m_chunk;
        public function i() { return $this->_m_i; }
        public function chunk() { return $this->_m_chunk; }
    }
}

namespace MifareClassic\Trailer\AccessConditions {
    class ValidChunk extends \Kaitai\Struct\Struct {
        public function __construct(int $invChunk, int $chunk, \Kaitai\Struct\Stream $_io, \MifareClassic\Trailer\AccessConditions $_parent = null, \MifareClassic $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_m_invChunk = $invChunk;
            $this->_m_chunk = $chunk;
            $this->_read();
        }

        private function _read() {
        }
        protected $_m_valid;
        public function valid() {
            if ($this->_m_valid !== null)
                return $this->_m_valid;
            $this->_m_valid = ($this->invChunk() ^ $this->chunk()) == 15;
            return $this->_m_valid;
        }
        protected $_m_invChunk;
        protected $_m_chunk;
        public function invChunk() { return $this->_m_invChunk; }

        /**
         * c3 c2 c1 c0
         */
        public function chunk() { return $this->_m_chunk; }
    }
}