This commit is contained in:
启星
2025-08-12 14:27:12 +08:00
parent 9d18b353b1
commit 1bd5e77c45
8785 changed files with 978163 additions and 2 deletions

230
Pods/MQTTClient/LICENSE generated Normal file
View File

@@ -0,0 +1,230 @@
Eclipse Public License - v 1.0
THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC
LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM
CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
1. DEFINITIONS
"Contribution" means:
a) in the case of the initial Contributor, the initial code and documentation
distributed under this Agreement, and
b) in the case of each subsequent Contributor:
i) changes to the Program, and
ii) additions to the Program;
where such changes and/or additions to the Program originate from and are
distributed by that particular Contributor. A Contribution 'originates'
from a Contributor if it was added to the Program by such Contributor
itself or anyone acting on such Contributor's behalf. Contributions do not
include additions to the Program which: (i) are separate modules of
software distributed in conjunction with the Program under their own
license agreement, and (ii) are not derivative works of the Program.
"Contributor" means any person or entity that distributes the Program.
"Licensed Patents" mean patent claims licensable by a Contributor which are
necessarily infringed by the use or sale of its Contribution alone or when
combined with the Program.
"Program" means the Contributions distributed in accordance with this
Agreement.
"Recipient" means anyone who receives the Program under this Agreement,
including all Contributors.
2. GRANT OF RIGHTS
a) Subject to the terms of this Agreement, each Contributor hereby grants
Recipient a non-exclusive, worldwide, royalty-free copyright license to
reproduce, prepare derivative works of, publicly display, publicly
perform, distribute and sublicense the Contribution of such Contributor,
if any, and such derivative works, in source code and object code form.
b) Subject to the terms of this Agreement, each Contributor hereby grants
Recipient a non-exclusive, worldwide, royalty-free patent license under
Licensed Patents to make, use, sell, offer to sell, import and otherwise
transfer the Contribution of such Contributor, if any, in source code and
object code form. This patent license shall apply to the combination of
the Contribution and the Program if, at the time the Contribution is
added by the Contributor, such addition of the Contribution causes such
combination to be covered by the Licensed Patents. The patent license
shall not apply to any other combinations which include the Contribution.
No hardware per se is licensed hereunder.
c) Recipient understands that although each Contributor grants the licenses
to its Contributions set forth herein, no assurances are provided by any
Contributor that the Program does not infringe the patent or other
intellectual property rights of any other entity. Each Contributor
disclaims any liability to Recipient for claims brought by any other
entity based on infringement of intellectual property rights or
otherwise. As a condition to exercising the rights and licenses granted
hereunder, each Recipient hereby assumes sole responsibility to secure
any other intellectual property rights needed, if any. For example, if a
third party patent license is required to allow Recipient to distribute
the Program, it is Recipient's responsibility to acquire that license
before distributing the Program.
d) Each Contributor represents that to its knowledge it has sufficient
copyright rights in its Contribution, if any, to grant the copyright
license set forth in this Agreement.
3. REQUIREMENTS
A Contributor may choose to distribute the Program in object code form under
its own license agreement, provided that:
a) it complies with the terms and conditions of this Agreement; and
b) its license agreement:
i) effectively disclaims on behalf of all Contributors all warranties
and conditions, express and implied, including warranties or
conditions of title and non-infringement, and implied warranties or
conditions of merchantability and fitness for a particular purpose;
ii) effectively excludes on behalf of all Contributors all liability for
damages, including direct, indirect, special, incidental and
consequential damages, such as lost profits;
iii) states that any provisions which differ from this Agreement are
offered by that Contributor alone and not by any other party; and
iv) states that source code for the Program is available from such
Contributor, and informs licensees how to obtain it in a reasonable
manner on or through a medium customarily used for software exchange.
When the Program is made available in source code form:
a) it must be made available under this Agreement; and
b) a copy of this Agreement must be included with each copy of the Program.
Contributors may not remove or alter any copyright notices contained
within the Program.
Each Contributor must identify itself as the originator of its Contribution,
if
any, in a manner that reasonably allows subsequent Recipients to identify the
originator of the Contribution.
4. COMMERCIAL DISTRIBUTION
Commercial distributors of software may accept certain responsibilities with
respect to end users, business partners and the like. While this license is
intended to facilitate the commercial use of the Program, the Contributor who
includes the Program in a commercial product offering should do so in a manner
which does not create potential liability for other Contributors. Therefore,
if a Contributor includes the Program in a commercial product offering, such
Contributor ("Commercial Contributor") hereby agrees to defend and indemnify
every other Contributor ("Indemnified Contributor") against any losses,
damages and costs (collectively "Losses") arising from claims, lawsuits and
other legal actions brought by a third party against the Indemnified
Contributor to the extent caused by the acts or omissions of such Commercial
Contributor in connection with its distribution of the Program in a commercial
product offering. The obligations in this section do not apply to any claims
or Losses relating to any actual or alleged intellectual property
infringement. In order to qualify, an Indemnified Contributor must:
a) promptly notify the Commercial Contributor in writing of such claim, and
b) allow the Commercial Contributor to control, and cooperate with the
Commercial Contributor in, the defense and any related settlement
negotiations. The Indemnified Contributor may participate in any such claim at
its own expense.
For example, a Contributor might include the Program in a commercial product
offering, Product X. That Contributor is then a Commercial Contributor. If
that Commercial Contributor then makes performance claims, or offers
warranties related to Product X, those performance claims and warranties are
such Commercial Contributor's responsibility alone. Under this section, the
Commercial Contributor would have to defend claims against the other
Contributors related to those performance claims and warranties, and if a
court requires any other Contributor to pay any damages as a result, the
Commercial Contributor must pay those damages.
5. NO WARRANTY
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE,
NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each
Recipient is solely responsible for determining the appropriateness of using
and distributing the Program and assumes all risks associated with its
exercise of rights under this Agreement , including but not limited to the
risks and costs of program errors, compliance with applicable laws, damage to
or loss of data, programs or equipment, and unavailability or interruption of
operations.
6. DISCLAIMER OF LIABILITY
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY
CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION
LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGES.
7. GENERAL
If any provision of this Agreement is invalid or unenforceable under
applicable law, it shall not affect the validity or enforceability of the
remainder of the terms of this Agreement, and without further action by the
parties hereto, such provision shall be reformed to the minimum extent
necessary to make such provision valid and enforceable.
If Recipient institutes patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Program itself
(excluding combinations of the Program with other software or hardware)
infringes such Recipient's patent(s), then such Recipient's rights granted
under Section 2(b) shall terminate as of the date such litigation is filed.
All Recipient's rights under this Agreement shall terminate if it fails to
comply with any of the material terms or conditions of this Agreement and does
not cure such failure in a reasonable period of time after becoming aware of
such noncompliance. If all Recipient's rights under this Agreement terminate,
Recipient agrees to cease use and distribution of the Program as soon as
reasonably practicable. However, Recipient's obligations under this Agreement
and any licenses granted by Recipient relating to the Program shall continue
and survive.
Everyone is permitted to copy and distribute copies of this Agreement, but in
order to avoid inconsistency the Agreement is copyrighted and may only be
modified in the following manner. The Agreement Steward reserves the right to
publish new versions (including revisions) of this Agreement from time to
time. No one other than the Agreement Steward has the right to modify this
Agreement. The Eclipse Foundation is the initial Agreement Steward. The
Eclipse Foundation may assign the responsibility to serve as the Agreement
Steward to a suitable separate entity. Each new version of the Agreement will
be given a distinguishing version number. The Program (including
Contributions) may always be distributed subject to the version of the
Agreement under which it was received. In addition, after a new version of the
Agreement is published, Contributor may elect to distribute the Program
(including its Contributions) under the new version. Except as expressly
stated in Sections 2(a) and 2(b) above, Recipient receives no rights or
licenses to the intellectual property of any Contributor under this Agreement,
whether expressly, by implication, estoppel or otherwise. All rights in the
Program not expressly granted under this Agreement are reserved.
This Agreement is governed by the laws of the State of New York and the
intellectual property laws of the United States of America. No party to this
Agreement will bring a legal action under this Agreement more than one year
after the cause of action arose. Each party waives its rights to a jury trial in
any resulting litigation.
Copyright (c) 2013-2015 Christoph Krey <krey.christoph@gmail.com>
Based on
https://github.com/m2mIO/mqttIO-objC
Copyright © 2011, 2013 2lemetry, LLC
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that
the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,23 @@
//
// ForegroundReconnection.h
// MQTTClient
//
// Created by Josip Cavar on 22/08/2017.
// Copyright © 2017 Christoph Krey. All rights reserved.
//
#import <Foundation/Foundation.h>
#if TARGET_OS_IPHONE == 1
@class MQTTSessionManager;
@interface ForegroundReconnection : NSObject
@property (weak, nonatomic) MQTTSessionManager *sessionManager;
- (instancetype)initWithMQTTSessionManager:(MQTTSessionManager *)manager;
@end
#endif

View File

@@ -0,0 +1,83 @@
//
// ForegroundReconnection.m
// MQTTClient
//
// Created by Josip Cavar on 22/08/2017.
// Copyright © 2017 Christoph Krey. All rights reserved.
//
#import "ForegroundReconnection.h"
#if TARGET_OS_IPHONE == 1
#import "MQTTSessionManager.h"
#import <UIKit/UIKit.h>
@interface ForegroundReconnection ()
@property (nonatomic) UIBackgroundTaskIdentifier backgroundTask;
@end
@implementation ForegroundReconnection
- (instancetype)initWithMQTTSessionManager:(MQTTSessionManager *)manager {
self = [super init];
self.sessionManager = manager;
self.backgroundTask = UIBackgroundTaskInvalid;
NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
[defaultCenter addObserver:self
selector:@selector(appWillResignActive)
name:UIApplicationWillResignActiveNotification
object:nil];
[defaultCenter addObserver:self
selector:@selector(appDidEnterBackground)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
[defaultCenter addObserver:self
selector:@selector(appDidBecomeActive)
name:UIApplicationDidBecomeActiveNotification
object:nil];
return self;
}
- (void)dealloc {
NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
[defaultCenter removeObserver:self name:UIApplicationWillResignActiveNotification object:nil];
[defaultCenter removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
[defaultCenter removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
}
- (void)appWillResignActive {
[self.sessionManager disconnectWithDisconnectHandler:nil];
}
- (void)appDidEnterBackground {
if (!self.sessionManager.requiresTearDown) {
// we don't want to tear down session as it's already closed
return;
}
__weak typeof(self) weakSelf = self;
self.backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
__strong typeof(weakSelf) strongSelf = weakSelf;
[strongSelf endBackgroundTask];
}];
}
- (void)appDidBecomeActive {
[self.sessionManager connectToLast:nil];
}
- (void)endBackgroundTask {
if (self.backgroundTask) {
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundTask];
self.backgroundTask = UIBackgroundTaskInvalid;
}
}
@end
#endif

View File

@@ -0,0 +1,19 @@
//
// Timer.h
// MQTTClient
//
// Created by Josip Cavar on 06/11/2017.
// Copyright © 2017 Christoph Krey. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface GCDTimer: NSObject
+ (GCDTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval
repeats:(BOOL)repeats
queue:(dispatch_queue_t)queue
block:(void (^)(void))block;
- (void)invalidate;
@end

View File

@@ -0,0 +1,59 @@
//
// Timer.m
// MQTTClient
//
// Created by Josip Cavar on 06/11/2017.
// Copyright © 2017 Christoph Krey. All rights reserved.
//
#import "GCDTimer.h"
@interface GCDTimer ()
@property (strong, nonatomic) dispatch_source_t timer;
@end
@implementation GCDTimer
+ (GCDTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval
repeats:(BOOL)repeats
queue:(dispatch_queue_t)queue
block:(void (^)(void))block {
GCDTimer *timer = [[GCDTimer alloc] initWithInterval:interval
repeats:repeats
queue:queue
block:block];
return timer;
}
- (instancetype)initWithInterval:(NSTimeInterval)interval
repeats:(BOOL)repeats
queue:(dispatch_queue_t)queue
block:(void (^)(void))block {
self = [super init];
if (self) {
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(self.timer, dispatch_time(DISPATCH_TIME_NOW, interval * NSEC_PER_SEC), interval * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(self.timer, ^{
if (!repeats) {
dispatch_source_cancel(self.timer);
}
block();
});
dispatch_resume(self.timer);
}
return self;
}
- (void)dealloc {
[self invalidate];
}
- (void)invalidate {
if (self.timer) {
dispatch_source_cancel(self.timer);
}
}
@end

View File

@@ -0,0 +1,39 @@
//
// MQTTCFSocketDecoder.h
// MQTTClient.framework
//
// Copyright © 2013-2017, Christoph Krey. All rights reserved.
//
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSInteger, MQTTCFSocketDecoderState) {
MQTTCFSocketDecoderStateInitializing,
MQTTCFSocketDecoderStateReady,
MQTTCFSocketDecoderStateError
};
@class MQTTCFSocketDecoder;
@protocol MQTTCFSocketDecoderDelegate <NSObject>
- (void)decoder:(MQTTCFSocketDecoder *)sender didReceiveMessage:(NSData *)data;
- (void)decoderDidOpen:(MQTTCFSocketDecoder *)sender;
- (void)decoder:(MQTTCFSocketDecoder *)sender didFailWithError:(NSError *)error;
- (void)decoderdidClose:(MQTTCFSocketDecoder *)sender;
@end
@interface MQTTCFSocketDecoder : NSObject <NSStreamDelegate>
@property (nonatomic) MQTTCFSocketDecoderState state;
@property (strong, nonatomic) NSError *error;
@property (strong, nonatomic) NSInputStream *stream;
@property (weak, nonatomic ) id<MQTTCFSocketDecoderDelegate> delegate;
- (void)open;
- (void)close;
@end

View File

@@ -0,0 +1,90 @@
//
// MQTTCFSocketDecoder.m
// MQTTClient.framework
//
// Copyright © 2013-2017, Christoph Krey. All rights reserved.
//
#import "MQTTCFSocketDecoder.h"
#import "MQTTLog.h"
@interface MQTTCFSocketDecoder()
@end
@implementation MQTTCFSocketDecoder
- (instancetype)init {
self = [super init];
self.state = MQTTCFSocketDecoderStateInitializing;
self.stream = nil;
return self;
}
- (void)open {
if (self.state == MQTTCFSocketDecoderStateInitializing) {
(self.stream).delegate = self;
[self.stream open];
}
}
- (void)dealloc {
[self close];
}
- (void)close {
[self.stream close];
[self.stream setDelegate:nil];
}
- (void)stream:(NSStream *)sender handleEvent:(NSStreamEvent)eventCode {
if (eventCode & NSStreamEventOpenCompleted) {
DDLogVerbose(@"[MQTTCFSocketDecoder] NSStreamEventOpenCompleted");
self.state = MQTTCFSocketDecoderStateReady;
[self.delegate decoderDidOpen:self];
}
if (eventCode & NSStreamEventHasBytesAvailable) {
DDLogVerbose(@"[MQTTCFSocketDecoder] NSStreamEventHasBytesAvailable");
if (self.state == MQTTCFSocketDecoderStateInitializing) {
self.state = MQTTCFSocketDecoderStateReady;
}
if (self.state == MQTTCFSocketDecoderStateReady) {
NSInteger n;
UInt8 buffer[768];
n = [self.stream read:buffer maxLength:sizeof(buffer)];
if (n == -1) {
self.state = MQTTCFSocketDecoderStateError;
[self.delegate decoder:self didFailWithError:nil];
} else {
NSData *data = [NSData dataWithBytes:buffer length:n];
DDLogVerbose(@"[MQTTCFSocketDecoder] received (%lu)=%@...", (unsigned long)data.length,
[data subdataWithRange:NSMakeRange(0, MIN(256, data.length))]);
[self.delegate decoder:self didReceiveMessage:data];
}
}
}
if (eventCode & NSStreamEventHasSpaceAvailable) {
DDLogVerbose(@"[MQTTCFSocketDecoder] NSStreamEventHasSpaceAvailable");
}
if (eventCode & NSStreamEventEndEncountered) {
DDLogVerbose(@"[MQTTCFSocketDecoder] NSStreamEventEndEncountered");
self.state = MQTTCFSocketDecoderStateInitializing;
self.error = nil;
[self.delegate decoderdidClose:self];
}
if (eventCode & NSStreamEventErrorOccurred) {
DDLogVerbose(@"[MQTTCFSocketDecoder] NSStreamEventErrorOccurred");
self.state = MQTTCFSocketDecoderStateError;
self.error = self.stream.streamError;
[self.delegate decoder:self didFailWithError:self.error];
}
}
@end

View File

@@ -0,0 +1,38 @@
//
// MQTTCFSocketEncoder.h
// MQTTClient.framework
//
// Copyright © 2013-2017, Christoph Krey. All rights reserved.
//
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSInteger, MQTTCFSocketEncoderState) {
MQTTCFSocketEncoderStateInitializing,
MQTTCFSocketEncoderStateReady,
MQTTCFSocketEncoderStateError
};
@class MQTTCFSocketEncoder;
@protocol MQTTCFSocketEncoderDelegate <NSObject>
- (void)encoderDidOpen:(MQTTCFSocketEncoder *)sender;
- (void)encoder:(MQTTCFSocketEncoder *)sender didFailWithError:(NSError *)error;
- (void)encoderdidClose:(MQTTCFSocketEncoder *)sender;
@end
@interface MQTTCFSocketEncoder : NSObject <NSStreamDelegate>
@property (nonatomic) MQTTCFSocketEncoderState state;
@property (strong, nonatomic) NSError *error;
@property (strong, nonatomic) NSOutputStream *stream;
@property (weak, nonatomic ) id<MQTTCFSocketEncoderDelegate> delegate;
- (void)open;
- (void)close;
- (BOOL)send:(NSData *)data;
@end

View File

@@ -0,0 +1,115 @@
//
// MQTTCFSocketEncoder.m
// MQTTClient.framework
//
// Copyright © 2013-2017, Christoph Krey. All rights reserved.
//
#import "MQTTCFSocketEncoder.h"
#import "MQTTLog.h"
@interface MQTTCFSocketEncoder()
@property (strong, nonatomic) NSMutableData *buffer;
@end
@implementation MQTTCFSocketEncoder
- (instancetype)init {
self = [super init];
self.state = MQTTCFSocketEncoderStateInitializing;
self.buffer = [[NSMutableData alloc] init];
self.stream = nil;
return self;
}
- (void)dealloc {
[self close];
}
- (void)open {
(self.stream).delegate = self;
[self.stream open];
}
- (void)close {
[self.stream close];
[self.stream setDelegate:nil];
}
- (void)setState:(MQTTCFSocketEncoderState)state {
DDLogVerbose(@"[MQTTCFSocketEncoder] setState %ld/%ld", (long)_state, (long)state);
_state = state;
}
- (void)stream:(NSStream *)sender handleEvent:(NSStreamEvent)eventCode {
if (eventCode & NSStreamEventOpenCompleted) {
DDLogVerbose(@"[MQTTCFSocketEncoder] NSStreamEventOpenCompleted");
}
if (eventCode & NSStreamEventHasBytesAvailable) {
DDLogVerbose(@"[MQTTCFSocketEncoder] NSStreamEventHasBytesAvailable");
}
if (eventCode & NSStreamEventHasSpaceAvailable) {
DDLogVerbose(@"[MQTTCFSocketEncoder] NSStreamEventHasSpaceAvailable");
if (self.state == MQTTCFSocketEncoderStateInitializing) {
self.state = MQTTCFSocketEncoderStateReady;
[self.delegate encoderDidOpen:self];
}
if (self.state == MQTTCFSocketEncoderStateReady) {
if (self.buffer.length) {
[self send:nil];
}
}
}
if (eventCode & NSStreamEventEndEncountered) {
DDLogVerbose(@"[MQTTCFSocketEncoder] NSStreamEventEndEncountered");
self.state = MQTTCFSocketEncoderStateInitializing;
self.error = nil;
[self.delegate encoderdidClose:self];
}
if (eventCode & NSStreamEventErrorOccurred) {
DDLogVerbose(@"[MQTTCFSocketEncoder] NSStreamEventErrorOccurred");
self.state = MQTTCFSocketEncoderStateError;
self.error = self.stream.streamError;
[self.delegate encoder:self didFailWithError:self.error];
}
}
- (BOOL)send:(NSData *)data {
@synchronized(self) {
if (self.state != MQTTCFSocketEncoderStateReady) {
DDLogInfo(@"[MQTTCFSocketEncoder] not MQTTCFSocketEncoderStateReady");
return NO;
}
if (data) {
[self.buffer appendData:data];
}
if (self.buffer.length) {
DDLogVerbose(@"[MQTTCFSocketEncoder] buffer to write (%lu)=%@...",
(unsigned long)self.buffer.length,
[self.buffer subdataWithRange:NSMakeRange(0, MIN(256, self.buffer.length))]);
NSInteger n = [self.stream write:self.buffer.bytes maxLength:self.buffer.length];
if (n == -1) {
DDLogVerbose(@"[MQTTCFSocketEncoder] streamError: %@", self.error);
self.state = MQTTCFSocketEncoderStateError;
self.error = self.stream.streamError;
return NO;
} else {
if (n < self.buffer.length) {
DDLogVerbose(@"[MQTTCFSocketEncoder] buffer partially written: %ld", (long)n);
}
[self.buffer replaceBytesInRange:NSMakeRange(0, n) withBytes:NULL length:0];
}
}
return YES;
}
}
@end

View File

@@ -0,0 +1,79 @@
//
// MQTTCFSocketTransport.h
// MQTTClient
//
// Created by Christoph Krey on 06.12.15.
// Copyright © 2015-2017 Christoph Krey. All rights reserved.
//
#import "MQTTTransport.h"
#import "MQTTCFSocketDecoder.h"
#import "MQTTCFSocketEncoder.h"
/** MQTTCFSocketTransport
* implements an MQTTTransport on top of CFNetwork
*/
@interface MQTTCFSocketTransport : MQTTTransport <MQTTTransport, MQTTCFSocketDecoderDelegate, MQTTCFSocketEncoderDelegate>
/** streamSSLLevel an NSString containing the security level for read and write streams
* For list of possible values see:
* https://developer.apple.com/documentation/corefoundation/cfstream/cfstream_socket_security_level_constants
* Please also note that kCFStreamSocketSecurityLevelTLSv1_2 is not in a list
* and cannot be used as constant, but you can use it as a string value
* defaults to kCFStreamSocketSecurityLevelNegotiatedSSL
*/
@property (strong, nonatomic) NSString *streamSSLLevel;
/** host an NSString containing the hostName or IP address of the host to connect to
* defaults to @"localhost"
*/
@property (strong, nonatomic) NSString *host;
/** port an unsigned 32 bit integer containing the IP port number to connect to
* defaults to 1883
*/
@property (nonatomic) UInt32 port;
/** tls a boolean indicating whether the transport should be using security
* defaults to NO
*/
@property (nonatomic) BOOL tls;
/** Require for VoIP background service
* defaults to NO
*/
@property (nonatomic) BOOL voip;
/** certificates An identity certificate used to reply to a server requiring client certificates according
* to the description given for SSLSetCertificate(). You may build the certificates array yourself or use the
* sundry method clientCertFromP12.
*/
@property (strong, nonatomic) NSArray *certificates;
/** reads the content of a PKCS12 file and converts it to an certificates array for initWith...
@param path the path to a PKCS12 file
@param passphrase the passphrase to unlock the PKCS12 file
@returns a certificates array or nil if an error occured
@code
NSString *path = [[NSBundle bundleForClass:[MQTTClientTests class]] pathForResource:@"filename"
ofType:@"p12"];
NSArray *myCerts = [MQTTCFSocketTransport clientCertsFromP12:path passphrase:@"passphrase"];
if (myCerts) {
self.session = [[MQTTSession alloc] init];
...
self.session.certificates = myCerts;
[self.session connect];
...
}
@endcode
*/
+ (NSArray *)clientCertsFromP12:(NSString *)path passphrase:(NSString *)passphrase;
@end

View File

@@ -0,0 +1,237 @@
//
// MQTTCFSocketTransport.m
// MQTTClient
//
// Created by Christoph Krey on 06.12.15.
// Copyright © 2015-2017 Christoph Krey. All rights reserved.
//
#import "MQTTCFSocketTransport.h"
#import "MQTTLog.h"
@interface MQTTCFSocketTransport() {
void *QueueIdentityKey;
}
@property (strong, nonatomic) MQTTCFSocketEncoder *encoder;
@property (strong, nonatomic) MQTTCFSocketDecoder *decoder;
@end
@implementation MQTTCFSocketTransport
@synthesize state;
@synthesize delegate;
@synthesize queue = _queue;
@synthesize streamSSLLevel;
@synthesize host;
@synthesize port;
- (instancetype)init {
self = [super init];
self.host = @"localhost";
self.port = 1883;
self.tls = false;
self.voip = false;
self.certificates = nil;
self.queue = dispatch_get_main_queue();
self.streamSSLLevel = (NSString *)kCFStreamSocketSecurityLevelNegotiatedSSL;
return self;
}
- (void)dealloc {
[self close];
}
- (void)setQueue:(dispatch_queue_t)queue {
_queue = queue;
// We're going to use dispatch_queue_set_specific() to "mark" our queue.
// The dispatch_queue_set_specific() and dispatch_get_specific() functions take a "void *key" parameter.
// Later we can use dispatch_get_specific() to determine if we're executing on our queue.
// From the documentation:
//
// > Keys are only compared as pointers and are never dereferenced.
// > Thus, you can use a pointer to a static variable for a specific subsystem or
// > any other value that allows you to identify the value uniquely.
//
// So we're just going to use the memory address of an ivar.
dispatch_queue_set_specific(_queue, &QueueIdentityKey, (__bridge void *)_queue, NULL);
}
- (void)open {
DDLogVerbose(@"[MQTTCFSocketTransport] open");
self.state = MQTTTransportOpening;
NSError* connectError;
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)self.host, self.port, &readStream, &writeStream);
CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
if (self.tls) {
NSMutableDictionary *sslOptions = [[NSMutableDictionary alloc] init];
sslOptions[(NSString *)kCFStreamSSLLevel] = self.streamSSLLevel;
if (self.certificates) {
sslOptions[(NSString *)kCFStreamSSLCertificates] = self.certificates;
}
if (!CFReadStreamSetProperty(readStream, kCFStreamPropertySSLSettings, (__bridge CFDictionaryRef)(sslOptions))) {
connectError = [NSError errorWithDomain:@"MQTT"
code:errSSLInternal
userInfo:@{NSLocalizedDescriptionKey : @"Fail to init ssl input stream!"}];
}
if (!CFWriteStreamSetProperty(writeStream, kCFStreamPropertySSLSettings, (__bridge CFDictionaryRef)(sslOptions))) {
connectError = [NSError errorWithDomain:@"MQTT"
code:errSSLInternal
userInfo:@{NSLocalizedDescriptionKey : @"Fail to init ssl output stream!"}];
}
}
if (!connectError) {
self.encoder.delegate = nil;
self.encoder = [[MQTTCFSocketEncoder alloc] init];
CFWriteStreamSetDispatchQueue(writeStream, self.queue);
self.encoder.stream = CFBridgingRelease(writeStream);
self.encoder.delegate = self;
if (self.voip) {
[self.encoder.stream setProperty:NSStreamNetworkServiceTypeVoIP forKey:NSStreamNetworkServiceType];
}
[self.encoder open];
self.decoder.delegate = nil;
self.decoder = [[MQTTCFSocketDecoder alloc] init];
CFReadStreamSetDispatchQueue(readStream, self.queue);
self.decoder.stream = CFBridgingRelease(readStream);
self.decoder.delegate = self;
if (self.voip) {
[self.decoder.stream setProperty:NSStreamNetworkServiceTypeVoIP forKey:NSStreamNetworkServiceType];
}
[self.decoder open];
} else {
[self close];
}
}
- (void)close {
// https://github.com/novastone-media/MQTT-Client-Framework/issues/325
// We need to make sure that we are closing streams on their queue
// Otherwise, we end up with race condition where delegate is deallocated
// but still used by run loop event
if (self.queue != dispatch_get_specific(&QueueIdentityKey)) {
dispatch_sync(self.queue, ^{
[self internalClose];
});
} else {
[self internalClose];
}
}
- (void)internalClose {
DDLogVerbose(@"[MQTTCFSocketTransport] close");
self.state = MQTTTransportClosing;
if (self.encoder) {
[self.encoder close];
self.encoder.delegate = nil;
}
if (self.decoder) {
[self.decoder close];
self.decoder.delegate = nil;
}
}
- (BOOL)send:(nonnull NSData *)data {
return [self.encoder send:data];
}
- (void)decoder:(MQTTCFSocketDecoder *)sender didReceiveMessage:(nonnull NSData *)data {
[self.delegate mqttTransport:self didReceiveMessage:data];
}
- (void)decoder:(MQTTCFSocketDecoder *)sender didFailWithError:(NSError *)error {
//self.state = MQTTTransportClosing;
//[self.delegate mqttTransport:self didFailWithError:error];
}
- (void)encoder:(MQTTCFSocketEncoder *)sender didFailWithError:(NSError *)error {
self.state = MQTTTransportClosing;
[self.delegate mqttTransport:self didFailWithError:error];
}
- (void)decoderdidClose:(MQTTCFSocketDecoder *)sender {
self.state = MQTTTransportClosed;
[self.delegate mqttTransportDidClose:self];
}
- (void)encoderdidClose:(MQTTCFSocketEncoder *)sender {
//self.state = MQTTTransportClosed;
//[self.delegate mqttTransportDidClose:self];
}
- (void)decoderDidOpen:(MQTTCFSocketDecoder *)sender {
//self.state = MQTTTransportOpen;
//[self.delegate mqttTransportDidOpen:self];
}
- (void)encoderDidOpen:(MQTTCFSocketEncoder *)sender {
self.state = MQTTTransportOpen;
[self.delegate mqttTransportDidOpen:self];
}
+ (NSArray *)clientCertsFromP12:(NSString *)path passphrase:(NSString *)passphrase {
if (!path) {
DDLogWarn(@"[MQTTCFSocketTransport] no p12 path given");
return nil;
}
NSData *pkcs12data = [[NSData alloc] initWithContentsOfFile:path];
if (!pkcs12data) {
DDLogWarn(@"[MQTTCFSocketTransport] reading p12 failed");
return nil;
}
if (!passphrase) {
DDLogWarn(@"[MQTTCFSocketTransport] no passphrase given");
return nil;
}
CFArrayRef keyref = NULL;
OSStatus importStatus = SecPKCS12Import((__bridge CFDataRef)pkcs12data,
(__bridge CFDictionaryRef)@{(__bridge id)kSecImportExportPassphrase: passphrase},
&keyref);
if (importStatus != noErr) {
DDLogWarn(@"[MQTTCFSocketTransport] Error while importing pkcs12 [%d]", (int)importStatus);
return nil;
}
CFDictionaryRef identityDict = CFArrayGetValueAtIndex(keyref, 0);
if (!identityDict) {
DDLogWarn(@"[MQTTCFSocketTransport] could not CFArrayGetValueAtIndex");
return nil;
}
SecIdentityRef identityRef = (SecIdentityRef)CFDictionaryGetValue(identityDict,
kSecImportItemIdentity);
if (!identityRef) {
DDLogWarn(@"[MQTTCFSocketTransport] could not CFDictionaryGetValue");
return nil;
};
SecCertificateRef cert = NULL;
OSStatus status = SecIdentityCopyCertificate(identityRef, &cert);
if (status != noErr) {
DDLogWarn(@"[MQTTCFSocketTransport] SecIdentityCopyCertificate failed [%d]", (int)status);
return nil;
}
NSArray *clientCerts = @[(__bridge id)identityRef, (__bridge id)cert];
return clientCerts;
}
@end

