// 
// Decompiled by Procyon v0.6.0
// 

package com.google.crypto.tink;

import com.google.errorprone.annotations.Immutable;
import com.google.crypto.tink.internal.MutableKeyCreationRegistry;
import com.google.crypto.tink.annotations.Alpha;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.crypto.tink.internal.MutableSerializationRegistry;
import com.google.crypto.tink.proto.OutputPrefixType;
import com.google.crypto.tink.internal.ProtoKeySerialization;
import com.google.errorprone.annotations.InlineMe;
import com.google.crypto.tink.internal.InternalConfiguration;
import com.google.protobuf.ByteString;
import com.google.protobuf.ExtensionRegistryLite;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.crypto.tink.proto.EncryptedKeyset;
import java.io.IOException;
import com.google.crypto.tink.tinkkey.KeyAccess;
import com.google.crypto.tink.proto.KeysetInfo;
import com.google.crypto.tink.proto.KeyData;
import com.google.crypto.tink.tinkkey.TinkKey;
import com.google.crypto.tink.tinkkey.internal.InternalKeyHandle;
import com.google.crypto.tink.tinkkey.internal.ProtoKey;
import com.google.crypto.tink.tinkkey.KeyHandle;
import com.google.crypto.tink.internal.TinkBugException;
import com.google.crypto.tink.internal.MonitoringClient;
import com.google.crypto.tink.internal.MutableMonitoringRegistry;
import java.util.Set;
import java.util.HashSet;
import com.google.crypto.tink.internal.MutableParametersRegistry;
import java.util.Iterator;
import java.util.Collections;
import java.security.GeneralSecurityException;
import com.google.crypto.tink.internal.LegacyProtoKey;
import com.google.crypto.tink.config.GlobalTinkFlags;
import java.util.ArrayList;
import com.google.crypto.tink.proto.Keyset;
import com.google.crypto.tink.proto.KeyStatusType;
import javax.annotation.Nullable;
import com.google.crypto.tink.internal.MonitoringAnnotations;
import java.util.List;
import com.google.crypto.tink.internal.KeysetHandleInterface;

public final class KeysetHandle implements KeysetHandleInterface
{
    private final List<Entry> entries;
    private final MonitoringAnnotations annotations;
    @Nullable
    private final KeysetHandle unmonitoredHandle;
    
    private static KeyStatus parseStatusWithDisabledFallback(final KeyStatusType in) {
        switch (in) {
            case ENABLED: {
                return KeyStatus.ENABLED;
            }
            case DESTROYED: {
                return KeyStatus.DESTROYED;
            }
            default: {
                return KeyStatus.DISABLED;
            }
        }
    }
    
    private static boolean isValidKeyStatusType(final KeyStatusType in) {
        switch (in) {
            case ENABLED:
            case DESTROYED:
            case DISABLED: {
                return true;
            }
            default: {
                return false;
            }
        }
    }
    
    private static KeyStatusType serializeStatus(final KeyStatus in) {
        if (KeyStatus.ENABLED.equals(in)) {
            return KeyStatusType.ENABLED;
        }
        if (KeyStatus.DISABLED.equals(in)) {
            return KeyStatusType.DISABLED;
        }
        if (KeyStatus.DESTROYED.equals(in)) {
            return KeyStatusType.DESTROYED;
        }
        throw new IllegalStateException("Unknown key status");
    }
    
    private static List<Entry> getEntriesFromKeyset(final Keyset keyset) throws GeneralSecurityException {
        final List<Entry> result = new ArrayList<Entry>(keyset.getKeyCount());
        for (final Keyset.Key protoKey : keyset.getKeyList()) {
            final int id = protoKey.getKeyId();
            Key key;
            boolean keyParsingFailed;
            try {
                key = toKey(protoKey);
                keyParsingFailed = false;
            }
            catch (final GeneralSecurityException e) {
                if (GlobalTinkFlags.validateKeysetsOnParsing.getValue()) {
                    throw e;
                }
                key = new LegacyProtoKey(toProtoKeySerialization(protoKey), InsecureSecretKeyAccess.get());
                keyParsingFailed = true;
            }
            if (GlobalTinkFlags.validateKeysetsOnParsing.getValue() && !isValidKeyStatusType(protoKey.getStatus())) {
                throw new GeneralSecurityException("Parsing of a single key failed (wrong status) and Tink is configured via validateKeysetsOnParsing to reject such keysets.");
            }
            final int x2;
            result.add(new Entry(key, protoKey.getStatus(), x2, (x2 = id) == keyset.getPrimaryKeyId(), keyParsingFailed, Entry.NO_LOGGING));
        }
        return Collections.unmodifiableList((List<? extends Entry>)result);
    }
    
