/*! @file

    @id $Id: cardos.hxx 3826 2012-05-21 12:25:29Z marc $
*/
//       1         2         3         4         5         6         7         8
// 45678901234567890123456789012345678901234567890123456789012345678901234567890

#ifndef __CARDOS_HXX__
#define __CARDOS_HXX__

#include <pcsc.hxx>
#include <cryptaux.hxx>
#include <openssl.hxx>
#include <mrw/string.hxx>
#include <stdexcept>

#ifndef CARDOS_LOG
#  ifdef DEBUG_SECRETS
#    define CARDOS_LOG(X) std::clog<<X<<std::endl
#  else
#    define CARDOS_LOG(X) // no logging by default
// use e.g. #define CARDOS_LOG(X) std::clog<<X<<std::endl
#  endif
#endif

/** @defgroup gcardos C++ Access to Siemens CardOS 4.4

    Implements APDUs for accessing Siemens CardOS V4.4 smartcards
    based on <a
    href="https://de.wikipedia.org/wiki/Application_Protocol_Data_Unit">ISO
    7816-4</a> */
//@{
/// @defgroup cardosexception CardOS Exceptions
/// @defgroup cardostypes CardOS Types
/// @defgroup cardoslib CardOS Library
//@}

/// @ref gcardos @copydoc gcardos
namespace cardos {

  /// @addtogroup cardosexception CardOS Exceptions
  //@{
  //============================================================================
  //----------------------------------------------------------------------------
  class exception: public pcsc::exception {
    public:
      exception(const std::string& reason) throw():
          pcsc::exception("cardos: "+reason) {
      }
  };
  //----------------------------------------------------------------------------
  class wrong_pin: public exception {
    public:
      wrong_pin(const std::string& s) throw(): exception("wrong pin "+s) {}
  };
  //----------------------------------------------------------------------------
  class pin_locked: public exception {
    public:
      pin_locked() throw(): exception("pin is locked and cannot be changed") {}
  };
  //----------------------------------------------------------------------------
  class wrong_puk: public wrong_pin {
    public:
      wrong_puk(const std::string& s) throw(): wrong_pin("wrong puk "+s) {}
  };
  //----------------------------------------------------------------------------
  class wrong_result: public exception {
    public:
      wrong_result(const std::string& reason, const std::string& data) throw():
          exception("wrong result, "+reason+": "+crypto::hex(data)) {}
      wrong_result(const std::string& reason) throw():
          exception("wrong result, "+reason) {}
  };
  //----------------------------------------------------------------------------
  class runtime_error: public exception {
    public:
      runtime_error(const std::string& reason) throw():
          exception("runtime error, "+reason) {}
      runtime_error(const std::string& reason, const std::string& data) throw():
          exception("runtime error, "+reason+": "+crypto::hex(data)) {}
  };
  //----------------------------------------------------------------------------
  class unexpected_challenge_length: public exception {
    public:
      unexpected_challenge_length(const std::string& data) throw():
          exception("challenge should be 8 bytes, challenge is: "
                    +crypto::hex(data)) {}
  };
  //----------------------------------------------------------------------------
  class card_transmission_failed: public exception {
    public:
      card_transmission_failed(const std::string& position,
                               const std::string& reason) throw():
          exception("transmission to card failed: "+position
                    +" reason: "+reason) {
      }
  };
  //----------------------------------------------------------------------------
  class wrong_dataformat: public exception {
    public:
      wrong_dataformat(const std::string& data,
                       const std::string& reason) throw():
          exception("wrong dataformat: "+crypto::hex(data)
                    +" reason: "+reason) {
      }
  };
  //----------------------------------------------------------------------------
  class too_large_for_tlv: public wrong_dataformat {
    public:
      too_large_for_tlv(const std::string& data) throw():
          wrong_dataformat(data, "data size too long for TLV") {
      }
  };
  //----------------------------------------------------------------------------
  class array_range: public exception {
    public:
      array_range(unsigned long i, unsigned long j) throw():
          exception("array index out of range: "+mrw::string(i)
                    +"; length: "+mrw::string(j)) {
      }
  };
  //@}

  //============================================================================
  /// @addtogroup cardoslib
  //@{

  class BerValue {
      
    public:
      
      enum Class {
        UNIVERSAL        = 0x00,
        APPLICATION      = 0x40,
        CONTEXT_SPECIFIC = 0x80,
        PRIVATE          = 0xC0
      };
      
      enum PC {
        PRIMITIVE   = 0x00,
        CONSTRUCTED = 0x20
      };
      
      enum Type {
        END_OF_CONTENT           = 0x00,
        BOOLEAN                  = 0x01,
        INTEGER                  = 0x02,
        BIT_STRING               = 0x03,
        OCTET_STRING             = 0x04,
        EMPTY                    = 0x05,
        OBJECT_IDENTIFIER        = 0x06,
        OBJECT_DESCRIPTOR        = 0x07,
        EXTERNAL                 = 0x08,
        REAL                     = 0x09,
        ENUMERATED               = 0x0A,
        EMBEDDED_PDV             = 0x0B,
        UTF8_STRING              = 0x0C,
        RELATIVE_OID             = 0x0D,
        SEQUENCE                 = 0x10,
        SET                      = 0x11,
        NUMERIC_STRING           = 0x12,
        PRINTABLE_STRING         = 0x13,
        T61_STRING               = 0x14,
        VIDEOTEX_STRING          = 0x15,
        IA5_STRING               = 0x16,
        UTC_TIME                 = 0x17,
        GENERALIZED_TIME         = 0x18,
        GRAPHIC_STRING           = 0x19,
        VISIBLE_STRING           = 0x1A,
        GENERAL_STRING           = 0x1B,
        UNIVERSAL_STRING         = 0x1C,
        CHARACTER_STRING         = 0x1D,
        BMP_STRING               = 0x1E,
      };

    protected: // use BerValues instead

      friend class BerValues;

      BerValue(const std::vector<BerValue>& sequence):
          _tag(PRIVATE|CONSTRUCTED|SEQUENCE),
          _sequence(sequence) {
      }
      
      BerValue(std::string& content) {
        set(content);
      }
      
      void set(std::string& content) {
        if (content.size()<2)
          throw wrong_dataformat(content, "not a BER, header size too small: \""
                                 +crypto::binToHex(content)+"\"");
        _tag = content[0];
        unsigned char length = content[1];
        _value = content.substr(2, length);
        if (content.size()<std::string::size_type(length)+2)
          throw wrong_dataformat(content, "not a BER, content size too"
                                 " small: \""+crypto::binToHex(_value)+"\"");
        content.erase(0, 2+length);
        if (tagType()==END_OF_CONTENT) return; // done
        if (isContainer())
          while (_value.size()) _sequence.push_back(BerValue(_value));
      }

    public:

      BerValue(const std::string& content) {
        std::string contentCopy(content);
        set(contentCopy);
      }

      BerValue(unsigned char t, const std::string& v):
          _tag(t), _value(v) {
        if (isContainer())
          while (_value.size()) _sequence.push_back(BerValue(_value));
      }
      
      BerValue(unsigned char t, const std::vector<BerValue>& vs):
          _tag(t), _sequence(vs) {
        if (!isContainer())
          throw runtime_error("BER tag 0x"+crypto::binToHex(_tag)
                              +" is not a container");
      }
      
      unsigned char tagClass() {
        return _tag&0xC0;
      }
      
      unsigned char tagPC() {
        return _tag&0x20;
      }
      
      unsigned char tagType() {
        return _tag&0x1F;
      }

      bool isContainer() {
        return tagPC()==CONSTRUCTED;
      }

