
/*---------------------------------------------------------------------------
 * Copyright (C) 1999,2000 Dallas Semiconductor Corporation, All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL DALLAS SEMICONDUCTOR BE LIABLE FOR ANY CLAIM, DAMAGES
 * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * Except as contained in this notice, the name of Dallas Semiconductor
 * shall not be used except as stated in the Dallas Semiconductor
 * Branding Policy.
 *---------------------------------------------------------------------------
 */

package com.dalsemi.onewire.container;

import com.dalsemi.onewire.*;
import com.dalsemi.onewire.adapter.*;
import java.util.Date;
import com.dalsemi.onewire.utils.CRC16;


/**
 * Higher level access class for a DS1963S, Sha iButton.  <br>
 * <br>
 * This method makes use of several tricks to enhance performance on TINI.
 * For instance, most methods are synchronized so that I can use instance variable
 * byte arrays rather than creating new byte arrays every time a transacation
 * is performed.  This could hurt performance if you are doing multi-threaded
 * applications, but the usefullness of having several threads contending
 * to talk to a single iButton is questionable since the methods in DSPortAdapter
 * 'beginExclusive' and 'endExclusive' should be used.  <br>
 * <br>
 * A SHAiButton can be a coprocessor or a user.  A coprocessor button verifies
 * signatures and signs data for user ibuttons.  A coprocessor might be located
 * inside a vending machine, where a person would bring their user iButton.  When
 * the user iButton is pressed to the Blue Dot to perform a transaction, the coprocessor
 * would first verify that this button belongs to the system, i.e that it has the same
 * secret (example: a Visa terminal making sure the iButton had a Visa account installed).
 * Then the coprocessor would verify the signed data, or the money, to make sure it was
 * valid.  If someone tried to overwrire their money file, even with a previously valid
 * money file (to 'restore' an earlier amount of money) it would be invalid because the
 * signature includes the write cycle counter, which is incremented every time a page is
 * written to.  So the coprocessor verifies the money, then signs a new data file that
 * contains the new amount of money.  <br>
 * <br>
 * The coprocessor has two secrets that are not even accessible from this class:  One secret
 * that is shared with the user iButtons--this is the authentication secret.  It lets
 * the system know that the user iButton belongs to the same group as the coprocessor.  The
 * second secret exists only on the coprocessor--it is used to sign and verify the money files.  <br>
 * <br>
 * This class makes no calls to the adapter class, i.e. it doesn't do any direct talking to
 * the device.  <br>
 * <br>
 * @version    0.00, 28 Aug 2000
 * @author     KLA
 */
public class SHAiButton
{
   private OneWireContainer18 ibc = null;   //access to the underlying container
   private byte[]             address;         //take out an extra method call or so
   private boolean            isCoprocessor;   //is this button a coprocessor?
   private boolean            isUser;          //is this button a user?
   private byte[]             bind_data = new byte [32];
   private byte[]             bind_code = new byte [7];

   //data that means something in a coprocessor
   private byte[] filename = null;   //filename is jsut the four bytes in user_account_file

   //plus one byte for user_account_file_extension
   private int signing_page_number        = -1;
   private int authentication_page_number = -1;
   private int workspace_page_number      = -1;

   //bind_data and bind_code already defined, is OK - they can be defined for both buttons
   private byte[] signing_challenge = new byte [3];
   private byte[] initial_signature = new byte [20];   //all 0's is legal for this one

   //data that means something in a user
   private int    user_account_file_page;
   private byte[] generic_money_page = new byte [32];
   private byte[] scratchpad_buffer;
   private int    last_error = 0;

   /**
    * Useful constant for getLastError().  Means no error occurred.
    */
   public static final int NO_ERROR = 0;

   /**
    * Useful constant for getLastError().  Probably due to a communication error,
    * meaning a retry might succeed.
    */
   public static final int ERASE_SCRATCHPAD_ERROR = 1;

   /**
    * Useful constant for getLastError().  Probably due to a communication error,
    * meaning a retry might succeed.
    */
   public static final int WRITE_SCRATCHPAD_ERROR = 2;

   /**
    * Useful constant for getLastError().  Probably due to a communication error,
    * meaning a retry might succeed.
    */
   public static final int READ_SCRATCHPAD_ERROR = 3;