    private Entry entryByIndex(final int i) {
        final Entry entry = this.entries.get(i);
        if (!isValidKeyStatusType(entry.keyStatusType)) {
            throw new IllegalStateException("Keyset-Entry at position " + i + " has wrong status");
        }
        if (entry.keyParsingFailed) {
            throw new IllegalStateException("Keyset-Entry at position " + i + " didn't parse correctly");
        }
        return this.entries.get(i);
    }
    
    public static Builder.Entry importKey(final Key key) {
        final Builder.Entry importedEntry = new Builder.Entry(key);
        final Integer requirement = key.getIdRequirementOrNull();
        if (requirement != null) {
            importedEntry.withFixedId(requirement);
        }
        return importedEntry;
    }
    
    public static Builder.Entry generateEntryFromParametersName(final String parametersName) throws GeneralSecurityException {
        final Parameters parameters = MutableParametersRegistry.globalInstance().get(parametersName);
        return new Builder.Entry(parameters);
    }
    
    public static Builder.Entry generateEntryFromParameters(final Parameters parameters) {
        return new Builder.Entry(parameters);
    }
    
    private KeysetHandle getUnmonitoredHandle() {
        return (this.unmonitoredHandle == null) ? this : this.unmonitoredHandle;
    }
    
    private static void validateNoDuplicateIds(final List<Entry> entries) throws GeneralSecurityException {
        final Set<Integer> idsSoFar = new HashSet<Integer>();
        boolean foundPrimary = false;
        for (final Entry e : entries) {
            if (idsSoFar.contains(e.getId())) {
                throw new GeneralSecurityException("KeyID " + e.getId() + " is duplicated in the keyset, and Tink is configured to reject such keysets with the flag validateKeysetsOnParsing.");
            }
            idsSoFar.add(e.getId());
            if (!e.isPrimary()) {
                continue;
            }
            foundPrimary = true;
        }
        if (!foundPrimary) {
            throw new GeneralSecurityException("Primary key id not found in keyset, and Tink is configured to reject such keysets with the flag validateKeysetsOnParsing.");
        }
    }
    
    private KeysetHandle(final List<Entry> entries, final MonitoringAnnotations annotations) throws GeneralSecurityException {
        this.entries = entries;
        this.annotations = annotations;
        if (GlobalTinkFlags.validateKeysetsOnParsing.getValue()) {
            validateNoDuplicateIds(entries);
        }
        this.unmonitoredHandle = null;
    }
    
    private KeysetHandle(final List<Entry> entries, final MonitoringAnnotations annotations, final KeysetHandle unmonitoredHandle) {
        this.entries = entries;
        this.annotations = annotations;
        this.unmonitoredHandle = unmonitoredHandle;
    }
    
    private static KeysetHandle addMonitoringIfNeeded(final KeysetHandle unmonitoredHandle) {
        final MonitoringAnnotations annotations = unmonitoredHandle.annotations;
        if (annotations.isEmpty()) {
            return unmonitoredHandle;
        }
        final Entry.EntryConsumer keyExportLogger = entryToLog -> {
            final MonitoringClient client = MutableMonitoringRegistry.globalInstance().getMonitoringClient();
            client.createLogger(unmonitoredHandle, annotations, "keyset_handle", "get_key").logKeyExport(entryToLog.getId());
            return;
        };
        final List<Entry> monitoredEntries = new ArrayList<Entry>(unmonitoredHandle.entries.size());
        for (final Entry e : unmonitoredHandle.entries) {
            monitoredEntries.add(new Entry(e.key, e.keyStatusType, e.id, e.isPrimary, e.keyParsingFailed, keyExportLogger));
        }
        return new KeysetHandle(monitoredEntries, annotations, unmonitoredHandle);
    }
    
    static final KeysetHandle fromKeyset(final Keyset keyset) throws GeneralSecurityException {
        assertEnoughKeyMaterial(keyset);
        final List<Entry> entries = getEntriesFromKeyset(keyset);
        return new KeysetHandle(entries, MonitoringAnnotations.EMPTY);
    }
    
