
/*---------------------------------------------------------------------------
 * 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.
 *---------------------------------------------------------------------------
 */

// JibComm.java
package com.dalsemi.onewire.container;

import java.io.*;
import com.dalsemi.onewire.*;
import com.dalsemi.onewire.adapter.*;
import com.dalsemi.onewire.utils.*;


/** JibComm
 *
 *  @version    0.00, 28 Aug 2000
 *  @author     JK
 *
 */
class JibComm
{

   //////////////////////////////////////////////////////////
   //  Masks for checking various bits in registers        //
   //////////////////////////////////////////////////////////

   /** accelerator status mask. */
   private static final byte COPROCESSOR_ACCELERATOR_RUNNING = ( byte ) 0x01;

   /** Power On Reset (POR) mask. */
   private static final byte POWER_ON_RESET = ( byte ) 0x40;

   /** command status mask.*/
   private static final byte COMMAND_COMPLETE = ( byte ) 0x0B;

   /** command status mask.*/
   private static final byte COMMAND_NOT_COMPLETE = ( byte ) 0x20;

   /** first birthday condition mask. */
   private static final byte FIRST_BIRTHDAY = ( byte ) 0x1D;

   /** Master Erase problems mask. */
   private static final byte MASTER_ERASE_PROBLEM = ( byte ) 0x1F;

   //////////////////////////////////////////////////////////
   //  Device Specific Commands for Java iButton.          //
   //////////////////////////////////////////////////////////

   /** Write IPR commmand. */
   private static final byte WRITE_IPR_COMMAND = ( byte ) 0x0F;

   /** Read IPR Command. */
   private static final byte READ_IPR_COMMAND = ( byte ) 0xAA;

   /** Write I/O buffer command. */
   private static final byte WRITE_IO_BUFFER_COMMAND = ( byte ) 0x2D;

   /** Read I/O buffer command. */
   private static final byte READ_IO_BUFFER_COMMAND = ( byte ) 0x22;

   /** Interrupt Micro command. */
   private static final byte INTERRUPT_MICRO_COMMAND = ( byte ) 0x77;

   /** Run Micro command. */
   private static final byte RUN_MICRO_COMMAND = ( byte ) 0x87;

   /** Reset Micro command. */
   private static final byte RESET_MICRO_COMMAND = ( byte ) 0xDD;

   /** Read Status command. */
   private static final byte READ_STATUS_COMMAND = ( byte ) 0xE1;

   /** Write Status command. */
   private static final byte WRITE_STATUS_COMMAND = ( byte ) 0xD2;

   /** header size of data block */
   private static final int HEADER_SIZE = 8;

   /** maximum data block size to send or receive. */

   // there are 8 bytes of overhead in each block of data send
   private final int MAX_BLOCK_SIZE = 128 - 8;

   /** minimum run time for the Java iButton in milliseconds.
   This number has been increased from the value specified in the
   data sheet.  This longer time to sleep attempts to keep the host
   from interrupting the Java iButton. */
   private static final int MIN_RUNTIME_IN_MILLIS = 96;

   /** maximum run time for the Java iButton in milliseconds. */
   private static final int MAX_RUNTIME_IN_MILLIS = 3813;

   /** minimum run time value for the status register (OWUS) */
   private static final int MIN_RUNTIME = 0;

   /** maximum run time value for the status register (OWUS) */
   private static final int MAX_RUNTIME = 15;

   /** sending bytes to Java iButton.*/
   private static final int SEND = 0x01;

   /** receiving bytes from Java iButton.*/
   private static final int RECEIVE = 0x02;

   /** adapter used to communicate with the Java iButton. */
   private DSPortAdapter adapter;

   /** byte array containing iButtonAddress(ID). */
   private byte[] address = new byte [8];

   /** debug mode. */
   private boolean doDebug = false;

   /** Power On Reset (POR) correction status.
   The POR will always be corrected at the begining of a
   transfer.
   */
   private boolean shouldCorrectPOR = false;

   /** 0xFF arrays for quick array initialization  */
   private byte[] ffBlock;