      std::vector<BerValue>::size_type size() {
        return _sequence.size();
      }

      unsigned char tag() {
        return _tag;
      }

      BerValue operator[](std::vector<BerValue>::size_type i) {
        if (i>=_sequence.size()) throw array_range(i, _sequence.size());
        return _sequence[i];
      }

      std::string binary() {
        std::string res;
        res.push_back(_tag);
        if (isContainer()) {
          std::string seq;
          for (std::vector<BerValue>::iterator it(_sequence.begin());
               it!=_sequence.end(); ++it) {
            seq += it->binary();
          }
          if (seq.size()>255) throw too_large_for_tlv(seq);
          res += (char)seq.size();
          res += seq;
        } else {
          if (_value.size()>255) throw too_large_for_tlv(_value);
          res += (char)_value.size();
          res += _value;
        }
        return res;
      }

      std::string string() {
        return _value;
      }

      std::string hex() {
        return crypto::hex(_value);
      }

      unsigned long ulong() {
        return crypto::ulongFromBinary(_value);
      }
      
      std::string print(int indent=0, int indentStep = 4) {
        std::stringstream ss;
        ss<<std::string(indent*indentStep, ' ')<<'['<<crypto::binToHex(_tag)
          <<'=';
        switch (tagClass()) {
          case UNIVERSAL: ss<<"UNIVERSAL,"; break;
          case APPLICATION: ss<<"APPLICATION,"; break;
          case CONTEXT_SPECIFIC: ss<<"CONTEXT_SPECIFIC,"; break;
          case PRIVATE: ss<<"PRIVATE,"; break;
          default: ss<<(unsigned int)tagClass();
        }
        switch (tagPC()) {
          case PRIMITIVE: ss<<"PRIMITIVE,"; break;
          case CONSTRUCTED: ss<<"CONSTRUCTED,"; break;
          default: ss<<(unsigned int)tagPC();
        }
        switch (tagType()) {
          case END_OF_CONTENT: ss<<"END_OF_CONTENT"; break;
          case BOOLEAN: ss<<"BOOLEAN"; break;
          case INTEGER: ss<<"INTEGER"; break;
          case BIT_STRING: ss<<"BIT_STRING"; break;
          case OCTET_STRING: ss<<"OCTET_STRING"; break;
          case EMPTY: ss<<"EMPTY"; break;
          case OBJECT_IDENTIFIER: ss<<"OBJECT_IDENTIFIER"; break;
          case OBJECT_DESCRIPTOR: ss<<"OBJECT_DESCRIPTOR"; break;
          case EXTERNAL: ss<<"EXTERNAL"; break;
          case REAL: ss<<"REAL"; break;
          case ENUMERATED: ss<<"ENUMERATED"; break;
          case EMBEDDED_PDV: ss<<"EMBEDDED_PDV"; break;
          case UTF8_STRING: ss<<"UTF8_STRING"; break;
          case RELATIVE_OID: ss<<"RELATIVE_OID"; break;
          case SEQUENCE: ss<<"SEQUENCE"; break;
          case SET: ss<<"SET"; break;
          case NUMERIC_STRING: ss<<"NUMERIC_STRING"; break;
          case PRINTABLE_STRING: ss<<"PRINTABLE_STRING"; break;
          case T61_STRING: ss<<"T61_STRING"; break;
          case VIDEOTEX_STRING: ss<<"VIDEOTEX_STRING"; break;
          case IA5_STRING: ss<<"IA5_STRING"; break;
          case UTC_TIME: ss<<"UTC_TIME"; break;
          case GENERALIZED_TIME: ss<<"GENERALIZED_TIME"; break;
          case GRAPHIC_STRING: ss<<"GRAPHIC_STRING"; break;
          case VISIBLE_STRING: ss<<"VISIBLE_STRING"; break;
          case GENERAL_STRING: ss<<"GENERAL_STRING"; break;
          case UNIVERSAL_STRING: ss<<"UNIVERSAL_STRING"; break;
          case CHARACTER_STRING: ss<<"CHARACTER_STRING"; break;
          case BMP_STRING: ss<<"BMP_STRING"; break;
          default: ss<<(unsigned int)tagType();
        }
        ss<<"] = ";
        if (isContainer()) {
          int i(0);
          ss<<"{"<<std::endl;
          for (std::vector<BerValue>::iterator it(_sequence.begin());
               it!=_sequence.end(); ++it) {
            ss<<std::string((indent+1)*indentStep, ' ')
              <<"["<<i++<<"] {"
              <<std::endl
              <<it->print(indent+2, indentStep)
              <<std::endl
              <<std::string((indent+1)*indentStep, ' ')<<'}'
              <<std::endl;
          }
          ss<<std::string(indent*indentStep, ' ')<<'}';
        } else {
          ss<<"0x"<<crypto::binToHex(_value)<<" = \"";
          for (std::string::const_iterator it(_value.begin());
               it!=_value.end(); ++it)
            ss<<(isprint(*it)?*it:'.');
          ss<<"\"";
        }
        return ss.str();
      }
      
    private:
      
      unsigned char _tag;
      std::string _value;
      std::vector<BerValue> _sequence;

  };

  //============================================================================
  /// Store a sequence of BerValue
  class BerValues: public std::vector<BerValue> {
    public:
      BerValues() {}
      BerValues(const std::string& content) {
        std::string contentCopy(content);
        while (contentCopy.size()) push_back(BerValue(contentCopy));
      }
      BerValues& operator=(std::string& content) {
        clear();
        std::string contentCopy(content);
        while (contentCopy.size()) push_back(BerValue(contentCopy));
        return *this;
      }
      BerValues& operator+=(const std::string& content) {
        std::string contentCopy(content);
        while (contentCopy.size()) push_back(BerValue(contentCopy));
        return *this;
      }
      BerValues& operator+=(const BerValue& value) {
        push_back(value);
        return *this;
      }
      BerValues& operator+=(const BerValues& values) {
        insert(end(), values.begin(), values.end());
        return *this;
      }
      std::string binary() {
        std::string res;
        for (BerValues::iterator it(begin()); it!=end(); ++it)
          res += it->binary();
        return res;
      }
      std::string print(int indent=0, int indentStep = 4) {
        std::stringstream ss;
        if (size()==1) {
          ss<<std::string(indent*indentStep, ' ')<<at(0).print();
        } else {
          int i(0);
          ss<<"{"<<std::endl;
          for (iterator it(begin()); it!=end(); ++it) {
            ss<<std::string((indent+1)*indentStep, ' ')
              <<"["<<i++<<"] {"
              <<std::endl
              <<it->print(indent+2, indentStep)
              <<std::endl
              <<std::string((indent+1)*indentStep, ' ')<<'}'
              <<std::endl;
          }
          ss<<std::string(indent*indentStep, ' ')<<'}';
        }
        return ss.str();
      }
  };

  //============================================================================
  /// Implements CardOS V4.4 commands.
  /** Directly sends CardOS V4.4 commands to a smart card using APDUs. */
  class Commands {
    
    public:

      //------------------------------------------------------------------------
      /// @name Setup Smart Card Reader
      //@{

      /// Uninitialized class
      /** Use @ref reader to setup assign smart card reader. */
      Commands() {}

      /// Initialize with given smart card reader.
      Commands(std::shared_ptr<pcsc::Connection::Reader> r):
          _reader(r) {
      }

      /// Set smart card reader.
      void reader(std::shared_ptr<pcsc::Connection::Reader> r) {
        _reader = r;
      }

      //@}

      //------------------------------------------------------------------------
      /// @name Basic CardOS Commands
      //@{

