//
// ZYJMQTTMessage.m
// ZYJMQTTClient.framework
//
// Copyright © 2013-2017, Christoph Krey. All rights reserved.
//
// based on
//
// Copyright (c) 2011, 2013, 2lemetry LLC
//
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// which accompanies this distribution, and is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// Contributors:
//    Kyle Roche - initial API and implementation and/or initial documentation
//

#import "ZYJMQTTMessage.h"
#import "ZYJMQTTProperties.h"

#import "ZYJMQTTLog.h"

@implementation ZYJMQTTMessage

+ (ZYJMQTTMessage *)connectMessageWithClientId:(NSString *)clientId
                                   userName:(NSString *)userName
                                   password:(NSString *)password
                                  keepAlive:(NSInteger)keepAlive
                               cleanSession:(BOOL)cleanSessionFlag
                                       will:(BOOL)will
                                  willTopic:(NSString *)willTopic
                                    willMsg:(NSData *)willMsg
                                    willQoS:(ZYJMQTTQosLevel)willQoS
                                 willRetain:(BOOL)willRetainFlag
                              protocolLevel:(ZYJMQTTProtocolVersion)protocolLevel
                      sessionExpiryInterval:(NSNumber *)sessionExpiryInterval
                                 authMethod:(NSString *)authMethod
                                   authData:(NSData *)authData
                  requestProblemInformation:(NSNumber *)requestProblemInformation
                          willDelayInterval:(NSNumber *)willDelayInterval
                 requestResponseInformation:(NSNumber *)requestResponseInformation
                             receiveMaximum:(NSNumber *)receiveMaximum
                          topicAliasMaximum:(NSNumber *)topicAliasMaximum
                               userProperty:(NSDictionary<NSString *,NSString *> *)userProperty
                          maximumPacketSize:(NSNumber *)maximumPacketSize {
    /*
     * setup flags w/o basic plausibility checks
     *
     */
    UInt8 flags = 0x00;

    if (cleanSessionFlag) {
        flags |= 0x02;
    }

    if (userName) {
        flags |= 0x80;
    }
    if (password) {
        flags |= 0x40;
    }

    if (will) {
        flags |= 0x04;
    }

    flags |= ((willQoS & 0x03) << 3);

    if (willRetainFlag) {
        flags |= 0x20;
    }

    NSMutableData* data = [NSMutableData data];

    switch (protocolLevel) {
        case ZYJMQTTProtocolVersion50:
            [data appendZYJMQTTString:@"ZYJMQTT"];
            [data appendByte:ZYJMQTTProtocolVersion50];
            break;

        case ZYJMQTTProtocolVersion311:
            [data appendZYJMQTTString:@"ZYJMQTT"];
            [data appendByte:ZYJMQTTProtocolVersion311];
            break;

        case ZYJMQTTProtocolVersion31:
            [data appendZYJMQTTString:@"MQIsdp"];
            [data appendByte:ZYJMQTTProtocolVersion31];
            break;

        case ZYJMQTTProtocolVersion0:
            [data appendZYJMQTTString:@""];
            [data appendByte:protocolLevel];
            break;

        default:
            [data appendZYJMQTTString:@"ZYJMQTT"];
            [data appendByte:protocolLevel];
            break;
    }
    [data appendByte:flags];
    [data appendUInt16BigEndian:keepAlive];

    if (protocolLevel == ZYJMQTTProtocolVersion50) {
        NSMutableData *properties = [[NSMutableData alloc] init];
        if (sessionExpiryInterval) {
            [properties appendByte:ZYJMQTTSessionExpiryInterval];
            [properties appendUInt32BigEndian:sessionExpiryInterval.unsignedIntValue];
        }
        if (authMethod) {
            [properties appendByte:ZYJMQTTAuthMethod];
            [properties appendZYJMQTTString:authMethod];
        }
        if (authData) {
            [properties appendByte:ZYJMQTTAuthData];
            [properties appendBinaryData:authData];
        }
        if (requestProblemInformation) {
            [properties appendByte:ZYJMQTTRequestProblemInformation];
            [properties appendByte:requestProblemInformation.unsignedIntValue];
        }
        if (willDelayInterval) {
            [properties appendByte:ZYJMQTTWillDelayInterval];
            [properties appendUInt32BigEndian:willDelayInterval.unsignedIntValue];
        }
        if (requestResponseInformation) {
            [properties appendByte:ZYJMQTTRequestResponseInformation];
            [properties appendByte:requestResponseInformation.unsignedIntValue];
        }
        if (receiveMaximum) {
            [properties appendByte:ZYJMQTTReceiveMaximum];
            [properties appendUInt16BigEndian:receiveMaximum.unsignedIntValue];
        }
        if (topicAliasMaximum) {
            [properties appendByte:ZYJMQTTTopicAliasMaximum];
            [properties appendUInt16BigEndian:topicAliasMaximum.unsignedIntValue];
        }
        if (userProperty) {
            for (NSString *key in userProperty.allKeys) {
                [properties appendByte:ZYJMQTTUserProperty];
                [properties appendZYJMQTTString:key];
                [properties appendZYJMQTTString:userProperty[key]];
            }
        }
        if (maximumPacketSize) {
            [properties appendByte:ZYJMQTTMaximumPacketSize];
            [properties appendUInt32BigEndian:maximumPacketSize.unsignedIntValue];
        }
        [data appendVariableLength:properties.length];
        [data appendData:properties];
    }

    [data appendZYJMQTTString:clientId];
    if (willTopic) {
        [data appendZYJMQTTString:willTopic];
    }
    if (willMsg) {
        [data appendUInt16BigEndian:willMsg.length];
        [data appendData:willMsg];
    }
    if (userName) {
        [data appendZYJMQTTString:userName];
    }
    if (password) {
        [data appendZYJMQTTString:password];
    }

    ZYJMQTTMessage *msg = [[ZYJMQTTMessage alloc] initWithType:ZYJMQTTConnect
                                                    data:data];
    return msg;
}