   //-------------------------------------------------------------------------

   /** Constructs a JibComm object used to communicate with Java iButton.
   *
   * @param newAdapter adapter used to communicate with the Java iButton.
   *
   * @param newAddress address of the Java iButton.
   *
   * @throw IllegalArgumentException
   */
   public JibComm (DSPortAdapter newAdapter, byte[] newAddress)
      throws IllegalArgumentException
   {

      // Check to see if the length of the address is correct.
      if (newAddress.length != 8)
         throw new IllegalArgumentException(
            "iButton Address must be of length 8.");

      System.arraycopy(newAddress, 0, address, 0, address.length);

      adapter = newAdapter;

      // create an array of 0xFF for quick array fill
      ffBlock = new byte [130];

      for (int i = 0; i < 130; i++)
         ffBlock [i] = ( byte ) 0xFF;
   }

   /**
    * Transfer data to and from the Java iButton.
    *
    * @param data    data to be written to the Java iButton.
    *
    * @param runTime a 4 bit value (0 -15) that represents
    *                the expected run time of the device.
    *
    * @return data read back from the Java iButton.
    *
    * @throws OneWireException
    * @throws OneWireIOException
    * @throws IllegalArgumentException
    *
    */
   public byte[] transferJibData (byte[] data, int runTime)
      throws OneWireException, OneWireIOException, IllegalArgumentException
   {
      BlockDataFragmenter frag = new BlockDataFragmenter(data);

      // Set correction of the POR to true to clear the first POR.
      shouldCorrectPOR = true;

      if (adapter.getSpeed() != adapter.SPEED_OVERDRIVE)
         throw new OneWireIOException("Adapter not in overdrive mode.");

      // Send all data and headers to the iButton.
      while (true)
      {
         checkStatus(MIN_RUNTIME, SEND);
         setHeader(frag.getNextHeaderToSend());   // Send Header
         setData(frag.getNextDataToSend());       // Send Data

         //      if (doDebug) System.out.println("Data has been written.");
         // NOTE:
         // The 'more' Variable in the BlockDataFragmenter is updated in
         // the getNextHeaderToSend function.
         // If this is the final block of data, then the full requested
         // runtime needs to be sent to the Java iButton instead of the
         // minimum one.
         if (frag.hasMore())
         {
            setStatus(MIN_RUNTIME);
            interrupt(MIN_RUNTIME);
         }
         else
         {

            // all data sent
            setStatus(runTime);
            interrupt(runTime);

            break;
         }
      }                                           // End of Data loading loop.

      byte[] header = new byte [HEADER_SIZE];
      byte[] status;

      // retrieve the data.
      do
      {

         // wait for iButton reply      
         status = checkStatus(runTime, RECEIVE);
         header = getHeader();                   // retrieve header
         data   = getData(( int ) header [1]);   // retrieve data

         //      if (doDebug) System.out.println("Data read back successfully.");
         // Check data CRC and build the return data array.
         frag.checkBlock(header, data);

         // for Java iButton, just checking the COMMAND_NOT_COMPLETE flag is
         // not sufficient as it may send data back in multiple blocks.
         // Block number 0x80 does not necessary means the last block.
         if (status [2] == COMMAND_COMPLETE)
            break;                               // last block received

         setStatus(MIN_RUNTIME);
         run(MIN_RUNTIME);
      }
      while (true);

      // Retrieve the data array from the BlockDataFragmenter.
      return frag.getDataFromRead();
   }

   /**
    * Correct the device from a Power-On-Reset(POR) error.
    *
    */
   public void correctPOR ()
      throws OneWireException, OneWireIOException
   {

      //     if (doDebug) System.out.println("Attempting to correct the POR!");
      reset();
      setStatus(MIN_RUNTIME);
      run(MIN_RUNTIME);
   }

