from ..common import *


class TD_COAP_OBS_06(CoAPTestCase):
    """
---
TD_COAP_OBS_06:
    obj: Server detection of deregistration (explicit RST)
    cfg: CoAP_CFG_BASIC
    ref: '[OBSERVE] 4.2.2'
    pre:
        - Client supports Observe option
        - Server supports Observe option
        - Server offers an observable resource /obs which changes periodically (e.g. every 5s) which produces confirmable notifications

    seq:
    -   s:
        - 'Client is requested to send to the server a confirmable GET request with'
        -  - observe option for resource /obs

    -   c:
        - 'The request sent by client contains:'
        - Type = 0 (CON)
        - Code = 1 (GET)
        - Token value  = a value generated by the client
        - Observe option = empty

    -   c:
        - 'Server sends the response containing:'
        - Type = 2 (ACK)
        - Code = 2.05 (Content)
        - Content-format of the resource /obs
        - Token value = same as one found in the step 2
        - Observe option with a sequence number
    -   s:
        - 'Server sends a notification containing:'
        - Type = 0 (CON)
        - Code = 2.05 (Content)
        - Content-format = same as one found in the step 3
        - Token value = same as one found in the step 2
        - Observe option indicating increasing values

    - v: Client displays the received information

    -   c:
        - 'Client sends an ACK'
    -   s:
        - 'Client is rebooted'

    -   c:  'Server is still sending notifications for the request in step 2 as in step 4'
    -   v: 'Client discards response and does not display information'

    -   c: 'Client sends RST to Server'

    -   v: Server does not send further response

    -   v: Client does not display further received information

    """
    @classmethod
    @typecheck
    def get_stimulis(cls) -> list_of(Value):
        """
        Get the stimulis of this test case. This has to be be implemented into
        each test cases class.

        :return: The stimulis of this TC
        :rtype: [Value]
        """
        return [
            CoAP(type='con', code='get', opt=Opt(CoAPOptionObserve(0),CoAPOptionUriPath("obs"))),
        ]

    def run (self):
        request = CoAP(type="con", code="get", opt=self.uri("/obs", CoAPOptionObserve (0)))
        response= CoAP(type="ack", code=2.05, opt=Opt(CoAPOptionObserve(), CoAPOptionContentFormat()))
        notif   = CoAP(type="con", code=2.05, opt=Opt(CoAPOptionObserve(), CoAPOptionContentFormat()))

        # Step 2
        self.match("client", request)

        token = self.coap["tok"]
        uri = self.coap.get_uri()

        self.next()

        # Step 3
        if not self.match("server", response):
            raise self.Stop()
        self.match("server", CoAP(tok = token), "fail")

        content_format = self.coap["opt"][CoAPOptionContentFormat]["val"]
        index          = self.coap["opt"][CoAPOptionObserve]["val"]

        self.next()

        # A list of message CMID that did not receive ACK from the client.
        CMID_without_ACK = set()
        if self.match("server", CoAP(type = "con", code=2.05, tok=token), None):
            CMID_without_ACK.add(self.coap["mid"])

        continue_loop = True
        rst_sent_by_client = False
        # Step 4-6
        while self.match("server", CoAP(type = "con", code=2.05, tok=token), None) and continue_loop:
            CMID_without_ACK.add(self.coap["mid"])
            self.match("server", CoAP(tok = token), "fail")
            self.match("server", CoAP(opt = Opt(CoAPOptionContentFormat (content_format))), "fail")

            # increasing observe value
            new_index = self.coap["opt"][CoAPOptionObserve]["val"]
            self.set_verdict(("pass" if new_index > index else "fail"),
                "value of observe option must be increasing")
            index = new_index
            last_frame = self._frame # see comment in the else: block
            self.next(True)
            if self.match("client", CoAP(type = "ack"), None):
                CMID_without_ACK.remove(self.coap["mid"])
                self.next()
            elif self.match("client", CoAP(type = "rst", code=0), None) :
                # Step 10
                rst_sent_by_client = True
                self.next(True)
            else:
                # Avoid infinite loop in the scenario where the client do not
                # send an RST. We could just put the above elif block outside
                # the loop as another solution, but it may make harder to do
                # more verification (e.g for dedug purpose or additional
                # verifications) in the event where it would have more frame
                # after the client sent an RST.
                if last_frame == self._frame:
                    continue_loop = False

        if len(CMID_without_ACK) == 0 and not rst_sent_by_client:
                        self.set_verdict("inconclusive", "There is no \
unacknowledged notification nor RST.")
        elif len(CMID_without_ACK) == 0 and rst_sent_by_client:
            self.set_verdict("inconclusive", "The client did send an RST \
before rebooting. Hence we do not have any unacknowledged notification. We\
cannot make any conclusion.")
        elif len(CMID_without_ACK) < 2 and not rst_sent_by_client:
            # STEP 8
            self.set_verdict("fail", "STEP 8: Server did not continue \
to send notifications as expected. It should continue sending some notification \
even after the first unacknowledged one UNTIL it received a RST \
or if enough time elapsed.\
In other words, while the client did not send ACK nor RST we must see \
unacknowledged notification(s) in the list of frames to obtain a pass verdict.")
        # Step 10 -- Check : Client sends RST to Server.
        # At this point STEP 8 is ok.
        elif rst_sent_by_client:
            self.set_verdict("pass", "STEP 8 : Server is still sending \
notifications for the request in step 2 as in step 4")
            self.set_verdict("pass", "STEP 10: RST has been sent by the client.")
        else:
            self.set_verdict("pass", "STEP 8 : Server is still sending \
notifications for the request in step 2 as in step 4")
            self.set_verdict("fail", "STEP 10: RST has not been sent by the client.")