   /**
    * Useful constant for getLastError().  Probably due to a communication error,
    * meaning a retry might succeed.
    */
   public static final int READ_MEMORY_PAGE_ERROR = 4;

   /**
    * Useful constant for getLastError().  Probably due to a communication error,
    * meaning a retry might succeed.
    */
   public static final int READ_AUTHENTICATED_ERROR = 5;

   /**
    * Useful constant for getLastError().  Probably due to a communication error,
    * meaning a retry might succeed.
    */
   public static final int BIND_SECRET_ERROR = 6;

   /**
    * Useful constant for getLastError().  Probably due to a communication error,
    * meaning a retry might succeed.
    */
   public static final int WRITE_MEMORY_PAGE_ERROR = 7;

   /**
    * Useful constant for getLastError().  Probably due to a communication error,
    * meaning a retry might succeed.
    */
   public static final int SHA_FUNCTION_ERROR = 8;

   /**
    * Useful constant for getLastError().  Probably due to a communication error,
    * meaning a retry might succeed.
    */
   public static final int CRC_ERROR = 9;

   /**
    * Useful constant for getLastError().  Means that the coprocessor
    * SHAiButton object has not been successfully setup.
    */
   public static final int NO_COPROCESSOR_ERROR = 10;

   /**
    * Useful constant for getLastError().  Means that the user
    * SHAiButton object has not been successfully setup.
    */
   public static final int NO_USER_ERROR = 11;

   /**
    * Useful constant for getLastError().  It is possible this error
    * is due to a communication error, and that a retry will be
    * successful.  However, it can also mean that the user SHAiButton
    * does not belong to the system (could not authenticate).
    */
   public static final int AUTHENTICATION_FAILED_ERROR = 12;

   /**
    * Useful constant for getLastError().  It is possible this error
    * is due to a communication error, and that a retry will be
    * successful.  However, it can also mean that the user SHAiButton's
    * money data file is invalid (has been tampered with).
    */
   public static final int VERIFICATION_FAILED_ERROR = 13;

   /**
    * Constructor SHAiButton
    *
    *
    * @param ibc
    *
    */
   public SHAiButton (OneWireContainer18 ibc)
   {
      this.ibc          = ibc;
      isCoprocessor     = false;
      isUser            = false;
      scratchpad_buffer = new byte [32];
      address           = ibc.getAddress();

      for (int i = 0; i < 32; i++)
         bind_data [i] = ( byte ) 0x0ff;

      for (int i = 0; i < 7; i++)
         bind_code [i] = ( byte ) 0x0ff;
   }

   /**
    * Sets the binding data for this Sha iButton.  This should be 32 bytes.
    * Anything shorter will result in an ArrayIndexOutOfBounds exception.
    * Anything longer will be ignored.  This data is used to bind secrets
    * to user/roaming iButtons and also to re-create user signatures in
    * Coprocessor buttons.
    *
    * @param buf Byte buffer with the binding data.  Must be at least offset+32 bytes long.
    * @param offset Offset to start reading from the buffer.  32 bytes will be read from offset.
    */
   public synchronized void setBindData (byte[] buf, int offset)
   {
      System.arraycopy(buf, offset, bind_data, 0, 32);
   }

   /**
    * Sets the code to bind used in signatures.  This data is written to
    * the scratchpad for binding secrets to iButtons and also to recreate
    * user iButton signatures on Coprocessor iButtons.  This data should
    * only be 7 bytes long.
    *
    * @param buf Byte buffer with the binding data.  Must be at least offset+32 bytes long.
    * @param offset Offset to start reading from the buffer.  32 bytes will be read from offset.
    */
   public synchronized void setBindCode (byte[] buf, int offset)
   {
      System.arraycopy(buf, offset, bind_code, 0, 7);
   }

   /**
    * Get the 32 byte binding data.  This data is used to bind secrets
    * to user/roaming iButtons and also to re-create user signatures in
    * Coprocessor buttons.
    *
    * @return Byte array of length 32.
    */
   public synchronized byte[] getBindData ()
   {
      byte[] b = new byte [bind_data.length];

      System.arraycopy(bind_data, 0, b, 0, b.length);

      return b;
   }

   /**
    * Get the 7 byte binding data.  This data is used to bind secrets
    * to user/roaming iButtons and also to re-create user signatures in
    * Coprocessor buttons.
    *
    * @return Byte array of length 7.
    */
   public synchronized byte[] getBindCode ()
   {
      byte[] b = new byte [bind_code.length];

      System.arraycopy(bind_code, 0, b, 0, b.length);

      return b;
   }