View File

@@ -0,0 +1,39 @@
//
// MQTTClient.h
// MQTTClient
//
// Created by Christoph Krey on 13.01.14.
// Copyright © 2013-2017 Christoph Krey. All rights reserved.
//
/**
Include this file to use MQTTClient classes in your application
@author Christoph Krey c@ckrey.de
@see http://mqtt.org
*/
#import <Foundation/Foundation.h>
#import <MQTTClient/MQTTSession.h>
#import <MQTTClient/MQTTDecoder.h>
#import <MQTTClient/MQTTSessionLegacy.h>
#import <MQTTClient/MQTTSessionSynchron.h>
#import <MQTTClient/MQTTProperties.h>
#import <MQTTClient/MQTTMessage.h>
#import <MQTTClient/MQTTTransport.h>
#import <MQTTClient/MQTTCFSocketTransport.h>
#import <MQTTClient/MQTTCoreDataPersistence.h>
#import <MQTTClient/MQTTSSLSecurityPolicyTransport.h>
#import <MQTTClient/MQTTLog.h>
#if __has_include(<MQTTClient/MQTTSessionManager.h>)
#import <MQTTClient/MQTTSessionManager.h>
#endif
//! Project version number for MQTTClient.
FOUNDATION_EXPORT double MQTTClientVersionNumber;
//! Project version string for MQTTClient&lt;.
FOUNDATION_EXPORT const unsigned char MQTTClientVersionString[];

View File

@@ -0,0 +1,21 @@
//
// MQTTCoreDataPersistence.h
// MQTTClient
//
// Created by Christoph Krey on 22.03.15.
// Copyright © 2015-2017 Christoph Krey. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#import "MQTTPersistence.h"
@interface MQTTCoreDataPersistence : NSObject <MQTTPersistence>
@end
@interface MQTTFlow : NSManagedObject <MQTTFlow>
@end
@interface MQTTCoreDataFlow : NSObject <MQTTFlow>
@end

View File

@@ -0,0 +1,513 @@
//
// MQTTCoreDataPersistence.m
// MQTTClient
//
// Created by Christoph Krey on 22.03.15.
// Copyright © 2015-2017 Christoph Krey. All rights reserved.
//
#import "MQTTCoreDataPersistence.h"
#import "MQTTLog.h"
@implementation MQTTFlow
@dynamic clientId;
@dynamic incomingFlag;
@dynamic retainedFlag;
@dynamic commandType;
@dynamic qosLevel;
@dynamic messageId;
@dynamic topic;
@dynamic data;
@dynamic deadline;
@end
@interface MQTTCoreDataFlow ()
- (MQTTCoreDataFlow *)initWithContext:(NSManagedObjectContext *)context andObject:(id<MQTTFlow>)object;
@property NSManagedObjectContext *context;
@property id<MQTTFlow> object;
@end
@implementation MQTTCoreDataFlow
@synthesize context;
@synthesize object;
- (MQTTCoreDataFlow *)initWithContext:(NSManagedObjectContext *)c andObject:(id<MQTTFlow>)o {
self = [super init];
self.context = c;
self.object = o;
return self;
}
- (NSString *)clientId {
__block NSString *_clientId;
[context performBlockAndWait:^{
_clientId = self.object.clientId;
}];
return _clientId;
}
- (void)setClientId:(NSString *)clientId {
[context performBlockAndWait:^{
self.object.clientId = clientId;
}];
}
- (NSNumber *)incomingFlag {
__block NSNumber *_incomingFlag;
[context performBlockAndWait:^{
_incomingFlag = self.object.incomingFlag;
}];
return _incomingFlag;
}
- (void)setIncomingFlag:(NSNumber *)incomingFlag {
[context performBlockAndWait:^{
self.object.incomingFlag = incomingFlag;
}];
}
- (NSNumber *)retainedFlag {
__block NSNumber *_retainedFlag;
[context performBlockAndWait:^{
_retainedFlag = self.object.retainedFlag;
}];
return _retainedFlag;
}
- (void)setRetainedFlag:(NSNumber *)retainedFlag {
[context performBlockAndWait:^{
self.object.retainedFlag = retainedFlag;
}];
}
- (NSNumber *)commandType {
__block NSNumber *_commandType;
[context performBlockAndWait:^{
_commandType = self.object.commandType;
}];
return _commandType;
}
- (void)setCommandType:(NSNumber *)commandType {
[context performBlockAndWait:^{
self.object.commandType = commandType;
}];
}
- (NSNumber *)qosLevel {
__block NSNumber *_qosLevel;
[context performBlockAndWait:^{
_qosLevel = self.object.qosLevel;
}];
return _qosLevel;
}
- (void)setQosLevel:(NSNumber *)qosLevel {
[context performBlockAndWait:^{
self.object.qosLevel = qosLevel;
}];
}
- (NSNumber *)messageId {
__block NSNumber *_messageId;
[context performBlockAndWait:^{
_messageId = self.object.messageId;
}];
return _messageId;
}
- (void)setMessageId:(NSNumber *)messageId {
[context performBlockAndWait:^{
self.object.messageId = messageId;
}];
}
- (NSString *)topic {
__block NSString *_topic;
[context performBlockAndWait:^{
_topic = self.object.topic;
}];
return _topic;
}
- (void)setTopic:(NSString *)topic {
[context performBlockAndWait:^{
self.object.topic = topic;
}];
}
- (NSData *)data {
__block NSData *_data;
[context performBlockAndWait:^{
_data = self.object.data;
}];
return _data;
}
- (void)setData:(NSData *)data {
[context performBlockAndWait:^{
self.object.data = data;
}];
}
- (NSDate *)deadline {
__block NSDate *_deadline;
[context performBlockAndWait:^{
_deadline = self.object.deadline;
}];
return _deadline;
}
- (void)setDeadline:(NSDate *)deadline {
[context performBlockAndWait:^{
self.object.deadline = deadline;
}];
}
@end
@interface MQTTCoreDataPersistence ()
@property (strong, nonatomic) NSManagedObjectContext *managedObjectContext;
@property (assign, nonatomic) unsigned long long fileSize;
@property (assign, nonatomic) unsigned long long fileSystemFreeSize;
@end
@implementation MQTTCoreDataPersistence
@synthesize persistent;
@synthesize maxSize;
@synthesize maxMessages;
@synthesize maxWindowSize;
- (MQTTCoreDataPersistence *)init {
self = [super init];
self.persistent = MQTT_PERSISTENT;
self.maxSize = MQTT_MAX_SIZE;
self.maxMessages = MQTT_MAX_MESSAGES;
self.maxWindowSize = MQTT_MAX_WINDOW_SIZE;
return self;
}
- (NSManagedObjectContext *)managedObjectContext {
if (!_managedObjectContext) {
NSPersistentStoreCoordinator *coordinator = [self createPersistentStoreCoordinator];
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
_managedObjectContext.persistentStoreCoordinator = coordinator;
}
return _managedObjectContext;
}
- (NSUInteger)windowSize:(NSString *)clientId {
NSUInteger windowSize = 0;
NSArray *flows = [self allFlowsforClientId:clientId
incomingFlag:NO];
for (MQTTCoreDataFlow *flow in flows) {
if ((flow.commandType).unsignedIntegerValue != MQTT_None) {
windowSize++;
}
}
return windowSize;
}
- (MQTTCoreDataFlow *)storeMessageForClientId:(NSString *)clientId
topic:(NSString *)topic
data:(NSData *)data
retainFlag:(BOOL)retainFlag
qos:(MQTTQosLevel)qos
msgId:(UInt16)msgId
incomingFlag:(BOOL)incomingFlag
commandType:(UInt8)commandType
deadline:(NSDate *)deadline {
if (([self allFlowsforClientId:clientId incomingFlag:incomingFlag].count <= self.maxMessages) &&
(self.fileSize <= self.maxSize)) {
MQTTCoreDataFlow *flow = [self createFlowforClientId:clientId
incomingFlag:incomingFlag
messageId:msgId];
flow.topic = topic;
flow.data = data;
flow.retainedFlag = @(retainFlag);
flow.qosLevel = @(qos);
flow.commandType = [NSNumber numberWithUnsignedInteger:commandType];
flow.deadline = deadline;
return flow;
} else {
return nil;
}
}
- (void)deleteFlow:(MQTTCoreDataFlow *)flow {
[self.managedObjectContext performBlockAndWait:^{
[self.managedObjectContext deleteObject:(NSManagedObject *)flow.object];
}];
[self sync];
}
- (void)deleteAllFlowsForClientId:(NSString *)clientId {
DDLogInfo(@"[MQTTCoreDataPersistence] deleteAllFlowsForClientId %@", clientId);
[self.managedObjectContext performBlockAndWait:^{
for (MQTTCoreDataFlow *flow in [self allFlowsforClientId:clientId incomingFlag:TRUE]) {
[self.managedObjectContext deleteObject:(NSManagedObject *)flow.object];
}
for (MQTTCoreDataFlow *flow in [self allFlowsforClientId:clientId incomingFlag:FALSE]) {
[self.managedObjectContext deleteObject:(NSManagedObject *)flow.object];
}
}];
[self sync];
}
- (void)sync {
[self.managedObjectContext performBlockAndWait:^{
[self internalSync];
}];
}
- (void)internalSync {
if (self.managedObjectContext.hasChanges) {
DDLogVerbose(@"[MQTTPersistence] pre-sync: i%lu u%lu d%lu",
(unsigned long)self.managedObjectContext.insertedObjects.count,
(unsigned long)self.managedObjectContext.updatedObjects.count,
(unsigned long)self.managedObjectContext.deletedObjects.count
);
NSError *error = nil;
if (![self.managedObjectContext save:&error]) {
DDLogError(@"[MQTTPersistence] sync error %@", error);
}
if (self.managedObjectContext.hasChanges) {
DDLogError(@"[MQTTPersistence] sync not complete");
}
DDLogVerbose(@"[MQTTPersistence] postsync: i%lu u%lu d%lu",
(unsigned long)self.managedObjectContext.insertedObjects.count,
(unsigned long)self.managedObjectContext.updatedObjects.count,
(unsigned long)self.managedObjectContext.deletedObjects.count
);
[self sizes];
}
}
- (NSArray *)allFlowsforClientId:(NSString *)clientId
incomingFlag:(BOOL)incomingFlag {
NSMutableArray *flows = [NSMutableArray array];
__block NSArray *rows;
[self.managedObjectContext performBlockAndWait:^{
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"MQTTFlow"];
fetchRequest.predicate = [NSPredicate predicateWithFormat:
@"clientId = %@ and incomingFlag = %@",
clientId,
@(incomingFlag)
];
fetchRequest.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"deadline" ascending:YES]];
NSError *error = nil;
rows = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (!rows) {
DDLogError(@"[MQTTPersistence] allFlowsforClientId %@", error);
}
}];
for (id<MQTTFlow>row in rows) {
[flows addObject:[[MQTTCoreDataFlow alloc] initWithContext:self.managedObjectContext andObject:row]];
}
return flows;
}
- (MQTTCoreDataFlow *)flowforClientId:(NSString *)clientId
incomingFlag:(BOOL)incomingFlag
messageId:(UInt16)messageId {
__block MQTTCoreDataFlow *flow = nil;
DDLogVerbose(@"flowforClientId requestingPerform");
[self.managedObjectContext performBlockAndWait:^{
flow = [self internalFlowForClientId:clientId
incomingFlag:incomingFlag
messageId:messageId];
}];
DDLogVerbose(@"flowforClientId performed");
return flow;
}
- (MQTTCoreDataFlow *)internalFlowForClientId:(NSString *)clientId
incomingFlag:(BOOL)incomingFlag
messageId:(UInt16)messageId {
MQTTCoreDataFlow *flow = nil;
DDLogVerbose(@"flowforClientId performing");
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"MQTTFlow"];
fetchRequest.predicate = [NSPredicate predicateWithFormat:
@"clientId = %@ and incomingFlag = %@ and messageId = %@",
clientId,
@(incomingFlag),
@(messageId)
];
NSArray *rows;
NSError *error = nil;
rows = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (!rows) {
DDLogError(@"[MQTTPersistence] flowForClientId %@", error);
} else {
if (rows.count) {
flow = [[MQTTCoreDataFlow alloc] initWithContext:self.managedObjectContext andObject:rows.lastObject];
}
}
return flow;
}
- (MQTTCoreDataFlow *)createFlowforClientId:(NSString *)clientId
incomingFlag:(BOOL)incomingFlag
messageId:(UInt16)messageId {
MQTTCoreDataFlow *flow = (MQTTCoreDataFlow *)[self flowforClientId:clientId
incomingFlag:incomingFlag
messageId:messageId];
if (!flow) {
__block id<MQTTFlow> row;
[self.managedObjectContext performBlockAndWait:^{
row = [NSEntityDescription insertNewObjectForEntityForName:@"MQTTFlow"
inManagedObjectContext:self.managedObjectContext];
row.clientId = clientId;
row.incomingFlag = @(incomingFlag);
row.messageId = @(messageId);
}];
flow = [[MQTTCoreDataFlow alloc] initWithContext:self.managedObjectContext andObject:row];
}
return flow;
}
#pragma mark - Core Data stack
- (NSManagedObjectModel *)createManagedObjectModel {
NSManagedObjectModel *managedObjectModel = [[NSManagedObjectModel alloc] init];
NSMutableArray *entities = [[NSMutableArray alloc] init];
NSMutableArray *properties = [[NSMutableArray alloc] init];
NSAttributeDescription *attributeDescription;
attributeDescription = [[NSAttributeDescription alloc] init];
attributeDescription.name = @"clientId";
attributeDescription.attributeType = NSStringAttributeType;
attributeDescription.attributeValueClassName = @"NSString";
[properties addObject:attributeDescription];
attributeDescription = [[NSAttributeDescription alloc] init];
attributeDescription.name = @"incomingFlag";
attributeDescription.attributeType = NSBooleanAttributeType;
attributeDescription.attributeValueClassName = @"NSNumber";
[properties addObject:attributeDescription];
attributeDescription = [[NSAttributeDescription alloc] init];
attributeDescription.name = @"retainedFlag";
attributeDescription.attributeType = NSBooleanAttributeType;
attributeDescription.attributeValueClassName = @"NSNumber";
[properties addObject:attributeDescription];
attributeDescription = [[NSAttributeDescription alloc] init];
attributeDescription.name = @"commandType";
attributeDescription.attributeType = NSInteger16AttributeType;
attributeDescription.attributeValueClassName = @"NSNumber";
[properties addObject:attributeDescription];
attributeDescription = [[NSAttributeDescription alloc] init];
attributeDescription.name = @"qosLevel";
attributeDescription.attributeType = NSInteger16AttributeType;
attributeDescription.attributeValueClassName = @"NSNumber";
[properties addObject:attributeDescription];
attributeDescription = [[NSAttributeDescription alloc] init];
attributeDescription.name = @"messageId";
attributeDescription.attributeType = NSInteger32AttributeType;
attributeDescription.attributeValueClassName = @"NSNumber";
[properties addObject:attributeDescription];
attributeDescription = [[NSAttributeDescription alloc] init];
attributeDescription.name = @"topic";
attributeDescription.attributeType = NSStringAttributeType;
attributeDescription.attributeValueClassName = @"NSString";
[properties addObject:attributeDescription];
attributeDescription = [[NSAttributeDescription alloc] init];
attributeDescription.name = @"data";
attributeDescription.attributeType = NSBinaryDataAttributeType;
attributeDescription.attributeValueClassName = @"NSData";
[properties addObject:attributeDescription];
attributeDescription = [[NSAttributeDescription alloc] init];
attributeDescription.name = @"deadline";
attributeDescription.attributeType = NSDateAttributeType;
attributeDescription.attributeValueClassName = @"NSDate";
[properties addObject:attributeDescription];
NSEntityDescription *entityDescription = [[NSEntityDescription alloc] init];
entityDescription.name = @"MQTTFlow";
entityDescription.managedObjectClassName = @"MQTTFlow";
entityDescription.abstract = FALSE;
entityDescription.properties = properties;
[entities addObject:entityDescription];
managedObjectModel.entities = entities;
return managedObjectModel;
}
- (NSPersistentStoreCoordinator *)createPersistentStoreCoordinator {
NSURL *persistentStoreURL = [[self applicationDocumentsDirectory]
URLByAppendingPathComponent:@"MQTTClient"];
DDLogInfo(@"[MQTTPersistence] Persistent store: %@", persistentStoreURL.path);
NSError *error = nil;
NSManagedObjectModel *model = [self createManagedObjectModel];
NSPersistentStoreCoordinator *persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc]
initWithManagedObjectModel:model];
NSDictionary *options = @{NSMigratePersistentStoresAutomaticallyOption: @YES,
NSInferMappingModelAutomaticallyOption: @YES,
NSSQLiteAnalyzeOption: @YES,
NSSQLiteManualVacuumOption: @YES
};
if (![persistentStoreCoordinator addPersistentStoreWithType:self.persistent ? NSSQLiteStoreType : NSInMemoryStoreType
configuration:nil
URL:self.persistent ? persistentStoreURL : nil
options:options
error:&error]) {
DDLogError(@"[MQTTPersistence] managedObjectContext save: %@", error);
persistentStoreCoordinator = nil;
}
return persistentStoreCoordinator;
}
#pragma mark - Application's Documents directory
- (NSURL *)applicationDocumentsDirectory
{
return [[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].lastObject;
}
- (void)sizes {
if (self.persistent) {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = paths[0];
NSString *persistentStorePath = [documentsDirectory stringByAppendingPathComponent:@"MQTTClient"];
NSError *error = nil;
NSDictionary *fileAttributes = [[NSFileManager defaultManager]
attributesOfItemAtPath:persistentStorePath error:&error];
NSDictionary *fileSystemAttributes = [[NSFileManager defaultManager]
attributesOfFileSystemForPath:persistentStorePath
error:&error];
self.fileSize = [fileAttributes[NSFileSize] unsignedLongLongValue];
self.fileSystemFreeSize = [fileSystemAttributes[NSFileSystemFreeSize] unsignedLongLongValue];
} else {
self.fileSize = 0;
self.fileSystemFreeSize = 0;
}
DDLogVerbose(@"[MQTTPersistence] sizes %llu/%llu", self.fileSize, self.fileSystemFreeSize);
}
@end

View File

@@ -0,0 +1,66 @@
//
// MQTTDecoder.h
// MQTTClient.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 <Foundation/Foundation.h>
#import "MQTTMessage.h"
typedef NS_ENUM(unsigned int, MQTTDecoderEvent) {
MQTTDecoderEventProtocolError,
MQTTDecoderEventConnectionClosed,
MQTTDecoderEventConnectionError
};
typedef NS_ENUM(unsigned int, MQTTDecoderState) {
MQTTDecoderStateInitializing,
MQTTDecoderStateDecodingHeader,
MQTTDecoderStateDecodingLength,
MQTTDecoderStateDecodingData,
MQTTDecoderStateConnectionClosed,
MQTTDecoderStateConnectionError,
MQTTDecoderStateProtocolError
};
@class MQTTDecoder;
@protocol MQTTDecoderDelegate <NSObject>
- (void)decoder:(MQTTDecoder *)sender didReceiveMessage:(NSData *)data;
- (void)decoder:(MQTTDecoder *)sender handleEvent:(MQTTDecoderEvent)eventCode error:(NSError *)error;
@end
@interface MQTTDecoder: NSObject <NSStreamDelegate>
@property (nonatomic) MQTTDecoderState state;
@property (strong, nonatomic) dispatch_queue_t queue;
@property (nonatomic) UInt32 length;
@property (nonatomic) UInt32 lengthMultiplier;
@property (nonatomic) int offset;
@property (strong, nonatomic) NSMutableData *dataBuffer;
@property (weak, nonatomic) id<MQTTDecoderDelegate> delegate;
- (void)open;
- (void)close;
- (void)decodeMessage:(NSData *)data;
@end

View File

@@ -0,0 +1,215 @@
//
// MQTTDecoder.m
// MQTTClient.framework
//
// Copyright © 2013-2017, Christoph Krey. All rights reserved.
//
#import "MQTTDecoder.h"
#import "MQTTLog.h"
@interface MQTTDecoder() {
void *QueueIdentityKey;
}
@property (nonatomic) NSMutableArray<NSInputStream *> *streams;
@end
@implementation MQTTDecoder
- (instancetype)init {
self = [super init];
self.state = MQTTDecoderStateInitializing;
self.streams = [NSMutableArray arrayWithCapacity:5];
self.queue = dispatch_get_main_queue();
return self;
}
- (void)dealloc {
[self close];
}
- (void)setQueue:(dispatch_queue_t)queue {
_queue = queue;
// We're going to use dispatch_queue_set_specific() to "mark" our queue.
// The dispatch_queue_set_specific() and dispatch_get_specific() functions take a "void *key" parameter.
// Later we can use dispatch_get_specific() to determine if we're executing on our queue.
// From the documentation:
//
// > Keys are only compared as pointers and are never dereferenced.
// > Thus, you can use a pointer to a static variable for a specific subsystem or
// > any other value that allows you to identify the value uniquely.
//
// So we're just going to use the memory address of an ivar.
dispatch_queue_set_specific(_queue, &QueueIdentityKey, (__bridge void *)_queue, NULL);
}
- (void)decodeMessage:(NSData *)data {
NSInputStream *stream = [NSInputStream inputStreamWithData:data];
CFReadStreamRef readStream = (__bridge CFReadStreamRef)stream;
CFReadStreamSetDispatchQueue(readStream, self.queue);
[self openStream:stream];
}
- (void)openStream:(NSInputStream *)stream {
[self.streams addObject:stream];
stream.delegate = self;
DDLogVerbose(@"[MQTTDecoder] #streams=%lu", (unsigned long)self.streams.count);
if (self.streams.count == 1) {
[stream open];
}
}
- (void)open {
self.state = MQTTDecoderStateDecodingHeader;
}
- (void)internalClose {
if (self.streams) {
for (NSInputStream *stream in self.streams) {
[stream close];
[stream setDelegate:nil];
}
[self.streams removeAllObjects];
}
}
- (void)close {
// https://github.com/novastone-media/MQTT-Client-Framework/issues/325
// We need to make sure that we are closing streams on their queue
// Otherwise, we end up with race condition where delegate is deallocated
// but still used by run loop event
if (self.queue != dispatch_get_specific(&QueueIdentityKey)) {
dispatch_sync(self.queue, ^{
[self internalClose];
});
} else {
[self internalClose];
}
}
- (void)stream:(NSStream *)sender handleEvent:(NSStreamEvent)eventCode {
// We contact our delegate, MQTTSession at some point in this method
// This call can cause MQTTSession to dealloc and thus, MQTTDecoder to dealloc
// So we end up with invalid object in the middle of the method
// To prevent this we retain self for duration of this method call
MQTTDecoder *strongDecoder = self;
(void)strongDecoder;
NSInputStream *stream = (NSInputStream *)sender;
if (eventCode & NSStreamEventOpenCompleted) {
DDLogVerbose(@"[MQTTDecoder] NSStreamEventOpenCompleted");
}
if (eventCode & NSStreamEventHasBytesAvailable) {
DDLogVerbose(@"[MQTTDecoder] NSStreamEventHasBytesAvailable");
if (self.state == MQTTDecoderStateDecodingHeader) {
UInt8 buffer;
NSInteger n = [stream read:&buffer maxLength:1];
if (n == -1) {
self.state = MQTTDecoderStateConnectionError;
[self.delegate decoder:self handleEvent:MQTTDecoderEventConnectionError error:stream.streamError];
} else if (n == 1) {
self.length = 0;
self.lengthMultiplier = 1;
self.state = MQTTDecoderStateDecodingLength;
self.dataBuffer = [[NSMutableData alloc] init];
[self.dataBuffer appendBytes:&buffer length:1];
self.offset = 1;
DDLogVerbose(@"[MQTTDecoder] fixedHeader=0x%02x", buffer);
}
}
while (self.state == MQTTDecoderStateDecodingLength) {
// TODO: check max packet length(prevent evil server response)
UInt8 digit;
NSInteger n = [stream read:&digit maxLength:1];
if (n == -1) {
self.state = MQTTDecoderStateConnectionError;
[self.delegate decoder:self handleEvent:MQTTDecoderEventConnectionError error:stream.streamError];
break;
} else if (n == 0) {
break;
}
DDLogVerbose(@"[MQTTDecoder] digit=0x%02x 0x%02x %d %d", digit, digit & 0x7f, (unsigned int)self.length, (unsigned int)self.lengthMultiplier);
[self.dataBuffer appendBytes:&digit length:1];
self.offset++;
self.length += ((digit & 0x7f) * self.lengthMultiplier);
if ((digit & 0x80) == 0x00) {
self.state = MQTTDecoderStateDecodingData;
} else {
self.lengthMultiplier *= 128;
}
}
DDLogVerbose(@"[MQTTDecoder] remainingLength=%d", (unsigned int)self.length);
if (self.state == MQTTDecoderStateDecodingData) {
if (self.length > 0) {
NSInteger n, toRead;
UInt8 buffer[768];
toRead = self.length + self.offset - self.dataBuffer.length;
if (toRead > sizeof buffer) {
toRead = sizeof buffer;
}
n = [stream read:buffer maxLength:toRead];
if (n == -1) {
self.state = MQTTDecoderStateConnectionError;
[self.delegate decoder:self handleEvent:MQTTDecoderEventConnectionError error:stream.streamError];
} else {
DDLogVerbose(@"[MQTTDecoder] read %ld %ld", (long)toRead, (long)n);
[self.dataBuffer appendBytes:buffer length:n];
}
}
if (self.dataBuffer.length == self.length + self.offset) {
DDLogVerbose(@"[MQTTDecoder] received (%lu)=%@...", (unsigned long)self.dataBuffer.length,
[self.dataBuffer subdataWithRange:NSMakeRange(0, MIN(256, self.dataBuffer.length))]);
[self.delegate decoder:self didReceiveMessage:self.dataBuffer];
self.dataBuffer = nil;
self.state = MQTTDecoderStateDecodingHeader;
} else {
DDLogWarn(@"[MQTTDecoder] oops received (%lu)=%@...", (unsigned long)self.dataBuffer.length,
[self.dataBuffer subdataWithRange:NSMakeRange(0, MIN(256, self.dataBuffer.length))]);
}
}
}
if (eventCode & NSStreamEventHasSpaceAvailable) {
DDLogVerbose(@"[MQTTDecoder] NSStreamEventHasSpaceAvailable");
}
if (eventCode & NSStreamEventEndEncountered) {
DDLogVerbose(@"[MQTTDecoder] NSStreamEventEndEncountered");
if (self.streams) {
[stream setDelegate:nil];
[stream close];
[self.streams removeObject:stream];
if (self.streams.count) {
NSInputStream *stream = (self.streams)[0];
[stream open];
}
}
}
if (eventCode & NSStreamEventErrorOccurred) {
DDLogVerbose(@"[MQTTDecoder] NSStreamEventErrorOccurred");
self.state = MQTTDecoderStateConnectionError;
NSError *error = stream.streamError;
if (self.streams) {
[self.streams removeObject:stream];
if (self.streams.count) {
NSInputStream *stream = (self.streams)[0];
[stream open];
}
}
[self.delegate decoder:self handleEvent:MQTTDecoderEventConnectionError error:error];
}
}
@end

View File

@@ -0,0 +1,16 @@
//
// MQTTInMemoryPersistence.h
// MQTTClient
//
// Created by Christoph Krey on 22.03.15.
// Copyright © 2015-2017 Christoph Krey. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "MQTTPersistence.h"
@interface MQTTInMemoryPersistence : NSObject <MQTTPersistence>
@end
@interface MQTTInMemoryFlow : NSObject <MQTTFlow>
@end

View File

@@ -0,0 +1,180 @@
//
// MQTTInMemoryPersistence.m
// MQTTClient
//
// Created by Christoph Krey on 22.03.15.
// Copyright © 2015-2017 Christoph Krey. All rights reserved.
//
#import "MQTTInMemoryPersistence.h"
#import "MQTTLog.h"
@implementation MQTTInMemoryFlow
@synthesize clientId;
@synthesize incomingFlag;
@synthesize retainedFlag;
@synthesize commandType;
@synthesize qosLevel;
@synthesize messageId;
@synthesize topic;
@synthesize data;
@synthesize deadline;
@end
@interface MQTTInMemoryPersistence()
@end
static NSMutableDictionary *clientIds;
@implementation MQTTInMemoryPersistence
@synthesize maxSize;
@synthesize persistent;
@synthesize maxMessages;
@synthesize maxWindowSize;
- (MQTTInMemoryPersistence *)init {
self = [super init];
self.maxMessages = MQTT_MAX_MESSAGES;
self.maxWindowSize = MQTT_MAX_WINDOW_SIZE;
@synchronized(clientIds) {
if (!clientIds) {
clientIds = [[NSMutableDictionary alloc] init];
}
}
return self;
}
- (NSUInteger)windowSize:(NSString *)clientId {
NSUInteger windowSize = 0;
NSArray *flows = [self allFlowsforClientId:clientId
incomingFlag:NO];
for (MQTTInMemoryFlow *flow in flows) {
if ((flow.commandType).intValue != MQTT_None) {
windowSize++;
}
}
return windowSize;
}
- (MQTTInMemoryFlow *)storeMessageForClientId:(NSString *)clientId
topic:(NSString *)topic
data:(NSData *)data
retainFlag:(BOOL)retainFlag
qos:(MQTTQosLevel)qos
msgId:(UInt16)msgId
incomingFlag:(BOOL)incomingFlag
commandType:(UInt8)commandType
deadline:(NSDate *)deadline {
@synchronized(clientIds) {
if (([self allFlowsforClientId:clientId incomingFlag:incomingFlag].count <= self.maxMessages)) {
MQTTInMemoryFlow *flow = (MQTTInMemoryFlow *)[self createFlowforClientId:clientId
incomingFlag:incomingFlag
messageId:msgId];
flow.topic = topic;
flow.data = data;
flow.retainedFlag = @(retainFlag);
flow.qosLevel = @(qos);
flow.commandType = [NSNumber numberWithUnsignedInteger:commandType];
flow.deadline = deadline;
return flow;
} else {
return nil;
}
}
}
- (void)deleteFlow:(MQTTInMemoryFlow *)flow {
@synchronized(clientIds) {
NSMutableDictionary *clientIdFlows = clientIds[flow.clientId];
if (clientIdFlows) {
NSMutableDictionary *clientIdDirectedFlow = clientIdFlows[flow.incomingFlag];
if (clientIdDirectedFlow) {
[clientIdDirectedFlow removeObjectForKey:flow.messageId];
}
}
}
}
- (void)deleteAllFlowsForClientId:(NSString *)clientId {
@synchronized(clientIds) {
DDLogInfo(@"[MQTTInMemoryPersistence] deleteAllFlowsForClientId %@", clientId);
[clientIds removeObjectForKey:clientId];
}
}
- (void)sync {
//
}
- (NSArray *)allFlowsforClientId:(NSString *)clientId
incomingFlag:(BOOL)incomingFlag {
@synchronized(clientIds) {
NSMutableArray *flows = nil;
NSMutableDictionary *clientIdFlows = clientIds[clientId];
if (clientIdFlows) {
NSMutableDictionary *clientIdDirectedFlow = clientIdFlows[@(incomingFlag)];
if (clientIdDirectedFlow) {
flows = [NSMutableArray array];
NSArray *keys = [clientIdDirectedFlow.allKeys sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"self" ascending:YES]]];
for (id key in keys) {
[flows addObject:clientIdDirectedFlow[key]];
}
}
}
return flows;
}
}
- (MQTTInMemoryFlow *)flowforClientId:(NSString *)clientId
incomingFlag:(BOOL)incomingFlag
messageId:(UInt16)messageId {
@synchronized(clientIds) {
MQTTInMemoryFlow *flow = nil;
NSMutableDictionary *clientIdFlows = clientIds[clientId];
if (clientIdFlows) {
NSMutableDictionary *clientIdDirectedFlow = clientIdFlows[@(incomingFlag)];
if (clientIdDirectedFlow) {
flow = clientIdDirectedFlow[[NSNumber numberWithUnsignedInteger:messageId]];
}
}
return flow;
}
}
- (MQTTInMemoryFlow *)createFlowforClientId:(NSString *)clientId
incomingFlag:(BOOL)incomingFlag
messageId:(UInt16)messageId {
@synchronized(clientIds) {
NSMutableDictionary *clientIdFlows = clientIds[clientId];
if (!clientIdFlows) {
clientIdFlows = [[NSMutableDictionary alloc] init];
clientIds[clientId] = clientIdFlows;
}
NSMutableDictionary *clientIdDirectedFlow = clientIdFlows[@(incomingFlag)];
if (!clientIdDirectedFlow) {
clientIdDirectedFlow = [[NSMutableDictionary alloc] init];
clientIdFlows[@(incomingFlag)] = clientIdDirectedFlow;
}
MQTTInMemoryFlow *flow = [[MQTTInMemoryFlow alloc] init];
flow.clientId = clientId;
flow.incomingFlag = @(incomingFlag);
flow.messageId = [NSNumber numberWithUnsignedInteger:messageId];
clientIdDirectedFlow[[NSNumber numberWithUnsignedInteger:messageId]] = flow;
return flow;
}
}
@end