   /**
    * Get the status from the Java iButton.
    *
    * @return status byte array read from the Java iButton.
    * The contents of the status array is as follows:
    *
    * <pre>
    *      Byte 0 - number of free bytes in the Input Buffer.
    *      Byte 1 - number of used bytes in the Output Buffer.
    *      Byte 2 - contents of the OWMS Register.
    *      Byte 3 - contents of the CPST register.
    *</pre>
    *
    * @throws OneWireException
    * @throws OneWireIOException
    *
    */
   public byte[] getStatus ()
      throws OneWireException, OneWireIOException
   {

      // Set up the command block
      byte[] commandBuffer = new byte [7];

      commandBuffer [0] = READ_STATUS_COMMAND;

      System.arraycopy(ffBlock, 0, commandBuffer, 1, 6);

      if (adapter.select(address))
      {
         adapter.dataBlock(commandBuffer, 0, 7);

         // Fix Andreas Bug in read Status with cap on One-Wire.
         // Mask off the last bit of the Free bytes
         commandBuffer [1] &= ( byte ) 0xFE;

         if (CRC16.compute(commandBuffer, 0, 7, 0) != 0xB001)
            throw new OneWireIOException(
               "Bad CRC on data returned in Read Status method.");

         // retrieve status
         byte[] status = new byte [4];

         System.arraycopy(commandBuffer, 1, status, 0, 4);

         return status;
      }
      else
         throw new OneWireException(
            "JibComm Error - Device not found on One-Wire Bus.");
   }

   /**
    * Check status of the Java iButton.
    *
    * @param runTime a 4 bit value (0 -15) that represents
    *                the expected run time of the device.
    * @param dir
    *
    * @param send true if sending data, false otherwise
    *
    * @throw OneWireIOException
    * @throw OneWireException
    */
   public byte[] checkStatus (int runTime, int dir)
      throws OneWireIOException, OneWireException
   {
      int    loopCount = 0;
      byte[] status;

      // loop until the conditions are satisfied which implies that the
      // coprocesser is not running, and the previous command has finished.
      while (true)
      {
         loopCount++;

         // Keeps from having a infinate loop.
         if (loopCount > 200)    // Value can be ajusted.
            throw new OneWireException("Unrecoverable error.  Fail.");

         status = getStatus();   // get current status

         // First error checking POR bit.
         if ((status [2] & POWER_ON_RESET) != 0)
         {
            if (shouldCorrectPOR)
               correctPOR();
            else
               throw new OneWireException("POR of Device is set.");

            shouldCorrectPOR = false;
         }
         else
         {

            // check CPST bit for Co-procsesser status.
            if ((status [3] & COPROCESSOR_ACCELERATOR_RUNNING) != 0)
            {

               //           if (doDebug) System.out.println("Coprocessor still running.");
               // give it time to finish.
               run(runTime + 6);
            }
            else if (((status [2] & MASTER_ERASE_PROBLEM) == FIRST_BIRTHDAY)
                     || ((status [2] & MASTER_ERASE_PROBLEM)
                         == MASTER_ERASE_PROBLEM))
            {

               //              if (doDebug) System.out.println("FirstBirth identified.");  
               setStatus(12);    // Optimum time for Master Erase.
               run(12 + 1);
            }
            else if ((status [2] & COMMAND_NOT_COMPLETE) != 0)
            {
               if (runTime == MIN_RUNTIME)
                  runTime = 2;
               else if (runTime < 8)
                  runTime *= 2;

               setStatus(runTime);
               run(runTime + 1);
            }
            else
            {

               // Check to see if send or receive called, procceed acordingly.
               if (dir == SEND)
               {

                  // Check the number of free bytes in the input buffer
                  if (status [0] < HEADER_SIZE)
                     throw new OneWireIOException(
                        "JibComm Error - No room in header input buffer.");
                  else
                     break;      // If all is good then break out of loop.
               }
               else
               {

                  // Check number of free bytes in output buffer.
                  if (status [1] == 0)
                     throw new OneWireIOException(
                        "JibComm Error - No header in output buffer.");
                  else if (status [1] != HEADER_SIZE)
                     throw new OneWireIOException(
                        "JibComm Error - Bad header in output buffer.");
                  else
                     break;      // If all is good then break out of loop.
               }
            }
         }                       // if(POR)
      }                          // while(true)

      return status;
   }   // checkStatus