    static final KeysetHandle fromKeysetAndAnnotations(final Keyset keyset, final MonitoringAnnotations annotations) throws GeneralSecurityException {
        assertEnoughKeyMaterial(keyset);
        final List<Entry> entries = getEntriesFromKeyset(keyset);
        return addMonitoringIfNeeded(new KeysetHandle(entries, annotations));
    }
    
    Keyset getKeyset() {
        try {
            final Keyset.Builder builder = Keyset.newBuilder();
            for (final Entry entry : this.entries) {
                final Keyset.Key protoKey = createKeysetKey(entry.getKey(), entry.keyStatusType, entry.getId());
                builder.addKey(protoKey);
                if (entry.isPrimary()) {
                    builder.setPrimaryKeyId(entry.getId());
                }
            }
            return builder.build();
        }
        catch (final GeneralSecurityException e) {
            throw new TinkBugException(e);
        }
    }
    
    public static Builder newBuilder() {
        return new Builder();
    }
    
    public static Builder newBuilder(final KeysetHandle handle) {
        final Builder builder = new Builder();
        for (int i = 0; i < handle.size(); ++i) {
            Entry entry;
            try {
                entry = handle.getAt(i);
            }
            catch (final IllegalStateException e) {
                builder.setErrorToThrow(new GeneralSecurityException("Keyset-Entry in original keyset at position " + i + " has wrong status or key parsing failed", e));
                break;
            }
            final Builder.Entry builderEntry = importKey(entry.getKey()).withFixedId(entry.getId());
            builderEntry.setStatus(entry.getStatus());
            if (entry.isPrimary()) {
                builderEntry.makePrimary();
            }
            builder.addEntry(builderEntry);
        }
        return builder;
    }
    
    @Override
    public Entry getPrimary() {
        for (final Entry entry : this.entries) {
            if (entry != null && entry.isPrimary()) {
                if (entry.getStatus() != KeyStatus.ENABLED) {
                    throw new IllegalStateException("Keyset has primary which isn't enabled");
                }
                return entry;
            }
        }
        throw new IllegalStateException("Keyset has no valid primary");
    }
    
    @Override
    public int size() {
        return this.entries.size();
    }
    
    @Override
    public Entry getAt(final int i) {
        if (i < 0 || i >= this.size()) {
            throw new IndexOutOfBoundsException("Invalid index " + i + " for keyset of size " + this.size());
        }
        return this.entryByIndex(i);
    }
    
    @Deprecated
    public List<KeyHandle> getKeys() {
        final ArrayList<KeyHandle> result = new ArrayList<KeyHandle>();
        final Keyset keyset = this.getKeyset();
        for (final Keyset.Key key : keyset.getKeyList()) {
            final KeyData keyData = key.getKeyData();
            result.add(new InternalKeyHandle(new ProtoKey(keyData, KeyTemplate.fromProto(key.getOutputPrefixType())), key.getStatus(), key.getKeyId()));
        }
        return Collections.unmodifiableList((List<? extends KeyHandle>)result);
    }
    
    @Deprecated
    public KeysetInfo getKeysetInfo() {
        final Keyset keyset = this.getKeyset();
        return Util.getKeysetInfo(keyset);
    }
    
    public static final KeysetHandle generateNew(final Parameters parameters) throws GeneralSecurityException {
        return newBuilder().addEntry(generateEntryFromParameters(parameters).withRandomId().makePrimary()).build();
    }
    
    @Deprecated
    public static final KeysetHandle generateNew(final com.google.crypto.tink.proto.KeyTemplate keyTemplate) throws GeneralSecurityException {
        return generateNew(TinkProtoParametersFormat.parse(keyTemplate.toByteArray()));
    }
    
    public static final KeysetHandle generateNew(final KeyTemplate keyTemplate) throws GeneralSecurityException {
        return generateNew(keyTemplate.toParameters());
    }
    
    @Deprecated
    public static final KeysetHandle createFromKey(final KeyHandle keyHandle, final KeyAccess access) throws GeneralSecurityException {
        final KeysetManager km = KeysetManager.withEmptyKeyset().add(keyHandle);
        km.setPrimary(km.getKeysetHandle().getKeysetInfo().getKeyInfo(0).getKeyId());
        return km.getKeysetHandle();
    }
    