View File

@@ -0,0 +1,123 @@
//
// MQTTLog.h
// MQTTClient
//
// Created by Christoph Krey on 10.02.16.
// Copyright © 2016-2017 Christoph Krey. All rights reserved.
//
@import Foundation;
#ifdef LUMBERJACK
#define LOG_LEVEL_DEF ddLogLevel
#import <CocoaLumberjack/CocoaLumberjack.h>
#else /* LUMBERJACK */
typedef NS_OPTIONS(NSUInteger, DDLogFlag){
/**
* 0...00001 DDLogFlagError
*/
DDLogFlagError = (1 << 0),
/**
* 0...00010 DDLogFlagWarning
*/
DDLogFlagWarning = (1 << 1),
/**
* 0...00100 DDLogFlagInfo
*/
DDLogFlagInfo = (1 << 2),
/**
* 0...01000 DDLogFlagDebug
*/
DDLogFlagDebug = (1 << 3),
/**
* 0...10000 DDLogFlagVerbose
*/
DDLogFlagVerbose = (1 << 4)
};
typedef NS_ENUM(NSUInteger, DDLogLevel){
DDLogLevelOff = 0,
/**
* Error logs only
*/
DDLogLevelError = (DDLogFlagError),
/**
* Error and warning logs
*/
DDLogLevelWarning = (DDLogLevelError | DDLogFlagWarning),
/**
* Error, warning and info logs
*/
DDLogLevelInfo = (DDLogLevelWarning | DDLogFlagInfo),
/**
* Error, warning, info and debug logs
*/
DDLogLevelDebug = (DDLogLevelInfo | DDLogFlagDebug),
/**
* Error, warning, info, debug and verbose logs
*/
DDLogLevelVerbose = (DDLogLevelDebug | DDLogFlagVerbose),
/**
* All logs (1...11111)
*/
DDLogLevelAll = NSUIntegerMax
};
#ifdef DEBUG
#define DDLogVerbose if (ddLogLevel & DDLogFlagVerbose) NSLog
#define DDLogDebug if (ddLogLevel & DDLogFlagDebug) NSLog
#define DDLogWarn if (ddLogLevel & DDLogFlagWarning) NSLog
#define DDLogInfo if (ddLogLevel & DDLogFlagInfo) NSLog
#define DDLogError if (ddLogLevel & DDLogFlagError) NSLog
#else
#define DDLogVerbose(...)
#define DDLogDebug(...)
#define DDLogWarn(...)
#define DDLogInfo(...)
#define DDLogError(...)
#endif /* DEBUG */
#endif /* LUMBERJACK */
extern DDLogLevel ddLogLevel;
/** MQTTLog lets you define the log level for MQTTClient
* independently of using CocoaLumberjack
*/
@interface MQTTLog: NSObject
/** setLogLevel controls the log level for MQTTClient
* @param logLevel as follows:
*
* default for DEBUG builds is DDLogLevelVerbose
* default for RELEASE builds is DDLogLevelWarning
*
* Available log levels:
* DDLogLevelAll
* DDLogLevelVerbose
* DDLogLevelDebug
* DDLogLevelInfo
* DDLogLevelWarning
* DDLogLevelError
* DDLogLevelOff
*/
+ (void)setLogLevel:(DDLogLevel)logLevel;
@end

View File

@@ -0,0 +1,27 @@
//
// MQTTLog.m
// MQTTClient
//
// Created by Josip Cavar on 06/07/2017.
//
//
#import "MQTTLog.h"
@implementation MQTTLog
#ifdef DEBUG
DDLogLevel ddLogLevel = DDLogLevelVerbose;
#else
DDLogLevel ddLogLevel = DDLogLevelWarning;
#endif
+ (void)setLogLevel:(DDLogLevel)logLevel {
ddLogLevel = logLevel;
}
@end

View File

@@ -0,0 +1,233 @@
//
// MQTTMessage.h
// MQTTClient.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 <Foundation/Foundation.h>
@class MQTTProperties;
/**
Enumeration of MQTT Quality of Service levels
*/
typedef NS_ENUM(UInt8, MQTTQosLevel) {
MQTTQosLevelAtMostOnce = 0,
MQTTQosLevelAtLeastOnce = 1,
MQTTQosLevelExactlyOnce = 2
};
/**
Enumeration of MQTT protocol version
*/
typedef NS_ENUM(UInt8, MQTTProtocolVersion) {
MQTTProtocolVersion0 = 0,
MQTTProtocolVersion31 = 3,
MQTTProtocolVersion311 = 4,
MQTTProtocolVersion50 = 5
};
typedef NS_ENUM(UInt8, MQTTCommandType) {
MQTT_None = 0,
MQTTConnect = 1,
MQTTConnack = 2,
MQTTPublish = 3,
MQTTPuback = 4,
MQTTPubrec = 5,
MQTTPubrel = 6,
MQTTPubcomp = 7,
MQTTSubscribe = 8,
MQTTSuback = 9,
MQTTUnsubscribe = 10,
MQTTUnsuback = 11,
MQTTPingreq = 12,
MQTTPingresp = 13,
MQTTDisconnect = 14,
MQTTAuth = 15
};
@interface MQTTMessage : NSObject
@property (nonatomic) MQTTCommandType type;
@property (nonatomic) MQTTQosLevel qos;
@property (nonatomic) BOOL retainFlag;
@property (nonatomic) BOOL dupFlag;
@property (nonatomic) UInt16 mid;
@property (strong, nonatomic) NSData *data;
@property (strong, nonatomic) NSNumber *returnCode;
@property (strong, nonatomic) NSNumber *connectAcknowledgeFlags;
@property (strong, nonatomic) MQTTProperties *properties;
/**
Enumeration of MQTT return codes
*/
typedef NS_ENUM(NSUInteger, MQTTReturnCode) {
MQTTAccepted = 0,
MQTTRefusedUnacceptableProtocolVersion = 1,
MQTTRefusedIdentiferRejected = 2,
MQTTRefusedServerUnavailable = 3,
MQTTRefusedBadUserNameOrPassword = 4,
MQTTRefusedNotAuthorized = 5,
MQTTSuccess = 0,
MQTTDisconnectWithWillMessage = 4,
MQTTNoSubscriptionExisted = 17,
MQTTContinueAuthentication = 24,
MQTTReAuthenticate = 25,
MQTTUnspecifiedError = 128,
MQTTMalformedPacket = 129,
MQTTProtocolError = 130,
MQTTImplementationSpecificError = 131,
MQTTUnsupportedProtocolVersion = 132,
MQTTClientIdentifierNotValid = 133,
MQTTBadUserNameOrPassword = 134,
MQTTNotAuthorized = 135,
MQTTServerUnavailable = 136,
MQTTServerBusy = 137,
MQTTBanned = 138,
MQTTServerShuttingDown = 139,
MQTTBadAuthenticationMethod = 140,
MQTTKeepAliveTimeout = 141,
MQTTSessionTakenOver = 142,
MQTTTopicFilterInvalid = 143,
MQTTTopicNameInvalid = 144,
MQTTPacketIdentifierInUse = 145,
MQTTPacketIdentifierNotFound = 146,
MQTTReceiveMaximumExceeded = 147,
MQTTPacketTooLarge = 149,
MQTTMessageRateTooHigh = 150,
MQTTQuotaExceeded = 151,
MQTTAdministrativeAction = 152,
MQTTPayloadFormatInvalid = 153,
MQTTRetainNotSupported = 154,
MQTTQoSNotSupported = 155,
MQTTUseAnotherServer = 156,
MQTTServerMoved = 157,
MQTTSharedSubscriptionNotSupported = 158,
MQTTConnectionRateExceeded = 159,
MQTTSubscriptionIdentifiersNotSupported = 161,
MQTTWildcardSubscriptionNotSupported = 162
};
// factory methods
+ (MQTTMessage *)connectMessageWithClientId:(NSString*)clientId
userName:(NSString*)userName
password:(NSString*)password
keepAlive:(NSInteger)keeplive
cleanSession:(BOOL)cleanSessionFlag
will:(BOOL)will
willTopic:(NSString*)willTopic
willMsg:(NSData*)willData
willQoS:(MQTTQosLevel)willQoS
willRetain:(BOOL)willRetainFlag
protocolLevel:(MQTTProtocolVersion)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
;
+ (MQTTMessage *)pingreqMessage;
+ (MQTTMessage *)disconnectMessage:(MQTTProtocolVersion)protocolLevel
returnCode:(MQTTReturnCode)returnCode
sessionExpiryInterval:(NSNumber *)sessionExpiryInterval
reasonString:(NSString *)reasonString
userProperty:(NSDictionary <NSString *, NSString *> *)userProperty;
+ (MQTTMessage *)subscribeMessageWithMessageId:(UInt16)msgId
topics:(NSDictionary *)topics
protocolLevel:(MQTTProtocolVersion)protocolLevel
subscriptionIdentifier:(NSNumber *)subscriptionIdentifier;
+ (MQTTMessage *)unsubscribeMessageWithMessageId:(UInt16)msgId
topics:(NSArray *)topics
protocolLevel:(MQTTProtocolVersion)protocolLevel;
+ (MQTTMessage *)publishMessageWithData:(NSData*)payload
onTopic:(NSString*)topic
qos:(MQTTQosLevel)qosLevel
msgId:(UInt16)msgId
retainFlag:(BOOL)retain
dupFlag:(BOOL)dup
protocolLevel:(MQTTProtocolVersion)protocolLevel
payloadFormatIndicator:(NSNumber *)payloadFormatIndicator
publicationExpiryInterval:(NSNumber *)publicationExpiryInterval
topicAlias:(NSNumber *)topicAlias
responseTopic:(NSString *)responseTopic
correlationData:(NSData *)correlationData
userProperty:(NSDictionary <NSString *, NSString *> *)userProperty
contentType:(NSString *)contentType;
+ (MQTTMessage *)pubackMessageWithMessageId:(UInt16)msgId
protocolLevel:(MQTTProtocolVersion)protocolLevel
returnCode:(MQTTReturnCode)returnCode
reasonString:(NSString *)reasonString
userProperty:(NSDictionary <NSString *, NSString *> *)userProperty;
+ (MQTTMessage *)pubrecMessageWithMessageId:(UInt16)msgId
protocolLevel:(MQTTProtocolVersion)protocolLevel
returnCode:(MQTTReturnCode)returnCode
reasonString:(NSString *)reasonString
userProperty:(NSDictionary <NSString *, NSString *> *)userProperty;
+ (MQTTMessage *)pubrelMessageWithMessageId:(UInt16)msgId
protocolLevel:(MQTTProtocolVersion)protocolLevel
returnCode:(MQTTReturnCode)returnCode
reasonString:(NSString *)reasonString
userProperty:(NSDictionary <NSString *, NSString *> *)userProperty;
+ (MQTTMessage *)pubcompMessageWithMessageId:(UInt16)msgId
protocolLevel:(MQTTProtocolVersion)protocolLevel
returnCode:(MQTTReturnCode)returnCode
reasonString:(NSString *)reasonString
userProperty:(NSDictionary <NSString *, NSString *> *)userProperty;
+ (MQTTMessage *)messageFromData:(NSData *)data protocolLevel:(MQTTProtocolVersion)protocolLevel;
// instance methods
- (instancetype)initWithType:(MQTTCommandType)type;
- (instancetype)initWithType:(MQTTCommandType)type
data:(NSData *)data;
- (instancetype)initWithType:(MQTTCommandType)type
qos:(MQTTQosLevel)qos
data:(NSData *)data;
- (instancetype)initWithType:(MQTTCommandType)type
qos:(MQTTQosLevel)qos
retainFlag:(BOOL)retainFlag
dupFlag:(BOOL)dupFlag
data:(NSData *)data;
@property (NS_NONATOMIC_IOSONLY, readonly, copy) NSData *wireFormat;
@end
@interface NSMutableData (MQTT)
- (void)appendByte:(UInt8)byte;
- (void)appendUInt16BigEndian:(UInt16)val;
- (void)appendUInt32BigEndian:(UInt32)val;
- (void)appendVariableLength:(unsigned long)length;
- (void)appendMQTTString:(NSString *)string;
- (void)appendBinaryData:(NSData *)data;
@end

View File

