PrometheusDataKeySet.java

/*
 * Prometheus: Application Framework
 * Copyright 2012-2026. Tony Washer
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License.  You may obtain a copy
 * of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package io.github.tonywasher.joceanus.prometheus.data;

import io.github.tonywasher.joceanus.gordianknot.api.base.GordianException;
import io.github.tonywasher.joceanus.gordianknot.api.keyset.GordianKeySet;
import io.github.tonywasher.joceanus.gordianknot.api.keyset.GordianKeySetFactory;
import io.github.tonywasher.joceanus.gordianknot.util.GordianUtilities;
import io.github.tonywasher.joceanus.metis.data.MetisDataResource;
import io.github.tonywasher.joceanus.metis.field.MetisFieldSet;
import io.github.tonywasher.joceanus.metis.field.MetisFieldVersionedSet;
import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataList.PrometheusListStyle;
import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataSet.PrometheusCryptographyDataType;
import io.github.tonywasher.joceanus.prometheus.exc.PrometheusDataException;
import io.github.tonywasher.joceanus.prometheus.exc.PrometheusSecurityException;

import java.util.Objects;

/**
 * ControlKey definition and list. The Control Key represents the passwordHash that controls
 * securing of the dataKeys. It maintains a map of the associated DataKeys.
 *
 * @author Tony Washer
 */
