1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.handler.codec.mqtt;
17
18 import io.netty.util.collection.IntObjectHashMap;
19
20 import java.util.Collection;
21 import java.util.Collections;
22 import java.util.List;
23 import java.util.ArrayList;
24
25
26
27
28 public final class MqttProperties {
29
30
31 static final int PAYLOAD_FORMAT_INDICATOR = 0x01;
32 static final int REQUEST_PROBLEM_INFORMATION = 0x17;
33 static final int REQUEST_RESPONSE_INFORMATION = 0x19;
34 static final int MAXIMUM_QOS = 0x24;
35 static final int RETAIN_AVAILABLE = 0x25;
36 static final int WILDCARD_SUBSCRIPTION_AVAILABLE = 0x28;
37 static final int SUBSCRIPTION_IDENTIFIER_AVAILABLE = 0x29;
38 static final int SHARED_SUBSCRIPTION_AVAILABLE = 0x2A;
39
40
41 static final int SERVER_KEEP_ALIVE = 0x13;
42 static final int RECEIVE_MAXIMUM = 0x21;
43 static final int TOPIC_ALIAS_MAXIMUM = 0x22;
44 static final int TOPIC_ALIAS = 0x23;
45
46
47 static final int PUBLICATION_EXPIRY_INTERVAL = 0x02;
48 static final int SESSION_EXPIRY_INTERVAL = 0x11;
49 static final int WILL_DELAY_INTERVAL = 0x18;
50 static final int MAXIMUM_PACKET_SIZE = 0x27;
51
52
53 static final int SUBSCRIPTION_IDENTIFIER = 0x0B;
54
55
56 static final int CONTENT_TYPE = 0x03;
57 static final int RESPONSE_TOPIC = 0x08;
58 static final int ASSIGNED_CLIENT_IDENTIFIER = 0x12;
59 static final int AUTHENTICATION_METHOD = 0x15;
60 static final int RESPONSE_INFORMATION = 0x1A;
61 static final int SERVER_REFERENCE = 0x1C;
62 static final int REASON_STRING = 0x1F;
63 static final int USER_PROPERTY = 0x26;
64
65
66 static final int CORRELATION_DATA = 0x09;
67 static final int AUTHENTICATION_DATA = 0x16;
68
69 @Deprecated
70 public enum MqttPropertyType {
71
72 PAYLOAD_FORMAT_INDICATOR(MqttProperties.PAYLOAD_FORMAT_INDICATOR),
73 REQUEST_PROBLEM_INFORMATION(MqttProperties.REQUEST_PROBLEM_INFORMATION),
74 REQUEST_RESPONSE_INFORMATION(MqttProperties.REQUEST_RESPONSE_INFORMATION),
75 MAXIMUM_QOS(MqttProperties.MAXIMUM_QOS),
76 RETAIN_AVAILABLE(MqttProperties.RETAIN_AVAILABLE),
77 WILDCARD_SUBSCRIPTION_AVAILABLE(MqttProperties.WILDCARD_SUBSCRIPTION_AVAILABLE),
78 SUBSCRIPTION_IDENTIFIER_AVAILABLE(MqttProperties.SUBSCRIPTION_IDENTIFIER_AVAILABLE),
79 SHARED_SUBSCRIPTION_AVAILABLE(MqttProperties.SHARED_SUBSCRIPTION_AVAILABLE),
80
81
82 SERVER_KEEP_ALIVE(MqttProperties.SERVER_KEEP_ALIVE),
83 RECEIVE_MAXIMUM(MqttProperties.RECEIVE_MAXIMUM),
84 TOPIC_ALIAS_MAXIMUM(MqttProperties.TOPIC_ALIAS_MAXIMUM),
85 TOPIC_ALIAS(MqttProperties.TOPIC_ALIAS),
86
87
88 PUBLICATION_EXPIRY_INTERVAL(MqttProperties.PUBLICATION_EXPIRY_INTERVAL),
89 SESSION_EXPIRY_INTERVAL(MqttProperties.SESSION_EXPIRY_INTERVAL),
90 WILL_DELAY_INTERVAL(MqttProperties.WILL_DELAY_INTERVAL),
91 MAXIMUM_PACKET_SIZE(MqttProperties.MAXIMUM_PACKET_SIZE),
92
93
94 SUBSCRIPTION_IDENTIFIER(MqttProperties.SUBSCRIPTION_IDENTIFIER),
95
96
97 CONTENT_TYPE(MqttProperties.CONTENT_TYPE),
98 RESPONSE_TOPIC(MqttProperties.RESPONSE_TOPIC),
99 ASSIGNED_CLIENT_IDENTIFIER(MqttProperties.ASSIGNED_CLIENT_IDENTIFIER),
100 AUTHENTICATION_METHOD(MqttProperties.AUTHENTICATION_METHOD),
101 RESPONSE_INFORMATION(MqttProperties.RESPONSE_INFORMATION),
102 SERVER_REFERENCE(MqttProperties.SERVER_REFERENCE),
103 REASON_STRING(MqttProperties.REASON_STRING),
104 USER_PROPERTY(MqttProperties.USER_PROPERTY),
105
106
107 CORRELATION_DATA(MqttProperties.CORRELATION_DATA),
108 AUTHENTICATION_DATA(MqttProperties.AUTHENTICATION_DATA);
109
110 private static final MqttPropertyType[] VALUES;
111
112 static {
113 VALUES = new MqttPropertyType[43];
114 for (MqttPropertyType v : values()) {
115 VALUES[v.value] = v;
116 }
117 }
118
119 private final int value;
120
121 MqttPropertyType(int value) {
122 this.value = value;
123 }
124
125 public int value() {
126 return value;
127 }
128
129 public static MqttPropertyType valueOf(int type) {
130 MqttPropertyType t = null;
131 try {
132 t = VALUES[type];
133 } catch (ArrayIndexOutOfBoundsException ignored) {
134
135 }
136 if (t == null) {
137 throw new IllegalArgumentException("unknown property type: " + type);
138 }
139 return t;
140 }
141 }
142
143 public static final MqttProperties NO_PROPERTIES = new MqttProperties(false);
144
145 static MqttProperties withEmptyDefaults(MqttProperties properties) {
146 if (properties == null) {
147 return NO_PROPERTIES;
148 }
149 return properties;
150 }
151
152
153
154
155
156
157 public abstract static class MqttProperty<T> {
158 final T value;
159 final int propertyId;
160
161 protected MqttProperty(int propertyId, T value) {
162 this.propertyId = propertyId;
163 this.value = value;
164 }
165
166
167
168
169
170
171 public T value() {
172 return value;
173 }
174
175
176
177
178
179 public int propertyId() {
180 return propertyId;
181 }
182
183 @Override
184 public int hashCode() {
185 return propertyId + 31 * value.hashCode();
186 }
187
188 @Override
189 public boolean equals(Object obj) {
190 if (this == obj) {
191 return true;
192 }
193 if (obj == null || getClass() != obj.getClass()) {
194 return false;
195 }
196 MqttProperty that = (MqttProperty) obj;
197 return this.propertyId == that.propertyId && this.value.equals(that.value);
198 }
199 }
200
201 public static final class IntegerProperty extends MqttProperty<Integer> {
202
203 public IntegerProperty(int propertyId, Integer value) {
204 super(propertyId, value);
205 }
206
207 @Override
208 public String toString() {
209 return "IntegerProperty(" + propertyId + ", " + value + ")";
210 }
211 }
212
213 public static final class StringProperty extends MqttProperty<String> {
214
215 public StringProperty(int propertyId, String value) {
216 super(propertyId, value);
217 }
218
219 @Override
220 public String toString() {
221 return "StringProperty(" + propertyId + ", " + value + ")";
222 }
223 }
224
225 public static final class StringPair {
226 public final String key;
227 public final String value;
228
229 public StringPair(String key, String value) {
230 this.key = key;
231 this.value = value;
232 }
233
234 @Override
235 public int hashCode() {
236 return key.hashCode() + 31 * value.hashCode();
237 }
238
239 @Override
240 public boolean equals(Object obj) {
241 if (this == obj) {
242 return true;
243 }
244 if (obj == null || getClass() != obj.getClass()) {
245 return false;
246 }
247 StringPair that = (StringPair) obj;
248
249 return that.key.equals(this.key) && that.value.equals(this.value);
250 }
251 }
252
253
254
255 public static final class UserProperties extends MqttProperty<List<StringPair>> {
256 public UserProperties() {
257 super(USER_PROPERTY, new ArrayList<>());
258 }
259
260
261
262
263
264
265 public UserProperties(Collection<StringPair> values) {
266 this();
267 this.value.addAll(values);
268 }
269
270 private static UserProperties fromUserPropertyCollection(Collection<UserProperty> properties) {
271 UserProperties userProperties = new UserProperties();
272 for (UserProperty property: properties) {
273 userProperties.add(new StringPair(property.value.key, property.value.value));
274 }
275 return userProperties;
276 }
277
278 public void add(StringPair pair) {
279 this.value.add(pair);
280 }
281
282 public void add(String key, String value) {
283 this.value.add(new StringPair(key, value));
284 }
285
286 @Override
287 public String toString() {
288 StringBuilder builder = new StringBuilder("UserProperties(");
289 boolean first = true;
290 for (StringPair pair: value) {
291 if (!first) {
292 builder.append(", ");
293 }
294 builder.append(pair.key + "->" + pair.value);
295 first = false;
296 }
297 builder.append(")");
298 return builder.toString();
299 }
300 }
301
302 public static final class UserProperty extends MqttProperty<StringPair> {
303 public UserProperty(String key, String value) {
304 super(USER_PROPERTY, new StringPair(key, value));
305 }
306
307 @Override
308 public String toString() {
309 return "UserProperty(" + value.key + ", " + value.value + ")";
310 }
311 }
312
313 public static final class BinaryProperty extends MqttProperty<byte[]> {
314
315 public BinaryProperty(int propertyId, byte[] value) {
316 super(propertyId, value);
317 }
318
319 @Override
320 public String toString() {
321 return "BinaryProperty(" + propertyId + ", " + value.length + " bytes)";
322 }
323 }
324
325 public MqttProperties() {
326 this(true);
327 }
328
329 private MqttProperties(boolean canModify) {
330 this.canModify = canModify;
331 }
332
333 private IntObjectHashMap<MqttProperty> props;
334 private List<UserProperty> userProperties;
335 private List<IntegerProperty> subscriptionIds;
336 private final boolean canModify;
337
338 public void add(MqttProperty property) {
339 if (!canModify) {
340 throw new UnsupportedOperationException("adding property isn't allowed");
341 }
342 IntObjectHashMap<MqttProperty> props = this.props;
343 if (property.propertyId == USER_PROPERTY) {
344 List<UserProperty> userProperties = this.userProperties;
345 if (userProperties == null) {
346 userProperties = new ArrayList<>(1);
347 this.userProperties = userProperties;
348 }
349 if (property instanceof UserProperty) {
350 userProperties.add((UserProperty) property);
351 } else if (property instanceof UserProperties) {
352 for (StringPair pair: ((UserProperties) property).value) {
353 userProperties.add(new UserProperty(pair.key, pair.value));
354 }
355 } else {
356 throw new IllegalArgumentException("User property must be of UserProperty or UserProperties type");
357 }
358 } else if (property.propertyId == SUBSCRIPTION_IDENTIFIER) {
359 List<IntegerProperty> subscriptionIds = this.subscriptionIds;
360 if (subscriptionIds == null) {
361 subscriptionIds = new ArrayList<>(1);
362 this.subscriptionIds = subscriptionIds;
363 }
364 if (property instanceof IntegerProperty) {
365 subscriptionIds.add((IntegerProperty) property);
366 } else {
367 throw new IllegalArgumentException("Subscription ID must be an integer property");
368 }
369 } else {
370 if (props == null) {
371 props = new IntObjectHashMap<MqttProperty>();
372 this.props = props;
373 }
374 props.put(property.propertyId, property);
375 }
376 }
377
378 public Collection<? extends MqttProperty> listAll() {
379 IntObjectHashMap<MqttProperty> props = this.props;
380 if (props == null && subscriptionIds == null && userProperties == null) {
381 return Collections.emptyList();
382 }
383 if (subscriptionIds == null && userProperties == null) {
384 return props.values();
385 }
386 if (props == null && userProperties == null) {
387 return subscriptionIds;
388 }
389 List<MqttProperty> propValues = new ArrayList<MqttProperty>(props != null ? props.size() : 1);
390 if (props != null) {
391 propValues.addAll(props.values());
392 }
393 if (subscriptionIds != null) {
394 propValues.addAll(subscriptionIds);
395 }
396 if (userProperties != null) {
397 propValues.add(UserProperties.fromUserPropertyCollection(userProperties));
398 }
399 return propValues;
400 }
401
402 public boolean isEmpty() {
403 IntObjectHashMap<MqttProperty> props = this.props;
404 return props == null || props.isEmpty();
405 }
406
407
408
409
410
411
412
413
414 public MqttProperty getProperty(int propertyId) {
415 if (propertyId == USER_PROPERTY) {
416
417 List<UserProperty> userProperties = this.userProperties;
418 if (userProperties == null) {
419 return null;
420 }
421 return UserProperties.fromUserPropertyCollection(userProperties);
422 }
423 if (propertyId == SUBSCRIPTION_IDENTIFIER) {
424 List<IntegerProperty> subscriptionIds = this.subscriptionIds;
425 if (subscriptionIds == null || subscriptionIds.isEmpty()) {
426 return null;
427 }
428 return subscriptionIds.get(0);
429 }
430 IntObjectHashMap<MqttProperty> props = this.props;
431 return props == null ? null : props.get(propertyId);
432 }
433
434
435
436
437
438
439
440
441
442 public List<? extends MqttProperty> getProperties(int propertyId) {
443 if (propertyId == USER_PROPERTY) {
444 return userProperties == null ? Collections.emptyList() : userProperties;
445 }
446 if (propertyId == SUBSCRIPTION_IDENTIFIER) {
447 return subscriptionIds == null ? Collections.emptyList() : subscriptionIds;
448 }
449 IntObjectHashMap<MqttProperty> props = this.props;
450 return (props == null || !props.containsKey(propertyId)) ?
451 Collections.emptyList() :
452 Collections.singletonList(props.get(propertyId));
453 }
454 }