@@ -0,0 +1,762 @@
//
// MQTTMessage.m
// MQTTClient.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 "MQTTMessage.h"
#import "MQTTProperties.h"
#import "MQTTLog.h"
@implementation MQTTMessage
+ (MQTTMessage *)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:(MQTTQosLevel)willQoS
willRetain:(BOOL)willRetainFlag
protocolLevel:(MQTTProtocolVersion)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 MQTTProtocolVersion50:
[data appendMQTTString:@"MQTT"];
[data appendByte:MQTTProtocolVersion50];
break;
case MQTTProtocolVersion311:
[data appendMQTTString:@"MQTT"];
[data appendByte:MQTTProtocolVersion311];
break;
case MQTTProtocolVersion31:
[data appendMQTTString:@"MQIsdp"];
[data appendByte:MQTTProtocolVersion31];
break;
case MQTTProtocolVersion0:
[data appendMQTTString:@""];
[data appendByte:protocolLevel];
break;
default:
[data appendMQTTString:@"MQTT"];
[data appendByte:protocolLevel];
break;
}
[data appendByte:flags];
[data appendUInt16BigEndian:keepAlive];
if (protocolLevel == MQTTProtocolVersion50) {
NSMutableData *properties = [[NSMutableData alloc] init];
if (sessionExpiryInterval) {
[properties appendByte:MQTTSessionExpiryInterval];
[properties appendUInt32BigEndian:sessionExpiryInterval.unsignedIntValue];
}
if (authMethod) {
[properties appendByte:MQTTAuthMethod];
[properties appendMQTTString:authMethod];
}
if (authData) {
[properties appendByte:MQTTAuthData];
[properties appendBinaryData:authData];
}
if (requestProblemInformation) {
[properties appendByte:MQTTRequestProblemInformation];
[properties appendByte:requestProblemInformation.unsignedIntValue];
}
if (willDelayInterval) {
[properties appendByte:MQTTWillDelayInterval];
[properties appendUInt32BigEndian:willDelayInterval.unsignedIntValue];
}
if (requestResponseInformation) {
[properties appendByte:MQTTRequestResponseInformation];
[properties appendByte:requestResponseInformation.unsignedIntValue];
}
if (receiveMaximum) {
[properties appendByte:MQTTReceiveMaximum];
[properties appendUInt16BigEndian:receiveMaximum.unsignedIntValue];
}
if (topicAliasMaximum) {
[properties appendByte:MQTTTopicAliasMaximum];
[properties appendUInt16BigEndian:topicAliasMaximum.unsignedIntValue];
}
if (userProperty) {
for (NSString *key in userProperty.allKeys) {
[properties appendByte:MQTTUserProperty];
[properties appendMQTTString:key];
[properties appendMQTTString:userProperty[key]];
}
}
if (maximumPacketSize) {
[properties appendByte:MQTTMaximumPacketSize];
[properties appendUInt32BigEndian:maximumPacketSize.unsignedIntValue];
}
[data appendVariableLength:properties.length];
[data appendData:properties];
}
[data appendMQTTString:clientId];
if (willTopic) {
[data appendMQTTString:willTopic];
}
if (willMsg) {
[data appendUInt16BigEndian:willMsg.length];
[data appendData:willMsg];
}
if (userName) {
[data appendMQTTString:userName];
}
if (password) {
[data appendMQTTString:password];
}
MQTTMessage *msg = [[MQTTMessage alloc] initWithType:MQTTConnect
data:data];
return msg;
}
+ (MQTTMessage *)pingreqMessage {
return [[MQTTMessage alloc] initWithType:MQTTPingreq];
}
+ (MQTTMessage *)disconnectMessage:(MQTTProtocolVersion)protocolLevel
returnCode:(MQTTReturnCode)returnCode
sessionExpiryInterval:(NSNumber *)sessionExpiryInterval
reasonString:(NSString *)reasonString
userProperty:(NSDictionary<NSString *,NSString *> *)userProperty {
NSMutableData* data = [NSMutableData data];
if (protocolLevel == MQTTProtocolVersion50) {
NSMutableData *properties = [[NSMutableData alloc] init];
if (sessionExpiryInterval) {
[properties appendByte:MQTTSessionExpiryInterval];
[properties appendUInt32BigEndian:sessionExpiryInterval.unsignedIntValue];
}
if (reasonString) {
[properties appendByte:MQTTReasonString];
[properties appendMQTTString:reasonString];
}
if (userProperty) {
for (NSString *key in userProperty.allKeys) {
[properties appendByte:MQTTUserProperty];
[properties appendMQTTString:key];
[properties appendMQTTString:userProperty[key]];
}
}
if (returnCode != MQTTSuccess || properties.length > 0) {
[data appendByte:returnCode];
}
if (properties.length > 0) {
[data appendVariableLength:properties.length];
[data appendData:properties];
}
}
MQTTMessage *msg = [[MQTTMessage alloc] initWithType:MQTTDisconnect
data:data];
return msg;
}
+ (MQTTMessage *)subscribeMessageWithMessageId:(UInt16)msgId
topics:(NSDictionary *)topics
protocolLevel:(MQTTProtocolVersion)protocolLevel
subscriptionIdentifier:(NSNumber *)subscriptionIdentifier {
NSMutableData* data = [NSMutableData data];
[data appendUInt16BigEndian:msgId];
if (protocolLevel == MQTTProtocolVersion50) {
NSMutableData *properties = [[NSMutableData alloc] init];
if (subscriptionIdentifier) {
[properties appendByte:MQTTSubscriptionIdentifier];
[properties appendVariableLength:subscriptionIdentifier.unsignedLongValue];
}
[data appendVariableLength:properties.length];
[data appendData:properties];
}
for (NSString *topic in topics.allKeys) {
[data appendMQTTString:topic];
[data appendByte:[topics[topic] intValue]];
}
MQTTMessage* msg = [[MQTTMessage alloc] initWithType:MQTTSubscribe
qos:1
data:data];
msg.mid = msgId;
return msg;
}
+ (MQTTMessage *)unsubscribeMessageWithMessageId:(UInt16)msgId
topics:(NSArray *)topics
protocolLevel:(MQTTProtocolVersion)protocolLevel {
NSMutableData* data = [NSMutableData data];
[data appendUInt16BigEndian:msgId];
for (NSString *topic in topics) {
[data appendMQTTString:topic];
}
MQTTMessage* msg = [[MQTTMessage alloc] initWithType:MQTTUnsubscribe
qos:1
data:data];
msg.mid = msgId;
return msg;
}
+ (MQTTMessage *)publishMessageWithData:(NSData *)payload
onTopic:(NSString *)topic
qos:(MQTTQosLevel)qosLevel
msgId:(UInt16)msgId
retainFlag:(BOOL)retain
dupFlag:(BOOL)dup
protocolLevel:(MQTTProtocolVersion)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 appendMQTTString:topic];
if (msgId) [data appendUInt16BigEndian:msgId];
if (protocolLevel == MQTTProtocolVersion50) {
NSMutableData *properties = [[NSMutableData alloc] init];
if (payloadFormatIndicator) {
[properties appendByte:MQTTPayloadFormatIndicator];
[properties appendByte:payloadFormatIndicator.unsignedIntValue];
}
if (publicationExpiryInterval) {
[properties appendByte:MQTTPublicationExpiryInterval];
[properties appendUInt32BigEndian:publicationExpiryInterval.unsignedIntValue];
}
if (topicAlias) {
[properties appendByte:MQTTTopicAlias];
[properties appendUInt16BigEndian:topicAlias.unsignedIntValue];
}
if (responseTopic) {
[properties appendByte:MQTTResponseTopic];
[properties appendMQTTString:responseTopic];
}
if (correlationData) {
[properties appendByte:MQTTCorrelationData];
[properties appendBinaryData:correlationData];
}
if (userProperty) {
for (NSString *key in userProperty.allKeys) {
[properties appendByte:MQTTUserProperty];
[properties appendMQTTString:key];
[properties appendMQTTString:userProperty[key]];
}
}
if (contentType) {
[properties appendByte:MQTTContentType];
[properties appendMQTTString:contentType];
}
[data appendVariableLength:properties.length];
[data appendData:properties];
}
[data appendData:payload];
MQTTMessage *msg = [[MQTTMessage alloc] initWithType:MQTTPublish
qos:qosLevel
retainFlag:retain
dupFlag:dup
data:data];
msg.mid = msgId;
return msg;
}
+ (MQTTMessage *)pubackMessageWithMessageId:(UInt16)msgId
protocolLevel:(MQTTProtocolVersion)protocolLevel
returnCode:(MQTTReturnCode)returnCode
reasonString:(NSString *)reasonString
userProperty:(NSDictionary<NSString *,NSString *> *)userProperty {
NSMutableData* data = [NSMutableData data];
[data appendUInt16BigEndian:msgId];
if (protocolLevel == MQTTProtocolVersion50) {
NSMutableData *properties = [[NSMutableData alloc] init];
if (reasonString) {
[properties appendByte:MQTTReasonString];
[properties appendMQTTString:reasonString];
}
if (userProperty) {
for (NSString *key in userProperty.allKeys) {
[properties appendByte:MQTTUserProperty];
[properties appendMQTTString:key];
[properties appendMQTTString:userProperty[key]];
}
}
[data appendByte:returnCode];
[data appendVariableLength:properties.length];
[data appendData:properties];
}
MQTTMessage *msg = [[MQTTMessage alloc] initWithType:MQTTPuback
data:data];
msg.mid = msgId;
return msg;
}
+ (MQTTMessage *)pubrecMessageWithMessageId:(UInt16)msgId
protocolLevel:(MQTTProtocolVersion)protocolLevel
returnCode:(MQTTReturnCode)returnCode
reasonString:(NSString *)reasonString
userProperty:(NSDictionary<NSString *,NSString *> *)userProperty {
NSMutableData* data = [NSMutableData data];
[data appendUInt16BigEndian:msgId];
if (protocolLevel == MQTTProtocolVersion50) {
NSMutableData *properties = [[NSMutableData alloc] init];
if (reasonString) {
[properties appendByte:MQTTReasonString];
[properties appendMQTTString:reasonString];
}
if (userProperty) {
for (NSString *key in userProperty.allKeys) {
[properties appendByte:MQTTUserProperty];
[properties appendMQTTString:key];
[properties appendMQTTString:userProperty[key]];
}
}
[data appendByte:returnCode];
[data appendVariableLength:properties.length];
[data appendData:properties];
}
MQTTMessage *msg = [[MQTTMessage alloc] initWithType:MQTTPubrec
data:data];
msg.mid = msgId;
return msg;
}
+ (MQTTMessage *)pubrelMessageWithMessageId:(UInt16)msgId
protocolLevel:(MQTTProtocolVersion)protocolLevel
returnCode:(MQTTReturnCode)returnCode
reasonString:(NSString *)reasonString
userProperty:(NSDictionary<NSString *,NSString *> *)userProperty {
NSMutableData* data = [NSMutableData data];
[data appendUInt16BigEndian:msgId];
if (protocolLevel == MQTTProtocolVersion50) {
NSMutableData *properties = [[NSMutableData alloc] init];
if (reasonString) {
[properties appendByte:MQTTReasonString];
[properties appendMQTTString:reasonString];
}
if (userProperty) {
for (NSString *key in userProperty.allKeys) {
[properties appendByte:MQTTUserProperty];
[properties appendMQTTString:key];
[properties appendMQTTString:userProperty[key]];
}
}
[data appendByte:returnCode];
[data appendVariableLength:properties.length];
[data appendData:properties];
}
MQTTMessage *msg = [[MQTTMessage alloc] initWithType:MQTTPubrel
qos:1
data:data];
msg.mid = msgId;
return msg;
}
+ (MQTTMessage *)pubcompMessageWithMessageId:(UInt16)msgId
protocolLevel:(MQTTProtocolVersion)protocolLevel
returnCode:(MQTTReturnCode)returnCode
reasonString:(NSString *)reasonString
userProperty:(NSDictionary<NSString *,NSString *> *)userProperty {
NSMutableData* data = [NSMutableData data];
[data appendUInt16BigEndian:msgId];
if (protocolLevel == MQTTProtocolVersion50) {
NSMutableData *properties = [[NSMutableData alloc] init];
if (reasonString) {
[properties appendByte:MQTTReasonString];
[properties appendMQTTString:reasonString];
}
if (userProperty) {
for (NSString *key in userProperty.allKeys) {
[properties appendByte:MQTTUserProperty];
[properties appendMQTTString:key];
[properties appendMQTTString:userProperty[key]];
}
}
[data appendByte:returnCode];
[data appendVariableLength:properties.length];
[data appendData:properties];
}
MQTTMessage *msg = [[MQTTMessage alloc] initWithType:MQTTPubcomp
data:data];
msg.mid = msgId;
return msg;
}
- (instancetype)init {
self = [super init];
self.type = 0;
self.qos = MQTTQosLevelAtMostOnce;
self.retainFlag = false;
self.mid = 0;
self.data = nil;
return self;
}
- (instancetype)initWithType:(MQTTCommandType)type {
self = [self init];
self.type = type;
return self;
}
- (instancetype)initWithType:(MQTTCommandType)type
data:(NSData *)data {
self = [self init];
self.type = type;
self.data = data;
return self;
}
- (instancetype)initWithType:(MQTTCommandType)type
qos:(MQTTQosLevel)qos
data:(NSData *)data {
self = [self init];
self.type = type;
self.qos = qos;
self.data = data;
return self;
}
- (instancetype)initWithType:(MQTTCommandType)type
qos:(MQTTQosLevel)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];
}
DDLogVerbose(@"[MQTTMessage] wireFormat(%lu)=%@...",
(unsigned long)buffer.length,
[buffer subdataWithRange:NSMakeRange(0, MIN(256, buffer.length))]);
return buffer;
}
+ (MQTTMessage *)messageFromData:(NSData *)data protocolLevel:(MQTTProtocolVersion)protocolLevel {
MQTTMessage *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) {
DDLogWarn(@"[MQTTMessage] 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) {
DDLogWarn(@"[MQTTMessage] message data too long remaining length");
multiplier = -1;
break;
}
} while ((digit & 0x80) != 0);
if (type >= MQTTConnect &&
type <= MQTTDisconnect) {
if (offset > 0 &&
multiplier > 0 &&
data.length == remainingLength + offset) {
if ((type == MQTTPublish && (qos >= MQTTQosLevelAtMostOnce && qos <= MQTTQosLevelExactlyOnce)) ||
(type == MQTTConnect && qos == 0) ||
(type == MQTTConnack && qos == 0) ||
(type == MQTTPuback && qos == 0) ||
(type == MQTTPubrec && qos == 0) ||
(type == MQTTPubrel && qos == 1) ||
(type == MQTTPubcomp && qos == 0) ||
(type == MQTTSubscribe && qos == 1) ||
(type == MQTTSuback && qos == 0) ||
(type == MQTTUnsubscribe && qos == 1) ||
(type == MQTTUnsuback && qos == 0) ||
(type == MQTTPingreq && qos == 0) ||
(type == MQTTPingresp && qos == 0) ||
(type == MQTTDisconnect && qos == 0)) {
message = [[MQTTMessage 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 == MQTTPublish &&
(qos == MQTTQosLevelAtLeastOnce ||
qos == MQTTQosLevelExactlyOnce)
) ||
type == MQTTPuback ||
type == MQTTPubrec ||
type == MQTTPubrel ||
type == MQTTPubcomp ||
type == MQTTSubscribe ||
type == MQTTSuback ||
type == MQTTUnsubscribe ||
type == MQTTUnsuback) {
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 {
DDLogWarn(@"[MQTTMessage] missing packet identifier");
message = nil;
}
}
if (type == MQTTPuback ||
type == MQTTPubrec ||
type == MQTTPubrel ||
type == MQTTPubcomp) {
if (protocolLevel != MQTTProtocolVersion50) {
if (message.data.length > 2) {
DDLogWarn(@"[MQTTMessage] unexpected payload after packet identifier");
message = nil;
}
} else {
if (message.data.length < 3) {
DDLogWarn(@"[MQTTMessage] no returncode");
message = nil;
} else {
const UInt8 *bytes = message.data.bytes;
message.returnCode = [NSNumber numberWithInt:bytes[2]];
if (message.data.length >= 3) {
message.properties = [[MQTTProperties alloc] initFromData:
[message.data subdataWithRange:NSMakeRange(3, message.data.length - 3)]];
}
}
}
}
if (type == MQTTUnsuback ) {
if (message.data.length > 2) {
DDLogWarn(@"[MQTTMessage] unexpected payload after packet identifier");
message = nil;
}
}
if (type == MQTTPingreq ||
type == MQTTPingresp) {
if (message.data.length > 0) {
DDLogWarn(@"[MQTTMessage] unexpected payload");
message = nil;
}
}
if (type == MQTTDisconnect) {
if (protocolLevel == MQTTProtocolVersion50) {
if (message.data.length == 0) {
message.properties = nil;
message.returnCode = @(MQTTSuccess);
} 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 = [[MQTTProperties alloc] initFromData:
[message.data subdataWithRange:NSMakeRange(1, message.data.length - 1)]];
}
} else {
if (message.data.length != 2) {
DDLogWarn(@"[MQTTMessage] unexpected payload");
message = nil;
}
}
}
if (type == MQTTConnect) {
if (message.data.length < 3) {
DDLogWarn(@"[MQTTMessage] missing connect variable header");
message = nil;
}
}
if (type == MQTTConnack) {
if (protocolLevel == MQTTProtocolVersion50) {
if (message.data.length < 3) {
DDLogWarn(@"[MQTTMessage] missing connack variable header");
message = nil;
}
} else {
if (message.data.length != 2) {
DDLogWarn(@"[MQTTMessage] 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 == MQTTProtocolVersion50) {
message.properties = [[MQTTProperties alloc] initFromData:
[message.data subdataWithRange:NSMakeRange(2, message.data.length - 2)]];
}
}
}
if (type == MQTTSubscribe) {
if (message.data.length < 3) {
DDLogWarn(@"[MQTTMessage] missing subscribe variable header");
message = nil;
}
}
if (type == MQTTSuback) {
if (message.data.length < 3) {
DDLogWarn(@"[MQTTMessage] missing suback variable header");
message = nil;
}
}
if (type == MQTTUnsubscribe) {
if (message.data.length < 3) {
DDLogWarn(@"[MQTTMessage] missing unsubscribe variable header");
message = nil;
}
}
} else {
DDLogWarn(@"[MQTTMessage] illegal header flags");
}
} else {
DDLogWarn(@"[MQTTMessage] remaining data wrong length");
}
} else {
DDLogWarn(@"[MQTTMessage] illegal message type");
}
} else {
DDLogWarn(@"[MQTTMessage] message data length < 2");
}
return message;
}
@end
@implementation NSMutableData (MQTT)
- (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)appendMQTTString:(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

View File

@@ -0,0 +1,124 @@
//
// MQTTPersistence.h
// MQTTClient
//
// Created by Christoph Krey on 22.03.15.
// Copyright © 2015-2017 Christoph Krey. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "MQTTMessage.h"
static BOOL const MQTT_PERSISTENT = NO;
static NSInteger const MQTT_MAX_SIZE = 64 * 1024 * 1024;
static NSInteger const MQTT_MAX_WINDOW_SIZE = 16;
static NSInteger const MQTT_MAX_MESSAGES = 1024;
/** MQTTFlow is an abstraction of the entity to be stored for persistence */
@protocol MQTTFlow
/** The clientID of the flow element */
@property (strong, nonatomic) NSString *clientId;
/** The flag indicating incoming or outgoing flow element */
@property (strong, nonatomic) NSNumber *incomingFlag;
/** The flag indicating if the flow element is retained*/
@property (strong, nonatomic) NSNumber *retainedFlag;
/** The MQTTCommandType of the flow element, might be MQTT_None for offline queueing */
@property (strong, nonatomic) NSNumber *commandType;
/** The MQTTQosLevel of the flow element */
@property (strong, nonatomic) NSNumber *qosLevel;
/** The messageId of the flow element */
@property (strong, nonatomic) NSNumber *messageId;
/** The topic of the flow element */
@property (strong, nonatomic) NSString *topic;
/** The data of the flow element */
@property (strong, nonatomic) NSData *data;
/** The deadline of the flow elelment before (re)trying transmission */
@property (strong, nonatomic) NSDate *deadline;
@end
/** The MQTTPersistence protocol is an abstraction of persistence classes for MQTTSession */
@protocol MQTTPersistence
/** The maximum Window Size for outgoing inflight messages per clientID. Defaults to 16 */
@property (nonatomic) NSUInteger maxWindowSize;
/** The maximum number of messages kept per clientID and direction. Defaults to 1024 */
@property (nonatomic) NSUInteger maxMessages;
/** Indicates if the persistence implementation should make the information permannent. Defaults to NO */
@property (nonatomic) BOOL persistent;
/** The maximum size of the storage used for persistence in total in bytes. Defaults to 1024*1024 bytes */
@property (nonatomic) NSUInteger maxSize;
/** The current Window Size for outgoing inflight messages per clientID.
* @param clientId identifying the session
* @return the current size of the outgoing inflight window
*/
- (NSUInteger)windowSize:(NSString *)clientId;
/** Stores one new message
* @param clientId identifying the session
* @param topic the topic of the message
* @param data the message's data
* @param retainFlag the retain flag of the message
* @param qos the quality of service of the message
* @param msgId the id of the message or zero for qos zero
* @param incomingFlag the direction of the message
* @param commandType the command of the message
* @param deadline the deadline of the message for repetitions
* @return the created MQTTFlow element or nil if the maxWindowSize has been exceeded
*/
- (id<MQTTFlow>)storeMessageForClientId:(NSString *)clientId
topic:(NSString *)topic
data:(NSData *)data
retainFlag:(BOOL)retainFlag
qos:(MQTTQosLevel)qos
msgId:(UInt16)msgId
incomingFlag:(BOOL)incomingFlag
commandType:(UInt8)commandType
deadline:(NSDate *)deadline;
/** Deletes an MQTTFlow element
* @param flow the MQTTFlow to delete
*/
- (void)deleteFlow:(id<MQTTFlow>)flow;
/** Deletes all MQTTFlow elements of a clientId
* @param clientId the client Id identifying all MQTTFlows to be deleted
*/
- (void)deleteAllFlowsForClientId:(NSString *)clientId;
/** Retrieves all MQTTFlow elements of a clientId and direction
* @param clientId whos MQTTFlows should be retrieved
* @param incomingFlag specifies the wether incoming or outgoing flows should be retrieved
* @return an NSArray of the retrieved MQTTFlow elements
*/
- (NSArray *)allFlowsforClientId:(NSString *)clientId
incomingFlag:(BOOL)incomingFlag;
/** Retrieves an MQTTFlow element
* @param clientId to which the MQTTFlow belongs to
* @param incomingFlag specifies the direction of the flow
* @param messageId specifies the message Id of the flow
* @return the retrieved MQTTFlow element or nil if the elememt was not found
*/
- (id<MQTTFlow>)flowforClientId:(NSString *)clientId
incomingFlag:(BOOL)incomingFlag
messageId:(UInt16)messageId;
/** sync is called to allow the MQTTPersistence implemetation to save data permanently */
- (void)sync;
@end

View File

@@ -0,0 +1,76 @@
//
// MQTTProperties.h
// MQTTClient
//
// Created by Christoph Krey on 04.04.17.
// Copyright © 2017 Christoph Krey. All rights reserved.
//
#import <Foundation/Foundation.h>
typedef NS_ENUM(UInt8, MQTTPropertyIdentifier) {
MQTTPayloadFormatIndicator = 1,
MQTTPublicationExpiryInterval = 2,
MQTTContentType = 3,
MQTTResponseTopic = 8,
MQTTCorrelationData = 9,
MQTTSubscriptionIdentifier = 11,
MQTTSessionExpiryInterval = 17,
MQTTAssignedClientIdentifier = 18,
MQTTServerKeepAlive = 19,
MQTTAuthMethod = 21,
MQTTAuthData = 22,
MQTTRequestProblemInformation = 23,
MQTTWillDelayInterval = 24,
MQTTRequestResponseInformation = 25,
MQTTResponseInformation = 26,
MQTTServerReference = 28,
MQTTReasonString = 31,
MQTTReceiveMaximum = 33,
MQTTTopicAliasMaximum = 34,
MQTTTopicAlias = 35,
MQTTMaximumQoS = 36,
MQTTRetainAvailable = 37,
MQTTUserProperty = 38,
MQTTMaximumPacketSize = 39,
MQTTWildcardSubscriptionAvailable = 40,
MQTTSubscriptionIdentifiersAvailable = 41,
MQTTSharedSubscriptionAvailable = 42
};
@interface MQTTProperties : NSObject
@property (strong, nonatomic) NSNumber *payloadFormatIndicator;
@property (strong, nonatomic) NSNumber *publicationExpiryInterval;
@property (strong, nonatomic) NSString *contentType;
@property (strong, nonatomic) NSString *responseTopic;
@property (strong, nonatomic) NSData *correlationData;
@property (strong, nonatomic) NSNumber *subscriptionIdentifier;
@property (strong, nonatomic) NSNumber *sessionExpiryInterval;
@property (strong, nonatomic) NSString *assignedClientIdentifier;
@property (strong, nonatomic) NSNumber *serverKeepAlive;
@property (strong, nonatomic) NSString *authMethod;
@property (strong, nonatomic) NSData *authData;
@property (strong, nonatomic) NSNumber *requestProblemInformation;
@property (strong, nonatomic) NSNumber *willDelayInterval;
@property (strong, nonatomic) NSNumber *requestResponseInformation;
@property (strong, nonatomic) NSString *responseInformation;
@property (strong, nonatomic) NSString *serverReference;
@property (strong, nonatomic) NSString *reasonString;
@property (strong, nonatomic) NSNumber *receiveMaximum;
@property (strong, nonatomic) NSNumber *topicAliasMaximum;
@property (strong, nonatomic) NSNumber *topicAlias;
@property (strong, nonatomic) NSNumber *maximumQoS;
@property (strong, nonatomic) NSNumber *retainAvailable;
@property (strong, nonatomic) NSMutableDictionary <NSString *, NSString *> *userProperty;
@property (strong, nonatomic) NSNumber *maximumPacketSize;
@property (strong, nonatomic) NSNumber *wildcardSubscriptionAvailable;
@property (strong, nonatomic) NSNumber *subscriptionIdentifiersAvailable;
@property (strong, nonatomic) NSNumber *sharedSubscriptionAvailable;
- (instancetype)initFromData:(NSData *)data NS_DESIGNATED_INITIALIZER;
+ (int)getVariableLength:(NSData *)data;
+ (int)variableIntLength:(int)length;
@end

View File

@@ -0,0 +1,336 @@
//
// MQTTProperties.m
// MQTTClient
//
// Created by Christoph Krey on 04.04.17.
// Copyright © 2017 Christoph Krey. All rights reserved.
//
#import "MQTTProperties.h"
@implementation MQTTProperties
- (instancetype)init {
return [self initFromData:[[NSData alloc] init]];
}
- (instancetype)initFromData:(NSData *)data {
self = [super init];
int propertyLength = [MQTTProperties getVariableLength:data];
int offset = [MQTTProperties variableIntLength:propertyLength];
NSData *remainingData = [data subdataWithRange:NSMakeRange(offset, data.length - offset)];
offset = 0;
if (remainingData.length >= propertyLength) {
while (propertyLength - offset > 0) {
const UInt8 *bytes = remainingData.bytes;
UInt8 propertyType = bytes[offset];
switch (propertyType) {
case MQTTPayloadFormatIndicator:
if (propertyLength - offset > 1) {
self.payloadFormatIndicator = [NSNumber numberWithInt:bytes[offset + 1]];
offset += 2;
}
break;
case MQTTPublicationExpiryInterval:
if (propertyLength - offset > 4) {
self.publicationExpiryInterval = @([MQTTProperties getFourByteInt:[remainingData subdataWithRange:NSMakeRange(offset + 1, remainingData.length - (offset + 1))]]);
offset += 5;
}
break;
case MQTTContentType:
if (propertyLength - offset > 2) {
int l = [MQTTProperties getTwoByteInt:[remainingData subdataWithRange:NSMakeRange(offset + 1, remainingData.length - (offset + 1))]];
self.contentType = [MQTTProperties getUtf8String:[remainingData subdataWithRange:NSMakeRange(offset + 1, remainingData.length - (offset + 1))]];
offset += 1 + 2 + l;
}
break;
case MQTTResponseTopic:
if (propertyLength - offset > 2) {
int l = [MQTTProperties getTwoByteInt:[remainingData subdataWithRange:NSMakeRange(offset + 1, remainingData.length - (offset + 1))]];
self.responseTopic = [MQTTProperties getUtf8String:[remainingData subdataWithRange:NSMakeRange(offset + 1, remainingData.length - (offset + 1))]];
offset += 1 + 2 + l;
}
break;
case MQTTCorrelationData:
if (propertyLength - offset > 2) {
int l = [MQTTProperties getTwoByteInt:[remainingData subdataWithRange:NSMakeRange(offset + 1, remainingData.length - (offset + 1))]];
self.correlationData = [MQTTProperties getBinaryData:[remainingData subdataWithRange:NSMakeRange(offset + 1, remainingData.length - (offset + 1))]];
offset += 1 + 2 + l;
}
break;
case MQTTSubscriptionIdentifier:
if (propertyLength - offset > 1) {
int subscriptionIdentifier = [MQTTProperties getVariableLength:[remainingData subdataWithRange:NSMakeRange(offset + 1, remainingData.length - (offset + 1))]];
int l = [MQTTProperties variableIntLength:subscriptionIdentifier];
self.subscriptionIdentifier = @(subscriptionIdentifier);
offset += 1 + l;
}
break;
case MQTTSessionExpiryInterval:
if (propertyLength - offset > 4) {
self.sessionExpiryInterval = @([MQTTProperties getFourByteInt:[remainingData subdataWithRange:NSMakeRange(offset + 1, remainingData.length - (offset + 1))]]);
offset += 5;
}
break;
case MQTTAssignedClientIdentifier:
if (propertyLength - offset > 2) {
int l = [MQTTProperties getTwoByteInt:[remainingData subdataWithRange:NSMakeRange(offset + 1, remainingData.length - (offset + 1))]];
self.assignedClientIdentifier = [MQTTProperties getUtf8String:[remainingData subdataWithRange:NSMakeRange(offset + 1, remainingData.length - (offset + 1))]];
offset += 1 + 2 + l;
}
break;
case MQTTServerKeepAlive:
if (propertyLength - offset > 2) {
self.serverKeepAlive = @([MQTTProperties getTwoByteInt:[remainingData subdataWithRange:NSMakeRange(offset + 1, remainingData.length - (offset + 1))]]);
offset += 3;
}
break;
case MQTTAuthMethod:
if (propertyLength - offset > 2) {
int l = [MQTTProperties getTwoByteInt:[remainingData subdataWithRange:NSMakeRange(offset + 1, remainingData.length - (offset + 1))]];
self.authMethod = [MQTTProperties getUtf8String:[remainingData subdataWithRange:NSMakeRange(offset + 1, remainingData.length - (offset + 1))]];
offset += 1 + 2 + l;
}
break;
case MQTTAuthData:
if (propertyLength - offset > 2) {
int l = [MQTTProperties getTwoByteInt:[remainingData subdataWithRange:NSMakeRange(offset + 1, remainingData.length - (offset + 1))]];
self.authData = [MQTTProperties getBinaryData:[remainingData subdataWithRange:NSMakeRange(offset + 1, remainingData.length - (offset + 1))]];
offset += 1 + 2 + l;
}
break;
case MQTTRequestProblemInformation:
if (propertyLength - offset > 1) {
self.requestProblemInformation = [NSNumber numberWithInt:bytes[offset + 1]];
offset += 2;
}
break;
case MQTTWillDelayInterval:
if (propertyLength - offset > 4) {
self.willDelayInterval = @([MQTTProperties getFourByteInt:[remainingData subdataWithRange:NSMakeRange(offset + 1, remainingData.length - (offset + 1))]]);
offset += 5;
}
break;
case MQTTRequestResponseInformation:
if (propertyLength - offset > 1) {
self.requestResponseInformation = [NSNumber numberWithInt:bytes[offset + 1]];
offset += 2;
}
break;
case MQTTResponseInformation:
if (propertyLength - offset > 2) {
int l = [MQTTProperties getTwoByteInt:[remainingData subdataWithRange:NSMakeRange(offset + 1, remainingData.length - (offset + 1))]];
self.responseInformation = [MQTTProperties getUtf8String:[remainingData subdataWithRange:NSMakeRange(offset + 1, remainingData.length - (offset + 1))]];
offset += 1 + 2 + l;
}
break;
case MQTTServerReference:
if (propertyLength - offset > 2) {
int l = [MQTTProperties getTwoByteInt:[remainingData subdataWithRange:NSMakeRange(offset + 1, remainingData.length - (offset + 1))]];
self.serverReference = [MQTTProperties getUtf8String:[remainingData subdataWithRange:NSMakeRange(offset + 1, remainingData.length - (offset + 1))]];
offset += 1 + 2 + l;
}
break;
case MQTTReasonString:
if (propertyLength - offset > 2) {
int l = [MQTTProperties getTwoByteInt:[remainingData subdataWithRange:NSMakeRange(offset + 1, remainingData.length - (offset + 1))]];
self.reasonString = [MQTTProperties getUtf8String:[remainingData subdataWithRange:NSMakeRange(offset + 1, remainingData.length - (offset + 1))]];
offset += 1 + 2 + l;
}
break;
case MQTTReceiveMaximum:
if (propertyLength - offset > 2) {
self.receiveMaximum = @([MQTTProperties getTwoByteInt:[remainingData subdataWithRange:NSMakeRange(offset + 1, remainingData.length - (offset + 1))]]);
offset += 3;
}
break;
case MQTTTopicAliasMaximum:
if (propertyLength - offset > 2) {
self.topicAliasMaximum = @([MQTTProperties getTwoByteInt:[remainingData subdataWithRange:NSMakeRange(offset + 1, remainingData.length - (offset + 1))]]);
offset += 3;
}
break;
case MQTTTopicAlias:
if (propertyLength - offset > 2) {
self.topicAlias = @([MQTTProperties getTwoByteInt:[remainingData subdataWithRange:NSMakeRange(offset + 1, remainingData.length - (offset + 1))]]);
offset += 3;
}
break;
case MQTTMaximumQoS:
if (propertyLength - offset > 1) {
self.maximumQoS = [NSNumber numberWithInt:bytes[offset + 1]];
offset += 2;
}
break;
case MQTTRetainAvailable:
if (propertyLength - offset > 1) {
self.retainAvailable = [NSNumber numberWithInt:bytes[offset + 1]];
offset += 2;
}
break;
case MQTTUserProperty:
if (propertyLength - offset > 4) {
int keyL = [MQTTProperties getTwoByteInt:[remainingData subdataWithRange:NSMakeRange(offset + 1, remainingData.length - (offset + 1))]];
NSString *key = [MQTTProperties getUtf8String:[remainingData subdataWithRange:NSMakeRange(offset + 1, remainingData.length - (offset + 1))]];
int valueL = [MQTTProperties getTwoByteInt:[remainingData subdataWithRange:NSMakeRange(offset + 1 + 2 + keyL, remainingData.length - (offset + 1))]];
NSString *value = [MQTTProperties getUtf8String:[remainingData subdataWithRange:NSMakeRange(offset + 1 + 2 + keyL, remainingData.length - (offset + 1))]];
if (!self.userProperty) {
self.userProperty = [[NSMutableDictionary alloc] init];
}
self.userProperty[key] = value;
offset += 1 + 2 + keyL + 2 + valueL;
}
break;
case MQTTMaximumPacketSize:
if (propertyLength - offset > 4) {
self.maximumPacketSize = @([MQTTProperties getFourByteInt:[remainingData subdataWithRange:NSMakeRange(offset + 1, remainingData.length - (offset + 1))]]);
offset += 5;
}
break;
case MQTTWildcardSubscriptionAvailable:
if (propertyLength - offset > 1) {
self.wildcardSubscriptionAvailable = [NSNumber numberWithInt:bytes[offset + 1]];
offset += 2;
}
break;
case MQTTSubscriptionIdentifiersAvailable:
if (propertyLength - offset > 1) {
self.subscriptionIdentifiersAvailable = [NSNumber numberWithInt:bytes[offset + 1]];
offset += 2;
}
break;
case MQTTSharedSubscriptionAvailable:
if (propertyLength - offset > 1) {
self.sharedSubscriptionAvailable = [NSNumber numberWithInt:bytes[offset + 1]];
offset += 2;
}
break;
default:
return self;
}
}
}
return self;
}
+ (int)getVariableLength:(NSData *)data {
int length = 0;
int offset = 0;
int multiplier = 1;
UInt8 digit;
do {
if (data.length < offset) {
return -1;
}
[data getBytes:&digit range:NSMakeRange(offset, 1)];
offset++;
length += (digit & 0x7f) * multiplier;
multiplier *= 128;
if (multiplier > 128 * 128 * 128) {
return -2;
}
} while ((digit & 0x80) != 0);
return length;
}
+ (int)getTwoByteInt:(NSData *)data {
int i = 0;
if (data.length >= 2) {
const UInt8 *bytes = data.bytes;
i = bytes[0] * 256 +
bytes[1];
}
return i;
}
+ (int)getFourByteInt:(NSData *)data {
int i = 0;
if (data.length >= 4) {
const UInt8 *bytes = data.bytes;
i = bytes[0] * 256 * 256 * 256 +
bytes[1] * 256 * 256 +
bytes[2] * 256 +
bytes[3];
}
return i;
}
+ (NSString *)getUtf8String:(NSData *)data {
NSString *s;
int l = [MQTTProperties getTwoByteInt:data];
if (data.length >= l + 2) {
s = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(2, l)] encoding:NSUTF8StringEncoding];
}
return s;
}
+ (NSData *)getBinaryData:(NSData *)data {
NSData *d;
int l = [MQTTProperties getTwoByteInt:data];
if (data.length >= l + 2) {
d = [data subdataWithRange:NSMakeRange(2, l)];
}
return d;
}
+ (int)variableIntLength:(int)length {
int l = 0;
if (length <= 127) {
l = 1;
} else if (length <= 16383) {
l = 2;
} else if (length <= 2097151) {
l = 3;
} else if (length <= 268435455) {
l = 4;
}
return l;
}
@end