+ (ZYJMQTTMessage *)pingreqMessage {
    return [[ZYJMQTTMessage alloc] initWithType:ZYJMQTTPingreq];
}

+ (ZYJMQTTMessage *)disconnectMessage:(ZYJMQTTProtocolVersion)protocolLevel
                        returnCode:(ZYJMQTTReturnCode)returnCode
             sessionExpiryInterval:(NSNumber *)sessionExpiryInterval
                      reasonString:(NSString *)reasonString
                      userProperty:(NSDictionary<NSString *,NSString *> *)userProperty {
    NSMutableData* data = [NSMutableData data];
    if (protocolLevel == ZYJMQTTProtocolVersion50) {
        NSMutableData *properties = [[NSMutableData alloc] init];
        if (sessionExpiryInterval) {
            [properties appendByte:ZYJMQTTSessionExpiryInterval];
            [properties appendUInt32BigEndian:sessionExpiryInterval.unsignedIntValue];
        }
        if (reasonString) {
            [properties appendByte:ZYJMQTTReasonString];
            [properties appendZYJMQTTString:reasonString];
        }
        if (userProperty) {
            for (NSString *key in userProperty.allKeys) {
                [properties appendByte:ZYJMQTTUserProperty];
                [properties appendZYJMQTTString:key];
                [properties appendZYJMQTTString:userProperty[key]];
            }
        }
        if (returnCode != ZYJMQTTSuccess || properties.length > 0) {
            [data appendByte:returnCode];
        }
        if (properties.length > 0) {
            [data appendVariableLength:properties.length];
            [data appendData:properties];
        }
    }
    ZYJMQTTMessage *msg = [[ZYJMQTTMessage alloc] initWithType:ZYJMQTTDisconnect
                                                    data:data];
    return msg;
}

+ (ZYJMQTTMessage *)subscribeMessageWithMessageId:(UInt16)msgId
                                        topics:(NSDictionary *)topics
                                 protocolLevel:(ZYJMQTTProtocolVersion)protocolLevel
                        subscriptionIdentifier:(NSNumber *)subscriptionIdentifier {
    NSMutableData* data = [NSMutableData data];
    [data appendUInt16BigEndian:msgId];
    if (protocolLevel == ZYJMQTTProtocolVersion50) {
        NSMutableData *properties = [[NSMutableData alloc] init];
        if (subscriptionIdentifier) {
            [properties appendByte:ZYJMQTTSubscriptionIdentifier];
            [properties appendVariableLength:subscriptionIdentifier.unsignedLongValue];
        }
        [data appendVariableLength:properties.length];
        [data appendData:properties];
    }

    for (NSString *topic in topics.allKeys) {
        [data appendZYJMQTTString:topic];
        [data appendByte:[topics[topic] intValue]];
    }
    ZYJMQTTMessage* msg = [[ZYJMQTTMessage alloc] initWithType:ZYJMQTTSubscribe
                                                     qos:1
                                                    data:data];
    msg.mid = msgId;
    return msg;
}

