// CHECKSTYLE:OFF
/*---------------------------------------------------------------------------
 * Copyright (C) 1999-2001 Maxim Integrated Products, 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 MAXIM INTEGRATED PRODUCTS 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 Maxim Integrated Products
 * shall not be used except as stated in the Maxim Integrated Products
 * Branding Policy.
 *---------------------------------------------------------------------------
 */

package com.dalsemi.onewire.application.sha;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Calendar;

import com.dalsemi.onewire.OneWireException;
import com.dalsemi.onewire.adapter.OneWireIOException;
import com.dalsemi.onewire.application.file.OWFile;
import com.dalsemi.onewire.application.file.OWFileInputStream;
import com.dalsemi.onewire.application.file.OWFileNotFoundException;
import com.dalsemi.onewire.application.file.OWFileOutputStream;
import com.dalsemi.onewire.container.OneWireContainer18;
import com.dalsemi.onewire.utils.IOHelper;

/**
 * <P>
 * Class for holding instances of SHA iButton Coprocessors involved in SHA
 * Transactions. The Coprocessor is used for digitally signing transaction data
 * as well as generating random challenges for users and verifying their
 * response.
 * </P>
 *
 * <p>
 * A DS1963S SHA iButton can be a <code>SHAiButtonCopr</code> or a
 * <code>SHAiButtonUser</code>. A Coprocessor iButton verifiessignatures 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 knows the
 * same authentication secret (example: a Visa terminal making sure the iButton
 * had a Visa account installed). Then the Coprocessor would verify the signed
 * data, probably money, to make sure it was valid. If someone tried to
 * overwrite the money file, even with a previously valid money file (an attempt
 * to 'restore' a previous amount of money), the signed file would be invalid
 * because the signature includes the write cycle counter, which is incremented
 * every time a page is written to. The write cycle counter is read-only and
 * does not roll over, so the previous amount of money could not be restored by
 * rolling the write counter. The Coprocessor verifies the money, then signs a
 * new data file that contains the new amount of money.
 * </p>
 *
 * <p>
 * There are two secrets involved with the transaction process. The first secret
 * is the authentication secret. It is used to validate a User iButton to a
 * system. The Coprocessor iButton has the system authentication secret
 * installed. On User iButtons, the system authentication secret is merged with
 * binding data and the unique address of the User iButton to create a unique
 * device authentication secret. The second secret is a signing secret. This
 * secret only exists on the Coprocessor iButton, and is used to sign and verify
 * transaction data (i.e. money). These secrets are inaccessible outside the
 * iButton. Once they are installed, they cannot be retrieved.
 * </p>
 *
 * <p>
 * This class makes use of several performance enhancements for TINI. For
 * instance, most methods are <code>synchronized</code> to access instance
 * variable byte arrays rather than creating new byte arrays every time a
 * transaction is performed. This could hurt performance in multi-threaded
 * applications, but the usefulness of having several threads contending to talk
 * to a single iButton is questionable since the methods in
 * <code>com.dalsemi.onewire.adapter.DSPortAdapter</code>
 * <code>beginExclusive(boolean)</code> and <code>endExclusive()</code> should
 * be used.
 * </p>
 *
 * @see SHATransaction
 * @see SHAiButtonUser
 *
 * @version 1.00
 * @author SKH
 */
public class SHAiButtonCopr {
	/**
	 * Turns on extra debugging output
	 */
	static final boolean DEBUG = false;

	// ***********************************************************************
	// Constants for Error codes
	// ***********************************************************************
	public static final int NO_ERROR = 0;
	public static final int WRITE_DATA_PAGE_FAILED = -1;
	public static final int WRITE_SCRATCHPAD_FAILED = -2;
	public static final int MATCH_SCRATCHPAD_FAILED = -3;
	public static final int ERASE_SCRATCHPAD_FAILED = -4;
	public static final int COPY_SCRATCHPAD_FAILED = -5;
	public static final int SHA_FUNCTION_FAILED = -6;
	public static final int BIND_SECRET_FAILED = -7;
	// ***********************************************************************

	/**
	 * Last error code raised
	 */
	protected int lastError;

	/**
	 * Reference to the OneWireContainer
	 */
	protected OneWireContainer18 ibc = null;

	/**
	 * Cache of 1-Wire Address
	 */
	protected byte[] address = null;

	/**
	 * Page used for generating user authentication secret.
	 */
	protected int authPageNumber = -1;

	/**
	 * Any auxiliary data stored on this coprocessor
	 */
	protected String auxData;

	/**
	 * 7 bytes of binding data for scratchpad to bind secret installation
	 */
	protected byte[] bindCode = new byte[7];

	/**
	 * 32 bytes of binding data to bind secret installation
	 */
	protected byte[] bindData = new byte[32];

	/**
	 * Specifies whether or not this coprocessor is compatible with the DS1961S.
	 * This entails the use of a specifically padded authentication secret.
	 */
	protected boolean DS1961Scompatible = false;

	/**
	 * Code used to specify encryption type.
	 */
	protected int encCode = -1;

	/**
	 * Filename, including extension, for user's service file
	 */
	protected byte[] filename = new byte[5];

	/**
	 * 20 byte initial signature, used for signing user account data
	 */
	protected byte[] initialSignature = new byte[20];

	/**
	 * The Provider name of the coprocessor's service
	 */
	protected String providerName;

	/**
	 * 3 byte challenge, used for signing user account data
	 */
	protected byte[] signingChallenge = new byte[3];

	/**
	 * Page used for signing user account data.
	 */
	protected int signPageNumber = 8;

	/**
	 * Code used to specify encryption type.
	 */
	protected int version = -1;

	/**
	 * Page used for generating user's validation MAC.
	 */
	protected int wspcPageNumber = -1;

	// ***********************************************************************
	// Constructors
	// ***********************************************************************

	/**
	 * <p>
	 * No default construct for user apps. Coprocessors, unlike users, are immutable
	 * classes, so there is no <code>setiButton</code> for User applications.
	 * </p>
	 *
	 * @see #SHAiButtonCopr(OneWireContainer18,String,boolean,int,int,int,int,int,byte,byte[],byte[],byte[],byte[],byte[],byte[],byte[],byte[],byte[])
	 * @see #SHAiButtonCopr(OneWireContainer18,String)
	 */
	protected SHAiButtonCopr() {

	}