View File

@@ -0,0 +1,158 @@
//
// MQTTSSLSecurityPolicy.h
// MQTTClient.framework
//
// Created by @bobwenx on 15/6/1.
//
// based on
//
// Copyright (c) 20112015 AFNetwork (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <Foundation/Foundation.h>
#import <Security/Security.h>
/**
## SSL Pinning Modes
The following constants are provided by `MQTTSSLPinningModeNone` as possible SSL pinning modes.
enum {
MQTTSSLPinningModeNone,
MQTTSSLPinningModePublicKey,
MQTTSSLPinningModeCertificate,
}
`MQTTSSLPinningModeNone`
Do not used pinned certificates to validate servers.
`MQTTSSLPinningModePublicKey`
Validate host certificates against public keys of pinned certificates.
`MQTTSSLPinningModeCertificate`
Validate host certificates against pinned certificates.
*/
typedef NS_ENUM(NSUInteger, MQTTSSLPinningMode) {
// Do not used pinned certificates to validate servers.
MQTTSSLPinningModeNone,
// Validate host certificates against public keys of pinned certificates.
MQTTSSLPinningModePublicKey,
// Validate host certificates against pinned certificates.
MQTTSSLPinningModeCertificate,
};
/**
`MQTTSSLSecurityPolicy` evaluates server trust against pinned X.509 certificates and public keys over secure connections.
If your app using security model which require pinning SSL certificates to helps prevent man-in-the-middle attacks
and other vulnerabilities. you need to set securityPolicy to properly value(see MQTTSSLSecurityPolicy.h for more detail).
NOTE: about self-signed server certificates:
if your server using Self-signed certificates to establish SSL/TLS connection, you need to set property:
MQTTSSLSecurityPolicy.allowInvalidCertificates=YES.
If SSL is enabled, by default it only evaluate server's certificates using CA infrastructure, and for most case, this type of check is enough.
However, if your app using security model which require pinning SSL certificates to helps prevent man-in-the-middle attacks
and other vulnerabilities. you may need to set securityPolicy to properly value(see MQTTSSLSecurityPolicy.h for more detail).
NOTE: about self-signed server certificates:
In CA infrastructure, you may establish a SSL/TLS connection with server which using self-signed certificates
by install the certificates into OS keychain(either programmatically or manually). however, this method has some disadvantages:
1. every socket you app created will trust certificates you added.
2. if user choice to remove certificates from keychain, you app need to handling certificates re-adding.
If you only want to verify the cert for the socket you are creating and for no other sockets in your app, you need to use
MQTTSSLSecurityPolicy.
And if you use self-signed server certificates, your need to set property: MQTTSSLSecurityPolicy.allowInvalidCertificates=YES
Adding pinned SSL certificates to your app helps prevent man-in-the-middle attacks and other vulnerabilities.
Applications dealing with sensitive customer data or financial information are strongly encouraged to route all communication
over an SSL/TLS connection with SSL pinning configured and enabled.
*/
@interface MQTTSSLSecurityPolicy : NSObject
/**
The criteria by which server trust should be evaluated against the pinned SSL certificates. Defaults to `MQTTSSLPinningMode`.
*/
@property (readonly, nonatomic, assign) MQTTSSLPinningMode SSLPinningMode;
/**
Whether to evaluate an entire SSL certificate chain, or just the leaf certificate. Defaults to `YES`.
*/
@property (nonatomic, assign) BOOL validatesCertificateChain;
/**
The certificates used to evaluate server trust according to the SSL pinning mode. By default, this property is set to any (`.cer`) certificates included in the app bundle.
Note: Array item type: NSData - Bytes of X.509 certificate file in der format.
Note that if you create an array with duplicate certificates, the duplicate certificates will be removed.
*/
@property (nonatomic, strong) NSArray *pinnedCertificates;
/**
Whether or not to trust servers with an invalid or expired SSL certificates. Defaults to `NO`.
Note: If your server-certificates are self signed, your should set this property to 'YES'.
*/
@property (nonatomic, assign) BOOL allowInvalidCertificates;
/**
Whether or not to validate the domain name in the certificate's CN field. Defaults to `YES`.
*/
@property (nonatomic, assign) BOOL validatesDomainName;
///-----------------------------------------
/// @name Getting Specific Security Policies
///-----------------------------------------
/**
Returns the shared default security policy, which does not allow invalid certificates, validates domain name, and does not validate against pinned certificates or public keys.
@return The default security policy.
*/
+ (instancetype)defaultPolicy;
///---------------------
/// @name Initialization
///---------------------
/**
Creates and returns a security policy with the specified pinning mode.
@param pinningMode The SSL pinning mode.
@return A new security policy.
*/
+ (instancetype)policyWithPinningMode:(MQTTSSLPinningMode)pinningMode;
///------------------------------
/// @name Evaluating Server Trust
///------------------------------
/**
Whether or not the specified server trust should be accepted, based on the security policy.
This method should be used when responding to an authentication challenge from a server.
@param serverTrust The X.509 certificate trust of the server.
@param domain The domain of serverTrust. If `nil`, the domain will not be validated.
@return Whether or not to trust the server.
*/
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain;
@end

View File

@@ -0,0 +1,297 @@
//
// MQTTSSLSecurityPolicy.m
// MQTTClient.framework
//
// Created by @bobwenx on 15/6/1.
//
// based on
//
// Copyright (c) 20112015 AFNetwork (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import "MQTTSSLSecurityPolicy.h"
#import <AssertMacros.h>
#import "MQTTLog.h"
static BOOL SSLSecKeyIsEqualToKey(SecKeyRef key1, SecKeyRef key2) {
return [(__bridge id) key1 isEqual:(__bridge id) key2];
}
static id SSLPublicKeyForCertificate(NSData *certificate) {
id allowedPublicKey = nil;
SecCertificateRef allowedCertificate;
SecCertificateRef allowedCertificates[1];
CFArrayRef tempCertificates = nil;
SecPolicyRef policy = nil;
SecTrustRef allowedTrust = nil;
SecTrustResultType result;
allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate);
__Require_Quiet(allowedCertificate != NULL, _out);
allowedCertificates[0] = allowedCertificate;
tempCertificates = CFArrayCreate(NULL, (const void **)allowedCertificates, 1, NULL);
policy = SecPolicyCreateBasicX509();
__Require_noErr_Quiet(SecTrustCreateWithCertificates(tempCertificates, policy, &allowedTrust), _out);
__Require_noErr_Quiet(SecTrustEvaluate(allowedTrust, &result), _out);
allowedPublicKey = (__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust);
_out:
if (allowedTrust) {
CFRelease(allowedTrust);
}
if (policy) {
CFRelease(policy);
}
if (tempCertificates) {
CFRelease(tempCertificates);
}
if (allowedCertificate) {
CFRelease(allowedCertificate);
}
return allowedPublicKey;
}
static BOOL SSLServerTrustIsValid(SecTrustRef serverTrust) {
BOOL isValid = NO;
SecTrustResultType result;
__Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out);
isValid = (result == kSecTrustResultUnspecified // The OS trusts this certificate implicitly.
|| result == kSecTrustResultProceed); // The user explicitly told the OS to trust it.
// else? It's somebody else's key. Fall immediately.
_out:
return isValid;
}
static NSArray * SSLCertificateTrustChainForServerTrust(SecTrustRef serverTrust) {
CFIndex certificateCount = SecTrustGetCertificateCount(serverTrust);
NSMutableArray *trustChain = [NSMutableArray arrayWithCapacity:(NSUInteger)certificateCount];
for (CFIndex i = 0; i < certificateCount; i++) {
SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, i);
[trustChain addObject:(__bridge_transfer NSData *)SecCertificateCopyData(certificate)];
}
return [NSArray arrayWithArray:trustChain];
}
static NSArray * SSLPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) {
SecPolicyRef policy = SecPolicyCreateBasicX509();
CFIndex certificateCount = SecTrustGetCertificateCount(serverTrust);
NSMutableArray *trustChain = [NSMutableArray arrayWithCapacity:(NSUInteger)certificateCount];
for (CFIndex i = 0; i < certificateCount; i++) {
SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, i);
SecCertificateRef someCertificates[] = {certificate};
CFArrayRef certificates = CFArrayCreate(NULL, (const void **)someCertificates, 1, NULL);
SecTrustRef trust;
__Require_noErr_Quiet(SecTrustCreateWithCertificates(certificates, policy, &trust), _out);
SecTrustResultType result;
__Require_noErr_Quiet(SecTrustEvaluate(trust, &result), _out);
[trustChain addObject:(__bridge_transfer id)SecTrustCopyPublicKey(trust)];
_out:
if (trust) {
CFRelease(trust);
}
if (certificates) {
CFRelease(certificates);
}
continue;
}
CFRelease(policy);
return [NSArray arrayWithArray:trustChain];
}
@interface MQTTSSLSecurityPolicy()
@property (readwrite, nonatomic, assign) MQTTSSLPinningMode SSLPinningMode;
@property (readwrite, nonatomic, strong) NSArray *pinnedPublicKeys;
@end
@implementation MQTTSSLSecurityPolicy
#pragma mark - SSL Security Policy
+ (NSArray *)defaultPinnedCertificates {
static NSArray *_defaultPinnedCertificates = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
NSArray *paths = [bundle pathsForResourcesOfType:@"cer" inDirectory:@"."];
NSMutableArray *certificates = [NSMutableArray arrayWithCapacity:paths.count];
for (NSString *path in paths) {
NSData *certificateData = [NSData dataWithContentsOfFile:path];
[certificates addObject:certificateData];
}
_defaultPinnedCertificates = [[NSArray alloc] initWithArray:certificates];
});
return _defaultPinnedCertificates;
}
+ (instancetype)defaultPolicy {
MQTTSSLSecurityPolicy *securityPolicy = [[self alloc] init];
securityPolicy.SSLPinningMode = MQTTSSLPinningModeNone;
return securityPolicy;
}
+ (instancetype)policyWithPinningMode:(MQTTSSLPinningMode)pinningMode {
MQTTSSLSecurityPolicy *securityPolicy = [[self alloc] init];
securityPolicy.SSLPinningMode = pinningMode;
securityPolicy.pinnedCertificates = [self defaultPinnedCertificates];
return securityPolicy;
}
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
self.validatesCertificateChain = YES;
self.validatesDomainName = YES;
return self;
}
- (void)setPinnedCertificates:(NSArray *)pinnedCertificates {
_pinnedCertificates = [NSOrderedSet orderedSetWithArray:pinnedCertificates].array;
if (self.pinnedCertificates) {
NSMutableArray *mutablePinnedPublicKeys = [NSMutableArray arrayWithCapacity:(self.pinnedCertificates).count];
for (NSData *certificate in self.pinnedCertificates) {
id publicKey = SSLPublicKeyForCertificate(certificate);
if (!publicKey) {
continue;
}
[mutablePinnedPublicKeys addObject:publicKey];
}
self.pinnedPublicKeys = [NSArray arrayWithArray:mutablePinnedPublicKeys];
} else {
self.pinnedPublicKeys = nil;
}
}
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain
{
NSMutableArray *policies = [NSMutableArray array];
if (self.validatesDomainName) {
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
} else {
[policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
if (self.SSLPinningMode == MQTTSSLPinningModeNone) {
return self.allowInvalidCertificates || SSLServerTrustIsValid(serverTrust);
}
// if client didn't allow invalid certs, it must pass CA infrastructure
// TODO: Can we change order here?
else if (!SSLServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
return NO;
}
NSArray *serverCertificates = SSLCertificateTrustChainForServerTrust(serverTrust);
switch (self.SSLPinningMode) {
case MQTTSSLPinningModeNone:
default:
return NO;
case MQTTSSLPinningModeCertificate: {
NSMutableArray *pinnedCertificates = [NSMutableArray array];
for (NSData *certificateData in self.pinnedCertificates) {
@try {
[pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
} @catch (NSException *exception) {
//fix issue #151, if the pinnedCertification is not a valid DER-encoded X.509 certificate, for example it is the PEM format, SecCertificateCreateWithData will return nil, and application will crash
if ([exception.name isEqual:NSInvalidArgumentException]) {
return NO;
}
}
}
SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);
if (!SSLServerTrustIsValid(serverTrust)) {
return NO;
}
if (!self.validatesCertificateChain) {
return YES;
}
NSUInteger trustedCertificateCount = 0;
for (NSData *trustChainCertificate in serverCertificates) {
if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
trustedCertificateCount++;
}
}
return trustedCertificateCount == serverCertificates.count;
}
case MQTTSSLPinningModePublicKey: {
NSUInteger trustedPublicKeyCount = 0;
NSArray *publicKeys = SSLPublicKeyTrustChainForServerTrust(serverTrust);
if (!self.validatesCertificateChain && publicKeys.count > 0) {
publicKeys = @[publicKeys.firstObject];
}
for (id trustChainPublicKey in publicKeys) {
for (id pinnedPublicKey in self.pinnedPublicKeys) {
if (SSLSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
trustedPublicKeyCount += 1;
}
}
}
return trustedPublicKeyCount > 0 && ((self.validatesCertificateChain && trustedPublicKeyCount == serverCertificates.count) || (!self.validatesCertificateChain && trustedPublicKeyCount >= 1));
}
}
return NO;
}
#pragma mark - NSKeyValueObserving
+ (NSSet *)keyPathsForValuesAffectingPinnedPublicKeys {
return [NSSet setWithObject:@"pinnedCertificates"];
}
@end

View File

@@ -0,0 +1,19 @@
//
// MQTTSSLSecurityPolicyDecoder.h
// MQTTClient.framework
//
// Copyright © 2013-2017, Christoph Krey. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "MQTTSSLSecurityPolicy.h"
#import "MQTTCFSocketDecoder.h"
@interface MQTTSSLSecurityPolicyDecoder : MQTTCFSocketDecoder
@property(strong, nonatomic) MQTTSSLSecurityPolicy *securityPolicy;
@property(strong, nonatomic) NSString *securityDomain;
@end

View File

@@ -0,0 +1,60 @@
//
// MQTTSSLSecurityPolicyDecoder.m
// MQTTClient.framework
//
// Copyright © 2013-2017, Christoph Krey. All rights reserved.
//
#import "MQTTSSLSecurityPolicyDecoder.h"
#import "MQTTLog.h"
@interface MQTTSSLSecurityPolicyDecoder()
@property (nonatomic) BOOL securityPolicyApplied;
@end
@implementation MQTTSSLSecurityPolicyDecoder
- (instancetype)init {
self = [super init];
self.securityPolicy = nil;
self.securityDomain = nil;
return self;
}
- (BOOL)applySSLSecurityPolicy:(NSStream *)readStream withEvent:(NSStreamEvent)eventCode{
if (!self.securityPolicy) {
return YES;
}
if (self.securityPolicyApplied) {
return YES;
}
SecTrustRef serverTrust = (__bridge SecTrustRef) [readStream propertyForKey: (__bridge NSString *)kCFStreamPropertySSLPeerTrust];
if (!serverTrust) {
return NO;
}
self.securityPolicyApplied = [self.securityPolicy evaluateServerTrust:serverTrust forDomain:self.securityDomain];
return self.securityPolicyApplied;
}
- (void)stream:(NSStream *)sender handleEvent:(NSStreamEvent)eventCode {
if (eventCode & NSStreamEventHasBytesAvailable) {
DDLogVerbose(@"[MQTTCFSocketDecoder] NSStreamEventHasBytesAvailable");
if (![self applySSLSecurityPolicy:sender withEvent:eventCode]){
self.state = MQTTCFSocketDecoderStateError;
self.error = [NSError errorWithDomain:@"MQTT"
code:errSSLXCertChainInvalid
userInfo:@{NSLocalizedDescriptionKey: @"Unable to apply security policy, the SSL connection is insecure!"}];
[self.delegate decoder:self didFailWithError:self.error];
return;
}
}
[super stream:sender handleEvent:eventCode];
}
@end

View File

@@ -0,0 +1,18 @@
//
// MQTTSSLSecurityPolicyEncoder.h
// MQTTClient.framework
//
// Copyright © 2013-2017, Christoph Krey. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "MQTTSSLSecurityPolicy.h"
#import "MQTTCFSocketEncoder.h"
@interface MQTTSSLSecurityPolicyEncoder : MQTTCFSocketEncoder
@property(strong, nonatomic) MQTTSSLSecurityPolicy *securityPolicy;
@property(strong, nonatomic) NSString *securityDomain;
@end

View File

@@ -0,0 +1,60 @@
//
// MQTTSSLSecurityPolicyEncoder.m
// MQTTClient.framework
//
// Copyright © 2013-2017, Christoph Krey. All rights reserved.
//
#import "MQTTSSLSecurityPolicyEncoder.h"
#import "MQTTLog.h"
@interface MQTTSSLSecurityPolicyEncoder()
@property (nonatomic) BOOL securityPolicyApplied;
@end
@implementation MQTTSSLSecurityPolicyEncoder
- (instancetype)init {
self = [super init];
self.securityPolicy = nil;
self.securityDomain = nil;
return self;
}
- (BOOL)applySSLSecurityPolicy:(NSStream *)writeStream withEvent:(NSStreamEvent)eventCode {
if (!self.securityPolicy) {
return YES;
}
if (self.securityPolicyApplied) {
return YES;
}
SecTrustRef serverTrust = (__bridge SecTrustRef)[writeStream propertyForKey:(__bridge NSString *)kCFStreamPropertySSLPeerTrust];
if (!serverTrust) {
return NO;
}
self.securityPolicyApplied = [self.securityPolicy evaluateServerTrust:serverTrust forDomain:self.securityDomain];
return self.securityPolicyApplied;
}
- (void)stream:(NSStream *)sender handleEvent:(NSStreamEvent)eventCode {
if (eventCode & NSStreamEventHasSpaceAvailable) {
DDLogVerbose(@"[MQTTCFSocketEncoder] NSStreamEventHasSpaceAvailable");
if (![self applySSLSecurityPolicy:sender withEvent:eventCode]){
self.state = MQTTCFSocketEncoderStateError;
self.error = [NSError errorWithDomain:@"MQTT"
code:errSSLXCertChainInvalid
userInfo:@{NSLocalizedDescriptionKey: @"Unable to apply security policy, the SSL connection is insecure!"}];
[self.delegate encoder:self didFailWithError:self.error];
return;
}
}
[super stream:sender handleEvent:eventCode];
}
@end

View File

@@ -0,0 +1,30 @@
//
// MQTTSSLSecurityPolicyTransport.h
// MQTTClient
//
// Created by Christoph Krey on 06.12.15.
// Copyright © 2015-2017 Christoph Krey. All rights reserved.
//
#import "MQTTTransport.h"
#import "MQTTSSLSecurityPolicy.h"
#import "MQTTCFSocketTransport.h"
/** MQTTSSLSecurityPolicyTransport
* implements an extension of the MQTTCFSocketTransport by replacing the OS's certificate chain evaluation
*/
@interface MQTTSSLSecurityPolicyTransport : MQTTCFSocketTransport
/**
* The security policy used to evaluate server trust for secure connections.
*
* if your app using security model which require pinning SSL certificates to helps prevent man-in-the-middle attacks
* and other vulnerabilities. you need to set securityPolicy to properly value(see MQTTSSLSecurityPolicy.h for more detail).
*
* NOTE: about self-signed server certificates:
* if your server using Self-signed certificates to establish SSL/TLS connection, you need to set property:
* MQTTSSLSecurityPolicy.allowInvalidCertificates=YES.
*/
@property (strong, nonatomic) MQTTSSLSecurityPolicy *securityPolicy;
@end

View File

@@ -0,0 +1,98 @@
//
// MQTTSSLSecurityPolicyTransport.m
// MQTTClient
//
// Created by Christoph Krey on 06.12.15.
// Copyright © 2015-2017 Christoph Krey. All rights reserved.
//
#import "MQTTSSLSecurityPolicyTransport.h"
#import "MQTTSSLSecurityPolicyEncoder.h"
#import "MQTTSSLSecurityPolicyDecoder.h"
#import "MQTTLog.h"
@interface MQTTSSLSecurityPolicyTransport()
@property (strong, nonatomic) MQTTSSLSecurityPolicyEncoder *encoder;
@property (strong, nonatomic) MQTTSSLSecurityPolicyDecoder *decoder;
@end
@implementation MQTTSSLSecurityPolicyTransport
@synthesize state;
@synthesize delegate;
- (instancetype)init {
self = [super init];
self.securityPolicy = nil;
return self;
}
- (void)open {
DDLogVerbose(@"[MQTTSSLSecurityPolicyTransport] open");
self.state = MQTTTransportOpening;
NSError* connectError;
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)self.host, self.port, &readStream, &writeStream);
CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
if (self.tls) {
NSMutableDictionary *sslOptions = [[NSMutableDictionary alloc] init];
// Delegate certificates verify operation to our secure policy.
// by disabling chain validation, it becomes our responsibility to verify that the host at the other end can be trusted.
// the server's certificates will be verified during MQTT encoder/decoder processing.
sslOptions[(NSString *)kCFStreamSSLValidatesCertificateChain] = @NO;
sslOptions[(NSString *)kCFStreamSSLLevel] = self.streamSSLLevel;
if (self.certificates) {
sslOptions[(NSString *)kCFStreamSSLCertificates] = self.certificates;
}
if (!CFReadStreamSetProperty(readStream, kCFStreamPropertySSLSettings, (__bridge CFDictionaryRef)(sslOptions))){
connectError = [NSError errorWithDomain:@"MQTT"
code:errSSLInternal
userInfo:@{NSLocalizedDescriptionKey : @"Fail to init ssl input stream!"}];
}
if (!CFWriteStreamSetProperty(writeStream, kCFStreamPropertySSLSettings, (__bridge CFDictionaryRef)(sslOptions))){
connectError = [NSError errorWithDomain:@"MQTT"
code:errSSLInternal
userInfo:@{NSLocalizedDescriptionKey : @"Fail to init ssl output stream!"}];
}
}
if (!connectError) {
self.encoder = [[MQTTSSLSecurityPolicyEncoder alloc] init];
CFWriteStreamSetDispatchQueue(writeStream, self.queue);
self.encoder.stream = CFBridgingRelease(writeStream);
self.encoder.securityPolicy = self.tls ? self.securityPolicy : nil;
self.encoder.securityDomain = self.tls ? self.host : nil;
self.encoder.delegate = self;
if (self.voip) {
[self.encoder.stream setProperty:NSStreamNetworkServiceTypeVoIP forKey:NSStreamNetworkServiceType];
}
[self.encoder open];
self.decoder = [[MQTTSSLSecurityPolicyDecoder alloc] init];
CFReadStreamSetDispatchQueue(readStream, self.queue);
self.decoder.stream = CFBridgingRelease(readStream);
self.decoder.securityPolicy = self.tls ? self.securityPolicy : nil;
self.decoder.securityDomain = self.tls ? self.host : nil;
self.decoder.delegate = self;
if (self.voip) {
[self.decoder.stream setProperty:NSStreamNetworkServiceTypeVoIP forKey:NSStreamNetworkServiceType];
}
[self.decoder open];
} else {
[self close];
}
}
@end

View File