    @Deprecated
    public static final KeysetHandle read(final KeysetReader reader, final Aead masterKey) throws GeneralSecurityException, IOException {
        return readWithAssociatedData(reader, masterKey, new byte[0]);
    }
    
    @Deprecated
    public static final KeysetHandle readWithAssociatedData(final KeysetReader reader, final Aead masterKey, final byte[] associatedData) throws GeneralSecurityException, IOException {
        final EncryptedKeyset encryptedKeyset = reader.readEncrypted();
        assertEnoughEncryptedKeyMaterial(encryptedKeyset);
        return fromKeyset(decrypt(encryptedKeyset, masterKey, associatedData));
    }
    
    @Deprecated
    public static final KeysetHandle readNoSecret(final KeysetReader reader) throws GeneralSecurityException, IOException {
        byte[] serializedKeyset;
        try {
            serializedKeyset = reader.read().toByteArray();
        }
        catch (final InvalidProtocolBufferException e) {
            throw new GeneralSecurityException("invalid keyset");
        }
        return readNoSecret(serializedKeyset);
    }
    
    @Deprecated
    public static final KeysetHandle readNoSecret(final byte[] serialized) throws GeneralSecurityException {
        try {
            final Keyset keyset = Keyset.parseFrom(serialized, ExtensionRegistryLite.getEmptyRegistry());
            assertNoSecretKeyMaterial(keyset);
            return fromKeyset(keyset);
        }
        catch (final InvalidProtocolBufferException e) {
            throw new GeneralSecurityException("invalid keyset");
        }
    }
    
    @Deprecated
    public void write(final KeysetWriter keysetWriter, final Aead masterKey) throws GeneralSecurityException, IOException {
        this.writeWithAssociatedData(keysetWriter, masterKey, new byte[0]);
    }
    
    @Deprecated
    public void writeWithAssociatedData(final KeysetWriter keysetWriter, final Aead masterKey, final byte[] associatedData) throws GeneralSecurityException, IOException {
        final Keyset keyset = this.getKeyset();
        final EncryptedKeyset encryptedKeyset = encrypt(keyset, masterKey, associatedData);
        keysetWriter.write(encryptedKeyset);
    }
    
    @Deprecated
    public void writeNoSecret(final KeysetWriter writer) throws GeneralSecurityException, IOException {
        final Keyset keyset = this.getKeyset();
        assertNoSecretKeyMaterial(keyset);
        writer.write(keyset);
    }
    
    private static EncryptedKeyset encrypt(final Keyset keyset, final Aead masterKey, final byte[] associatedData) throws GeneralSecurityException {
        final byte[] encryptedKeyset = masterKey.encrypt(keyset.toByteArray(), associatedData);
        return EncryptedKeyset.newBuilder().setEncryptedKeyset(ByteString.copyFrom(encryptedKeyset)).setKeysetInfo(Util.getKeysetInfo(keyset)).build();
    }
    
    private static Keyset decrypt(final EncryptedKeyset encryptedKeyset, final Aead masterKey, final byte[] associatedData) throws GeneralSecurityException {
        try {
            final Keyset keyset = Keyset.parseFrom(masterKey.decrypt(encryptedKeyset.getEncryptedKeyset().toByteArray(), associatedData), ExtensionRegistryLite.getEmptyRegistry());
            assertEnoughKeyMaterial(keyset);
            return keyset;
        }
        catch (final InvalidProtocolBufferException e) {
            throw new GeneralSecurityException("invalid keyset, corrupted key material");
        }
    }
    