      //! Activates a deactivated file or file tree.
      /*! This command reactivates the current file in the file
          system. If there were any child files (file tree), they can
          subsequently be selected and thus used again.  The command
          will not return an error, if the current file is already
          active.

          @pre The command can only be executed, if the right
               referenced by the file’s AC ACTIVATE is granted in the
               security status of the current DF. The command cannot
               be applied to CODE files. */
      void activateFile() {
        CRYPTOLOG("log");
        check(send(0x00, 0x44, 0x00, 0x00));
      }

      //! Manages the transaction buffer for command transactions
      //! and/or command sequence transactions
      /*! The command allocates or frees a transaction buffer
          
          In case of mode Allocate a possibly existing old buffer
          block is marked as unused and a new block with size HI-LO is
          initialized. The ID of the new buffer is returned as
          response.  After the command has been executed successfully,
          all the EEPROM contents, which will be affected by a command
          with the setting AutoTR=ON, will be stored in the allocated
          EEPROM buffer, so that in case of an unforeseen interruption
          the former EEPROM state can be restored after the next reset
          (see also SET TRANSACTION STATE command).

          In case of mode Free, the buffer with the ID in P2 will not
          be used anymore.

          The recommended buffer size is 300 bytes. A small buffer
          will increase the EEPROM stress.

          Buffers should be allocated and freed again by the
          application to reduce the EEPROM stress.

          <table>
          <caption>Bytes P1-P2</caption>
          <tr><td colspan="2">P1 (MODE)</td><td>P2</td><td>Meaning</td></tr>
          <tr><td>Bit 7</td><td>Bits 6 – 0</td><td></td><td></td></tr>
          <tr>
          <td>1</td><td>HI value</td><td>LO value</td>
          <td>Allocate buffer with size HI-LO</td>
          </tr><tr>
          <td>0</td><td>rfu</td><td>ID</td>
          <td>Free buffer with ID in P2</td>
          </tr>
          </table>

          @pre The command can only be executed, if the right
               referenced by the MF’s AC ALLOCATE is granted.

          @returns 1 byte: ID of allocated buffer

          @see freeTransactionBuffer */
      std::string allocateTransactionBuffer(unsigned short size) {
        CRYPTOLOG("log");
        if (size>0x7FFF) throw runtime_error("requested buffer too large");
        return check(send(0x80, 0x12, (unsigned char)(8|size>>8),
                          (unsigned char)(size&0xFF)));
      }

      //! Free transaction buffer
      /*! @see allocateTransactionBuffer */
      void freeTransactionBuffer(unsigned char id) {
        CRYPTOLOG("log");
        check(send(0x80, 0x12, 0x00, id));
      }

      //! Creates a new record in a record oriented file
      /*! This command creates a new record in the currently selected
          file, or, if P2 is not 00h, in the file, which is referenced
          by the Short File Identifier (SFI). The command can only be
          used on LINEAR FIXED, CYCLIC FIXED and LINEAR TLV files.
          
          In the parameter EF-ID a file ID can be specified with an
          SFI. See table 3.4-1 for its format. The thus referenced
          file will then be selected.

          EF-ID Byte P2:
          - 0x00 → use current EF
          - bits 7-3 = ppppp, bits 2-0=0 → use SFI ppppp (11111 not allowed)
          - other value → rfu

          If an SFI is present the actual FID to be used is built by
          adding the constant value FE00h to the specified SFI.

          The Record_Data contain the contents of the new record. The
          length of the Record_Data must not be more than 254 bytes.

          For LINEAR FIXED or CYCLIC FIXED files the length of the
          Record_Data must match the length, which was specified by
          the belonging CREATE FILE command.

          For LINEAR TLV files the command data field must have a
          valid TLV format.

          If there is not sufficient file memory to store the new
          record, an error message is generated for all files, except
          for a CYCLIC FIXED file, where the previous record will be
          overwritten (same behavior as UPDATE RECORD in PREV mode).

          For all of file types the newly written record becomes the
          current record.  For CYCLIC FIXED files the newly written
          record becomes the logical first record.

          For LINEAR TLV files it is assumed that the length field of
          the record consists of one byte.  The tag byte is not
          interpreted by APPEND RECORD.

          @pre The command can only be executed, if the right
               referenced by the file’s AC APPEND is granted in the
               security status of the current DF. */
      void appendRecord(unsigned char efId, std::string data) {
        CRYPTOLOG("log");
        check(send(0x00, 0xE2, 0x00, efId, data));
      }

      //! Computes a digital signature of internally hashed data
      //! provided by the application and the card itself in order to
      //! authenticate the card to the application.
      /*! The CARD AUTHENTICATE command allows to check a card’s
          authenticity before personalization takes place.  It returns
          a digital signature, which is computed over an internal hash
          value.

          Inputs for the hash value calculation are:
          - The System_Challenge (s. Command Data Field)
          - the command header of the CARD AUTHENTICATE command
            (CLA, INS, P1, P2)
          - the global life cycle phase (s. GET DATA command, mode 83h)
          - the number of loaded Packages (s. GET DATA command, mode 88h)
          - the system internal parameters (9 bytes consisting 22h) and
          - optionally, the Chip Unique Identification Number
            (as indicated in mode byte P1, @ref getData, mode 81h,
            Bytes 11-16).
          
          The commands supports 2 modes indicated by the parameter P1:
          - 0x01: The Chip Unique Identification Number is not used for
                  the calculation of the hash value
          - 0x02: Include the Chip Unique Identification Number for the
                  calculation of hash value
          
          The mode 01h can be used to compute a constant signature for
          different cards of the same CardOS version, depending on
          their global life cycle phase and loaded system packages
          that use the system internal data The command uses a
          1536-bit private RSA2_SIG_SHA-1 key stored in ROM, see
          chapter 2.4.2.4

          <table>
            <caption>Format of the input for hash value calculation</caption>
            <tr>
              <th>System_Challenge</th>
              <th>Command Header<br/>CLA INS P1 P2</th>
              <th>Global Life Cycle Phase</th>
              <th>Internal Parameters</th>
              <th>Number of loaded Packages</th>
              <th>Chip Unique Identification Number (opt.)</th>
            </tr>
            <tr>
              <td>xxh…xxh</td>
              <td>80h 88h Mode 00h</td>
              <td>xxh</td>
              <td>22h…22h</td>
              <td>xxh</td>
              <td>xxh…xxh</td>
            </tr>
            <tr>
              <td>n bytes</td>
              <td>4 bytes</td>
              <td>1 byte</td>
              <td>9 bytes</td>
              <td>1 byte</td>
              <td>6 bytes/empty</td>
            </tr>
          </table>

          @pre The System_Challenge provided by the application must
               be greater or equal to 16 bytes.  Since the command
               does not support chaining, the length of the
               System_Challenge is limited by the IO buffer size.

          @note The application must use the corresponding RSA2 public
                key to verify the Digital Signature received from the
                card and thus establishes the card’s authenticity. The
                RSA2 public key is contained in the CardOS V4.4 PRNs.

          @return Digital Signature */
      std::string cardAuthenticate(bool hashInclIdentNum,
                                   std::string systemChallange) {
        CRYPTOLOG("log");
        return check(send(0x80, 0x88, hashInclIdentNum?0x01:0x02, 0x00,
                          systemChallange));
      }

      //! Changes the object data of any BS object except for PIN TEST objects
      void changeKeyData() {
        CRYPTOLOG("log");
        assert(!"not implemented");
      }

      //! Changes the object data of a PIN TEST object
      /** Changes a PIN. */
      void changeReferenceData(unsigned char id, std::string newData,
                               std::string oldData=std::string()) {
        CRYPTOLOG("log");
        check(send(0x00, 0x24, oldData.size()?0x00:0x01, 0x80|id,
                   oldData+newData));
      }