   private byte[] answer_readpg        = new byte [42];
   private byte[] answer_pad_challenge = new byte [32];

   /**
    * Answers a challenge from the coprocessor by signing a data page with the
    * authentication secret.  If the coprocessor can verify this signature,
    * then the iButton belongs to the system (has nothing to do with the
    * validity of the signed data in the page).
    *
    * @param challenge A 3 byte random challenge.  This must be generated by the DS1963S.
    * @param mac A holding place for the returned signature.  The coprocessor will use this signature to
    * check the authentication.  This must be at least 20 bytes long.
    * @param pagedata A holding place for the page data.  The coprocessor must know the data that was on the page
    * in order to recreate the user signature.  This array must be at least 32 bytes long.
    * @return The write cycle counter value of the read page.  Also, the field 'mac' will contain the 20
    * byte signature that is generated by the authenticate command.  Also, the field 'pagedata'
    * will contain the 32 bytes of the page used by the iButton to sign the data.
    * @exception com.dalsemi.onewire.adapter.OneWireIOException
    * @exception com.dalsemi.onewire.OneWireException
    */
   public synchronized int answerChallenge (byte[] challenge, byte[] mac,
                                            byte[] pagedata)
      throws OneWireIOException, OneWireException
   {
      last_error = NO_ERROR;

      System.arraycopy(challenge, 0, answer_pad_challenge, 20, 3);

      if (ibc.eraseScratchPad(user_account_file_page))
      {
         ibc.useResume(true);

         if (ibc.writeScratchPad(user_account_file_page, 0,
                                 answer_pad_challenge, 0, 32))
         {
            boolean readOK = ibc.readAuthenticatedPage(user_account_file_page,
                                                       answer_readpg, 0);
            int     wcc    = (answer_readpg [35] & 0x0ff);

            wcc = (wcc << 8) | (answer_readpg [34] & 0x0ff);
            wcc = (wcc << 8) | (answer_readpg [33] & 0x0ff);
            wcc = (wcc << 8) | (answer_readpg [32] & 0x0ff);

            int len = ibc.readScratchPad(scratchpad_buffer, 0);

            if ((!readOK) || (len < 0))
            {
               if (!readOK)
                  last_error = READ_AUTHENTICATED_ERROR;
               else
                  last_error = READ_SCRATCHPAD_ERROR;

               return -1;
            }

            System.arraycopy(answer_readpg, 0, pagedata, 0, 32);
            System.arraycopy(scratchpad_buffer, 8, mac, 0, 20);
            ibc.useResume(false);

            return wcc;
         }
         else
         {
            last_error = this.WRITE_SCRATCHPAD_ERROR;

            ibc.useResume(false);

            return -1;
         }
      }

      last_error = ERASE_SCRATCHPAD_ERROR;

      ibc.useResume(false);

      return -1;
   }

   /**
    * Generates a 3 byte random challenge in the iButton, sufficient to be used
    * in answering a challenge by a user iButton.
    *
    * @param page_number The page number that the random number generation will be based on.  Cannot
    * be 0 or 8.
    * @param offset When this function runs in the iButton, 20 bytes of pseudo-random data will be
    * generated.  You can pick from where in this data you would like to pull your 3 bytes.
    * @param ch The 3 byte challenge is returned here.  This byte array must be of length 3 or more.
    * @return True if successful.
    * @exception com.dalsemi.onewire.adapter.OneWireIOException
    * @exception com.dalsemi.onewire.OneWireException
    */
   public synchronized boolean generateChallenge (int page_number,
           int offset, byte[] ch)
      throws OneWireIOException, OneWireException
   {
      last_error = NO_ERROR;

      int addr = page_number << 5;

      if (ibc.eraseScratchPad(page_number))
      {
         ibc.useResume(true);

         if (ibc.SHAFunction(ibc.COMPUTE_CHALLENGE, addr))
         {
            int len = ibc.readScratchPad(scratchpad_buffer, 0);

            System.arraycopy(scratchpad_buffer, 8 + (offset % 17), ch, 0, 3);
            ibc.useResume(false);

            return true;
         }
         else
         {
            last_error = SHA_FUNCTION_ERROR;

            ibc.useResume(false);

            return false;
         }
      }

      last_error = ERASE_SCRATCHPAD_ERROR;

      ibc.useResume(false);

      return false;
   }