@@ -0,0 +1,886 @@
//
// MQTTSession.h
// MQTTClient.framework
//
/**
Using MQTT in your Objective-C application
@author Christoph Krey c@ckrey.de
@copyright 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
@see http://mqtt.org
*/
#import <Foundation/Foundation.h>
#import "MQTTMessage.h"
#import "MQTTPersistence.h"
#import "MQTTTransport.h"
@class MQTTSession;
@class MQTTSSLSecurityPolicy;
/**
Enumeration of MQTTSession states
*/
typedef NS_ENUM(NSInteger, MQTTSessionStatus) {
MQTTSessionStatusCreated,
MQTTSessionStatusConnecting,
MQTTSessionStatusConnected,
MQTTSessionStatusDisconnecting,
MQTTSessionStatusClosed,
MQTTSessionStatusError
};
/**
Enumeration of MQTTSession events
*/
typedef NS_ENUM(NSInteger, MQTTSessionEvent) {
MQTTSessionEventConnected,
MQTTSessionEventConnectionRefused,
MQTTSessionEventConnectionClosed,
MQTTSessionEventConnectionError,
MQTTSessionEventProtocolError,
MQTTSessionEventConnectionClosedByBroker
};
/**
The error domain used for all errors created by MQTTSession
*/
extern NSString * const MQTTSessionErrorDomain;
/**
The error codes used for all errors created by MQTTSession
*/
typedef NS_ENUM(NSInteger, MQTTSessionError) {
MQTTSessionErrorConnectionRefused = -8, // Sent if the server closes the connection without sending an appropriate error CONNACK
MQTTSessionErrorIllegalMessageReceived = -7,
MQTTSessionErrorDroppingOutgoingMessage = -6, // For some reason the value is the same as for MQTTSessionErrorNoResponse
MQTTSessionErrorNoResponse = -6, // For some reason the value is the same as for MQTTSessionErrorDroppingOutgoingMessage
MQTTSessionErrorEncoderNotReady = -5,
MQTTSessionErrorInvalidConnackReceived = -2, // Sent if the message received from server was an invalid connack message
MQTTSessionErrorNoConnackReceived = -1, // Sent if first message received from server was no connack message
MQTTSessionErrorConnackUnacceptableProtocolVersion = 1, // Value as defined by MQTT Protocol
MQTTSessionErrorConnackIdentifierRejected = 2, // Value as defined by MQTT Protocol
MQTTSessionErrorConnackServeUnavailable = 3, // Value as defined by MQTT Protocol
MQTTSessionErrorConnackBadUsernameOrPassword = 4, // Value as defined by MQTT Protocol
MQTTSessionErrorConnackNotAuthorized = 5, // Value as defined by MQTT Protocol
MQTTSessionErrorConnackReserved = 6, // Should be value 6-255, as defined by MQTT Protocol
};
/** Session delegate gives your application control over the MQTTSession
@note all callback methods are optional
*/
@protocol MQTTSessionDelegate <NSObject>
@optional
/** gets called when a new message was received
@param session the MQTTSession reporting the new message
@param data the data received, might be zero length
@param topic the topic the data was published to
@param qos the qos of the message
@param retained indicates if the data retransmitted from server storage
@param mid the Message Identifier of the message if qos = 1 or 2, zero otherwise
*/
- (void)newMessage:(MQTTSession *)session
data:(NSData *)data
onTopic:(NSString *)topic
qos:(MQTTQosLevel)qos
retained:(BOOL)retained
mid:(unsigned int)mid;
/** gets called when a new message was received
@param session the MQTTSession reporting the new message
@param data the data received, might be zero length
@param topic the topic the data was published to
@param qos the qos of the message
@param retained indicates if the data retransmitted from server storage
@param mid the Message Identifier of the message if qos = 1 or 2, zero otherwise
@return true if the message was or will be processed, false if the message shall not be ack-ed
*/
- (BOOL)newMessageWithFeedback:(MQTTSession *)session
data:(NSData *)data
onTopic:(NSString *)topic
qos:(MQTTQosLevel)qos
retained:(BOOL)retained
mid:(unsigned int)mid;
/** for mqttio-OBJC backward compatibility
@param session see newMessage for description
@param data see newMessage for description
@param topic see newMessage for description
*/
- (void)session:(MQTTSession*)session newMessage:(NSData*)data onTopic:(NSString*)topic;
/** gets called when a connection is established, closed or a problem occurred
@param session the MQTTSession reporting the event
@param eventCode the code of the event
@param error an optional additional error object with additional information
*/
- (void)handleEvent:(MQTTSession *)session event:(MQTTSessionEvent)eventCode error:(NSError *)error;
/** for mqttio-OBJC backward compatibility
@param session the MQTTSession reporting the event
@param eventCode the code of the event
*/
- (void)session:(MQTTSession*)session handleEvent:(MQTTSessionEvent)eventCode;
/** gets called when a connection has been successfully established
@param session the MQTTSession reporting the connect
*/
- (void)connected:(MQTTSession *)session;
/** gets called when a connection has been successfully established
@param session the MQTTSession reporting the connect
@param sessionPresent represents the Session Present flag sent by the broker
*/
- (void)connected:(MQTTSession *)session sessionPresent:(BOOL)sessionPresent;
/** gets called when a connection has been refused
@param session the MQTTSession reporting the refusal
@param error an optional additional error object with additional information
*/
- (void)connectionRefused:(MQTTSession *)session error:(NSError *)error;
/** gets called when a connection has been closed
@param session the MQTTSession reporting the close
*/
- (void)connectionClosed:(MQTTSession *)session;
/** gets called when a connection error happened
@param session the MQTTSession reporting the connect error
@param error an optional additional error object with additional information
*/
- (void)connectionError:(MQTTSession *)session error:(NSError *)error;
/** gets called when an MQTT protocol error happened
@param session the MQTTSession reporting the protocol error
@param error an optional additional error object with additional information
*/
- (void)protocolError:(MQTTSession *)session error:(NSError *)error;
/** gets called when a published message was actually delivered
@param session the MQTTSession reporting the delivery
@param msgID the Message Identifier of the delivered message
@note this method is called after a publish with qos 1 or 2 only
*/
- (void)messageDelivered:(MQTTSession *)session msgID:(UInt16)msgID;
/** gets called when a published message was actually delivered
@param session the MQTTSession reporting the delivery
@param msgID the Message Identifier of the delivered message
@param topic the topic of the delivered message
@param data the data Identifier of the delivered message
@param qos the QoS level of the delivered message
@param retainFlag the retain Flag of the delivered message
@note this method is called after a publish with qos 1 or 2 only
*/
- (void)messageDelivered:(MQTTSession *)session
msgID:(UInt16)msgID
topic:(NSString *)topic
data:(NSData *)data
qos:(MQTTQosLevel)qos
retainFlag:(BOOL)retainFlag;
/** gets called when a subscription is acknowledged by the MQTT broker
@param session the MQTTSession reporting the acknowledge
@param msgID the Message Identifier of the SUBSCRIBE message
@param qoss an array containing the granted QoS(s) related to the SUBSCRIBE message
(see subscribeTopic, subscribeTopics)
*/
- (void)subAckReceived:(MQTTSession *)session msgID:(UInt16)msgID grantedQoss:(NSArray<NSNumber *> *)qoss;
/** gets called when an unsubscribe is acknowledged by the MQTT broker
@param session the MQTTSession reporting the acknowledge
@param msgID the Message Identifier of the UNSUBSCRIBE message
*/
- (void)unsubAckReceived:(MQTTSession *)session msgID:(UInt16)msgID;
/** gets called when a command is sent to the MQTT broker
use this for low level monitoring of the MQTT connection
@param session the MQTTSession reporting the sent command
@param type the MQTT command type
@param qos the Quality of Service of the command
@param retained the retained status of the command
@param duped the duplication status of the command
@param mid the Message Identifier of the command
@param data the payload data of the command if any, might be zero length
*/
- (void)sending:(MQTTSession *)session type:(MQTTCommandType)type qos:(MQTTQosLevel)qos retained:(BOOL)retained duped:(BOOL)duped mid:(UInt16)mid data:(NSData *)data;
/** gets called when a command is received from the MQTT broker
use this for low level monitoring of the MQTT connection
@param session the MQTTSession reporting the received command
@param type the MQTT command type
@param qos the Quality of Service of the command
@param retained the retained status of the command
@param duped the duplication status of the command
@param mid the Message Identifier of the command
@param data the payload data of the command if any, might be zero length
*/
- (void)received:(MQTTSession *)session type:(MQTTCommandType)type qos:(MQTTQosLevel)qos retained:(BOOL)retained duped:(BOOL)duped mid:(UInt16)mid data:(NSData *)data;
/** gets called when a command is received from the MQTT broker
use this for low level control of the MQTT connection
@param session the MQTTSession reporting the received command
@param type the MQTT command type
@param qos the Quality of Service of the command
@param retained the retained status of the command
@param duped the duplication status of the command
@param mid the Message Identifier of the command
@param data the payload data of the command if any, might be zero length
@return true if the sessionmanager should ignore the received message
*/
- (BOOL)ignoreReceived:(MQTTSession *)session type:(MQTTCommandType)type qos:(MQTTQosLevel)qos retained:(BOOL)retained duped:(BOOL)duped mid:(UInt16)mid data:(NSData *)data;
/** gets called when the content of MQTTClients internal buffers change
use for monitoring the completion of transmitted and received messages
@param session the MQTTSession reporting the change
@param queued for backward compatibility only: MQTTClient does not queue messages anymore except during QoS protocol
@param flowingIn the number of incoming messages not acknowledged by the MQTTClient yet
@param flowingOut the number of outgoing messages not yet acknowledged by the MQTT broker
*/
- (void)buffered:(MQTTSession *)session
queued:(NSUInteger)queued
flowingIn:(NSUInteger)flowingIn
flowingOut:(NSUInteger)flowingOut;
/** gets called when the content of MQTTClients internal buffers change
use for monitoring the completion of transmitted and received messages
@param session the MQTTSession reporting the change
@param flowingIn the number of incoming messages not acknowledged by the MQTTClient yet
@param flowingOut the number of outgoing messages not yet acknowledged by the MQTT broker
*/
- (void)buffered:(MQTTSession *)session
flowingIn:(NSUInteger)flowingIn
flowingOut:(NSUInteger)flowingOut;
@end
typedef void (^MQTTConnectHandler)(NSError *error);
typedef void (^MQTTDisconnectHandler)(NSError *error);
typedef void (^MQTTSubscribeHandler)(NSError *error, NSArray<NSNumber *> *gQoss);
typedef void (^MQTTUnsubscribeHandler)(NSError *error);
typedef void (^MQTTPublishHandler)(NSError *error);
/** Session implements the MQTT protocol for your application
*
*/
@interface MQTTSession : NSObject
/** set this member variable to receive delegate messages
@code
#import "MQTTClient.h"
@interface MyClass : NSObject <MQTTSessionDelegate>
...
@end
...
MQTTSession *session = [[MQTTSession alloc] init];
session.delegate = self;
...
- (void)handleEvent:(MQTTSession *)session
event:(MQTTSessionEvent)eventCode
error:(NSError *)error {
...
}
- (void)newMessage:(MQTTSession *)session
data:(NSData *)data
onTopic:(NSString *)topic
qos:(MQTTQosLevel)qos
retained:(BOOL)retained
mid:(unsigned int)mid {
...
}
@endcode
*/
@property (weak, nonatomic) id<MQTTSessionDelegate> delegate;
/** Control MQTT persistence by setting the properties of persistence before connecting to an MQTT broker.
The settings are specific to a clientId.
persistence.persistent = YES or NO (default) to establish file or in memory persistence. IMPORTANT: set immediately after creating the MQTTSession before calling any other method. Otherwise the default value (NO) will be used
for this session.
persistence.maxWindowSize (a positive number, default is 16) to control the number of messages sent before waiting for acknowledgement in Qos 1 or 2. Additional messages are
stored and transmitted later.
persistence.maxSize (a positive number of bytes, default is 64 MB) to limit the size of the persistence file. Messages published after the limit is reached are dropped.
persistence.maxMessages (a positive number, default is 1024) to limit the number of messages stored. Additional messages published are dropped.
Messages are deleted after they have been acknowledged.
*/
@property (strong, nonatomic) id<MQTTPersistence> persistence;
/** block called once when connection is established
*/
@property (copy, nonatomic) MQTTConnectHandler connectHandler;
/** block called when connection is established
*/
@property (strong) void (^connectionHandler)(MQTTSessionEvent event);
/** block called when message is received
*/
@property (strong) void (^messageHandler)(NSData* message, NSString* topic);
/** Session status
*/
@property (nonatomic, readonly) MQTTSessionStatus status;
/** Indicates if the broker found a persistent session when connecting with cleanSession:FALSE
*/
@property (nonatomic, readonly) BOOL sessionPresent;
/** streamSSLLevel an NSString containing the security level for read and write streams
* For list of possible values see:
* https://developer.apple.com/documentation/corefoundation/cfstream/cfstream_socket_security_level_constants
* Please also note that kCFStreamSocketSecurityLevelTLSv1_2 is not in a list
* and cannot be used as constant, but you can use it as a string value
* defaults to kCFStreamSocketSecurityLevelNegotiatedSSL
*/
@property (strong, nonatomic) NSString *streamSSLLevel;
/** host an NSString containing the hostName or IP address of the Server
*/
@property (readonly) NSString *host;
/** port an unsigned 32 bit integer containing the IP port number of the Server
*/
@property (readonly) UInt32 port;
/** The Client Identifier identifies the Client to the Server. If nil, a random clientId is generated.
*/
@property (strong, nonatomic) NSString *clientId;
/** see userName an NSString object containing the user's name (or ID) for authentication. May be nil. */
@property (strong, nonatomic) NSString *userName;
/** see password an NSString object containing the user's password. If userName is nil, password must be nil as well.*/
@property (strong, nonatomic) NSString *password;
/** see keepAliveInterval The Keep Alive is a time interval measured in seconds.
* The MQTTClient ensures that the interval between Control Packets being sent does not exceed
* the Keep Alive value. In the absence of sending any other Control Packets, the Client sends a PINGREQ Packet.
*/
@property (nonatomic) UInt16 keepAliveInterval;
/** The serverKeepAlive is a time interval measured in seconds.
* This value may be set by the broker and overrides keepAliveInterval if present
* Zero means the broker does not perform any keep alive checks
*/
@property (readonly, strong, nonatomic) NSNumber *serverKeepAlive;
/** effectiveKeepAlive is a time interval measured in seconds
* It indicates the effective keep alive interval after a successfull connect
* where keepAliveInterval might have been overridden by the broker.
*/
@property (readonly, nonatomic) UInt16 effectiveKeepAlive;
/**
* dupTimeout If PUBACK or PUBREC not received, message will be resent after this interval
*/
@property (nonatomic) double dupTimeout;
/** leanSessionFlag specifies if the server should discard previous session information. */
@property (nonatomic) BOOL cleanSessionFlag;
/** willFlag If the Will Flag is set to YES this indicates that
* a Will Message MUST be published by the Server when the Server detects
* that the Client is disconnected for any reason other than the Client flowing a DISCONNECT Packet.
*/
@property (nonatomic) BOOL willFlag;
/** willTopic If the Will Flag is set to YES, the Will Topic is a string, nil otherwise. */
@property (strong, nonatomic) NSString *willTopic;
/** willMsg If the Will Flag is set to YES the Will Message must be specified, nil otherwise. */
@property (strong, nonatomic) NSData *willMsg;
/** willQoS specifies the QoS level to be used when publishing the Will Message.
* If the Will Flag is set to NO, then the Will QoS MUST be set to 0.
* If the Will Flag is set to YES, the Will QoS MUST be a valid MQTTQosLevel.
*/
@property (nonatomic) MQTTQosLevel willQoS;
/** willRetainFlag indicates if the server should publish the Will Messages with retainFlag.
* If the Will Flag is set to NO, then the Will Retain Flag MUST be set to NO .
* If the Will Flag is set to YES: If Will Retain is set to NO, the Serve
* MUST publish the Will Message as a non-retained publication [MQTT-3.1.2-14].
* If Will Retain is set to YES, the Server MUST publish the Will Message as a retained publication [MQTT-3.1.2-15].
*/
@property (nonatomic) BOOL willRetainFlag;
/** protocolLevel specifies the protocol to be used */
@property (nonatomic) MQTTProtocolVersion protocolLevel;
/** sessionExpiryInterval specifies the number of seconds after which a session should expire MQTT v5.0*/
@property (strong, nonatomic) NSNumber *sessionExpiryInterval;
/** authMethod specifies the number of seconds after which a session should expire MQTT v5.0*/
@property (strong, nonatomic) NSString *authMethod;
/** authData specifies the number of seconds after which a session should expire MQTT v5.0*/
@property (strong, nonatomic) NSData *authData;
/** requestProblemInformation specifies the number of seconds after which a session should expire MQTT v5.0*/
@property (strong, nonatomic) NSNumber *requestProblemInformation;
/** willDelayInterval specifies the number of seconds after which a session should expire MQTT v5.0*/
@property (strong, nonatomic) NSNumber *willDelayInterval;
/** requestResponseInformation specifies the number of seconds after which a session should expire MQTT v5.0*/
@property (strong, nonatomic) NSNumber *requestResponseInformation;
/** receiveMaximum specifies the number of seconds after which a session should expire MQTT v5.0*/
@property (strong, nonatomic) NSNumber *receiveMaximum;
/** topicAliasMaximum specifies the number of seconds after which a session should expire MQTT v5.0*/
@property (strong, nonatomic) NSNumber *topicAliasMaximum;
/** topicAliasMaximum specifies the number of seconds after which a session should expire MQTT v5.0*/
@property (strong, nonatomic) NSDictionary <NSString *, NSString*> *userProperty;
/** maximumPacketSize specifies the number of seconds after which a session should expire MQTT v5.0*/
@property (strong, nonatomic) NSNumber *maximumPacketSize;
/** queue The queue where the streams are scheduled. */
@property (strong, nonatomic) dispatch_queue_t queue;
/** for mqttio-OBJC backward compatibility
the connect message used is stored here
*/
@property (strong, nonatomic) MQTTMessage *connectMessage;
/** the transport provider for MQTTClient
*
* assign an in instance of a class implementing the MQTTTransport protocol e.g.
* MQTTCFSocketTransport before connecting.
*/
@property (strong, nonatomic) id <MQTTTransport> transport;
/** certificates an NSArray holding client certificates or nil */
@property (strong, nonatomic) NSArray *certificates;
/** Require for VoIP background service
* defaults to NO
*/
@property (nonatomic) BOOL voip;
/** connect to the given host through the given transport with the given
* MQTT session parameters asynchronously
*
*/
- (void)connect;
/** connects to the specified MQTT server
@param connectHandler identifies a block which is executed on successfull or unsuccessfull connect. Might be nil
error is nil in the case of a successful connect
sessionPresent indicates in MQTT 3.1.1 if persistent session data was present at the server
returns nothing and returns immediately. To check the connect results, register as an MQTTSessionDelegate and
- watch for events
- watch for connect or connectionRefused messages
- watch for error messages
or use the connectHandler block
@code
#import "MQTTClient.h"
MQTTSession *session = [[MQTTSession alloc] init];
...
[session connectWithConnectHandler:^(NSError *error, BOOL sessionPresent) {
if (error) {
NSLog(@"Error Connect %@", error.localizedDescription);
} else {
NSLog(@"Connected sessionPresent:%d", sessionPresent);
}
}];
@endcode
*/
- (void)connectWithConnectHandler:(MQTTConnectHandler)connectHandler;
/** disconnect gracefully
*
*/
- (void)disconnect;
/** disconnect V5
* @param returnCode the returncode send to the broker
* @param sessionExpiryInterval the time in seconds before the session can be deleted
* @param reasonString a string explaining the reason
* @param userProperty additional dictionary of user key/value combinations
*/
- (void)disconnectWithReturnCode:(MQTTReturnCode)returnCode
sessionExpiryInterval:(NSNumber *)sessionExpiryInterval
reasonString:(NSString *)reasonString
userProperty:(NSDictionary <NSString *, NSString *> *)userProperty;
/** initialises the MQTT session with default values
@return the initialised MQTTSession object
@code
#import "MQTTClient.h"
MQTTSession *session = [[MQTTSession alloc] init];
@endcode
*/
- (MQTTSession *)init;
/** subscribes to a topic at a specific QoS level
@param topic see subscribeToTopic:atLevel:subscribeHandler: for description
@param qosLevel see subscribeToTopic:atLevel:subscribeHandler: for description
@return the Message Identifier of the SUBSCRIBE message.
@note returns immediately. To check results, register as an MQTTSessionDelegate and watch for events.
@code
#import "MQTTClient.h"
MQTTSession *session = [[MQTTSession alloc] init];
...
[session connect];
...
[session subscribeToTopic:@"example/#" atLevel:2];
@endcode
*/
- (UInt16)subscribeToTopic:(NSString *)topic
atLevel:(MQTTQosLevel)qosLevel;
/** subscribes to a topic at a specific QoS level
@param topic the Topic Filter to subscribe to.
@param qosLevel specifies the QoS Level of the subscription.
qosLevel can be 0, 1, or 2.
@param subscribeHandler identifies a block which is executed on successfull or unsuccessfull subscription.
Might be nil. error is nil in the case of a successful subscription. In this case gQoss represents an
array of grantes Qos
@return the Message Identifier of the SUBSCRIBE message.
@note returns immediately. To check results, register as an MQTTSessionDelegate and watch for events.
@code
#import "MQTTClient.h"
MQTTSession *session = [[MQTTSession alloc] init];
...
[session connect];
...
[session subscribeToTopic:@"example/#" atLevel:2 subscribeHandler:^(NSError *error, NSArray<NSNumber *> *gQoss){
if (error) {
NSLog(@"Subscription failed %@", error.localizedDescription);
} else {
NSLog(@"Subscription sucessfull! Granted Qos: %@", gQoss);
}
}];
@endcode
*/
- (UInt16)subscribeToTopic:(NSString *)topic
atLevel:(MQTTQosLevel)qosLevel
subscribeHandler:(MQTTSubscribeHandler)subscribeHandler;
/** subscribes a number of topics
@param topics an NSDictionary<NSString *, NSNumber *> containing the Topic Filters to subscribe to as keys and
the corresponding QoS as NSNumber values
@return the Message Identifier of the SUBSCRIBE message.
@note returns immediately. To check results, register as an MQTTSessionDelegate and watch for events.
@code
#import "MQTTClient.h"
MQTTSession *session = [[MQTTSession alloc] init];
...
[session connect];
[session subscribeToTopics:@{
@"example/#": @(0),
@"example/status": @(2),
@"other/#": @(1)
}];
@endcode
*/
- (UInt16)subscribeToTopics:(NSDictionary<NSString *, NSNumber *> *)topics;
/** subscribes a number of topics
@param topics an NSDictionary<NSString *, NSNumber *> containing the Topic Filters to subscribe to as keys and
the corresponding QoS as NSNumber values
@param subscribeHandler identifies a block which is executed on successfull or unsuccessfull subscription.
Might be nil. error is nil in the case of a successful subscription. In this case gQoss represents an
array of grantes Qos
@return the Message Identifier of the SUBSCRIBE message.
@note returns immediately. To check results, register as an MQTTSessionDelegate and watch for events.
@code
#import "MQTTClient.h"
MQTTSession *session = [[MQTTSession alloc] init];
...
[session connect];
[session subscribeToTopics:@{
@"example/#": @(0),
@"example/status": @(2),
@"other/#": @(1)
} subscribeHandler:^(NSError *error, NSArray<NSNumber *> *gQoss){
if (error) {
NSLog(@"Subscription failed %@", error.localizedDescription);
} else {
NSLog(@"Subscription sucessfull! Granted Qos: %@", gQoss);
}
}];
@endcode
*/
- (UInt16)subscribeToTopics:(NSDictionary<NSString *, NSNumber *> *)topics
subscribeHandler:(MQTTSubscribeHandler)subscribeHandler;
/** unsubscribes from a topic
@param topic the Topic Filter to unsubscribe from.
@return the Message Identifier of the UNSUBSCRIBE message.
@note returns immediately. To check results, register as an MQTTSessionDelegate and watch for events.
@code
#import "MQTTClient.h"
MQTTSession *session = [[MQTTSession alloc] init];
...
[session connect];
[session unsubscribeTopic:@"example/#"];
@endcode
*/
- (UInt16)unsubscribeTopic:(NSString *)topic;
/** unsubscribes from a topic
@param topic the Topic Filter to unsubscribe from.
@param unsubscribeHandler identifies a block which is executed on successfull or unsuccessfull subscription.
Might be nil. error is nil in the case of a successful subscription. In this case gQoss represents an
array of grantes Qos
@return the Message Identifier of the UNSUBSCRIBE message.
@note returns immediately.
*/
- (UInt16)unsubscribeTopic:(NSString *)topic
unsubscribeHandler:(MQTTUnsubscribeHandler)unsubscribeHandler;
/** unsubscribes from a number of topics
@param topics an NSArray<NSString *> of topics to unsubscribe from
@return the Message Identifier of the UNSUBSCRIBE message.
@note returns immediately. To check results, register as an MQTTSessionDelegate and watch for events.
@code
#import "MQTTClient.h"
MQTTSession *session = [[MQTTSession alloc] init];
...
[session connect];
[session unsubscribeTopics:@[
@"example/#",
@"example/status",
@"other/#"
]];
@endcode
*/
- (UInt16)unsubscribeTopics:(NSArray<NSString *> *)topics;
/** unsubscribes from a number of topics
@param topics an NSArray<NSString *> of topics to unsubscribe from
@param unsubscribeHandler identifies a block which is executed on successfull or unsuccessfull subscription.
Might be nil. error is nil in the case of a successful subscription. In this case gQoss represents an
array of grantes Qos
@return the Message Identifier of the UNSUBSCRIBE message.
@note returns immediately.
*/
- (UInt16)unsubscribeTopics:(NSArray<NSString *> *)topics
unsubscribeHandler:(MQTTUnsubscribeHandler)unsubscribeHandler;
/** publishes data on a given topic at a specified QoS level and retain flag
@param data the data to be sent. length may range from 0 to 268,435,455 - 4 - _lengthof-topic_ bytes. Defaults to length 0.
@param topic the Topic to identify the data
@param retainFlag if YES, data is stored on the MQTT broker until overwritten by the next publish with retainFlag = YES
@param qos specifies the Quality of Service for the publish
qos can be 0, 1, or 2.
@return the Message Identifier of the PUBLISH message. Zero if qos 0. If qos 1 or 2, zero if message was dropped
@note returns immediately. To check results, register as an MQTTSessionDelegate and watch for events.
@code
#import "MQTTClient.h"
MQTTSession *session = [[MQTTSession alloc] init];
...
[session connect];
[session publishData:[@"Sample Data" dataUsingEncoding:NSUTF8StringEncoding]
topic:@"example/data"
retain:YES
qos:1];
@endcode
*/
- (UInt16)publishData:(NSData *)data
onTopic:(NSString *)topic
retain:(BOOL)retainFlag
qos:(MQTTQosLevel)qos;
/** publishes data on a given topic at a specified QoS level and retain flag
@param data the data to be sent. length may range from 0 to 268,435,455 - 4 - _lengthof-topic_ bytes. Defaults to length 0.
@param topic the Topic to identify the data
@param retainFlag if YES, data is stored on the MQTT broker until overwritten by the next publish with retainFlag = YES
@param qos specifies the Quality of Service for the publish
qos can be 0, 1, or 2.
@param publishHandler identifies a block which is executed on successfull or unsuccessfull publsh. Might be nil
error is nil in the case of a successful connect
sessionPresent indicates in MQTT 3.1.1 if persistent session data was present at the server
@return the Message Identifier of the PUBLISH message. Zero if qos 0. If qos 1 or 2, zero if message was dropped
@note returns immediately. To check results, register as an MQTTSessionDelegate and watch for events.
@code
#import "MQTTClient.h"
MQTTSession *session = [[MQTTSession alloc] init];
...
[session connect];
[session publishData:[@"Sample Data" dataUsingEncoding:NSUTF8StringEncoding]
topic:@"example/data"
retain:YES
qos:1
publishHandler:^(NSError *error){
if (error) {
DDLogVerbose(@"error: %@ %@", error.localizedDescription, payload);
} else {
DDLogVerbose(@"delivered:%@", payload);
delivered++;
}
}];
@endcode
*/
- (UInt16)publishData:(NSData *)data
onTopic:(NSString *)topic
retain:(BOOL)retainFlag
qos:(MQTTQosLevel)qos
publishHandler:(MQTTPublishHandler)publishHandler;
/** closes an MQTTSession gracefully
If the connection was successfully established before, a DISCONNECT is sent.
@param disconnectHandler identifies a block which is executed on successfull or unsuccessfull disconnect. Might be nil. error is nil in the case of a successful disconnect
@code
#import "MQTTClient.h"
MQTTSession *session = [[MQTTSession alloc] init];
...
[session connect];
...
[session closeWithDisconnectHandler^(NSError *error) {
if (error) {
NSLog(@"Error Disconnect %@", error.localizedDescription);
}
NSLog(@"Session closed");
}];
@endcode
*/
- (void)closeWithDisconnectHandler:(MQTTDisconnectHandler)disconnectHandler;
/** close V5
* @param returnCode the returncode send to the broker
* @param sessionExpiryInterval the time in seconds before the session can be deleted
* @param reasonString a string explaining the reason
* @param userProperty additional dictionary of user key/value combinations
* @param disconnectHandler will be called when the disconnect finished
*/
- (void)closeWithReturnCode:(MQTTReturnCode)returnCode
sessionExpiryInterval:(NSNumber *)sessionExpiryInterval
reasonString:(NSString *)reasonString
userProperty:(NSDictionary <NSString *, NSString *> *)userProperty
disconnectHandler:(MQTTDisconnectHandler)disconnectHandler;
@end

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,112 @@
//
// MQTTSessionLegacy.h
// MQTTClient.framework
//
/**
Using MQTT in your Objective-C application
This file contains definitions for mqttio-OBJC backward compatibility
@author Christoph Krey c@ckrey.de
@copyright 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
@see http://mqtt.org
*/
#import <Foundation/Foundation.h>
#import "MQTTSession.h"
@interface MQTTSession(Create)
/** initialises the MQTT session
*
* this constructor can specifies SSL securityPolicy. the default value of securityPolicy is nil(which do nothing).
*
* if SSL is enabled, by default it only evaluate server's certificates using CA infrastructure, and for most case, this type of check is enough.
* However, if your app using security model which require pinning SSL certificates to helps prevent man-in-the-middle attacks
* and other vulnerabilities. you may need to set securityPolicy to properly value(see MQTTSSLSecurityPolicy.h for more detail).
*
* NOTE: about self-signed server certificates:
* In CA infrastructure, you may establish a SSL/TLS connection with server which using self-signed certificates
* by install the certificates into OS keychain(either programmatically or manually). however, this method has some disadvantages:
* 1. every socket you app created will trust certificates you added.
* 2. if user choice to remove certificates from keychain, you app need to handling certificates re-adding.
*
* If you only want to verify the cert for the socket you are creating and for no other sockets in your app, you need to use
* MQTTSSLSecurityPolicy.
* And if you use self-signed server certificates, your need to set property: MQTTSSLSecurityPolicy.allowInvalidCertificates=YES
* (see MQTTSSLSecurityPolicy.h for more detail).
*
* @param clientId The Client Identifier identifies the Client to the Server. If nil, a random clientId is generated.
* @param userName an NSString object containing the user's name (or ID) for authentication. May be nil.
* @param password an NSString object containing the user's password. If userName is nil, password must be nil as well.
* @param keepAliveInterval The Keep Alive is a time interval measured in seconds. The MQTTClient ensures that the interval between Control Packets being sent does not exceed the Keep Alive value. In the absence of sending any other Control Packets, the Client sends a PINGREQ Packet.
* @param cleanSessionFlag specifies if the server should discard previous session information.
* @param willFlag If the Will Flag is set to YES this indicates that a Will Message MUST be published by the Server when the Server detects that the Client is disconnected for any reason other than the Client flowing a DISCONNECT Packet.
* @param willTopic If the Will Flag is set to YES, the Will Topic is a string, nil otherwise.
* @param willMsg If the Will Flag is set to YES the Will Message must be specified, nil otherwise.
* @param willQoS specifies the QoS level to be used when publishing the Will Message. If the Will Flag is set to NO, then the Will QoS MUST be set to 0. If the Will Flag is set to YES, the value of Will QoS can be 0 (0x00), 1 (0x01), or 2 (0x02).
* @param willRetainFlag indicates if the server should publish the Will Messages with retainFlag. If the Will Flag is set to NO, then the Will Retain Flag MUST be set to NO . If the Will Flag is set to YES: If Will Retain is set to NO, the Server MUST publish the Will Message as a non-retained publication [MQTT-3.1.2-14]. If Will Retain is set to YES, the Server MUST publish the Will Message as a retained publication [MQTT-3.1.2-15].
* @param protocolLevel specifies the protocol to be used. The value of the Protocol Level field for the version 3.1.1 of the protocol is 4. The value for the version 3.1 is 3.
* @param queue The queue where the streams are scheduled.
* @param securityPolicy The security policy used to evaluate server trust for secure connections.
* @param certificates An identity certificate used to reply to a server requiring client certificates according to the description given for SSLSetCertificate(). You may build the certificates array yourself or use the sundry method clientCertFromP12
* @return the initialised MQTTSession object
*
* @code
#import "MQTTClient.h"
NSString* certificate = [[NSBundle bundleForClass:[MQTTSession class]] pathForResource:@"certificate" ofType:@"cer"];
MQTTSSLSecurityPolicy *securityPolicy = [MQTTSSLSecurityPolicy policyWithPinningMode:MQTTSSLPinningModeCertificate];
securityPolicy.pinnedCertificates = @[ [NSData dataWithContentsOfFile:certificate] ];
securityPolicy.allowInvalidCertificates = YES; // if your certificate is self-signed(which didn't coupled with CA infrastructure)
MQTTSession *session = [[MQTTSession alloc]
initWithClientId:@"example-1234"
userName:@"user"
password:@"secret"
keepAlive:60
cleanSession:YES
will:YES
willTopic:@"example/status"
willMsg:[[@"Client off-line"] dataUsingEncoding:NSUTF8StringEncoding]
willQoS:2
willRetainFlag:YES
protocolLevel:4
queue:dispatch_queue_get_main_queue()
securityPolicy:securityPolicy
certificates:certificates];
[session connectToHost:@"example-1234" port:1883 usingSSL:YES];
@endcode
*/
- (MQTTSession *)initWithClientId:(NSString *)clientId
userName:(NSString *)userName
password:(NSString *)password
keepAlive:(UInt16)keepAliveInterval
connectMessage:(MQTTMessage *)theConnectMessage
cleanSession:(BOOL)cleanSessionFlag
will:(BOOL)willFlag
willTopic:(NSString *)willTopic
willMsg:(NSData *)willMsg
willQoS:(MQTTQosLevel)willQoS
willRetainFlag:(BOOL)willRetainFlag
protocolLevel:(UInt8)protocolLevel
queue:(dispatch_queue_t)queue
securityPolicy:(MQTTSSLSecurityPolicy *) securityPolicy
certificates:(NSArray *)certificates;
/** for mqttio-OBJC backward compatibility
@param payload JSON payload is converted to NSData and then send. See publishData for description
@param theTopic see publishData for description
*/
- (void)publishJson:(id)payload onTopic:(NSString *)theTopic;
@end

