// 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 com.dalsemi.onewire.OneWireException;
import com.dalsemi.onewire.adapter.DSPortAdapter;
import com.dalsemi.onewire.adapter.OneWireIOException;
import com.dalsemi.onewire.application.file.OWFile;
import com.dalsemi.onewire.container.OneWireContainer;
import com.dalsemi.onewire.utils.Address;

/**
 * <P>
 * The abstract superclass for all users of a SHAiButton transaction system. The
 * user in a SHA transaction system mainly consists of a page (or pages) of
 * account data. The abstract superclass guarantees an interface for retrieving
 * information about account data, as well as reading and writing that data.
 * <P>
 *
 * <P>
 * <code>SHAiButtonUser</code> was defined for use with the DS1963S (family code
 * 0x18) and the DS1961S (family code 0x33). The benefit to using our SHA
 * iButtons in a transaction is for device authentication. Using a
 * challenge-response protocol, the DS1961S and the DS1963S can authenticate
 * themselves to the system before their actual account data is verified. But,
 * if device authentication isn't important to your transaction, the
 * <code>SHAiButtonUser</code> can be extended to support any 1-Wire memory
 * device.
 * </P>
 *
 * <P>
 * The format of user's account data is not specified by this class, only the
 * interface for accessing it. Each different SHATransaction is free to
 * implement whatever format is appropriate for the transaction type.
 * </P>
 *
 * @see SHATransaction
 * @see SHAiButtonCopr
 * @see SHAiButtonUser18
 * @see SHAiButtonUser33
 *
 * @version 1.00
 * @author SKH
 */
public abstract class SHAiButtonUser {
	/**
	 * Turns on extra debugging output
	 */
	static final boolean DEBUG = false;

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

	/**
	 * local cache of accountData
	 */
	protected final byte[] accountData = new byte[32];

	/**
	 * page number account data is stored on
	 */
	protected int accountPageNumber = -1;

	/**
	 * used to construct appropriate string for OWFile constructor
	 */
	protected final byte[] serviceFile = { (byte) 'D', (byte) 'L', (byte) 'S', (byte) 'M' };
	/**
	 * stores string name of user's service file
	 */
	protected String strServiceFilename = null;

	/**
	 * maintains a cache of the fullBindCode, for later binding of coprocessor.
	 */
	protected final byte[] fullBindCode = new byte[15];

	/**
	 * local cache of writeCycleCounter for data page
	 */
	protected int writeCycleCounter = -1;

	/**
	 * force 1-wire container into overdrive speed
	 */
	protected boolean forceOverdrive = false;

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

	/**
	 * <P>
	 * Returns the page number of the first memory page where account data is
	 * stored.
	 * </P>
	 *
	 * @return page number where account data is stored.
	 */
	public int getAccountPageNumber() {
		return this.accountPageNumber;
	}

	/**
	 * <P>
	 * Returns the TMEX filename of the user's account data file.
	 * </P>
	 *
	 * @return filename of user's account data file
	 */
	public String getAccountFilename() {
		return this.strServiceFilename;
	}

	/**
	 * <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);
	}

	/**
	 * Sets whether or not the container should be forced into overdrive.
	 *
	 * @param value if true, the container will be forced to overdrive
	 */
	public void setForceOverdrive(boolean value) {
		this.forceOverdrive = value;
	}

	/**
	 * Reports whether or not the container is forced into overdrive.
	 *
	 * @return if true, the container will be forced to overdrive
	 */
	public boolean getForceOverdrive() {
		return this.forceOverdrive;
	}

	/**
	 * <P>
	 * Create's empty service file for the user with the given filename. Also, it
	 * populates <code>accountPageNumber</code> with the page that the service file
	 * is stored on.
	 * </P>
	 *
	 * @param owc          the 1-wire device where the service file will be created.
	 * @param filename     the filename of the service file.
	 * @param formatDevice if <code>true</code>, the device is formatted before
	 *                     creating the service file.
	 */
	protected boolean createServiceFile(OneWireContainer owc, String filename, boolean formatDevice) {
		var bRetVal = false;
		var owf = new OWFile(owc, this.strServiceFilename);
		try {
			if (formatDevice) {
				owf.format();
			}

			if (!owf.exists()) {
				owf.createNewFile();
			}

			// save reference to page number for service file
			this.accountPageNumber = owf.getPageList()[0];

			bRetVal = true;
		} catch (IOException ioe) {
			bRetVal = false;
			// \\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\
			if (DEBUG) {
				ioe.printStackTrace();
			}
			// \\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\
		}

		try {
			owf.close();
			return bRetVal;
		} catch (IOException ioe) {
			// \\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\
			if (DEBUG) {
				ioe.printStackTrace();
			}
			// \\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\
			/* well, at least I tried! */
			return false;
		}
	}

