# Authors:
#   Trevor Perrin
#   Dave Baggett (Arcode Corporation) - canonicalCipherName
#
# See the LICENSE file for legal information regarding use of this file.

"""Class representing a TLS session."""

from .utils.compat import *
from .mathtls import *
from .constants import *

class Session(object):
    """
    This class represents a TLS session.

    TLS distinguishes between connections and sessions.  A new
    handshake creates both a connection and a session.  Data is
    transmitted over the connection.

    The session contains a more permanent record of the handshake.  The
    session can be inspected to determine handshake results.  The
    session can also be used to create a new connection through
    "session resumption". If the client and server both support this,
    they can create a new connection based on an old session without
    the overhead of a full handshake.

    The session for a :py:class:`~tlslite.tlsconnection.TLSConnection` can be
    retrieved from the connection's 'session' attribute.

    :vartype srpUsername: str
    :ivar srpUsername: The client's SRP username (or None).

    :vartype clientCertChain: ~tlslite.x509certchain.X509CertChain
    :ivar clientCertChain: The client's certificate chain (or None).

    :vartype serverCertChain: ~tlslite.x509certchain.X509CertChain
    :ivar serverCertChain: The server's certificate chain (or None).

    :vartype tackExt: tack.structures.TackExtension.TackExtension
    :ivar tackExt: The server's TackExtension (or None).

    :vartype tackInHelloExt: bool
    :ivar tackInHelloExt: True if a TACK was presented via TLS Extension.

    :vartype ~.encryptThenMAC: bool
    :ivar ~.encryptThenMAC: True if connection uses CBC cipher in
        encrypt-then-MAC mode

    :vartype appProto: bytearray
    :ivar appProto: name of the negotiated application level protocol, None
        if not negotiated

    :vartype cl_app_secret: bytearray
    :ivar cl_app_secret: key used for deriving keys used by client to encrypt
        and protect data in TLS 1.3

    :vartype sr_app_secret: bytearray
    :ivar sr_app_secret: key used for deriving keys used by server to encrypt
        and protect data in TLS 1.3

    :vartype exporterMasterSecret: bytearray
    :ivar exporterMasterSecret: master secret used for TLS Exporter in TLS1.3

    :vartype resumptionMasterSecret: bytearray
    :ivar resumptionMasterSecret: master secret used for session resumption in
        TLS 1.3

    :vartype tickets: list
    :ivar tickets: list of TLS 1.3 session tickets received from the server

    :vartype tls_1_0_tickets: list
    :ivar tls_1_0_tickets: list of TLS 1.2 and earlier session tickets received
        from the server

    :vartype ec_point_format: int
    :ivar ec_point_format: used EC point format for the ECDH key exchange;
    """

    def __init__(self):
        self.masterSecret = bytearray(0)
        self.sessionID = bytearray(0)
        self.cipherSuite = 0
        self.srpUsername = ""
        self.clientCertChain = None
        self.serverCertChain = None
        self.tackExt = None
        self.tackInHelloExt = False
        self.serverName = ""
        self.resumable = False
        self.encryptThenMAC = False
        self.extendedMasterSecret = False
        self.appProto = bytearray(0)
        self.cl_app_secret = bytearray(0)
        self.sr_app_secret = bytearray(0)
        self.exporterMasterSecret = bytearray(0)
        self.resumptionMasterSecret = bytearray(0)
        self.tickets = None
        self.tls_1_0_tickets = None
        self.ec_point_format = 0

    def create(self, masterSecret, sessionID, cipherSuite,
               srpUsername, clientCertChain, serverCertChain,
               tackExt, tackInHelloExt, serverName, resumable=True,
               encryptThenMAC=False, extendedMasterSecret=False,
               appProto=bytearray(0), cl_app_secret=bytearray(0),
               sr_app_secret=bytearray(0), exporterMasterSecret=bytearray(0),
               resumptionMasterSecret=bytearray(0), tickets=None,
               tls_1_0_tickets=None, ec_point_format=None):
        self.masterSecret = masterSecret
        self.sessionID = sessionID
        self.cipherSuite = cipherSuite
        self.srpUsername = srpUsername
        self.clientCertChain = clientCertChain
        self.serverCertChain = serverCertChain
        self.tackExt = tackExt
        self.tackInHelloExt = tackInHelloExt
        self.serverName = serverName
        self.resumable = resumable
        self.encryptThenMAC = encryptThenMAC
        self.extendedMasterSecret = extendedMasterSecret
        self.appProto = appProto
        self.cl_app_secret = cl_app_secret
        self.sr_app_secret = sr_app_secret
        self.exporterMasterSecret = exporterMasterSecret
        self.resumptionMasterSecret = resumptionMasterSecret
        # NOTE we need a reference copy not a copy of object here!
        self.tickets = tickets
        self.tls_1_0_tickets = tls_1_0_tickets
        self.ec_point_format = ec_point_format

    def _clone(self):
        other = Session()
        other.masterSecret = self.masterSecret
        other.sessionID = self.sessionID
        other.cipherSuite = self.cipherSuite
        other.srpUsername = self.srpUsername
        other.clientCertChain = self.clientCertChain
        other.serverCertChain = self.serverCertChain
        other.tackExt = self.tackExt
        other.tackInHelloExt = self.tackInHelloExt
        other.serverName = self.serverName
        other.resumable = self.resumable
        other.encryptThenMAC = self.encryptThenMAC
        other.extendedMasterSecret = self.extendedMasterSecret
        other.appProto = self.appProto
        other.cl_app_secret = self.cl_app_secret
        other.sr_app_secret = self.sr_app_secret
        other.exporterMasterSecret = self.exporterMasterSecret
        other.resumptionMasterSecret = self.resumptionMasterSecret
        other.tickets = self.tickets
        other.tls_1_0_tickets = self.tls_1_0_tickets
        other.ec_point_format = self.ec_point_format
        return other

    def valid(self):
        """If this session can be used for session resumption.

        :rtype: bool
        :returns: If this session can be used for session resumption.
        """
        # TODO add checks for tickets received from server (freshness etc.)
        return self.resumable and (self.sessionID or self.tickets or
                                   self.tls_1_0_tickets)

    def _setResumable(self, boolean):
        #Only let it be set to True if the sessionID is non-null
        if (not boolean) or (boolean and self.sessionID):
            self.resumable = boolean

    def getTackId(self):
        if self.tackExt and self.tackExt.tack:
            return self.tackExt.tack.getTackId()
        else:
            return None

    def getBreakSigs(self):
        if self.tackExt and self.tackExt.break_sigs:
            return self.tackExt.break_sigs
        else:
            return None

    def getCipherName(self):
        """Get the name of the cipher used with this connection.

        :rtype: str
        :returns: The name of the cipher used with this connection.
        """
        return CipherSuite.canonicalCipherName(self.cipherSuite)

    def getMacName(self):
        """Get the name of the HMAC hash algo used with this connection.

        :rtype: str
        :returns: The name of the HMAC hash algo used with this connection.
        """
        return CipherSuite.canonicalMacName(self.cipherSuite)


class Ticket(object):
    """
    This class holds the ticket and ticket lifetime which are recieved from
    the server, together with the session object, it's all the information
    needed to resume a session using SessionTickets in TLSv1.2.
    Currently objects of this class are only used in client side session cache
    where we can iterate over them and use them for resumption when possible.

    :vartype ticket: bytearray
    :ivar ticket: the actual ticket recieved from the server

    :vartype ticket_lifetime: int
    :ivar ticket_lifetime: lifetime of the ticket defined by the server

    :vartype master_secret: bytearray
    :ivar master_secret: master secret used to resume the session

    :vartype cipher_suite: int
    :ivar cipher_suite: ciphersuite used to resume the session

    :vartype time_recieved: int
    :ivar time_recieved: the actual time when we recieved the ticket
    """

    def __init__(self, ticket, ticket_lifetime, master_secret, cipher_suite):
        self.ticket = ticket
        self.ticket_lifetime = ticket_lifetime
        self.master_secret = master_secret
        self.cipher_suite = cipher_suite
        self.time_received = time.time()

    def valid(self):
        return time.time() < self.time_received + self.ticket_lifetime
