
/*---------------------------------------------------------------------------
 * 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.adapter.OneWireIOException;
import com.dalsemi.onewire.OneWireException;
import com.dalsemi.onewire.utils.CRC16;
import com.dalsemi.onewire.adapter.DSPortAdapter;
import java.util.Vector;
import java.util.Enumeration;


/**
 * OneWire container for iButton family type 18 (hex), DS1963S.<br>
 * <br>
 * This container makes use of several optimizations to help it run fast
 * on TINI.  These optimizations do little for the PC, but they do not
 * slow down the PC.  Most methods are synchronized because they all access
 * an instance byte array.  This is less expensive than creating new byte
 * arrays for every method, because in virtually all cases there should
 * not be contention for the resources in this container between threads.
 * Threads should use the DSPortAdapter methods beginExclusive and
 * endExclusive to synchronize on the OneWire port.<br>
 * <br>
 * This container provides the functionality to use the raw power of the
 * DS1963S.  It does not immediately implement transactions and authentication.
 * The class com.dalsemi.onewire.container.SHAiButton does these.  The SHAiButton
 * class exists on top of this class, making higher level calls to implement
 * transactions.<br>
 * <br>
 * Notice that for maximum performance, you should call the method setSpeedCheck()
 * with an argument <code>false</code> before any other methods that access the
 * OneWire.  A program that calls this function is assumed to understand and
 * control the speed of communication.  If not called, a call to the function
 * OneWireContainer.doSpeed() will occur in almost every function.  While this should
 * guarantee that you never have the bus at an unknown speed, it will slow down your
 * throughput considerably.<br>
 *
 * @author KLA
 * @version 0.00, 28 Aug 2000
 * @see com.dalsemi.onewire.container.SHAiButton
 */