   //pagedata must be 32 bytes!
   //return the write cycle counter
   private byte[] authentication_mac1            = new byte [20];
   private byte[] authentication_full_bind_code1 = new byte [15];
   private byte[] authentication_pad_challenge1  = new byte [32];
   private byte[] auth_challenge                 = new byte [3];

   /**
    * Determines if the 'user' SHAiButton belongs to the system
    * defined by this Coprocessor iButton.  You must be a coprocessor
    * to call this function.  The TMEX page with the money signature
    * is returned in pagedata.
    *
    * @param user The SHAiButton that will answer the challenge and be authenticated.
    * This SHAiButton must be a user iButton (it must have had setUser called).
    * @param pagedata The contents of the data page that will be used by 'user' to generate
    * the authentication signature.  This byte array must be AT LEAST 32
    * bytes long.  The data will be copied into this array.
    * @return The write cycle counter of the verified authentication page, or -1 if
    * it could not authenticate (the iButton does not belong to the system).
    * @exception com.dalsemi.onewire.OneWireException
    * @exception com.dalsemi.onewire.adapter.OneWireIOException
    */
   public synchronized int verifyAuthentication (SHAiButton user,
           byte[] pagedata)
      throws OneWireException, OneWireIOException
   {
      last_error = NO_ERROR;

      //local cache-ing of stuff
      int    user_page                     = user.user_account_file_page;
      int    wspc_page_number              = workspace_page_number;
      byte[] authentication_pad_challenge  = authentication_pad_challenge1;
      byte[] authentication_full_bind_code = authentication_full_bind_code1;
      byte[] authentication_mac            = authentication_mac1;

      if (!isCoprocessor)
      {
         last_error = NO_COPROCESSOR_ERROR;

         return -1;
      }

      if (!user.isUser)
      {
         last_error = NO_USER_ERROR;

         return -1;
      }

      //steps here are
      //  1 generate a challenge
      //  2 make the user generate a response
      //  3 verify that response
      //NOTE! we can't get out of making the iButton generate the challenge here
      //because it messes with flags that alter the outcome of the SHA engine
      generateChallenge(authentication_page_number, 0, auth_challenge);

      //THIS IS OUR LONE COMMUNICATION WITH THE USER IBUTTON
      int write_cycle_counter = user.answerChallenge(auth_challenge,
                                   authentication_mac, pagedata);

      //AFTER THIS LETS PUT SOME OF THAT "RESUME" STUFF TO USE
      OneWireContainer18 ibcL = ibc;

      //now verify the response
      System.arraycopy(bind_code, 0, authentication_full_bind_code, 0, 4);

      authentication_full_bind_code [4] = ( byte ) user_page;

      System.arraycopy(user.address, 0, authentication_full_bind_code, 5, 7);
      System.arraycopy(bind_code, 4, authentication_full_bind_code, 12, 3);

      authentication_pad_challenge [8]  = ( byte ) (write_cycle_counter
              & 0x0ff);
      authentication_pad_challenge [9]  = ( byte ) ((write_cycle_counter >> 8)
              & 0x0ff);
      authentication_pad_challenge [10] =
         ( byte ) ((write_cycle_counter >> 16) & 0x0ff);
      authentication_pad_challenge [11] =
         ( byte ) ((write_cycle_counter >> 24) & 0x0ff);

      System.arraycopy(auth_challenge, 0, authentication_pad_challenge, 20,
                       3);

      //we've already copied user_account_file_page and iButton.address to full_bind_code,
      //now why don't we just do
      //      System.arraycopy(authentication_full_bind_code,4,authentication_pad_challenge,12,8);????
      //copies the iButton address and the page number from where it already is locally
      //skip that method invocation and extra assignment
      System.arraycopy(authentication_full_bind_code, 4,
                       authentication_pad_challenge, 12, 8);

      //the first thing bindSecretToiButton does with this thing is mask it with 7, so why do it twice?
      if (ibcL.bindSecretToiButton(authentication_page_number, bind_data,
                                   authentication_full_bind_code,
                                   wspc_page_number))
      {
         ibcL.useResume(true);

         if (ibcL.writeDataPage(wspc_page_number, pagedata))
         {
            if (ibcL.writeScratchPad(wspc_page_number, 0,
                                     authentication_pad_challenge, 0, 32))
            {
               int addr = wspc_page_number << 5;

               if (ibcL.SHAFunction(ibc.VALIDATE_DATA_PAGE, addr))
               {
                  if (ibcL.matchScratchPad(authentication_mac))
                  {
                     ibcL.useResume(false);

                     //pagedata has already been copied into where it needs to be
                     return write_cycle_counter;
                  }
                  else
                  {
                     last_error = AUTHENTICATION_FAILED_ERROR;
                  }
               }
               else
                  last_error = SHA_FUNCTION_ERROR;
            }
            else
               last_error = WRITE_SCRATCHPAD_ERROR;
         }
         else
            last_error = WRITE_MEMORY_PAGE_ERROR;
      }
      else
         last_error = BIND_SECRET_ERROR;

      ibc.useResume(false);

      return -1;
   }