View File

@@ -0,0 +1,86 @@
//
// MQTTSessionLegacy.m
// MQTTClient.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
//
/**
Using MQTT in your Objective-C application
This file contains implementation for mqttio-OBJC backward compatibility
@author Christoph Krey c@ckrey.de
@see http://mqtt.org
*/
#import "MQTTSession.h"
#import "MQTTSessionLegacy.h"
#import "MQTTCFSocketTransport.h"
#import "MQTTSSLSecurityPolicyTransport.h"
#import "MQTTLog.h"
@interface MQTTSession()
@property (strong, nonatomic) MQTTSSLSecurityPolicy *securityPolicy;
@end
@implementation MQTTSession(Legacy)
- (MQTTSession *)initWithClientId:(NSString *)clientId
userName:(NSString *)userName
password:(NSString *)password
keepAlive:(UInt16)keepAliveInterval
connectMessage:(MQTTMessage *)theConnectMessage
cleanSession:(BOOL)cleanSessionFlag
will:(BOOL)willFlag
willTopic:(NSString *)willTopic
willMsg:(NSData *)willMsg
willQoS:(MQTTQosLevel)willQoS
willRetainFlag:(BOOL)willRetainFlag
protocolLevel:(UInt8)protocolLevel
queue:(dispatch_queue_t)queue
securityPolicy:(MQTTSSLSecurityPolicy *)securityPolicy
certificates:(NSArray *)certificates {
DDLogVerbose(@"[MQTTSessionLegacy] initWithClientId:%@ ", clientId);
self = [self init];
self.connectMessage = theConnectMessage;
self.clientId = clientId;
self.userName = userName;
self.password = password;
self.keepAliveInterval = keepAliveInterval;
self.cleanSessionFlag = cleanSessionFlag;
self.willFlag = willFlag;
self.willTopic = willTopic;
self.willMsg = willMsg;
self.willQoS = willQoS;
self.willRetainFlag = willRetainFlag;
self.protocolLevel = protocolLevel;
self.queue = queue;
self.securityPolicy = securityPolicy;
self.certificates = certificates;
return self;
}
- (void)publishJson:(id)payload onTopic:(NSString*)theTopic {
NSData *data = [NSJSONSerialization dataWithJSONObject:payload options:0 error:nil];
if (data) {
[self publishData:data onTopic:theTopic retain:FALSE qos:MQTTQosLevelAtLeastOnce];
}
}
@end

View File

@@ -0,0 +1,237 @@
//
// MQTTSessionManager.h
// MQTTClient
//
// Created by Christoph Krey on 09.07.14.
// Copyright © 2013-2017 Christoph Krey. All rights reserved.
//
#import <Foundation/Foundation.h>
#if TARGET_OS_IPHONE == 1
#import <UIKit/UIKit.h>
#endif
#import "MQTTSession.h"
#import "MQTTSessionLegacy.h"
#import "MQTTSSLSecurityPolicy.h"
@class MQTTSessionManager;
/** delegate gives your application access to received messages
*/
@protocol MQTTSessionManagerDelegate <NSObject>
/**
Enumeration of MQTTSessionManagerState values
*/
typedef NS_ENUM(int, MQTTSessionManagerState) {
MQTTSessionManagerStateStarting,
MQTTSessionManagerStateConnecting,
MQTTSessionManagerStateError,
MQTTSessionManagerStateConnected,
MQTTSessionManagerStateClosing,
MQTTSessionManagerStateClosed
};
@optional
/** gets called when a new message was received
@param data the data received, might be zero length
@param topic the topic the data was published to
@param retained indicates if the data retransmitted from server storage
*/
- (void)handleMessage:(NSData *)data onTopic:(NSString *)topic retained:(BOOL)retained;
/** gets called when a new message was received
@param sessionManager the instance of MQTTSessionManager whose state changed
@param data the data received, might be zero length
@param topic the topic the data was published to
@param retained indicates if the data retransmitted from server storage
*/
- (void)sessionManager:(MQTTSessionManager *)sessionManager
didReceiveMessage:(NSData *)data
onTopic:(NSString *)topic
retained:(BOOL)retained;
/** gets called when a published message was actually delivered
@param msgID the Message Identifier of the delivered message
@note this method is called after a publish with qos 1 or 2 only
*/
- (void)messageDelivered:(UInt16)msgID;
/** gets called when a published message was actually delivered
@param sessionManager the instance of MQTTSessionManager whose state changed
@param msgID the Message Identifier of the delivered message
@note this method is called after a publish with qos 1 or 2 only
*/
- (void)sessionManager:(MQTTSessionManager *)sessionManager didDeliverMessage:(UInt16)msgID;
/** gets called when the connection status changes
@param sessionManager the instance of MQTTSessionManager whose state changed
@param newState the new connection state of the sessionManager. This will be identical to `sessionManager.state`.
*/
- (void)sessionManager:(MQTTSessionManager *)sessionManager didChangeState:(MQTTSessionManagerState)newState;
@end
/** SessionManager handles the MQTT session for your application
*/
@interface MQTTSessionManager : NSObject <MQTTSessionDelegate>
/** Underlying MQTTSession currently in use.
*/
@property (strong, nonatomic, readonly) MQTTSession *session;
/** host an NSString containing the hostName or IP address of the Server
*/
@property (readonly) NSString *host;
/** port an unsigned 32 bit integer containing the IP port number of the Server
*/
@property (readonly) UInt32 port;
/** the delegate receiving incoming messages
*/
@property (weak, nonatomic) id<MQTTSessionManagerDelegate> delegate;
/** indicates if manager requires tear down
*/
@property (readonly) BOOL requiresTearDown;
/** subscriptions is a dictionary of NSNumber instances indicating the MQTTQoSLevel.
* The keys are topic filters.
* The SessionManager subscribes to the given subscriptions after successfull (re-)connect
* according to the cleansession parameter and the state of the session as indicated by the broker.
* Setting a new subscriptions dictionary initiates SUBSCRIBE or UNSUBSCRIBE messages by SessionManager
* by comparing the old and new subscriptions.
*/
@property (strong, nonatomic) NSDictionary<NSString *, NSNumber *> *subscriptions;
/** effectiveSubscriptions s a dictionary of NSNumber instances indicating the granted MQTTQoSLevel, or 0x80 for subscription failure.
* The keys are topic filters.
* effectiveSubscriptions is observable and is updated everytime subscriptions change
* @code
...
MQTTSessionManager *manager = [[MQTTSessionManager alloc] init];
manager.delegate = self;
[manager addObserver:self
forKeyPath:@"effectiveSubscriptions"
options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
context:nil];
manager.subscriptions = [@{@"#": @(0)} mutableCopy];
[manager connectTo: ...
...
[manager removeObserver:self forKeyPath:@"effectiveSubscriptions"];
...
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSString *,id> *)change
context:(void *)context {
if ([keyPath isEqualToString:@"effectiveSubscriptions"]) {
MQTTSessionManager *manager = (MQTTSessionManager *)object;
DDLogVerbose(@"effectiveSubscriptions changed: %@", manager.effectiveSubscriptions);
}
}
* @endcode
*/
@property (readonly, strong, nonatomic) NSDictionary<NSString *, NSNumber *> *effectiveSubscriptions;
/** SessionManager status
*/
@property (nonatomic, readonly) MQTTSessionManagerState state;
/** SessionManager last error code when state equals MQTTSessionManagerStateError
*/
@property (nonatomic, readonly) NSError *lastErrorCode;
/** initWithPersistence sets the MQTTPersistence properties other than default
* @param persistent YES or NO (default) to establish file or in memory persistence.
* @param maxWindowSize (a positive number, default is 16) to control the number of messages sent before waiting for acknowledgement in Qos 1 or 2. Additional messages are stored and transmitted later.
* @param maxSize (a positive number of bytes, default is 64 MB) to limit the size of the persistence file. Messages published after the limit is reached are dropped.
* @param maxMessages (a positive number, default is 1024) to limit the number of messages stored. Additional messages published are dropped.
* @param maxRetryInterval The duration at which the connection-retry timer should be capped. When MQTTSessionManager receives a ClosedByBroker or an Error
event, it will attempt to reconnect to the broker. The time in between connection attempts is doubled each time, until it remains at maxRetryInterval.
Defaults to 64 seconds.
* @param connectInForeground Whether or not to connect the MQTTSession when the app enters the foreground, and disconnect when it becomes inactive. When NO, the caller is responsible for calling -connectTo: and -disconnect. Defaults to YES.
* @param streamSSLLevel an NSString containing the security level for read and write streams
* For list of possible values see:
* https://developer.apple.com/documentation/corefoundation/cfstream/cfstream_socket_security_level_constants
* Please also note that kCFStreamSocketSecurityLevelTLSv1_2 is not in a list
* and cannot be used as constant, but you can use it as a string value
* defaults to kCFStreamSocketSecurityLevelNegotiatedSSL
* @param queue Queue for MQTTSession.
* @return the initialized MQTTSessionManager object
*/
- (MQTTSessionManager *)initWithPersistence:(BOOL)persistent
maxWindowSize:(NSUInteger)maxWindowSize
maxMessages:(NSUInteger)maxMessages
maxSize:(NSUInteger)maxSize
maxConnectionRetryInterval:(NSTimeInterval)maxRetryInterval
connectInForeground:(BOOL)connectInForeground
streamSSLLevel:(NSString *)streamSSLLevel
queue:(dispatch_queue_t)queue NS_DESIGNATED_INITIALIZER;
/** Connects to the MQTT broker and stores the parameters for subsequent reconnects
* @param host specifies the hostname or ip address to connect to. Defaults to @"localhost".
* @param port specifies the port to connect to
* @param tls specifies whether to use SSL or not
* @param keepalive The Keep Alive is a time interval measured in seconds. The MQTTClient ensures that the interval between Control Packets being sent does not exceed the Keep Alive value. In the absence of sending any other Control Packets, the Client sends a PINGREQ Packet.
* @param clean specifies if the server should discard previous session information.
* @param auth specifies the user and pass parameters should be used for authenthication
* @param user an NSString object containing the user's name (or ID) for authentication. May be nil.
* @param pass an NSString object containing the user's password. If userName is nil, password must be nil as well.
* @param will indicates whether a will shall be sent
* @param willTopic the Will Topic is a string, may be nil
* @param willMsg the Will Message, might be zero length or nil
* @param willQos specifies the QoS level to be used when publishing the Will Message.
* @param willRetainFlag indicates if the server should publish the Will Messages with retainFlag.
* @param clientId The Client Identifier identifies the Client to the Server. If nil, a random clientId is generated.
* @param securityPolicy A custom SSL security policy or nil.
* @param certificates An NSArray of the pinned certificates to use or nil.
* @param protocolLevel Protocol version of the connection.
* @param connectHandler Called when first connected or if error occurred. It is not called on subsequent internal reconnects.
*/
- (void)connectTo:(NSString *)host
port:(NSInteger)port
tls:(BOOL)tls
keepalive:(NSInteger)keepalive
clean:(BOOL)clean
auth:(BOOL)auth
user:(NSString *)user
pass:(NSString *)pass
will:(BOOL)will
willTopic:(NSString *)willTopic
willMsg:(NSData *)willMsg
willQos:(MQTTQosLevel)willQos
willRetainFlag:(BOOL)willRetainFlag
withClientId:(NSString *)clientId
securityPolicy:(MQTTSSLSecurityPolicy *)securityPolicy
certificates:(NSArray *)certificates
protocolLevel:(MQTTProtocolVersion)protocolLevel
connectHandler:(MQTTConnectHandler)connectHandler;
/** Re-Connects to the MQTT broker using the parameters for given in the connectTo method
*/
- (void)connectToLast:(MQTTConnectHandler)connectHandler;
/** publishes data on a given topic at a specified QoS level and retain flag
@param data the data to be sent. length may range from 0 to 268,435,455 - 4 - _lengthof-topic_ bytes. Defaults to length 0.
@param topic the Topic to identify the data
@param retainFlag if YES, data is stored on the MQTT broker until overwritten by the next publish with retainFlag = YES
@param qos specifies the Quality of Service for the publish
qos can be 0, 1, or 2.
@return the Message Identifier of the PUBLISH message. Zero if qos 0. If qos 1 or 2, zero if message was dropped
@note returns immediately.
*/
- (UInt16)sendData:(NSData *)data topic:(NSString *)topic qos:(MQTTQosLevel)qos retain:(BOOL)retainFlag;
/** Disconnects gracefully from the MQTT broker
*/
- (void)disconnectWithDisconnectHandler:(MQTTDisconnectHandler)disconnectHandler;
@end

View File

@@ -0,0 +1,420 @@
//
// MQTTSessionManager.m
// MQTTClient
//
// Created by Christoph Krey on 09.07.14.
// Copyright © 2013-2017 Christoph Krey. All rights reserved.
//
#import "MQTTSessionManager.h"
#import "MQTTCoreDataPersistence.h"
#import "MQTTLog.h"
#import "ReconnectTimer.h"
#import "ForegroundReconnection.h"
#import "MQTTSSLSecurityPolicyTransport.h"
@interface MQTTSessionManager()
@property (nonatomic, readwrite) MQTTSessionManagerState state;
@property (nonatomic, readwrite) NSError *lastErrorCode;
@property (strong, nonatomic) ReconnectTimer *reconnectTimer;
@property (nonatomic) BOOL reconnectFlag;
@property (strong, nonatomic) MQTTSession *session;
@property (strong, nonatomic) NSString *host;
@property (nonatomic) UInt32 port;
@property (nonatomic) BOOL tls;
@property (nonatomic) NSInteger keepalive;
@property (nonatomic) BOOL clean;
@property (nonatomic) BOOL auth;
@property (nonatomic) BOOL will;
@property (strong, nonatomic) NSString *user;
@property (strong, nonatomic) NSString *pass;
@property (strong, nonatomic) NSString *willTopic;
@property (strong, nonatomic) NSData *willMsg;
@property (nonatomic) NSInteger willQos;
@property (nonatomic) BOOL willRetainFlag;
@property (strong, nonatomic) NSString *clientId;
@property (strong, nonatomic) dispatch_queue_t queue;
@property (strong, nonatomic) MQTTSSLSecurityPolicy *securityPolicy;
@property (strong, nonatomic) NSArray *certificates;
@property (nonatomic) MQTTProtocolVersion protocolLevel;
#if TARGET_OS_IPHONE == 1
@property (strong, nonatomic) ForegroundReconnection *foregroundReconnection;
#endif
@property (nonatomic) BOOL persistent;
@property (nonatomic) NSUInteger maxWindowSize;
@property (nonatomic) NSUInteger maxSize;
@property (nonatomic) NSUInteger maxMessages;
@property (strong, nonatomic) NSString *streamSSLLevel;
@property (strong, nonatomic) NSDictionary<NSString *, NSNumber *> *internalSubscriptions;
@property (strong, nonatomic) NSDictionary<NSString *, NSNumber *> *effectiveSubscriptions;
@property (strong, nonatomic) NSLock *subscriptionLock;
@end
#define RECONNECT_TIMER 1.0
#define RECONNECT_TIMER_MAX_DEFAULT 64.0
@implementation MQTTSessionManager
- (instancetype)init {
self = [self initWithPersistence:MQTT_PERSISTENT
maxWindowSize:MQTT_MAX_WINDOW_SIZE
maxMessages:MQTT_MAX_MESSAGES
maxSize:MQTT_MAX_SIZE
maxConnectionRetryInterval:RECONNECT_TIMER_MAX_DEFAULT
connectInForeground:YES
streamSSLLevel:(NSString *)kCFStreamSocketSecurityLevelNegotiatedSSL
queue:dispatch_get_main_queue()];
return self;
}
- (MQTTSessionManager *)initWithPersistence:(BOOL)persistent
maxWindowSize:(NSUInteger)maxWindowSize
maxMessages:(NSUInteger)maxMessages
maxSize:(NSUInteger)maxSize
maxConnectionRetryInterval:(NSTimeInterval)maxRetryInterval
connectInForeground:(BOOL)connectInForeground
streamSSLLevel:(NSString *)streamSSLLevel
queue:(dispatch_queue_t)queue {
self = [super init];
self.streamSSLLevel = streamSSLLevel;
self.queue = queue;
[self updateState:MQTTSessionManagerStateStarting];
self.internalSubscriptions = [[NSMutableDictionary alloc] init];
self.effectiveSubscriptions = [[NSMutableDictionary alloc] init];
self.persistent = persistent;
self.maxWindowSize = maxWindowSize;
self.maxSize = maxSize;
self.maxMessages = maxMessages;
__weak MQTTSessionManager *weakSelf = self;
self.reconnectTimer = [[ReconnectTimer alloc] initWithRetryInterval:RECONNECT_TIMER
maxRetryInterval:maxRetryInterval
queue:self.queue
reconnectBlock:^{
[weakSelf reconnect:nil];
}];
#if TARGET_OS_IPHONE == 1
if (connectInForeground) {
self.foregroundReconnection = [[ForegroundReconnection alloc] initWithMQTTSessionManager:self];
}
#endif
self.subscriptionLock = [[NSLock alloc] init];
return self;
}
- (void)connectTo:(NSString *)host
port:(NSInteger)port
tls:(BOOL)tls
keepalive:(NSInteger)keepalive
clean:(BOOL)clean
auth:(BOOL)auth
user:(NSString *)user
pass:(NSString *)pass
will:(BOOL)will
willTopic:(NSString *)willTopic
willMsg:(NSData *)willMsg
willQos:(MQTTQosLevel)willQos
willRetainFlag:(BOOL)willRetainFlag
withClientId:(NSString *)clientId
securityPolicy:(MQTTSSLSecurityPolicy *)securityPolicy
certificates:(NSArray *)certificates
protocolLevel:(MQTTProtocolVersion)protocolLevel
connectHandler:(MQTTConnectHandler)connectHandler {
DDLogVerbose(@"MQTTSessionManager connectTo:%@", host);
BOOL shouldReconnect = self.session != nil;
if (!self.session ||
![host isEqualToString:self.host] ||
port != self.port ||
tls != self.tls ||
keepalive != self.keepalive ||
clean != self.clean ||
auth != self.auth ||
![user isEqualToString:self.user] ||
![pass isEqualToString:self.pass] ||
![willTopic isEqualToString:self.willTopic] ||
![willMsg isEqualToData:self.willMsg] ||
willQos != self.willQos ||
willRetainFlag != self.willRetainFlag ||
![clientId isEqualToString:self.clientId] ||
securityPolicy != self.securityPolicy ||
certificates != self.certificates) {
self.host = host;
self.port = (int)port;
self.tls = tls;
self.keepalive = keepalive;
self.clean = clean;
self.auth = auth;
self.user = user;
self.pass = pass;
self.will = will;
self.willTopic = willTopic;
self.willMsg = willMsg;
self.willQos = willQos;
self.willRetainFlag = willRetainFlag;
self.clientId = clientId;
self.securityPolicy = securityPolicy;
self.certificates = certificates;
self.protocolLevel = protocolLevel;
self.session = [[MQTTSession alloc] initWithClientId:clientId
userName:auth ? user : nil
password:auth ? pass : nil
keepAlive:keepalive
connectMessage:nil
cleanSession:clean
will:will
willTopic:willTopic
willMsg:willMsg
willQoS:willQos
willRetainFlag:willRetainFlag
protocolLevel:protocolLevel
queue:self.queue
securityPolicy:securityPolicy
certificates:certificates];
self.session.streamSSLLevel = self.streamSSLLevel;
MQTTCoreDataPersistence *persistence = [[MQTTCoreDataPersistence alloc] init];
persistence.persistent = self.persistent;
persistence.maxWindowSize = self.maxWindowSize;
persistence.maxSize = self.maxSize;
persistence.maxMessages = self.maxMessages;
self.session.persistence = persistence;
self.session.delegate = self;
self.reconnectFlag = FALSE;
}
if (shouldReconnect) {
DDLogVerbose(@"[MQTTSessionManager] reconnecting");
[self disconnectWithDisconnectHandler:nil];
[self reconnect:connectHandler];
} else {
DDLogVerbose(@"[MQTTSessionManager] connecting");
[self connectToInternal:connectHandler];
}
}
- (UInt16)sendData:(NSData *)data topic:(NSString *)topic qos:(MQTTQosLevel)qos retain:(BOOL)retainFlag {
if (self.state != MQTTSessionManagerStateConnected) {
[self connectToLast:nil];
}
UInt16 msgId = [self.session publishData:data
onTopic:topic
retain:retainFlag
qos:qos];
return msgId;
}
- (void)disconnectWithDisconnectHandler:(MQTTDisconnectHandler)disconnectHandler {
[self updateState:MQTTSessionManagerStateClosing];
[self.session closeWithDisconnectHandler:disconnectHandler];
[self.reconnectTimer stop];
}
- (BOOL)requiresTearDown {
return (self.state != MQTTSessionManagerStateClosed &&
self.state != MQTTSessionManagerStateStarting);
}
- (void)updateState:(MQTTSessionManagerState)newState {
self.state = newState;
if ([self.delegate respondsToSelector:@selector(sessionManager:didChangeState:)]) {
[self.delegate sessionManager:self didChangeState:newState];
}
}
#pragma mark - MQTT Callback methods
- (void)handleEvent:(MQTTSession *)session event:(MQTTSessionEvent)eventCode error:(NSError *)error {
#ifdef DEBUG
__unused const NSDictionary *events = @{
@(MQTTSessionEventConnected): @"connected",
@(MQTTSessionEventConnectionRefused): @"connection refused",
@(MQTTSessionEventConnectionClosed): @"connection closed",
@(MQTTSessionEventConnectionError): @"connection error",
@(MQTTSessionEventProtocolError): @"protocoll error",
@(MQTTSessionEventConnectionClosedByBroker): @"connection closed by broker"
};
DDLogVerbose(@"[MQTTSessionManager] eventCode: %@ (%ld) %@", events[@(eventCode)], (long)eventCode, error);
#endif
switch (eventCode) {
case MQTTSessionEventConnected:
self.lastErrorCode = nil;
[self updateState:MQTTSessionManagerStateConnected];
[self.reconnectTimer resetRetryInterval];
break;
case MQTTSessionEventConnectionClosed:
[self updateState:MQTTSessionManagerStateClosed];
break;
case MQTTSessionEventConnectionClosedByBroker:
if (self.state != MQTTSessionManagerStateClosing) {
[self triggerDelayedReconnect];
}
[self updateState:MQTTSessionManagerStateClosed];
break;
case MQTTSessionEventProtocolError:
case MQTTSessionEventConnectionRefused:
case MQTTSessionEventConnectionError:
[self triggerDelayedReconnect];
self.lastErrorCode = error;
[self updateState:MQTTSessionManagerStateError];
break;
default:
break;
}
}
- (void)newMessage:(MQTTSession *)session data:(NSData *)data onTopic:(NSString *)topic qos:(MQTTQosLevel)qos retained:(BOOL)retained mid:(unsigned int)mid {
if (self.delegate) {
if ([self.delegate respondsToSelector:@selector(sessionManager:didReceiveMessage:onTopic:retained:)]) {
[self.delegate sessionManager:self didReceiveMessage:data onTopic:topic retained:retained];
}
if ([self.delegate respondsToSelector:@selector(handleMessage:onTopic:retained:)]) {
[self.delegate handleMessage:data onTopic:topic retained:retained];
}
}
}
- (void)connected:(MQTTSession *)session sessionPresent:(BOOL)sessionPresent {
if (self.clean || !self.reconnectFlag || !sessionPresent) {
NSDictionary *subscriptions = [self.internalSubscriptions copy];
[self.subscriptionLock lock];
self.effectiveSubscriptions = [[NSMutableDictionary alloc] init];
[self.subscriptionLock unlock];
if (subscriptions.count) {
__weak MQTTSessionManager *weakSelf = self;
[self.session subscribeToTopics:subscriptions subscribeHandler:^(NSError *error, NSArray<NSNumber *> *gQoss) {
MQTTSessionManager *strongSelf = weakSelf;
if (!error) {
NSArray<NSString *> *allTopics = subscriptions.allKeys;
for (int i = 0; i < allTopics.count; i++) {
NSString *topic = allTopics[i];
NSNumber *gQos = gQoss[i];
[strongSelf.subscriptionLock lock];
NSMutableDictionary *newEffectiveSubscriptions = [strongSelf.subscriptions mutableCopy];
newEffectiveSubscriptions[topic] = gQos;
strongSelf.effectiveSubscriptions = newEffectiveSubscriptions;
[strongSelf.subscriptionLock unlock];
}
}
}];
}
self.reconnectFlag = TRUE;
}
}
- (void)messageDelivered:(MQTTSession *)session msgID:(UInt16)msgID {
if (self.delegate) {
if ([self.delegate respondsToSelector:@selector(sessionManager:didDeliverMessage:)]) {
[self.delegate sessionManager:self didDeliverMessage:msgID];
}
if ([self.delegate respondsToSelector:@selector(messageDelivered:)]) {
[self.delegate messageDelivered:msgID];
}
}
}
- (void)connectToInternal:(MQTTConnectHandler)connectHandler {
if (self.session && self.state == MQTTSessionManagerStateStarting) {
[self updateState:MQTTSessionManagerStateConnecting];
MQTTCFSocketTransport *transport;
if (self.securityPolicy) {
transport = [[MQTTSSLSecurityPolicyTransport alloc] init];
((MQTTSSLSecurityPolicyTransport *)transport).securityPolicy = self.securityPolicy;
} else {
transport = [[MQTTCFSocketTransport alloc] init];
}
transport.host = self.host;
transport.port = self.port;
transport.tls = self.tls;
transport.certificates = self.certificates;
transport.voip = self.session.voip;
transport.queue = self.queue;
transport.streamSSLLevel = self.streamSSLLevel;
self.session.transport = transport;
[self.session connectWithConnectHandler:connectHandler];
}
}
- (void)reconnect:(MQTTConnectHandler)connectHandler {
[self updateState:MQTTSessionManagerStateStarting];
[self connectToInternal:connectHandler];
}
- (void)connectToLast:(MQTTConnectHandler)connectHandler {
if (self.state == MQTTSessionManagerStateConnected) {
return;
}
[self.reconnectTimer resetRetryInterval];
[self reconnect:connectHandler];
}
- (void)triggerDelayedReconnect {
[self.reconnectTimer schedule];
}
- (NSDictionary<NSString *, NSNumber *> *)subscriptions {
return self.internalSubscriptions;
}
- (void)setSubscriptions:(NSDictionary<NSString *, NSNumber *> *)newSubscriptions {
if (self.state == MQTTSessionManagerStateConnected) {
NSDictionary *currentSubscriptions = [self.effectiveSubscriptions copy];
for (NSString *topicFilter in currentSubscriptions) {
if (!newSubscriptions[topicFilter]) {
__weak MQTTSessionManager *weakSelf = self;
[self.session unsubscribeTopic:topicFilter unsubscribeHandler:^(NSError *error) {
MQTTSessionManager *strongSelf = weakSelf;
if (!error) {
[strongSelf.subscriptionLock lock];
NSMutableDictionary *newEffectiveSubscriptions = [strongSelf.subscriptions mutableCopy];
[newEffectiveSubscriptions removeObjectForKey:topicFilter];
strongSelf.effectiveSubscriptions = newEffectiveSubscriptions;
[strongSelf.subscriptionLock unlock];
}
}];
}
}
for (NSString *topicFilter in newSubscriptions) {
if (!currentSubscriptions[topicFilter]) {
NSNumber *number = newSubscriptions[topicFilter];
MQTTQosLevel qos = number.unsignedIntValue;
__weak MQTTSessionManager *weakSelf = self;
[self.session subscribeToTopic:topicFilter atLevel:qos subscribeHandler:^(NSError *error, NSArray<NSNumber *> *gQoss) {
MQTTSessionManager *strongSelf = weakSelf;
if (!error) {
NSNumber *gQos = gQoss[0];
[strongSelf.subscriptionLock lock];
NSMutableDictionary *newEffectiveSubscriptions = [strongSelf.subscriptions mutableCopy];
newEffectiveSubscriptions[topicFilter] = gQos;
strongSelf.effectiveSubscriptions = newEffectiveSubscriptions;
[strongSelf.subscriptionLock unlock];
}
}];
}
}
}
self.internalSubscriptions = newSubscriptions;
DDLogVerbose(@"MQTTSessionManager internalSubscriptions: %@", self.internalSubscriptions);
}
@end