	/**
	 * <P>
	 * Sets up this coprocessor object based on the provided parameters and saves
	 * all of these parameters as the contents of the file <code>coprFilename</code>
	 * stored on <code>owc</code>. Then, the system secret and authentication secret
	 * are installed on the coprocessor button.
	 * </P>
	 *
	 * <P>
	 * For the proper format of the coprocessor data file, see the document entitled
	 * "Implementing Secured D-Identification and E-Payment Applications using SHA
	 * iButtons". For the format of TMEX file structures, see Application Note 114.
	 * </P>
	 *
	 * @param l_owc              The DS1963S used as a coprocessor.
	 * @param coprFilename       The TMEX filename where coprocessor configuration
	 *                           data is stored. Usually, "COPR.0".
	 * @param l_formatDevice     boolean indicating whether or not the TMEX
	 *                           filesystem of this device should be formatted
	 *                           before the coprocessor data file is stored.
	 * @param l_signPageNumber   page number used for signing user account data.
	 *                           (Should be page 8, but page 0 is acceptable if you
	 *                           don't need the TMEX directory structure)
	 * @param l_authPageNumber   page number used for recreating user secret.
	 * @param l_wspcPageNumber   page number used for storing user secret and
	 *                           recreating authentication MAC.
	 * @param l_version          version of the service provided by this
	 *                           coprocessor.
	 * @param l_encCode          refers to a type of encryption used for user
	 *                           account data stored on user buttons.
	 * @param l_serviceFileExt   the file extension used for the service file. (An
	 *                           extension of decimal 102 is reserved for Money
	 *                           files).
	 * @param l_serviceFilename  the 4-byte name of the user's account data file.
	 * @param l_providerName     the name of the provider of this service
	 * @param l_bindData         the binding data used to finalize secret
	 *                           installation on user buttons.
	 * @param l_bindCode         the binding code used to finalize secret
	 *                           installation on user buttons.
	 * @param l_auxData          any auxiliary or miscellaneous data to be stored on
	 *                           the coprocessor.
	 * @param l_initialSignature the 20-byte initial MAC placed in user account data
	 *                           before generating actual MAC.
	 * @param l_signingChlg      the 3-byte challenge used for signing user account
	 *                           data.
	 * @param l_signingSecret    the system signing secret used by the service being
	 *                           installed on this coprocessor.
	 * @param l_authSecret       the system authentication secret used by the
	 *                           service being installed on this coprocessor.
	 *
	 * @throws OneWireIOException on a 1-Wire communication error such as reading an
	 *                            incorrect CRC from a 1-Wire device. This could be
	 *                            caused by a physical interruption in the 1-Wire
	 *                            Network due to shorts or a newly arriving 1-Wire
	 *                            device issuing a 'presence pulse'.
	 * @throws OneWireException   on a communication or setup error with the 1-Wire
	 *                            adapter
	 *
	 * @see #SHAiButtonCopr(OneWireContainer18,String)
	 */
	public SHAiButtonCopr(OneWireContainer18 l_owc, String coprFilename, boolean l_formatDevice, int l_signPageNumber,
			int l_authPageNumber, int l_wspcPageNumber, int l_version, int l_encCode, byte l_serviceFileExt,
			byte[] l_serviceFilename, byte[] l_providerName, byte[] l_bindData, byte[] l_bindCode, byte[] l_auxData,
			byte[] l_initialSignature, byte[] l_signingChlg, byte[] l_signingSecret, byte[] l_authSecret)
			throws OneWireException, OneWireIOException {
		// clear any errors
		this.lastError = SHAiButtonCopr.NO_ERROR;

		// Do some bounds checking on our array data.
		if (l_bindData.length != 32) {
			throw new OneWireException("Invalid Binding Data");
		}
		if (l_bindCode.length != 7) {
			throw new OneWireException("Invalid Binding Code");
		}
		if (l_signingChlg.length != 3) {
			throw new OneWireException("Invalid Signing Challenge");
		}
		if (l_serviceFilename.length < 4) {
			throw new OneWireException("Invalid Service Filename");
		}
		if (l_signPageNumber != 0 && l_signPageNumber != 8) {
			throw new OneWireException("Invalid Signing Page Number (must be 0 or 8)");
		}

		// Check to see if this coprocessor's authentication secret
		// is appropriately padded to be used with a DS1961S
		this.DS1961Scompatible = l_authSecret.length % 47 == 0;
		var secretDiv = l_authSecret.length / 47;
		for (var j = 0; j < secretDiv && this.DS1961Scompatible; j++) {
			var offset = 47 * j;
			for (var i = 32; i < 36 && this.DS1961Scompatible; i++) {
				this.DS1961Scompatible = l_authSecret[i + offset] == (byte) 0x0FF;
			}
			for (var i = 44; i < 47 && this.DS1961Scompatible; i++) {
				this.DS1961Scompatible = l_authSecret[i + offset] == (byte) 0x0FF;
			}
		}

		// get the current month, date, and year
		var c = Calendar.getInstance();
		var month = c.get(Calendar.MONTH) + 1;
		var date = c.get(Calendar.DATE);
		var year = c.get(Calendar.YEAR) - 1900;
		var yearMSB = (byte) (year >> 8 & 0x0FF);
		var yearLSB = (byte) (year & 0x0FF);

		try {
			if (l_formatDevice) {
				// format if necessary
				var f = new OWFile(l_owc, coprFilename);
				f.format();
				f.close();
			}
			// Create the service file
			var fos = new OWFileOutputStream(l_owc, coprFilename);
			fos.write(l_serviceFilename, 0, 4);
			fos.write(l_serviceFileExt);
			fos.write(l_signPageNumber);
			fos.write(l_authPageNumber);
			fos.write(l_wspcPageNumber);
			fos.write(l_version);
			fos.write(month);
			fos.write(date);
			fos.write(yearMSB);
			fos.write(yearLSB);
			fos.write(l_bindData);
			fos.write(l_bindCode);
			fos.write(l_signingChlg);
			fos.write((byte) l_providerName.length);
			fos.write((byte) l_initialSignature.length);
			fos.write((byte) l_auxData.length);
			fos.write(l_providerName, 0, (byte) l_providerName.length);
			fos.write(l_initialSignature, 0, (byte) l_initialSignature.length);
			fos.write(l_auxData, 0, (byte) l_auxData.length);
			fos.write(l_encCode);
			fos.write(this.DS1961Scompatible ? 0x55 : 0x00);
			fos.flush();
			fos.close();
		} catch (Exception ioe) {
			ioe.printStackTrace();
			throw new OneWireException("Creating Service File failed!");
		}

		// Install the system signing secret, used to sign and validate all user data
		if (!l_owc.installMasterSecret(l_signPageNumber, l_signingSecret, l_signPageNumber & 7)) {
			throw new OneWireException("Could not install signing secret");
		}

		// Install the system authentication secret, used to authenticate users
		if (!l_owc.installMasterSecret(l_authPageNumber, l_authSecret, l_authPageNumber & 7)) {
			throw new OneWireException("Could not install authentication secret");
		}

		// \\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\
		if (DEBUG) {
			IOHelper.writeLine("------------------------------------");
			IOHelper.writeLine("Initialized Coprocessor");
			IOHelper.writeLine("address");
			IOHelper.writeBytesHex(l_owc.getAddress());
			IOHelper.writeLine("signPageNumber: " + l_signPageNumber);
			IOHelper.writeLine("authPageNumber: " + l_authPageNumber);
			IOHelper.writeLine("wspcPageNumber: " + l_wspcPageNumber);
			IOHelper.writeLine("serviceFilename");
			IOHelper.writeBytesHex(l_serviceFilename);
			IOHelper.writeLine("bindData");
			IOHelper.writeBytesHex(l_bindData);
			IOHelper.writeLine("bindCode");
			IOHelper.writeBytesHex(l_bindCode);
			IOHelper.writeLine("initialSignature");
			IOHelper.writeBytesHex(l_initialSignature);
			IOHelper.writeLine("signingChlg");
			IOHelper.writeBytesHex(l_signingChlg);
			IOHelper.writeLine("signingSecret");
			IOHelper.writeBytesHex(l_signingSecret);
			IOHelper.writeLine("authSecret");
			IOHelper.writeBytesHex(l_authSecret);
			IOHelper.writeLine("------------------------------------");
		}
		// \\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\

		// Call this method because it will read back the file. Ensuring
		// there were no errors in writing the file in the first place.
		this.setiButton(l_owc, coprFilename);

	}