	/**
	 * <P>
	 * Asserts that proper account info exists for this SHAiButtonUser and
	 * establishes a buffer for caching account data.
	 * </P>
	 *
	 * @param ibc the OneWireContainer whose account info is checked
	 * @return whether or not the device is initialized properly
	 */
	protected synchronized boolean checkAccountPageInfo(OneWireContainer ibc) {
		// this flag should only be set if there is valid data
		if (this.accountPageNumber <= 0) {
			try {
				// create a file object representing service file
				var owf = new OWFile(ibc, this.strServiceFilename);

				// check to see if file exists
				if (!owf.exists()) {
					return false;
				}

				// get the page number for the file
				// this.accountPageNumber = owf.getPageList()[0];
				this.accountPageNumber = owf.getStartPage();

				// close the file
				owf.close();

				// mark the cache as dirty
				this.accountData[0] = 0;

				// clear the write cycle counter
				this.writeCycleCounter = -1;

			} catch (Exception e) {
				this.accountPageNumber = -1;
				if (DEBUG) {
					e.printStackTrace();
				}
			}
		}

		return this.accountPageNumber > 0;
	}

	// *************************************************************** //
	// Begin Abstract Methods for SHAiButtonUser //
	// *************************************************************** //

	/**
	 * <P>
	 * Modifies this SHA iButton so that it refers to another device. If this object
	 * already has an appropriate instance of OneWireContainer, that instance is
	 * updated with the new address.
	 * </P>
	 *
	 * @param adapter The adapter that the device can be found on.
	 * @param address The address of the 1-Wire device
	 *
	 * @return <code>true</code> if a valid account service file exists on this
	 *         <code>OneWireContainer18</code>.
	 *
	 * @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
	 */
	public abstract boolean setiButtonUser(DSPortAdapter adapter, byte[] address)
			throws OneWireException, OneWireIOException;

	/**
	 * <P>
	 * Modifies this SHA iButton so that it refers to another device. If this object
	 * does not already has an appropriate instance of OneWireContainer, it returns
	 * false immediately, because there is no adapter info available. Otherwise, it
	 * reuses the same adapter.
	 * </P>
	 *
	 * @param address The address of the 1-Wire device
	 *
	 * @return <code>true</code> if a valid account service file exists on this
	 *         <code>OneWireContainer18</code>.
	 *
	 * @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
	 */
	public abstract boolean setiButtonUser(byte[] address) throws OneWireException, OneWireIOException;

	/**
	 * <P>
	 * Returns the value of the write cycle counter for the page where the account
	 * data is stored. If the write cycle counter has ever been retrieved, this
	 * returns the cached value. Otherwise, this method reads the value from the
	 * part.
	 * </P>
	 *
	 * <P>
	 * For devices that do not support write cycle counters, this method always
	 * returns -1.
	 * </P>
	 *
	 * @return the value of the write cycle counter for the account data page.
	 *
	 * @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
	 */
	public abstract int getWriteCycleCounter() throws OneWireException, OneWireIOException;

	/**
	 * <P>
	 * Returns <code>true</code> if this buttons account data is stored on a page
	 * that has a write cycle counter.
	 * </P>
	 *
	 * @return <code>true</code> if account page has write cycle counter.
	 */
	public abstract boolean hasWriteCycleCounter();

	/**
	 * <P>
	 * This function creates the full 15-byte binding data for the coprocessor to
	 * use to recreate this user's secret on the copr's workspace page. This
	 * function is located in the SHAiButtonUser class to support binding codes for
	 * user buttons who use alternate techniques (such as the DS1961S) for secret
	 * computation.
	 * </P>
	 *
	 * @param bindCode     the 7-byte binding code from coprocessor's service file
	 * @param fullBindCode the 15-byte full binding code to to be copied into the
	 *                     coprocessor's scratchpad. There should be 15 bytes
	 *                     available starting from the offset.
	 * @param offset       the offset into fullBindCode where copying should begin.
	 *
	 */
	public abstract void getFullBindCode(byte[] l_fullBindCode, int offset);