+ (ZYJMQTTMessage *)unsubscribeMessageWithMessageId:(UInt16)msgId
                                          topics:(NSArray *)topics
                                   protocolLevel:(ZYJMQTTProtocolVersion)protocolLevel {
    NSMutableData* data = [NSMutableData data];
    [data appendUInt16BigEndian:msgId];
    for (NSString *topic in topics) {
        [data appendZYJMQTTString:topic];
    }
    ZYJMQTTMessage* msg = [[ZYJMQTTMessage alloc] initWithType:ZYJMQTTUnsubscribe
                                                     qos:1
                                                    data:data];
    msg.mid = msgId;
    return msg;
}

+ (ZYJMQTTMessage *)publishMessageWithData:(NSData *)payload
                                onTopic:(NSString *)topic
                                    qos:(ZYJMQTTQosLevel)qosLevel
                                  msgId:(UInt16)msgId
                             retainFlag:(BOOL)retain
                                dupFlag:(BOOL)dup
                          protocolLevel:(ZYJMQTTProtocolVersion)protocolLevel
                 payloadFormatIndicator:(NSNumber *)payloadFormatIndicator
              publicationExpiryInterval:(NSNumber *)publicationExpiryInterval
                             topicAlias:(NSNumber *)topicAlias
                          responseTopic:(NSString *)responseTopic
                        correlationData:(NSData *)correlationData
                           userProperty:(NSDictionary<NSString *,NSString *> *)userProperty
                            contentType:(NSString *)contentType {
    NSMutableData *data = [[NSMutableData alloc] init];
    [data appendZYJMQTTString:topic];
    if (msgId) [data appendUInt16BigEndian:msgId];
    if (protocolLevel == ZYJMQTTProtocolVersion50) {
        NSMutableData *properties = [[NSMutableData alloc] init];
        if (payloadFormatIndicator) {
            [properties appendByte:ZYJMQTTPayloadFormatIndicator];
            [properties appendByte:payloadFormatIndicator.unsignedIntValue];
        }
        if (publicationExpiryInterval) {
            [properties appendByte:ZYJMQTTPublicationExpiryInterval];
            [properties appendUInt32BigEndian:publicationExpiryInterval.unsignedIntValue];
        }
        if (topicAlias) {
            [properties appendByte:ZYJMQTTTopicAlias];
            [properties appendUInt16BigEndian:topicAlias.unsignedIntValue];
        }
        if (responseTopic) {
            [properties appendByte:ZYJMQTTResponseTopic];
            [properties appendZYJMQTTString:responseTopic];
        }
        if (correlationData) {
            [properties appendByte:ZYJMQTTCorrelationData];
            [properties appendBinaryData:correlationData];
        }
        if (userProperty) {
            for (NSString *key in userProperty.allKeys) {
                [properties appendByte:ZYJMQTTUserProperty];
                [properties appendZYJMQTTString:key];
                [properties appendZYJMQTTString:userProperty[key]];
            }
        }
        if (contentType) {
            [properties appendByte:ZYJMQTTContentType];
            [properties appendZYJMQTTString:contentType];
        }
        [data appendVariableLength:properties.length];
        [data appendData:properties];
    }
    [data appendData:payload];
    ZYJMQTTMessage *msg = [[ZYJMQTTMessage alloc] initWithType:ZYJMQTTPublish
                                                     qos:qosLevel
                                              retainFlag:retain
                                                 dupFlag:dup
                                                    data:data];
    msg.mid = msgId;
    return msg;
}