      //! Changes the data of a system key to the new key data
      //! provided with the command.
      void changeSystemKey() {
        CRYPTOLOG("log");
        assert(!"not implemented");
      }

      //! Creates a file (only EF or DF)
      void createFile(std::string /*path*/="", const std::string& /*data*/="") {
        CRYPTOLOG("log");
        // pcsc::Connection::Reader::Transaction lock(_reader);
        // if (path.size()) select(path);
        // check(send(0x00, 0xE0, 0x00, 0x00, 
        // check(send(0x00, 0xE0, 0x00, 0x00, BerValue(0x62h,
        // data).raw()));
        assert(!"not implemented");
      }

      //! Creates a EF file
      void createBinary(std::string path="", std::string id="",
                        const std::string data="") {
        CRYPTOLOG("log");
        pcsc::Connection::Reader::Transaction lock(_reader);
        if (path.size()) select(path);
        BerValues c;
        c += BerValue(0x80, crypto::toBinary(data.size()));
        c += BerValue(0x82, crypto::hexToBin("01"));
        std::string idbin(crypto::hexToBin(id));
        if (idbin.size()!=2) throw runtime_error("file id must be two bytes");
        c += BerValue(0x83, idbin);
        c += BerValue(0x85, std::string(1, (char)(1<<7)));
        c += BerValue(0x86, crypto::hexToBin("00000000000000"));
        check(send(0x00, 0xE0, 0x00, 0x00, BerValue(0x62, c).binary()));
        updateBinary(data);
      }

      //! Deactivates a file or a file tree
      void deactivateFile() {
        CRYPTOLOG("log");
        assert(!"not implemented");
      }

      //! Decreases a record value
      void decrease() {
        CRYPTOLOG("log");
        assert(!"not implemented");
      }

      //! Deletes a file (DF or EF)
      void deleteFile() {
        CRYPTOLOG("log");
        assert(!"not implemented");
      }

      enum FileTypes {DF=0x00, EF=0x01, DF_EF=0x02};

      //! Reads file information of EFs and/or DFs in the current DF
      BerValues directory(std::string path, FileTypes types=DF_EF,
                          unsigned char offset=0) {
        CRYPTOLOG("log");
        unsigned char o(offset);
        BerValues content;
        pcsc::Connection::Reader::Transaction lock(_reader);
        if (path.size()) select(path);
        /*while (o<(unsigned char)-1)*/ { /// @todo read until done
          std::string res(check(send(0x80, 0x16, types, o++)));
          //if (cardos::Commands::retCode(res)!=0x9000) break;
          content += res;
        }
        return content;
      }

      //! Enables an already loaded and activated but disabled license package.
      void enablePackage() {
        CRYPTOLOG("log");
        assert(!"not implemented");
      }

      //! Erases the file system in the EEPROM.
      void eraseFiles() {
        CRYPTOLOG("log");
        assert(!"not implemented");
      }

      //! Performs a challenge/response test
      void externalAuthenticate() {
        CRYPTOLOG("log");
        assert(!"not implemented");
      }

      //! Changes the life cycle phase from MANUFACTURING to
      //! ADMINISTRATION after creation of the MF or changes only from
      //! MANUFACTURING to INITIALIZATION.
      void format() {
        CRYPTOLOG("log");
        assert(!"not implemented");
      }

      //! Generates a key pair for the RSA/RSA2 algorithms within the card
      void generateKeyPair() {
        CRYPTOLOG("log");
        assert(!"not implemented");
      }

      //! Generates an internal random number (i.e. SC_Challenge)
      void getChallange() {
        CRYPTOLOG("log");
        assert(!"not implemented");
      }

      //! Reads system information
      std::string getData(unsigned char mode) {
        CRYPTOLOG("log");
        return check(send(0x00, 0xCA, 0x01, mode));
      }

      //! Product name, version, release date, copyright string
      std::string getDataProductName() {
        CRYPTOLOG("log");
        return getData(0x80);
      }

      struct ChipProductionData {
          std::string serial;
          unsigned char type;
          std::string id;
      };

      //! Chip production data as supplied by Infineon, PROM area
      ChipProductionData getDataChipProduction() {
        CRYPTOLOG("log");
        std::string code(getData(0x81));
        ChipProductionData res = {
          code.substr(8, 8),
          (unsigned char)code[9],
          code.substr(11, 6)
        };
        return res;
      }

      //! Transmits an external random number (i.e. System_Random) to
      //! the smart card
      /*! The command transmits an external random number of n bytes
          the so-called System_Random to the smart card. System_Random
          will be used for the next response- SM (SIG or ENC_SIG)
          calculation.  Valid values for n in short APDU mode are in
          the range 1 – 256. In extended APDU mode nmax is dependent
          on the preset data field length (nmax = data field length).

          The external random number System_Random can be used only
          once. System_Random is valid during the same smart card
          session until it has been used by response-SM or until it is
          overwritten by a new GIVE RANDOM command.

          The external random number is stored in the XRAM of the
          smart card.  */
      void giveRandom(std::string random) {
        CRYPTOLOG("log");
        check(send(0x80, 0x86, 0x00, 0x00, random));
      }

      //! Increases a record value
      /** This command increases the value of the current record of a
          cyclic fixed file by the @p size.

          EF-ID Byte P2:
          - 0x00 → use current EF
          - bits 7-3 = ppppp, bits 2-0=0 → use SFI ppppp (11111 not allowed)
          - other value → rfu */
      std::string increase(unsigned char efId=0, unsigned short size=1) {
        CRYPTOLOG("log");
        std::string data;
        data.resize(2);
        data[0] = char((unsigned char)(size>>8));
        data[1] = char((unsigned char)(size&0xff));
        return check(send(0x80, 0x32, 0x00, efId, data)); // 0x08 or 0x80?
        // old code was 0x80, manual says 0x08 (manual exactly says: "8xh")
      }

      //! Manufacturer use only
      void initializeEeprom() {
        CRYPTOLOG("log");
        assert(!"not implemented");
      }

      //! Manufacturer use only
      void initializeEnd() {
        CRYPTOLOG("log");
        assert(!"not implemented");
      }

      //! Performs cryptographic algorithms for authentication
      void internalAuthenticate() {
        CRYPTOLOG("log");
        assert(!"not implemented");
      }

      //! Activates a package
      void loadExecutable() {
        CRYPTOLOG("log");
        assert(!"not implemented");
      }

      //! Opens or closes a logical channel
      std::string manageChannel(unsigned char mode, unsigned char channelId) {
        CRYPTOLOG("log");
        return check(send(0x00, 0x70, mode, channelId));
      }

      //! Loads a CSE (RESTORE) or sets a component of the CSE (SET)
      //! and/or sets an extended headerlist
      void manageSecureEnvironment() {
        CRYPTOLOG("log");
        assert(!"not implemented");
      }

      //! Performs a challenge/response test and a subsequent
      //! MAC/Signature calculation and, depending on the input, a
      //! session key derivation.
      void mutualAuthenticate() {
        CRYPTOLOG("log");
        assert(!"not implemented");
      }

      //! Performs a cryptographic operation
      void performSecurityOperation() {
        CRYPTOLOG("log");
        assert(!"not implemented");
      }

      //! Controls the command sequence transactions
      void performTransactonOperation() {
        CRYPTOLOG("log");
        assert(!"not implemented");
      }

      //! Personalizer use only
      void personalize() {
        CRYPTOLOG("log");
        assert(!"not implemented");
      }