   /**
    * Determines if this SHAiButton has enough of its parameters
    * set to be a coprocessor iButton.  To be a coprocessor, the
    * following must be set:<br>
    * <ul><li>byte[] filename - 4 bytes of the name + 1 byte extension</li>
    *     <li>int signing_page_number</li>
    *     <li>int workspace_page_number</li>
    *     <li>int authentication_page_number</li></ul>
    * The initial signature defaults to 20 bytes of 0x00's.  The
    * binding code and binding data default to all 0x0ff's.  The
    * signing challenge defaults to 3 bytes of 0x00's.
    *
    * @return True if this button can act as a coprocessor.
    */
   public boolean isCoprocessor ()
   {
      return isCoprocessor;
   }

   /**
    * Returns a string with the unique address of the SHA iButton
    * in hex.
    *
    * @return A string with the unique address of the SHA iButton in hex.
    */
   public String toString ()
   {
      return ibc.getAddressAsString();
   }

   /**
    * Sets up this SHAiButton as a user iButton.
    *
    * @param file_page_number The page number of the SHAiButton that contains the money file.
    * @return True if successful.
    */
   public synchronized boolean setUser (int file_page_number)
   {
      if (file_page_number < 8)
      {
         file_page_number += 8;   //assume that it points to the first page of a multi-page file,

         //the next page must be the corresponding secure page
      }

      user_account_file_page = file_page_number;
      generic_money_page [0] = ( byte ) 0x1d;   //TMEX length

      //transaction ID is at locations 1-2            ]
      //money conversion factor is at locations 3-4   ]  this is all taken care
      //balance is at locations 5,6,7                 ]  of in the sign data function
      //signature is at locations 8-27                ]
      //data type is at location 28                   ]
      //continuation pointer is at location 29        ]
      //CRC is at locations 30,31                     ]
      //now the coprocessor must copy in initial_signature and balance, and then sign!
      isUser = true;

      return true;
   }

   /**
    * Sets the initial signature.  <br>
    * <br>
    * The initial signature is only used
    * by coprocessor iButtons.  <br>
    *
    * @param sig_ini Byte array containing the new initial signature.  Up to 20
    * bytes will be copied if sig_ini's length permits.
    * @param start Index to start reading the new initial signature from sig_ini.
    */
   public synchronized void setInitialSignature (byte[] sig_ini, int start)
   {
      System.arraycopy(sig_ini, start, initial_signature, 0,
                       ((sig_ini.length > 20 + start) ? 20
                                                      : sig_ini.length
                                                        - start));
   }

   /**
    * Sets the signing challenge.  <br>
    * <br>
    * The signing challenge is only used by
    * coprocessor iButtons.  <br>
    *
    * @param ch Byte array containing the new random challenge.  Up to 3 bytes wil be copied,
    * if ch.length permits.
    * @param start Index into the array 'ch' to start copying from.
    */
   public synchronized void setSigningChallenge (byte[] ch, int start)
   {
      System.arraycopy(ch, start, signing_challenge, 0,
                       ((ch.length > 3 + start) ? 3
                                                : ch.length - start));
   }