	/**
	 * <P>
	 * Returns a byte representing the appropriate authorization command for the
	 * coprocessor to use to authenticate this user. For a DS1961S, the
	 * authentication command is AUTH_HOST, but for a DS1963S, the authentication
	 * command is VALIDATE_PAGE.
	 * </P>
	 *
	 * @return byte indicating appropriate command for authenticating user
	 *
	 */
	public abstract byte getAuthorizationCommand();

	/**
	 * <P>
	 * Writes the account data to the SHAiButton. First, this function asserts that
	 * the account page number is known. The account data is copied from dataBuffer
	 * starting at the offset. If there are less than 32 bytes available to copy,
	 * this function only copies the bytes that are available.
	 * </P>
	 *
	 * @param dataBuffer the buffer to copy the account data from
	 * @param offset     the index into the buffer where copying should begin
	 * @return whether or not the data write succeeded
	 *
	 * @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
	 */
	public abstract boolean writeAccountData(byte[] dataBuffer, int offset) throws OneWireException, OneWireIOException;

	/**
	 * <P>
	 * Reads the account data off the SHAiButton using a standard READ command.
	 * First, this function asserts that the account page number is known as well as
	 * the length of the account file. The 32 byte account data page is copied into
	 * dataBuffer starting at the given offset.
	 * </P>
	 *
	 * @param dataBuffer the buffer to copy the account data into
	 * @param offset     the index into the buffer where copying should begin
	 * @return whether or not the read was successful
	 *
	 * @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
	 */
	public abstract boolean readAccountData(byte[] dataBuffer, int offset) throws OneWireException, OneWireIOException;

	/**
	 * <P>
	 * Reads the account data off the SHAiButton using a READ_AUTHENTICATE command.
	 * First, this function asserts that the account page number is known as well as
	 * the length of the account file. Then it copies the 3 byte challenge to the
	 * scratchpad before sending the command for READ_AUTHENTICATE. The 32 byte
	 * account data page is copied into dataBuffer starting at dataStart.
	 * </P>
	 *
	 * <P>
	 * In addition to the account data, this function also returns a calculated MAC.
	 * The MAC requires 20 bytes after the start index. The return value is the
	 * write cycle counter value for the account data page
	 * <p>
	 *
	 * @param chlg       the buffer containing a 3-byte random challenge.
	 * @param chlgStart  the index into the buffer where the 3 byte challenge
	 *                   begins.
	 * @param dataBuffer the buffer to copy the account data into
	 * @param dataStart  the index into the buffer where copying should begin
	 * @param mac        the buffer to copy the resulting Message Authentication
	 *                   Code
	 * @param macStart   the index into the mac buffer to start copying
	 *
	 * @return the value of the write cycle counter for the page
	 *
	 * @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
	 */
	public abstract int readAccountData(byte[] chlg, int chlgStart, byte[] dataBuffer, int dataStart, byte[] mac,
			int macStart) throws OneWireException, OneWireIOException;

	// *************************************************************** //
	// End Abstract Methods for SHAiButtonUser //
	// *************************************************************** //

	/**
	 * Refreshes eeprom SHA devices in case of weakly-programmed bits on the account
	 * page.
	 *
	 * @return true if the refresh was successful
	 */
	public boolean refreshDevice() throws OneWireException, OneWireIOException {
		// no-op by default
		return true;
	}

	/**
	 * <P>
	 * Returns a string representing this SHAiButton.
	 * </P>
	 *
	 * @return a string containing the 8-byte address of this 1-Wire device.
	 */
	@Override
	public String toString() {
		var sb = new StringBuilder(100);
		sb.append("USER: ");
		sb.append(Address.toString(this.address));
		sb.append(", service: ");
		sb.append(this.strServiceFilename);
		sb.append(", acctPageNum: ");
		sb.append(this.accountPageNumber);
		return sb.toString();
	}
}
// CHECKSTYLE:ON