+ (ZYJMQTTMessage *)pubackMessageWithMessageId:(UInt16)msgId
                              protocolLevel:(ZYJMQTTProtocolVersion)protocolLevel
                                 returnCode:(ZYJMQTTReturnCode)returnCode
                               reasonString:(NSString *)reasonString
                               userProperty:(NSDictionary<NSString *,NSString *> *)userProperty {
    NSMutableData* data = [NSMutableData data];
    [data appendUInt16BigEndian:msgId];
    if (protocolLevel == ZYJMQTTProtocolVersion50) {
        NSMutableData *properties = [[NSMutableData alloc] init];
        if (reasonString) {
            [properties appendByte:ZYJMQTTReasonString];
            [properties appendZYJMQTTString:reasonString];
        }
        if (userProperty) {
            for (NSString *key in userProperty.allKeys) {
                [properties appendByte:ZYJMQTTUserProperty];
                [properties appendZYJMQTTString:key];
                [properties appendZYJMQTTString:userProperty[key]];
            }
        }
        [data appendByte:returnCode];
        [data appendVariableLength:properties.length];
        [data appendData:properties];
    }
    ZYJMQTTMessage *msg = [[ZYJMQTTMessage alloc] initWithType:ZYJMQTTPuback
                                                    data:data];
    msg.mid = msgId;
    return msg;
}

+ (ZYJMQTTMessage *)pubrecMessageWithMessageId:(UInt16)msgId
                              protocolLevel:(ZYJMQTTProtocolVersion)protocolLevel
                                 returnCode:(ZYJMQTTReturnCode)returnCode
                               reasonString:(NSString *)reasonString
                               userProperty:(NSDictionary<NSString *,NSString *> *)userProperty {
    NSMutableData* data = [NSMutableData data];
    [data appendUInt16BigEndian:msgId];
    if (protocolLevel == ZYJMQTTProtocolVersion50) {
        NSMutableData *properties = [[NSMutableData alloc] init];
        if (reasonString) {
            [properties appendByte:ZYJMQTTReasonString];
            [properties appendZYJMQTTString:reasonString];
        }
        if (userProperty) {
            for (NSString *key in userProperty.allKeys) {
                [properties appendByte:ZYJMQTTUserProperty];
                [properties appendZYJMQTTString:key];
                [properties appendZYJMQTTString:userProperty[key]];
            }
        }
        [data appendByte:returnCode];
        [data appendVariableLength:properties.length];
        [data appendData:properties];
    }
    ZYJMQTTMessage *msg = [[ZYJMQTTMessage alloc] initWithType:ZYJMQTTPubrec
                                                    data:data];
    msg.mid = msgId;
    return msg;
}

+ (ZYJMQTTMessage *)pubrelMessageWithMessageId:(UInt16)msgId
                              protocolLevel:(ZYJMQTTProtocolVersion)protocolLevel
                                 returnCode:(ZYJMQTTReturnCode)returnCode
                               reasonString:(NSString *)reasonString
                               userProperty:(NSDictionary<NSString *,NSString *> *)userProperty {
    NSMutableData* data = [NSMutableData data];
    [data appendUInt16BigEndian:msgId];
    if (protocolLevel == ZYJMQTTProtocolVersion50) {
        NSMutableData *properties = [[NSMutableData alloc] init];
        if (reasonString) {
            [properties appendByte:ZYJMQTTReasonString];
            [properties appendZYJMQTTString:reasonString];
        }
        if (userProperty) {
            for (NSString *key in userProperty.allKeys) {
                [properties appendByte:ZYJMQTTUserProperty];
                [properties appendZYJMQTTString:key];
                [properties appendZYJMQTTString:userProperty[key]];
            }
        }
        [data appendByte:returnCode];
        [data appendVariableLength:properties.length];
        [data appendData:properties];
    }
    ZYJMQTTMessage *msg = [[ZYJMQTTMessage alloc] initWithType:ZYJMQTTPubrel
                                                     qos:1
                                                    data:data];
    msg.mid = msgId;
    return msg;
}

