1"""
2*******************************************************************
3  Copyright (c) 2017, 2019 IBM Corp.
4
5  All rights reserved. This program and the accompanying materials
6  are made available under the terms of the Eclipse Public License v2.0
7  and Eclipse Distribution License v1.0 which accompany this distribution.
8
9  The Eclipse Public License is available at
10     http://www.eclipse.org/legal/epl-v10.html
11  and the Eclipse Distribution License is available at
12    http://www.eclipse.org/org/documents/edl-v10.php.
13
14  Contributors:
15     Ian Craggs - initial implementation and/or documentation
16*******************************************************************
17"""
18
19import sys
20
21from .packettypes import PacketTypes
22
23
24class ReasonCodes:
25    """MQTT version 5.0 reason codes class.
26
27    See ReasonCodes.names for a list of possible numeric values along with their
28    names and the packets to which they apply.
29
30    """
31
32    def __init__(self, packetType, aName="Success", identifier=-1):
33        """
34        packetType: the type of the packet, such as PacketTypes.CONNECT that
35            this reason code will be used with.  Some reason codes have different
36            names for the same identifier when used a different packet type.
37
38        aName: the String name of the reason code to be created.  Ignored
39            if the identifier is set.
40
41        identifier: an integer value of the reason code to be created.
42
43        """
44
45        self.packetType = packetType
46        self.names = {
47            0: {"Success": [PacketTypes.CONNACK, PacketTypes.PUBACK,
48                            PacketTypes.PUBREC, PacketTypes.PUBREL, PacketTypes.PUBCOMP,
49                            PacketTypes.UNSUBACK, PacketTypes.AUTH],
50                "Normal disconnection": [PacketTypes.DISCONNECT],
51                "Granted QoS 0": [PacketTypes.SUBACK]},
52            1: {"Granted QoS 1": [PacketTypes.SUBACK]},
53            2: {"Granted QoS 2": [PacketTypes.SUBACK]},
54            4: {"Disconnect with will message": [PacketTypes.DISCONNECT]},
55            16: {"No matching subscribers":
56                 [PacketTypes.PUBACK, PacketTypes.PUBREC]},
57            17: {"No subscription found": [PacketTypes.UNSUBACK]},
58            24: {"Continue authentication": [PacketTypes.AUTH]},
59            25: {"Re-authenticate": [PacketTypes.AUTH]},
60            128: {"Unspecified error": [PacketTypes.CONNACK, PacketTypes.PUBACK,
61                                        PacketTypes.PUBREC, PacketTypes.SUBACK, PacketTypes.UNSUBACK,
62                                        PacketTypes.DISCONNECT], },
63            129: {"Malformed packet":
64                  [PacketTypes.CONNACK, PacketTypes.DISCONNECT]},
65            130: {"Protocol error":
66                  [PacketTypes.CONNACK, PacketTypes.DISCONNECT]},
67            131: {"Implementation specific error": [PacketTypes.CONNACK,
68                                                    PacketTypes.PUBACK, PacketTypes.PUBREC, PacketTypes.SUBACK,
69                                                    PacketTypes.UNSUBACK, PacketTypes.DISCONNECT], },
70            132: {"Unsupported protocol version": [PacketTypes.CONNACK]},
71            133: {"Client identifier not valid": [PacketTypes.CONNACK]},
72            134: {"Bad user name or password": [PacketTypes.CONNACK]},
73            135: {"Not authorized": [PacketTypes.CONNACK, PacketTypes.PUBACK,
74                                     PacketTypes.PUBREC, PacketTypes.SUBACK, PacketTypes.UNSUBACK,
75                                     PacketTypes.DISCONNECT], },
76            136: {"Server unavailable": [PacketTypes.CONNACK]},
77            137: {"Server busy": [PacketTypes.CONNACK, PacketTypes.DISCONNECT]},
78            138: {"Banned": [PacketTypes.CONNACK]},
79            139: {"Server shutting down": [PacketTypes.DISCONNECT]},
80            140: {"Bad authentication method":
81                  [PacketTypes.CONNACK, PacketTypes.DISCONNECT]},
82            141: {"Keep alive timeout": [PacketTypes.DISCONNECT]},
83            142: {"Session taken over": [PacketTypes.DISCONNECT]},
84            143: {"Topic filter invalid":
85                  [PacketTypes.SUBACK, PacketTypes.UNSUBACK, PacketTypes.DISCONNECT]},
86            144: {"Topic name invalid":
87                  [PacketTypes.CONNACK, PacketTypes.PUBACK,
88                   PacketTypes.PUBREC, PacketTypes.DISCONNECT]},
89            145: {"Packet identifier in use":
90                  [PacketTypes.PUBACK, PacketTypes.PUBREC,
91                   PacketTypes.SUBACK, PacketTypes.UNSUBACK]},
92            146: {"Packet identifier not found":
93                  [PacketTypes.PUBREL, PacketTypes.PUBCOMP]},
94            147: {"Receive maximum exceeded": [PacketTypes.DISCONNECT]},
95            148: {"Topic alias invalid": [PacketTypes.DISCONNECT]},
96            149: {"Packet too large": [PacketTypes.CONNACK, PacketTypes.DISCONNECT]},
97            150: {"Message rate too high": [PacketTypes.DISCONNECT]},
98            151: {"Quota exceeded": [PacketTypes.CONNACK, PacketTypes.PUBACK,
99                                     PacketTypes.PUBREC, PacketTypes.SUBACK, PacketTypes.DISCONNECT], },
100            152: {"Administrative action": [PacketTypes.DISCONNECT]},
101            153: {"Payload format invalid":
102                  [PacketTypes.PUBACK, PacketTypes.PUBREC, PacketTypes.DISCONNECT]},
103            154: {"Retain not supported":
104                  [PacketTypes.CONNACK, PacketTypes.DISCONNECT]},
105            155: {"QoS not supported":
106                  [PacketTypes.CONNACK, PacketTypes.DISCONNECT]},
107            156: {"Use another server":
108                  [PacketTypes.CONNACK, PacketTypes.DISCONNECT]},
109            157: {"Server moved":
110                  [PacketTypes.CONNACK, PacketTypes.DISCONNECT]},
111            158: {"Shared subscription not supported":
112                  [PacketTypes.SUBACK, PacketTypes.DISCONNECT]},
113            159: {"Connection rate exceeded":
114                  [PacketTypes.CONNACK, PacketTypes.DISCONNECT]},
115            160: {"Maximum connect time":
116                  [PacketTypes.DISCONNECT]},
117            161: {"Subscription identifiers not supported":
118                  [PacketTypes.SUBACK, PacketTypes.DISCONNECT]},
119            162: {"Wildcard subscription not supported":
120                  [PacketTypes.SUBACK, PacketTypes.DISCONNECT]},
121        }
122        if identifier == -1:
123            if packetType == PacketTypes.DISCONNECT and aName == "Success":
124                aName = "Normal disconnection"
125            self.set(aName)
126        else:
127            self.value = identifier
128            self.getName()  # check it's good
129
130    def __getName__(self, packetType, identifier):
131        """
132        Get the reason code string name for a specific identifier.
133        The name can vary by packet type for the same identifier, which
134        is why the packet type is also required.
135
136        Used when displaying the reason code.
137        """
138        assert identifier in self.names.keys(), identifier
139        names = self.names[identifier]
140        namelist = [name for name in names.keys() if packetType in names[name]]
141        assert len(namelist) == 1
142        return namelist[0]
143
144    def getId(self, name):
145        """
146        Get the numeric id corresponding to a reason code name.
147
148        Used when setting the reason code for a packetType
149        check that only valid codes for the packet are set.
150        """
151        identifier = None
152        for code in self.names.keys():
153            if name in self.names[code].keys():
154                if self.packetType in self.names[code][name]:
155                    identifier = code
156                break
157        assert identifier is not None, name
158        return identifier
159
160    def set(self, name):
161        self.value = self.getId(name)
162
163    def unpack(self, buffer):
164        c = buffer[0]
165        if sys.version_info[0] < 3:
166            c = ord(c)
167        name = self.__getName__(self.packetType, c)
168        self.value = self.getId(name)
169        return 1
170
171    def getName(self):
172        """Returns the reason code name corresponding to the numeric value which is set.
173        """
174        return self.__getName__(self.packetType, self.value)
175
176    def __eq__(self, other):
177        if isinstance(other, int):
178            return self.value == other
179        if isinstance(other, str):
180            return self.value == str(self)
181        if isinstance(other, ReasonCodes):
182            return self.value == other.value
183        return False
184
185    def __str__(self):
186        return self.getName()
187
188    def json(self):
189        return self.getName()
190
191    def pack(self):
192        return bytearray([self.value])
193