   /**
    * Set status register of the Java iButton.
    *
    * @param runTime a 4 bit value (0 -15) that represents
    *                the expected run time of the device.
    *
    * @throws OneWireException
    * @throws OneWireIOException
    *
    */
   public void setStatus (int runTime)
      throws OneWireException, OneWireIOException
   {
      if (runTime > MAX_RUNTIME)
         runTime = MAX_RUNTIME;

      // Set up the command block
      byte[] commandBuffer = new byte [4];

      commandBuffer [0] = WRITE_STATUS_COMMAND;
      commandBuffer [1] = ( byte ) (runTime & 0x0F);
      commandBuffer [2] = ( byte ) 0xFF;   // Fill with 0xFF's to read the CRC.
      commandBuffer [3] = ( byte ) 0xFF;

      // Set up the release code block
      byte[] releaseBuffer = new byte [2];

      releaseBuffer [0] = ( byte ) 0x7F;   // Release byte required by the iButton.
      releaseBuffer [1] = ( byte ) 0x51;   // Rest of the Release byte.

      sendCommand(commandBuffer, releaseBuffer, false, 0);

      return;
   }

   /**
    * Set the header in the I/O buffer.
    *
    * @param header byte array with the header information.
    *
    * @throws OneWireException
    * @throws OneWireIOException
    * @throws IllegalArgumentException
    *
    */
   public void setHeader (byte[] header)
      throws OneWireException, OneWireIOException, IllegalArgumentException
   {
      if (header.length != HEADER_SIZE)
         throw new IllegalArgumentException("The header must be of length "
                                            + HEADER_SIZE);

      // Set up the command block
      // set to command length + CRC + HEADER_SIZE.
      byte[] commandBuffer = new byte [4 + HEADER_SIZE];

      commandBuffer [0] = WRITE_IO_BUFFER_COMMAND;
      commandBuffer [1] = ( byte ) HEADER_SIZE;

      System.arraycopy(header, 0, commandBuffer, 2, HEADER_SIZE);

      commandBuffer [10] = ( byte ) 0xFF;   // Fill with 0xFF's to read the CRC.
      commandBuffer [11] = ( byte ) 0xFF;

      // Set up the release code block
      byte[] releaseBuffer = new byte [2];

      releaseBuffer [0] = ( byte ) 0xB3;   // Release byte required by the iButton.
      releaseBuffer [1] = ( byte ) 0x9D;   // Rest of the Release byte.

      sendCommand(commandBuffer, releaseBuffer, false, 0);

      return;
   }

   /**
    * Gets header the I/O Buffer.
    *
    * @return header read from the Java iButton.
    *
    * @throws OneWireException
    * @throws OneWireIOException
    *
    */
   public byte[] getHeader ()
      throws OneWireException, OneWireIOException
   {

      // Set up the command block
      byte[] commandBuffer = new byte [12];

      commandBuffer [0] = READ_IO_BUFFER_COMMAND;
      commandBuffer [1] = ( byte ) HEADER_SIZE;

      System.arraycopy(ffBlock, 0, commandBuffer, 2, 10);

      // Set up the release code block
      byte[] releaseBuffer = new byte [2];

      releaseBuffer [0] = ( byte ) 0x4C;   // Release byte required by the iButton.
      releaseBuffer [1] = ( byte ) 0x62;   // Rest of the Release byte.

      sendCommand(commandBuffer, releaseBuffer, false, 0);

      byte[] header = new byte [HEADER_SIZE];

      System.arraycopy(commandBuffer, 2, header, 0, HEADER_SIZE);

      return header;
   }