public class PrometheusDataKeySet
        extends PrometheusDataItem {
    /**
     * Object name.
     */
    public static final String OBJECT_NAME = PrometheusCryptographyDataType.DATAKEYSET.getItemName();

    /**
     * List name.
     */
    public static final String LIST_NAME = PrometheusCryptographyDataType.DATAKEYSET.getListName();

    /**
     * KeySetWrapLength.
     */
    public static final int WRAPLEN = GordianUtilities.getMaximumKeySetWrapLength();

    /**
     * Report fields.
     */
    private static final MetisFieldVersionedSet<PrometheusDataKeySet> FIELD_DEFS = MetisFieldVersionedSet.newVersionedFieldSet(PrometheusDataKeySet.class);

    /*
     * FieldIds.
     */
    static {
        FIELD_DEFS.declareLinkField(PrometheusCryptographyDataType.CONTROLKEYSET);
        FIELD_DEFS.declareByteArrayField(PrometheusDataResource.KEYSET_KEYSETDEF, WRAPLEN);
        FIELD_DEFS.declareDerivedVersionedField(PrometheusDataResource.KEYSET_KEYSET);
        FIELD_DEFS.declareDerivedVersionedField(PrometheusDataResource.KEYSET_ENCRYPTOR);
    }

    /**
     * Copy Constructor.
     *
     * @param pList   the list the copy belongs to
     * @param pSource The Key to copy
     */
    protected PrometheusDataKeySet(final PrometheusDataKeySetList pList,
                                   final PrometheusDataKeySet pSource) {
        /* Set standard values */
        super(pList, pSource);

        /* Switch on the LinkStyle */
        if (Objects.requireNonNull(getStyle()) == PrometheusListStyle.CLONE) {
            final GordianKeySet myKeySet = pSource.getKeySet();
            setValueKeySet(myKeySet);
            setValueEncryptor(pSource.getEncryptor());
        }
    }

    /**
     * Values constructor.
     *
     * @param pList   the List to add to
     * @param pValues the values constructor
     * @throws OceanusException on error
     */
    private PrometheusDataKeySet(final PrometheusDataKeySetList pList,
                                 final PrometheusDataValues pValues) throws OceanusException {
        /* Initialise the item */
        super(pList, pValues);

        /* Access the Password manager */
        final PrometheusDataSet myData = getDataSet();
        final OceanusDataFormatter myFormatter = myData.getDataFormatter();

        /* Store the ControlKey */
        Object myValue = pValues.getValue(PrometheusCryptographyDataType.CONTROLKEYSET);
        if (myValue instanceof Integer i) {
            /* Store the integer */
            setValueControlKeySet(i);

            /* Resolve the ControlKey */
            resolveDataLink(PrometheusCryptographyDataType.CONTROLKEYSET, myData.getControlKeySets());
        } else if (myValue instanceof PrometheusControlKeySet ks) {
            /* Store the controlKey */
            setValueControlKeySet(ks);
        }

        /* Access the controlKey */
        final PrometheusControlKeySet myControlKeySet = getControlKeySet();

        /* Store the WrappedKeySetDef */
        myValue = pValues.getValue(PrometheusDataResource.KEYSET_KEYSETDEF);
        if (myValue instanceof byte[] ba) {
            setValueSecuredKeySetDef(ba);
        }

        /* Store/Resolve the keySet */
        myValue = pValues.getValue(PrometheusDataResource.KEYSET_KEYSET);
        if (myValue instanceof GordianKeySet ks) {
            setValueKeySet(ks);
            setValueEncryptor(new PrometheusEncryptor(myFormatter, ks));
        } else if (getSecuredKeySetDef() != null) {
            /* Protect against exceptions */
            try {
                final GordianKeySet myKeySet = myControlKeySet.getKeySet().deriveKeySet(getSecuredKeySetDef());
                setValueKeySet(myKeySet);
                setValueEncryptor(new PrometheusEncryptor(myFormatter, myKeySet));
            } catch (GordianException e) {
                throw new PrometheusSecurityException(e);
            }
        }

        /* Register the DataKeySet */
        myControlKeySet.registerDataKeySet(this);
    }

    /**
     * Constructor for a new DataKeySet.
     *
     * @param pList          the list to which to add the keySet to
     * @param pControlKeySet the control keySet
     * @throws OceanusException on error
     */
    protected PrometheusDataKeySet(final PrometheusDataKeySetList pList,
                                   final PrometheusControlKeySet pControlKeySet) throws OceanusException {
        /* Initialise the item */
        super(pList, 0);

        /* Protect against exceptions */
        try {
            /* Store the Details */
            setValueControlKeySet(pControlKeySet);

            /* Access the Formatter */
            final PrometheusDataSet myData = getDataSet();
            final OceanusDataFormatter myFormatter = myData.getDataFormatter();

            /* Create the KeySet */
            final GordianKeySetFactory myKeySets = pControlKeySet.getSecurityFactory().getKeySetFactory();
            final GordianKeySet myKeySet = myKeySets.generateKeySet(getDataSet().getKeySetSpec());
            setValueKeySet(myKeySet);
            setValueEncryptor(new PrometheusEncryptor(myFormatter, myKeySet));

            /* Set the wrappedKeySetDef */
            setValueSecuredKeySetDef(pControlKeySet.getKeySet().secureKeySet(myKeySet));

            /* Catch Exceptions */
        } catch (GordianException
                 | OceanusException e) {
            /* Pass on exception */
            throw new PrometheusDataException(this, ERROR_CREATEITEM, e);
        }
    }

    @Override
    public MetisFieldSetDef getDataFieldSet() {
        return FIELD_DEFS;
    }

    /**
     * Get the ControlKey.
     *
     * @return the controlKey
     */
    public final PrometheusControlKey getControlKey() {
        return getControlKeySet().getControlKey();
    }

    /**
     * Get the ControlKeySet.
     *
     * @return the controlKeySet
     */
    public final PrometheusControlKeySet getControlKeySet() {
        return getValues().getValue(PrometheusCryptographyDataType.CONTROLKEYSET, PrometheusControlKeySet.class);
    }

    /**
     * Get the ControlKeyId for this item.
     *
     * @return the ControlKeyId
     */
    public Integer getControlKeySetId() {
        final PrometheusControlKeySet myKeySet = getControlKeySet();
        return myKeySet == null
                ? null
                : myKeySet.getIndexedId();
    }

    /**
     * Get the securedKeySetDef.
     *
     * @return the securedKeySetDef
     */
    public final byte[] getSecuredKeySetDef() {
        return getValues().getValue(PrometheusDataResource.KEYSET_KEYSETDEF, byte[].class);
    }

    /**
     * Get the KeySet.
     *
     * @return the keySet
     */
    public GordianKeySet getKeySet() {
        return getValues().getValue(PrometheusDataResource.KEYSET_KEYSET, GordianKeySet.class);
    }

    /**
     * Get the Encryptor.
     *
     * @return the encryptor
     */
    public PrometheusEncryptor getEncryptor() {
        return getValues().getValue(PrometheusDataResource.KEYSET_ENCRYPTOR, PrometheusEncryptor.class);
    }

    /**
     * Set the ControlKeySet Id.
     *
     * @param pId the controlKeySet id
     * @throws OceanusException on error
     */
    private void setValueControlKeySet(final Integer pId) throws OceanusException {
        getValues().setValue(PrometheusCryptographyDataType.CONTROLKEYSET, pId);
    }

    /**
     * Set the ControlKeySet.
     *
     * @param pKeySet the controlKeySet
     * @throws OceanusException on error
     */
    private void setValueControlKeySet(final PrometheusControlKeySet pKeySet) throws OceanusException {
        getValues().setValue(PrometheusCryptographyDataType.CONTROLKEYSET, pKeySet);
    }

    /**
     * Set the securedKeySetDef.
     *
     * @param pValue the securedKeySetDef
     */
    private void setValueSecuredKeySetDef(final byte[] pValue) {
        getValues().setUncheckedValue(PrometheusDataResource.KEYSET_KEYSETDEF, pValue);
    }

    /**
     * Set the keySet.
     *
     * @param pValue the keySet
     */
    private void setValueKeySet(final GordianKeySet pValue) {
        getValues().setUncheckedValue(PrometheusDataResource.KEYSET_KEYSET, pValue);
    }

    /**
     * Set the encryptor.
     *
     * @param pValue the encryptor
     */
    private void setValueEncryptor(final PrometheusEncryptor pValue) {
        getValues().setUncheckedValue(PrometheusDataResource.KEYSET_ENCRYPTOR, pValue);
    }

    @Override
    public PrometheusDataKeySet getBase() {
        return (PrometheusDataKeySet) super.getBase();
    }

    @Override
    public PrometheusDataKeySetList getList() {
        return (PrometheusDataKeySetList) super.getList();
    }

    @Override
    public int compareValues(final PrometheusDataItem pThat) {
        /* Only sort on id */
        return 0;
    }

    @Override
    public void resolveDataSetLinks() throws OceanusException {
        /* Resolve the ControlKey */
        final PrometheusDataSet myData = getDataSet();
        resolveDataLink(PrometheusCryptographyDataType.CONTROLKEYSET, myData.getControlKeySets());
        final PrometheusControlKeySet myControlKeySet = getControlKeySet();

        /* Register the KeySet */
        myControlKeySet.registerDataKeySet(this);
    }

    /**
     * Delete the old set of DataKeySet and DataKeys.
     */
    protected void deleteDataKeySet() {
        /* Mark this dataKeySet as deleted */
        setDeleted(true);
    }

    /**
     * DataKeySet List.
     */
    public static class PrometheusDataKeySetList
            extends PrometheusDataList<PrometheusDataKeySet> {
        /**
         * Report fields.
         */
        private static final MetisFieldSet<PrometheusDataKeySetList> FIELD_DEFS = MetisFieldSet.newFieldSet(PrometheusDataKeySetList.class);

        /**
         * Construct an empty CORE list.
         *
         * @param pData the DataSet for the list
         */
        protected PrometheusDataKeySetList(final PrometheusDataSet pData) {
            this(pData, PrometheusListStyle.CORE);
        }

        /**
         * Construct an empty generic ControlKey list.
         *
         * @param pData  the DataSet for the list
         * @param pStyle the style of the list
         */
        protected PrometheusDataKeySetList(final PrometheusDataSet pData,
                                           final PrometheusListStyle pStyle) {
            super(PrometheusDataKeySet.class, pData, PrometheusCryptographyDataType.DATAKEYSET, pStyle);
        }

        /**
         * Constructor for a cloned List.
         *
         * @param pSource the source List
         */
        private PrometheusDataKeySetList(final PrometheusDataKeySetList pSource) {
            super(pSource);
        }

        @Override
        public MetisFieldSet<PrometheusDataKeySetList> getDataFieldSet() {
            return FIELD_DEFS;
        }

        @Override
        public String listName() {
            return LIST_NAME;
        }

        @Override
        public MetisFieldSet<PrometheusDataKeySet> getItemFields() {
            return PrometheusDataKeySet.FIELD_DEFS;
        }

        @Override
        public boolean includeDataXML() {
            return false;
        }

        @Override
        protected PrometheusDataKeySetList getEmptyList(final PrometheusListStyle pStyle) {
            final PrometheusDataKeySetList myList = new PrometheusDataKeySetList(this);
            myList.setStyle(pStyle);
            return myList;
        }

        @Override
        public PrometheusDataKeySetList deriveList(final PrometheusListStyle pStyle) throws OceanusException {
            return (PrometheusDataKeySetList) super.deriveList(pStyle);
        }

        @Override
        public PrometheusDataKeySetList deriveDifferences(final PrometheusDataSet pDataSet,
                                                          final PrometheusDataList<?> pOld) {
            return (PrometheusDataKeySetList) super.deriveDifferences(pDataSet, pOld);
        }

        @Override
        public PrometheusDataKeySet addCopyItem(final PrometheusDataItem pItem) {
            /* Can only clone a DataKeySet */
            if (!(pItem instanceof PrometheusDataKeySet)) {
                return null;
            }

            /* Clone the data key set */
            final PrometheusDataKeySet mySet = new PrometheusDataKeySet(this, (PrometheusDataKeySet) pItem);
            add(mySet);
            return mySet;
        }

        @Override
        public PrometheusDataKeySet addNewItem() {
            throw new UnsupportedOperationException();
        }

        @Override
        public PrometheusDataKeySet addValuesItem(final PrometheusDataValues pValues) throws OceanusException {
            /* Create the dataKeySet */
            final PrometheusDataKeySet mySet = new PrometheusDataKeySet(this, pValues);

            /* Check that this keyId has not been previously added */
            if (!isIdUnique(mySet.getIndexedId())) {
                mySet.addError(ERROR_DUPLICATE, MetisDataResource.DATA_ID);
                throw new PrometheusDataException(mySet, ERROR_VALIDATION);
            }

            /* Add to the list */
            add(mySet);

            /* Return it */
            return mySet;
        }

        /**
         * Clone KeySet from a DataBase.
         *
         * @param pControlKeySet the ControlKeySet to clone
         * @param pKeySet        the DataKeySet to clone
         * @return the new DataKeySet
         * @throws OceanusException on error
         */
        protected PrometheusDataKeySet cloneDataKeySet(final PrometheusControlKeySet pControlKeySet,
                                                       final PrometheusDataKeySet pKeySet) throws OceanusException {
            /* Build data values */
            final PrometheusDataValues myValues = new PrometheusDataValues(OBJECT_NAME);
            myValues.addValue(MetisDataResource.DATA_ID, pKeySet.getIndexedId());
            myValues.addValue(PrometheusCryptographyDataType.CONTROLKEYSET, pControlKeySet);
            myValues.addValue(PrometheusDataResource.KEYSET_KEYSETDEF, pKeySet.getSecuredKeySetDef());
            myValues.addValue(PrometheusDataResource.KEYSET_KEYSET, pKeySet.getKeySet());

            /* Clone the dataKeySet */
            return addValuesItem(myValues);
        }

        @Override
        public void postProcessOnLoad() throws OceanusException {
            /* Just sort the list */
            reSort();
        }

        @Override
        protected PrometheusDataMapItem allocateDataMap() {
            /* Unused */
            throw new UnsupportedOperationException();
        }
    }
}