    public KeysetHandle getPublicKeysetHandle() throws GeneralSecurityException {
        final Keyset keyset = this.getKeyset();
        final List<Entry> publicEntries = new ArrayList<Entry>(this.entries.size());
        int i = 0;
        for (final Entry entry : this.entries) {
            Entry publicEntry;
            if (entry.getKey() instanceof PrivateKey) {
                final Key publicKey = ((PrivateKey)entry.getKey()).getPublicKey();
                publicEntry = new Entry(publicKey, entry.keyStatusType, entry.getId(), entry.isPrimary(), false, Entry.NO_LOGGING);
                validateKeyId(publicKey, entry.getId());
            }
            else {
                final Keyset.Key protoKey = keyset.getKey(i);
                final KeyData keyData = getPublicKeyDataFromRegistry(protoKey.getKeyData());
                final Keyset.Key publicProtoKey = protoKey.toBuilder().setKeyData(keyData).build();
                Key publicKey2;
                boolean keyParsingFailed;
                try {
                    publicKey2 = toKey(publicProtoKey);
                    keyParsingFailed = false;
                }
                catch (final GeneralSecurityException e) {
                    if (GlobalTinkFlags.validateKeysetsOnParsing.getValue()) {
                        throw e;
                    }
                    publicKey2 = new LegacyProtoKey(toProtoKeySerialization(publicProtoKey), InsecureSecretKeyAccess.get());
                    keyParsingFailed = true;
                }
                final int id = publicProtoKey.getKeyId();
                publicEntry = new Entry(publicKey2, entry.keyStatusType, id, id == keyset.getPrimaryKeyId(), keyParsingFailed, Entry.NO_LOGGING);
            }
            publicEntries.add(publicEntry);
            ++i;
        }
        return addMonitoringIfNeeded(new KeysetHandle(publicEntries, this.annotations));
    }
    
    private static KeyData getPublicKeyDataFromRegistry(final KeyData privateKeyData) throws GeneralSecurityException {
        if (privateKeyData.getKeyMaterialType() != KeyData.KeyMaterialType.ASYMMETRIC_PRIVATE) {
            throw new GeneralSecurityException("The keyset contains a non-private key");
        }
        final KeyData publicKeyData = Registry.getPublicKeyData(privateKeyData.getTypeUrl(), privateKeyData.getValue());
        return publicKeyData;
    }
    
    @Override
    public String toString() {
        return this.getKeysetInfo().toString();
    }
    
    private static void assertNoSecretKeyMaterial(final Keyset keyset) throws GeneralSecurityException {
        for (final Keyset.Key key : keyset.getKeyList()) {
            if (key.getKeyData().getKeyMaterialType() == KeyData.KeyMaterialType.UNKNOWN_KEYMATERIAL || key.getKeyData().getKeyMaterialType() == KeyData.KeyMaterialType.SYMMETRIC || key.getKeyData().getKeyMaterialType() == KeyData.KeyMaterialType.ASYMMETRIC_PRIVATE) {
                throw new GeneralSecurityException(String.format("keyset contains key material of type %s for type url %s", key.getKeyData().getKeyMaterialType().name(), key.getKeyData().getTypeUrl()));
            }
        }
    }
    
    private static void assertEnoughKeyMaterial(final Keyset keyset) throws GeneralSecurityException {
        if (keyset == null || keyset.getKeyCount() <= 0) {
            throw new GeneralSecurityException("empty keyset");
        }
    }
    
    private static void assertEnoughEncryptedKeyMaterial(final EncryptedKeyset keyset) throws GeneralSecurityException {
        if (keyset == null || keyset.getEncryptedKeyset().size() == 0) {
            throw new GeneralSecurityException("empty keyset");
        }
    }
    
    private <P> P getPrimitiveInternal(final InternalConfiguration config, final Class<P> classObject) throws GeneralSecurityException {
        final Keyset keyset = this.getUnmonitoredHandle().getKeyset();
        Util.validateKeyset(keyset);
        for (int i = 0; i < this.size(); ++i) {
            if (this.entries.get(i).keyParsingFailed || !isValidKeyStatusType(this.entries.get(i).keyStatusType)) {
                final Keyset.Key protoKey = keyset.getKey(i);
                throw new GeneralSecurityException("Key parsing of key with index " + i + " and type_url " + protoKey.getKeyData().getTypeUrl() + " failed, unable to get primitive");
            }
        }
        return config.wrap(this.getUnmonitoredHandle(), this.annotations, classObject);
    }
    
    public <P> P getPrimitive(final Configuration configuration, final Class<P> targetClassObject) throws GeneralSecurityException {
        if (!(configuration instanceof InternalConfiguration)) {
            throw new GeneralSecurityException("Currently only subclasses of InternalConfiguration are accepted");
        }
        final InternalConfiguration internalConfig = (InternalConfiguration)configuration;
        return this.getPrimitiveInternal(internalConfig, targetClassObject);
    }
    
    @Deprecated
    @InlineMe(replacement = "this.getPrimitive(RegistryConfiguration.get(), targetClassObject)", imports = { "com.google.crypto.tink.RegistryConfiguration" })
    public <P> P getPrimitive(final Class<P> targetClassObject) throws GeneralSecurityException {
        return this.getPrimitive(RegistryConfiguration.get(), targetClassObject);
    }
    