   /**
    * Sets data to be written to the IPR
    *
    * @param data data to be written to the Java iButton.
    *
    * @throws OneWireException
    * @throws OneWireIOException
    *
    */
   public void setData (byte[] data)
      throws OneWireException, OneWireIOException
   {

      // Set up the command block
      byte[] commandBuffer = new byte [4 + data.length];

      commandBuffer [0] = WRITE_IPR_COMMAND;
      commandBuffer [1] = ( byte ) data.length;

      System.arraycopy(data, 0, commandBuffer, 2, data.length);

      commandBuffer [data.length + 2] = ( byte ) 0xFF;   // Fill with 0xFF's to read the CRC.
      commandBuffer [data.length + 3] = ( byte ) 0xFF;

      sendCommand(commandBuffer, null, false, 0);

      return;
   }

   /**
    * Gets data from the Java iButton.
    *
    * @param length expected number of bytes of data to be read from the IPR.
    *
    * @return data from the Java iButton.
    *
    * @throws OneWireException
    * @throws OneWireIOException
    *
    */
   public byte[] getData (int length)
      throws OneWireException, OneWireIOException
   {

      // Set up the command block
      byte[] commandBuffer = new byte [length + 4];

      commandBuffer [0] = READ_IPR_COMMAND;
      commandBuffer [1] = ( byte ) (length & 0xFF);

      // Fill the rest with 0xFF.
      System.arraycopy(ffBlock, 0, commandBuffer, 2, length + 2);
      sendCommand(commandBuffer, null, false, 0);

      byte[] data = new byte [length];

      System.arraycopy(commandBuffer, 2, data, 0, length);

      return data;
   }

   /**
    * Runs the Micro in the Java iButton.
    *
    * @param runTime a 4 bit value (0 -15) that represents
    *                the expected run time of the device.
    *
    * @throws OneWireException
    * @throws OneWireIOException
    *
    */
   public void run (int runTime)
      throws OneWireException, OneWireIOException
   {
      long sleepTime = (runTime * 250) + MIN_RUNTIME_IN_MILLIS + 25;

      // Set up the release code block
      byte[] releaseBuffer = new byte [3];

      releaseBuffer [0] = RUN_MICRO_COMMAND;
      releaseBuffer [1] = ( byte ) 0x73;   // Release code for runing micro.
      releaseBuffer [2] = ( byte ) 0x5D;

      sendCommand(null, releaseBuffer, true, sleepTime);

      return;
   }

   /**
    * Interrupts the Micro in the Java iButton.
    *
    * @param runTime a 4 bit value (0 -15) that represents
    *                the expected run time of the device.
    *
    * @throws OneWireException
    * @throws OneWireIOException
    *
    */
   public void interrupt (int runTime)
      throws OneWireException, OneWireIOException
   {
      long sleepTime = (runTime * 250) + MIN_RUNTIME_IN_MILLIS + 25;

      // Set up the release code block
      byte[] releaseBuffer = new byte [3];

      releaseBuffer [0] = INTERRUPT_MICRO_COMMAND;
      releaseBuffer [1] = ( byte ) 0x43;   // Release code for intterupt function.
      releaseBuffer [2] = ( byte ) 0x6D;   // Release code.

      sendCommand(null, releaseBuffer, true, sleepTime);

      return;
   }

   /**
    * Reset the Micro in the Java iButton.
    *
    * @throws OneWireException
    * @throws OneWireIOException
    *
    */
   public void reset ()
      throws OneWireException, OneWireIOException
   {

      // Set up the release code block
      byte[] releaseBuffer = new byte [3];

      releaseBuffer [0] = RESET_MICRO_COMMAND;
      releaseBuffer [1] = ( byte ) 0xBC;   // Release code for intterupt function.
      releaseBuffer [2] = ( byte ) 0x92;   // Rest of the Release byte.

      sendCommand(null, releaseBuffer, false, 0);

      return;
   }