      //! Changes from life cycle phase ADMINISTRATION to OPERATIONAL
      //! and vice versa.
      /*! The command changes the global life cycle phase of the smart
          card from ADMINISTRATION to OPERATIONAL. This change is
          permanently valid for all DFs and will be stored in EEPROM,
          i.e. this life cycle phase is still valid after a reset of
          the smart card.

          The change from life cycle phase OPERATIONAL to
          ADMINISTRATION, however, is only temporary and valid only
          for the current DF (current life cycle phase) The life cycle
          phase is set back to OPERATIONAL when a context change
          (selection of a different DF) occurs or after a reset of the
          smart card. If the current life cycle phase shall be changed
          to ADMINISTRATION in several DFs, a PHASE CONTROL command
          has to be issued each time after the selection of one of
          those DFs.  The temporary change to ADMINISTRATION in one DF
          can also be undone (without reset) with another PHASE
          CONTROL command.

          @pre Changing from ADMINISTRATION to OPERATIONAL is not
               protected.  Changing from OPERATIONAL to ADMINISTRATION
               is controlled by the right referenced by the current
               DF’s AC LCYCLE.

          @pre The command can be executed in the life cycle phases
               ADMINISTRATION and OPERATIONAL.  */
       void phaseControl() {
        CRYPTOLOG("log");
         check(send(0x80, 0x10, 0x00, 0x00));
      }

      //! Installs / administrates / overwrites different objects
      void putData() {
        CRYPTOLOG("log");
        assert(!"not implemented");
      }

      //! Read a BINARY file
      std::string readBinary(std::string path="", unsigned short offset = 0) {
        CRYPTOLOG("log");
        pcsc::Connection::Reader::Transaction lock(_reader);
        if (path.size()) select(path);
        return check(send(0x00, 0xB0, (unsigned char)(offset>>8),
                          (unsigned char)(offset&0xFF)));
      }

      enum ReadRecordMode {
        FIRST_RECORD =0,
        LAST_RECORD = 1,
        NEXT_RECORD = 2,
        PREVIOUS_RECORD = 3,
        CURRENT_RECORD = 4,
        ABSOLUTE_RECORD = 4
      };

      //! Read a record oriented file
      std::string readRecord(const std::string& path = std::string(),
                             unsigned char record = 0,
                             unsigned char sfi = 0,
                             ReadRecordMode mode = ABSOLUTE_RECORD) {
        CRYPTOLOG("log");
        pcsc::Connection::Reader::Transaction lock(_reader);
        if (path.size()) select(path);
        return check(send(0x00, 0xB2, record,
                          (unsigned char)((sfi&0xF8)|mode)));
      }

      /// Read all records from a record oriented file
      BerValues readBerFile(std::string path="") {
        CRYPTOLOG("log");
        BerValues content;
        pcsc::Connection::Reader::Transaction lock(_reader);
        if (path.size()) select(path);
        while (true) {
          std::string res(send(0x00, 0xB2, 0, NEXT_RECORD));
          if (cardos::Commands::retCode(res)!=0x9000) break;
          content += retData(res).substr(2);
        }
        return content;
      }

      //! Resets the error counter of a BS object to its maximum
      void resetRetryCounter() {
        CRYPTOLOG("log");
        assert(!"not implemented");
      }

      //! Resets the security status of the current DF
      void resetSecurityCounter() {
        CRYPTOLOG("log");
        assert(!"not implemented");
      }

      enum FileSelectionMode {
        DF_OR_EF_DIRECTLY_BELOW_CURRENT_DF_USING_FILE_ID = 0x00,
        DF_DIRECTLY_BELOW_CURRENT_DF_USING_FILE_ID = 0x01,
        EF_DIRECTLY_BELOW_CURRENT_DF_USING_FILE_ID = 0x02,
        PARENT_DF_OF_CURRENT_DF = 0x03,
        DF_BY_NAME = 0x04,
        DF_OR_EF_USING_PATH_FROM_MF = 0x08,
        DF_OR_EF_USING_PATH_FROM_CURRENT_DF = 0x09
      };

      enum FileSelectionReturn {
        RETURN_FCI_DATA = 0x00,
        RETURN_FCP_DATA = 0x04,
        RETURN_NOTHING = 0x0C
      };

      //! Selects a file
      BerValues selectFile(std::string file,
                          FileSelectionMode mode
                          = DF_OR_EF_USING_PATH_FROM_MF,
                          FileSelectionReturn ret
                          = RETURN_NOTHING) {
        CRYPTOLOG("log");
        return BerValues(check(send(0x00, 0xA4, mode, ret, file)));
      }

      //! Sets the so-called Data_Field_Length parameter relevant for
      //! the effective Command Data Field Length / Response Data
      //! Field Length.
      void setDataFieldLength() {
        CRYPTOLOG("log");
        assert(!"not implemented");
      }

      //! Enables or disables the usage of an existing transaction buffer
      void setTransactionState() {
        CRYPTOLOG("log");
        assert(!"not implemented");
      }

      //! Signs a hashed input using a decryption key with the RSA_SIG
      //! algorithm.
      void signByDecryptionKey() {
        CRYPTOLOG("log");
        assert(!"not implemented");
      }

      //! Uninstalls a package of the smart card
      void uninstallPackage() {
        CRYPTOLOG("log");
        assert(!"not implemented");
      }

      // same as readBinary
      // /// Read the previously file from smart card
      // std::string readBinFile() {
      //   CRYPTOLOG("log");
      //   return check(send(0x00, 0xB0, 0x00, 0x00));
      // }

      //! Updates a BINARY file
      void updateBinary(std::string data, unsigned short offset=0) {
        CRYPTOLOG("log");
        check(send(0x00, 0xD6, (unsigned char)(offset>>8),
                   (unsigned char)(offset&0xFF), data));
      }

      //! Overwrites an existing record.
      std::string updateRecord(std::string data,
                               unsigned char record = 0,
                               unsigned char sfi = 0,
                               ReadRecordMode mode = ABSOLUTE_RECORD) {
        CRYPTOLOG("log");
        return check(send(0x00, 0xDC, record,
                          (unsigned char)((sfi&0xF8)|mode), data));
      }

      enum VerifyMode {
        SEARCH_IN_MF = 0,
        SEARCH_FROM_DF = 0x80
      };

      //! Performs a PIN test (CHV test)
      void verify(std::string pin, unsigned char id,
                  VerifyMode mode = SEARCH_FROM_DF) {
        CRYPTOLOG("log pin=\""<<pin<<"\" id=\""<<std::hex<<(int)id<<"\"");
        check(send(0x00, 0x20, 0x00, (unsigned char)(id|mode), pin));
      }

      //! Performs a PIN test (CHV test)
      /*! @return number of remaining PIN retries or -1 if PIN is locked */
      int verify(unsigned char id, VerifyMode mode = SEARCH_FROM_DF) {
        CRYPTOLOG("log");
        std::string res(send(0x00, 0x20, 0x00, (unsigned char)(id|mode)));
        unsigned int value((((unsigned int)(unsigned char)res[0])*256)
                           +((unsigned int)(unsigned char)res[1]));
        if ((value&0x63C0)==0x63C0) return value&0x0F;
        return -1;
      }

      //@}
      
      //------------------------------------------------------------------------
      /// @name Files and IDs on a Smart Card
      //@{

      /// Path to MF
      static std::string mf() {
        return "3f00";
      }

      /// Path to PKCS#15
      static std::string pkcs15() {
        return mf()+"5015";
      }

      /// Path to SigG (Signaturgesetz)
      static std::string sigG() {
        return mf()+"1fff";
      }