public class OneWireContainer18
   extends OneWireContainer
{

   //--------
   //-------- Variables
   //--------

   /**
    * Scratchpad access memory bank
    */
   private MemoryBankScratch scratch;

   /**
    * Main memory access memory bank
    */
   private MemoryBankNVCRC memory;

   /**
    * Main memory with counter access memory bank
    */
   private MemoryBankNVCRC memoryPlus;
   private int             block_wait_count = 20;   //number of times we wait for the 10101010... to appear

   //on commands such as read_auth_page, copy_scratch...

   /** Field TA1, TA2, ES           */
   public byte TA1, TA2, ES;   //for use in the copy scratchpad command, these are

   //public so that someone can bypass the readScratchPad
   //command if speed is more of a concern
   private boolean resume = false;   //are we currently using resume?  some internal code

   //makes use of the resume function, but often we can't
   //tell from inside this class
   private boolean doSpeedEnable = true;   //should we call doSpeed every time we do something?

   //this is the safe way to make sure we never loose
   //communication, but this stuff has really gotta
   //fly, so to make it fly, disable this before doing anything
   //else!
   private byte[] byte_buffer     = new byte [60];   //use this everywhere to communicate
   private byte[] FF              = new byte [60];   //use this to fill an array with 0x0ff's
   private byte[] private_address = new byte [8];

   //--------
   //-------- PUBLIC STATIC FINAL's
   //--------

   /*
    * DS1963S Memory and SHA commands
    * See the datasheet for the DS1963 for more information on use.
    */

   /** Field READ_MEMORY           */
   public static final byte READ_MEMORY = ( byte ) 0xF0;

   /** Field WRITE_SCRATCHPAD           */
   public static final byte WRITE_SCRATCHPAD = ( byte ) 0x0F;

   /** Field MATCH_SCRATCHPAD           */
   public static final byte MATCH_SCRATCHPAD = ( byte ) 0x3C;

   /** Field ERASE_SCRATCHPAD           */
   public static final byte ERASE_SCRATCHPAD = ( byte ) 0xC3;

   /** Field READ_SCRATCHPAD           */
   public static final byte READ_SCRATCHPAD = ( byte ) 0xAA;

   /** Field READ_AUTHENTICATED_PAGE           */
   public static final byte READ_AUTHENTICATED_PAGE = ( byte ) 0xA5;

   /** Field COPY_SCRATCHPAD           */
   public static final byte COPY_SCRATCHPAD = ( byte ) 0x55;

   /** Field COMPUTE_SHA           */
   public static final byte COMPUTE_SHA = ( byte ) 0x33;

   //SHA commands

   /** Field COMPUTE_FIRST_SECRET           */
   public static final byte COMPUTE_FIRST_SECRET = ( byte ) 0x0F;

   /** Field COMPUTE_NEXT_SECRET           */
   public static final byte COMPUTE_NEXT_SECRET = ( byte ) 0xF0;

   /** Field VALIDATE_DATA_PAGE           */
   public static final byte VALIDATE_DATA_PAGE = ( byte ) 0x3C;

   /** Field SIGN_DATA_PAGE           */
   public static final byte SIGN_DATA_PAGE = ( byte ) 0xC3;

   /** Field COMPUTE_CHALLENGE           */
   public static final byte COMPUTE_CHALLENGE = ( byte ) 0xCC;

   /** Field AUTH_HOST           */
   public static final byte AUTH_HOST = ( byte ) 0xAA;

   //special DS1963S command, allows you to resume communicating
   //without the entire select call

   /** Field RESUME           */
   public static final byte RESUME = ( byte ) 0xA5;

   //--------
   //-------- Constructors
   //--------

   /**
    * Default constructor
    */
   public OneWireContainer18 ()
   {
      super();

      for (int i = 0; i < FF.length; i++)
         FF [i] = ( byte ) 0x0ff;

      // initialize the memory banks
      initMem();
   }

   /**
    * Create a container with a provided adapter object
    * and the address of the iButton or 1-Wire device.
    *
    * @param  sourceAdapter     adapter object required to communicate with
    * this iButton.
    * @param  newAddress        address of this 1-Wire device
    */
   public OneWireContainer18 (DSPortAdapter sourceAdapter, byte[] newAddress)
   {
      super(sourceAdapter, newAddress);

      for (int i = 0; i < FF.length; i++)
         FF [i] = ( byte ) 0x0ff;

      // initialize the memory banks
      initMem();
   }

   /**
    * Create a container with a provided adapter object
    * and the address of the iButton or 1-Wire device.
    *
    * @param  sourceAdapter     adapter object required to communicate with
    * this iButton.
    * @param  newAddress        address of this 1-Wire device
    */
   public OneWireContainer18 (DSPortAdapter sourceAdapter, long newAddress)
   {
      super(sourceAdapter, newAddress);

      for (int i = 0; i < FF.length; i++)
         FF [i] = ( byte ) 0x0ff;

      // initialize the memory banks
      initMem();
   }

   /**
    * Create a container with a provided adapter object
    * and the address of the iButton or 1-Wire device.
    *
    * @param  sourceAdapter     adapter object required to communicate with
    * this iButton.
    * @param  newAddress        address of this 1-Wire device
    */
   public OneWireContainer18 (DSPortAdapter sourceAdapter, String newAddress)
   {
      super(sourceAdapter, newAddress);

      for (int i = 0; i < FF.length; i++)
         FF [i] = ( byte ) 0x0ff;

      // initialize the memory banks
      initMem();
   }

   //--------
   //-------- Methods
   //--------

   /**
    * Provide this container the adapter object used to access this device
    * and provide the address of this iButton or 1-Wire device.  Override the
    * OneWireContainer method because it has a malloc in it.  This just makes
    * it a little faster for TINI.
    *
    *
    * @param  sourceAdapter     adapter object required to communicate with
    *                           this iButton.
    * @param  newAddress        address of this 1-Wire device
    */
   public void setupContainer (DSPortAdapter sourceAdapter, byte[] newAddress)
   {
      super.setupContainer(sourceAdapter, newAddress);

      // set the Address
      synchronized (this)
      {
         System.arraycopy(newAddress, 0, private_address, 0, 8);

         address = private_address;
      }
   }

   /**
    * Retrieve the Dallas Semiconductor part number of the iButton
    * as a string.  For example 'DS1992'.
    *
    * @return string represetation of the iButton name.
    */
   public String getName ()
   {
      return "DS1963S";
   }

   /**
    * Retrieve the alternate Dallas Semiconductor part numbers or names.
    * A 'family' of MicroLAN devices may have more than one part number
    * depending on packaging.  There can also be nicknames such as
    * 'Crypto iButton'.
    *
    * @return string represetation of the alternate names.
    */
   public String getAlternateNames ()
   {
      return "SHA-1 iButton";
   }

   /**
    * Retrieve a short description of the function of the iButton type.
    *
    * @return string represetation of the function description.
    */
   public String getDescription ()
   {
      return "4096 bits of read/write nonvolatile memory. Memory "
             + "is partitioned into sixteen pages of 256 bits each. "
             + "Has overdrive mode.  One-chip 512-bit SHA-1 engine "
             + "and secret storage.";
   }

   /**
    * Returns the maximum speed this iButton can communicate at.
    */
   public int getMaxSpeed ()
   {
      return DSPortAdapter.SPEED_OVERDRIVE;
   }

   /**
    * Return an enumeration of memory banks. Look at the
    * MemoryBank, PagedMemoryBank and OTPMemoryBank classes.
    */
   public Enumeration getMemoryBanks ()
   {
      Vector bank = new Vector(5);

      // Address number in read-only-memory
      bank.addElement(new MemoryBankROM(this));

      // scratchpad
      bank.addElement(scratch);

      // NVRAM (no write cycle)
      bank.addElement(memory);

      // NVRAM (with write cycle counters)
      bank.addElement(memoryPlus);

      // Page Write cycle counters
      MemoryBankNV cnt = new MemoryBankNV(this,
                                          ( MemoryBankScratch ) scratch);

      cnt.numberPages          = 3;
      cnt.size                 = 96;
      cnt.bankDescription      = "Write cycle counters and PRNG counter";
      cnt.startPhysicalAddress = 608;
      cnt.readOnly             = true;
      cnt.pageAutoCRC          = false;
      cnt.generalPurposeMemory = false;
      cnt.readWrite            = false;

      bank.addElement(cnt);

      return bank.elements();
   }

   /**
    * Construct the memory banks used for I/O.
    */
   private void initMem ()
   {

      // scratchpad
      scratch = new MemoryBankScratchSHA(this);

      // NVRAM (no write cycle)
      memory                      = new MemoryBankNVCRC(this,
              ( MemoryBankScratch ) scratch);
      memory.numberPages          = 8;
      memory.size                 = 256;
      memory.extraInfoLength      = 8;
      memory.readContinuePossible = false;
      memory.numVerifyBytes       = 8;

      // NVRAM (with write cycle counters)
      memoryPlus                      = new MemoryBankNVCRC(this,
              ( MemoryBankScratch ) scratch);
      memoryPlus.numberPages          = 8;
      memoryPlus.size                 = 256;
      memoryPlus.bankDescription      = "Memory with write cycle counter";
      memoryPlus.startPhysicalAddress = 256;
      memoryPlus.extraInfo            = true;
      memoryPlus.extraInfoDescription = "Write cycle counter";
      memoryPlus.extraInfoLength      = 8;
      memoryPlus.readContinuePossible = false;
      memoryPlus.numVerifyBytes       = 8;
   }

   ////////////////////////////////////////////////////////////////////   
   //SHAiButton real methods!!!!!!   
   ////////////////////////////////////////////////////////////////////

   /**
    * To ensure that all parts can talk to the onewire bus
    * at their desired speed, each method contains a call
    * to doSpeed().  However, this is an expensive operation
    * and the functions that talk to the DS1963S must be
    * fast.  Therefore, you can handle the speed in your
    * application without the expense of the doSpeed call
    * by calling this with false.  The default behaviour is
    * to call doSpeed().
    *
    * @param doSpeedCheck True if you want doSpeed() called before every
    * onewire bus access.  False to skip this expensive
    * call.
    */
   public synchronized void setSpeedCheck (boolean doSpeedCheck)
   {
      doSpeedEnable = doSpeedCheck;
   }

   /**
    * Tells the OneWireContainer18 whether it can use the resume command.
    * If it uses the resume command, the entire 9 byte select sequence is
    * abandoned for a one byte resume command.  If you talk to another device,
    * you must turn resume to false, select the part again, and then set resume
    * to true.
    *
    * @param set True means use the resume command instead of the select command.
    */
   public synchronized void useResume (boolean set)
   {
      resume = set;
   }

   /**
    * Erases the scratchpad of the DS1963S.  This must be used to do
    * any normal memory operations, as the cryptographic features of
    * the button leave the memory in 'hidden' mode, so that you cannot
    * read the scratchpad, copy to normal memory space, or write the
    * scratchpad.  Fills the scratchpad with 0x0ff.
    *
    * @param page The target page number.  Normally this does not matter, but in cases
    * where you would like to erase a memory page, you would call this method
    * with the page number, then read scratchpad to read back the registers,
    * then copy scratchpad to erase the page.
    * @return True if successful.
    * @exception com.dalsemi.onewire.adapter.OneWireIOException
    * @exception com.dalsemi.onewire.OneWireException
    */
   public synchronized boolean eraseScratchPad (int page)
      throws OneWireIOException, OneWireException
   {
      if (doSpeedEnable && (!resume))
         doSpeed();

      // select the device
      if (!resume)
         adapter.select(address);
      else
      {
         adapter.reset();
         adapter.putByte(RESUME);
      }

      // build block to send
      byte[] buffer = byte_buffer;

      buffer [0] = ERASE_SCRATCHPAD;
      buffer [1] = ( byte ) (page << 5);
      buffer [2] = ( byte ) (page >> 3);

      System.arraycopy(FF, 0, buffer, 3, 3);

      // send block (check copy indication complete)
      adapter.dataBlock(buffer, 0, 6);

      if (buffer [5] == ( byte ) 0x0ff)
         return waitForSuccessfulFinish();
      else
         return true;
   }

   /**
    * Waits for the DS1963S's output to alternate.  Several operations must
    * wait for the DS1963S to stop sending 0x0ff's and begin alternating its
    * bits.  This method reads until it finds a non-0x0ff byte or until it
    * reaches a specified number of tries, indicating failure.
    *
    * @return True if the DS1963S completed its operation successfully.
    * False if the DS1963S failed its operation.  See the datasheet
    * for which operations function in this way.
    * @exception com.dalsemi.onewire.adapter.OneWireIOException
    * @exception com.dalsemi.onewire.OneWireException
    */
   public synchronized boolean waitForSuccessfulFinish ()
      throws OneWireIOException, OneWireException
   {

      //this method should be called after another method, so let's not worry
      //about making sure the speed is right, it should be already
      int count = 0;

      while (adapter.getByte() == 0xff)
      {
         count++;

         if (count == block_wait_count)
            return false;
      }

      return true;
   }

   /**
    * Reads a memory page from the DS1963S.  Pages 0-15 are normal memory pages.
    * Pages 16 and 17 are the secrets, so 0x0ff's are returned by the iButton.
    * Page 18 is the scratchpad--the data is returned if the part is not in
    * hidden mode (else 0x0ff's are returned).  Pages 19 and 20 are the
    * write cycle counters.  Page 21 contains the counter for the pseudo
    * random number generator.
    *
    * @param pageNum Page number to read.
    * @param data Byte array for the return of the data.   This array must be AT LEAST 32 bytes long.
    * @param start Location in the byte array to start copying page data to.
    * @exception com.dalsemi.onewire.adapter.OneWireIOException
    * @exception com.dalsemi.onewire.OneWireException
    */
   public void readMemoryPage (int pageNum, byte[] data, int start)
      throws OneWireIOException, OneWireException
   {

      //don't need to be synchronized, since readMemoryPage(int, byte, int, byte[], int) is
      readMemoryPage(pageNum, READ_MEMORY, 32, data, start);
   }

   /*
    * read the contents of a data page
    */
   private synchronized void readMemoryPage (int pageNum, byte COMMAND,
                                             int bytes_to_read, byte[] data,
                                             int start)
      throws OneWireIOException, OneWireException
   {
      if (doSpeedEnable && (!resume))
         doSpeed();

      if (!resume)
         adapter.select(address);
      else
      {
         adapter.reset();
         adapter.putByte(RESUME);
      }

      byte[] buffer = byte_buffer;
      int    addr   = pageNum << 5;   //pageNumber * 32

      buffer [0] = COMMAND;
      buffer [1] = ( byte ) addr;
      buffer [2] = ( byte ) (addr >> 8);

      System.arraycopy(FF, 0, buffer, 3, bytes_to_read);
      adapter.dataBlock(buffer, 0, 3 + bytes_to_read);

      // copy data for return
      System.arraycopy(buffer, 3, data, start, bytes_to_read);
   }

   /**
    * Reads and authenticates a page.  See readMemoryPage for a description
    * of page numbers.  This method will also generate a signature for the
    * selected page, used in the authentication of roaming (User) iButtons.
    * Extra data is returned with the page as well--such as the write cycle
    * counter for the page and the write cycle counter of the selected
    * secret page.
    *
    * @param pageNum Page number to read and authenticate.
    * @param data Byte array for the page data plus extra information (2 write cycle
    * coutnersof 4 bytes each, one 2 byte CRC, appended after 32 bytes of
    * the data page).  This byte array must be AT LEAST 42 bytes long.
    * @param start Offset to copy into the array data.
    * @return True if successful.
    * @exception com.dalsemi.onewire.adapter.OneWireIOException
    * @exception com.dalsemi.onewire.OneWireException
    */

   //data must be 42+start bytes long
   public boolean readAuthenticatedPage (int pageNum, byte[] data, int start)
      throws OneWireIOException, OneWireException
   {

      //don't need to be synchronized, since readMemoryPage(int, byte, int, byte[], int) is
      //read 42 bytes = 32 page bytes + 4 bytes secret counter + 4 bytes page counter + 2 bytes CRC
      readMemoryPage(pageNum, READ_AUTHENTICATED_PAGE, 42, data, start);

      int crc = CRC16.compute(READ_AUTHENTICATED_PAGE);

      crc = CRC16.compute(( byte ) (pageNum << 5), crc);   //(pagenumber*32 = address) lower 8 bits
      crc = CRC16.compute(( byte ) (pageNum >>> 3), crc);   //pagenumber*32 is pagenumber<<5, but

      //we want the upper 8 bits, so we would just do
      // (pagenum<<5)>>>8, so just make it one op
      // check CRC16
      if (CRC16.compute(data, start, 42, crc) != 0xB001)
      {
         return false;
      }

      return (waitForSuccessfulFinish());
   }

   /**
    * Write to the scratchpad.  Note that the addresses passed to this
    * method will be the addresses the data is copied to if the copy function
    * is called afterward.  Also note that if too many bytes are tried to write,
    * this method will truncate the data so that only valid data will be sent.
    *
    * @param targetPage The page number this data will eventually be copied to.
    * @param targetPageOffset The offset on the page to copy this data to.
    * @param inputbuffer The data that will be copied into the scratchpad.
    * @param start Offset into the input buffer for the data to write.
    * @param length The number of bytes to write.
    * @return True if successful.
    * @exception com.dalsemi.onewire.adapter.OneWireIOException
    * @exception com.dalsemi.onewire.OneWireException
    */
   public synchronized boolean writeScratchPad (int targetPage,
           int targetPageOffset, byte[] inputbuffer, int start, int length)
      throws OneWireIOException, OneWireException
   {
      if (doSpeedEnable && (!resume))
         doSpeed();

      if (!resume)
         adapter.select(address);
      else
      {
         adapter.reset();
         adapter.putByte(RESUME);
      }

      int    addr   = (targetPage << 5) + targetPageOffset;
      byte[] buffer = byte_buffer;

      buffer [0] = WRITE_SCRATCHPAD;
      buffer [1] = ( byte ) addr;
      buffer [2] = ( byte ) (addr >> 8);

      int maxbytes = 32 - (addr & 31);

      if (length > maxbytes)
         length = maxbytes;   //if we are going to go over the 32byte boundary

      //let's cut it
      System.arraycopy(inputbuffer, start, buffer, 3, length);

      buffer [3 + length] = ( byte ) 0xff;
      buffer [4 + length] = ( byte ) 0xff;   //leave space for the CRC

      //this nasty statement sends the length depending on if we are
      //going to get a CRC back.  you only get a CRC if you get to the end of
      //the scratchpad writing.  so we look at the address and add the length 
      //that we are writing.  if this is not a multiple of 32 then we do not
      //finish reading at the scratchpad boundary (and we checked earlier to 
      //make sure don't go over THEREFORE we have gone under).  if we are at
      //the boundary we need two extra bytes to read the crc
      adapter.dataBlock(buffer, 0,
                        ((((addr + length) & 31) == 0) ? length + 5
                                                       : length + 3));

      //if we dont check the CRC we are done
      if (((addr + length) & 31) != 0)
         return true;

      //else we need to do a CRC calculation
      if (CRC16.compute(buffer, 0, length + 5, 0) != 0xB001)
      {
         return false;
      }

      return true;
   }

   /**
    * After a Validate command, the scratchpad contains a signature
    * that cannot be read, but must be checked against another
    * signature.  This method allows you to pass in an outside
    * signature to see if it equals the hidden signature in the
    * DS1963S's scratchpad.
    *
    * @param mac Byte array containing the signature.  Must be at least 20 bytes long,
    * and the signature must start at offset 0.
    * @return True if the signature matches.
    * @exception com.dalsemi.onewire.adapter.OneWireIOException
    * @exception com.dalsemi.onewire.OneWireException
    */
   public synchronized boolean matchScratchPad (byte[] mac)
      throws OneWireIOException, OneWireException
   {
      if (doSpeedEnable && (!resume))
         doSpeed();

      if (!resume)
         adapter.select(address);
      else
      {
         adapter.reset();
         adapter.putByte(RESUME);
      }

      byte[] buffer = byte_buffer;

      buffer [0] = MATCH_SCRATCHPAD;

      System.arraycopy(mac, 0, buffer, 1, 20);

      buffer [21] = ( byte ) 0x0ff;   //CRC1
      buffer [22] = ( byte ) 0x0ff;   //CRC2
      buffer [23] = ( byte ) 0x0ff;   //status

      adapter.dataBlock(buffer, 0, 24);

      if (CRC16.compute(buffer, 0, 23, 0) != 0xB001)
      {
         return false;
      }

      if (buffer [23] != ( byte ) 0x0ff)
         return true;

      return false;
   }

   /**
    * Read the contents of the scratchpad.  If the part is in hidden
    * mode, 0x0ff's will be returned.
    *
    * @param data Byte array to hold the contents of the scratchpad.
    * Up to 32 bytes will be copied.  Fewer bytes may be
    * copied if the target address stored inside the
    * DS1963S is not a multiple of 32.
    * @param start Offset into the byte array to copy the scratchpad data.
    * @return The number of bytes read, or -1 if there was a failure.
    * @exception com.dalsemi.onewire.adapter.OneWireIOException
    * @exception com.dalsemi.onewire.OneWireException
    */

   //returns the length of SP read
   public synchronized int readScratchPad (byte[] data, int start)
      throws OneWireIOException, OneWireException
   {
      if (doSpeedEnable && (!resume))
         doSpeed();

      if (!resume)
         adapter.select(address);
      else
      {
         adapter.reset();
         adapter.putByte(RESUME);
      }

      byte[] buffer = byte_buffer;

      buffer [0] = READ_SCRATCHPAD;

      System.arraycopy(FF, 0, buffer, 1, 37);
      adapter.dataBlock(buffer, 0, 38);

      TA1 = buffer [1];
      TA2 = buffer [2];
      ES  = buffer [3];

      int length = 32 - (TA1 & 31);

      if (CRC16.compute(buffer, 0, 6 + length, 0) != 0xB001)
      {
         return -1;
      }

      if (data != null)
         System.arraycopy(buffer, 4, data, start, length);

      return length;
   }

   /**
    * Copies the contents of the scratchpad to the target destination
    * that was specified in a call to write scratchpad or erase scratchpad.
    * You must call read scratchpad before this method to verify the
    * target address registers in the DS1963S.
    *
    * @return True if successful.
    * @exception com.dalsemi.onewire.adapter.OneWireIOException
    * @exception com.dalsemi.onewire.OneWireException
    */
   public synchronized boolean copyScratchPad ()
      throws OneWireIOException, OneWireException
   {
      if (doSpeedEnable && (!resume))
         doSpeed();

      if (!resume)
         adapter.select(address);
      else
      {
         adapter.reset();
         adapter.putByte(RESUME);
      }

      byte[] buffer = byte_buffer;

      buffer [0] = COPY_SCRATCHPAD;
      buffer [1] = TA1;
      buffer [2] = TA2;
      buffer [3] = ES;

      System.arraycopy(FF, 0, buffer, 4, 5);

      //adapter.dataBlock(buffer,0,4);
      adapter.dataBlock(buffer, 0, 9);

      if (buffer [8] == ( byte ) 0x0ff)
         return waitForSuccessfulFinish();
      else
         return true;
   }

   /**
    * Installs a secret on a DS1963S.  The secret is written in partial phrases
    * of 47 bytes (32 bytes to a memory page, 15 bytes to the scratchpad) and
    * is cumulative until the entire secret is processed.
    *
    * @param page The page number used to write the partial secrets to.  In most cases, this
    * should be equal to secret_number or secret_number+8.
    * @param secret A byte array containing the entire secret to be installed.  This
    * method will be faster if this byte array's length is a multiple
    * of 47, however--installing master secrets should be a part of
    * initialization, so a slight speed increase is probably not very
    * important in most cases.
    * @param secret_number The secret 'page' to use.  Secrets are stored across pages 16
    * and 17, in 8 byte chunks (there are 8 secrets). This should be
    * a number between 0 and 8.
    * @return True if successful.
    * @exception com.dalsemi.onewire.adapter.OneWireIOException
    * @exception com.dalsemi.onewire.OneWireException
    */
   public synchronized boolean installMasterSecret (int page, byte[] secret,
           int secret_number)
      throws OneWireIOException, OneWireException
   {

      //47 is a magic number here because every time a partial secret
      //is to be computed, 32 bytes goes in the page and 15 goes in
      //the scratchpad, so it's going to be easier in the computations
      //if i know the input buffer length is divisible by 47
      if (secret.length == 0)
         return false;

      byte[] input_secret      = null;
      byte[] buffer            = byte_buffer;
      int    secret_mod_length = secret.length % 47;

      if (secret_mod_length == 0)   //if the length of the secret is divisible by 47
         input_secret = secret;
      else
      {

         /* i figure in the case where secret is not divisible by 47
            it will be quicker to just create a new array once and
            copy the data in, rather than on every partial secret
            calculation do bounds checking */
         input_secret = new byte [secret.length + (47 - secret_mod_length)];

         System.arraycopy(secret, 0, input_secret, 0, secret.length);
      }

      //make sure the secret number is between 0 and 7
      secret_number = secret_number & 7;

      //the secret page is 16 for secrets 0-3, page 17 for secrets 4-7
      int secret_page = (secret_number > 3) ? 17
                                            : 16;

      //each page has 4 secrets, so look at 2 LS bits
      int    secret_offset = (secret_number & 3) << 3;
      int    offset        = 0;   //the current offset into the input_secret buffer
      byte[] sp_buffer     = new byte [32];

      while (offset < input_secret.length)
      {
         if (!eraseScratchPad(page))
            return false;

         if (!writeScratchPad(page, 0, input_secret, offset, 32))
            return false;   //first write the whole page

         if (readScratchPad(buffer, 0) < 0)
            return false;   //get the address registers

         if (!copyScratchPad())
            return false;   //copy the page into memory

         System.arraycopy(input_secret, offset + 32, sp_buffer, 8, 15);

         if (!writeScratchPad(page, 0, sp_buffer, 0, 32))
            return false;   //now write the scratchpad data

         if (!SHAFunction((offset == 0) ? COMPUTE_FIRST_SECRET
                                        : COMPUTE_NEXT_SECRET))
            return false;   //means a failure

         //here we have to write the scratchpad with 32 bytes of dummy data
         if (!write_read_copy_quick(secret_page, secret_offset))
            return false;

         offset += 47;
      }

      //now lets clean up - erase the scratchpad
      eraseScratchPad(page);
      readScratchPad(buffer, 0);
      copyScratchPad();

      return true;
   }

   //local cahce to make TINI fast
   private byte[] bind_code_temp     = new byte [32];
   private byte[] bind_code_alt_temp = new byte [32];
   private byte[] bind_data_temp     = new byte [32];

   /**
    * Binds an installed secret to a DS1963S byte using
    * well-known binding data and the DS1963S's unique
    * identification number.  This makes the secret unique
    * for this iButton.  Coprocessor iButtons use this function
    * to recreate the iButton's secret to verify authentication.
    *
    * @param page The page number that has the master secret installed.  In this case,
    * the page number does not need to be equivalent to the secret_number
    * modulo 8.  The new secret (installed secret + binding secret) is generated
    * from this page but can be copied into another secret.  User iButtons should
    * bind to the same page the secret was installed on.  Coprocessor iButtons
    * must copy to a new secret to preserve the general system authentication
    * secret.
    * @param bind_data 32 bytes of binding data used to bind the iButton to the system.
    * @param bind_code This byte array can be of two lengths.  A 15-length byte array is unaltered
    * and placed in the scratchpad for the binding.  A 7-length byte array is
    * combined with the page number and DS1963S unique ID and then placed
    * in the scratchpad.  Coprocessors should use a pre-formatted 15-length
    * byte array.  User iButtons should let the method format for them (i.e.
    * use the 7-length byte array option)
    * @param secret_number Secret number to copy the resulting secret to.  See the discussion on page number.
    * @return True if successful.
    * @exception com.dalsemi.onewire.adapter.OneWireIOException
    * @exception com.dalsemi.onewire.OneWireException
    */
   public synchronized boolean bindSecretToiButton (int page,
           byte[] bind_data, byte[] bind_code, int secret_number)
      throws OneWireIOException, OneWireException
   {
      if (bind_data.length != 32)
      {
         System.arraycopy(bind_data, 0, bind_data_temp, 0,
                          (bind_data.length > 32 ? 32
                                                 : bind_data.length));

         bind_data = bind_data_temp;
      }

      if (bind_code.length != 15)
      {
         if (bind_code.length == 7)
         {
            System.arraycopy(bind_code, 0, bind_code_alt_temp, 0, 4);

            bind_code_alt_temp [4] = ( byte ) page;

            System.arraycopy(address, 0, bind_code_alt_temp, 5, 7);
            System.arraycopy(bind_code, 4, bind_code_alt_temp, 12, 3);
         }
         else
         {
            System.arraycopy(bind_code, 0, bind_code_alt_temp, 0,
                             (bind_code.length > 15 ? 15
                                                    : bind_code.length));
         }

         bind_code = bind_code_alt_temp;
      }

      System.arraycopy(bind_code, 0, bind_code_temp, 8, 15);

      bind_code = bind_code_temp;

      if (!writeDataPage(page, bind_data))
         return false;

      resume = true;

      if (!writeScratchPad(page, 0, bind_code, 0, 32))
      {
         resume = false;

         return false;
      }

      if (!SHAFunction(COMPUTE_NEXT_SECRET))
      {
         resume = false;

         return false;   //means a failure
      }

      //go ahead and set resume = false, but write_read_copy_quick doesn't
      //check resume, it automatically assumes it can resume
      resume = false;

      //make sure the secret number is between 0 and 7
      secret_number = secret_number & 7;

      //the secret page is 16 for secrets 0-3, page 17 for secrets 4-7
      int secret_page = (secret_number > 3) ? 17
                                            : 16;

      //each page has 4 secrets, so look at 2 LS bits
      int secret_offset = (secret_number & 3) << 3;

      return (write_read_copy_quick(secret_page, secret_offset));
   }

   //used when copying secrets from the scratchpad to a secret page
   private synchronized boolean write_read_copy_quick (int secret_page,
           int secret_offset)
      throws OneWireIOException, OneWireException
   {

      //don't worry about doSpeed here, this should never be called before something else 
      //that would call doSpeed
      int    addr   = (secret_page << 5) + secret_offset;
      byte[] buffer = byte_buffer;

      //assume we can resume
      buffer [0] = ( byte ) RESUME;
      buffer [1] = ( byte ) WRITE_SCRATCHPAD;
      buffer [2] = ( byte ) addr;          //(secret_page << 5);
      buffer [3] = ( byte ) (addr >> 8);   // secret_offset;

      int length = 32 - secret_offset;

      //write the scratchpad
      adapter.reset();
      System.arraycopy(FF, 0, buffer, 4, length + 2);
      adapter.dataBlock(buffer, 0, length + 6);

      if (CRC16.compute(buffer, 1, length + 5, 0) != 0xB001)
      {
         return false;
      }

      //here we want to read the scratchpad WITHOUT reading the rest of the data
      buffer [1] = ( byte ) READ_SCRATCHPAD;

      System.arraycopy(FF, 0, buffer, 2, 8);
      adapter.reset();
      adapter.dataBlock(buffer, 0, 5);

      //here we just shoot the buffer back out to call copyScratchpad
      buffer [1] = COPY_SCRATCHPAD;

      adapter.reset();

      //adapter.dataBlock(buffer,0,5);
      adapter.dataBlock(buffer, 0, 8);

      if (buffer [7] == ( byte ) 0x0ff)
         return waitForSuccessfulFinish();
      else
         return true;
   }

   /**
    * Writes a data page on the DS1963S.
    *
    * This method is the equivalent of calling:
    *     eraseScratchpad
    *     writeScratchpad
    *     readScratchpad
    *     copyScratchpad
    * with a few optimizations.
    *
    * @param page_number Page number to write.
    * @param page_data Byte array with the page data to write.  This array must
    * be at least 32 bytes long.
    * @return True if successful.
    * @exception com.dalsemi.onewire.adapter.OneWireIOException
    * @exception com.dalsemi.onewire.OneWireException
    */

   //page_data must be 32 bytes long
   public synchronized boolean writeDataPage (int page_number,
                                              byte[] page_data)
      throws OneWireIOException, OneWireException
   {
      if (doSpeedEnable && (!resume))
         doSpeed();

      //first we need to erase the scratchpad
      if (!resume)
         adapter.select(address);
      else
      {
         adapter.reset();
         adapter.putByte(RESUME);
      }

      byte[] buffer = byte_buffer;

      buffer [1] = ERASE_SCRATCHPAD;
      buffer [2] = ( byte ) 0;
      buffer [3] = ( byte ) 0;

      // send block (check copy indication complete)
      System.arraycopy(FF, 0, buffer, 4, 3);
      adapter.dataBlock(buffer, 1, 6);

      if (buffer [6] == ( byte ) 0x0ff)
         if (!waitForSuccessfulFinish())
            return false;

      //then we need to write the scratchpad
      int addr = page_number << 5;

      buffer [0] = ( byte ) RESUME;
      buffer [1] = WRITE_SCRATCHPAD;
      buffer [2] = ( byte ) addr;
      buffer [3] = ( byte ) (addr >> 8);

      System.arraycopy(page_data, 0, buffer, 4, 32);

      buffer [36] = ( byte ) 0xff;
      buffer [37] = ( byte ) 0xff;   //leave space for the CRC

      adapter.reset();

      //adapter.putByte(RESUME);
      adapter.dataBlock(buffer, 0, 38);

      if (CRC16.compute(buffer, 1, 37, 0) != 0xB001)
      {
         return false;
      }

      //then we need to do a 'read' to get out TA1,TA2,E/S
      adapter.reset();

      //buffer[0] = RESUME;
      buffer [1] = READ_SCRATCHPAD;

      System.arraycopy(FF, 0, buffer, 2, 37);
      adapter.dataBlock(buffer, 0, 39);

      /*        TA1 = buffer[1];
              TA2 = buffer[2];
              ES  = buffer[3];
              int length = 32 - (TA1 & 31);*/
      int length = 32 - (buffer [2] & 31);

      if (CRC16.compute(buffer, 1, 6 + length, 0) != 0xB001)
      {
         return false;
      }

      //now we can copy the scratchpad
      adapter.reset();

      //buffer[0] still has the resume command in it
      buffer [1] = COPY_SCRATCHPAD;

      //TA1,TA2,ES area already in buffer
      System.arraycopy(FF, 0, buffer, 5, 3);
      adapter.dataBlock(buffer, 0, 8);

      if (buffer [7] == ( byte ) 0x0ff)
         return waitForSuccessfulFinish();

      return true;
   }

   /**
    * Performs one of the DS1963S's SHA functions (see the datasheet for
    * more on these functions).
    *
    * @param function The function code to use (i.e. COMPUTE_NEXT_SECRET)
    * @return True if the function successfully completed.
    * @exception com.dalsemi.onewire.adapter.OneWireIOException
    * @exception com.dalsemi.onewire.OneWireException
    */

   //this is not called by anyone in the critical path
   public boolean SHAFunction (byte function)
      throws OneWireIOException, OneWireException
   {
      return SHAFunction(function, (TA1 & 0x0ff) | (TA2 << 8));
   }

   /**
    * Performs one of the DS1963S's SHA functions (see the datasheet for
    * more on these functions).
    *
    * @param function The function code to use (i.e. COMPUTE_NEXT_SECRET)
    * @param T The address to use (page number).
    * @return True if the function successfully completed.
    * @exception com.dalsemi.onewire.adapter.OneWireIOException
    * @exception com.dalsemi.onewire.OneWireException
    */
   public synchronized boolean SHAFunction (byte function, int T)
      throws OneWireIOException, OneWireException
   {
      if (doSpeedEnable && (!resume))
         doSpeed();

      if (!resume)
         adapter.select(address);
      else
      {
         adapter.reset();
         adapter.putByte(RESUME);
      }

      byte[] buffer = byte_buffer;

      buffer [0] = COMPUTE_SHA;
      buffer [1] = ( byte ) T;
      buffer [2] = ( byte ) (T >> 8);
      buffer [3] = function;

      System.arraycopy(FF, 0, buffer, 4, 5);
      adapter.dataBlock(buffer, 0, 9);

      if (CRC16.compute(buffer, 0, 6, 0) != 0xB001)
      {
         return false;
      }

      if (buffer [8] == ( byte ) 0x0ff)
         return waitForSuccessfulFinish();

      return true;
   }
}