   /**
    * Sets the file name for this coprocessor's service file.
    * Example: DLSM.102<br><pre>
    *   buf[0] = (byte)'D';
    *   buf[1] = (byte)'L';
    *   buf[2] = (byte)'S';
    *   buf[3] = (byte)'M';
    *   buf[4] = (byte)102;
    * </pre>
    * This function <em>must</em> be called to initialize
    * a coprocessor.
    *
    * @param buf Byte array containing the file name and extension.  See above.
    * 5 bytes must be available to copy.
    * @param start Index to start copying from byte array.
    */
   public synchronized void setFilename (byte[] buf, int start)
   {
      filename = new byte [5];

      System.arraycopy(buf, start, filename, 0, 5);
      checkCoprocessor();
   }

   /**
    * Sets the page used to generate signatures.  Valid choices
    * are 0 and 8.  <br>
    * <br>
    * This function <em>must</em> be called to initialize
    * a coprocessor.  <br>
    *
    * @param pg Page number for generating signatures.  Valid choices are 0 and 8.
    */
   public synchronized void setSigningPageNumber (int pg)
   {
      if (pg == 0)
         pg = 8;

      signing_page_number = pg;

      checkCoprocessor();
   }

   /**
    * Sets the page that the authentication secret is
    * installed on.  <br>
    * <br>
    * This function <em>must</em> be called to initialize
    * a coprocessor.  <br>
    *
    * @param pg Page the installation secret is installed on.
    */
   public synchronized void setAuthenticationPageNumber (int pg)
   {
      if (pg < 8)
         pg += 8;

      authentication_page_number = pg;

      checkCoprocessor();
   }

   /**
    * Page the iButton will use to re-generate user iButton
    * specific secrets.  No data should be stored here, as it
    * will be overridden when the system tries to authenticate.  <br>
    * <br>
    * This function <em>must</em> be called to initialize
    * a coprocessor.  <br>
    *
    * @param pg Workspace page number.
    */
   public synchronized void setWorkspacePageNumber (int pg)
   {
      workspace_page_number = pg;

      checkCoprocessor();
   }

   private boolean checkCoprocessor ()
   {
      if ((signing_page_number != -1) && (authentication_page_number != -1)
              && (workspace_page_number != -1) && (filename != null))
         isCoprocessor = true;

      return isCoprocessor;
   }

   /**
    * Returns the name of this coprocessor's
    * service file.  Throws a null pointer exception
    * if the file name has not been set.
    *
    * @return String of length 4.
    */
   public String getUserFileName ()
   {

      //return user_account_file;
      return new String(filename, 0, 4);
   }

   /**
    * Returns the extension of this coprocessor's
    * service file.    Throws a null pointer exception
    * if the file name has not been set.
    *
    * @return File extension.
    */
   public int getUserFileExtension ()
   {

      //return user_account_file_extension;
      return (filename [4] & 0x0ff);
   }

   private byte[] sign_scratchpad1 = new byte [32];