	/**
	 * <P>
	 * Sets up this coprocessor object based on the contents of the file
	 * <code>coprFilename</code> stored on <code>owc</code>. This sets all the
	 * properties of the object as a consequence of what's in the coprocessor file.
	 * </P>
	 *
	 * <P>
	 * For the proper format of the coprocessor data file, see the document entitled
	 * "Implementing Secured D-Identification and E-Payment Applications using SHA
	 * iButtons". For the format of TMEX file structures, see Application Note 114.
	 *
	 * @param owc          The DS1963S used as a coprocessor
	 * @param coprFilename The TMEX filename where coprocessor configuration data is
	 *                     stored. Usually, "COPR.0".
	 *
	 * @throws OneWireIOException on a 1-Wire communication error such as reading an
	 *                            incorrect CRC from a 1-Wire device. This could be
	 *                            caused by a physical interruption in the 1-Wire
	 *                            Network due to shorts or a newly arriving 1-Wire
	 *                            device issuing a 'presence pulse'.
	 * @throws OneWireException   on a communication or setup error with the 1-Wire
	 *                            adapter
	 *
	 * @see #SHAiButtonCopr(OneWireContainer18,String,boolean,int,int,int,int,int,byte,byte[],byte[],byte[],byte[],byte[],byte[],byte[],byte[],byte[])
	 */
	public SHAiButtonCopr(OneWireContainer18 owc, String coprFilename) throws OneWireException, OneWireIOException {
		this.setiButton(owc, coprFilename);
	}

	// ***********************************************************************
	// Modifier Methods
	// - setiButton is the only essential modifier. It updates all
	// data members based on contents of service file alone.
	// ***********************************************************************

	/**
	 * <P>
	 * Sets up this coprocessor object based on the contents of the file
	 * <code>coprFilename</code> stored on <code>owc</code>. This sets all the
	 * properties of the object as a consequence of what's in the coprocessor file.
	 * </P>
	 *
	 * <P>
	 * In essence, this is the classes only proper modifier. All data members should
	 * be set by this method alone.
	 * </P>
	 *
	 * <P>
	 * For the proper format of the coprocessor data file, see the document entitled
	 * "Implementing Secured D-Identification and E-Payment Applications using SHA
	 * iButtons". For the format of TMEX file structures, see Application Note 114.
	 *
	 * @param owc          The DS1963S used as a coprocessor
	 * @param coprFilename The TMEX filename where coprocessor configuration data is
	 *                     stored. Usually, "COPR.0".
	 *
	 * @throws OneWireIOException on a 1-Wire communication error such as reading an
	 *                            incorrect CRC from a 1-Wire device. This could be
	 *                            caused by a physical interruption in the 1-Wire
	 *                            Network due to shorts or a newly arriving 1-Wire
	 *                            device issuing a 'presence pulse'.
	 * @throws OneWireException   on a communication or setup error with the 1-Wire
	 *                            adapter
	 *
	 */
	private void setiButton(OneWireContainer18 owc, String coprFilename) throws OneWireException, OneWireIOException {
		// hold container reference
		this.ibc = owc;
		// and address
		this.address = owc.getAddress();

		OWFileInputStream fis = null;
		try {
			if (DEBUG) {
				IOHelper.writeLine("opening file: " + coprFilename + " on token: " + owc);
			}
			fis = new OWFileInputStream(owc, coprFilename);
		} catch (OWFileNotFoundException e) {
			if (DEBUG) {
				e.printStackTrace();
			}
			throw new OneWireIOException("Coprocessor service file Not found: " + e);
		}
		try {
			// configures this object from the info in the given stream
			this.fromStream(fis);
		} catch (IOException ioe) {
			if (DEBUG) {
				ioe.printStackTrace();
			}
			throw new OneWireException("Bad Data in Coprocessor Service File: " + ioe);
		} finally {
			try {
				fis.close();
			} catch (IOException ioe) {
				/* well, at least I tried! */
			}
		}

		// \\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\
		if (DEBUG) {
			IOHelper.writeLine("------------------------------------");
			IOHelper.writeLine("Loaded Coprocessor");
			IOHelper.writeLine("address");
			IOHelper.writeBytesHex(owc.getAddress());
			IOHelper.writeLine("signPageNumber: " + this.signPageNumber);
			IOHelper.writeLine("authPageNumber: " + this.authPageNumber);
			IOHelper.writeLine("wspcPageNumber: " + this.wspcPageNumber);
			IOHelper.writeLine("serviceFilename");
			IOHelper.writeBytesHex(this.filename);
			IOHelper.writeLine("bindData");
			IOHelper.writeBytesHex(this.bindData);
			IOHelper.writeLine("bindCode");
			IOHelper.writeBytesHex(this.bindCode);
			IOHelper.writeLine("initialSignature");
			IOHelper.writeBytesHex(this.initialSignature);
			IOHelper.writeLine("signingChallenge");
			IOHelper.writeBytesHex(this.signingChallenge);
			IOHelper.writeLine("------------------------------------");
		}
		// \\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\
	}

	/*
	 * Do I even need modifiers. The class should only be modified with "setibutton"
	 * // *********************************************************************** //
	 * Protected Modifiers // - All modifiers of this class's data are protected.
	 * The reason // is that all data members are set based on information in the //
	 * Coprocessor's service file. These member's should never be // directly
	 * updated by User applications, but rather as a result // to changes in the
	 * service file. //
	 * ***********************************************************************
	 * protected void setSigningPageNumber(int pg) { this.signPageNumber = pg; }
	 * protected void setWorkspacePageNumber(int pg) { this.wspcPageNumber = pg; }
	 * protected void setAuthenticationPageNumber(int pg) { this.authPageNumber =
	 * pg; } protected void setFilename(byte[] l_filename, int offset) { int cnt =
	 * Math.min(l_filename.length - offset, 4); System.arraycopy(l_filename, offset,
	 * filename, 0, cnt); } protected void setFilenameExt(byte ext) { filename[4] =
	 * ext; } protected void setBindData(byte[] l_bindData, int offset) { int cnt =
	 * Math.min(l_bindData.length - offset, 32); System.arraycopy(l_bindData,
	 * offset, bindData, 0, cnt); } protected void setBindCode(byte[] l_bindCode,
	 * int offset) { int cnt = Math.min(l_bindCode.length - offset, 7);
	 * System.arraycopy(l_bindCode, offset, bindCode, 0, cnt); } protected
	 * synchronized void setSigningChallenge (byte[] chlg, int offset) { int cnt =
	 * (chlg.length > (3+offset)) ? 3 : (chlg.length - offset);
	 * System.arraycopy(chlg, offset, signingChallenge, 0, cnt); } protected
	 * synchronized void setInitialSignature (byte[] sig_ini, int offset) { int cnt
	 * = (sig_ini.length > (20+offset)) ? 20 : (sig_ini.length - offset);
	 * System.arraycopy(sig_ini, offset, initialSignature, 0, cnt); }
	 */