      /// ID of transport PIN
      static unsigned char transportPin() {
        return 0x71;
      }

      /// ID of PKCS#15 user PIN
      static unsigned char pkcs15Pin() {
        return 0x01;
      }

      /// ID of SigG (Signaturgesetz) secure PIN
      static unsigned char sigGPin() {
        return 0x01;
      }

      /// ID of PUK to unlock PKCS#15 user PIN
      static unsigned char puk() {
        return 0x02;
      }

      //@}

      //------------------------------------------------------------------------
      /// @name High Level Smart Card Access
      //@{

      /// Logon with transport PIN
      void logonTransport(std::string pin) {
        CRYPTOLOG("log pin=\""<<pin<<"\"");
        pcsc::Connection::Reader::Transaction lock(_reader);
        selectSigG();
        try {
          logon(transportPin(), pin);
        } catch (std::exception& x) {
          throw wrong_pin(x.what());
        }
      }

      /// Logon with SigG (Signaturgesetz) secure PIN
      void logonSigG(std::string pin) {
        CRYPTOLOG("log");
        pcsc::Connection::Reader::Transaction lock(_reader);
        selectSigG();
        try {
          logon(sigGPin(), pin);
        } catch (std::exception& x) {
          throw wrong_pin(x.what());
        }
      }

      /// Logon with PKCS#15 user PUK to unlock user PIN
      void logonPuk(std::string pin) {
        CRYPTOLOG("log");
        pcsc::Connection::Reader::Transaction lock(_reader);
        selectMF();
        try {
          logon(puk(), pin);
        } catch (std::exception& x) {
          throw wrong_puk(x.what());
        }
      }

      /// Logon with PKCS#15 user PIN
       void logonPkcs15(std::string pin) {
        CRYPTOLOG("log");
        pcsc::Connection::Reader::Transaction lock(_reader);
        selectMF();
        try {
          logon(pkcs15Pin(), pin);
        } catch (std::exception& x) {
          throw wrong_pin(x.what());
        }
      }

      /// Change SigG (Signaturgesetz) secure PIN
      /** If smart card is in transport state, @p oldPin must be
          transport PIN and then the card is unlocked and the
          transport state is unset. */
      void changeSigGPin(std::string newPin, std::string oldPin) {
        CRYPTOLOG("log newPin=\""<<newPin<<"\" oldPin=\""<<oldPin<<"\"");
        if (transportState()) { // first time use, reset transport state
          pcsc::Connection::Reader::Transaction lock(_reader);
          logonTransport(oldPin);
          changeReferenceData(sigGPin(), newPin);
          unsetTransportState();
        } else { // ordinary PIN change
          pcsc::Connection::Reader::Transaction lock(_reader);
          logonSigG(oldPin);
          selectSigG();
          changeReferenceData(sigGPin(), newPin, oldPin);
        }
      }

      /// Change PKCS#15 user PIN
       void changePkcs15Pin(std::string newPin, std::string oldPin) {
        CRYPTOLOG("log newPin=\""<<newPin<<"\" oldPin=\""<<oldPin<<"\"");
        pcsc::Connection::Reader::Transaction lock(_reader);
        selectMF();
        changeReferenceData(pkcs15Pin(), newPin, oldPin);
      }

      /// Change PKCS#15 user PIN
      /** To keep all PINs synchronized: Detect if it is in transport
          state, if so, old PIN must be transport PIN. in any case,
          change PIN on PKCS#15 and SigG from the same old PIN to the
          same new PIN. */
      void changePins(std::string newPin, std::string oldPin) {
        CRYPTOLOG("log newPin=\""<<newPin<<"\" oldPin=\""<<oldPin<<"\"");
        pcsc::Connection::Reader::Transaction lock(_reader);
        if (transportState()) { // transport state
          changeSigGPin(newPin, oldPin);
          changePkcs15Pin(newPin, oldPin);
        } else {
          if (pkcs15PinRetries()!=-1) { // normal pin change
            changePkcs15Pin(newPin, oldPin);
            try {
              if (sigGPinRetries()!=-1) changeSigGPin(newPin, oldPin);
            } catch (...) {
              // undo PKCS#15 PIN change
              if (pkcs15PinRetries()!=-1) changePkcs15Pin(oldPin, newPin);
              throw;
            }
          } else if (pukRetries()==-1 && sigGPinRetries()!=-1) {
            changeSigGPin(newPin, oldPin); // only valid sigg left
          } else throw pin_locked();
        }
      }

      /// Unlock PKCS#15 PIN using PUK
      /** If the PIN is still the same as the old PIN was, SigG PIN
          can be saved. If you have forgotten your SigG PIN, just use
          @c force to completely break it. Otherwise PIN will first be
          applied to SigG, if that works, PKCS#15 will be changed
          accordingly. */
      void unlock(std::string pin, std::string puk, bool force=false) {
        CRYPTOLOG("log");
        if (pukRetries()==-1) throw pin_locked(); // too late, puk broken
        logonPuk(puk);
        CRYPTOLOG("PUK accepted");
        while (sigGPinRetries()!=-1) try {
            logonSigG(pin); // check SigG
            CRYPTOLOG("SigG successfully checked");
            break; // SigG still works
          } catch (...) {
            CRYPTOLOG("SigG PIN failed");
            if (!force) throw; // don't enforce, abort after one try
            // otherwise break the SigG PUK now
            // continue until SigG is broken
            CRYPTOLOG("Force kill SigG");
          }
        selectMF();
        changeReferenceData(pkcs15Pin(), pin);
        CRYPTOLOG("Successfully restored PKCS#15 PIN");
      }
      
      /// Select a file in the PKCS#15 part on the smart card
      void selectPkcs15File(std::string file) {
        CRYPTOLOG("log");
        check(send(0x00, 0xA4, 0x08, 0x0C, crypto::hexToBin("3f005015"+file)));
      }

      /// Select a file in the SigG (Signaturgesetz) part on the smart card
      void selectSigGFile(std::string file) {
        CRYPTOLOG("log");
        check(send(0x00, 0xA4, 0x08, 0x0C, crypto::hexToBin("3f001fff"+file)));
      }

      /// Select a file in the MF part on the smart card
      void selectMfFile(std::string file) {
        CRYPTOLOG("log");
        check(send(0x00, 0xA4, 0x08, 0x0C, crypto::hexToBin("3f00"+file)));
      }

      /// Generic select file
      void select(std::string path) {
        CRYPTOLOG("log: select "<<path);
        check(send(0x00, 0xA4, 0x08, 0x0C, crypto::hexToBin(path)));
      }

     /// Select the PKCS#15 part on the smart card
      void selectPkcs15() {
        CRYPTOLOG("log");
        check(send(0x00, 0xA4, 0x08, 0x0C, crypto::hexToBin("3f005015")));
      }

      /// Select the SigG (Signaturgesetz) part on the smart card
      void selectSigG() {
        CRYPTOLOG("log");
        check(send(0x00, 0xA4, 0x08, 0x0C, crypto::hexToBin("3f001fff")));
      }

      /// Select the MFpart on the smart card
      void selectMF() {
        CRYPTOLOG("log");
        check(send(0x00, 0xA4, 0x00, 0x0C));
      }

      /// @return binary serial number
      std::string serial() {
        CRYPTOLOG("log");
        return getDataChipProduction().serial;
      }

      /// @return @c true if card is still in transport state
      bool transportState() {
        CRYPTOLOG("log");
        pcsc::Connection::Reader::Transaction lock(_reader);
        selectSigGFile("fe15");
        return std::string(4, '\0')==readRecord();
      }

