/// <summary>
        /// <P>Performs the unsigned debit, subtracting the debit amount from
        /// the user's balance and storing the new, unsigned account data on the
        /// user's iButton.  The debit amount can be set using
        /// <code>transaction.setParameter(SHADebit.DEBIT_AMOUNT, 50)</code>,
        /// where the value is in units of cents (i.e. for 1 dollar, use 100).</P>
        ///
        /// <P><B>Flow of action: </B>
        ///   <ul>
        ///     <li> Read the account data from user </li>
        ///     <li> Extract account balance </li>
        ///     <li> Assert balance greater than debit amount </li>
        ///     <li> Debit money from balance </li>
        ///     <li> Insert the new balance </li>
        ///     <li> Write the account data to the user </li>
        ///   </ul></P>
        ///
        /// <P>If previous steps have been executed, all "Read" commands on
        /// the user are reading from cached data.</P>
        /// </summary>
        /// <param name="user"> SHAiButtonUser upon which the transaction occurs.
        /// </param>
        /// <returns> <code>true</code> if and only if the user has enough in the
        /// account balance to perform the requested debit AND the account data
        /// has been written to the button.
        /// </returns>
        /// <seealso cref= SHAiButtonUser#readAccountData(byte[],int) </seealso>
        /// <seealso cref= SHAiButtonUser#writeAccountData(byte[],int) </seealso>
        /// <seealso cref= #getLastError() </seealso>
        public override bool executeTransaction(SHAiButtonUser user, bool verifySuccess)
        {
            lock (this)
            {
                //clear any error
                this.lastError = NO_ERROR;

                //init local vars
                //holds the working copy of account data
                byte[] accountData = this.executeTransaction_accountData;
                //holds the backup copy of account data before writing
                byte[] oldAcctData = this.executeTransaction_oldAcctData;
                //holds the account data read back for checking
                byte[] newAcctData = this.executeTransaction_newAcctData;
                //just make the transaction ID a random number, so it changes
                int transID = rand.Next();

                //if verifyUser was called, this is a read of cached data
                user.readAccountData(accountData, 0);

                //before we update the account data array at all, let's make a backup copy
                Array.Copy(accountData, 0, oldAcctData, 0, 32);

                //get the user's balance from accountData
                int balance = -1;
                if (accountData[I_FILE_LENGTH] == RECORD_A_LENGTH)
                {
                    balance = Convert.toInt(accountData, I_BALANCE_A, 3);
                }
                else if (accountData[I_FILE_LENGTH] == RECORD_B_LENGTH)
                {
                    balance = Convert.toInt(accountData, I_BALANCE_B, 3);
                }

                //if there are insufficient funds
                if (this.debitAmount > balance)
                {
                    this.lastError = USER_BAD_ACCOUNT_DATA;
                    return(false);
                }

                //update the user's balance
                this.userBalance = (balance - this.debitAmount);

                // attempt to update the page
                bool success = false;
                try
                {
                    success = writeTransactionData(user, transID, this.userBalance, accountData);
                }
                catch (System.Exception)
                { // sink
                }

                //if write didn't succeeded or if we need to perform
                //a verification step anyways, let's double-check what
                //the user has on the button.
                if (verifySuccess || !success)
                {
                    OneWireEventSource.Log.Debug("attempting to re-write transaction data: ");
                    OneWireEventSource.Log.Debug("cur Data: " + Convert.toHexString(accountData));
                    OneWireEventSource.Log.Debug("old data: " + Convert.toHexString(oldAcctData));

                    bool dataOK = false;
                    int  cnt    = MAX_RETRY_CNT;
                    do
                    {
                        try
                        {
                            // let's refresh the page
                            user.refreshDevice();
                            //calling verify user re-issues a challenge-response
                            //and reloads the cached account data in the user object.
                            if (verifyUser(user))
                            {
                                //compare the user's account data against the working
                                //copy and the backup copy.
                                if (user.readAccountData(newAcctData, 0))
                                {
                                    OneWireEventSource.Log.Debug("new data: " + Convert.toHexString(newAcctData));
                                    bool isOld = true;
                                    bool isCur = true;
                                    for (int i = 0; i < 32 && (isOld || isCur); i++)
                                    {
                                        //match the backup
                                        isOld = isOld && (newAcctData[i] == oldAcctData[i]);
                                        //match the working copy
                                        isCur = isCur && (newAcctData[i] == accountData[i]);
                                    }
                                    if (isOld)
                                    {
                                        //if it matches the backup copy, we didn't write anything
                                        //and the data is still okay, but we didn't do a debit
                                        dataOK  = true;
                                        success = false;
                                    }
                                    else if (isCur)
                                    {
                                        dataOK  = true;
                                        success = true;
                                    }
                                    else
                                    {
                                        int cnt2 = MAX_RETRY_CNT;
                                        do
                                        {
                                            //iBUTTON DATA IS TOTALLY HOSED
                                            //keep trying to get account data on the button
                                            try
                                            {
                                                success = writeTransactionData(user, transID, this.userBalance, accountData);
                                            }
                                            catch (OneWireIOException owioe)
                                            {
                                                if (cnt2 == 0)
                                                {
                                                    throw owioe;
                                                }
                                            }
                                            catch (OneWireException owe)
                                            {
                                                if (cnt2 == 0)
                                                {
                                                    throw owe;
                                                }
                                            }
                                        } while (((cnt2 -= 1) > 0) && !success);
                                    }
                                }
                            }
                        }
                        catch (OneWireIOException owioe)
                        {
                            if (cnt == 0)
                            {
                                throw owioe;
                            }
                        }
                        catch (OneWireException owe)
                        {
                            if (cnt == 0)
                            {
                                throw owe;
                            }
                        }
                    } while (!dataOK && ((cnt -= 1) > 0)); //TODO

                    if (!dataOK)
                    {
                        //couldn't fix the data after 255 retries
                        IOHelper.writeLine("Catastrophic Failure!");
                        success = false;
                    }
                }

                return(success);
            }
        }
        /// <summary>
        /// <P>Verifies user's account data.  Account data is "verified" if and
        /// only if the account balance is greater than zero.  No digital
        /// signature is checked by this transaction.</P>
        ///
        /// <P><B>Flow of action: </B>
        ///   <ul>
        ///     <li> Read the account data from user </li>
        ///     <li> Extract account balance </li>
        ///     <li> Debit money from balance </li>
        ///     <li> Insert the new balance </li>
        ///     <li> Write the account data to the user </li>
        ///   </ul></P>
        ///
        /// <P>If previous steps have been executed, all "Read" commands on
        /// the user are reading from cached data.</P>
        /// </summary>
        /// <param name="user"> SHAiButtonUser upon which the transaction occurs.
        /// </param>
        /// <returns> <code>true</code> if and only if the account balance is
        /// greater than zero.
        /// </returns>
        /// <seealso cref= SHAiButtonUser#readAccountData(byte[],int) </seealso>
        /// <seealso cref= #getLastError() </seealso>
        public override bool verifyTransactionData(SHAiButtonUser user)
        {
            lock (this)
            {
                //clear any error
                this.lastError = NO_ERROR;

                byte[] accountData = this.verifyData_accountData;

                //if verifyUser was called, this is a read of cached data
                user.readAccountData(accountData, 0);

                // verify the A-B data scheme is valid
                bool validPtr = false, validA = false, validB = false;

                byte fileLength = accountData[I_FILE_LENGTH];
                int  crc16      = CRC16.compute(accountData, 0, fileLength + 3, user.AccountPageNumber);

                if (fileLength == RECORD_A_LENGTH || fileLength == RECORD_B_LENGTH)
                {
                    validPtr = true;
                }

                // was the crc of the file correct?
                if (crc16 == 0xB001)
                {
                    // if the header points to a valid record, we're done
                    if (validPtr)
                    {
                        // nothing more to check for DS1961S/DS2432, since
                        // it carries no signed data, we're finished
                        return(true);
                    }
                    // header points to neither record A nor B, but
                    // crc is absolutely correct.  that can only mean
                    // we're looking at something that is the wrong
                    // size from what was expected, but apparently is
                    // exactly what was meant to be written.  I'm done.
                    this.lastError = USER_BAD_ACCOUNT_DATA;
                    return(false);
                }

                // restore the other header information
                accountData[I_DATA_TYPE_CODE]        = 0x01;
                accountData[I_CONVERSION_FACTOR + 0] = 0x8B;
                accountData[I_CONVERSION_FACTOR + 1] = 0x48;
                // zero-out the don't care bytes
                accountData[I_DONT_CARE]     = 0x00;
                accountData[I_DONT_CARE + 1] = 0x00;
                accountData[I_DONT_CARE + 2] = 0x00;
                accountData[I_DONT_CARE + 3] = 0x00;

                // lets try Record A and check the crc
                accountData[I_FILE_LENGTH] = RECORD_A_LENGTH;
                crc16 = CRC16.compute(accountData, 0, RECORD_A_LENGTH + 3, user.AccountPageNumber);
                if (crc16 == 0xB001)
                {
                    validA = true;
                }

                // lets try Record B and check the crc
                accountData[I_FILE_LENGTH] = RECORD_B_LENGTH;
                crc16 = CRC16.compute(accountData, 0, RECORD_B_LENGTH + 3, user.AccountPageNumber);
                if (crc16 == 0xB001)
                {
                    validB = true;
                }

                if (validA && validB)
                {
                    OneWireEventSource.Log.Debug("Both A and B are valid");
                    // Both A & B are valid!  And we know that we can only
                    // get here if the pointer or the header was not valid.
                    // That means that B was the last updated one but the
                    // header got hosed and the debit was not finished...
                    // which means A is the last known good value, let's go with A.
                    accountData[I_FILE_LENGTH] = RECORD_A_LENGTH;
                }
                else if (validA)
                {
                    OneWireEventSource.Log.Debug("A is valid not B");
                    // B is invalid, A is valid.  Means A is the last updated one,
                    // but B is the last known good value.  The header was not updated
                    // to point to A before debit was aborted.  Let's go with B
                    accountData[I_FILE_LENGTH] = RECORD_B_LENGTH;
                }
                else if (validB)
                {
                    OneWireEventSource.Log.Debug("B is valid not A - impossible");
                    // A is invalid, B is valid. Should never ever happen.  Something
                    // got completely hosed.  What should happen here?
                    this.lastError = USER_BAD_ACCOUNT_DATA;
                    return(false);
                }
                else
                {
                    OneWireEventSource.Log.Debug("Neither record has valid CRC");

                    // neither record contains a valid CRC.  What should happen here?
                    // probably got weak bit in ptr record, no telling which way to go
                    this.lastError = USER_BAD_ACCOUNT_DATA;
                    return(false);
                }

                //get the user's balance from accountData
                int balance = -1;
                if (accountData[I_FILE_LENGTH] == RECORD_A_LENGTH)
                {
                    balance = Convert.toInt(accountData, I_BALANCE_A, 3);
                }
                else if (accountData[I_FILE_LENGTH] == RECORD_B_LENGTH)
                {
                    balance = Convert.toInt(accountData, I_BALANCE_B, 3);
                }

                if (balance < 0)
                {
                    this.lastError = USER_BAD_ACCOUNT_DATA;
                    return(false);
                }

                this.userBalance = balance;

                int cnt = MAX_RETRY_CNT;
                do
                {
                    if (user.refreshDevice() && writeTransactionData(user, -1, balance, accountData))
                    {
                        break;
                    }
                } while (--cnt > 0);

                return(false);
            }
        }