public static MqttConnectMessage connect(ConnectOptions options) { MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.CONNECT, false, MqttQoS.AT_MOST_ONCE, false, 10); MqttConnectVariableHeader variableHeader = new MqttConnectVariableHeader(options.version().protocolName(), options.version().protocolLevel(), options.userName() != null, options.password() != null, options.will() == null ? false : options.will().isRetain(), options.will() == null ? 0 : options.will().qos().value(), options.will() != null, options.cleanSession(), options.keepAliveTimeSeconds()); MqttConnectPayload payload = new MqttConnectPayload(Strings.nullToEmpty(options.clientId()), options.will() == null ? "" : options.will().topicName(), options.will() == null ? "" : new String(options.will().message(), CharsetUtil.UTF_8), Strings.nullToEmpty(options.userName()), Strings.nullToEmpty(options.password())); return new MqttConnectMessage(fixedHeader, variableHeader, payload); }
private MqttConnAckMessage executeNormalChannelRead0(String clientId, boolean cleanSession, ChannelId channelId) throws Exception { MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.CONNECT, false, MqttQoS.AT_MOST_ONCE, false, 10); MqttConnectVariableHeader variableHeader = new MqttConnectVariableHeader("MQTT", 4, true, true, true, 0, true, cleanSession, 60); MqttConnectPayload payload = new MqttConnectPayload(clientId, "willtopic", "willmessage", "username", "password"); MqttConnectMessage msg = new MqttConnectMessage(fixedHeader, variableHeader, payload); ChannelId cid = channelId == null ? TestUtil.newChannelId(clientId, false) : channelId; EmbeddedChannel channel = new EmbeddedChannel(cid, new ConnectReceiver()); channel.writeInbound(msg); return channel.readOutbound(); }
@Override public void channelRead(ChannelHandlerContext ctx, Object message) { MqttMessage msg = (MqttMessage) message; MqttMessageType messageType = msg.fixedHeader().messageType(); try { switch (messageType) { case PUBLISH: LOG.info("Received a message of type {}", messageType); handlePublish((MqttPublishMessage) msg); return; default: LOG.info("Received a message of type {}", messageType); } } catch (Exception ex) { LOG.error("Bad error in processing the message", ex); } }
@Override public void channelRead(ChannelHandlerContext ctx, Object message) { MqttMessage msg = (MqttMessage) message; MqttMessageType type = msg.fixedHeader().messageType(); try { switch (type) { case PUBLISH: LOG.info("Received a message of type {}", type); handlePublish((MqttPublishMessage) msg); return; default: LOG.info("Received a message of type {}", type); } } catch (Exception ex) { LOG.error("Bad error in processing the message", ex); } }
private void sendPubAck(String clientId, int messageID) { LOG.trace("sendPubAck invoked"); MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBACK, false, AT_MOST_ONCE, false, 0); MqttPubAckMessage pubAckMessage = new MqttPubAckMessage(fixedHeader, from(messageID)); try { if (connectionDescriptors == null) { throw new RuntimeException("Internal bad error, found connectionDescriptors to null while it should " + "be initialized, somewhere it's overwritten!!"); } LOG.trace("connected clientIDs are {}", connectionDescriptors.getConnectedClientIds()); if (!connectionDescriptors.isConnected(clientId)) { throw new RuntimeException(String.format("Can't find a ConnectionDescriptor for client %s in cache %s", clientId, connectionDescriptors)); } connectionDescriptors.sendMessage(pubAckMessage, messageID, clientId); } catch (Throwable t) { LOG.error(null, t); } }
@Override public void channelRead(ChannelHandlerContext ctx, Object message) { MqttMessage msg = (MqttMessage) message; MqttMessageType messageType = msg.fixedHeader().messageType(); switch (messageType) { case PUBLISH: this.publishesMetrics.mark(); break; case SUBSCRIBE: this.subscribeMetrics.mark(); break; case CONNECT: this.connectedClientsMetrics.inc(); break; case DISCONNECT: this.connectedClientsMetrics.dec(); break; default: break; } ctx.fireChannelRead(message); }
/** * Convert Map to InternalMessage * * @param map Map * @return InternalMessage */ public static InternalMessage mapToInternal(Map<String, String> map) { if (map == null || map.isEmpty()) return null; int type = Integer.parseInt(map.get("type")); if (type == MqttMessageType.PUBLISH.value()) { byte[] payload = null; if (map.get("payload") != null) try { payload = map.get("payload").getBytes("ISO-8859-1"); } catch (UnsupportedEncodingException ignore) { } return new InternalMessage<>( MqttMessageType.PUBLISH, BooleanUtils.toBoolean(map.getOrDefault("dup", "0"), "1", "0"), MqttQoS.valueOf(Integer.parseInt(map.getOrDefault("qos", "0"))), BooleanUtils.toBoolean(map.getOrDefault("retain", "0"), "1", "0"), MqttVersion.valueOf(map.getOrDefault("version", MqttVersion.MQTT_3_1_1.toString())), map.get("clientId"), map.get("userName"), null, new Publish( map.get("topicName"), Integer.parseInt(map.getOrDefault("packetId", "0")), payload )); } else if (type == MqttMessageType.PUBREL.value()) { return new InternalMessage<>( MqttMessageType.PUBREL, false, MqttQoS.AT_LEAST_ONCE, false, MqttVersion.valueOf(map.getOrDefault("version", MqttVersion.MQTT_3_1_1.toString())), map.get("clientId"), map.get("userName"), null, new PacketId(Integer.parseInt(map.getOrDefault("packetId", "0")))); } else { throw new IllegalArgumentException("Invalid in-flight MQTT message type: " + MqttMessageType.valueOf(type)); } }
/** * Convert InternalMessage to Map * * @param msg InternalMessage * @return Map */ public static Map<String, String> internalToMap(InternalMessage msg) { Map<String, String> map = new HashMap<>(); if (msg == null) return map; if (msg.getMessageType() == MqttMessageType.PUBLISH) { Publish publish = (Publish) msg.getPayload(); map.put("type", String.valueOf(MqttMessageType.PUBLISH.value())); map.put("retain", BooleanUtils.toString(msg.isRetain(), "1", "0")); map.put("qos", String.valueOf(msg.getQos().value())); map.put("dup", BooleanUtils.toString(msg.isDup(), "1", "0")); map.put("version", msg.getVersion().toString()); if (!msg.isRetain()) map.put("clientId", msg.getClientId()); map.put("userName", msg.getUserName()); map.put("topicName", publish.getTopicName()); if (!msg.isRetain()) map.put("packetId", String.valueOf(publish.getPacketId())); if (publish.getPayload() != null && publish.getPayload().length > 0) try { map.put("payload", new String(publish.getPayload(), "ISO-8859-1")); } catch (UnsupportedEncodingException ignore) { } return map; } else if (msg.getMessageType() == MqttMessageType.PUBREL) { PacketId packetId = (PacketId) msg.getPayload(); map.put("type", String.valueOf(MqttMessageType.PUBREL.value())); map.put("version", msg.getVersion().toString()); map.put("clientId", msg.getClientId()); map.put("userName", msg.getUserName()); map.put("packetId", String.valueOf(packetId.getPacketId())); return map; } else { throw new IllegalArgumentException("Invalid in-flight MQTT message type: " + msg.getMessageType()); } }
@Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof MqttMessage) { MqttMessage mqtt = (MqttMessage) msg; if (StringUtils.isBlank(this.clientId) && mqtt.fixedHeader().messageType() == MqttMessageType.CONNECT) { this.clientId = ((MqttConnectPayload) mqtt.payload()).clientId(); } if (StringUtils.isNotBlank(this.clientId)) { this.metrics.measurement(this.clientId, this.brokerId, MessageDirection.IN, mqtt.fixedHeader().messageType()); } this.metrics.measurement(this.brokerId, MessageDirection.IN, mqtt.fixedHeader().messageType()); } ctx.fireChannelRead(msg); }
@Override public void measurement(String clientId, String brokerId, MessageDirection direction, MqttMessageType type) { Point point = Point.measurement("mqtt_client_" + clientId) .time(System.currentTimeMillis(), TimeUnit.MILLISECONDS) .tag("broker", brokerId) .tag("direction", direction.toString()) .tag("type", getMessageTypeName(type)) .field("count", 1L) .build(); this.influxDB.write(this.dbName, "default", point); }
@Override public void measurement(String brokerId, MessageDirection direction, MqttMessageType type) { Point point = Point.measurement("mqtt_broker_" + brokerId) .time(System.currentTimeMillis(), TimeUnit.MILLISECONDS) .tag("direction", direction.toString()) .tag("type", getMessageTypeName(type)) .field("count", 1L) .build(); this.influxDB.write(this.dbName, "default", point); }
protected String getMessageTypeName(MqttMessageType type) { switch (type) { case CONNECT: return "connect"; case CONNACK: return "connack"; case PUBLISH: return "publish"; case PUBACK: return "puback"; case PUBREC: return "pubrec"; case PUBREL: return "pubrel"; case PUBCOMP: return "pubcomp"; case SUBSCRIBE: return "subscribe"; case SUBACK: return "suback"; case UNSUBSCRIBE: return "unsubscribe"; case UNSUBACK: return "unsuback"; case PINGREQ: return "pingreq"; case PINGRESP: return "pingresp"; case DISCONNECT: return "disconnect"; default: return "unknown"; } }
public static void find(){ Set<Class<?>> validatorClasses = find(Validator.class, VALIDATOR_PKG); Iterator<Class<?>> iterator = validatorClasses.iterator(); while(iterator.hasNext()){ Class<?> cls = iterator.next(); Validator validator = cls.getAnnotation(Validator.class); MqttMessageType type = validator.type(); switch (type){ case CONNECT: buildConnectValidatorCache(cls); break; } } }
public MqttMessage doMessage(Channel channel, MqttMessage msg) { String channelId = channel.id().asLongText(); logger.debug("MQTT PINGREQ " + channelId); // 更新最新连接时间 ApplicationContext.updateChannelConTime(channelId); MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PINGRESP, false, MqttQoS.AT_MOST_ONCE, false, 0); MqttMessage message = new MqttMessage(fixedHeader); return message; }
@Override public void onMsg(SessionCtrlMsg msg) throws SessionException { if (msg instanceof SessionCloseMsg) { pushToNetwork( new MqttMessage(new MqttFixedHeader(MqttMessageType.DISCONNECT, false, MqttQoS.AT_MOST_ONCE, false, 0))); channel.close(); } }
private MqttPublishMessage createMqttPublishMsg(DeviceSessionCtx ctx, String topic, JsonElement json) { MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(MqttMessageType.PUBLISH, false, MqttQoS.AT_LEAST_ONCE, false, 0); MqttPublishVariableHeader header = new MqttPublishVariableHeader(topic, ctx.nextMsgId()); ByteBuf payload = ALLOCATOR.buffer(); payload.writeBytes(GSON.toJson(json).getBytes(UTF8)); return new MqttPublishMessage(mqttFixedHeader, header, payload); }
public static MqttConnAckMessage connack(MqttConnectReturnCode returnCode, boolean sessionPresent) { MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.CONNACK, false, MqttQoS.AT_MOST_ONCE, false, 2); MqttConnAckVariableHeader variableHeader = new MqttConnAckVariableHeader(returnCode, sessionPresent); return new MqttConnAckMessage(fixedHeader, variableHeader); }
public static MqttPubAckMessage puback(int messageId) { MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBACK, false, MqttQoS.AT_MOST_ONCE, false, 2); MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(messageId); return new MqttPubAckMessage(fixedHeader, variableHeader); }
public static MqttMessage pubrec(int messageId) { MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBREC, false, MqttQoS.AT_MOST_ONCE, false, 2); MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(messageId); return new MqttMessage(fixedHeader, variableHeader); }
public static MqttMessage pubrel(int messageId) { MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBREL, false, MqttQoS.AT_LEAST_ONCE, false, 2); MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(messageId); return new MqttMessage(fixedHeader, variableHeader); }
public static MqttMessage pubcomp(int messageId) { MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBCOMP, false, MqttQoS.AT_MOST_ONCE, false, 2); MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(messageId); return new MqttMessage(fixedHeader, variableHeader); }
public static MqttSubscribeMessage subscribe(int messageId, MqttTopicSubscription... topicSubscriptions) { int topicNameSize = 0; int topicCount = topicSubscriptions.length; for (MqttTopicSubscription item : topicSubscriptions) { topicNameSize += item.topicName().getBytes(CharsetUtil.UTF_8).length; } MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.SUBSCRIBE, false, MqttQoS.AT_LEAST_ONCE, false, 2 + topicNameSize + topicCount); MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(messageId); MqttSubscribePayload payload = new MqttSubscribePayload(Lists.newArrayList(topicSubscriptions)); return new MqttSubscribeMessage(fixedHeader, variableHeader, payload); }
public static MqttSubAckMessage suback(int messageId, List<Integer> grantedQoSLevels) { MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.SUBACK, false, MqttQoS.AT_MOST_ONCE, false, 2 + grantedQoSLevels.size()); MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(messageId); MqttSubAckPayload payload = new MqttSubAckPayload(grantedQoSLevels); return new MqttSubAckMessage(fixedHeader, variableHeader, payload); }
public static MqttUnsubAckMessage unsuback(int messageId) { MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.UNSUBACK, false, MqttQoS.AT_MOST_ONCE, false, 2); MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(messageId); return new MqttUnsubAckMessage(fixedHeader, variableHeader); }
/** * Sends PUBREC packet to server * * @param publishMessage a PUBLISH message to acknowledge */ void publishReceived(MqttPublishMessage publishMessage) { MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBREC, false, AT_MOST_ONCE, false, 0); MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(publishMessage.messageId()); io.netty.handler.codec.mqtt.MqttMessage pubrec = MqttMessageFactory.newMessage(fixedHeader, variableHeader, null); qos2inbound.put(publishMessage.messageId(), publishMessage); this.write(pubrec); }
/** * Sends the PUBREL message to server * * @param publishMessageId identifier of the PUBLISH message to acknowledge */ void publishRelease(int publishMessageId) { MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBREL, false, MqttQoS.AT_LEAST_ONCE, false, 0); MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(publishMessageId); io.netty.handler.codec.mqtt.MqttMessage pubrel = MqttMessageFactory.newMessage(fixedHeader, variableHeader, null); qos2outbound.put(publishMessageId, pubrel); this.write(pubrel); }
private MqttMessage createConnectPacket(MqttClientOptions options) { MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.CONNECT, false, MqttQoS.AT_MOST_ONCE, false, 0); MqttConnectVariableHeader variableHeader = new MqttConnectVariableHeader( PROTOCOL_NAME, PROTOCOL_VERSION, options.hasUsername(), options.hasPassword(), options.isWillRetain(), options.getWillQoS(), options.isWillFlag(), options.isCleanSession(), options.getKeepAliveTimeSeconds() ); MqttConnectPayload payload = new MqttConnectPayload( options.getClientId() == null ? "" : options.getClientId(), options.getWillTopic(), options.getWillMessage() != null ? options.getWillMessage().getBytes(StandardCharsets.UTF_8) : null, options.hasUsername() ? options.getUsername() : null, options.hasPassword() ? options.getPassword().getBytes(StandardCharsets.UTF_8) : null ); return MqttMessageFactory.newMessage(fixedHeader, variableHeader, payload); }
@Override public void messageReceived(ChannelHandlerContext ctx, MqttMessage msg) { this.stats.incrementMqttStat(); MqttMessageType messageType = msg.fixedHeader().messageType(); switch (messageType) { case PUBLISH : MqttPublishMessage publishMessage = (MqttPublishMessage) msg; String topic = publishMessage.variableHeader().topicName(); switch (topic.toLowerCase()) { case "hardware" : hardware.messageReceived(ctx, state, publishMessage); break; } break; case PINGREQ : ctx.writeAndFlush( MqttMessageFactory.newMessage(msg.fixedHeader(), msg.variableHeader(), null), ctx.voidPromise()); break; case DISCONNECT : log.trace("Got disconnect. Closing..."); ctx.close(); break; } }
void sendPublishProtocolControlMessage(int messageId, MqttMessageType messageType) { MqttQoS qos = (messageType == MqttMessageType.PUBREL) ? MqttQoS.AT_LEAST_ONCE : MqttQoS.AT_MOST_ONCE; MqttFixedHeader fixedHeader = new MqttFixedHeader(messageType, false, qos, // Spec requires 01 in header for rel false, 0); MqttPubAckMessage rel = new MqttPubAckMessage(fixedHeader, MqttMessageIdVariableHeader.from(messageId)); sendToClient(rel); }
void handleSubscribe(MqttSubscribeMessage message) throws Exception { MQTTSubscriptionManager subscriptionManager = session.getSubscriptionManager(); int[] qos = subscriptionManager.addSubscriptions(message.payload().topicSubscriptions()); MqttFixedHeader header = new MqttFixedHeader(MqttMessageType.SUBACK, false, MqttQoS.AT_MOST_ONCE, false, 0); MqttSubAckMessage ack = new MqttSubAckMessage(header, message.variableHeader(), new MqttSubAckPayload(qos)); sendToClient(ack); }
protected void send(int messageId, String topicName, int qosLevel, boolean isRetain, ByteBuf payload, int deliveryCount) { boolean redelivery = qosLevel == 0 ? false : (deliveryCount > 0); MqttFixedHeader header = new MqttFixedHeader(MqttMessageType.PUBLISH, redelivery, MqttQoS.valueOf(qosLevel), isRetain, 0); MqttPublishVariableHeader varHeader = new MqttPublishVariableHeader(topicName, messageId); MqttMessage publish = new MqttPublishMessage(header, varHeader, payload); sendToClient(publish); }
public boolean sendMessage(MqttMessage message, Integer messageID, String clientID) { final MqttMessageType messageType = message.fixedHeader().messageType(); try { if (messageID != null) { LOG.info("Sending {} message CId=<{}>, messageId={}", messageType, clientID, messageID); } else { LOG.debug("Sending {} message CId=<{}>", messageType, clientID); } ConnectionDescriptor descriptor = connectionDescriptors.get(clientID); if (descriptor == null) { if (messageID != null) { LOG.error("Client has just disconnected. {} message could not be sent. CId=<{}>, messageId={}", messageType, clientID, messageID); } else { LOG.error("Client has just disconnected. {} could not be sent. CId=<{}>", messageType, clientID); } /* * If the client has just disconnected, its connection descriptor will be null. We * don't have to make the broker crash: we'll just discard the PUBACK message. */ return false; } descriptor.writeAndFlush(message); return true; } catch (Throwable e) { String errorMsg = "Unable to send " + messageType + " message. CId=<" + clientID + ">"; if (messageID != null) { errorMsg += ", messageId=" + messageID; } LOG.error(errorMsg, e); return false; } }
@PermitAll @POST /** * Handle MQTT Un-Subscribe Request in RESTful style */ public ResultEntity<Boolean> unsubscribe(@PathParam("clientId") String clientId, @Auth UserPrincipal user, @QueryParam("protocol") @DefaultValue("4") byte protocol, @QueryParam("packetId") @DefaultValue("0") int packetId, List<String> topics) { String userName = user.getName(); MqttVersion version = MqttVersion.fromProtocolLevel(protocol); // HTTP interface require valid Client Id if (!this.validator.isClientIdValid(clientId)) { logger.debug("Protocol violation: Client id {} not valid based on configuration", clientId); throw new ValidateException(new ErrorEntity(ErrorCode.INVALID)); } // Validate Topic Filter based on configuration for (String topic : topics) { if (!this.validator.isTopicFilterValid(topic)) { logger.debug("Protocol violation: Client {} un-subscription {} is not valid based on configuration", clientId, topic); throw new ValidateException(new ErrorEntity(ErrorCode.INVALID)); } } logger.debug("Message received: Received UNSUBSCRIBE message from client {} user {} topics {}", clientId, userName, ArrayUtils.toString(topics)); // The Topic Filters (whether they contain wildcards or not) supplied in an UNSUBSCRIBE packet MUST be // compared character-by-character with the current set of Topic Filters held by the Server for the Client. If // any filter matches exactly then its owning Subscription is deleted, otherwise no additional processing // occurs // If a Server deletes a Subscription: // It MUST stop adding any new messages for delivery to the Client. //1 It MUST complete the delivery of any QoS 1 or QoS 2 messages which it has started to send to // the Client. // It MAY continue to deliver any existing messages buffered for delivery to the Client. topics.forEach(topic -> { logger.trace("Remove subscription: Remove client {} subscription with topic {}", clientId, topic); this.redis.removeSubscription(clientId, Topics.sanitize(topic)); }); // Pass message to 3rd party application Unsubscribe us = new Unsubscribe(packetId, topics); InternalMessage<Unsubscribe> m = new InternalMessage<>(MqttMessageType.UNSUBSCRIBE, false, MqttQoS.AT_LEAST_ONCE, false, version, clientId, null, null, us); this.communicator.sendToApplication(m); return new ResultEntity<>(true); }
public RequestRecorder(){ history = new ArrayList<MqttMessageType>(); }
public void record(MqttMessageType type){ history.add(type); }
public MqttMessageType getHistory(int index){ return (index > history.size() -1) ? null : history.get(index); }
public static MqttMessage pingresp() { MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PINGRESP, false, MqttQoS.AT_MOST_ONCE, false, 0); return new MqttMessage(fixedHeader); }
public static MqttMessage disconnect() { MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.DISCONNECT, false, MqttQoS.AT_MOST_ONCE, false, 2); return new MqttMessage(fixedHeader); }
public MqttEndpointImpl subscribeAcknowledge(int subscribeMessageId, List<MqttQoS> grantedQoSLevels) { this.checkConnected(); MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.SUBACK, false, MqttQoS.AT_MOST_ONCE, false, 0); MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(subscribeMessageId); MqttSubAckPayload payload = new MqttSubAckPayload(grantedQoSLevels.stream().mapToInt(MqttQoS::value).toArray()); io.netty.handler.codec.mqtt.MqttMessage suback = MqttMessageFactory.newMessage(fixedHeader, variableHeader, payload); this.write(suback); return this; }
private static MqttConnAckMessage createConnAckMessage(MqttConnectReturnCode code) { MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(MqttMessageType.CONNACK, false, MqttQoS.AT_MOST_ONCE, false, 2); MqttConnAckVariableHeader mqttConnAckVariableHeader = new MqttConnAckVariableHeader(code, true); return new MqttConnAckMessage(mqttFixedHeader, mqttConnAckVariableHeader); }