    @Deprecated
    public KeyHandle primaryKey() throws GeneralSecurityException {
        final Keyset keyset = this.getKeyset();
        final int primaryKeyId = keyset.getPrimaryKeyId();
        for (final Keyset.Key key : keyset.getKeyList()) {
            if (key.getKeyId() == primaryKeyId) {
                return new InternalKeyHandle(new ProtoKey(key.getKeyData(), KeyTemplate.fromProto(key.getOutputPrefixType())), key.getStatus(), key.getKeyId());
            }
        }
        throw new GeneralSecurityException("No primary key found in keyset.");
    }
    
    public boolean equalsKeyset(final KeysetHandle other) {
        if (this.size() != other.size()) {
            return false;
        }
        boolean primaryFound = false;
        for (int i = 0; i < this.size(); ++i) {
            final Entry thisEntry = this.entries.get(i);
            final Entry otherEntry = other.entries.get(i);
            if (thisEntry.keyParsingFailed) {
                return false;
            }
            if (otherEntry.keyParsingFailed) {
                return false;
            }
            if (!isValidKeyStatusType(thisEntry.keyStatusType)) {
                return false;
            }
            if (!isValidKeyStatusType(otherEntry.keyStatusType)) {
                return false;
            }
            if (!thisEntry.equalsEntry(otherEntry)) {
                return false;
            }
            primaryFound |= thisEntry.isPrimary;
        }
        return primaryFound;
    }
    
    private static ProtoKeySerialization toProtoKeySerialization(final Keyset.Key protoKey) throws GeneralSecurityException {
        final int id = protoKey.getKeyId();
        final Integer idRequirement = (protoKey.getOutputPrefixType() == OutputPrefixType.RAW) ? null : Integer.valueOf(id);
        return ProtoKeySerialization.create(protoKey.getKeyData().getTypeUrl(), protoKey.getKeyData().getValue(), protoKey.getKeyData().getKeyMaterialType(), protoKey.getOutputPrefixType(), idRequirement);
    }
    
    private static Key toKey(final Keyset.Key protoKey) throws GeneralSecurityException {
        final ProtoKeySerialization protoKeySerialization = toProtoKeySerialization(protoKey);
        return MutableSerializationRegistry.globalInstance().parseKeyWithLegacyFallback(protoKeySerialization, InsecureSecretKeyAccess.get());
    }
    
    private static Keyset.Key toKeysetKey(final int id, final KeyStatusType status, final ProtoKeySerialization protoKeySerialization) {
        return Keyset.Key.newBuilder().setKeyData(KeyData.newBuilder().setTypeUrl(protoKeySerialization.getTypeUrl()).setValue(protoKeySerialization.getValue()).setKeyMaterialType(protoKeySerialization.getKeyMaterialType())).setStatus(status).setKeyId(id).setOutputPrefixType(protoKeySerialization.getOutputPrefixType()).build();
    }
    
    private static void validateKeyId(final Key key, final int id) throws GeneralSecurityException {
        final Integer idRequirement = key.getIdRequirementOrNull();
        if (idRequirement != null && idRequirement != id) {
            throw new GeneralSecurityException("Wrong ID set for key with ID requirement");
        }
    }
    
    private static Keyset.Key createKeysetKey(final Key key, final KeyStatusType keyStatus, final int id) throws GeneralSecurityException {
        final ProtoKeySerialization serializedKey = MutableSerializationRegistry.globalInstance().serializeKey(key, ProtoKeySerialization.class, InsecureSecretKeyAccess.get());
        validateKeyId(key, id);
        return toKeysetKey(id, keyStatus, serializedKey);
    }
    
    public static final class Builder
    {
        private final List<Entry> entries;
        @Nullable
        private GeneralSecurityException errorToThrow;
        private MonitoringAnnotations annotations;
        private boolean buildCalled;
        
        public Builder() {
            this.entries = new ArrayList<Entry>();
            this.errorToThrow = null;
            this.annotations = MonitoringAnnotations.EMPTY;
            this.buildCalled = false;
        }
        
        private void clearPrimary() {
            for (final Entry entry : this.entries) {
                entry.isPrimary = false;
            }
        }
        