	// ***********************************************************************
	// Begin Accessor Methods
	// ***********************************************************************

	/**
	 * <P>
	 * Returns the 8 byte address of the OneWireContainer this SHAiButton refers to.
	 * </P>
	 *
	 * @return 8 byte array containing family code, address, and crc8 of the OneWire
	 *         device.
	 */
	public byte[] getAddress() {
		var data = new byte[8];
		System.arraycopy(this.address, 0, data, 0, 8);
		return data;
	}

	/**
	 * <P>
	 * Copies the 8 byte address of the OneWireContainer into the provided array
	 * starting at the given offset.
	 * </P>
	 *
	 * @param data   array with at least 8 bytes after offset
	 * @param offset the index at which copying starts
	 */
	public void getAddress(byte[] data, int offset) {
		System.arraycopy(this.address, 0, data, offset, 8);
	}

	/**
	 * <P>
	 * Copies the specified number of bytes from the address of the OneWireContainer
	 * into the provided array starting at the given offset.
	 * </P>
	 *
	 * @param data   array with at least cnt bytes after offset
	 * @param offset the index at which copying starts
	 * @param cnt    the number of bytes to copy
	 */
	public void getAddress(byte[] data, int offset, int cnt) {
		System.arraycopy(this.address, 0, data, offset, cnt);
	}

	/**
	 * <P>
	 * Returns the page number used by this coprocessor for storing system
	 * authentication secret and recreating user's authentication secret. The
	 * authentication secret stays constant, while the new secret is copied on to
	 * the secret corresponding to the workspace page.
	 * </P>
	 *
	 * @return page number used for system authentication secret
	 * @see OneWireContainer18#bindSecretToiButton(int,byte[],byte[],int)
	 */
	public int getAuthenticationPageNumber() {
		return this.authPageNumber;
	}

	/**
	 * <P>
	 * Returns a string representing the auxiliary data associated with this
	 * coprocessor's service.
	 * </P>
	 *
	 * @return the auxiliary data of this coprocessor's service
	 */
	public String getAuxilliaryData() {
		return this.auxData;
	}

	/**
	 * <P>
	 * Returns 7 byte binding code for finalizing secret installation on user
	 * buttons. This is copied into the user's scratchpad, along with 8 other bytes
	 * of binding data (see <code>bindSecretToiButton</code>) for finalizing the
	 * secret and making it unique to the button.
	 * </P>
	 *
	 * @return the binding code for user's scratchpad
	 * @see OneWireContainer18#bindSecretToiButton(int,byte[],byte[],int)
	 */
	public byte[] getBindCode() {
		var data = new byte[7];
		System.arraycopy(this.bindCode, 0, data, 0, 7);
		return data;
	}

	/**
	 * <P>
	 * Copies 7 byte binding code for finalizing secret installation on user
	 * buttons. This is copied into the user's scratchpad, along with 8 other bytes
	 * of binding data (see <code>bindSecretToiButton</code>) for finalizing the
	 * secret and making it unique to the button.
	 * </P>
	 *
	 * @param data   array for copying the binding code for user's scratchpad
	 * @param offset the index at which to start copying.
	 * @see OneWireContainer18#bindSecretToiButton(int,byte[],byte[],int)
	 */
	public void getBindCode(byte[] data, int offset) {
		System.arraycopy(this.bindCode, 0, data, offset, 7);
	}

	/**
	 * <P>
	 * Returns 32 byte binding data for finalizing secret installation on user
	 * buttons. This is copied into the user's account data page (see
	 * <code>bindSecretToiButton</code>) for finalizing the secret and making it
	 * unique to the button.
	 * </P>
	 *
	 * @return the binding data for user's data page
	 * @see OneWireContainer18#bindSecretToiButton(int,byte[],byte[],int)
	 */
	public byte[] getBindData() {
		var data = new byte[32];
		System.arraycopy(this.bindData, 0, data, 0, 32);
		return data;
	}

	/**
	 * <P>
	 * Copies 32 byte binding data for finalizing secret installation on user
	 * buttons. This is then copied into the user's account data page (see
	 * <code>bindSecretToiButton</code>) for finalizing the secret and making it
	 * unique to the button.
	 * </P>
	 *
	 * @param data   array for copying the binding data for user's data page
	 * @param offset the index at which to start copying.
	 * @see OneWireContainer18#bindSecretToiButton(int,byte[],byte[],int)
	 */
	public void getBindData(byte[] data, int offset) {
		System.arraycopy(this.bindData, 0, data, offset, 32);
	}

	/**
	 * <P>
	 * Returns an integer representing the encryption code for this coprocessor. No
	 * handling of specific encryption codes are in place with this API, but could
	 * be added easily at the <code>SHATransaction</codE> layer.
	 * </P>
	 *
	 * @return an integer representing type of encryption for user data.
	 */
	public int getEncryptionCode() {
		return this.encCode;
	}

	/**
	 * <P>
	 * Copies the filename (used for storing account data on user buttons) into the
	 * specified array starting at the specified offset.
	 * </P>
	 *
	 * @param l_filename the array into which the filename will be copied.
	 * @param offset     the starting index for copying the filename.
	 */
	public void getFilename(byte[] l_filename, int offset) {
		var cnt = Math.min(l_filename.length - offset, 4);
		System.arraycopy(this.filename, offset, l_filename, offset, cnt);
	}

	/**
	 * <P>
	 * Returns the extension of the filename used for storing account data on user
	 * buttons. If the type of this service is an e-cash application, the file
	 * extension will be decimal 102.
	 * </P>
	 *
	 * @return proper file extension for user's account data file.
	 */
	public byte getFilenameExt() {
		return this.filename[4];
	}

	/**
	 * <P>
	 * Returns the 20-byte initial signature used by this coprocessor for signing
	 * account data.
	 * </P>
	 *
	 * @return 20-byte initial signature.
	 */
	public byte[] getInitialSignature() {
		var data = new byte[20];
		System.arraycopy(this.initialSignature, 0, data, 0, 20);
		return data;
	}

	/**
	 * <P>
	 * Copies the 20-byte initial signature used by this coprocessor for signing
	 * account data into the specified array starting at the specified offset.
	 * </P>
	 *
	 * @param data   array for copying the 20-byte initial signature.
	 * @param offset the index at which to start copying.
	 */
	public void getInitialSignature(byte[] data, int offset) {
		System.arraycopy(this.initialSignature, 0, data, offset, 20);
	}

	/**
	 * <P>
	 * Returns error code matching last error encountered by the coprocessor. An
	 * error code of zero implies NO_ERROR.
	 * </P>
	 *
	 * @return the error code, zero for none.
	 */
	public int getLastError() {
		return this.lastError;
	}