+ (ZYJMQTTMessage *)pubcompMessageWithMessageId:(UInt16)msgId
                               protocolLevel:(ZYJMQTTProtocolVersion)protocolLevel
                                  returnCode:(ZYJMQTTReturnCode)returnCode
                                reasonString:(NSString *)reasonString
                                userProperty:(NSDictionary<NSString *,NSString *> *)userProperty {
    NSMutableData* data = [NSMutableData data];
    [data appendUInt16BigEndian:msgId];
    if (protocolLevel == ZYJMQTTProtocolVersion50) {
        NSMutableData *properties = [[NSMutableData alloc] init];
        if (reasonString) {
            [properties appendByte:ZYJMQTTReasonString];
            [properties appendZYJMQTTString:reasonString];
        }
        if (userProperty) {
            for (NSString *key in userProperty.allKeys) {
                [properties appendByte:ZYJMQTTUserProperty];
                [properties appendZYJMQTTString:key];
                [properties appendZYJMQTTString:userProperty[key]];
            }
        }
        [data appendByte:returnCode];
        [data appendVariableLength:properties.length];
        [data appendData:properties];
    }
    ZYJMQTTMessage *msg = [[ZYJMQTTMessage alloc] initWithType:ZYJMQTTPubcomp
                                                    data:data];
    msg.mid = msgId;
    return msg;
}

- (instancetype)init {
    self = [super init];
    self.type = 0;
    self.qos = ZYJMQTTQosLevelAtMostOnce;
    self.retainFlag = false;
    self.mid = 0;
    self.data = nil;
    return self;
}

- (instancetype)initWithType:(ZYJMQTTCommandType)type {
    self = [self init];
    self.type = type;
    return self;
}

- (instancetype)initWithType:(ZYJMQTTCommandType)type
                        data:(NSData *)data {
    self = [self init];
    self.type = type;
    self.data = data;
    return self;
}

- (instancetype)initWithType:(ZYJMQTTCommandType)type
                         qos:(ZYJMQTTQosLevel)qos
                        data:(NSData *)data {
    self = [self init];
    self.type = type;
    self.qos = qos;
    self.data = data;
    return self;
}

- (instancetype)initWithType:(ZYJMQTTCommandType)type
                         qos:(ZYJMQTTQosLevel)qos
                  retainFlag:(BOOL)retainFlag
                     dupFlag:(BOOL)dupFlag
                        data:(NSData *)data {
    self = [self init];
    self.type = type;
    self.qos = qos;
    self.retainFlag = retainFlag;
    self.dupFlag = dupFlag;
    self.data = data;
    return self;
}

- (NSData *)wireFormat {
    NSMutableData *buffer = [[NSMutableData alloc] init];

    // encode fixed header
    UInt8 header;
    header = (self.type & 0x0f) << 4;
    if (self.dupFlag) {
        header |= 0x08;
    }
    header |= (self.qos & 0x03) << 1;
    if (self.retainFlag) {
        header |= 0x01;
    }
    [buffer appendBytes:&header length:1];
    [buffer appendVariableLength:self.data.length];

    // encode message data
    if (self.data != nil) {
        [buffer appendData:self.data];
    }

//    NSLog(@"[ZYJMQTTMessage] wireFormat(%lu)=%@...",
//                 (unsigned long)buffer.length,
//                 [buffer subdataWithRange:NSMakeRange(0, MIN(256, buffer.length))]);

    return buffer;
}