        @CanIgnoreReturnValue
        public Builder addEntry(final Entry entry) {
            if (entry.builder != null) {
                throw new IllegalStateException("Entry has already been added to a KeysetHandle.Builder");
            }
            if (entry.isPrimary) {
                this.clearPrimary();
            }
            entry.builder = this;
            this.entries.add(entry);
            return this;
        }
        
        @CanIgnoreReturnValue
        @Alpha
        public Builder setMonitoringAnnotations(final MonitoringAnnotations annotations) {
            this.annotations = annotations;
            return this;
        }
        
        public int size() {
            return this.entries.size();
        }
        
        public Entry getAt(final int i) {
            return this.entries.get(i);
        }
        
        @Deprecated
        @CanIgnoreReturnValue
        public Entry removeAt(final int i) {
            return this.entries.remove(i);
        }
        
        @CanIgnoreReturnValue
        public Builder deleteAt(final int i) {
            this.entries.remove(i);
            return this;
        }
        
        private static void checkIdAssignments(final List<Entry> entries) throws GeneralSecurityException {
            for (int i = 0; i < entries.size() - 1; ++i) {
                if (entries.get(i).strategy == KeyIdStrategy.RANDOM_ID && entries.get(i + 1).strategy != KeyIdStrategy.RANDOM_ID) {
                    throw new GeneralSecurityException("Entries with 'withRandomId()' may only be followed by other entries with 'withRandomId()'.");
                }
            }
        }
        
        private void setErrorToThrow(final GeneralSecurityException errorToThrow) {
            this.errorToThrow = errorToThrow;
        }
        
        private static int randomIdNotInSet(final Set<Integer> ids) {
            int id;
            for (id = 0; id == 0 || ids.contains(id); id = com.google.crypto.tink.internal.Util.randKeyId()) {}
            return id;
        }
        
        private static int getNextIdFromBuilderEntry(final Entry builderEntry, final Set<Integer> idsSoFar) throws GeneralSecurityException {
            int id = 0;
            if (builderEntry.strategy == null) {
                throw new GeneralSecurityException("No ID was set (with withFixedId or withRandomId)");
            }
            if (builderEntry.strategy == KeyIdStrategy.RANDOM_ID) {
                id = randomIdNotInSet(idsSoFar);
            }
            else {
                id = builderEntry.strategy.getFixedId();
            }
            return id;
        }
        
        public KeysetHandle build() throws GeneralSecurityException {
            if (this.errorToThrow != null) {
                throw new GeneralSecurityException("Cannot build keyset due to error in original", this.errorToThrow);
            }
            if (this.buildCalled) {
                throw new GeneralSecurityException("KeysetHandle.Builder#build must only be called once");
            }
            this.buildCalled = true;
            final List<KeysetHandle.Entry> handleEntries = new ArrayList<KeysetHandle.Entry>(this.entries.size());
            Integer primaryId = null;
            checkIdAssignments(this.entries);
            final Set<Integer> idsSoFar = new HashSet<Integer>();
            for (final Entry builderEntry : this.entries) {
                if (builderEntry.keyStatus == null) {
                    throw new GeneralSecurityException("Key Status not set.");
                }
                final int id = getNextIdFromBuilderEntry(builderEntry, idsSoFar);
                if (idsSoFar.contains(id)) {
                    throw new GeneralSecurityException("Id " + id + " is used twice in the keyset");
                }
                idsSoFar.add(id);
                KeysetHandle.Entry handleEntry;
                if (builderEntry.key != null) {
                    validateKeyId(builderEntry.key, id);
                    handleEntry = new KeysetHandle.Entry(builderEntry.key, serializeStatus(builderEntry.keyStatus), id, builderEntry.isPrimary, false, KeysetHandle.Entry.NO_LOGGING);
                }
                else {
                    final Integer idRequirement = builderEntry.parameters.hasIdRequirement() ? Integer.valueOf(id) : null;
                    final Key key = MutableKeyCreationRegistry.globalInstance().createKey(builderEntry.parameters, idRequirement);
                    handleEntry = new KeysetHandle.Entry(key, serializeStatus(builderEntry.keyStatus), id, builderEntry.isPrimary, false, KeysetHandle.Entry.NO_LOGGING);
                }
                if (builderEntry.isPrimary) {
                    if (primaryId != null) {
                        throw new GeneralSecurityException("Two primaries were set");
                    }
                    primaryId = id;
                    if (builderEntry.keyStatus != KeyStatus.ENABLED) {
                        throw new GeneralSecurityException("Primary key is not enabled");
                    }
                }
                handleEntries.add(handleEntry);
            }
            if (primaryId == null) {
                throw new GeneralSecurityException("No primary was set");
            }
            final KeysetHandle unmonitoredKeyset = new KeysetHandle(handleEntries, this.annotations, (KeysetHandle$1)null);
            return addMonitoringIfNeeded(unmonitoredKeyset);
        }
        