   /**
    * Makes this coprocessor produce a signature for a new amount
    * of money for the user SHAiButton, then places the new
    * money file on the user.
    *
    * @param user SHAiButton object that has been intiialized as a user.
    * @param newbalance New balance to be stored on the user SHA iButton.
    * @param write_cycle_counter The current write cycle counter of the user iButton's
    * money page.  The write cycle counter is part of the
    * signature to make sure someone does not try to
    * 're-install' money.  If this is initialization of the
    * SHA iButton, make this argument -1 and the code will determine
    * the value of the write cycle counter to use.  This costs extra time,
    * so do not call with this argument as -1 in normal transactions.
    * @param pagedata The money data page.
    * @return True if successful.
    * @exception com.dalsemi.onewire.adapter.OneWireIOException
    * @exception com.dalsemi.onewire.OneWireException
    */
   public synchronized boolean signDataFile (SHAiButton user, int newbalance,
                                             int write_cycle_counter,
                                             byte[] pagedata)
      throws OneWireIOException, OneWireException
   {
      last_error = NO_ERROR;

      if (!isCoprocessor)
      {
         last_error = NO_COPROCESSOR_ERROR;

         return false;
      }

      if (!user.isUser)
      {
         last_error = NO_USER_ERROR;

         return false;
      }

      //local cache
      byte[] user_generic_money_page     = user.generic_money_page;
      int    user_user_account_file_page = user.user_account_file_page;
      byte[] sign_scratchpad             = sign_scratchpad1;

      System.arraycopy(pagedata, 1, user_generic_money_page, 1, 4);

      user_generic_money_page [28] = pagedata [28];

      System.arraycopy(initial_signature, 0, user_generic_money_page, 8,
                       initial_signature.length > 20 ? 20
                                                     : initial_signature.length);

      user_generic_money_page [5]  = ( byte ) newbalance;
      user_generic_money_page [6]  = ( byte ) (newbalance >> 8);
      user_generic_money_page [7]  = ( byte ) (newbalance >> 16);
      user_generic_money_page [30] = ( byte ) 0;
      user_generic_money_page [31] = ( byte ) 0;

      int wcc = 0;

      if (write_cycle_counter == -1)
      {

         //this should only be done on initialization of user ibutton by coprocessor,
         //not during transactions
         //now we also need to get things like wcc, user_page_number, user ID
         byte[] wcc_page = new byte [32];   //this is OK, we are not in critical path here

         //so malloc-ing doesnt really matter
         user.ibc.readMemoryPage(19, wcc_page, 0);

         //we also need to increment the WCC since we are going to write then later verify
         int offset = (user_user_account_file_page & 7) << 2;

         wcc = wcc_page [offset++] & 0x0ff;
         wcc = wcc | ((wcc_page [offset++] & 0x0ff) << 8);
         wcc = wcc | ((wcc_page [offset++] & 0x0ff) << 16);
         wcc = wcc | ((wcc_page [offset] & 0x0ff) << 24);
      }
      else
         wcc = write_cycle_counter;

      //we need to increment the write_cycle_counter since we will be writing to the part
      wcc++;

      sign_scratchpad [8]  = ( byte ) (wcc & 0x0ff);
      sign_scratchpad [9]  = ( byte ) ((wcc >> 8) & 0x0ff);
      sign_scratchpad [10] = ( byte ) ((wcc >> 16) & 0x0ff);
      sign_scratchpad [11] = ( byte ) ((wcc >> 24) & 0x0ff);
      sign_scratchpad [12] = ( byte ) user_user_account_file_page;

      System.arraycopy(user.address, 0, sign_scratchpad, 13, 7);
      System.arraycopy(signing_challenge, 0, sign_scratchpad, 20, 3);

      OneWireContainer18 ibcL = ibc;

      //now we are ready to make a signature
      if (!ibcL.writeDataPage(signing_page_number, user_generic_money_page))   //writing to THIS ibutton
      {
         last_error = WRITE_MEMORY_PAGE_ERROR;

         return false;
      }

      ibcL.useResume(true);

      if (!ibcL.writeScratchPad(0, 0, sign_scratchpad, 0, 32))
      {
         last_error = WRITE_SCRATCHPAD_ERROR;

         ibcL.useResume(false);

         return false;
      }

      //sign that baby!
      int addr = signing_page_number << 5;

      if (!ibcL.SHAFunction(ibc.SIGN_DATA_PAGE, addr))
      {
         last_error = SHA_FUNCTION_ERROR;

         ibcL.useResume(false);

         return false;
      }

      //after signature make sure to dump in the inverted CRC
      ibcL.readScratchPad(sign_scratchpad, 0);
      System.arraycopy(sign_scratchpad, 8, user.generic_money_page, 8, 20);

      int crc = ~CRC16.compute(user_generic_money_page, 0,
                               user_generic_money_page [0] + 1,
                               user_user_account_file_page);

      user_generic_money_page [30] = ( byte ) crc;
      user_generic_money_page [31] = ( byte ) (crc >> 8);

      ibcL.useResume(false);

      //now put the page on the user ibutton
      if (!user.ibc.writeDataPage(user_user_account_file_page,
                                  user_generic_money_page))
      {
         last_error = WRITE_MEMORY_PAGE_ERROR;

         return false;
      }

      return true;
   }

   private byte[] verify_macpage     = new byte [20];
   private byte[] verify_scratchpad1 = new byte [32];