	/**
	 * <P>
	 * Returns a string representing the Provider name associated with this
	 * coprocessor's service.
	 * </P>
	 *
	 * @return the name of the provider's service.
	 */
	public String getProviderName() {
		return this.providerName;
	}

	/**
	 * <P>
	 * Returns the 3-byte signing challenge used by this coprocessor for data
	 * validation.
	 * </P>
	 *
	 * @return 3-byte challenge
	 */
	public byte[] getSigningChallenge() {
		var data = new byte[3];
		System.arraycopy(this.signingChallenge, 0, data, 0, 3);
		return data;
	}

	/**
	 * <P>
	 * Copies the 3-byte signing challenge used by this coprocessor for data
	 * validation into the specified array starting at the specified offset.
	 * </P>
	 *
	 * @param data   the array for copying the 3-byte challenge.
	 * @param offset the index at which to start copying.
	 */
	public void getSigningChallenge(byte[] data, int offset) {
		System.arraycopy(this.signingChallenge, 0, data, offset, 3);
	}

	/**
	 * <P>
	 * Returns the page number used by this coprocessor for signing account data.
	 * </P>
	 *
	 * @return page number used for signing
	 */
	public int getSigningPageNumber() {
		return this.signPageNumber;
	}

	/**
	 * <P>
	 * Returns the version number of this service.
	 * </P>
	 *
	 * @return version number for service.
	 */
	public int getVersion() {
		return this.version;
	}

	/**
	 * <P>
	 * Returns the page number used by this coprocessor for regenerating the user
	 * authentication. This page is the target page for
	 * <code>bindSecretToiButton</code> when trying to reproduce a user's
	 * authentication secret.
	 * </P>
	 *
	 * @return page number used for regenerating user authentication.
	 * @see OneWireContainer18#bindSecretToiButton(int,byte[],byte[],int)
	 */
	public int getWorkspacePageNumber() {
		return this.wspcPageNumber;
	}

	/**
	 * <P>
	 * Returns a boolean indicating whether or not this coprocessor's secret's were
	 * formatted for compatibility with the DS1961S.
	 * </P>
	 *
	 * @return <code>true</code> if this coprocessor can authenticate a DS1961S
	 *         using it's system authentication secret.
	 * @see #reformatFor1961S(byte[])
	 */
	public boolean isDS1961Scompatible() {
		return this.DS1961Scompatible;
	}
	// ***********************************************************************
	// End Accessor Methods
	// ***********************************************************************

	// ***********************************************************************
	// Begin SHA iButton Methods
	// ***********************************************************************

	/**
	 * <P>
	 * Given a 32-byte array for page data and a 32-byte array for scratchpad
	 * content, this function will create a 20-byte signature for the data based on
	 * SHA-1. The format of the calculation of the data signature is as follows:
	 * First 4-bytes of signing secret, 32-bytes of accountData, 12 bytes of
	 * scratchpad data starting at index 8, last 4-bytes of signing secret, 3 bytes
	 * of scratchpad data starting at index 20, and the rest is padding as specified
	 * for standard SHA-1. This is all laid out, in detail, in the DS1963S data
	 * sheet.
	 * </P>
	 *
	 * <P>
	 * The resulting 20-byte signature is copied into <code>mac_buffer</code>
	 * starting at <code>macStart</code>. If you're updating a signature that
	 * already exists in the accountData array, it is acceptable to call the method
	 * like so: <code><pre>
	 *   copr.createDataSignature(accountData, spad, accountData, 8);
	 * </pre></code> assuming that the signature starts at index 8 of the
	 * accountData array.
	 * </p>
	 *
	 * @param accountData    the 32-byte data page for which the signature is
	 *                       generated.
	 * @param signScratchpad the 32-byte scratchpad contents for which the signature
	 *                       is generated. This will contain parameters such as the
	 *                       user's write cycle counter for the page, the user's
	 *                       1-wire address, and the page number where account data
	 *                       is stored.
	 * @param mac_buffer     used to return the 20-byte signature generated by
	 *                       signing the page using the coprocessor's system signing
	 *                       secret.
	 * @param macStart       the offset into mac_buffer where copying should start.
	 *
	 * @return <code>true</code> if successful, <code>false</code> if an error
	 *         occurred (use <code>getLastError()</code> for more information on the
	 *         type of error)
	 *
	 * @throws OneWireIOException on a 1-Wire communication error such as reading an
	 *                            incorrect CRC from a 1-Wire device. This could be
	 *                            caused by a physical interruption in the 1-Wire
	 *                            Network due to shorts or a newly arriving 1-Wire
	 *                            device issuing a 'presence pulse'.
	 * @throws OneWireException   on a communication or setup error with the 1-Wire
	 *                            adapter
	 *
	 * @see OneWireContainer18#SHAFunction(byte,int)
	 * @see #getLastError()
	 */
	public boolean createDataSignature(byte[] accountData, byte[] signScratchpad, byte[] mac_buffer, int macStart)
			throws OneWireException, OneWireIOException {
		// clear any errors
		this.lastError = SHAiButtonCopr.NO_ERROR;

		// maintain local reference to container
		var ibcL = this.ibc;
		var addr = this.signPageNumber << 5;

		// now we are ready to make a signature
		if (!ibcL.writeDataPage(this.signPageNumber, accountData)) {
			this.lastError = SHAiButtonCopr.WRITE_DATA_PAGE_FAILED;
			return false;
		}

		// allow resume access to coprocessor
		ibcL.useResume(true);

		// write the signing information to the scratchpad
		if (!ibcL.writeScratchPad(0, 0, signScratchpad, 0, 32)) {
			this.lastError = SHAiButtonCopr.WRITE_SCRATCHPAD_FAILED;
			ibcL.useResume(false);
			return false;
		}

		// \\//\\//\\//\\//\\//\\//\\////\\////\\////\\////\\////\\////\\//
		if (DEBUG) {
			IOHelper.writeLine("-----------------------------------------------------------");
			IOHelper.writeLine("COPR DEBUG - createDataSignature");
			IOHelper.write("address: ");
			IOHelper.writeBytesHex(this.address, 0, 8);
			IOHelper.writeLine("speed: " + this.ibc.getAdapter().getSpeed());
			IOHelper.writeLine("-----------------------------------------------------------");
		}
		// \\//\\//\\//\\//\\//\\//\\////\\////\\////\\////\\////\\////\\//

		// sign that baby!
		if (ibcL.SHAFunction(OneWireContainer18.SIGN_DATA_PAGE, addr)) {
			// get the MAC from the scratchpad
			ibcL.readScratchPad(signScratchpad, 0);

			// copy the MAC into the accountData page
			System.arraycopy(signScratchpad, 8, mac_buffer, macStart, 20);

			ibcL.useResume(false);
			return true;
		}
		this.lastError = SHAiButtonCopr.SHA_FUNCTION_FAILED;

		ibcL.useResume(false);
		return false;
	}