      /// Mark card as initiakized and no more in transport state
      void unsetTransportState() {
        CRYPTOLOG("log");
        pcsc::Connection::Reader::Transaction lock(_reader);
        selectSigGFile("fe15");
        increase();
      }

      /*! @return number of remaining transport PIN retries or -1 if locked */
      int transportPinRetries() {
        CRYPTOLOG("log");
        pcsc::Connection::Reader::Transaction lock(_reader);
        selectSigG();
        return verify(transportPin());
      }

      /*! @return number of remaining PKCS#15 PIN retries or -1 if locked */
      int pkcs15PinRetries() {
        CRYPTOLOG("log");
        pcsc::Connection::Reader::Transaction lock(_reader);
        selectMF();
        return verify(pkcs15Pin());
      }

      /*! @return number of remaining SigG PIN retries or -1 if locked */
      int sigGPinRetries() {
        CRYPTOLOG("log");
        pcsc::Connection::Reader::Transaction lock(_reader);
        selectSigG();
        return verify(sigGPin());
      }

      /*! @return number of remaining PUK retries or -1 if locked */
      int pukRetries() {
        CRYPTOLOG("log");
        pcsc::Connection::Reader::Transaction lock(_reader);
        selectMF();
        return verify(puk());
      }

      //@}

    protected:

      /// @name More Generic Internal Auxiliary Methods
      //@{
      
      void logon(unsigned char id, std::string pin) {
        verify(pin, id);
      }

      int pinStatus(unsigned char id) {
        std::string res(send(0x00, 0x20, 0x00, id|0x80));
        unsigned int value((((unsigned int)(unsigned char)res[0])*256)
                           +((unsigned int)(unsigned char)res[1]));
        switch (value) {
          case 0x6983: CARDOS_LOG("LOCKED"); return -1;
          case 0x63Ca: CARDOS_LOG("TEN"); return 10;
          case 0x63C9: CARDOS_LOG("NINE"); return 9;
          case 0x63C8: CARDOS_LOG("EIGHT"); return 8;
          case 0x63C7: CARDOS_LOG("SEVEN"); return 7;
          case 0x63C6: CARDOS_LOG("SIX"); return 6;
          case 0x63C5: CARDOS_LOG("FIVE"); return 5;
          case 0x63C4: CARDOS_LOG("FOUR"); return 4;
          case 0x63C3: CARDOS_LOG("THREE"); return 3;
          case 0x63C2: CARDOS_LOG("TWO"); return 2;
          case 0x63C1: CARDOS_LOG("ONE"); return 1;
          case 0x63C0: CARDOS_LOG("ZERO"); return 0;
          case 0x6300: CARDOS_LOG("LOCKED"); return -1;
          default: CARDOS_LOG("UNKNOWN"); return -1;
        }
      }
      
      std::string sendAPDU(std::string data) {
        if (data.size()==4)
          return send(data[0], data[1], data[2], data[3]);
        else if (data.size()>4)
          return send(data[0], data[1], data[2], data[3], data.substr(4));
        else
          throw runtime_error("wrong APDU pass at least 4 bytes in hex");
      }

      //! @return error text of APDU result string
      static std::string error(std::string res) {
        return error(retCode(res));
      }
      
      //! @return error text of return code evaluated by @ref retCode
      static std::string error(int ret) {
        switch (ret) {
          case 0x6283:
            return "File is deactivated (see DEACTIVATE FILE command)";
          case 0x6285:
            return "File is terminated";
          case 0x6300:
            return "Authentication failed";
          case 0x6581:
            return "EEPROM error; command aborted";
          case 0x6700:
            return "LC invalid";
          case 0x6881:
            return "Logical channel not supported";
          case 0x6882:
            return "SM mode not supported";
          case 0x6884:
            return "Chaining Error";
          case 0x6981:
            return "Command cannot be used for file structure";
          case 0x6982:
            return "Required access right not granted";
          case 0x6983:
            return "BS object blocked";
          case 0x6984:
            return "BS object has invalid format";
          case 0x6985:
            return
              "Conditions of use not satisfied; no random number available";
          case 0x6986:
            return "no current EF selected";
          case 0x6987:
            return "Key object for SM not found";
          case 0x6988:
            return "Key object used for SM has invalid format";
          case 0x6a80:
            return "Invalid parameters in data field";
          case 0x6a81:
            return "Function / mode not supported";
          case 0x6a82:
            return "File not found";
          case 0x6a83:
            return "Record / object not found";
          case 0x6a84:
            return
              "Not enough memory in file / in file system (e.g. CREATE FILE)"
              " available";
          case 0x6a85:
            return "LC does not fit the TLV structure of the data field";
          case 0x6a86:
            return "P1 / P2 invalid";
          case 0x6a87:
            return "LC does not fit P1 /P2";
          case 0x6a88:
            return "Object not found (GET DATA)";
          case 0x6a89:
            return "File already exists";
          case 0x6a8a:
            return "DF name already exists";
          case 0x6c00:
            return "LE does not fit the data to be sent (e.g. SM)";
          case 0x6d00:
            return "INS invalid";
          case 0x6e00:
            return "CLA invalid (Hi nibble)";
          case 0x6f00:
            return
              "Technical Error: "
              " 1 Attempt to create more than 254 records in a file. "
              " 2 Package uses SDK version which is not compatible to"
              " API version "
              " 3 Package contains invalid statements (LOAD EXECUTABLE)";
          case 0x6f81:
            return "File is invalidated because of checksum error (prop.)";
          case 0x6f82:
            return "Not enough memory available in XRAM";
          case 0x6f83:
            return
              "Transaction error (i.e. command must not be used in a"
              " transaction)";
          case 0x6f84:
            return "General protection fault (prop.)";
          case 0x6f85:
            return "Internal failure of PK-API (e.g. wrong CCMS format)";
          case 0x6f86:
            return "Key Object not found";
          case 0x6f87:
            return
              "Internal hardware attack detected, change to life cycle death";
          case 0x6f88:
            return "Transaction buffer too small";
          case 0x6fff:
            return
              "Internal assertion (invalid internal error) "
              "This error is no runtime error but an internal error which can"
              " occur because of a programming error only.";
          case 0x9000:
            return "Command executed correctly";
          case 0x9001:
            return
              "Command executed correctly; EEPROM weakness detected"
              " (EEPROM written with second trial; the EEPROM area"
              " overwritten has a limited life time only.)";
          case 0x9850:
            return "Overflow through INCREASE / underflow through DECREASE";
          case -1:
            return "No return code received";
          default:
            std::stringstream ss;
            if ((ret&0xff00)==0x6100)
              ss<<(ret&0xff)<<" bytes of response data can be received"
                <<" with GET RESPONSE (only T=0 transmission protocol)";
            else
              ss<<"Unknown CardOS error code: 0x"<<std::hex<<ret;
            return ss.str();
        }
      }

      static unsigned int retCode(const std::string& res) {
        if (res.size()>=2)
          return ((((unsigned int)(unsigned char)res[res.size()-2])*256)
                  +((unsigned int)(unsigned char)res[res.size()-1]));
        else
          return -1;
      }

      static std::string retData(const std::string& res) {
        if (res.size()>=2)
          return res.substr(0, res.size()-2);
        else
          return std::string();
      }

      std::string check(std::string res) {
        unsigned int value(retCode(res));
        if (value==0x6300) throw wrong_pin(error(value));
        if (value!=0x9000) throw runtime_error(error(value));
        return retData(res);
      }
      