   /**
    * Sends command to Java iButton.
    *
    * @param commandBuffer byte array containing the command
    *
    * @param releaseBuffer byte array containing the release code
    *
    * @param powerMode true if power supply is to be toggled
    *
    * @param sleepTime sleep time for the program while the Micro runs.
    *                  Applicable only if powerMode is true.
    *
    * @throws OneWireException
    * @throws OneWireIOException
    *
    */
   public void sendCommand (byte[] commandBuffer, byte[] releaseBuffer,
                            boolean powerMode, long sleepTime)
      throws OneWireException, OneWireIOException
   {

      // Select the Java iButton and send the command.
      if (adapter.select(address))
      {
         if (commandBuffer != null)        // send command
            adapter.dataBlock(commandBuffer, 0, commandBuffer.length);

         if (releaseBuffer != null)
         {                                 // send release code
            adapter.dataBlock(releaseBuffer, 0, releaseBuffer.length);

            if (powerMode)
            {
               adapter.setPowerDuration(5);   // Pull line up for Power Delivery

               // Deliver after checking bit.
               adapter.startPowerDelivery(adapter.CONDITION_AFTER_BIT);

               if (adapter.getBit())
                  throw new OneWireIOException(
                     "Command not understood by Java iButton.");

               try
               {

                  // Wait for power delivery to complete the conversion
                  Thread.sleep(sleepTime);
               }
               catch (InterruptedException e){}
               ;

               adapter.setPowerNormal();   // Turn power off.
            }
            else
            {

               // Check final byte of buffer to see if the command was understood.
               if (adapter.getBit())
                  throw new OneWireIOException(
                     "Command not understood by Java iButton.");
            }
         }                                 // if releaseBuffer
      }
      else
         throw new OneWireException(
            "JibComm Error - Device not found on One Wire Bus.");

      return;
   }

   //------------------------------------------------------------------------------
   //------------- Inner class Block Data Fragmenter
   //------------------------------------------------------------------------------
   //----------------------------------------------------------------------------

   /**
    *  This class is used to handle all calculations and interpretations of
    *  block data fragmentation for the Java iButton.  It is Synchronized so
    *  that it can only be accessed by one thread at a time.  Otherwise errors
    *  could develop because of the need to hold the data over several accesses.
    *
    *  @version    0.00, 10 July 2000
    *  @author     JK
    */
   class BlockDataFragmenter
   {

      /**(128 arrays of size 128) */
      private final long MAX_DATA_LENGTH = 128 * 128;

      /** last block mask. */
      private final int FINAL_BLOCK = 0x80;

      /** data to be fragmented into blocks. */
      private byte[] dataBuffer;

      /** length of data to be fragmented. */
      private int dataBufferLength;

      /** data read back from the Java iButton. */
      private ByteArrayOutputStream dataRead = new ByteArrayOutputStream();

      /** true if there are more blocks to send. */
      private boolean more = true;

      /** header of the current block. */
      private byte[] currentHeader = new byte [HEADER_SIZE];

      /** data of the current block. */
      private byte[] currentDataBlock;

      /** length of the current block. */
      private int currentBlockLength;

      /** current block number. */
      private int currentBlockNumber = 0;

      /** number of bytes already sent to the Java iButton. */
      private int byteSent = 0;

      /** check sum on data and header sent. */
      private long checkSum = 0;

      /**
       * Constructs a block data fragmenter.
       *
       * @param data data byte array to be fragmented.
       */
      public BlockDataFragmenter (byte[] data)
      {
         dataBufferLength = data.length;

         if (dataBufferLength == 0)
            throw new IllegalArgumentException("Data array cannot be empty.");
         else if (dataBufferLength > MAX_DATA_LENGTH)
            throw new IllegalArgumentException(
               "Data array size cannot exceed " + MAX_DATA_LENGTH);

         // Set up internal Data Holder array with passed data array.
         dataBuffer = new byte [dataBufferLength];

         System.arraycopy(data, 0, dataBuffer, 0, dataBufferLength);

         return;
      }

      /**
       * Check if there are more blocks to send.
       *
       * @return true if there are more blocks, false otherwise.
       */
      public boolean hasMore ()
      {
         return more;
      }