+ (ZYJMQTTMessage *)messageFromData:(NSData *)data protocolLevel:(ZYJMQTTProtocolVersion)protocolLevel {
    ZYJMQTTMessage *message = nil;
    if (data.length >= 2) {
        UInt8 header;
        [data getBytes:&header length:sizeof(header)];
        UInt8 type = (header >> 4) & 0x0f;
        UInt8 dupFlag = (header >> 3) & 0x01;
        UInt8 qos = (header >> 1) & 0x03;
        UInt8 retainFlag = header & 0x01;
        UInt32 remainingLength = 0;
        UInt32 multiplier = 1;
        UInt8 offset = 1;
        UInt8 digit;
        do {
            if (data.length < offset) {
                NSLog(@"[ZYJMQTTMessage] message data incomplete remaining length");
                offset = -1;
                break;
            }
            [data getBytes:&digit range:NSMakeRange(offset, 1)];
            offset++;
            remainingLength += (digit & 0x7f) * multiplier;
            multiplier *= 128;
            if (multiplier > 128*128*128) {
                NSLog(@"[ZYJMQTTMessage] message data too long remaining length");
                multiplier = -1;
                break;
            }
        } while ((digit & 0x80) != 0);

        if (type >= ZYJMQTTConnect &&
            type <= ZYJMQTTDisconnect) {
            if (offset > 0 &&
                multiplier > 0 &&
                data.length == remainingLength + offset) {
                if ((type == ZYJMQTTPublish && (qos >= ZYJMQTTQosLevelAtMostOnce && qos <= ZYJMQTTQosLevelExactlyOnce)) ||
                    (type == ZYJMQTTConnect && qos == 0) ||
                    (type == ZYJMQTTConnack && qos == 0) ||
                    (type == ZYJMQTTPuback && qos == 0) ||
                    (type == ZYJMQTTPubrec && qos == 0) ||
                    (type == ZYJMQTTPubrel && qos == 1) ||
                    (type == ZYJMQTTPubcomp && qos == 0) ||
                    (type == ZYJMQTTSubscribe && qos == 1) ||
                    (type == ZYJMQTTSuback && qos == 0) ||
                    (type == ZYJMQTTUnsubscribe && qos == 1) ||
                    (type == ZYJMQTTUnsuback && qos == 0) ||
                    (type == ZYJMQTTPingreq && qos == 0) ||
                    (type == ZYJMQTTPingresp && qos == 0) ||
                    (type == ZYJMQTTDisconnect && qos == 0)) {
                    message = [[ZYJMQTTMessage alloc] init];
                    message.type = type;
                    message.dupFlag = dupFlag == 1;
                    message.retainFlag = retainFlag == 1;
                    message.qos = qos;
                    message.data = [data subdataWithRange:NSMakeRange(offset, remainingLength)];
                    if ((type == ZYJMQTTPublish &&
                         (qos == ZYJMQTTQosLevelAtLeastOnce ||
                          qos == ZYJMQTTQosLevelExactlyOnce)
                         ) ||
                        type == ZYJMQTTPuback ||
                        type == ZYJMQTTPubrec ||
                        type == ZYJMQTTPubrel ||
                        type == ZYJMQTTPubcomp ||
                        type == ZYJMQTTSubscribe ||
                        type == ZYJMQTTSuback ||
                        type == ZYJMQTTUnsubscribe ||
                        type == ZYJMQTTUnsuback) {
                        if (message.data.length >= 2) {
                            [message.data getBytes:&digit range:NSMakeRange(0, 1)];
                            message.mid = digit * 256;
                            [message.data getBytes:&digit range:NSMakeRange(1, 1)];
                            message.mid += digit;
                        } else {
                            NSLog(@"[ZYJMQTTMessage] missing packet identifier");
                            message = nil;
                        }
                    }
                    if (type == ZYJMQTTPuback ||
                        type == ZYJMQTTPubrec ||
                        type == ZYJMQTTPubrel ||
                        type == ZYJMQTTPubcomp) {
                        if (protocolLevel != ZYJMQTTProtocolVersion50) {
                            if (message.data.length > 2) {
                                NSLog(@"[ZYJMQTTMessage] unexpected payload after packet identifier");
                                message = nil;
                            }
                        } else {
                            if (message.data.length < 3) {
                                NSLog(@"[ZYJMQTTMessage] no returncode");
                                message = nil;
                            } else {
                                const UInt8 *bytes = message.data.bytes;
                                message.returnCode = [NSNumber numberWithInt:bytes[2]];
                                if (message.data.length >= 3) {
                                    message.properties = [[ZYJMQTTProperties alloc] initFromData:
                                                          [message.data subdataWithRange:NSMakeRange(3, message.data.length - 3)]];
                                }
                            }

                        }
                    }
                    if (type == ZYJMQTTUnsuback ) {
                        if (message.data.length > 2) {
                            NSLog(@"[ZYJMQTTMessage] unexpected payload after packet identifier");
                            message = nil;
                        }
                    }
                    if (type == ZYJMQTTPingreq ||
                        type == ZYJMQTTPingresp) {
                        if (message.data.length > 0) {
                            NSLog(@"[ZYJMQTTMessage] unexpected payload");
                            message = nil;
                        }
                    }
                    if (type == ZYJMQTTDisconnect) {
                        if (protocolLevel == ZYJMQTTProtocolVersion50) {
                            if (message.data.length == 0) {
                                message.properties = nil;
                                message.returnCode = @(ZYJMQTTSuccess);
                            } else if (message.data.length == 1) {
                                message.properties = nil;
                                const UInt8 *bytes = message.data.bytes;
                                message.returnCode = [NSNumber numberWithUnsignedInt:bytes[0]];
                            } else if (message.data.length > 1) {
                                const UInt8 *bytes = message.data.bytes;
                                message.returnCode = [NSNumber numberWithUnsignedInt:bytes[0]];
                                message.properties = [[ZYJMQTTProperties alloc] initFromData:
                                                      [message.data subdataWithRange:NSMakeRange(1, message.data.length - 1)]];
                            }
                        } else {
                            if (message.data.length != 2) {
                                NSLog(@"[ZYJMQTTMessage] unexpected payload");
                                message = nil;
                            }
                        }
                    }
                    if (type == ZYJMQTTConnect) {
                        if (message.data.length < 3) {
                            NSLog(@"[ZYJMQTTMessage] missing connect variable header");
                            message = nil;
                        }
                    }
                    if (type == ZYJMQTTConnack) {
                        if (protocolLevel == ZYJMQTTProtocolVersion50) {
                            if (message.data.length < 3) {
                                NSLog(@"[ZYJMQTTMessage] missing connack variable header");
                                message = nil;
                            }
                        } else {
                            if (message.data.length != 2) {
                                NSLog(@"[ZYJMQTTMessage] missing connack variable header");
                                message = nil;
                            }
                        }
                        if (message) {
                            const UInt8 *bytes = message.data.bytes;
                            message.connectAcknowledgeFlags = [NSNumber numberWithUnsignedInt:bytes[0]];
                            message.returnCode = [NSNumber numberWithUnsignedInt:bytes[1]];
                            if (protocolLevel == ZYJMQTTProtocolVersion50) {
                                message.properties = [[ZYJMQTTProperties alloc] initFromData:
                                                      [message.data subdataWithRange:NSMakeRange(2, message.data.length - 2)]];
                            }
                        }
                    }
                    if (type == ZYJMQTTSubscribe) {
                        if (message.data.length < 3) {
                            NSLog(@"[ZYJMQTTMessage] missing subscribe variable header");
                            message = nil;
                        }
                    }
                    if (type == ZYJMQTTSuback) {
                        if (message.data.length < 3) {
                            NSLog(@"[ZYJMQTTMessage] missing suback variable header");
                            message = nil;
                        }
                    }
                    if (type == ZYJMQTTUnsubscribe) {
                        if (message.data.length < 3) {
                            NSLog(@"[ZYJMQTTMessage] missing unsubscribe variable header");
                            message = nil;
                        }
                    }
                } else {
                    NSLog(@"[ZYJMQTTMessage] illegal header flags");
                }
            } else {
                NSLog(@"[ZYJMQTTMessage] remaining data wrong length");
            }
        } else {
            NSLog(@"[ZYJMQTTMessage] illegal message type");
        }
    } else {
        NSLog(@"[ZYJMQTTMessage] message data length < 2");
    }
    return message;
}

@end

@implementation NSMutableData (ZYJMQTT)

- (void)appendByte:(UInt8)byte {
    [self appendBytes:&byte length:1];
}

- (void)appendUInt16BigEndian:(UInt16)val {
    [self appendByte:val / 256];
    [self appendByte:val % 256];
}

- (void)appendUInt32BigEndian:(UInt32)val {
    [self appendByte:(val / (256 * 256 * 256))];
    [self appendByte:(val / (256 * 256)) & 0xff];
    [self appendByte:(val / 256) & 0xff];
    [self appendByte:val % 256];
}

- (void)appendVariableLength:(unsigned long)length {
    do {
        UInt8 digit = length % 128;
        length /= 128;
        if (length > 0) {
            digit |= 0x80;
        }
        [self appendBytes:&digit length:1];
    }
    while (length > 0);
}

- (void)appendZYJMQTTString:(NSString *)string {
    if (string) {
        NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
        [self appendUInt16BigEndian:data.length];
        [self appendData:data];
    }
}

- (void)appendBinaryData:(NSData *)data {
    if (data) {
        [self appendUInt16BigEndian:data.length];
        [self appendData:data];
    }
}

@end