   /**
    * This method allows a coprocessor to verify the money file
    * from a user iButton.
    *
    * @param userpage The full 32 byte TMEX file from the user SHA iButton.
    * @param user The SHAiButton object intialized as a user object.
    * @param wcc The write cycle counter of the user's money page.
    * @return True if the data file is valid.
    * @exception com.dalsemi.onewire.adapter.OneWireIOException
    * @exception com.dalsemi.onewire.OneWireException
    */
   public synchronized boolean verifyUserMoney (byte[] userpage,
           SHAiButton user, int wcc)
      throws OneWireIOException, OneWireException
   {

      //WE TALK EXCLUSIVELY TO THE COPROCESSOR HERE SO WE CAN USE RESUME
      //  trick - call writeDataPage first then set resume to true.  writeDataPage only does one select
      //but then we don't have an extra method invocation, so its down below
      last_error = NO_ERROR;

      int    page              = user.user_account_file_page;
      byte[] verify_scratchpad = verify_scratchpad1;

      if (!isCoprocessor)
      {
         last_error = NO_COPROCESSOR_ERROR;

         return false;
      }

      if (!user.isUser)   //don't do this check yet
      {
         last_error = NO_USER_ERROR;

         return false;
      }

      System.arraycopy(userpage, 8, verify_macpage, 0, 20);

      //now lets 0-out the mac and the CRC
      System.arraycopy(initial_signature, 0, userpage, 8, 20);

      userpage [30] = ( byte ) 0;
      userpage [31] = ( byte ) 0;

      //now we also need to get things like wcc, user_page_number, user ID
      verify_scratchpad [8]  = ( byte ) (wcc);
      verify_scratchpad [9]  = ( byte ) (wcc >> 8);
      verify_scratchpad [10] = ( byte ) (wcc >> 16);
      verify_scratchpad [11] = ( byte ) (wcc >> 24);
      verify_scratchpad [12] = ( byte ) page;

      System.arraycopy(user.address, 0, verify_scratchpad, 13, 7);
      System.arraycopy(signing_challenge, 0, verify_scratchpad, 20, 3);

      //now we are ready to make a signature
      if (!ibc.writeDataPage(signing_page_number, userpage))   //writing to THIS ibutton
      {
         last_error = WRITE_MEMORY_PAGE_ERROR;

         ibc.useResume(false);

         return false;
      }

      ibc.useResume(true);

      if (!ibc.writeScratchPad(0, 0, verify_scratchpad, 0, 32))
      {
         last_error = WRITE_SCRATCHPAD_ERROR;

         ibc.useResume(false);

         return false;
      }

      //sign that baby!
      int addr = signing_page_number << 5;

      if (!ibc.SHAFunction(ibc.VALIDATE_DATA_PAGE, addr))
      {
         last_error = SHA_FUNCTION_ERROR;

         ibc.useResume(false);

         return false;
      }
      else
      {
         if (ibc.matchScratchPad(verify_macpage))
         {
            ibc.useResume(false);

            return true;
         }
         else
         {
            last_error = VERIFICATION_FAILED_ERROR;
         }
      }

      ibc.useResume(false);

      return false;
   }

   private byte[] pg0 = new byte [32];

   /**
    * Utility to read a TMEX file starting at a specified page.  <br>
    * <br>
    * Call readFile(0) to read the root directory.  <br>
    *
    * @param start_page Page of the DS1963S to start reading from.
    * @param page Byte array to put the file into.  Either the
    * entire file will be placed in this buffer or up to
    * page.length bytes will be placed.
    * @return Integer length of data read.
    * @exception com.dalsemi.onewire.adapter.OneWireIOException
    * @exception com.dalsemi.onewire.OneWireException
    */
   public synchronized int readFile (int start_page, byte[] page)
      throws OneWireIOException, OneWireException
   {
      last_error = NO_ERROR;

      int index           = 0;
      int cont_ptr        = -1;
      int length_to_write = 0;

      while ((cont_ptr != 0) && (index < page.length))
      {
         ibc.readMemoryPage(start_page, pg0, 0);

         int len = pg0 [0];

         if ((0x0ff & len) > 32)   //bad read, not a file...
            return 0;

         length_to_write = (index + len - 1 > page.length)
                           ? page.length - index
                           : len - 1;

         //baos.write(pg0,1,len-1);
         System.arraycopy(pg0, 1, page, index, length_to_write);

         index    += length_to_write;
         cont_ptr = pg0 [len];

         if (CRC16.compute(pg0, 0, len + 3, start_page) != 0xb001)
         {
            last_error = CRC_ERROR;

            return 0;
         }

         start_page = cont_ptr;
      }

      return index;
   }

   /**
    * Returns the last error that occurred.  See above for a description of different errors.
    *
    * @return Integer representing last error.
    */
   public int getLastError ()
   {
      return last_error;
   }
}
