/**
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you 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 org.apache.kerby.kerberos.kerb.gss.impl;

import com.sun.security.jgss.InquireType;
import org.apache.kerby.kerberos.kerb.KrbException;
import org.apache.kerby.kerberos.kerb.gss.GssMechFactory;
import org.apache.kerby.kerberos.kerb.gss.KerbyGssProvider;
import org.apache.kerby.kerberos.kerb.request.ApRequest;
import org.apache.kerby.kerberos.kerb.response.ApResponse;
import org.apache.kerby.kerberos.kerb.type.ad.AuthorizationData;
import org.apache.kerby.kerberos.kerb.type.ap.ApRep;
import org.apache.kerby.kerberos.kerb.type.ap.ApReq;
import org.apache.kerby.kerberos.kerb.type.ap.Authenticator;
import org.apache.kerby.kerberos.kerb.type.base.EncryptionKey;
import org.apache.kerby.kerberos.kerb.type.base.PrincipalName;
import org.apache.kerby.kerberos.kerb.type.kdc.EncKdcRepPart;
import org.apache.kerby.kerberos.kerb.type.ticket.EncTicketPart;
import org.apache.kerby.kerberos.kerb.type.ticket.SgtTicket;
import org.apache.kerby.kerberos.kerb.type.ticket.TicketFlags;
import org.ietf.jgss.ChannelBinding;
import org.ietf.jgss.GSSContext;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.MessageProp;
import org.ietf.jgss.Oid;
import sun.security.jgss.GSSCaller;
import sun.security.jgss.spi.GSSContextSpi;
import sun.security.jgss.spi.GSSCredentialSpi;
import sun.security.jgss.spi.GSSNameSpi;

import javax.security.auth.kerberos.KerberosTicket;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.security.Provider;

@SuppressWarnings("PMD")
public class GssContext implements GSSContextSpi {

    private static final int STATE_NONE = 0;
    private static final int STATE_ESTABLISHING = 1;
    private static final int STATE_ESTABLISHED = 2;
    private static final int STATE_DESTROYED = 3;

    private static final byte[] MSG_AP_REQ = {(byte) 0x1, (byte) 0};
    private static final byte[] MSG_AP_REP = {(byte) 0x2, (byte) 0};

    private int ctxState = STATE_NONE;

    private final GSSCaller caller;
    private GssCredElement myCred;
    private boolean initiator;
    private GssNameElement myName;
    private GssNameElement peerName;
    private int lifeTime;
    private ChannelBinding channelBinding;

    private boolean mutualAuth  = true;
    private boolean replayDet  = true;
    private boolean sequenceDet  = true;
    private boolean credDeleg  = false;
    private boolean confState  = true;
    private boolean integState  = true;
    private boolean delegPolicy = false;

    public static final int INVALID_KEY = 0;
    public static final int SESSION_KEY = 1;
    public static final int INITIATOR_SUBKEY = 2;
    public static final int ACCEPTOR_SUBKEY = 4;
    private int keyComesFrom = INVALID_KEY;

    private EncryptionKey sessionKey;   // used between client and app server
    private TicketFlags ticketFlags;
    private ApReq outApReq;

    private GssEncryptor gssEncryptor;

    // Called on initiator's side.
    public GssContext(GSSCaller caller, GssNameElement peerName, GssCredElement myCred,
                      int lifeTime)
            throws GSSException {
        if (peerName == null) {
            throw new IllegalArgumentException("Cannot have null peer name");
        }

        this.caller = caller;
        this.peerName = peerName;
        this.myCred = myCred;
        this.lifeTime = lifeTime;
        this.initiator = true;

        mySequenceNumberLock = new Object();
        peerSequenceNumberLock = new Object();
    }

    public GssContext(GSSCaller caller, GssAcceptCred myCred)
            throws GSSException {
        this.caller = caller;
        this.myCred = myCred;
        this.initiator = false;

        mySequenceNumberLock = new Object();
        peerSequenceNumberLock = new Object();
    }

    public GssContext(GSSCaller caller, byte[] interProcessToken)
            throws GSSException {
        throw new GSSException(GSSException.UNAVAILABLE, -1, "Unsupported feature");
    }

    public Provider getProvider() {
        return new KerbyGssProvider();
    }

    public void requestLifetime(int lifeTime) throws GSSException {
        if (ctxState == STATE_NONE && isInitiator()) {
            this.lifeTime = lifeTime;
        }
    }

    public void requestMutualAuth(boolean state) throws GSSException {
        if (ctxState == STATE_NONE && isInitiator()) {
            mutualAuth  = state;
        }
    }

    public void requestReplayDet(boolean state) throws GSSException {
        if (ctxState == STATE_NONE && isInitiator()) {
            replayDet = state;
        }
    }

    public void requestSequenceDet(boolean state) throws GSSException {
        if (ctxState == STATE_NONE && isInitiator()) {
            replayDet = state;
        }
    }

    public void requestCredDeleg(boolean state) throws GSSException {
        if (ctxState == STATE_NONE && isInitiator() && myCred == null) {
            credDeleg  = state;
        }
    }

    public void requestAnonymity(boolean state) throws GSSException {
        // anonymous context not supported
    }

    public void requestConf(boolean state) throws GSSException {
        if (ctxState == STATE_NONE && isInitiator()) {
            confState = state;
        }
    }

    public void requestInteg(boolean state) throws GSSException {
        if (ctxState == STATE_NONE && isInitiator()) {
            integState = state;
        }
    }

    public void requestDelegPolicy(boolean state) throws GSSException {
        if (ctxState == STATE_NONE && isInitiator()) {
            delegPolicy = state;
        }
    }

    public void setChannelBinding(ChannelBinding cb) throws GSSException {
        this.channelBinding = cb;
    }

    public boolean getCredDelegState() {
        return credDeleg;
    }

    public boolean getMutualAuthState() {
        return mutualAuth;
    }

    public boolean getReplayDetState() {
        return replayDet || sequenceDet;
    }

    public boolean getSequenceDetState() {
        return sequenceDet;
    }

    public boolean getAnonymityState() {
        return false;
    }

    public boolean getDelegPolicyState() {
        return delegPolicy;
    }

    public boolean isTransferable() throws GSSException {
        return false;
    }

    public boolean isProtReady() {
        return ctxState == STATE_ESTABLISHED;
    }

    public boolean isInitiator() {
        return initiator;
    }

    public boolean getConfState() {
        return confState;
    }

    public boolean getIntegState() {
        return integState;
    }

    public int getLifetime() {
        return GSSContext.INDEFINITE_LIFETIME;
    }

    public boolean isEstablished() {
        return ctxState == STATE_ESTABLISHED;
    }

    public GSSNameSpi getSrcName() throws GSSException {
        return isInitiator() ? myName : peerName;
    }

    public GSSNameSpi getTargName() throws GSSException {
        return !isInitiator() ? myName : peerName;
    }

    public Oid getMech() throws GSSException {
        return GssMechFactory.getOid();
    }

    public GSSCredentialSpi getDelegCred() throws GSSException {
        throw new GSSException(GSSException.FAILURE, -1, "API not implemented");  // TODO:
    }

    public byte[] initSecContext(InputStream is, int mechTokenSize)
            throws GSSException {
        if (!isInitiator()) {
            throw new GSSException(GSSException.FAILURE, -1, "initSecContext called on acceptor");
        }

        byte[] ret = null;

        if (ctxState == STATE_NONE) {

            if (!myCred.isInitiatorCredential()) {
                throw new GSSException(GSSException.NO_CRED, -1, "No TGT available");
            }

            // check if service ticket already exists
            // if not, prepare to get it through TGS_REQ
            SgtTicket sgtTicket = null;
            String serviceName = peerName.getPrincipalName().getName();
            myName = (GssNameElement) myCred.getName();
            PrincipalName clientPrincipal = myName.getPrincipalName();

            sgtTicket = GssUtil.getSgtCredentialFromContext(caller, clientPrincipal.getName(), serviceName);

            if (sgtTicket == null) {
                sgtTicket = GssUtil.applySgtCredential(((GssInitCred) myCred).getKerberosTicket(),
                                                       ((GssInitCred) myCred).getKrbToken(),
                                                       serviceName);

                // add this service credential to context
                final KerberosTicket ticket =
                        GssUtil.convertKrbTicketToKerberosTicket(sgtTicket, myName.getPrincipalName().getName());
                CredUtils.addCredentialToSubject(ticket);
            }

            ApRequest apRequest = new ApRequest(clientPrincipal, sgtTicket);
            try {
                outApReq = apRequest.getApReq();
            } catch (KrbException e) {
                throw new GSSException(GSSException.FAILURE, -1, "Generate ApReq failed: " + e.getMessage());
            }
            setupInitiatorContext(sgtTicket, apRequest);
            try {
                ByteBuffer outBuffer = ByteBuffer.allocate(outApReq.encodingLength() + 2);
                outBuffer.put(MSG_AP_REQ);
                outApReq.encode(outBuffer);
                outBuffer.flip();
                ret = outBuffer.array();
            } catch (IOException e) {
                throw new GSSException(GSSException.FAILURE, -1, "Generate ApReq bytes failed: " + e.getMessage());
            }

            ctxState = STATE_ESTABLISHING;
            if (!getMutualAuthState()) {
                gssEncryptor = new GssEncryptor(getSessionKey());
                ctxState = STATE_ESTABLISHED;
            }

        } else if (ctxState == STATE_ESTABLISHING) {
            verifyServerToken(is, mechTokenSize);
            gssEncryptor = new GssEncryptor(getSessionKey());
            outApReq = null;
            ctxState = STATE_ESTABLISHED;
        }
        return ret;
    }

    private void setupInitiatorContext(SgtTicket sgt, ApRequest apRequest) throws GSSException {
        EncKdcRepPart encKdcRepPart = sgt.getEncKdcRepPart();
        TicketFlags ticketFlags = encKdcRepPart.getFlags();
        setTicketFlags(ticketFlags);

        setAuthTime(encKdcRepPart.getAuthTime().toString());

        Authenticator auth;
        try {
            auth = apRequest.getApReq().getAuthenticator();
        } catch (KrbException e) {
            throw new GSSException(GSSException.FAILURE, -1, "ApReq failed in Initiator");
        }
        setMySequenceNumber(auth.getSeqNumber());

        EncryptionKey subKey = auth.getSubKey();
        if (subKey != null) {
            setSessionKey(subKey, GssContext.INITIATOR_SUBKEY);
        } else {
            setSessionKey(sgt.getSessionKey(), GssContext.SESSION_KEY);
        }

        if (!getMutualAuthState()) {
            setPeerSequenceNumber(0);
        }
    }

    /**
     * Verify the AP_REP from server and set context accordingly
     * @param is
     * @param mechTokenSize
     * @return
     * @throws GSSException
     * @throws IOException
     */
    private void verifyServerToken(InputStream is, int mechTokenSize)
            throws GSSException {
        byte[] token;
        ApRep apRep;
        try {
            if (!(is.read() == MSG_AP_REP[0] && is.read() == MSG_AP_REP[1])) {
                throw new GSSException(GSSException.FAILURE, -1, "Invalid ApRep message ID");
            }
            token = new byte[mechTokenSize - MSG_AP_REP.length];
            is.read(token);
            apRep = new ApRep();
            apRep.decode(token);
        } catch (IOException e) {
            throw new GSSException(GSSException.FAILURE, -1, "Invalid ApRep " + e.getMessage());
        }

        try {
            ApResponse.validate(getSessionKey(), apRep, outApReq);
        } catch (KrbException e) {
            throw new GSSException(GSSException.UNAUTHORIZED, -1, "ApRep verification failed");
        }

        EncryptionKey key = apRep.getEncRepPart().getSubkey();
        if (key != null) {
            setSessionKey(key, ACCEPTOR_SUBKEY);
        }

        int seqNum = apRep.getEncRepPart().getSeqNumber();
        setPeerSequenceNumber(seqNum == -1 ? 0 : seqNum);
    }

    public byte[] acceptSecContext(InputStream is, int mechTokenSize)
            throws GSSException {
        byte[] ret = null;

        if (isInitiator()) {
            throw new GSSException(GSSException.FAILURE, -1, "acceptSecContext called on initiator");
        }

        if (ctxState == STATE_NONE) {
            ctxState = STATE_ESTABLISHING;
            if (!myCred.isAcceptorCredential()) {
                throw new GSSException(GSSException.FAILURE, -1, "No acceptor credential available");
            }

            GssAcceptCred acceptCred = (GssAcceptCred) myCred;
            CredUtils.checkPrincipalPermission(
                    ((GssNameElement) acceptCred.getName()).getPrincipalName().getName(), "accept");

            if (getMutualAuthState()) {
                ret = verifyClientToken(acceptCred, is, mechTokenSize);
            }

            gssEncryptor = new GssEncryptor(getSessionKey());

            myCred = null;
            ctxState = STATE_ESTABLISHED;
        }

        return ret;
    }

    private byte[] verifyClientToken(GssAcceptCred acceptCred, InputStream is, int mechTokenSize)
            throws GSSException {
        byte[] token;
        ApReq apReq;
        try {
            if (!(is.read() == MSG_AP_REQ[0] && is.read() == MSG_AP_REQ[1])) {
                throw new GSSException(GSSException.FAILURE, -1, "Invalid ApReq message ID");
            }

            token = new byte[mechTokenSize - MSG_AP_REQ.length];
            is.read(token);
            apReq = new ApReq();
            apReq.decode(token);
        } catch (IOException e) {
            throw new GSSException(GSSException.UNAUTHORIZED, -1, "ApReq invalid:" + e.getMessage());
        }

        int kvno = apReq.getTicket().getEncryptedEncPart().getKvno();
        int encryptType = apReq.getTicket().getEncryptedEncPart().getEType().getValue();

        EncryptionKey serverKey = acceptCred.getEncryptionKey(encryptType, kvno);
        if (serverKey == null) {
            throw new GSSException(GSSException.FAILURE, -1, "Server key not found");
        }
        peerName = (GssNameElement) acceptCred.getName();

        try {
            ApRequest.validate(serverKey, apReq,
                    channelBinding == null ? null : channelBinding.getInitiatorAddress(), 5 * 60 * 1000);
        } catch (KrbException e) {
            throw new GSSException(GSSException.UNAUTHORIZED, -1, "ApReq verification failed: " + e.getMessage());
        }

        ApResponse apResponse = new ApResponse(apReq);
        ApRep apRep;
        try {
            apRep = apResponse.getApRep();
        } catch (KrbException e) {
            throw new GSSException(GSSException.UNAUTHORIZED, -1, "Generate ApRep failed");
        }

        EncTicketPart apReqTicketEncPart = apReq.getTicket().getEncPart();

        EncryptionKey ssKey = apReqTicketEncPart.getKey();
        Authenticator auth = apReq.getAuthenticator();
        EncryptionKey subKey = auth.getSubKey();

        if (subKey != null) {
            setSessionKey(subKey, INITIATOR_SUBKEY);
        } else {
            setSessionKey(ssKey, SESSION_KEY);
        }

        // initial seqNumber
        int seqNumber = auth.getSeqNumber();
        setMySequenceNumber(seqNumber);
        // initial authtime, tktflags, authdata,
        setAuthTime(apReqTicketEncPart.getAuthTime().toString());
        setTicketFlags(apReqTicketEncPart.getFlags());
        setAuthData(apReqTicketEncPart.getAuthorizationData());

        byte[] ret = null;
        try {
            ByteBuffer outBuffer = ByteBuffer.allocate(apRep.encodingLength() + 2);
            outBuffer.put(MSG_AP_REP);
            apRep.encode(outBuffer);
            outBuffer.flip();
            ret = outBuffer.array();
        } catch (IOException e) {
            throw new GSSException(GSSException.FAILURE, -1, "Generate ApRep bytes failed:" + e.getMessage());
        }
        return ret;
    }

    public int getWrapSizeLimit(int qop, boolean confReq, int maxTokSize)
            throws GSSException {
        if (gssEncryptor.isV2()) {
            return WrapTokenV2.getMsgSizeLimit(qop, confReq, maxTokSize, gssEncryptor);
        } else {
            return WrapTokenV1.getMsgSizeLimit(qop, confReq, maxTokSize, gssEncryptor);
        }
    }

    public void wrap(InputStream is, OutputStream os, MessageProp msgProp)
            throws GSSException {
        if (ctxState != STATE_ESTABLISHED) {
            throw new GSSException(GSSException.NO_CONTEXT, -1, "Context invalid for wrap");
        }

        int len;
        byte[] inBuf;
        try {
            len = is.available();
            inBuf = new byte[len];
            is.read(inBuf);
        } catch (IOException e) {
            throw new GSSException(GSSException.FAILURE, -1, "Error when get user data:" + e.getMessage());
        }
        if (gssEncryptor.isV2()) {
            WrapTokenV2 token = new WrapTokenV2(this, inBuf, 0, len, msgProp);
            token.wrap(os);
        } else {
            WrapTokenV1 token = new WrapTokenV1(this, inBuf, 0, len, msgProp);
            token.wrap(os);
        }
    }

    public byte[] wrap(byte[] inBuf, int offset, int len,
                       MessageProp msgProp) throws GSSException {
        if (ctxState != STATE_ESTABLISHED) {
            throw new GSSException(GSSException.NO_CONTEXT, -1, "Context invalid for wrap");
        }
        byte[] ret;
        if (gssEncryptor.isV2()) {
            WrapTokenV2 token = new WrapTokenV2(this, inBuf, offset, len, msgProp);
            ret = token.wrap();
        } else {
            WrapTokenV1 token = new WrapTokenV1(this, inBuf, offset, len, msgProp);
            ret = token.wrap();
        }
        return ret;
    }

    public void unwrap(InputStream is, OutputStream os,
                       MessageProp msgProp) throws GSSException {
        if (ctxState != STATE_ESTABLISHED) {
            throw new GSSException(GSSException.NO_CONTEXT, -1, "Context invalid for unwrap");
        }

        if (gssEncryptor.isV2()) {
            WrapTokenV2 token = new WrapTokenV2(this, msgProp, is);
            token.unwrap(os);
        } else {
            WrapTokenV1 token = new WrapTokenV1(this, msgProp, is);
            token.unwrap(os);
        }
    }

    public byte[] unwrap(byte[] inBuf, int offset, int len,
                         MessageProp msgProp) throws GSSException {
        if (ctxState != STATE_ESTABLISHED) {
            throw new GSSException(GSSException.NO_CONTEXT, -1, "Context invalid for unwrap");
        }

        byte[] ret;
        if (gssEncryptor.isV2()) {
            WrapTokenV2 token = new WrapTokenV2(this, msgProp, inBuf, offset, len);
            ret = token.unwrap();
        } else {
            WrapTokenV1 token = new WrapTokenV1(this, msgProp, inBuf, offset, len);
            ret = token.unwrap();
        }
        return ret;
    }

    public void getMIC(InputStream is, OutputStream os,
                       MessageProp msgProp) throws GSSException {
        if (ctxState != STATE_ESTABLISHED) {
            throw new GSSException(GSSException.NO_CONTEXT, -1, "Context invalid for getMIC");
        }

        try {
            int len = is.available();
            byte[] inMsg = new byte[len];
            is.read(inMsg);
            if (gssEncryptor.isV2()) {
                MicTokenV2 token = new MicTokenV2(this, inMsg, 0, len, msgProp);
                token.getMic(os);
            } else {
                MicTokenV1 token = new MicTokenV1(this, inMsg, 0, len, msgProp);
                token.getMic(os);
            }
        } catch (IOException e) {
            throw new GSSException(GSSException.FAILURE, -1, "Error when get user data in getMIC:" + e.getMessage());
        }
    }

    public byte[] getMIC(byte[] inMsg, int offset, int len,
                         MessageProp msgProp) throws GSSException {
        if (ctxState != STATE_ESTABLISHED) {
            throw new GSSException(GSSException.NO_CONTEXT, -1, "Context invalid for getMIC");
        }

        byte[] ret;
        if (gssEncryptor.isV2()) {
            MicTokenV2 token = new MicTokenV2(this, inMsg, offset, len, msgProp);
            ret = token.getMic();
        } else {
            MicTokenV1 token = new MicTokenV1(this, inMsg, offset, len, msgProp);
            ret = token.getMic();
        }
        return ret;
    }

    public void verifyMIC(InputStream is, InputStream msgStr,
                          MessageProp msgProp) throws GSSException {
        if (ctxState != STATE_ESTABLISHED) {
            throw new GSSException(GSSException.NO_CONTEXT, -1, "Context invalid for verifyMIC");
        }

        try {
            int tokLen = is.available();
            byte[] inTok = new byte[tokLen];
            int msgLen = msgStr.available();
            byte[] inMsg = new byte[msgLen];

           verifyMIC(inTok, 0, tokLen, inMsg, 0, msgLen, msgProp);
        } catch (IOException e) {
            throw new GSSException(GSSException.FAILURE, -1,
                    "Error when get user data in verifyMIC:" + e.getMessage());
        }
    }

    public void verifyMIC(byte[]inTok, int tokOffset, int tokLen,
                          byte[] inMsg, int msgOffset, int msgLen,
                          MessageProp msgProp) throws GSSException {
        if (ctxState != STATE_ESTABLISHED) {
            throw new GSSException(GSSException.NO_CONTEXT, -1, "Context invalid for verifyMIC");
        }

        if (gssEncryptor.isV2()) {
            MicTokenV2 token = new MicTokenV2(this, msgProp, inTok, tokOffset, tokLen);
            token.verify(inMsg, msgOffset, msgLen);
        } else {
            MicTokenV1 token = new MicTokenV1(this, msgProp, inTok, tokOffset, tokLen);
            token.verify(inMsg, msgOffset, msgLen);
        }
    }

    public byte[] export() throws GSSException {
        throw new GSSException(GSSException.UNAVAILABLE, -1, "Unsupported export() method");
    }

    public void dispose() throws GSSException {
        ctxState = STATE_DESTROYED;
        setSessionKey(null, 0);
        peerName = null;
        myCred = null;
        myName = null;
    }


    private String authTime;
    private void setAuthTime(String authTime) {
        this.authTime = authTime;
    }

    public Object inquireSecContext(String type) throws GSSException {
        if (ctxState != STATE_ESTABLISHED) {
            throw new GSSException(GSSException.NO_CONTEXT, -1, "Invalid context");
        }

        switch (type) {
            case "KRB5_GET_SESSION_KEY":
                return getSessionKey();
            case "KRB5_GET_TKT_FLAGS":
                return GssUtil.ticketFlagsToBooleans(ticketFlags);
            case "KRB5_GET_AUTHZ_DATA":
                if (isInitiator()) {
                    throw new GSSException(GSSException.UNAVAILABLE, -1,
                            "Authorization data not available for initiator");
                } else {
                    return GssUtil.kerbyAuthorizationDataToJgssAuthorizationDataEntries(authData);
                }
            case "KRB5_GET_AUTHTIME":
                return authTime;
        }
        throw new GSSException(GSSException.UNAVAILABLE, -1, "Unsupported inquire type");
    }

    public Object inquireSecContext(InquireType type) throws GSSException {
        if (ctxState != STATE_ESTABLISHED) {
            throw new GSSException(GSSException.NO_CONTEXT, -1, "Invalid context");
        }

        switch (type) {
            case KRB5_GET_SESSION_KEY:
                return getSessionKey();
            case KRB5_GET_TKT_FLAGS:
                return GssUtil.ticketFlagsToBooleans(ticketFlags);
            case KRB5_GET_AUTHZ_DATA:
                if (isInitiator()) {
                    throw new GSSException(GSSException.UNAVAILABLE, -1,
                            "Authorization data not available for initiator");
                } else {
                    return GssUtil.kerbyAuthorizationDataToJgssAuthorizationDataEntries(authData);
                }
            case KRB5_GET_AUTHTIME:
                return authTime;
        }
        throw new GSSException(GSSException.UNAVAILABLE, -1, "Unsupported inquire type");
    }


    // functions not belong to SPI
    private void setSessionKey(EncryptionKey encryptionKey, int keyComesFrom) {
        this.sessionKey = encryptionKey;
        this.keyComesFrom = keyComesFrom;
    }

    public int getKeyComesFrom() {
        return keyComesFrom;
    }

    private EncryptionKey getSessionKey() {
        return sessionKey;
    }

    private void setTicketFlags(TicketFlags ticketFlags) {
        this.ticketFlags = ticketFlags;
    }

    private AuthorizationData authData;
    private void setAuthData(AuthorizationData authData) {
        this.authData = authData;
    }


    private int mySequenceNumber;
    private int peerSequenceNumber;
    private Object mySequenceNumberLock;
    private Object peerSequenceNumberLock;

    public void setMySequenceNumber(int sequenceNumber) {
        synchronized (mySequenceNumberLock) {
            mySequenceNumber = sequenceNumber;
        }
    }

    public int incMySequenceNumber() {
        synchronized (mySequenceNumberLock) {
            return mySequenceNumber++;
        }
    }

    public void setPeerSequenceNumber(int sequenceNumber) {
        synchronized (peerSequenceNumberLock) {
            peerSequenceNumber = sequenceNumber;
        }
    }

    public int incPeerSequenceNumber() {
        synchronized (peerSequenceNumberLock) {
            return peerSequenceNumber++;
        }
    }

    public GssEncryptor getGssEncryptor() {
        return gssEncryptor;
    }
}