	/**
	 * <p>
	 * Creates a data signature, but instead of using the signing secret, it uses
	 * the authentication secret, bound for a particular button.
	 * </p>
	 *
	 * <P>
	 * fullBindCode can be null if the secret is already bound and in the signing
	 * page.
	 * </p>
	 *
	 * @param accountData    the 32-byte data page for which the signature is
	 *                       generated.
	 * @param signScratchpad the 32-byte scratchpad contents for which the signature
	 *                       is generated. This will contain parameters such as the
	 *                       user's write cycle counter for the page, the user's
	 *                       1-wire address, and the page number where account data
	 *                       is stored.
	 * @param mac_buffer     used to return the 20-byte signature generated by
	 *                       signing the page using the coprocessor's system signing
	 *                       secret.
	 * @param macStart       the offset into mac_buffer where copying should start.
	 * @param fullBindCode   used to recreate the user iButton's unique secret
	 *
	 * @return <code>true</code> if successful, <code>false</code> if an error
	 *         occurred (use <code>getLastError()</code> for more information on the
	 *         type of error)
	 *
	 * @throws OneWireIOException on a 1-Wire communication error such as reading an
	 *                            incorrect CRC from a 1-Wire device. This could be
	 *                            caused by a physical interruption in the 1-Wire
	 *                            Network due to shorts or a newly arriving 1-Wire
	 *                            device issuing a 'presence pulse'.
	 * @throws OneWireException   on a communication or setup error with the 1-Wire
	 *                            adapter
	 *
	 * @see OneWireContainer18#SHAFunction(byte,int)
	 * @see #createDataSignature(byte[],byte[],byte[],int)
	 * @see #getLastError()
	 */
	public boolean createDataSignatureAuth(byte[] accountData, byte[] signScratchpad, byte[] mac_buffer, int macStart,
			byte[] fullBindCode) throws OneWireException, OneWireIOException {
		// clear any errors
		this.lastError = SHAiButtonCopr.NO_ERROR;

		// maintain local reference to container
		var ibcL = this.ibc;
		var page = this.signPageNumber;
		var addr = page << 5;

		// \\//\\//\\//\\//\\//\\//\\////\\////\\////\\////\\////\\////\\//
		if (DEBUG) {
			IOHelper.writeLine("-----------------------------------------------------------");
			IOHelper.writeLine("COPR DEBUG - createDataSignatureAuth");
			IOHelper.writeLine("address:");
			IOHelper.writeBytesHex(this.address, 0, 8);
			IOHelper.writeLine("accountData");
			IOHelper.writeBytesHex(accountData);
			IOHelper.writeLine("signScratchpad");
			IOHelper.writeBytesHex(signScratchpad);
			IOHelper.writeLine("mac_buffer: ");
			IOHelper.writeBytesHex(mac_buffer, macStart, 20);
			IOHelper.writeLine("fullBindCode: ");
			if (fullBindCode != null) {
				IOHelper.writeBytesHex(fullBindCode);
			} else {
				IOHelper.writeLine("null");
			}
			IOHelper.writeLine("-----------------------------------------------------------");
		}

		// \\//\\//\\//\\//\\//\\//\\////\\////\\////\\////\\////\\////\\//
		if (fullBindCode != null) {
			// recreate the user's secret on the coprocessor.
			if (!ibcL.bindSecretToiButton(this.authPageNumber, this.bindData, fullBindCode, page & 7)) {
				this.lastError = SHAiButtonCopr.BIND_SECRET_FAILED; // bind secret failed
				return false;
			}
		}

		// now we are ready to make a signature
		if (!ibcL.writeDataPage(this.signPageNumber, accountData)) {
			this.lastError = SHAiButtonCopr.WRITE_DATA_PAGE_FAILED;
			return false;
		}

		// allow resume access to coprocessor
		ibcL.useResume(true);

		// write the signing information to the scratchpad
		if (!ibcL.writeScratchPad(0, 0, signScratchpad, 0, 32)) {
			this.lastError = SHAiButtonCopr.WRITE_SCRATCHPAD_FAILED;
			ibcL.useResume(false);
			return false;
		}

		// sign that baby!
		if (ibcL.SHAFunction(OneWireContainer18.SIGN_DATA_PAGE, addr)) {
			// get the MAC from the scratchpad
			ibcL.readScratchPad(signScratchpad, 0);

			// copy the MAC into the accountData page
			System.arraycopy(signScratchpad, 8, mac_buffer, macStart, 20);

			ibcL.useResume(false);
			return true;
		}
		this.lastError = SHAiButtonCopr.SHA_FUNCTION_FAILED;

		ibcL.useResume(false);
		return false;
	}

	// prevent malloc'ing in the critical path
	private final byte[] generateChallenge_scratchpad = new byte[32];

	/**
	 * <p>
	 * Generates a 3 byte random challenge in the iButton, sufficient to be used as
	 * a challenge to be answered by a User iButton. The user answers the challenge
	 * with an authenticated read of it's account data.
	 * </p>
	 *
	 * <p>
	 * The DS1963S will generate 20 bytes of pseudo random data, though only 3 bytes
	 * are needed for the challenge. Programs can add more 'randomness' by selecting
	 * different bytes from the 20 bytes of random data using the
	 * <code>offset</code> parameter.
	 * </p>
	 *
	 * <p>
	 * The random number generator is actually the DS1963S's SHA engine, which
	 * requires page data to compute a hash. Select a page number with the
	 * <code>page_number</code> parameter.
	 * </p>
	 *
	 * @param offset offset into the 20 random bytes to draw random data from (must
	 *               be in range 0-16)
	 * @param ch     buffer for the challenge to be returned (must be of length 3 or
	 *               more)
	 * @param start  the starting index into array <code>ch</code> to begin copying
	 *               the challenge bytes.
	 *
	 * @return <code>true</code> if successful, <code>false</code> if an error
	 *         occurred (use <code>getLastError()</code> for more information on the
	 *         type of error)
	 *
	 * @throws OneWireIOException on a 1-Wire communication error such as reading an
	 *                            incorrect CRC from a 1-Wire device. This could be
	 *                            caused by a physical interruption in the 1-Wire
	 *                            Network due to shorts or a newly arriving 1-Wire
	 *                            device issuing a 'presence pulse'.
	 * @throws OneWireException   on a communication or setup error with the 1-Wire
	 *                            adapter
	 *
	 * @see SHAiButtonUser#readAccountData(byte[],int,byte[],int,byte[],int)
	 * @see #getLastError()
	 */
	public synchronized boolean generateChallenge(int offset, byte[] ch, int start)
			throws OneWireIOException, OneWireException {
		// clear any errors
		this.lastError = SHAiButtonCopr.NO_ERROR;

		// maintain local reference to container
		var ibcL = this.ibc;
		var scratchpad = this.generateChallenge_scratchpad;
		var addr = this.authPageNumber << 5;

		if (ibcL.eraseScratchPad(this.authPageNumber)) {
			// ibcL.useResume(true);

			if (ibcL.SHAFunction(OneWireContainer18.COMPUTE_CHALLENGE, addr)) {
				// get the mac from the scratchpad
				ibcL.readScratchPad(scratchpad, 0);

				// copy the requested 3 return bytes
				System.arraycopy(scratchpad, 8 + offset % 17, ch, start, 3);

				// \\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\
				if (DEBUG) {
					IOHelper.writeLine("-----------------------------------------------------------");
					IOHelper.writeLine("COPR DEBUG");
					IOHelper.writeLine("address:");
					IOHelper.writeLine("speed: " + this.ibc.getAdapter().getSpeed());
					IOHelper.writeBytesHex(this.address, 0, 8);
					IOHelper.writeLine("Challenge:");
					IOHelper.writeBytesHex(ch, start, 3);
					ch[start] = (byte) 0x01;
					ch[start + 1] = (byte) 0x02;
					ch[start + 2] = (byte) 0x03;
					IOHelper.writeBytesHex(ch, start, 3);
					IOHelper.writeLine("-----------------------------------------------------------");
				}
				// \\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\
				ibcL.useResume(false);
				return true;
			}
			this.lastError = SHAiButtonCopr.SHA_FUNCTION_FAILED;
		} else {
			this.lastError = SHAiButtonCopr.ERASE_SCRATCHPAD_FAILED;
		}

		ibcL.useResume(false);
		return false;
	}