        private static class KeyIdStrategy
        {
            private static final KeyIdStrategy RANDOM_ID;
            private final int fixedId;
            
            private KeyIdStrategy() {
                this.fixedId = 0;
            }
            
            private KeyIdStrategy(final int id) {
                this.fixedId = id;
            }
            
            private static KeyIdStrategy randomId() {
                return KeyIdStrategy.RANDOM_ID;
            }
            
            private static KeyIdStrategy fixedId(final int id) {
                return new KeyIdStrategy(id);
            }
            
            private int getFixedId() {
                return this.fixedId;
            }
            
            static {
                RANDOM_ID = new KeyIdStrategy();
            }
        }
        
        public static final class Entry
        {
            private boolean isPrimary;
            private KeyStatus keyStatus;
            @Nullable
            private final Key key;
            @Nullable
            private final Parameters parameters;
            private KeyIdStrategy strategy;
            @Nullable
            private Builder builder;
            
            private Entry(final Key key) {
                this.keyStatus = KeyStatus.ENABLED;
                this.strategy = null;
                this.builder = null;
                this.key = key;
                this.parameters = null;
            }
            
            private Entry(final Parameters parameters) {
                this.keyStatus = KeyStatus.ENABLED;
                this.strategy = null;
                this.builder = null;
                this.key = null;
                this.parameters = parameters;
            }
            
            @CanIgnoreReturnValue
            public Entry makePrimary() {
                if (this.builder != null) {
                    this.builder.clearPrimary();
                }
                this.isPrimary = true;
                return this;
            }
            
            public boolean isPrimary() {
                return this.isPrimary;
            }
            
            @CanIgnoreReturnValue
            public Entry setStatus(final KeyStatus status) {
                this.keyStatus = status;
                return this;
            }
            
            public KeyStatus getStatus() {
                return this.keyStatus;
            }
            
            @CanIgnoreReturnValue
            public Entry withFixedId(final int id) {
                this.strategy = fixedId(id);
                return this;
            }
            
            @CanIgnoreReturnValue
            public Entry withRandomId() {
                this.strategy = randomId();
                return this;
            }
        }
    }
    
    @Immutable
    public static final class Entry implements KeysetHandleInterface.Entry
    {
        private static final EntryConsumer NO_LOGGING;
        private final Key key;
        private final KeyStatusType keyStatusType;
        private final KeyStatus keyStatus;
        private final int id;
        private final boolean isPrimary;
        private final boolean keyParsingFailed;
        private final EntryConsumer keyExportLogger;
        
        private Entry(final Key key, final KeyStatusType keyStatusType, final int id, final boolean isPrimary, final boolean keyParsingFailed, final EntryConsumer keyExportLogger) {
            this.key = key;
            this.keyStatusType = keyStatusType;
            this.keyStatus = parseStatusWithDisabledFallback(keyStatusType);
            this.id = id;
            this.isPrimary = isPrimary;
            this.keyParsingFailed = keyParsingFailed;
            this.keyExportLogger = keyExportLogger;
        }
        
        @Override
        public Key getKey() {
            this.keyExportLogger.accept(this);
            return this.key;
        }
        
        @Override
        public KeyStatus getStatus() {
            return this.keyStatus;
        }
        
        @Override
        public int getId() {
            return this.id;
        }
        
        @Override
        public boolean isPrimary() {
            return this.isPrimary;
        }
        
        private boolean equalsEntry(final Entry other) {
            return other.isPrimary == this.isPrimary && other.keyStatusType.equals(this.keyStatusType) && other.id == this.id && other.key.equalsKey(this.key);
        }
        
        static {
            NO_LOGGING = (e -> {});
        }
        
        @Immutable
        private interface EntryConsumer
        {
            void accept(final Entry entry);
        }
    }
}