View File

@@ -0,0 +1,194 @@
//
// MQTTSessionSynchron.h
// MQTTClient.framework
//
/**
Synchronous API
@author Christoph Krey c@ckrey.de
@copyright Copyright © 2013-2017, Christoph Krey. All rights reserved.
*/
#import <Foundation/Foundation.h>
#import "MQTTSession.h"
@interface MQTTSession(Synchron)
/** connects to the specified MQTT server synchronously
@param timeout defines the maximum time to wait. Defaults to 0 for no timeout.
@return true if the connection was established
@code
#import "MQTTClient.h"
MQTTSession *session = [[MQTTSession alloc] init];
[session connectAndWaitTimeout:30];
@endcode
*/
- (BOOL)connectAndWaitTimeout:(NSTimeInterval)timeout;
/** subscribes to a topic at a specific QoS level synchronously
@param topic the Topic Filter to subscribe to.
@param qosLevel specifies the QoS Level of the subscription.
qosLevel can be 0, 1, or 2.
@param timeout defines the maximum time to wait
@return TRUE if successfully subscribed
@code
#import "MQTTClient.h"
MQTTSession *session = [[MQTTSession alloc] init];
[session connectAndWaitTimeout:30];
[session subscribeAndWaitToTopic:@"example/#" atLevel:2 timeout:10];
@endcode
*/
- (BOOL)subscribeAndWaitToTopic:(NSString *)topic
atLevel:(MQTTQosLevel)qosLevel
timeout:(NSTimeInterval)timeout;
/** subscribes a number of topics
@param topics an NSDictionary<NSString *, NSNumber *> containing the Topic Filters to subscribe to as keys and
the corresponding QoS as NSNumber values
@param timeout defines the maximum time to wait
@return TRUE if the subscribe was succesfull
@code
#import "MQTTClient.h"
MQTTSession *session = [[MQTTSession alloc] init];
[session connectAndWaitTimeout:30];
[session subscribeAndWaitToTopics:@{
@"example/#": @(0),
@"example/status": @(2),
@"other/#": @(1)
}
timeout:10];
@endcode
*/
- (BOOL)subscribeAndWaitToTopics:(NSDictionary<NSString *, NSNumber *> *)topics
timeout:(NSTimeInterval)timeout;
/** unsubscribes from a topic synchronously
@param topic the Topic Filter to unsubscribe from.
@param timeout defines the maximum time to wait
@return TRUE if sucessfully unsubscribed
@code
#import "MQTTClient.h"
MQTTSession *session = [[MQTTSession alloc] init];
[session connectAndWaitTimeout:30];
...
[session unsubscribeAndWaitTopic:@"example/#" timeout:10];
@endcode
*/
- (BOOL)unsubscribeAndWaitTopic:(NSString *)topic
timeout:(NSTimeInterval)timeout;
/** unsubscribes from a number of topics synchronously
@param topics an NSArray<NSString *> of topics to unsubscribe from
@param timeout defines the maximum time to wait
@return TRUE if the unsubscribe was successful
@code
#import "MQTTClient.h"
MQTTSession *session = [[MQTTSession alloc] init];
[session connectAndWaitTimeout:30];
...
[session unsubscribeAndWaitTopics:@[
@"example/#",
@"example/status",
@"other/#"
]
timeout:10];
@endcode
*/
- (BOOL)unsubscribeAndWaitTopics:(NSArray<NSString *> *)topics
timeout:(NSTimeInterval)timeout;
/** publishes synchronously data on a given topic at a specified QoS level and retain flag
@param data the data to be sent. length may range from 0 to 268,435,455 - 4 - _lengthof-topic_ bytes. Defaults to length 0.
@param topic the Topic to identify the data
@param retainFlag if YES, data is stored on the MQTT broker until overwritten by the next publish with retainFlag = YES
@param qos specifies the Quality of Service for the publish
qos can be 0, 1, or 2.
@param timeout defines the maximum time to wait
@returns TRUE if the publish was successful
@code
#import "MQTTClient.h"
MQTTSession *session = [[MQTTSession alloc] init];
[session connectAndWaitTimeout:30];
[session publishAndWaitData:[@"Sample Data" dataUsingEncoding:NSUTF8StringEncoding]
topic:@"example/data"
retain:YES
qos:1
timeout:10];
@endcode
*/
- (BOOL)publishAndWaitData:(NSData *)data
onTopic:(NSString *)topic
retain:(BOOL)retainFlag
qos:(MQTTQosLevel)qos
timeout:(NSTimeInterval)timeout;
/** closes an MQTTSession gracefully synchronously
@param timeout defines the maximum time to wait
If the connection was successfully established before, a DISCONNECT is sent.
@code
#import "MQTTClient.h"
MQTTSession *session = [[MQTTSession alloc] init];
[session connectAndWaitTimeout:30];
...
[session closeAndWait:10];
@endcode
*/
- (void)closeAndWait:(NSTimeInterval)timeout;
@end

View File

@@ -0,0 +1,192 @@
//
// MQTTSessionSynchron.m
// MQTTClient.framework
//
// Copyright © 2013-2017, Christoph Krey. All rights reserved.
//
/**
Synchronous API
@author Christoph Krey c@ckrey.de
@see http://mqtt.org
*/
#import "MQTTSession.h"
#import "MQTTSessionLegacy.h"
#import "MQTTSessionSynchron.h"
#import "MQTTLog.h"
@interface MQTTSession()
@property (nonatomic) BOOL synchronPub;
@property (nonatomic) UInt16 synchronPubMid;
@property (nonatomic) BOOL synchronUnsub;
@property (nonatomic) UInt16 synchronUnsubMid;
@property (nonatomic) BOOL synchronSub;
@property (nonatomic) UInt16 synchronSubMid;
@property (nonatomic) BOOL synchronConnect;
@property (nonatomic) BOOL synchronDisconnect;
@end
@implementation MQTTSession(Synchron)
/** Synchron connect
*
*/
- (BOOL)connectAndWaitTimeout:(NSTimeInterval)timeout {
NSDate *started = [NSDate date];
self.synchronConnect = TRUE;
[self connect];
[[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];
while (self.synchronConnect && (timeout == 0 || started.timeIntervalSince1970 + timeout > [NSDate date].timeIntervalSince1970)) {
DDLogVerbose(@"[MQTTSessionSynchron] waiting for connect");
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:.1]];
}
DDLogVerbose(@"[MQTTSessionSynchron] end connect");
return (self.status == MQTTSessionStatusConnected);
}
- (BOOL)subscribeAndWaitToTopic:(NSString *)topic atLevel:(MQTTQosLevel)qosLevel timeout:(NSTimeInterval)timeout {
NSDate *started = [NSDate date];
self.synchronSub = TRUE;
UInt16 mid = [self subscribeToTopic:topic atLevel:qosLevel];
self.synchronSubMid = mid;
[[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];
while (self.synchronSub && (timeout == 0 || started.timeIntervalSince1970 + timeout > [NSDate date].timeIntervalSince1970)) {
DDLogVerbose(@"[MQTTSessionSynchron] waiting for suback %d", mid);
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:.1]];
}
DDLogVerbose(@"[MQTTSessionSynchron] end subscribe");
if (self.synchronSub || self.synchronSubMid != mid) {
return FALSE;
} else {
return TRUE;
}
}
- (BOOL)subscribeAndWaitToTopics:(NSDictionary<NSString *, NSNumber *> *)topics timeout:(NSTimeInterval)timeout {
NSDate *started = [NSDate date];
self.synchronSub = TRUE;
UInt16 mid = [self subscribeToTopics:topics];
self.synchronSubMid = mid;
[[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];
while (self.synchronSub && (timeout == 0 || started.timeIntervalSince1970 + timeout > [NSDate date].timeIntervalSince1970)) {
DDLogVerbose(@"[MQTTSessionSynchron] waiting for suback %d", mid);
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:.1]];
}
DDLogVerbose(@"[MQTTSessionSynchron] end subscribe");
if (self.synchronSub || self.synchronSubMid != mid) {
return FALSE;
} else {
return TRUE;
}
}
- (BOOL)unsubscribeAndWaitTopic:(NSString *)theTopic timeout:(NSTimeInterval)timeout {
NSDate *started = [NSDate date];
self.synchronUnsub = TRUE;
UInt16 mid = [self unsubscribeTopic:theTopic];
self.synchronUnsubMid = mid;
[[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];
while (self.synchronUnsub && (timeout == 0 || started.timeIntervalSince1970 + timeout > [NSDate date].timeIntervalSince1970)) {
DDLogVerbose(@"[MQTTSessionSynchron] waiting for unsuback %d", mid);
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:.1]];
}
DDLogVerbose(@"[MQTTSessionSynchron] end unsubscribe");
if (self.synchronUnsub || self.synchronUnsubMid != mid) {
return FALSE;
} else {
return TRUE;
}
}
- (BOOL)unsubscribeAndWaitTopics:(NSArray<NSString *> *)topics timeout:(NSTimeInterval)timeout {
NSDate *started = [NSDate date];
self.synchronUnsub = TRUE;
UInt16 mid = [self unsubscribeTopics:topics];
self.synchronUnsubMid = mid;
[[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];
while (self.synchronUnsub && (timeout == 0 || started.timeIntervalSince1970 + timeout > [NSDate date].timeIntervalSince1970)) {
DDLogVerbose(@"[MQTTSessionSynchron] waiting for unsuback %d", mid);
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:.1]];
}
DDLogVerbose(@"[MQTTSessionSynchron] end unsubscribe");
if (self.synchronUnsub || self.synchronUnsubMid != mid) {
return FALSE;
} else {
return TRUE;
}
}
- (BOOL)publishAndWaitData:(NSData*)data
onTopic:(NSString*)topic
retain:(BOOL)retainFlag
qos:(MQTTQosLevel)qos
timeout:(NSTimeInterval)timeout {
NSDate *started = [NSDate date];
if (qos != MQTTQosLevelAtMostOnce) {
self.synchronPub = TRUE;
}
UInt16 mid = self.synchronPubMid = [self publishData:data onTopic:topic retain:retainFlag qos:qos];
if (qos == MQTTQosLevelAtMostOnce) {
return TRUE;
} else {
[[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];
while (self.synchronPub && (timeout == 0 || started.timeIntervalSince1970 + timeout > [NSDate date].timeIntervalSince1970)) {
DDLogVerbose(@"[MQTTSessionSynchron] waiting for mid %d", mid);
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:.1]];
}
DDLogVerbose(@"[MQTTSessionSynchron] end publish");
if (self.synchronPub || self.synchronPubMid != mid) {
return FALSE;
} else {
return TRUE;
}
}
}
- (void)closeAndWait:(NSTimeInterval)timeout {
NSDate *started = [NSDate date];
self.synchronDisconnect = TRUE;
[self closeWithDisconnectHandler:nil];
[[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];
while (self.synchronDisconnect && (timeout == 0 || started.timeIntervalSince1970 + timeout > [NSDate date].timeIntervalSince1970)) {
DDLogVerbose(@"[MQTTSessionSynchron] waiting for close");
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:.1]];
}
DDLogVerbose(@"[MQTTSessionSynchron] end close");
}
@end

View File

@@ -0,0 +1,33 @@
//
// MQTTStrict.h
// MQTTClient
//
// Created by Christoph Krey on 24.07.17.
// Copyright © 2017 Christoph Krey. All rights reserved.
//
#import <Foundation/Foundation.h>
/** MQTTStrict controls the behaviour of MQTTClient with regards to parameter checking
* If strict is true, all parameters passed by the caller are checked before
* the corresponding message is send (CONNECT, PUBLISH, SUBSCRIBE, UNSUBSCRIBE)
* and an exception is thrown if any invalid values or inconsistencies are detected
*
* If strict is false, parameters are used as passed by the caller.
* Messages will be sent "incorrectly" and
* parameter checking will be done on the broker end.
*
*/
@interface MQTTStrict : NSObject
/** strict returns the current strict flag
* @return the strict flag
*/
+ (BOOL)strict;
/** setString sets the global strict flag
* @param strict the new strict flag
*/
+ (void)setStrict:(BOOL)strict;
@end

View File

@@ -0,0 +1,22 @@
//
// MQTTStrict.m
// MQTTClient
//
// Created by Christoph Krey on 24.07.17.
// Copyright © 2017 Christoph Krey. All rights reserved.
//
#import "MQTTStrict.h"
@implementation MQTTStrict
static BOOL internalStrict = false;
+ (BOOL)strict {
return internalStrict;
}
+ (void)setStrict:(BOOL)strict {
internalStrict = strict;
}
@end

View File

@@ -0,0 +1,114 @@
//
// MQTTTransport.h
// MQTTClient
//
// Created by Christoph Krey on 06.12.15.
// Copyright © 2015-2017 Christoph Krey. All rights reserved.
//
#import <Foundation/Foundation.h>
@protocol MQTTTransportDelegate;
/** MQTTTransport is a protocol abstracting the underlying transport level for MQTTClient
*
*/
@protocol MQTTTransport <NSObject>
/** MQTTTransport state defines the possible state of an abstract transport
*
*/
typedef NS_ENUM(NSInteger, MQTTTransportState) {
/** MQTTTransportCreated indicates an initialized transport */
MQTTTransportCreated = 0,
/** MQTTTransportOpening indicates a transport in the process of opening a connection */
MQTTTransportOpening,
/** MQTTTransportCreated indicates a transport opened ready for communication */
MQTTTransportOpen,
/** MQTTTransportCreated indicates a transport in the process of closing */
MQTTTransportClosing,
/** MQTTTransportCreated indicates a closed transport */
MQTTTransportClosed
};
/** queue The queue where the streams are scheduled. */
@property (strong, nonatomic, nonnull) dispatch_queue_t queue;
/** streamSSLLevel an NSString containing the security level for read and write streams
* For list of possible values see:
* https://developer.apple.com/documentation/corefoundation/cfstream/cfstream_socket_security_level_constants
* Please also note that kCFStreamSocketSecurityLevelTLSv1_2 is not in a list
* and cannot be used as constant, but you can use it as a string value
* defaults to kCFStreamSocketSecurityLevelNegotiatedSSL
*/
@property (strong, nonatomic, nonnull) NSString *streamSSLLevel;
/** host an NSString containing the hostName or IP address of the host to connect to */
@property (strong, nonatomic, nonnull) NSString *host;
/** port an unsigned 32 bit integer containing the IP port number to connect to */
@property (nonatomic) UInt32 port;
/** MQTTTransportDelegate needs to be set to a class implementing th MQTTTransportDelegate protocol
* to receive delegate messages.
*/
@property (weak, nonatomic) _Nullable id<MQTTTransportDelegate> delegate;
/** state contains the current MQTTTransportState of the transport */
@property (nonatomic) MQTTTransportState state;
/** open opens the transport and prepares it for communication
* actual transports may require additional parameters to be set before opening
*/
- (void)open;
/** send transmits a data message
* @param data data to be send, might be zero length
* @result a boolean indicating if the data could be send or not
*/
- (BOOL)send:(nonnull NSData *)data;
/** close closes the transport */
- (void)close;
@end
/** MQTTTransportDelegate protocol
* Note: the implementation of the didReceiveMessage method is mandatory, the others are optional
*/
@protocol MQTTTransportDelegate <NSObject>
/** didReceiveMessage gets called when a message was received
* @param mqttTransport the transport on which the message was received
* @param message the data received which may be zero length
*/
- (void)mqttTransport:(nonnull id<MQTTTransport>)mqttTransport didReceiveMessage:(nonnull NSData *)message;
@optional
/** mqttTransportDidOpen gets called when a transport is successfully opened
* @param mqttTransport the transport which was successfully opened
*/
- (void)mqttTransportDidOpen:(_Nonnull id<MQTTTransport>)mqttTransport;
/** didFailWithError gets called when an error was detected on the transport
* @param mqttTransport the transport which detected the error
* @param error available error information, might be nil
*/
- (void)mqttTransport:(_Nonnull id<MQTTTransport>)mqttTransport didFailWithError:(nullable NSError *)error;
/** mqttTransportDidClose gets called when the transport closed
* @param mqttTransport the transport which was closed
*/
- (void)mqttTransportDidClose:(_Nonnull id<MQTTTransport>)mqttTransport;
@end
@interface MQTTTransport : NSObject <MQTTTransport>
@end

View File

@@ -0,0 +1,40 @@
//
// MQTTTransport.m
// MQTTClient
//
// Created by Christoph Krey on 05.01.16.
// Copyright © 2016-2017 Christoph Krey. All rights reserved.
//
#import "MQTTTransport.h"
#import "MQTTLog.h"
@implementation MQTTTransport
@synthesize state;
@synthesize queue;
@synthesize streamSSLLevel;
@synthesize delegate;
@synthesize host;
@synthesize port;
- (instancetype)init {
self = [super init];
self.state = MQTTTransportCreated;
return self;
}
- (void)open {
DDLogError(@"MQTTTransport is abstract class");
}
- (void)close {
DDLogError(@"MQTTTransport is abstract class");
}
- (BOOL)send:(NSData *)data {
DDLogError(@"MQTTTransport is abstract class");
return FALSE;
}
@end

View File

@@ -0,0 +1,21 @@
//
// ReconnectTimer.h
// MQTTClient
//
// Created by Josip Cavar on 22/08/2017.
// Copyright © 2017 Christoph Krey. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface ReconnectTimer : NSObject
- (instancetype)initWithRetryInterval:(NSTimeInterval)retryInterval
maxRetryInterval:(NSTimeInterval)maxRetryInterval
queue:(dispatch_queue_t)queue
reconnectBlock:(void (^)(void))block;
- (void)schedule;
- (void)stop;
- (void)resetRetryInterval;
@end

View File

@@ -0,0 +1,67 @@
//
// ReconnectTimer.m
// MQTTClient
//
// Created by Josip Cavar on 22/08/2017.
// Copyright © 2017 Christoph Krey. All rights reserved.
//
#import "ReconnectTimer.h"
#import "GCDTimer.h"
@interface ReconnectTimer()
@property (strong, nonatomic) GCDTimer *timer;
@property (assign, nonatomic) NSTimeInterval retryInterval;
@property (assign, nonatomic) NSTimeInterval currentRetryInterval;
@property (assign, nonatomic) NSTimeInterval maxRetryInterval;
@property (strong, nonatomic) dispatch_queue_t queue;
@property (copy, nonatomic) void (^reconnectBlock)(void);
@end
@implementation ReconnectTimer
- (instancetype)initWithRetryInterval:(NSTimeInterval)retryInterval
maxRetryInterval:(NSTimeInterval)maxRetryInterval
queue:(dispatch_queue_t)queue
reconnectBlock:(void (^)(void))block {
self = [super init];
if (self) {
self.retryInterval = retryInterval;
self.currentRetryInterval = retryInterval;
self.maxRetryInterval = maxRetryInterval;
self.reconnectBlock = block;
self.queue = queue;
}
return self;
}
- (void)schedule {
__weak typeof(self) weakSelf = self;
self.timer = [GCDTimer scheduledTimerWithTimeInterval:self.currentRetryInterval
repeats:NO
queue:self.queue
block:^{
[weakSelf reconnect];
}];
}
- (void)stop {
[self.timer invalidate];
self.timer = nil;
}
- (void)resetRetryInterval {
self.currentRetryInterval = self.retryInterval;
}
- (void)reconnect {
[self stop];
if (self.currentRetryInterval < self.maxRetryInterval) {
self.currentRetryInterval *= 2;
}
self.reconnectBlock();
}
@end

153
Pods/MQTTClient/README.md generated Normal file
View File

@@ -0,0 +1,153 @@
# MQTT-Client-Framework
[![Build Status](https://travis-ci.org/novastone-media/MQTT-Client-Framework.svg?branch=master)](https://travis-ci.org/novastone-media/MQTT-Client-Framework)
[![codecov](https://codecov.io/gh/novastone-media/MQTT-Client-Framework/branch/master/graph/badge.svg)](https://codecov.io/gh/novastone-media/MQTT-Client-Framework)
[![CocoaPods Version](https://img.shields.io/cocoapods/v/MQTTClient.svg)](https://img.shields.io/cocoapods/v/MQTTClient.svg)
[![Platform](https://img.shields.io/cocoapods/p/MQTTClient.svg?style=flat)](https://img.shields.io/cocoapods/p/MQTTClient.svg?style=flat)
**Welcome to MQTT-Client-Framework**
MQTT-Client-Framework is a native Objective-C iOS library. It uses `CFNetwork` for networking and `CoreData` for persistence. It is a complete implementation of MQTT 3.1.1 and supports TLS.
You can read [introduction](http://www.hivemq.com/blog/mqtt-client-library-encyclopedia-mqtt-client-framework) to learn more about framework.
MQTT-Client-Framework is tested with a long list of brokers:
* mosquitto
* paho
* rabbitmq
* hivemq
* rsmb
* mosca
* vernemq
* emqtt
* moquette
* ActiveMQ
* Apollo
* CloudMQTT
* aws
* hbmqtt (MQTTv311 only, limitations)
* [aedes](https://github.com/mcollina/aedes)
* [flespi](https://flespi.com/mqtt-broker)
## Usage
Create a new client and connect to a broker:
```objective-c
#import "MQTTClient.h"
MQTTCFSocketTransport *transport = [[MQTTCFSocketTransport alloc] init];
transport.host = @"test.mosquitto.org";
transport.port = 1883;
MQTTSession *session = [[MQTTSession alloc] init];
session.transport = transport;
[session connectWithConnectHandler:^(NSError *error) {
// Do some work
}];
```
Subscribe to a topic:
```objective-c
[session subscribeToTopic:@"example/#" atLevel:MQTTQosLevelExactlyOnce subscribeHandler:^(NSError *error, NSArray<NSNumber *> *gQoss) {
if (error) {
NSLog(@"Subscription failed %@", error.localizedDescription);
} else {
NSLog(@"Subscription sucessfull! Granted Qos: %@", gQoss);
}
}];
```
In your `MQTTSession` delegate, add the following to receive messages for the subscribed topics:
```objective-c
- (void)newMessage:(MQTTSession *)session data:(NSData *)data onTopic:(NSString *)topic qos:(MQTTQosLevel)qos retained:(BOOL)retained mid:(unsigned int)mid {
// New message received in topic
}
```
Publish a message to a topic:
```objective-c
[session publishData:someData onTopic:@"example/#" retain:NO qos:MQTTQosLevelAtMostOnce publishHandler:^(NSError *error) {
}];
```
If you already have a self signed URL from broker like AWS IoT endpoint, use the `url` property of `MQTTWebsocketTransport`:
```objective-c
MQTTWebsocketTransport *transport = [[MQTTWebsocketTransport alloc] init];
transport.url = @"wss://aws.iot-amazonaws.com/mqtt?expiry='2018-05-01T23:12:32.950Z'"
MQTTSession *session = [[MQTTSession alloc] init];
session.transport = transport;
[session connectWithConnectHandler:^(NSError *error) {
// Do some work
}];
```
## Installation
### CocoaPods
Add this to your Podfile:
```
pod 'MQTTClient'
```
which is a short for:
```
pod 'MQTTClient/Min'
pod 'MQTTClient/Manager'
```
The Manager subspec includes the `MQTTSessionManager` class.
If you want to use MQTT over Websockets:
```
pod 'MQTTClient/Websocket'
```
If you want to do your logging with CocoaLumberjack (recommended):
```
pod 'MQTTClient/MinL'
pod 'MQTTClient/ManagerL'
pod 'MQTTClient/WebsocketL'
```
### Carthage
In your Cartfile:
```
github "novastone-media/MQTT-Client-Framework"
```
### Manually
#### Git submodule
1. Add MQTT-Client-Framework as a git submodule into your top-level project directory or simply copy whole folder
2. Find MQTTClient.xcodeproj and drag it into the file navigator of your app project.
3. In Xcode, navigate to the target configuration window by clicking on the blue project icon, and selecting the application target under the "Targets" heading in the sidebar.
4. Under "General" panel go to "Linked Frameworks and Libraries" and add MQTTClient.framework
#### Framework
1. Download MQTT-Client-Framework
2. Build it and you should find MQTTClient.framework under "Products" group.
3. Right click on it and select "Show in Finder" option.
4. Just drag and drop MQTTClient.framework to your project
## Security Disclosure
If you believe you have identified a security vulnerability with MQTT-Client-Framework, please report it to ios@novastonemedia.com and do not post it to a public issue tracker.
## Thanks
This project was originally written by [Christoph Krey](https://github.com/ckrey).