	/**
	 * <p>
	 * Determines if a <code>SHAiButtonUser</code> belongs to the system defined by
	 * this Coprocessor iButton.See the usage example in this class for initializing
	 * a Coprocessor iButton.
	 * </p>
	 *
	 * <p>
	 * The first step in user authentication is to recreate the user's unique secret
	 * on the coprocessor button using
	 * <code>bindSecretToiButton(int,byte[],byte[],int)</code>. Then the coprocessor
	 * signs the pageData to produce a MAC. If the MAC matches that produced by the
	 * user, the user belongs to the system.
	 * </p>
	 *
	 * <p>
	 * The TMEX formatted page with the user's account data is in the 32-byte
	 * parameter <code>pageData</code>. If the verification is successful, the data
	 * data signature must still be verified with the <code>verifySignature()</code>
	 * method.
	 * </p>
	 *
	 * <p>
	 * Failure of this method does not necessarily mean that the User iButton does
	 * not belong to the system. It is possible that a communication disruption here
	 * could cause a CRC error that would be indistinguishable from a failed
	 * authentication. However, repeated attempts should reveal whether it was truly
	 * a communication problem or a User iButton that does not belong to the system.
	 * </p>
	 *
	 * @param fullBindCode 15-byte binding code used to recreate user iButtons
	 *                     unique secret in the coprocessor.
	 * @param pageData     32-byte buffer containing the data page holding the
	 *                     user's account data.
	 * @param scratchpad   the 32-byte scratchpad contents for which the signature
	 *                     is generated. This will contain parameters such as the
	 *                     user's write cycle counter for the page, the user's
	 *                     1-wire address, and the page number where account data is
	 *                     stored.
	 * @param verify_mac   the 20-byte buffer containing the user's authentication
	 *                     response to the coprocessor's challenge.
	 *
	 * @return <code>true</code> if the operation was successful and the user's MAC
	 *         matches that generated by the coprocessor.
	 *
	 * @throws OneWireIOException on a 1-Wire communication error such as reading an
	 *                            incorrect CRC from a 1-Wire device. This could be
	 *                            caused by a physical interruption in the 1-Wire
	 *                            Network due to shorts or a newly arriving 1-Wire
	 *                            device issuing a 'presence pulse'.
	 * @throws OneWireException   on a communication or setup error with the 1-Wire
	 *                            adapter
	 *
	 * @see #generateChallenge(int,byte[],int)
	 * @see #verifySignature(byte[],byte[],byte[])
	 * @see OneWireContainer18#bindSecretToiButton(int,byte[],byte[],int)
	 * @see OneWireContainer18#SHAFunction(byte,int)
	 * @see OneWireContainer18#matchScratchPad(byte[])
	 * @see #getLastError()
	 */
	public boolean verifyAuthentication(byte[] fullBindCode, byte[] pageData, byte[] scratchpad, byte[] verify_mac,
			byte authCmd) throws OneWireIOException, OneWireException {
		// clear any errors
		this.lastError = SHAiButtonCopr.NO_ERROR;

		// maintain local reference to container
		var ibcL = this.ibc;
		var addr = this.wspcPageNumber << 5;
		var wspc = this.wspcPageNumber;

		// recreate the user's secret on the coprocessor.
		if (!ibcL.bindSecretToiButton(this.authPageNumber, this.bindData, fullBindCode, wspc)) {
			this.lastError = SHAiButtonCopr.BIND_SECRET_FAILED; // bind secret failed
			return false;
		}

		ibcL.useResume(true);

		// \\//\\//\\//\\//\\//\\//\\////\\////\\////\\////\\////\\////\\//
		if (DEBUG) {
			IOHelper.writeLine("-----------------------------------------------------------");
			IOHelper.writeLine("COPR DEBUG - verifyAuthentication");
			IOHelper.write("address: ");
			IOHelper.writeBytesHex(this.address, 0, 8);
			IOHelper.writeLine("speed: " + this.ibc.getAdapter().getSpeed());
			IOHelper.writeLine("pageData");
			IOHelper.writeBytesHex(pageData);
			IOHelper.writeLine("scratchpad");
			IOHelper.writeBytesHex(scratchpad);
			IOHelper.writeLine("authCmd: " + authCmd);
			IOHelper.writeLine("bindData: ");
			IOHelper.writeBytesHex(this.bindData);
			IOHelper.writeLine("fullBindCode: ");
			IOHelper.writeBytesHex(fullBindCode);
			IOHelper.writeLine("-----------------------------------------------------------");
		}
		// \\//\\//\\//\\//\\//\\//\\////\\////\\////\\////\\////\\////\\//

		// write the account data
		if (!ibcL.writeDataPage(wspc, pageData)) {
			this.lastError = SHAiButtonCopr.WRITE_DATA_PAGE_FAILED;
			ibcL.useResume(false);
			return false;
		}

		// write the scratchapd data
		if (!ibcL.writeScratchPad(wspc, 0, scratchpad, 0, 32)) {
			this.lastError = SHAiButtonCopr.WRITE_SCRATCHPAD_FAILED;
			ibcL.useResume(false);
			return false;
		}

		// generate the MAC
		if (ibcL.SHAFunction(authCmd, addr)) {
			if (ibcL.matchScratchPad(verify_mac)) {
				ibcL.useResume(false);
				return true;
			}
			this.lastError = SHAiButtonCopr.MATCH_SCRATCHPAD_FAILED;
		} else {
			this.lastError = SHAiButtonCopr.SHA_FUNCTION_FAILED;
		}

		ibcL.useResume(false);

		return false;
	}