      /**
       * Gets the next header to send.
       *
       * @return header for the next transfer
       */
      public byte[] getNextHeaderToSend ()
      {

         // copy instance variables to local variables
         int  bSent            = byteSent;
         int  blkLen           = currentBlockLength;
         int  blkNum           = currentBlockNumber;
         int  dataLen          = dataBufferLength;
         long chkSum           = checkSum;
         int  remainingDataLen = dataLen - bSent;

         // Determine length of current block of data.
         blkLen = (remainingDataLen > MAX_BLOCK_SIZE) ? MAX_BLOCK_SIZE
                                                      : remainingDataLen;

         if (remainingDataLen == blkLen)
         {
            more   = false;
            blkNum |= FINAL_BLOCK;   // set most significant bit to one.
         }

         // Initalize to the correct size and copy over the needed data
         byte[] data = new byte [blkLen];

         System.arraycopy(dataBuffer, bSent, data, 0, blkLen);

         // compute CRC for data block
         int crc = CRC16.compute(blkLen, 0);

         crc = CRC16.compute(data, 0, blkLen, crc);

         byte[] header = currentHeader;

         // Build the header.
         header [0] = ( byte ) blkNum;
         header [1] = ( byte ) blkLen;
         header [2] = ( byte ) (remainingDataLen & 0xFF);   // low byte
         header [3] = ( byte ) ((remainingDataLen >> 8) & 0xFF);   // high byte
         header [4] = ( byte ) (crc & 0xFF);                // low byte of crc
         header [5] = ( byte ) ((crc >> 8) & 0xFF);         // high byte of crc

         // cycle through the header adding up for the check sum.
         // This step adds the block number, block length, remaing length bytes,
         // and the CRC bytes.
         chkSum += ((( int ) header [0]) & 0xFF);
         chkSum += ((( int ) header [1]) & 0xFF);
         chkSum += ((( int ) header [2]) & 0xFF);
         chkSum += ((( int ) header [3]) & 0xFF);
         chkSum += ((( int ) header [4]) & 0xFF);
         chkSum += ((( int ) header [5]) & 0xFF);

         // add in the data values.
         for (int i = 0; i < blkLen; i++)
            chkSum += ((( int ) data [i]) & 0xFF);

         // place check sum into header and add the check sum bytes to itself
         header [6] = ( byte ) (chkSum & 0xFF);
         header [7] = ( byte ) ((chkSum >> 8) & 0xFF);
         chkSum     += header [6];
         chkSum     += header [7];

         blkNum++;   // update block number

         bSent += blkLen;   // update number of bytes sent

         // copy local variables to instance variables
         byteSent           = bSent;
         currentDataBlock   = data;
         currentBlockLength = blkLen;
         currentBlockNumber = blkNum;
         checkSum           = chkSum;

         return header;
      }

      /**
       * Gets the data to be sent.
       *
       * @return data that will be sent to the Java iButton.
       */
      public byte[] getNextDataToSend ()
      {
         return currentDataBlock;
      }

      /**
       * Checks if the header and data are correct.
       *
       * @param header header from the Java iButton.
       *
       * @param data   data from the Java iButton.
       *
       * @throws OneWireIOException
       */
      public void checkBlock (byte[] header, byte[] data)
         throws OneWireIOException
      {

         // compute CRC16 of the data passed.
         int crc = CRC16.compute(header [1], 0);

         crc = CRC16.compute(data, 0, data.length, crc);

         // compare this CRC16 value to the value returned in the header.
         int crcFromHeader = ((( int ) header [5]) & 0xFF);

         crcFromHeader <<= 8;
         crcFromHeader |= ((( int ) header [4]) & 0xFF);

         if (crc != crcFromHeader)
            throw new OneWireIOException(
               "CRC passed in header does not match CRC computed from passed data.");

         // put data into the byte array output steam for keeping, then return.
         // Here it is nessessary to dump the three trash bytes that are left over
         // from the old CIB API that are not used with the Java iButton.
         dataRead.write(data, 3, data.length - 3);

         return;
      }

      /**
       * Gets the data read from the Java iButton.
       *
       * @return data read from the Java iButton.
       */
      public byte[] getDataFromRead ()
      {
         return dataRead.toByteArray();
      }
   }   // BlockDataFragmenter
}