      std::string logResult(const std::string& result) {
        std::string txt;
        for (std::string::const_iterator it(result.begin());
             it!=result.end(); ++it)
          txt += isprint(*it)?*it:'.';
        if (result.size()>=2) {
          CARDOS_LOG(error(result)<<std::endl
                     <<"RC: "
                     <<crypto::binToHex(result.substr(result.size()-2, 2)));
          if (result.size()>2) {
            CARDOS_LOG("Data: "
                       <<crypto::binToHex(result.substr(0, result.size()-2)));
            CARDOS_LOG("Text: "
                       <<txt.substr(0, result.size()-2));
          }
        } else {
          CARDOS_LOG("RC: ?? Data: "<<crypto::binToHex(result)<<" Text: "<<txt);
        }
        return result;
      }

      std::string send(unsigned char cla, unsigned char ins,
                       unsigned char p1, unsigned char p2,
                       std::string lc) {
        if (!_reader) throw runtime_error("no reader selected");
        CARDOS_LOG("APDU: cla="<<crypto::binToHex(cla)
                   <<" ins="<<crypto::binToHex(ins)
                   <<" p1="<<crypto::binToHex(p1)
                   <<" p2="<<crypto::binToHex(p2)
                   <<" lc="<<crypto::binToHex(lc));
        return logResult(_reader->transmit(cla, ins, p1, p2, lc));
      }
      
      std::string send(unsigned char cla, unsigned char ins,
                       unsigned char p1, unsigned char p2) {
        if (!_reader) throw runtime_error("no reader selected");
        CARDOS_LOG("APDU: cla="<<crypto::binToHex(cla)
                   <<" ins="<<crypto::binToHex(ins)
                   <<" p1="<<crypto::binToHex(p1)
                   <<" p2="<<crypto::binToHex(p2));
        return logResult(_reader->transmit(cla, ins, p1, p2));
      }

      //@}
    
    protected:

      std::shared_ptr<pcsc::Connection::Reader> _reader;

  };
  
  //============================================================================
  /// Represents a CardOS Filesystem Object
  class Object {
    public:
      typedef std::shared_ptr<Object> Child;
      typedef std::vector<Child> Children;
    public:
      Object(const std::string& p): _path(p) {}
      virtual ~Object() {}
      virtual const std::string& path() const {
        return _path;
      }
      virtual std::string name() const {
        return _path;
      }
      virtual std::string logicalName() const {
        switch (crypto::ulongFromBinary(crypto::hexToBin(name()))) {
          case 0x3f00: return "Masterfile";
          case 0x5649: return "Version Info";
          case 0x2F02: return "Global Definition Object";
          case 0x5015: return "PKCS#15";
          case 0x5032: return "Token Info";
          case 0x5600: return "File System Version";
          case 0x4404: return "Certificate Definition";
          case 0x4304: return "Certificates";
          case 0x4401: return "Public Key Definition";
          case 0x5075: return "Public Keys";
          case 0x4408: return "Authentication Object Definition";
          case 0x5031: return "Object Definition";
          case 0x4407: return "Data Object Definition";
          case 0x4444: return "Data Objects";
          case 0x4400: return "Private Key Definition";
          case 0x5072: return "Private Keys";
          case 0x1fff: return "SigG";
          case 0xfe15: return "Transport Protection State";
          case 0xc100: return "Public Key";
          default:
            switch (crypto::ulongFromBinary(crypto::hexToBin(name()))&0xff00) {
              case 0x4b00: return "Private Key "+name().substr(2);
              case 0x5500: return "Public Key "+name().substr(2);
              default: return type();
            }
        }
      }
      virtual const std::string& content() const {
        const static std::string c;
        return c;
      }
      virtual const std::string& contentInfo() const {
        const static std::string c;
        return c;
      }
      virtual std::string print(int indent=0, int=4) const {
        return std::string(indent, ' ')+type()+": "+name();
      }
      virtual bool isFile() const {
        return false;
      }
      virtual bool isDir() const {
        return false;
      }
      virtual bool hasChildren() const {
        return _children.size();
      }
      virtual Children::size_type size() const {
        return _children.size();
      }
      virtual const Children& children() const {
        return _children;
      }
      virtual std::string type() const = 0;
    protected:
      std::string _path;
      Children _children;
  };
  
  //============================================================================
  /// Represents a File on a Smart Card
  class File: public Object {
    public:
      File(Commands& cmd, const std::string& p, const std::string& file):
          Object(p), _file(file) {
        CRYPTOLOG("new file "<<file<<" at "<<p);
        try {
          _content = cmd.readBinary(p+file);
          try {
            _contentInfo = cardos::BerValues(_content).print();
          } catch (...) {
          }
        } catch (std::exception& x) {
          _contentInfo = x.what();
        }
      }
      File(const std::string& p, const std::string& file):
          Object(p), _file(file) {
        CRYPTOLOG("new file "<<file<<" at "<<p);
      }
      std::string name() const {
        return _file;
      }
      const std::string& content() const {
        return _content;
      }
      const std::string& contentInfo() const {
        return _contentInfo;
      }
      bool isFile() const {
        return true;
      }
      std::string type() const {
        return "File";
      }
    protected:
      std::string _file;
      std::string _content;
      std::string _contentInfo;
  };
  
  //============================================================================
  /// Represents a Link on a Smart Card
  class Link: public File {
    public:
      Link(Commands& cmd, const std::string& p, const std::string& file):
          File(cmd, p, file) {
        CRYPTOLOG("new link "<<file<<" at "<<p);
      }
      std::string type() const {
        return "Link";
      }
  };
  
  //============================================================================
  /// Represents a Counter on a Smart Card
  class Counter: public File {
    public:
      Counter(Commands& cmd, const std::string& p, const std::string& file):
          File(p, file) {
        CRYPTOLOG("new counter "<<file<<" at "<<p);
        try {
          _content = cmd.readRecord(p+file);
          std::stringstream s;
          s<<crypto::ulongFromBinary(_content);
          _contentInfo = s.str();
        } catch (std::exception& x) {
          _contentInfo = x.what();
        }
      }
      std::string type() const {
        return "Counter";
      }
  };
  
  //============================================================================
  /// Represents a Directory on a Smart Card
  class Dir: public Object  {
    public:
      Dir(Commands& cmd, const std::string& p): Object(p) {
        CRYPTOLOG("new directory path="<<p);
        setup(cmd);
      }
      std::string name() const {
        return _path.substr(_path.size()-4);
      }
      bool isDir() const {
        return true;
      }
      std::string print(int indent=0, int step=4) const {
        std::string res(std::string(indent, ' ')+type()+": "+name());
        for (Children::const_iterator it(_children.begin());
             it!=_children.end(); ++it)
          res+='\n'+(*it)->print(indent+step, step);
        return res;
      }
      std::string type() const {
        return "Directory";
      }
    protected:
      void setup(Commands& cmd) {
        _children.clear();
        BerValues i(cmd.directory(_path));
        CRYPTOLOG("Found Structure: "<<i.print());
        for (BerValues::iterator it(i.begin()); it!=i.end(); ++it) {
          CRYPTOLOG(" -> Value: "<<it->print());
          if (it->size()<2) continue; // empty dir
          switch ((*it)[0].ulong()) {
            case 0x38:
              _children.push_back(Child(new Dir(cmd, _path+(*it)[1].hex())));
              break;
            case 0x01:
              _children.push_back(Child(new File(cmd, _path, (*it)[1].hex())));
              break;
            case 0x05:
              _children.push_back(Child(new Link(cmd, _path, (*it)[1].hex())));
              break;
            case 0x06:
              _children.push_back(Child(new Counter(cmd, _path, (*it)[1].hex())));
              break;
            default: // unknown
              throw runtime_error("unknown object: "+it->print());
          }
        }
      }
      
  };

  //@}
  
}
//@}

#endif