	/**
	 * <P>
	 * Verifies a User iButton's signed data on this Coprocessor iButton. The
	 * Coprocessor must recreate the signature based on the data in the file and the
	 * contents of the given scratchpad, and then match that with the signature
	 * passed in verify_mac.
	 * </P>
	 *
	 * @param pageData   the full 32 byte TMEX file from the User iButton (from
	 *                   <code>verifyAuthentication</code>) with the
	 * @param scratchpad the 32-byte scratchpad contents for which the signature is
	 *                   generated. This will contain parameters such as the user's
	 *                   write cycle counter for the page, the user's 1-wire
	 *                   address, and the page number where account data is stored.
	 * @param verify_mac the 20-byte buffer containing the signature the user had
	 *                   stored with the account data file.
	 *
	 * @return <code>true<code> if the data file is valid, <code>false</code> if an
	 *         error occurred (use <code>getLastError()</code> for more information
	 *         on the type of error)
	 *
	 * @throws OneWireIOException on a 1-Wire communication error such as reading an
	 *                            incorrect CRC from a 1-Wire device. This could be
	 *                            caused by a physical interruption in the 1-Wire
	 *                            Network due to shorts or a newly arriving 1-Wire
	 *                            device issuing a 'presence pulse'.
	 * @throws OneWireException   on a communication or setup error with the 1-Wire
	 *                            adapter
	 *
	 * @see #verifyAuthentication(byte[],byte[],byte[],byte[],byte)
	 * @see #getLastError()
	 */
	public boolean verifySignature(byte[] pageData, byte[] scratchpad, byte[] verify_mac)
			throws OneWireIOException, OneWireException {
		// clear any errors
		this.lastError = SHAiButtonCopr.NO_ERROR;

		// maintain local reference to container
		var ibcL = this.ibc;
		var addr = this.signPageNumber << 5;

		// now we are ready to make a signature
		if (!ibcL.writeDataPage(this.signPageNumber, pageData)) {
			this.lastError = SHAiButtonCopr.WRITE_DATA_PAGE_FAILED;
			ibcL.useResume(false);
			return false;
		}

		ibcL.useResume(true);
		if (!ibcL.writeScratchPad(0, 0, scratchpad, 0, 32)) {
			this.lastError = SHAiButtonCopr.WRITE_SCRATCHPAD_FAILED;
			ibcL.useResume(false);
			return false;
		}

		// \\//\\//\\//\\//\\//\\//\\////\\////\\////\\////\\////\\////\\//
		if (DEBUG) {
			IOHelper.writeLine("-----------------------------------------------------------");
			IOHelper.writeLine("COPR DEBUG - verifySignature");
			IOHelper.write("address: ");
			IOHelper.writeBytesHex(this.address, 0, 8);
			IOHelper.writeLine("speed: " + this.ibc.getAdapter().getSpeed());
			IOHelper.writeLine("-----------------------------------------------------------");
		}
		// \\//\\//\\//\\//\\//\\//\\////\\////\\////\\////\\////\\////\\//

		// sign that baby!
		if (ibcL.SHAFunction(OneWireContainer18.VALIDATE_DATA_PAGE, addr)) {
			if (ibcL.matchScratchPad(verify_mac)) {
				ibcL.useResume(false);
				return true;
			}
			this.lastError = SHAiButtonCopr.MATCH_SCRATCHPAD_FAILED;
		} else {
			this.lastError = SHAiButtonCopr.SHA_FUNCTION_FAILED;
		}

		ibcL.useResume(false);
		return false;
	}

	// ***********************************************************************
	// End SHA iButton Methods
	// ***********************************************************************

	/**
	 * Returns a string representing the 1-Wire address of this SHAiButton.
	 *
	 * @return a string containing the 8-byte address of this 1-Wire device.
	 */
	@Override
	public String toString() {
		return "COPR: " + this.ibc.getAddressAsString() + ", provider: " + this.providerName + ", version: "
				+ this.version;
	}

	/**
	 * Configuration helper. Used also by Coprocessor VM
	 */
	protected void fromStream(InputStream is) throws IOException {
		is.read(this.filename, 0, 5);

		this.signPageNumber = is.read();
		this.authPageNumber = is.read();
		this.wspcPageNumber = is.read();

		this.version = is.read();

		is.skip(4);// skip date info

		is.read(this.bindData, 0, 32);
		is.read(this.bindCode, 0, 7);
		is.read(this.signingChallenge, 0, 3);

		var namelen = is.read();
		var siglen = is.read();
		var auxlen = is.read();

		var l_providerName = new byte[namelen];
		is.read(l_providerName);
		this.providerName = new String(l_providerName);

		var cnt = Math.min(this.initialSignature.length, siglen);
		is.read(this.initialSignature, 0, cnt);

		var l_auxData = new byte[auxlen];
		is.read(l_auxData);
		this.auxData = new String(l_auxData);

		this.encCode = is.read();
		this.DS1961Scompatible = is.read() != 0;
	}

	/**
	 * Configuration saving method. Used also by Coprocessor VM
	 */
	protected void toStream(OutputStream os) throws IOException {
		// first part written is completely standard format
		os.write(this.filename, 0, 5);
		os.write(this.signPageNumber);
		os.write(this.authPageNumber);
		os.write(this.wspcPageNumber);
		os.write(this.version);

		// month, date, and year ignored
		os.write(1);
		os.write(1);
		os.write(0);
		os.write(100);

		os.write(this.bindData);
		os.write(this.bindCode);
		os.write(this.signingChallenge);

		var l_providerName = this.providerName.getBytes();
		var l_auxData = this.auxData.getBytes();
		os.write((byte) l_providerName.length);
		os.write((byte) this.initialSignature.length);
		os.write((byte) l_auxData.length);
		os.write(l_providerName, 0, (byte) l_providerName.length);
		os.write(this.initialSignature, 0, (byte) this.initialSignature.length);
		os.write(l_auxData, 0, (byte) l_auxData.length);
		os.write(this.encCode);
		os.write(this.DS1961Scompatible ? 0x55 : 0x00);

		os.flush();
	}

	// ***********************************************************************
	// Begin Static Utility Methods
	// ***********************************************************************

	/**
	 * <P>
	 * Static method that reformats the inputted authentication secret so it is
	 * compatible with the DS1961S. This means that for every group of 47 bytes in
	 * the secret, bytes at indices 32-35 and indices 44-46 are all set to 0xFF.
	 * Check the format for secret generation in the DS1961S data sheet to verify
	 * format of digest buffer.
	 * </P>
	 *
	 * <P>
	 * Note that if a coprocessor button uses this formatted secret, this function
	 * should be called for all user buttons including the DS1963S and DS1961S to
	 * ensure compatibility
	 * </P>
	 *
	 * @param auth_secret the authentication secret to be reformatted.
	 *
	 * @return a reformatted authentication secret, with the appropriate padding for
	 *         DS1961S interaction.
	 */
	public static byte[] reformatFor1961S(byte[] auth_secret) {
		var numPartials = auth_secret.length / 47 + 1;
		var new_secret = new byte[47 * numPartials];

		for (var i = 0; i < numPartials; i++) {
			var cnt = Math.min(auth_secret.length - i * 47, 47);
			System.arraycopy(auth_secret, i * 47, new_secret, i * 47, cnt);
			new_secret[i * 47 + 32] = (byte) 0xFF;
			new_secret[i * 47 + 33] = (byte) 0xFF;
			new_secret[i * 47 + 34] = (byte) 0xFF;
			new_secret[i * 47 + 35] = (byte) 0xFF;
			new_secret[i * 47 + 44] = (byte) 0xFF;
			new_secret[i * 47 + 45] = (byte) 0xFF;
			new_secret[i * 47 + 46] = (byte) 0xFF;
		}
		return new_secret;
	}
}
// CHECKSTYLE:ON
