Index: /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/AES256/NSData+AES256.h
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/AES256/NSData+AES256.h	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/AES256/NSData+AES256.h	(working copy)
@@ -0,0 +1,18 @@
+//
+//  NSData+AES256.h
+//  MacTunnel
+//
+//  Created by wangxy on 2017/3/21.
+//  Copyright © 2017年 wangxy. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import <CommonCrypto/CommonDigest.h>
+#import <CommonCrypto/CommonCryptor.h>
+
+@interface NSData (AES256)
+
+- (NSData *)aes256_encrypt:(NSString *)key;
+- (NSData *)aes256_decrypt:(NSString *)key;
+
+@end
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/AES256/NSData+AES256.m
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/AES256/NSData+AES256.m	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/AES256/NSData+AES256.m	(working copy)
@@ -0,0 +1,61 @@
+//
+//  NSData+AES256.m
+//  MacTunnel
+//
+//  Created by wangxy on 2017/3/21.
+//  Copyright © 2017年 wangxy. All rights reserved.
+//
+
+#import "NSData+AES256.h"
+
+@implementation NSData (AES256)
+
+- (NSData *)aes256_encrypt:(NSString *)key   //加密
+{
+    char keyPtr[kCCKeySizeAES256+1];
+    bzero(keyPtr, sizeof(keyPtr));
+    [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
+    NSUInteger dataLength = [self length];
+    size_t bufferSize = dataLength + kCCBlockSizeAES128;
+    void *buffer = malloc(bufferSize);
+    size_t numBytesEncrypted = 0;
+    CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmAES128,
+                                          kCCOptionPKCS7Padding | kCCOptionECBMode,
+                                          keyPtr, kCCBlockSizeAES128,
+                                          NULL,
+                                          [self bytes], dataLength,
+                                          buffer, bufferSize,
+                                          &numBytesEncrypted);
+    if (cryptStatus == kCCSuccess) {
+        return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
+    }
+    free(buffer);
+    return nil;
+}
+
+
+- (NSData *)aes256_decrypt:(NSString *)key   //解密
+{
+    char keyPtr[kCCKeySizeAES256+1];
+    bzero(keyPtr, sizeof(keyPtr));
+    [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
+    NSUInteger dataLength = [self length];
+    size_t bufferSize = dataLength + kCCBlockSizeAES128;
+    void *buffer = malloc(bufferSize);
+    size_t numBytesDecrypted = 0;
+    CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt, kCCAlgorithmAES128,
+                                          kCCOptionPKCS7Padding | kCCOptionECBMode,
+                                          keyPtr, kCCBlockSizeAES128,
+                                          NULL,
+                                          [self bytes], dataLength,
+                                          buffer, bufferSize,
+                                          &numBytesDecrypted);
+    if (cryptStatus == kCCSuccess) {
+        return [NSData dataWithBytesNoCopy:buffer length:numBytesDecrypted];
+        
+    }
+    free(buffer);
+    return nil;
+}
+
+@end
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/AES256/NSString+AES256.h
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/AES256/NSString+AES256.h	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/AES256/NSString+AES256.h	(working copy)
@@ -0,0 +1,20 @@
+//
+//  NSString+AES256.h
+//  MacTunnel
+//
+//  Created by wangxy on 2017/3/21.
+//  Copyright © 2017年 wangxy. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import <CommonCrypto/CommonDigest.h>
+#import <CommonCrypto/CommonCryptor.h>
+
+#import "NSData+AES256.h"
+
+@interface NSString (AES256)
+
+- (NSString *)aes256_encrypt:(NSString *)key;
+- (NSString *)aes256_decrypt:(NSString *)key;
+
+@end
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/AES256/NSString+AES256.m
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/AES256/NSString+AES256.m	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/AES256/NSString+AES256.m	(working copy)
@@ -0,0 +1,55 @@
+//
+//  NSString+AES256.m
+//  MacTunnel
+//
+//  Created by wangxy on 2017/3/21.
+//  Copyright © 2017年 wangxy. All rights reserved.
+//
+
+#import "NSString+AES256.h"
+
+@implementation NSString (AES256)
+
+- (NSString *)aes256_encrypt:(NSString *)key
+{
+    const char *cstr = [self cStringUsingEncoding:NSUTF8StringEncoding];
+    NSData *data = [NSData dataWithBytes:cstr length:self.length];
+    //对数据进行加密
+    NSData *result = [data aes256_encrypt:key];
+    
+    //转换为2进制字符串
+    if (result && result.length > 0) {
+        
+        Byte *datas = (Byte*)[result bytes];
+        NSMutableString *output = [NSMutableString stringWithCapacity:result.length * 2];
+        for(int i = 0; i < result.length; i++){
+            [output appendFormat:@"%02x", datas[i]];
+        }
+        return output;
+    }
+    return nil;
+}
+
+- (NSString *)aes256_decrypt:(NSString *)key
+{
+    //转换为2进制Data
+    NSMutableData *data = [NSMutableData dataWithCapacity:self.length / 2];
+    unsigned char whole_byte;
+    char byte_chars[3] = {'\0','\0','\0'};
+    int i;
+    for (i=0; i < [self length] / 2; i++) {
+        byte_chars[0] = [self characterAtIndex:i*2];
+        byte_chars[1] = [self characterAtIndex:i*2+1];
+        whole_byte = strtol(byte_chars, NULL, 16);
+        [data appendBytes:&whole_byte length:1];
+    }
+    
+    //对数据进行解密
+    NSData* result = [data aes256_decrypt:key];
+    if (result && result.length > 0) {
+        return [[NSString alloc] initWithData:result encoding:NSUTF8StringEncoding];
+    }
+    return nil;
+}
+
+@end
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/Base64/Base64.h
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/Base64/Base64.h	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/Base64/Base64.h	(working copy)
@@ -0,0 +1,53 @@
+//
+//  Base64.h
+//
+//  Version 1.1
+//
+//  Created by Nick Lockwood on 12/01/2012.
+//  Copyright (C) 2012 Charcoal Design
+//
+//  Distributed under the permissive zlib License
+//  Get the latest version from here:
+//
+//  https://github.com/nicklockwood/Base64
+//
+//  This software is provided 'as-is', without any express or implied
+//  warranty.  In no event will the authors be held liable for any damages
+//  arising from the use of this software.
+//
+//  Permission is granted to anyone to use this software for any purpose,
+//  including commercial applications, and to alter it and redistribute it
+//  freely, subject to the following restrictions:
+//
+//  1. The origin of this software must not be misrepresented; you must not
+//  claim that you wrote the original software. If you use this software
+//  in a product, an acknowledgment in the product documentation would be
+//  appreciated but is not required.
+//
+//  2. Altered source versions must be plainly marked as such, and must not be
+//  misrepresented as being the original software.
+//
+//  3. This notice may not be removed or altered from any source distribution.
+//
+
+#import <Foundation/Foundation.h>
+
+
+@interface NSData (Base64)
+
++ (NSData *)dataWithBase64EncodedString:(NSString *)string;
+- (NSString *)base64EncodedStringWithWrapWidth:(NSUInteger)wrapWidth;
+- (NSString *)base64EncodedString;
+
+@end
+
+
+@interface NSString (Base64)
+
++ (NSString *)stringWithBase64EncodedString:(NSString *)string;
+- (NSString *)base64EncodedStringWithWrapWidth:(NSUInteger)wrapWidth;
+- (NSString *)base64EncodedString;
+- (NSString *)base64DecodedString;
+- (NSData *)base64DecodedData;
+
+@end
\ No newline at end of file
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/Base64/Base64.m
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/Base64/Base64.m	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/Base64/Base64.m	(working copy)
@@ -0,0 +1,202 @@
+//
+//  Base64.m
+//
+//  Version 1.1
+//
+//  Created by Nick Lockwood on 12/01/2012.
+//  Copyright (C) 2012 Charcoal Design
+//
+//  Distributed under the permissive zlib License
+//  Get the latest version from here:
+//
+//  https://github.com/nicklockwood/Base64
+//
+//  This software is provided 'as-is', without any express or implied
+//  warranty.  In no event will the authors be held liable for any damages
+//  arising from the use of this software.
+//
+//  Permission is granted to anyone to use this software for any purpose,
+//  including commercial applications, and to alter it and redistribute it
+//  freely, subject to the following restrictions:
+//
+//  1. The origin of this software must not be misrepresented; you must not
+//  claim that you wrote the original software. If you use this software
+//  in a product, an acknowledgment in the product documentation would be
+//  appreciated but is not required.
+//
+//  2. Altered source versions must be plainly marked as such, and must not be
+//  misrepresented as being the original software.
+//
+//  3. This notice may not be removed or altered from any source distribution.
+//
+
+#import "Base64.h"
+
+
+#import <Availability.h>
+#if !__has_feature(objc_arc)
+#error This library requires automatic reference counting
+#endif
+
+
+@implementation NSData (Base64)
+
++ (NSData *)dataWithBase64EncodedString:(NSString *)string
+{
+    const char lookup[] =
+    {
+        99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 
+        99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 
+        99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 62, 99, 99, 99, 63, 
+        52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 99, 99, 99, 99, 99, 99, 
+        99,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 
+        15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 99, 99, 99, 99, 99, 
+        99, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 
+        41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 99, 99, 99, 99, 99
+    };
+    
+    NSData *inputData = [string dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
+    long long inputLength = [inputData length];
+    const unsigned char *inputBytes = [inputData bytes];
+    
+    long long maxOutputLength = (inputLength / 4 + 1) * 3;
+    NSMutableData *outputData = [NSMutableData dataWithLength:(NSInteger)maxOutputLength];
+    unsigned char *outputBytes = (unsigned char *)[outputData mutableBytes];
+
+    int accumulator = 0;
+    long long outputLength = 0;
+    unsigned char accumulated[] = {0, 0, 0, 0};
+    for (long long i = 0; i < inputLength; i++)
+    {
+        unsigned char decoded = lookup[inputBytes[i] & 0x7F];
+        if (decoded != 99)
+        {
+            accumulated[accumulator] = decoded;
+            if (accumulator == 3)
+            {
+                outputBytes[outputLength++] = (accumulated[0] << 2) | (accumulated[1] >> 4);  
+                outputBytes[outputLength++] = (accumulated[1] << 4) | (accumulated[2] >> 2);  
+                outputBytes[outputLength++] = (accumulated[2] << 6) | accumulated[3];
+            }
+            accumulator = (accumulator + 1) % 4;
+        }
+    }
+    
+    //handle left-over data
+    if (accumulator > 0) outputBytes[outputLength] = (accumulated[0] << 2) | (accumulated[1] >> 4);
+    if (accumulator > 1) outputBytes[++outputLength] = (accumulated[1] << 4) | (accumulated[2] >> 2);
+    if (accumulator > 2) outputLength++;
+    
+    //truncate data to match actual output length
+    outputData.length = (NSInteger)outputLength;
+    return outputLength? outputData: nil;
+}
+
+- (NSString *)base64EncodedStringWithWrapWidth:(NSUInteger)wrapWidth
+{
+    //ensure wrapWidth is a multiple of 4
+    wrapWidth = (wrapWidth / 4) * 4;
+    
+    const char lookup[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+    
+    long long inputLength = [self length];
+    const unsigned char *inputBytes = [self bytes];
+    
+    long long maxOutputLength = (inputLength / 3 + 1) * 4;
+    maxOutputLength += wrapWidth? (maxOutputLength / wrapWidth) * 2: 0;
+    unsigned char *outputBytes = (unsigned char *)malloc((size_t)maxOutputLength);
+    
+    long long i;
+    long long outputLength = 0;
+    for (i = 0; i < inputLength - 2; i += 3)
+    {
+        outputBytes[outputLength++] = lookup[(inputBytes[i] & 0xFC) >> 2];
+        outputBytes[outputLength++] = lookup[((inputBytes[i] & 0x03) << 4) | ((inputBytes[i + 1] & 0xF0) >> 4)];
+        outputBytes[outputLength++] = lookup[((inputBytes[i + 1] & 0x0F) << 2) | ((inputBytes[i + 2] & 0xC0) >> 6)];
+        outputBytes[outputLength++] = lookup[inputBytes[i + 2] & 0x3F];
+        
+        //add line break
+        if (wrapWidth && (outputLength + 2) % (wrapWidth + 2) == 0)
+        {
+            outputBytes[outputLength++] = '\r';
+            outputBytes[outputLength++] = '\n';
+        }
+    }
+    
+    //handle left-over data
+    if (i == inputLength - 2)
+    {
+        // = terminator
+        outputBytes[outputLength++] = lookup[(inputBytes[i] & 0xFC) >> 2];
+        outputBytes[outputLength++] = lookup[((inputBytes[i] & 0x03) << 4) | ((inputBytes[i + 1] & 0xF0) >> 4)];
+        outputBytes[outputLength++] = lookup[(inputBytes[i + 1] & 0x0F) << 2];
+        outputBytes[outputLength++] =   '=';
+    }
+    else if (i == inputLength - 1)
+    {
+        // == terminator
+        outputBytes[outputLength++] = lookup[(inputBytes[i] & 0xFC) >> 2];
+        outputBytes[outputLength++] = lookup[(inputBytes[i] & 0x03) << 4];
+        outputBytes[outputLength++] = '=';
+        outputBytes[outputLength++] = '=';
+    }
+    
+    if (outputLength >= 4)
+    {
+        //truncate data to match actual output length
+        outputBytes = realloc(outputBytes, (size_t)outputLength);
+        return [[NSString alloc] initWithBytesNoCopy:outputBytes
+                                              length:(NSInteger)outputLength
+                                            encoding:NSASCIIStringEncoding
+                                        freeWhenDone:YES];
+    }
+    else if (outputBytes)
+    {
+        free(outputBytes);
+    }
+    return nil;
+}
+
+- (NSString *)base64EncodedString
+{
+    return [self base64EncodedStringWithWrapWidth:0];
+}
+
+@end
+
+
+@implementation NSString (Base64)
+
++ (NSString *)stringWithBase64EncodedString:(NSString *)string
+{
+    NSData *data = [NSData dataWithBase64EncodedString:string];
+    if (data)
+    {
+        return [[self alloc] initWithData:data encoding:NSUTF8StringEncoding];
+    }
+    return nil;
+}
+
+- (NSString *)base64EncodedStringWithWrapWidth:(NSUInteger)wrapWidth
+{
+    NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
+    return [data base64EncodedStringWithWrapWidth:wrapWidth];
+}
+
+- (NSString *)base64EncodedString
+{
+    NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
+    return [data base64EncodedString];
+}
+
+- (NSString *)base64DecodedString
+{
+    return [NSString stringWithBase64EncodedString:self];
+}
+
+- (NSData *)base64DecodedData
+{
+    return [NSData dataWithBase64EncodedString:self];
+}
+
+@end
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/FBKVOController/FBKVOController.h
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/FBKVOController/FBKVOController.h	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/FBKVOController/FBKVOController.h	(working copy)
@@ -0,0 +1,105 @@
+/**
+  Copyright (c) 2014-present, Facebook, Inc.
+  All rights reserved.
+
+  This source code is licensed under the BSD-style license found in the
+  LICENSE file in the root directory of this source tree. An additional grant
+  of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#import <Foundation/Foundation.h>
+
+/**
+ @abstract Block called on key-value change notification.
+ @param observer The observer of the change.
+ @param object The object changed.
+ @param change The change dictionary.
+ */
+typedef void (^FBKVONotificationBlock)(id observer, id object, NSDictionary *change);
+
+
+/**
+ @abstract FBKVOController makes Key-Value Observing simpler and safer.
+ @discussion FBKVOController adds support for handling key-value changes with blocks and custom actions, as well as the NSKeyValueObserving callback. Notification will never message a deallocated observer. Observer removal never throws exceptions, and observers are removed implicitely on controller deallocation. FBKVOController is also thread safe. When used in a concurrent environment, it protects observers from possible ressurection and avoids ensuing crash. By default, the controller maintains a strong reference to objects observed.
+ */
+@interface FBKVOController : NSObject
+
+/**
+ @abstract Creates and returns an initialized KVO controller instance.
+ @param observer The object notified on key-value change.
+ @return The initialized KVO controller instance.
+ */
++ (instancetype)controllerWithObserver:(id)observer;
+
+/**
+ @abstract The designated initializer.
+ @param observer The object notified on key-value change. The specified observer must support weak references.
+ @param retainObserved Flag indicating whether observed objects should be retained.
+ @return The initialized KVO controller instance.
+ @discussion Use retainObserved = NO when a strong reference between controller and observee would create a retain loop. When not retaining observees, special care must be taken to remove observation info prior to observee dealloc.
+ */
+- (instancetype)initWithObserver:(id)observer retainObserved:(BOOL)retainObserved;
+
+/**
+ @abstract Convenience initializer.
+ @param observer The object notified on key-value change. The specified observer must support weak references.
+ @return The initialized KVO controller instance.
+ @discussion By default, KVO controller retains objects observed.
+ */
+- (instancetype)initWithObserver:(id)observer;
+
+/// The observer notified on key-value change. Specified on initialization.
+@property (atomic, weak, readonly) id observer;
+
+/**
+ @abstract Registers observer for key-value change notification.
+ @param object The object to observe.
+ @param keyPath The key path to observe.
+ @param options The NSKeyValueObservingOptions to use for observation.
+ @param block The block to execute on notification.
+ @discussion On key-value change, the specified block is called. Inorder to avoid retain loops, the block must avoid referencing the KVO controller or an owner thereof. Observing an already observed object key path or nil results in no operation.
+ */
+- (void)observe:(id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block;
+
+/**
+ @abstract Registers observer for key-value change notification.
+ @param object The object to observe.
+ @param keyPath The key path to observe.
+ @param options The NSKeyValueObservingOptions to use for observation.
+ @param action The observer selector called on key-value change.
+ @discussion On key-value change, the observer's action selector is called. The selector provided should take the form of -propertyDidChange, -propertyDidChange: or -propertyDidChange:object:, where optional parameters delivered will be KVO change dictionary and object observed. Observing nil or observing an already observed object's key path results in no operation.
+ */
+- (void)observe:(id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options action:(SEL)action;
+
+/**
+ @abstract Registers observer for key-value change notification.
+ @param object The object to observe.
+ @param keyPath The key path to observe.
+ @param options The NSKeyValueObservingOptions to use for observation.
+ @param context The context specified.
+ @discussion On key-value change, the observer's -observeValueForKeyPath:ofObject:change:context: method is called. Observing an already observed object key path or nil results in no operation.
+ */
+- (void)observe:(id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;
+
+/**
+ @abstract Unobserve object key path.
+ @param object The object to unobserve.
+ @param keyPath The key path to observe.
+ @discussion If not observing object key path, or unobserving nil, this method results in no operation.
+ */
+- (void)unobserve:(id)object keyPath:(NSString *)keyPath;
+
+/**
+ @abstract Unobserve all object key paths.
+ @param object The object to unobserve.
+ @discussion If not observing object, or unobserving nil, this method results in no operation.
+ */
+- (void)unobserve:(id)object;
+
+/**
+ @abstract Unobserve all objects.
+ @discussion If not observing any objects, this method results in no operation.
+ */
+- (void)unobserveAll;
+
+@end
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/FBKVOController/FBKVOController.m
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/FBKVOController/FBKVOController.m	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/FBKVOController/FBKVOController.m	(working copy)
@@ -0,0 +1,583 @@
+/**
+  Copyright (c) 2014-present, Facebook, Inc.
+  All rights reserved.
+
+  This source code is licensed under the BSD-style license found in the
+  LICENSE file in the root directory of this source tree. An additional grant
+  of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#import "FBKVOController.h"
+
+#import <libkern/OSAtomic.h>
+#import <objc/message.h>
+
+#if !__has_feature(objc_arc)
+#error This file must be compiled with ARC. Convert your project to ARC or specify the -fobjc-arc flag.
+#endif
+
+#pragma mark Utilities -
+
+static NSString *describe_option(NSKeyValueObservingOptions option)
+{
+  switch (option) {
+    case NSKeyValueObservingOptionNew:
+      return @"NSKeyValueObservingOptionNew";
+      break;
+    case NSKeyValueObservingOptionOld:
+      return @"NSKeyValueObservingOptionOld";
+      break;
+    case NSKeyValueObservingOptionInitial:
+      return @"NSKeyValueObservingOptionInitial";
+      break;
+    case NSKeyValueObservingOptionPrior:
+      return @"NSKeyValueObservingOptionPrior";
+      break;
+    default:
+      NSCAssert(NO, @"unexpected option %tu", option);
+      break;
+  }
+  return nil;
+}
+
+static void append_option_description(NSMutableString *s, NSUInteger option)
+{
+  if (0 == s.length) {
+    [s appendString:describe_option(option)];
+  } else {
+    [s appendString:@"|"];
+    [s appendString:describe_option(option)];
+  }
+}
+
+static NSUInteger enumerate_flags(NSUInteger *ptrFlags)
+{
+  NSCAssert(ptrFlags, @"expected ptrFlags");
+  if (!ptrFlags) {
+    return 0;
+  }
+  
+  NSUInteger flags = *ptrFlags;
+  if (!flags) {
+    return 0;
+  }
+
+  NSUInteger flag = 1 << __builtin_ctzl(flags);
+  flags &= ~flag;
+  *ptrFlags = flags;
+  return flag;
+}
+
+static NSString *describe_options(NSKeyValueObservingOptions options)
+{
+  NSMutableString *s = [NSMutableString string];
+  NSUInteger option;
+  while (0 != (option = enumerate_flags(&options))) {
+    append_option_description(s, option);
+  }
+  return s;
+}
+
+#pragma mark _FBKVOInfo -
+
+/**
+ @abstract The key-value observation info.
+ @discussion Object equality is only used within the scope of a controller instance. Safely omit controller from equality definition.
+ */
+@interface _FBKVOInfo : NSObject
+@end
+
+@implementation _FBKVOInfo
+{
+@public
+  __weak FBKVOController *_controller;
+  NSString *_keyPath;
+  NSKeyValueObservingOptions _options;
+  SEL _action;
+  void *_context;
+  FBKVONotificationBlock _block;
+}
+
+- (instancetype)initWithController:(FBKVOController *)controller keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block action:(SEL)action context:(void *)context
+{
+  self = [super init];
+  if (nil != self) {
+    _controller = controller;
+    _block = [block copy];
+    _keyPath = [keyPath copy];
+    _options = options;
+    _action = action;
+    _context = context;
+  }
+  return self;
+}
+
+- (instancetype)initWithController:(FBKVOController *)controller keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block
+{
+  return [self initWithController:controller keyPath:keyPath options:options block:block action:NULL context:NULL];
+}
+
+- (instancetype)initWithController:(FBKVOController *)controller keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options action:(SEL)action
+{
+  return [self initWithController:controller keyPath:keyPath options:options block:NULL action:action context:NULL];
+}
+
+- (instancetype)initWithController:(FBKVOController *)controller keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
+{
+  return [self initWithController:controller keyPath:keyPath options:options block:NULL action:NULL context:context];
+}
+
+- (instancetype)initWithController:(FBKVOController *)controller keyPath:(NSString *)keyPath
+{
+  return [self initWithController:controller keyPath:keyPath options:0 block:NULL action:NULL context:NULL];
+}
+
+- (NSUInteger)hash
+{
+  return [_keyPath hash];
+}
+
+- (BOOL)isEqual:(id)object
+{
+  if (nil == object) {
+    return NO;
+  }
+  if (self == object) {
+    return YES;
+  }
+  if (![object isKindOfClass:[self class]]) {
+    return NO;
+  }
+  return [_keyPath isEqualToString:((_FBKVOInfo *)object)->_keyPath];
+}
+
+- (NSString *)debugDescription
+{
+  NSMutableString *s = [NSMutableString stringWithFormat:@"<%@:%p keyPath:%@", NSStringFromClass([self class]), self, _keyPath];
+  if (0 != _options) {
+    [s appendFormat:@" options:%@", describe_options(_options)];
+  }
+  if (NULL != _action) {
+    [s appendFormat:@" action:%@", NSStringFromSelector(_action)];
+  }
+  if (NULL != _context) {
+    [s appendFormat:@" context:%p", _context];
+  }
+  if (NULL != _block) {
+    [s appendFormat:@" block:%p", _block];
+  }
+  [s appendString:@">"];
+  return s;
+}
+
+@end
+
+#pragma mark _FBKVOSharedController -
+
+/**
+ @abstract The shared KVO controller instance.
+ @discussion Acts as a receptionist, receiving and forwarding KVO notifications.
+ */
+@interface _FBKVOSharedController : NSObject
+
+/** A shared instance that never deallocates. */
++ (instancetype)sharedController;
+
+/** observe an object, info pair */
+- (void)observe:(id)object info:(_FBKVOInfo *)info;
+
+/** unobserve an object, info pair */
+- (void)unobserve:(id)object info:(_FBKVOInfo *)info;
+
+/** unobserve an object with a set of infos */
+- (void)unobserve:(id)object infos:(NSSet *)infos;
+
+@end
+
+@implementation _FBKVOSharedController
+{
+  NSHashTable *_infos;
+  OSSpinLock _lock;
+}
+
++ (instancetype)sharedController
+{
+  static _FBKVOSharedController *_controller = nil;
+  static dispatch_once_t onceToken;
+  dispatch_once(&onceToken, ^{
+    _controller = [[_FBKVOSharedController alloc] init];
+  });
+  return _controller;
+}
+
+- (instancetype)init
+{
+  self = [super init];
+  if (nil != self) {
+    NSHashTable *infos = [NSHashTable alloc];
+#ifdef __IPHONE_OS_VERSION_MIN_REQUIRED
+    _infos = [infos initWithOptions:NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality capacity:0];
+#elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
+    if ([NSHashTable respondsToSelector:@selector(weakObjectsHashTable)]) {
+      _infos = [infos initWithOptions:NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality capacity:0];
+    } else {
+      // silence deprecated warnings
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+      _infos = [infos initWithOptions:NSPointerFunctionsZeroingWeakMemory|NSPointerFunctionsObjectPointerPersonality capacity:0];
+#pragma clang diagnostic pop
+    }
+
+#endif
+    _lock = OS_SPINLOCK_INIT;
+  }
+  return self;
+}
+
+- (NSString *)debugDescription
+{
+  NSMutableString *s = [NSMutableString stringWithFormat:@"<%@:%p", NSStringFromClass([self class]), self];
+  
+  // lock
+  OSSpinLockLock(&_lock);
+  
+  NSMutableArray *infoDescriptions = [NSMutableArray arrayWithCapacity:_infos.count];
+  for (_FBKVOInfo *info in _infos) {
+    [infoDescriptions addObject:info.debugDescription];
+  }
+  
+  [s appendFormat:@" contexts:%@", infoDescriptions];
+  
+  // unlock
+  OSSpinLockUnlock(&_lock);
+  
+  [s appendString:@">"];
+  return s;
+}
+
+- (void)observe:(id)object info:(_FBKVOInfo *)info
+{
+  if (nil == info) {
+    return;
+  }
+  
+  // register info
+  OSSpinLockLock(&_lock);
+  [_infos addObject:info];
+  OSSpinLockUnlock(&_lock);
+  
+  // add observer
+  [object addObserver:self forKeyPath:info->_keyPath options:info->_options context:(void *)info];
+}
+
+- (void)unobserve:(id)object info:(_FBKVOInfo *)info
+{
+  if (nil == info) {
+    return;
+  }
+  
+  // unregister info
+  OSSpinLockLock(&_lock);
+  [_infos removeObject:info];
+  OSSpinLockUnlock(&_lock);
+  
+  // remove observer
+  [object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];
+}
+
+- (void)unobserve:(id)object infos:(NSSet *)infos
+{
+  if (0 == infos.count) {
+    return;
+  }
+  
+  // unregister info
+  OSSpinLockLock(&_lock);
+  for (_FBKVOInfo *info in infos) {
+    [_infos removeObject:info];
+  }
+  OSSpinLockUnlock(&_lock);
+  
+  // remove observer
+  for (_FBKVOInfo *info in infos) {
+    [object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];
+  }
+}
+
+- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
+{
+  NSAssert(context, @"missing context keyPath:%@ object:%@ change:%@", keyPath, object, change);
+  
+  _FBKVOInfo *info;
+  
+  {
+    // lookup context in registered infos, taking out a strong reference only if it exists
+    OSSpinLockLock(&_lock);
+    info = [_infos member:(__bridge id)context];
+    OSSpinLockUnlock(&_lock);
+  }
+  
+  if (nil != info) {
+    
+    // take strong reference to controller
+    FBKVOController *controller = info->_controller;
+    if (nil != controller) {
+      
+      // take strong reference to observer
+      id observer = controller.observer;
+      if (nil != observer) {
+        
+        // dispatch custom block or action, fall back to default action
+        if (info->_block) {
+          info->_block(observer, object, change);
+        } else if (info->_action) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
+          [observer performSelector:info->_action withObject:change withObject:object];
+#pragma clang diagnostic pop
+        } else {
+          [observer observeValueForKeyPath:keyPath ofObject:object change:change context:info->_context];
+        }
+      }
+    }
+  }
+}
+
+@end
+
+#pragma mark FBKVOController -
+
+@implementation FBKVOController
+{
+  NSMapTable *_objectInfosMap;
+  OSSpinLock _lock;
+}
+
+#pragma mark Lifecycle -
+
++ (instancetype)controllerWithObserver:(id)observer
+{
+  return [[self alloc] initWithObserver:observer];
+}
+
+- (instancetype)initWithObserver:(id)observer retainObserved:(BOOL)retainObserved
+{
+  self = [super init];
+  if (nil != self) {
+    _observer = observer;
+    NSPointerFunctionsOptions keyOptions = retainObserved ? NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPointerPersonality : NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality;
+    _objectInfosMap = [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPersonality capacity:0];
+    _lock = OS_SPINLOCK_INIT;
+  }
+  return self;
+}
+
+- (instancetype)initWithObserver:(id)observer
+{
+  return [self initWithObserver:observer retainObserved:YES];
+}
+
+- (void)dealloc
+{
+  [self unobserveAll];
+}
+
+#pragma mark Properties -
+
+- (NSString *)debugDescription
+{
+  NSMutableString *s = [NSMutableString stringWithFormat:@"<%@:%p", NSStringFromClass([self class]), self];
+  [s appendFormat:@" observer:<%@:%p>", NSStringFromClass([_observer class]), _observer];
+  
+  // lock
+  OSSpinLockLock(&_lock);
+  
+  if (0 != _objectInfosMap.count) {
+    [s appendString:@"\n  "];
+  }
+  
+  for (id object in _objectInfosMap) {
+    NSMutableSet *infos = [_objectInfosMap objectForKey:object];
+    NSMutableArray *infoDescriptions = [NSMutableArray arrayWithCapacity:infos.count];
+    [infos enumerateObjectsUsingBlock:^(_FBKVOInfo *info, BOOL *stop) {
+      [infoDescriptions addObject:info.debugDescription];
+    }];
+    [s appendFormat:@"%@ -> %@", object, infoDescriptions];
+  }
+  
+  // unlock
+  OSSpinLockUnlock(&_lock);
+  
+  [s appendString:@">"];
+  return s;
+}
+
+#pragma mark Utilities -
+
+- (void)_observe:(id)object info:(_FBKVOInfo *)info
+{
+  // lock
+  OSSpinLockLock(&_lock);
+  
+  NSMutableSet *infos = [_objectInfosMap objectForKey:object];
+  
+  // check for info existence
+  _FBKVOInfo *existingInfo = [infos member:info];
+  if (nil != existingInfo) {
+    NSLog(@"observation info already exists %@", existingInfo);
+    
+    // unlock and return
+    OSSpinLockUnlock(&_lock);
+    return;
+  }
+  
+  // lazilly create set of infos
+  if (nil == infos) {
+    infos = [NSMutableSet set];
+    [_objectInfosMap setObject:infos forKey:object];
+  }
+  
+  // add info and oberve
+  [infos addObject:info];
+  
+  // unlock prior to callout
+  OSSpinLockUnlock(&_lock);
+  
+  [[_FBKVOSharedController sharedController] observe:object info:info];
+}
+
+- (void)_unobserve:(id)object info:(_FBKVOInfo *)info
+{
+  // lock
+  OSSpinLockLock(&_lock);
+  
+  // get observation infos
+  NSMutableSet *infos = [_objectInfosMap objectForKey:object];
+  
+  // lookup registered info instance
+  _FBKVOInfo *registeredInfo = [infos member:info];
+  
+  if (nil != registeredInfo) {
+    [infos removeObject:registeredInfo];
+    
+    // remove no longer used infos
+    if (0 == infos.count) {
+      [_objectInfosMap removeObjectForKey:object];
+    }
+  }
+  
+  // unlock
+  OSSpinLockUnlock(&_lock);
+  
+  // unobserve
+  [[_FBKVOSharedController sharedController] unobserve:object info:registeredInfo];
+}
+
+- (void)_unobserve:(id)object
+{
+  // lock
+  OSSpinLockLock(&_lock);
+  
+  NSMutableSet *infos = [_objectInfosMap objectForKey:object];
+  
+  // remove infos
+  [_objectInfosMap removeObjectForKey:object];
+  
+  // unlock
+  OSSpinLockUnlock(&_lock);
+  
+  // unobserve
+  [[_FBKVOSharedController sharedController] unobserve:object infos:infos];
+}
+
+- (void)_unobserveAll
+{
+  // lock
+  OSSpinLockLock(&_lock);
+  
+  NSMapTable *objectInfoMaps = [_objectInfosMap copy];
+  
+  // clear table and map
+  [_objectInfosMap removeAllObjects];
+  
+  // unlock
+  OSSpinLockUnlock(&_lock);
+  
+  _FBKVOSharedController *shareController = [_FBKVOSharedController sharedController];
+  
+  for (id object in objectInfoMaps) {
+    // unobserve each registered object and infos
+    NSSet *infos = [objectInfoMaps objectForKey:object];
+    [shareController unobserve:object infos:infos];
+  }
+}
+
+#pragma mark API -
+
+- (void)observe:(id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block
+{
+  NSAssert(0 != keyPath.length && NULL != block, @"missing required parameters observe:%@ keyPath:%@ block:%p", object, keyPath, block);
+  if (nil == object || 0 == keyPath.length || NULL == block) {
+    return;
+  }
+  
+  // create info
+  _FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath options:options block:block];
+  
+  // observe object with info
+  [self _observe:object info:info];
+}
+
+- (void)observe:(id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options action:(SEL)action
+{
+  NSAssert(0 != keyPath.length && NULL != action, @"missing required parameters observe:%@ keyPath:%@ action:%@", object, keyPath, NSStringFromSelector(action));
+  NSAssert([_observer respondsToSelector:action], @"%@ does not respond to %@", _observer, NSStringFromSelector(action));
+  if (nil == object || 0 == keyPath.length || NULL == action) {
+    return;
+  }
+  
+  // create info
+  _FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath options:options action:action];
+  
+  // observe object with info
+  [self _observe:object info:info];
+}
+
+- (void)observe:(id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
+{
+  NSAssert(0 != keyPath.length, @"missing required parameters observe:%@ keyPath:%@", object, keyPath);
+  if (nil == object || 0 == keyPath.length) {
+    return;
+  }
+  
+  // create info
+  _FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath options:options context:context];
+  
+  // observe object with info
+  [self _observe:object info:info];
+}
+
+- (void)unobserve:(id)object keyPath:(NSString *)keyPath
+{
+  // create representative info
+  _FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath];
+  
+  // unobserve object property
+  [self _unobserve:object info:info];
+}
+
+- (void)unobserve:(id)object
+{
+  if (nil == object) {
+    return;
+  }
+  
+  [self _unobserve:object];
+}
+
+- (void)unobserveAll
+{
+  [self _unobserveAll];
+}
+
+@end
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/Keychain/ANKeychain.h
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/Keychain/ANKeychain.h	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/Keychain/ANKeychain.h	(working copy)
@@ -0,0 +1,21 @@
+//
+//  ANKeychain.h
+//  MobileNow
+//
+//  Created by Huangjl on 13-4-2.
+//  Copyright (c) 2013年 MobileNow. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface ANKeychain : NSObject
+
++ (NSString *)passwordForUsername:(NSString *)uname host:(NSString *)host;
++ (void)savePassword:(NSString *)password forUsername:(NSString *)uname host:(NSString *)host;
++ (void)deletePasswordForUsername:(NSString *)uname host:(NSString *)host;
+
++ (NSString *)passwordForAccountKey:(NSString *)key;
++ (void)savePassword:(NSString *)password forAccoutKey:(NSString *)key;
++ (void)deletePasswordForAccountKey:(NSString *)key;
+
+@end
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/Keychain/ANKeychain.m
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/Keychain/ANKeychain.m	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/Keychain/ANKeychain.m	(working copy)
@@ -0,0 +1,155 @@
+//
+//  ANKeychain.m
+//  MobileNow
+//
+//  Created by Huangjl on 13-4-2.
+//  Copyright (c) 2013年 MobileNow. All rights reserved.
+//
+
+#import "ANKeychain.h"
+#include <Security/Security.h>
+
+static NSString *serviceName = @"net.arraynetworks.MotionProPlus";
+
+@interface ANKeychain()
++ (NSMutableDictionary *)newSearchDictionary:(NSString *)identifier;
++ (NSData *)searchKeychainCopyMatching:(NSString *)identifier;
++ (BOOL)createKeychainValue:(NSString *)password forIdentifier:(NSString *)identifier;
++ (BOOL)updateKeychainValue:(NSString *)password forIdentifier:(NSString *)identifier;
++ (void)deleteKeychainValue:(NSString *)identifier;
+@end
+
+@implementation ANKeychain
++ (NSString *)passwordForAccountKey:(NSString *)key
+{
+    NSData *passwordData = [self searchKeychainCopyMatching:key];
+    NSString *password;
+    if (passwordData) {
+        password = [[NSString alloc] initWithData:passwordData
+                                         encoding:NSUTF8StringEncoding];
+    }
+    
+    if (password.length != 0) {
+        return password;
+    }
+    return nil;
+}
+
++ (void)savePassword:(NSString *)password forAccoutKey:(NSString *)key
+{
+    if (password.length == 0 || key.length == 0) return;
+    
+    [self createKeychainValue:password forIdentifier:key];
+}
++ (void)deletePasswordForAccountKey:(NSString *)key
+{
+    [self deleteKeychainValue:key];
+}
+
+
++ (NSString *)passwordForUsername:(NSString *)uname host:(NSString *)host
+{
+    NSString *account = [NSString stringWithFormat:@"%@/%@",uname,host];
+    NSData *passwordData = [self searchKeychainCopyMatching:account];
+    NSString *password;
+    if (passwordData) {
+        password = [[NSString alloc] initWithData:passwordData
+                                                   encoding:NSUTF8StringEncoding];
+    }
+    
+    if (password.length != 0) {
+        return password;
+    } 
+    return nil;
+}
+
++ (void)savePassword:(NSString *)password forUsername:(NSString *)uname host:(NSString *)host
+{
+    NSString *account = [NSString stringWithFormat:@"%@/%@",uname,host];
+    [self createKeychainValue:password forIdentifier:account];
+}
+
++ (void)deletePasswordForUsername:(NSString *)uname host:(NSString *)host
+{
+    NSString *account = [NSString stringWithFormat:@"%@/%@",uname,host];
+    [self deleteKeychainValue:account];
+}
+
++ (NSMutableDictionary *)newSearchDictionary:(NSString *)identifier {
+    NSMutableDictionary *searchDictionary = [[NSMutableDictionary alloc] init];
+    
+    [searchDictionary setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
+    
+    NSData *encodedIdentifier = [identifier dataUsingEncoding:NSUTF8StringEncoding];
+    [searchDictionary setObject:encodedIdentifier forKey:(__bridge id)kSecAttrGeneric];
+    [searchDictionary setObject:encodedIdentifier forKey:(__bridge id)kSecAttrAccount];
+    [searchDictionary setObject:serviceName forKey:(__bridge id)kSecAttrService];
+    
+    return searchDictionary;
+}
+
++ (NSData *)searchKeychainCopyMatching:(NSString *)identifier {
+    OSStatus status;
+    NSData *result = nil;
+    
+    NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
+    
+    // Add search attributes
+    [searchDictionary setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
+    
+    // Add search return types
+    [searchDictionary setObject:(id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
+    
+    CFTypeRef ret;
+    status = SecItemCopyMatching((__bridge CFDictionaryRef)searchDictionary, &ret);
+
+    if (status != errSecSuccess) {
+        return  nil;
+    }
+    result = (__bridge_transfer NSData *)ret;
+    
+
+    
+    return result;
+}
+
++ (BOOL)createKeychainValue:(NSString *)password forIdentifier:(NSString *)identifier {
+    OSStatus status;
+    
+
+    NSMutableDictionary *dictionary = [self newSearchDictionary:identifier];
+    
+    NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
+    [dictionary setObject:passwordData forKey:(__bridge id)kSecValueData];
+    
+    status = SecItemAdd((__bridge CFDictionaryRef)dictionary, NULL);
+    
+
+    return status == errSecSuccess ? YES : NO;
+}
+
++ (BOOL)updateKeychainValue:(NSString *)password forIdentifier:(NSString *)identifier {
+    OSStatus status;
+    
+
+    NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
+    NSMutableDictionary *updateDictionary = [[NSMutableDictionary alloc] init];
+    NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
+    [updateDictionary setObject:passwordData forKey:(__bridge id)kSecValueData];
+    
+    status = SecItemUpdate((__bridge CFDictionaryRef)searchDictionary,
+                           (__bridge CFDictionaryRef)updateDictionary);
+
+    
+    return status == errSecSuccess ? YES : NO;
+}
+
++ (void)deleteKeychainValue:(NSString *)identifier {
+
+    NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
+    SecItemDelete((__bridge CFDictionaryRef)searchDictionary);
+    
+
+}
+
+@end
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/Keychain/ANKeychain.m.InfosecEnterprise
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/Keychain/ANKeychain.m.InfosecEnterprise	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/Keychain/ANKeychain.m.InfosecEnterprise	(working copy)
@@ -0,0 +1,241 @@
+//
+//  ANKeychain.m
+//  MobileNow
+//
+//  Created by Huangjl on 13-4-2.
+//  Copyright (c) 2013年 MobileNow. All rights reserved.
+//
+
+#import "ANKeychain.h"
+#include <Security/Security.h>
+
+static NSString *serviceName = @"net.infosec.MotionProPlusEnterprise";
+
+@interface ANKeychain()
++ (NSMutableDictionary *)newSearchDictionary:(NSString *)identifier;
++ (NSData *)searchKeychainCopyMatching:(NSString *)identifier;
++ (BOOL)createKeychainValue:(NSString *)password forIdentifier:(NSString *)identifier;
++ (BOOL)updateKeychainValue:(NSString *)password forIdentifier:(NSString *)identifier;
++ (void)deleteKeychainValue:(NSString *)identifier;
+@end
+
+@implementation ANKeychain
++ (NSString *)passwordForAccountKey:(NSString *)key
+{
+    NSData *passwordData = [self searchKeychainCopyMatching:key];
+    NSString *password;
+    if (passwordData) {
+        password = [[NSString alloc] initWithData:passwordData
+                                         encoding:NSUTF8StringEncoding];
+    }
+    
+    if (password.length != 0) {
+        return password;
+    }
+    return nil;
+}
+
++ (void)savePassword:(NSString *)password forAccoutKey:(NSString *)key
+{
+    if (password.length == 0 || key.length == 0) return;
+    
+    [self createKeychainValue:password forIdentifier:key];
+}
++ (void)deletePasswordForAccountKey:(NSString *)key
+{
+    [self deleteKeychainValue:key];
+}
+
+
++ (NSString *)passwordForUsername:(NSString *)uname host:(NSString *)host
+{
+    NSString *account = [NSString stringWithFormat:@"%@/%@",uname,host];
+    NSData *passwordData = [self searchKeychainCopyMatching:account];
+    NSString *password;
+    if (passwordData) {
+        password = [[NSString alloc] initWithData:passwordData
+                                                   encoding:NSUTF8StringEncoding];
+    }
+    
+    if (password.length != 0) {
+        return password;
+    } 
+    return nil;
+}
+
++ (void)savePassword:(NSString *)password forUsername:(NSString *)uname host:(NSString *)host
+{
+    NSString *account = [NSString stringWithFormat:@"%@/%@",uname,host];
+    [self createKeychainValue:password forIdentifier:account];
+}
+
++ (void)deletePasswordForUsername:(NSString *)uname host:(NSString *)host
+{
+    NSString *account = [NSString stringWithFormat:@"%@/%@",uname,host];
+    [self deleteKeychainValue:account];
+}
+
++ (NSMutableDictionary *)newSearchDictionary:(NSString *)identifier {
+    NSMutableDictionary *searchDictionary = [[NSMutableDictionary alloc] init];
+    
+    [searchDictionary setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
+    
+    NSData *encodedIdentifier = [identifier dataUsingEncoding:NSUTF8StringEncoding];
+    [searchDictionary setObject:encodedIdentifier forKey:(__bridge id)kSecAttrGeneric];
+    [searchDictionary setObject:encodedIdentifier forKey:(__bridge id)kSecAttrAccount];
+    [searchDictionary setObject:serviceName forKey:(__bridge id)kSecAttrService];
+    
+    return searchDictionary;
+}
+
++ (NSData *)searchKeychainCopyMatching:(NSString *)identifier {
+    OSStatus status;
+    NSData *result = nil;
+    
+#if TARGET_OS_IPHONE
+    NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
+    
+    // Add search attributes
+    [searchDictionary setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
+    
+    // Add search return types
+    [searchDictionary setObject:(id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
+    
+    CFTypeRef ret;
+    status = SecItemCopyMatching((__bridge CFDictionaryRef)searchDictionary, &ret);
+
+    if (status != errSecSuccess) {
+        return  nil;
+    }
+    result = (__bridge_transfer NSData *)ret;
+    
+#elif TARGET_OS_MAC
+    UInt32 passwordLength = 0;
+    void *passwordData = NULL;
+    
+    status = SecKeychainFindGenericPassword(
+                                            NULL,
+                                            (UInt32)[serviceName length],
+                                            [serviceName UTF8String],
+                                            (UInt32)[identifier length],
+                                            [identifier UTF8String],
+                                            &passwordLength,
+                                            &passwordData,
+                                            NULL
+                                            );
+    if (status == errSecSuccess) {
+        if (passwordData && passwordLength > 0) {
+            result = [NSData dataWithBytes:passwordData length:passwordLength];
+            
+            SecKeychainItemFreeContent(NULL, passwordData);
+        }
+    }
+#endif
+    
+    return result;
+}
+
++ (BOOL)createKeychainValue:(NSString *)password forIdentifier:(NSString *)identifier {
+    OSStatus status;
+    
+#if TARGET_OS_IPHONE
+    NSMutableDictionary *dictionary = [self newSearchDictionary:identifier];
+    
+    NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
+    [dictionary setObject:passwordData forKey:(__bridge id)kSecValueData];
+    
+    status = SecItemAdd((__bridge CFDictionaryRef)dictionary, NULL);
+    
+#elif TARGET_OS_MAC    
+    status = SecKeychainAddGenericPassword(
+                                           NULL,
+                                           (UInt32)[serviceName length],
+                                           [serviceName UTF8String],
+                                           (UInt32)[identifier length],
+                                           [identifier UTF8String],
+                                           (UInt32)[password length],
+                                           [password UTF8String],
+                                           NULL
+                                           );
+#endif
+    
+    return status == errSecSuccess ? YES : NO;
+}
+
++ (BOOL)updateKeychainValue:(NSString *)password forIdentifier:(NSString *)identifier {
+    OSStatus status;
+    
+#if TARGET_OS_IPHONE
+    NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
+    NSMutableDictionary *updateDictionary = [[NSMutableDictionary alloc] init];
+    NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
+    [updateDictionary setObject:passwordData forKey:(__bridge id)kSecValueData];
+    
+    status = SecItemUpdate((__bridge CFDictionaryRef)searchDictionary,
+                           (__bridge CFDictionaryRef)updateDictionary);
+    
+#elif TARGET_OS_MAC
+    SecKeychainItemRef itemRef;
+    
+    status = SecKeychainFindGenericPassword(
+                                            NULL,
+                                            (UInt32)[serviceName length],
+                                            [serviceName UTF8String],
+                                            (UInt32)[identifier length],
+                                            [identifier UTF8String],
+                                            NULL,
+                                            NULL,
+                                            &itemRef
+                                            );
+    
+    if (status == errSecSuccess) {
+        if (itemRef) {
+            status = SecKeychainItemModifyAttributesAndData(
+                                                            itemRef,
+                                                            NULL,
+                                                            (UInt32)[password length],
+                                                            [password UTF8String]
+                                                            );
+            
+            CFRelease(itemRef);
+        } else {
+            return NO;
+        }
+    } else {
+        return NO;
+    }
+
+#endif
+    
+    return status == errSecSuccess ? YES : NO;
+}
+
++ (void)deleteKeychainValue:(NSString *)identifier {
+#if TARGET_OS_IPHONE
+    NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
+    SecItemDelete((__bridge CFDictionaryRef)searchDictionary);
+    
+#elif TARGET_OS_MAC
+    SecKeychainItemRef itemRef;
+    OSStatus status = SecKeychainFindGenericPassword(
+                                                     NULL,
+                                                     (UInt32)[serviceName length],
+                                                     [serviceName UTF8String],
+                                                     (UInt32)[identifier length],
+                                                     [identifier UTF8String],
+                                                     NULL,
+                                                     NULL,
+                                                     &itemRef
+                                                     );
+    if (status == errSecSuccess) {
+        if (itemRef) {
+            SecKeychainItemDelete(itemRef);
+            
+            CFRelease(itemRef);
+        }
+    }
+    
+#endif
+}
+
+@end
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/Keychain/ANKeychain.m.InfosecStore
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/Keychain/ANKeychain.m.InfosecStore	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/Keychain/ANKeychain.m.InfosecStore	(working copy)
@@ -0,0 +1,241 @@
+//
+//  ANKeychain.m
+//  MobileNow
+//
+//  Created by Huangjl on 13-4-2.
+//  Copyright (c) 2013年 MobileNow. All rights reserved.
+//
+
+#import "ANKeychain.h"
+#include <Security/Security.h>
+
+static NSString *serviceName = @"net.infosec.MotionPro";
+
+@interface ANKeychain()
++ (NSMutableDictionary *)newSearchDictionary:(NSString *)identifier;
++ (NSData *)searchKeychainCopyMatching:(NSString *)identifier;
++ (BOOL)createKeychainValue:(NSString *)password forIdentifier:(NSString *)identifier;
++ (BOOL)updateKeychainValue:(NSString *)password forIdentifier:(NSString *)identifier;
++ (void)deleteKeychainValue:(NSString *)identifier;
+@end
+
+@implementation ANKeychain
++ (NSString *)passwordForAccountKey:(NSString *)key
+{
+    NSData *passwordData = [self searchKeychainCopyMatching:key];
+    NSString *password;
+    if (passwordData) {
+        password = [[NSString alloc] initWithData:passwordData
+                                         encoding:NSUTF8StringEncoding];
+    }
+    
+    if (password.length != 0) {
+        return password;
+    }
+    return nil;
+}
+
++ (void)savePassword:(NSString *)password forAccoutKey:(NSString *)key
+{
+    if (password.length == 0 || key.length == 0) return;
+    
+    [self createKeychainValue:password forIdentifier:key];
+}
++ (void)deletePasswordForAccountKey:(NSString *)key
+{
+    [self deleteKeychainValue:key];
+}
+
+
++ (NSString *)passwordForUsername:(NSString *)uname host:(NSString *)host
+{
+    NSString *account = [NSString stringWithFormat:@"%@/%@",uname,host];
+    NSData *passwordData = [self searchKeychainCopyMatching:account];
+    NSString *password;
+    if (passwordData) {
+        password = [[NSString alloc] initWithData:passwordData
+                                                   encoding:NSUTF8StringEncoding];
+    }
+    
+    if (password.length != 0) {
+        return password;
+    } 
+    return nil;
+}
+
++ (void)savePassword:(NSString *)password forUsername:(NSString *)uname host:(NSString *)host
+{
+    NSString *account = [NSString stringWithFormat:@"%@/%@",uname,host];
+    [self createKeychainValue:password forIdentifier:account];
+}
+
++ (void)deletePasswordForUsername:(NSString *)uname host:(NSString *)host
+{
+    NSString *account = [NSString stringWithFormat:@"%@/%@",uname,host];
+    [self deleteKeychainValue:account];
+}
+
++ (NSMutableDictionary *)newSearchDictionary:(NSString *)identifier {
+    NSMutableDictionary *searchDictionary = [[NSMutableDictionary alloc] init];
+    
+    [searchDictionary setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
+    
+    NSData *encodedIdentifier = [identifier dataUsingEncoding:NSUTF8StringEncoding];
+    [searchDictionary setObject:encodedIdentifier forKey:(__bridge id)kSecAttrGeneric];
+    [searchDictionary setObject:encodedIdentifier forKey:(__bridge id)kSecAttrAccount];
+    [searchDictionary setObject:serviceName forKey:(__bridge id)kSecAttrService];
+    
+    return searchDictionary;
+}
+
++ (NSData *)searchKeychainCopyMatching:(NSString *)identifier {
+    OSStatus status;
+    NSData *result = nil;
+    
+#if TARGET_OS_IPHONE
+    NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
+    
+    // Add search attributes
+    [searchDictionary setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
+    
+    // Add search return types
+    [searchDictionary setObject:(id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
+    
+    CFTypeRef ret;
+    status = SecItemCopyMatching((__bridge CFDictionaryRef)searchDictionary, &ret);
+
+    if (status != errSecSuccess) {
+        return  nil;
+    }
+    result = (__bridge_transfer NSData *)ret;
+    
+#elif TARGET_OS_MAC
+    UInt32 passwordLength = 0;
+    void *passwordData = NULL;
+    
+    status = SecKeychainFindGenericPassword(
+                                            NULL,
+                                            (UInt32)[serviceName length],
+                                            [serviceName UTF8String],
+                                            (UInt32)[identifier length],
+                                            [identifier UTF8String],
+                                            &passwordLength,
+                                            &passwordData,
+                                            NULL
+                                            );
+    if (status == errSecSuccess) {
+        if (passwordData && passwordLength > 0) {
+            result = [NSData dataWithBytes:passwordData length:passwordLength];
+            
+            SecKeychainItemFreeContent(NULL, passwordData);
+        }
+    }
+#endif
+    
+    return result;
+}
+
++ (BOOL)createKeychainValue:(NSString *)password forIdentifier:(NSString *)identifier {
+    OSStatus status;
+    
+#if TARGET_OS_IPHONE
+    NSMutableDictionary *dictionary = [self newSearchDictionary:identifier];
+    
+    NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
+    [dictionary setObject:passwordData forKey:(__bridge id)kSecValueData];
+    
+    status = SecItemAdd((__bridge CFDictionaryRef)dictionary, NULL);
+    
+#elif TARGET_OS_MAC    
+    status = SecKeychainAddGenericPassword(
+                                           NULL,
+                                           (UInt32)[serviceName length],
+                                           [serviceName UTF8String],
+                                           (UInt32)[identifier length],
+                                           [identifier UTF8String],
+                                           (UInt32)[password length],
+                                           [password UTF8String],
+                                           NULL
+                                           );
+#endif
+    
+    return status == errSecSuccess ? YES : NO;
+}
+
++ (BOOL)updateKeychainValue:(NSString *)password forIdentifier:(NSString *)identifier {
+    OSStatus status;
+    
+#if TARGET_OS_IPHONE
+    NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
+    NSMutableDictionary *updateDictionary = [[NSMutableDictionary alloc] init];
+    NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
+    [updateDictionary setObject:passwordData forKey:(__bridge id)kSecValueData];
+    
+    status = SecItemUpdate((__bridge CFDictionaryRef)searchDictionary,
+                           (__bridge CFDictionaryRef)updateDictionary);
+    
+#elif TARGET_OS_MAC
+    SecKeychainItemRef itemRef;
+    
+    status = SecKeychainFindGenericPassword(
+                                            NULL,
+                                            (UInt32)[serviceName length],
+                                            [serviceName UTF8String],
+                                            (UInt32)[identifier length],
+                                            [identifier UTF8String],
+                                            NULL,
+                                            NULL,
+                                            &itemRef
+                                            );
+    
+    if (status == errSecSuccess) {
+        if (itemRef) {
+            status = SecKeychainItemModifyAttributesAndData(
+                                                            itemRef,
+                                                            NULL,
+                                                            (UInt32)[password length],
+                                                            [password UTF8String]
+                                                            );
+            
+            CFRelease(itemRef);
+        } else {
+            return NO;
+        }
+    } else {
+        return NO;
+    }
+
+#endif
+    
+    return status == errSecSuccess ? YES : NO;
+}
+
++ (void)deleteKeychainValue:(NSString *)identifier {
+#if TARGET_OS_IPHONE
+    NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
+    SecItemDelete((__bridge CFDictionaryRef)searchDictionary);
+    
+#elif TARGET_OS_MAC
+    SecKeychainItemRef itemRef;
+    OSStatus status = SecKeychainFindGenericPassword(
+                                                     NULL,
+                                                     (UInt32)[serviceName length],
+                                                     [serviceName UTF8String],
+                                                     (UInt32)[identifier length],
+                                                     [identifier UTF8String],
+                                                     NULL,
+                                                     NULL,
+                                                     &itemRef
+                                                     );
+    if (status == errSecSuccess) {
+        if (itemRef) {
+            SecKeychainItemDelete(itemRef);
+            
+            CFRelease(itemRef);
+        }
+    }
+    
+#endif
+}
+
+@end
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/Keychain/ANKeychain.m.MotionPro
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/Keychain/ANKeychain.m.MotionPro	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/Keychain/ANKeychain.m.MotionPro	(working copy)
@@ -0,0 +1,241 @@
+//
+//  ANKeychain.m
+//  MobileNow
+//
+//  Created by Huangjl on 13-4-2.
+//  Copyright (c) 2013年 MobileNow. All rights reserved.
+//
+
+#import "ANKeychain.h"
+#include <Security/Security.h>
+
+static NSString *serviceName = @"net.arraynetworks.MotionPro";
+
+@interface ANKeychain()
++ (NSMutableDictionary *)newSearchDictionary:(NSString *)identifier;
++ (NSData *)searchKeychainCopyMatching:(NSString *)identifier;
++ (BOOL)createKeychainValue:(NSString *)password forIdentifier:(NSString *)identifier;
++ (BOOL)updateKeychainValue:(NSString *)password forIdentifier:(NSString *)identifier;
++ (void)deleteKeychainValue:(NSString *)identifier;
+@end
+
+@implementation ANKeychain
++ (NSString *)passwordForAccountKey:(NSString *)key
+{
+    NSData *passwordData = [self searchKeychainCopyMatching:key];
+    NSString *password;
+    if (passwordData) {
+        password = [[NSString alloc] initWithData:passwordData
+                                         encoding:NSUTF8StringEncoding];
+    }
+    
+    if (password.length != 0) {
+        return password;
+    }
+    return nil;
+}
+
++ (void)savePassword:(NSString *)password forAccoutKey:(NSString *)key
+{
+    if (password.length == 0 || key.length == 0) return;
+    
+    [self createKeychainValue:password forIdentifier:key];
+}
++ (void)deletePasswordForAccountKey:(NSString *)key
+{
+    [self deleteKeychainValue:key];
+}
+
+
++ (NSString *)passwordForUsername:(NSString *)uname host:(NSString *)host
+{
+    NSString *account = [NSString stringWithFormat:@"%@/%@",uname,host];
+    NSData *passwordData = [self searchKeychainCopyMatching:account];
+    NSString *password;
+    if (passwordData) {
+        password = [[NSString alloc] initWithData:passwordData
+                                                   encoding:NSUTF8StringEncoding];
+    }
+    
+    if (password.length != 0) {
+        return password;
+    } 
+    return nil;
+}
+
++ (void)savePassword:(NSString *)password forUsername:(NSString *)uname host:(NSString *)host
+{
+    NSString *account = [NSString stringWithFormat:@"%@/%@",uname,host];
+    [self createKeychainValue:password forIdentifier:account];
+}
+
++ (void)deletePasswordForUsername:(NSString *)uname host:(NSString *)host
+{
+    NSString *account = [NSString stringWithFormat:@"%@/%@",uname,host];
+    [self deleteKeychainValue:account];
+}
+
++ (NSMutableDictionary *)newSearchDictionary:(NSString *)identifier {
+    NSMutableDictionary *searchDictionary = [[NSMutableDictionary alloc] init];
+    
+    [searchDictionary setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
+    
+    NSData *encodedIdentifier = [identifier dataUsingEncoding:NSUTF8StringEncoding];
+    [searchDictionary setObject:encodedIdentifier forKey:(__bridge id)kSecAttrGeneric];
+    [searchDictionary setObject:encodedIdentifier forKey:(__bridge id)kSecAttrAccount];
+    [searchDictionary setObject:serviceName forKey:(__bridge id)kSecAttrService];
+    
+    return searchDictionary;
+}
+
++ (NSData *)searchKeychainCopyMatching:(NSString *)identifier {
+    OSStatus status;
+    NSData *result = nil;
+    
+#if TARGET_OS_IPHONE
+    NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
+    
+    // Add search attributes
+    [searchDictionary setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
+    
+    // Add search return types
+    [searchDictionary setObject:(id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
+    
+    CFTypeRef ret;
+    status = SecItemCopyMatching((__bridge CFDictionaryRef)searchDictionary, &ret);
+
+    if (status != errSecSuccess) {
+        return  nil;
+    }
+    result = (__bridge_transfer NSData *)ret;
+    
+#elif TARGET_OS_MAC
+    UInt32 passwordLength = 0;
+    void *passwordData = NULL;
+    
+    status = SecKeychainFindGenericPassword(
+                                            NULL,
+                                            (UInt32)[serviceName length],
+                                            [serviceName UTF8String],
+                                            (UInt32)[identifier length],
+                                            [identifier UTF8String],
+                                            &passwordLength,
+                                            &passwordData,
+                                            NULL
+                                            );
+    if (status == errSecSuccess) {
+        if (passwordData && passwordLength > 0) {
+            result = [NSData dataWithBytes:passwordData length:passwordLength];
+            
+            SecKeychainItemFreeContent(NULL, passwordData);
+        }
+    }
+#endif
+    
+    return result;
+}
+
++ (BOOL)createKeychainValue:(NSString *)password forIdentifier:(NSString *)identifier {
+    OSStatus status;
+    
+#if TARGET_OS_IPHONE
+    NSMutableDictionary *dictionary = [self newSearchDictionary:identifier];
+    
+    NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
+    [dictionary setObject:passwordData forKey:(__bridge id)kSecValueData];
+    
+    status = SecItemAdd((__bridge CFDictionaryRef)dictionary, NULL);
+    
+#elif TARGET_OS_MAC    
+    status = SecKeychainAddGenericPassword(
+                                           NULL,
+                                           (UInt32)[serviceName length],
+                                           [serviceName UTF8String],
+                                           (UInt32)[identifier length],
+                                           [identifier UTF8String],
+                                           (UInt32)[password length],
+                                           [password UTF8String],
+                                           NULL
+                                           );
+#endif
+    
+    return status == errSecSuccess ? YES : NO;
+}
+
++ (BOOL)updateKeychainValue:(NSString *)password forIdentifier:(NSString *)identifier {
+    OSStatus status;
+    
+#if TARGET_OS_IPHONE
+    NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
+    NSMutableDictionary *updateDictionary = [[NSMutableDictionary alloc] init];
+    NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
+    [updateDictionary setObject:passwordData forKey:(__bridge id)kSecValueData];
+    
+    status = SecItemUpdate((__bridge CFDictionaryRef)searchDictionary,
+                           (__bridge CFDictionaryRef)updateDictionary);
+    
+#elif TARGET_OS_MAC
+    SecKeychainItemRef itemRef;
+    
+    status = SecKeychainFindGenericPassword(
+                                            NULL,
+                                            (UInt32)[serviceName length],
+                                            [serviceName UTF8String],
+                                            (UInt32)[identifier length],
+                                            [identifier UTF8String],
+                                            NULL,
+                                            NULL,
+                                            &itemRef
+                                            );
+    
+    if (status == errSecSuccess) {
+        if (itemRef) {
+            status = SecKeychainItemModifyAttributesAndData(
+                                                            itemRef,
+                                                            NULL,
+                                                            (UInt32)[password length],
+                                                            [password UTF8String]
+                                                            );
+            
+            CFRelease(itemRef);
+        } else {
+            return NO;
+        }
+    } else {
+        return NO;
+    }
+
+#endif
+    
+    return status == errSecSuccess ? YES : NO;
+}
+
++ (void)deleteKeychainValue:(NSString *)identifier {
+#if TARGET_OS_IPHONE
+    NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
+    SecItemDelete((__bridge CFDictionaryRef)searchDictionary);
+    
+#elif TARGET_OS_MAC
+    SecKeychainItemRef itemRef;
+    OSStatus status = SecKeychainFindGenericPassword(
+                                                     NULL,
+                                                     (UInt32)[serviceName length],
+                                                     [serviceName UTF8String],
+                                                     (UInt32)[identifier length],
+                                                     [identifier UTF8String],
+                                                     NULL,
+                                                     NULL,
+                                                     &itemRef
+                                                     );
+    if (status == errSecSuccess) {
+        if (itemRef) {
+            SecKeychainItemDelete(itemRef);
+            
+            CFRelease(itemRef);
+        }
+    }
+    
+#endif
+}
+
+@end
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/Keychain/ANKeychain.m.MotionProEnterprise
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/Keychain/ANKeychain.m.MotionProEnterprise	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/Keychain/ANKeychain.m.MotionProEnterprise	(working copy)
@@ -0,0 +1,241 @@
+//
+//  ANKeychain.m
+//  MobileNow
+//
+//  Created by Huangjl on 13-4-2.
+//  Copyright (c) 2013年 MobileNow. All rights reserved.
+//
+
+#import "ANKeychain.h"
+#include <Security/Security.h>
+
+static NSString *serviceName = @"net.arraynetworks.MotionProPlusEnterprise";
+
+@interface ANKeychain()
++ (NSMutableDictionary *)newSearchDictionary:(NSString *)identifier;
++ (NSData *)searchKeychainCopyMatching:(NSString *)identifier;
++ (BOOL)createKeychainValue:(NSString *)password forIdentifier:(NSString *)identifier;
++ (BOOL)updateKeychainValue:(NSString *)password forIdentifier:(NSString *)identifier;
++ (void)deleteKeychainValue:(NSString *)identifier;
+@end
+
+@implementation ANKeychain
++ (NSString *)passwordForAccountKey:(NSString *)key
+{
+    NSData *passwordData = [self searchKeychainCopyMatching:key];
+    NSString *password;
+    if (passwordData) {
+        password = [[NSString alloc] initWithData:passwordData
+                                         encoding:NSUTF8StringEncoding];
+    }
+    
+    if (password.length != 0) {
+        return password;
+    }
+    return nil;
+}
+
++ (void)savePassword:(NSString *)password forAccoutKey:(NSString *)key
+{
+    if (password.length == 0 || key.length == 0) return;
+    
+    [self createKeychainValue:password forIdentifier:key];
+}
++ (void)deletePasswordForAccountKey:(NSString *)key
+{
+    [self deleteKeychainValue:key];
+}
+
+
++ (NSString *)passwordForUsername:(NSString *)uname host:(NSString *)host
+{
+    NSString *account = [NSString stringWithFormat:@"%@/%@",uname,host];
+    NSData *passwordData = [self searchKeychainCopyMatching:account];
+    NSString *password;
+    if (passwordData) {
+        password = [[NSString alloc] initWithData:passwordData
+                                                   encoding:NSUTF8StringEncoding];
+    }
+    
+    if (password.length != 0) {
+        return password;
+    } 
+    return nil;
+}
+
++ (void)savePassword:(NSString *)password forUsername:(NSString *)uname host:(NSString *)host
+{
+    NSString *account = [NSString stringWithFormat:@"%@/%@",uname,host];
+    [self createKeychainValue:password forIdentifier:account];
+}
+
++ (void)deletePasswordForUsername:(NSString *)uname host:(NSString *)host
+{
+    NSString *account = [NSString stringWithFormat:@"%@/%@",uname,host];
+    [self deleteKeychainValue:account];
+}
+
++ (NSMutableDictionary *)newSearchDictionary:(NSString *)identifier {
+    NSMutableDictionary *searchDictionary = [[NSMutableDictionary alloc] init];
+    
+    [searchDictionary setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
+    
+    NSData *encodedIdentifier = [identifier dataUsingEncoding:NSUTF8StringEncoding];
+    [searchDictionary setObject:encodedIdentifier forKey:(__bridge id)kSecAttrGeneric];
+    [searchDictionary setObject:encodedIdentifier forKey:(__bridge id)kSecAttrAccount];
+    [searchDictionary setObject:serviceName forKey:(__bridge id)kSecAttrService];
+    
+    return searchDictionary;
+}
+
++ (NSData *)searchKeychainCopyMatching:(NSString *)identifier {
+    OSStatus status;
+    NSData *result = nil;
+    
+#if TARGET_OS_IPHONE
+    NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
+    
+    // Add search attributes
+    [searchDictionary setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
+    
+    // Add search return types
+    [searchDictionary setObject:(id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
+    
+    CFTypeRef ret;
+    status = SecItemCopyMatching((__bridge CFDictionaryRef)searchDictionary, &ret);
+
+    if (status != errSecSuccess) {
+        return  nil;
+    }
+    result = (__bridge_transfer NSData *)ret;
+    
+#elif TARGET_OS_MAC
+    UInt32 passwordLength = 0;
+    void *passwordData = NULL;
+    
+    status = SecKeychainFindGenericPassword(
+                                            NULL,
+                                            (UInt32)[serviceName length],
+                                            [serviceName UTF8String],
+                                            (UInt32)[identifier length],
+                                            [identifier UTF8String],
+                                            &passwordLength,
+                                            &passwordData,
+                                            NULL
+                                            );
+    if (status == errSecSuccess) {
+        if (passwordData && passwordLength > 0) {
+            result = [NSData dataWithBytes:passwordData length:passwordLength];
+            
+            SecKeychainItemFreeContent(NULL, passwordData);
+        }
+    }
+#endif
+    
+    return result;
+}
+
++ (BOOL)createKeychainValue:(NSString *)password forIdentifier:(NSString *)identifier {
+    OSStatus status;
+    
+#if TARGET_OS_IPHONE
+    NSMutableDictionary *dictionary = [self newSearchDictionary:identifier];
+    
+    NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
+    [dictionary setObject:passwordData forKey:(__bridge id)kSecValueData];
+    
+    status = SecItemAdd((__bridge CFDictionaryRef)dictionary, NULL);
+    
+#elif TARGET_OS_MAC    
+    status = SecKeychainAddGenericPassword(
+                                           NULL,
+                                           (UInt32)[serviceName length],
+                                           [serviceName UTF8String],
+                                           (UInt32)[identifier length],
+                                           [identifier UTF8String],
+                                           (UInt32)[password length],
+                                           [password UTF8String],
+                                           NULL
+                                           );
+#endif
+    
+    return status == errSecSuccess ? YES : NO;
+}
+
++ (BOOL)updateKeychainValue:(NSString *)password forIdentifier:(NSString *)identifier {
+    OSStatus status;
+    
+#if TARGET_OS_IPHONE
+    NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
+    NSMutableDictionary *updateDictionary = [[NSMutableDictionary alloc] init];
+    NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
+    [updateDictionary setObject:passwordData forKey:(__bridge id)kSecValueData];
+    
+    status = SecItemUpdate((__bridge CFDictionaryRef)searchDictionary,
+                           (__bridge CFDictionaryRef)updateDictionary);
+    
+#elif TARGET_OS_MAC
+    SecKeychainItemRef itemRef;
+    
+    status = SecKeychainFindGenericPassword(
+                                            NULL,
+                                            (UInt32)[serviceName length],
+                                            [serviceName UTF8String],
+                                            (UInt32)[identifier length],
+                                            [identifier UTF8String],
+                                            NULL,
+                                            NULL,
+                                            &itemRef
+                                            );
+    
+    if (status == errSecSuccess) {
+        if (itemRef) {
+            status = SecKeychainItemModifyAttributesAndData(
+                                                            itemRef,
+                                                            NULL,
+                                                            (UInt32)[password length],
+                                                            [password UTF8String]
+                                                            );
+            
+            CFRelease(itemRef);
+        } else {
+            return NO;
+        }
+    } else {
+        return NO;
+    }
+
+#endif
+    
+    return status == errSecSuccess ? YES : NO;
+}
+
++ (void)deleteKeychainValue:(NSString *)identifier {
+#if TARGET_OS_IPHONE
+    NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
+    SecItemDelete((__bridge CFDictionaryRef)searchDictionary);
+    
+#elif TARGET_OS_MAC
+    SecKeychainItemRef itemRef;
+    OSStatus status = SecKeychainFindGenericPassword(
+                                                     NULL,
+                                                     (UInt32)[serviceName length],
+                                                     [serviceName UTF8String],
+                                                     (UInt32)[identifier length],
+                                                     [identifier UTF8String],
+                                                     NULL,
+                                                     NULL,
+                                                     &itemRef
+                                                     );
+    if (status == errSecSuccess) {
+        if (itemRef) {
+            SecKeychainItemDelete(itemRef);
+            
+            CFRelease(itemRef);
+        }
+    }
+    
+#endif
+}
+
+@end
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/Keychain/ANKeychain.m.zrt
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/Keychain/ANKeychain.m.zrt	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/Keychain/ANKeychain.m.zrt	(working copy)
@@ -0,0 +1,241 @@
+//
+//  ANKeychain.m
+//  MobileNow
+//
+//  Created by Huangjl on 13-4-2.
+//  Copyright (c) 2013年 MobileNow. All rights reserved.
+//
+
+#import "ANKeychain.h"
+#include <Security/Security.h>
+
+static NSString *serviceName = @"net.infosec.MotionProZRTEnterprise";
+
+@interface ANKeychain()
++ (NSMutableDictionary *)newSearchDictionary:(NSString *)identifier;
++ (NSData *)searchKeychainCopyMatching:(NSString *)identifier;
++ (BOOL)createKeychainValue:(NSString *)password forIdentifier:(NSString *)identifier;
++ (BOOL)updateKeychainValue:(NSString *)password forIdentifier:(NSString *)identifier;
++ (void)deleteKeychainValue:(NSString *)identifier;
+@end
+
+@implementation ANKeychain
++ (NSString *)passwordForAccountKey:(NSString *)key
+{
+    NSData *passwordData = [self searchKeychainCopyMatching:key];
+    NSString *password;
+    if (passwordData) {
+        password = [[NSString alloc] initWithData:passwordData
+                                         encoding:NSUTF8StringEncoding];
+    }
+    
+    if (password.length != 0) {
+        return password;
+    }
+    return nil;
+}
+
++ (void)savePassword:(NSString *)password forAccoutKey:(NSString *)key
+{
+    if (password.length == 0 || key.length == 0) return;
+    
+    [self createKeychainValue:password forIdentifier:key];
+}
++ (void)deletePasswordForAccountKey:(NSString *)key
+{
+    [self deleteKeychainValue:key];
+}
+
+
++ (NSString *)passwordForUsername:(NSString *)uname host:(NSString *)host
+{
+    NSString *account = [NSString stringWithFormat:@"%@/%@",uname,host];
+    NSData *passwordData = [self searchKeychainCopyMatching:account];
+    NSString *password;
+    if (passwordData) {
+        password = [[NSString alloc] initWithData:passwordData
+                                                   encoding:NSUTF8StringEncoding];
+    }
+    
+    if (password.length != 0) {
+        return password;
+    } 
+    return nil;
+}
+
++ (void)savePassword:(NSString *)password forUsername:(NSString *)uname host:(NSString *)host
+{
+    NSString *account = [NSString stringWithFormat:@"%@/%@",uname,host];
+    [self createKeychainValue:password forIdentifier:account];
+}
+
++ (void)deletePasswordForUsername:(NSString *)uname host:(NSString *)host
+{
+    NSString *account = [NSString stringWithFormat:@"%@/%@",uname,host];
+    [self deleteKeychainValue:account];
+}
+
++ (NSMutableDictionary *)newSearchDictionary:(NSString *)identifier {
+    NSMutableDictionary *searchDictionary = [[NSMutableDictionary alloc] init];
+    
+    [searchDictionary setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
+    
+    NSData *encodedIdentifier = [identifier dataUsingEncoding:NSUTF8StringEncoding];
+    [searchDictionary setObject:encodedIdentifier forKey:(__bridge id)kSecAttrGeneric];
+    [searchDictionary setObject:encodedIdentifier forKey:(__bridge id)kSecAttrAccount];
+    [searchDictionary setObject:serviceName forKey:(__bridge id)kSecAttrService];
+    
+    return searchDictionary;
+}
+
++ (NSData *)searchKeychainCopyMatching:(NSString *)identifier {
+    OSStatus status;
+    NSData *result = nil;
+    
+#if TARGET_OS_IPHONE
+    NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
+    
+    // Add search attributes
+    [searchDictionary setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
+    
+    // Add search return types
+    [searchDictionary setObject:(id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
+    
+    CFTypeRef ret;
+    status = SecItemCopyMatching((__bridge CFDictionaryRef)searchDictionary, &ret);
+
+    if (status != errSecSuccess) {
+        return  nil;
+    }
+    result = (__bridge_transfer NSData *)ret;
+    
+#elif TARGET_OS_MAC
+    UInt32 passwordLength = 0;
+    void *passwordData = NULL;
+    
+    status = SecKeychainFindGenericPassword(
+                                            NULL,
+                                            (UInt32)[serviceName length],
+                                            [serviceName UTF8String],
+                                            (UInt32)[identifier length],
+                                            [identifier UTF8String],
+                                            &passwordLength,
+                                            &passwordData,
+                                            NULL
+                                            );
+    if (status == errSecSuccess) {
+        if (passwordData && passwordLength > 0) {
+            result = [NSData dataWithBytes:passwordData length:passwordLength];
+            
+            SecKeychainItemFreeContent(NULL, passwordData);
+        }
+    }
+#endif
+    
+    return result;
+}
+
++ (BOOL)createKeychainValue:(NSString *)password forIdentifier:(NSString *)identifier {
+    OSStatus status;
+    
+#if TARGET_OS_IPHONE
+    NSMutableDictionary *dictionary = [self newSearchDictionary:identifier];
+    
+    NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
+    [dictionary setObject:passwordData forKey:(__bridge id)kSecValueData];
+    
+    status = SecItemAdd((__bridge CFDictionaryRef)dictionary, NULL);
+    
+#elif TARGET_OS_MAC    
+    status = SecKeychainAddGenericPassword(
+                                           NULL,
+                                           (UInt32)[serviceName length],
+                                           [serviceName UTF8String],
+                                           (UInt32)[identifier length],
+                                           [identifier UTF8String],
+                                           (UInt32)[password length],
+                                           [password UTF8String],
+                                           NULL
+                                           );
+#endif
+    
+    return status == errSecSuccess ? YES : NO;
+}
+
++ (BOOL)updateKeychainValue:(NSString *)password forIdentifier:(NSString *)identifier {
+    OSStatus status;
+    
+#if TARGET_OS_IPHONE
+    NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
+    NSMutableDictionary *updateDictionary = [[NSMutableDictionary alloc] init];
+    NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
+    [updateDictionary setObject:passwordData forKey:(__bridge id)kSecValueData];
+    
+    status = SecItemUpdate((__bridge CFDictionaryRef)searchDictionary,
+                           (__bridge CFDictionaryRef)updateDictionary);
+    
+#elif TARGET_OS_MAC
+    SecKeychainItemRef itemRef;
+    
+    status = SecKeychainFindGenericPassword(
+                                            NULL,
+                                            (UInt32)[serviceName length],
+                                            [serviceName UTF8String],
+                                            (UInt32)[identifier length],
+                                            [identifier UTF8String],
+                                            NULL,
+                                            NULL,
+                                            &itemRef
+                                            );
+    
+    if (status == errSecSuccess) {
+        if (itemRef) {
+            status = SecKeychainItemModifyAttributesAndData(
+                                                            itemRef,
+                                                            NULL,
+                                                            (UInt32)[password length],
+                                                            [password UTF8String]
+                                                            );
+            
+            CFRelease(itemRef);
+        } else {
+            return NO;
+        }
+    } else {
+        return NO;
+    }
+
+#endif
+    
+    return status == errSecSuccess ? YES : NO;
+}
+
++ (void)deleteKeychainValue:(NSString *)identifier {
+#if TARGET_OS_IPHONE
+    NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
+    SecItemDelete((__bridge CFDictionaryRef)searchDictionary);
+    
+#elif TARGET_OS_MAC
+    SecKeychainItemRef itemRef;
+    OSStatus status = SecKeychainFindGenericPassword(
+                                                     NULL,
+                                                     (UInt32)[serviceName length],
+                                                     [serviceName UTF8String],
+                                                     (UInt32)[identifier length],
+                                                     [identifier UTF8String],
+                                                     NULL,
+                                                     NULL,
+                                                     &itemRef
+                                                     );
+    if (status == errSecSuccess) {
+        if (itemRef) {
+            SecKeychainItemDelete(itemRef);
+            
+            CFRelease(itemRef);
+        }
+    }
+    
+#endif
+}
+
+@end
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/Keychain/UICKeyChainStore.h
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/Keychain/UICKeyChainStore.h	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/Keychain/UICKeyChainStore.h	(working copy)
@@ -0,0 +1,273 @@
+//
+//  UICKeyChainStore.h
+//  UICKeyChainStore
+//
+//  Created by Kishikawa Katsumi on 11/11/20.
+//  Copyright (c) 2011 Kishikawa Katsumi. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+#if !__has_feature(nullability)
+#define NS_ASSUME_NONNULL_BEGIN
+#define NS_ASSUME_NONNULL_END
+#define nullable
+#define nonnull
+#define null_unspecified
+#define null_resettable
+#define __nullable
+#define __nonnull
+#define __null_unspecified
+#endif
+
+NS_ASSUME_NONNULL_BEGIN
+
+extern NSString * const UICKeyChainStoreErrorDomain;
+
+typedef NS_ENUM(NSInteger, UICKeyChainStoreErrorCode) {
+    UICKeyChainStoreErrorInvalidArguments = 1,
+};
+
+typedef NS_ENUM(NSInteger, UICKeyChainStoreItemClass) {
+    UICKeyChainStoreItemClassGenericPassword = 1,
+    UICKeyChainStoreItemClassInternetPassword,
+};
+
+typedef NS_ENUM(NSInteger, UICKeyChainStoreProtocolType) {
+    UICKeyChainStoreProtocolTypeFTP = 1,
+    UICKeyChainStoreProtocolTypeFTPAccount,
+    UICKeyChainStoreProtocolTypeHTTP,
+    UICKeyChainStoreProtocolTypeIRC,
+    UICKeyChainStoreProtocolTypeNNTP,
+    UICKeyChainStoreProtocolTypePOP3,
+    UICKeyChainStoreProtocolTypeSMTP,
+    UICKeyChainStoreProtocolTypeSOCKS,
+    UICKeyChainStoreProtocolTypeIMAP,
+    UICKeyChainStoreProtocolTypeLDAP,
+    UICKeyChainStoreProtocolTypeAppleTalk,
+    UICKeyChainStoreProtocolTypeAFP,
+    UICKeyChainStoreProtocolTypeTelnet,
+    UICKeyChainStoreProtocolTypeSSH,
+    UICKeyChainStoreProtocolTypeFTPS,
+    UICKeyChainStoreProtocolTypeHTTPS,
+    UICKeyChainStoreProtocolTypeHTTPProxy,
+    UICKeyChainStoreProtocolTypeHTTPSProxy,
+    UICKeyChainStoreProtocolTypeFTPProxy,
+    UICKeyChainStoreProtocolTypeSMB,
+    UICKeyChainStoreProtocolTypeRTSP,
+    UICKeyChainStoreProtocolTypeRTSPProxy,
+    UICKeyChainStoreProtocolTypeDAAP,
+    UICKeyChainStoreProtocolTypeEPPC,
+    UICKeyChainStoreProtocolTypeNNTPS,
+    UICKeyChainStoreProtocolTypeLDAPS,
+    UICKeyChainStoreProtocolTypeTelnetS,
+    UICKeyChainStoreProtocolTypeIRCS,
+    UICKeyChainStoreProtocolTypePOP3S,
+};
+
+typedef NS_ENUM(NSInteger, UICKeyChainStoreAuthenticationType) {
+    UICKeyChainStoreAuthenticationTypeNTLM = 1,
+    UICKeyChainStoreAuthenticationTypeMSN,
+    UICKeyChainStoreAuthenticationTypeDPA,
+    UICKeyChainStoreAuthenticationTypeRPA,
+    UICKeyChainStoreAuthenticationTypeHTTPBasic,
+    UICKeyChainStoreAuthenticationTypeHTTPDigest,
+    UICKeyChainStoreAuthenticationTypeHTMLForm,
+    UICKeyChainStoreAuthenticationTypeDefault,
+};
+
+typedef NS_ENUM(NSInteger, UICKeyChainStoreAccessibility) {
+    UICKeyChainStoreAccessibilityWhenUnlocked = 1,
+    UICKeyChainStoreAccessibilityAfterFirstUnlock,
+    UICKeyChainStoreAccessibilityAlways,
+    UICKeyChainStoreAccessibilityWhenPasscodeSetThisDeviceOnly
+    __OSX_AVAILABLE_STARTING(__MAC_10_10, __IPHONE_8_0),
+    UICKeyChainStoreAccessibilityWhenUnlockedThisDeviceOnly,
+    UICKeyChainStoreAccessibilityAfterFirstUnlockThisDeviceOnly,
+    UICKeyChainStoreAccessibilityAlwaysThisDeviceOnly,
+}
+__OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_4_0);
+
+typedef NS_ENUM(NSInteger, UICKeyChainStoreAuthenticationPolicy) {
+    UICKeyChainStoreAuthenticationPolicyUserPresence = kSecAccessControlUserPresence,
+};
+
+@interface UICKeyChainStore : NSObject
+
+@property (nonatomic, readonly) UICKeyChainStoreItemClass itemClass;
+
+@property (nonatomic, readonly, nullable) NSString *service;
+@property (nonatomic, readonly, nullable) NSString *accessGroup;
+
+@property (nonatomic, readonly, nullable) NSURL *server;
+@property (nonatomic, readonly) UICKeyChainStoreProtocolType protocolType;
+@property (nonatomic, readonly) UICKeyChainStoreAuthenticationType authenticationType;
+
+@property (nonatomic) UICKeyChainStoreAccessibility accessibility;
+@property (nonatomic, readonly) UICKeyChainStoreAuthenticationPolicy authenticationPolicy
+__OSX_AVAILABLE_STARTING(__MAC_10_10, __IPHONE_8_0);
+
+@property (nonatomic) BOOL synchronizable;
+
+@property (nonatomic, nullable) NSString *authenticationPrompt
+__OSX_AVAILABLE_STARTING(__MAC_NA, __IPHONE_8_0);
+
+@property (nonatomic, readonly, nullable) NSArray *allKeys;
+@property (nonatomic, readonly, nullable) NSArray *allItems;
+
++ (NSString *)defaultService;
++ (void)setDefaultService:(NSString *)defaultService;
+
++ (UICKeyChainStore *)keyChainStore;
++ (UICKeyChainStore *)keyChainStoreWithService:(nullable NSString *)service;
++ (UICKeyChainStore *)keyChainStoreWithService:(nullable NSString *)service accessGroup:(nullable NSString *)accessGroup;
+
++ (UICKeyChainStore *)keyChainStoreWithServer:(NSURL *)server protocolType:(UICKeyChainStoreProtocolType)protocolType;
++ (UICKeyChainStore *)keyChainStoreWithServer:(NSURL *)server protocolType:(UICKeyChainStoreProtocolType)protocolType authenticationType:(UICKeyChainStoreAuthenticationType)authenticationType;
+
+- (instancetype)init;
+- (instancetype)initWithService:(nullable NSString *)service;
+- (instancetype)initWithService:(nullable NSString *)service accessGroup:(nullable NSString *)accessGroup;
+
+- (instancetype)initWithServer:(NSURL *)server protocolType:(UICKeyChainStoreProtocolType)protocolType;
+- (instancetype)initWithServer:(NSURL *)server protocolType:(UICKeyChainStoreProtocolType)protocolType authenticationType:(UICKeyChainStoreAuthenticationType)authenticationType;
+
++ (nullable NSString *)stringForKey:(NSString *)key;
++ (nullable NSString *)stringForKey:(NSString *)key service:(nullable NSString *)service;
++ (nullable NSString *)stringForKey:(NSString *)key service:(nullable NSString *)service accessGroup:(nullable NSString *)accessGroup;
++ (BOOL)setString:(nullable NSString *)value forKey:(NSString *)key;
++ (BOOL)setString:(nullable NSString *)value forKey:(NSString *)key service:(nullable NSString *)service;
++ (BOOL)setString:(nullable NSString *)value forKey:(NSString *)key service:(nullable NSString *)service accessGroup:(nullable NSString *)accessGroup;
+
++ (nullable NSData *)dataForKey:(NSString *)key;
++ (nullable NSData *)dataForKey:(NSString *)key service:(nullable NSString *)service;
++ (nullable NSData *)dataForKey:(NSString *)key service:(nullable NSString *)service accessGroup:(nullable NSString *)accessGroup;
++ (BOOL)setData:(nullable NSData *)data forKey:(NSString *)key;
++ (BOOL)setData:(nullable NSData *)data forKey:(NSString *)key service:(nullable NSString *)service;
++ (BOOL)setData:(nullable NSData *)data forKey:(NSString *)key service:(nullable NSString *)service accessGroup:(nullable NSString *)accessGroup;
+
+- (BOOL)contains:(nullable NSString *)key;
+
+- (BOOL)setString:(nullable NSString *)string forKey:(nullable NSString *)key;
+- (BOOL)setString:(nullable NSString *)string forKey:(nullable NSString *)key label:(nullable NSString *)label comment:(nullable NSString *)comment;
+- (nullable NSString *)stringForKey:(NSString *)key;
+
+- (BOOL)setData:(nullable NSData *)data forKey:(NSString *)key;
+- (BOOL)setData:(nullable NSData *)data forKey:(NSString *)key label:(nullable NSString *)label comment:(nullable NSString *)comment;
+- (nullable NSData *)dataForKey:(NSString *)key;
+
++ (BOOL)removeItemForKey:(NSString *)key;
++ (BOOL)removeItemForKey:(NSString *)key service:(nullable NSString *)service;
++ (BOOL)removeItemForKey:(NSString *)key service:(nullable NSString *)service accessGroup:(nullable NSString *)accessGroup;
+
++ (BOOL)removeAllItems;
++ (BOOL)removeAllItemsForService:(nullable NSString *)service;
++ (BOOL)removeAllItemsForService:(nullable NSString *)service accessGroup:(nullable NSString *)accessGroup;
+
+- (BOOL)removeItemForKey:(NSString *)key;
+
+- (BOOL)removeAllItems;
+
+- (nullable NSString *)objectForKeyedSubscript:(NSString<NSCopying> *)key;
+- (void)setObject:(nullable NSString *)obj forKeyedSubscript:(NSString<NSCopying> *)key;
+
++ (nullable NSArray *)allKeysWithItemClass:(UICKeyChainStoreItemClass)itemClass;
+- (nullable NSArray *)allKeys;
+
++ (nullable NSArray *)allItemsWithItemClass:(UICKeyChainStoreItemClass)itemClass;
+- (nullable NSArray *)allItems;
+
+- (void)setAccessibility:(UICKeyChainStoreAccessibility)accessibility authenticationPolicy:(UICKeyChainStoreAuthenticationPolicy)authenticationPolicy
+__OSX_AVAILABLE_STARTING(__MAC_10_10, __IPHONE_8_0);
+
+#if TARGET_OS_IPHONE
+- (void)sharedPasswordWithCompletion:(nullable void (^)(NSString * __nullable account, NSString * __nullable password, NSError * __nullable error))completion;
+- (void)sharedPasswordForAccount:(NSString *)account completion:(nullable void (^)(NSString * __nullable password, NSError * __nullable error))completion;
+
+- (void)setSharedPassword:(nullable NSString *)password forAccount:(NSString *)account completion:(nullable void (^)(NSError * __nullable error))completion;
+- (void)removeSharedPasswordForAccount:(NSString *)account completion:(nullable void (^)(NSError * __nullable error))completion;
+
++ (void)requestSharedWebCredentialWithCompletion:(nullable void (^)(NSArray * credentials, NSError * __nullable error))completion;
++ (void)requestSharedWebCredentialForDomain:(nullable NSString *)domain account:(nullable NSString *)account completion:(nullable void (^)(NSArray * credentials, NSError * __nullable error))completion;
+
++ (NSString *)generatePassword;
+#endif
+
+@end
+
+@interface UICKeyChainStore (ErrorHandling)
+
++ (nullable NSString *)stringForKey:(NSString *)key error:(NSError * __nullable __autoreleasing * __nullable)error;
++ (nullable NSString *)stringForKey:(NSString *)key service:(nullable NSString *)service error:(NSError * __nullable __autoreleasing * __nullable)error;
++ (nullable NSString *)stringForKey:(NSString *)key service:(nullable NSString *)service accessGroup:(nullable NSString *)accessGroup error:(NSError * __nullable __autoreleasing * __nullable)error;
+
++ (BOOL)setString:(nullable NSString *)value forKey:(NSString *)key error:(NSError * __nullable __autoreleasing * __nullable)error;
++ (BOOL)setString:(nullable NSString *)value forKey:(NSString *)key service:(nullable NSString *)service error:(NSError * __nullable __autoreleasing * __nullable)error;
++ (BOOL)setString:(nullable NSString *)value forKey:(NSString *)key service:(nullable NSString *)service accessGroup:(nullable NSString *)accessGroup error:(NSError * __nullable __autoreleasing * __nullable)error;
+
++ (nullable NSData *)dataForKey:(NSString *)key error:(NSError * __nullable __autoreleasing * __nullable)error;
++ (nullable NSData *)dataForKey:(NSString *)key service:(nullable NSString *)service error:(NSError * __nullable __autoreleasing * __nullable)error;
++ (nullable NSData *)dataForKey:(NSString *)key service:(nullable NSString *)service accessGroup:(nullable NSString *)accessGroup error:(NSError * __nullable __autoreleasing * __nullable)error;
+
++ (BOOL)setData:(nullable NSData *)data forKey:(NSString *)key error:(NSError * __nullable __autoreleasing * __nullable)error;
++ (BOOL)setData:(nullable NSData *)data forKey:(NSString *)key service:(nullable NSString *)service error:(NSError * __nullable __autoreleasing * __nullable)error;
++ (BOOL)setData:(nullable NSData *)data forKey:(NSString *)key service:(nullable NSString *)service accessGroup:(nullable NSString *)accessGroup error:(NSError * __nullable __autoreleasing * __nullable)error;
+
+- (BOOL)setString:(nullable NSString *)string forKey:(NSString * )key error:(NSError * __nullable __autoreleasing * __nullable)error;
+- (BOOL)setString:(nullable NSString *)string forKey:(NSString * )key label:(nullable NSString *)label comment:(nullable NSString *)comment error:(NSError * __nullable __autoreleasing * __nullable)error;
+
+- (BOOL)setData:(nullable NSData *)data forKey:(NSString *)key error:(NSError * __nullable __autoreleasing * __nullable)error;
+- (BOOL)setData:(nullable NSData *)data forKey:(NSString *)key label:(nullable NSString *)label comment:(nullable NSString *)comment error:(NSError * __nullable __autoreleasing * __nullable)error;
+
+- (nullable NSString *)stringForKey:(NSString *)key error:(NSError * __nullable __autoreleasing * __nullable)error;
+- (nullable NSData *)dataForKey:(NSString *)key error:(NSError * __nullable __autoreleasing * __nullable)error;
+
++ (BOOL)removeItemForKey:(NSString *)key error:(NSError * __nullable __autoreleasing * __nullable)error;
++ (BOOL)removeItemForKey:(NSString *)key service:(nullable NSString *)service error:(NSError * __nullable __autoreleasing * __nullable)error;
++ (BOOL)removeItemForKey:(NSString *)key service:(nullable NSString *)service accessGroup:(nullable NSString *)accessGroup error:(NSError * __nullable __autoreleasing * __nullable)error;
+
++ (BOOL)removeAllItemsWithError:(NSError * __nullable __autoreleasing * __nullable)error;
++ (BOOL)removeAllItemsForService:(nullable NSString *)service error:(NSError * __nullable __autoreleasing * __nullable)error;
++ (BOOL)removeAllItemsForService:(nullable NSString *)service accessGroup:(nullable NSString *)accessGroup error:(NSError * __nullable __autoreleasing * __nullable)error;
+
+- (BOOL)removeItemForKey:(NSString *)key error:(NSError * __nullable __autoreleasing * __nullable)error;
+- (BOOL)removeAllItemsWithError:(NSError * __nullable __autoreleasing * __nullable)error;
+
+@end
+
+@interface UICKeyChainStore (ForwardCompatibility)
+
++ (BOOL)setString:(nullable NSString *)value forKey:(NSString *)key genericAttribute:(nullable id)genericAttribute;
++ (BOOL)setString:(nullable NSString *)value forKey:(NSString *)key genericAttribute:(nullable id)genericAttribute error:(NSError * __nullable __autoreleasing * __nullable)error;
+
++ (BOOL)setString:(nullable NSString *)value forKey:(NSString *)key service:(nullable NSString *)service genericAttribute:(nullable id)genericAttribute;
++ (BOOL)setString:(nullable NSString *)value forKey:(NSString *)key service:(nullable NSString *)service genericAttribute:(nullable id)genericAttribute error:(NSError * __nullable __autoreleasing * __nullable)error;
+
++ (BOOL)setString:(nullable NSString *)value forKey:(NSString *)key service:(nullable NSString *)service accessGroup:(nullable NSString *)accessGroup genericAttribute:(nullable id)genericAttribute;
++ (BOOL)setString:(nullable NSString *)value forKey:(NSString *)key service:(nullable NSString *)service accessGroup:(nullable NSString *)accessGroup genericAttribute:(nullable id)genericAttribute error:(NSError * __nullable __autoreleasing * __nullable)error;
+
++ (BOOL)setData:(nullable NSData *)data forKey:(NSString *)key genericAttribute:(nullable id)genericAttribute;
++ (BOOL)setData:(nullable NSData *)data forKey:(NSString *)key genericAttribute:(nullable id)genericAttribute error:(NSError * __nullable __autoreleasing * __nullable)error;
+
++ (BOOL)setData:(nullable NSData *)data forKey:(NSString *)key service:(nullable NSString *)service genericAttribute:(nullable id)genericAttribute;
++ (BOOL)setData:(nullable NSData *)data forKey:(NSString *)key service:(nullable NSString *)service genericAttribute:(nullable id)genericAttribute error:(NSError * __nullable __autoreleasing * __nullable)error;
+
++ (BOOL)setData:(nullable NSData *)data forKey:(NSString *)key service:(nullable NSString *)service accessGroup:(nullable NSString *)accessGroup genericAttribute:(nullable id)genericAttribute;
++ (BOOL)setData:(nullable NSData *)data forKey:(NSString *)key service:(nullable NSString *)service accessGroup:(nullable NSString *)accessGroup genericAttribute:(nullable id)genericAttribute error:(NSError * __nullable __autoreleasing * __nullable)error;
+
+- (BOOL)setString:(nullable NSString *)string forKey:(NSString *)key genericAttribute:(nullable id)genericAttribute;
+- (BOOL)setString:(nullable NSString *)string forKey:(NSString *)key genericAttribute:(nullable id)genericAttribute error:(NSError * __nullable __autoreleasing * __nullable)error;
+
+- (BOOL)setData:(nullable NSData *)data forKey:(NSString *)key genericAttribute:(nullable id)genericAttribute;
+- (BOOL)setData:(nullable NSData *)data forKey:(NSString *)key genericAttribute:(nullable id)genericAttribute error:(NSError * __nullable __autoreleasing * __nullable)error;
+
+@end
+
+@interface UICKeyChainStore (Deprecation)
+
+- (void)synchronize __attribute__((deprecated("calling this method is no longer required")));
+- (BOOL)synchronizeWithError:(NSError * __nullable __autoreleasing * __nullable)error __attribute__((deprecated("calling this method is no longer required")));
+
+@end
+
+NS_ASSUME_NONNULL_END
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/Keychain/UICKeyChainStore.m
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/Keychain/UICKeyChainStore.m	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/Keychain/UICKeyChainStore.m	(working copy)
@@ -0,0 +1,1356 @@
+//
+//  UICKeyChainStore.m
+//  UICKeyChainStore
+//
+//  Created by Kishikawa Katsumi on 11/11/20.
+//  Copyright (c) 2011 Kishikawa Katsumi. All rights reserved.
+//
+
+#import "UICKeyChainStore.h"
+
+NSString * const UICKeyChainStoreErrorDomain = @"net.arraynetworks.uickeychainstore";
+static NSString *_defaultService;
+
+@interface UICKeyChainStore ()
+
+@end
+
+@implementation UICKeyChainStore
+
++ (NSString *)defaultService
+{
+    if (!_defaultService) {
+        //_defaultService = [[NSBundle mainBundle] bundleIdentifier] ?: @"";
+        _defaultService = @"net.arraynetworks.MPP";
+    }
+    
+    return _defaultService;
+}
+
++ (void)setDefaultService:(NSString *)defaultService
+{
+    _defaultService = defaultService;
+}
+
+#pragma mark -
+
++ (UICKeyChainStore *)keyChainStore
+{
+    return [[self alloc] initWithService:nil accessGroup:nil];
+}
+
++ (UICKeyChainStore *)keyChainStoreWithService:(NSString *)service
+{
+    return [[self alloc] initWithService:service accessGroup:nil];
+}
+
++ (UICKeyChainStore *)keyChainStoreWithService:(NSString *)service accessGroup:(NSString *)accessGroup
+{
+    return [[self alloc] initWithService:service accessGroup:accessGroup];
+}
+
+#pragma mark -
+
++ (UICKeyChainStore *)keyChainStoreWithServer:(NSURL *)server protocolType:(UICKeyChainStoreProtocolType)protocolType
+{
+    return [[self alloc] initWithServer:server protocolType:protocolType authenticationType:UICKeyChainStoreAuthenticationTypeDefault];
+}
+
++ (UICKeyChainStore *)keyChainStoreWithServer:(NSURL *)server protocolType:(UICKeyChainStoreProtocolType)protocolType authenticationType:(UICKeyChainStoreAuthenticationType)authenticationType
+{
+    return [[self alloc] initWithServer:server protocolType:protocolType authenticationType:authenticationType];
+}
+
+#pragma mark -
+
+- (instancetype)init
+{
+    return [self initWithService:[self.class defaultService] accessGroup:nil];
+}
+
+- (instancetype)initWithService:(NSString *)service
+{
+    return [self initWithService:service accessGroup:nil];
+}
+
+- (instancetype)initWithService:(NSString *)service accessGroup:(NSString *)accessGroup
+{
+    self = [super init];
+    if (self) {
+        _itemClass = UICKeyChainStoreItemClassGenericPassword;
+        
+        if (!service) {
+            service = [self.class defaultService];
+        }
+        _service = service.copy;
+        _accessGroup = accessGroup.copy;
+        [self commonInit];
+    }
+    
+    return self;
+}
+
+#pragma mark -
+
+- (instancetype)initWithServer:(NSURL *)server protocolType:(UICKeyChainStoreProtocolType)protocolType
+{
+    return [self initWithServer:server protocolType:protocolType authenticationType:UICKeyChainStoreAuthenticationTypeDefault];
+}
+
+- (instancetype)initWithServer:(NSURL *)server protocolType:(UICKeyChainStoreProtocolType)protocolType authenticationType:(UICKeyChainStoreAuthenticationType)authenticationType
+{
+    self = [super init];
+    if (self) {
+        _itemClass = UICKeyChainStoreItemClassInternetPassword;
+        
+        _server = server.copy;
+        _protocolType = protocolType;
+        _authenticationType = authenticationType;
+        
+        [self commonInit];
+    }
+    
+    return self;
+}
+
+#pragma mark -
+
+- (void)commonInit
+{
+    _accessibility = UICKeyChainStoreAccessibilityAfterFirstUnlock;
+}
+
+#pragma mark -
+
++ (NSString *)stringForKey:(NSString *)key
+{
+    return [self stringForKey:key service:nil accessGroup:nil error:nil];
+}
+
++ (NSString *)stringForKey:(NSString *)key error:(NSError *__autoreleasing *)error
+{
+    return [self stringForKey:key service:nil accessGroup:nil error:error];
+}
+
++ (NSString *)stringForKey:(NSString *)key service:(NSString *)service
+{
+    return [self stringForKey:key service:service accessGroup:nil error:nil];
+}
+
++ (NSString *)stringForKey:(NSString *)key service:(NSString *)service error:(NSError *__autoreleasing *)error
+{
+    return [self stringForKey:key service:service accessGroup:nil error:error];
+}
+
++ (NSString *)stringForKey:(NSString *)key service:(NSString *)service accessGroup:(NSString *)accessGroup
+{
+    return [self stringForKey:key service:service accessGroup:accessGroup error:nil];
+}
+
++ (NSString *)stringForKey:(NSString *)key service:(NSString *)service accessGroup:(NSString *)accessGroup error:(NSError *__autoreleasing *)error
+{
+    if (!key) {
+        NSError *e = [self argumentError:NSLocalizedString(@"the key must not to be nil", nil)];
+        if (error) {
+            *error = e;
+        }
+        return nil;
+    }
+    if (!service) {
+        service = [self defaultService];
+    }
+    
+    UICKeyChainStore *keychain = [UICKeyChainStore keyChainStoreWithService:service accessGroup:accessGroup];
+    return [keychain stringForKey:key error:error];
+}
+
+#pragma mark -
+
++ (BOOL)setString:(NSString *)value forKey:(NSString *)key
+{
+    return [self setString:value forKey:key service:nil accessGroup:nil genericAttribute:nil error:nil];
+}
+
++ (BOOL)setString:(NSString *)value forKey:(NSString *)key error:(NSError *__autoreleasing *)error
+{
+    return [self setString:value forKey:key service:nil accessGroup:nil genericAttribute:nil error:error];
+}
+
++ (BOOL)setString:(NSString *)value forKey:(NSString *)key genericAttribute:(id)genericAttribute
+{
+    return [self setString:value forKey:key service:nil accessGroup:nil genericAttribute:genericAttribute error:nil];
+}
+
++ (BOOL)setString:(NSString *)value forKey:(NSString *)key genericAttribute:(id)genericAttribute error:(NSError * __autoreleasing *)error
+{
+    return [self setString:value forKey:key service:nil accessGroup:nil genericAttribute:genericAttribute error:error];
+}
+
++ (BOOL)setString:(NSString *)value forKey:(NSString *)key service:(NSString *)service
+{
+    return [self setString:value forKey:key service:service accessGroup:nil genericAttribute:nil error:nil];
+}
+
++ (BOOL)setString:(NSString *)value forKey:(NSString *)key service:(NSString *)service error:(NSError *__autoreleasing *)error
+{
+    return [self setString:value forKey:key service:service accessGroup:nil genericAttribute:nil error:error];
+}
+
++ (BOOL)setString:(NSString *)value forKey:(NSString *)key service:(NSString *)service genericAttribute:(id)genericAttribute
+{
+    return [self setString:value forKey:key service:service accessGroup:nil genericAttribute:genericAttribute error:nil];
+}
+
++ (BOOL)setString:(NSString *)value forKey:(NSString *)key service:(NSString *)service genericAttribute:(id)genericAttribute error:(NSError * __autoreleasing *)error
+{
+    return [self setString:value forKey:key service:service accessGroup:nil genericAttribute:genericAttribute error:error];
+}
+
++ (BOOL)setString:(NSString *)value forKey:(NSString *)key service:(NSString *)service accessGroup:(NSString *)accessGroup
+{
+    return [self setString:value forKey:key service:service accessGroup:accessGroup genericAttribute:nil error:nil];
+}
+
++ (BOOL)setString:(NSString *)value forKey:(NSString *)key service:(NSString *)service accessGroup:(NSString *)accessGroup error:(NSError *__autoreleasing *)error
+{
+    return [self setString:value forKey:key service:service accessGroup:accessGroup genericAttribute:nil error:error];
+}
+
++ (BOOL)setString:(NSString *)value forKey:(NSString *)key service:(NSString *)service accessGroup:(NSString *)accessGroup genericAttribute:(id)genericAttribute
+{
+    return [self setString:value forKey:key service:service accessGroup:accessGroup genericAttribute:genericAttribute error:nil];
+}
+
++ (BOOL)setString:(NSString *)value forKey:(NSString *)key service:(NSString *)service accessGroup:(NSString *)accessGroup genericAttribute:(id)genericAttribute error:(NSError * __autoreleasing *)error
+{
+    if (!value) {
+        return [self removeItemForKey:key service:service accessGroup:accessGroup error:error];
+    }
+    NSData *data = [value dataUsingEncoding:NSUTF8StringEncoding];
+    if (data) {
+        return [self setData:data forKey:key service:service accessGroup:accessGroup genericAttribute:genericAttribute error:error];
+    }
+    NSError *e = [self conversionError:NSLocalizedString(@"failed to convert string to data", nil)];
+    if (error) {
+        *error = e;
+    }
+    return NO;
+}
+
+#pragma mark -
+
++ (NSData *)dataForKey:(NSString *)key
+{
+    return [self dataForKey:key service:nil accessGroup:nil error:nil];
+}
+
++ (NSData *)dataForKey:(NSString *)key error:(NSError *__autoreleasing *)error
+{
+    return [self dataForKey:key service:nil accessGroup:nil error:error];
+}
+
++ (NSData *)dataForKey:(NSString *)key service:(NSString *)service
+{
+    return [self dataForKey:key service:service accessGroup:nil error:nil];
+}
+
++ (NSData *)dataForKey:(NSString *)key service:(NSString *)service error:(NSError *__autoreleasing *)error
+{
+    return [self dataForKey:key service:service accessGroup:nil error:error];
+}
+
++ (NSData *)dataForKey:(NSString *)key service:(NSString *)service accessGroup:(NSString *)accessGroup
+{
+    return [self dataForKey:key service:service accessGroup:accessGroup error:nil];
+}
+
++ (NSData *)dataForKey:(NSString *)key service:(NSString *)service accessGroup:(NSString *)accessGroup error:(NSError *__autoreleasing *)error
+{
+    if (!key) {
+        NSError *e = [self argumentError:NSLocalizedString(@"the key must not to be nil", nil)];
+        if (error) {
+            *error = e;
+        }
+        return nil;
+    }
+    if (!service) {
+        service = [self defaultService];
+    }
+    
+    UICKeyChainStore *keychain = [UICKeyChainStore keyChainStoreWithService:service accessGroup:accessGroup];
+    return [keychain dataForKey:key error:error];
+}
+
+#pragma mark -
+
++ (BOOL)setData:(NSData *)data forKey:(NSString *)key
+{
+    return [self setData:data forKey:key service:nil accessGroup:nil genericAttribute:nil error:nil];
+}
+
++ (BOOL)setData:(NSData *)data forKey:(NSString *)key error:(NSError *__autoreleasing *)error
+{
+    return [self setData:data forKey:key service:nil accessGroup:nil genericAttribute:nil error:error];
+}
+
++ (BOOL)setData:(NSData *)data forKey:(NSString *)key genericAttribute:(id)genericAttribute
+{
+    return [self setData:data forKey:key service:nil accessGroup:nil genericAttribute:genericAttribute error:nil];
+}
+
++ (BOOL)setData:(NSData *)data forKey:(NSString *)key genericAttribute:(id)genericAttribute error:(NSError * __autoreleasing *)error
+{
+    return [self setData:data forKey:key service:nil accessGroup:nil genericAttribute:genericAttribute error:error];
+}
+
++ (BOOL)setData:(NSData *)data forKey:(NSString *)key service:(NSString *)service
+{
+    return [self setData:data forKey:key service:service accessGroup:nil genericAttribute:nil error:nil];
+}
+
++ (BOOL)setData:(NSData *)data forKey:(NSString *)key service:(NSString *)service error:(NSError *__autoreleasing *)error
+{
+    return [self setData:data forKey:key service:service accessGroup:nil genericAttribute:nil error:error];
+}
+
++ (BOOL)setData:(NSData *)data forKey:(NSString *)key service:(NSString *)service genericAttribute:(id)genericAttribute
+{
+    return [self setData:data forKey:key service:service accessGroup:nil genericAttribute:genericAttribute error:nil];
+}
+
++ (BOOL)setData:(NSData *)data forKey:(NSString *)key service:(NSString *)service genericAttribute:(id)genericAttribute error:(NSError * __autoreleasing *)error
+{
+    return [self setData:data forKey:key service:service accessGroup:nil genericAttribute:genericAttribute error:error];
+}
+
++ (BOOL)setData:(NSData *)data forKey:(NSString *)key service:(NSString *)service accessGroup:(NSString *)accessGroup
+{
+    return [self setData:data forKey:key service:service accessGroup:accessGroup genericAttribute:nil error:nil];
+}
+
++ (BOOL)setData:(NSData *)data forKey:(NSString *)key service:(NSString *)service accessGroup:(NSString *)accessGroup error:(NSError *__autoreleasing *)error
+{
+    return [self setData:data forKey:key service:service accessGroup:accessGroup genericAttribute:nil error:error];
+}
+
++ (BOOL)setData:(NSData *)data forKey:(NSString *)key service:(NSString *)service accessGroup:(NSString *)accessGroup genericAttribute:(id)genericAttribute
+{
+    return [self setData:data forKey:key service:service accessGroup:accessGroup genericAttribute:genericAttribute error:nil];
+}
+
++ (BOOL)setData:(NSData *)data forKey:(NSString *)key service:(NSString *)service accessGroup:(NSString *)accessGroup genericAttribute:(id)genericAttribute error:(NSError * __autoreleasing *)error
+{
+    if (!key) {
+        NSError *e = [self argumentError:NSLocalizedString(@"the key must not to be nil", nil)];
+        if (error) {
+            *error = e;
+        }
+        return NO;
+    }
+    if (!service) {
+        service = [self defaultService];
+    }
+    
+    UICKeyChainStore *keychain = [UICKeyChainStore keyChainStoreWithService:service accessGroup:accessGroup];
+    return [keychain setData:data forKey:key genericAttribute:genericAttribute];
+}
+
+#pragma mark -
+
+- (BOOL)contains:(NSString *)key
+{
+    NSMutableDictionary *query = [self query];
+    query[(__bridge __strong id)kSecAttrAccount] = key;
+    
+    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL);
+    return status == errSecSuccess;
+}
+
+#pragma mark -
+
+- (NSString *)stringForKey:(id)key
+{
+    return [self stringForKey:key error:nil];
+}
+
+- (NSString *)stringForKey:(id)key error:(NSError *__autoreleasing *)error
+{
+    NSData *data = [self dataForKey:key error:error];
+    if (data) {
+        NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+        if (string) {
+            return string;
+        }
+        NSError *e = [self.class conversionError:NSLocalizedString(@"failed to convert data to string", nil)];
+        if (error) {
+            *error = e;
+        }
+        return nil;
+    }
+    
+    return nil;
+}
+
+#pragma mark -
+
+- (BOOL)setString:(NSString *)string forKey:(NSString *)key
+{
+    return [self setString:string forKey:key genericAttribute:nil label:nil comment:nil error:nil];
+}
+
+- (BOOL)setString:(NSString *)string forKey:(NSString *)key error:(NSError *__autoreleasing *)error
+{
+    return [self setString:string forKey:key genericAttribute:nil label:nil comment:nil error:error];
+}
+
+- (BOOL)setString:(NSString *)string forKey:(NSString *)key genericAttribute:(id)genericAttribute
+{
+    return [self setString:string forKey:key genericAttribute:genericAttribute label:nil comment:nil error:nil];
+}
+
+- (BOOL)setString:(NSString *)string forKey:(NSString *)key genericAttribute:(id)genericAttribute error:(NSError * __autoreleasing *)error
+{
+    return [self setString:string forKey:key genericAttribute:genericAttribute label:nil comment:nil error:error];
+}
+
+- (BOOL)setString:(NSString *)string forKey:(NSString *)key label:(NSString *)label comment:(NSString *)comment
+{
+    return [self setString:string forKey:key genericAttribute:nil label:label comment:comment error:nil];
+}
+
+- (BOOL)setString:(NSString *)string forKey:(NSString *)key label:(NSString *)label comment:(NSString *)comment error:(NSError *__autoreleasing *)error
+{
+    return [self setString:string forKey:key genericAttribute:nil label:label comment:comment error:error];
+}
+
+- (BOOL)setString:(NSString *)string forKey:(NSString *)key genericAttribute:(id)genericAttribute label:(NSString *)label comment:(NSString *)comment error:(NSError *__autoreleasing *)error
+{
+    if (!string) {
+        return [self removeItemForKey:key error:error];
+    }
+    NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
+    if (data) {
+        return [self setData:data forKey:key genericAttribute:genericAttribute label:label comment:comment error:error];
+    }
+    NSError *e = [self.class conversionError:NSLocalizedString(@"failed to convert string to data", nil)];
+    if (error) {
+        *error = e;
+    }
+    return NO;
+}
+
+#pragma mark -
+
+- (NSData *)dataForKey:(NSString *)key
+{
+    return [self dataForKey:key error:nil];
+}
+
+- (NSData *)dataForKey:(NSString *)key error:(NSError *__autoreleasing *)error
+{
+    NSMutableDictionary *query = [self query];
+    query[(__bridge __strong id)kSecMatchLimit] = (__bridge id)kSecMatchLimitOne;
+    query[(__bridge __strong id)kSecReturnData] = (__bridge id)kCFBooleanTrue;
+    
+    query[(__bridge __strong id)kSecAttrAccount] = key;
+    
+    CFTypeRef data = nil;
+    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &data);
+    
+    if (status == errSecSuccess) {
+        NSData *ret = [NSData dataWithData:(__bridge NSData *)data];
+        if (data) {
+            CFRelease(data);
+            return ret;
+        } else {
+            NSError *e = [self.class unexpectedError:NSLocalizedString(@"Unexpected error has occurred.", nil)];
+            if (error) {
+                *error = e;
+            }
+            return nil;
+        }
+    } else if (status == errSecItemNotFound) {
+        return nil;
+    }
+    
+    NSError *e = [self.class securityError:status];
+    if (error) {
+        *error = e;
+    }
+    return nil;
+}
+
+#pragma mark -
+
+- (BOOL)setData:(NSData *)data forKey:(NSString *)key
+{
+    return [self setData:data forKey:key genericAttribute:nil label:nil comment:nil error:nil];
+}
+
+- (BOOL)setData:(NSData *)data forKey:(NSString *)key error:(NSError *__autoreleasing *)error
+{
+    return [self setData:data forKey:key genericAttribute:nil label:nil comment:nil error:error];
+}
+
+- (BOOL)setData:(NSData *)data forKey:(NSString *)key genericAttribute:(id)genericAttribute
+{
+    return [self setData:data forKey:key genericAttribute:genericAttribute label:nil comment:nil error:nil];
+}
+
+- (BOOL)setData:(NSData *)data forKey:(NSString *)key genericAttribute:(id)genericAttribute error:(NSError * __autoreleasing *)error
+{
+    return [self setData:data forKey:key genericAttribute:genericAttribute label:nil comment:nil error:error];
+}
+
+- (BOOL)setData:(NSData *)data forKey:(NSString *)key label:(NSString *)label comment:(NSString *)comment
+{
+    return [self setData:data forKey:key genericAttribute:nil label:label comment:comment error:nil];
+}
+
+- (BOOL)setData:(NSData *)data forKey:(NSString *)key label:(NSString *)label comment:(NSString *)comment error:(NSError *__autoreleasing *)error
+{
+    return [self setData:data forKey:key genericAttribute:nil label:label comment:comment error:error];
+}
+
+- (BOOL)setData:(NSData *)data forKey:(NSString *)key genericAttribute:(id)genericAttribute label:(NSString *)label comment:(NSString *)comment error:(NSError *__autoreleasing *)error
+{
+    if (!key) {
+        NSError *e = [self.class argumentError:NSLocalizedString(@"the key must not to be nil", nil)];
+        if (error) {
+            *error = e;
+        }
+        return NO;
+    }
+    if (!data) {
+        return [self removeItemForKey:key error:error];
+    }
+    
+    NSMutableDictionary *query = [self query];
+    query[(__bridge __strong id)kSecAttrAccount] = key;
+
+    if (floor(NSFoundationVersionNumber) > floor(1047.25)) { // iOS 8+
+        query[(__bridge __strong id)kSecUseNoAuthenticationUI] = (__bridge id)kCFBooleanTrue;
+    }
+    
+    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL);
+    if (status == errSecSuccess || status == errSecInteractionNotAllowed) {
+        query = [self query];
+        query[(__bridge __strong id)kSecAttrAccount] = key;
+        
+        NSError *unexpectedError = nil;
+        NSMutableDictionary *attributes = [self attributesWithKey:nil value:data error:&unexpectedError];
+        
+        if (genericAttribute) {
+            attributes[(__bridge __strong id)kSecAttrGeneric] = genericAttribute;
+        }
+        if (label) {
+            attributes[(__bridge __strong id)kSecAttrLabel] = label;
+        }
+        if (comment) {
+            attributes[(__bridge __strong id)kSecAttrComment] = comment;
+        }
+        
+        if (unexpectedError) {
+            NSLog(@"error: [%@] %@", @(unexpectedError.code), NSLocalizedString(@"Unexpected error has occurred.", nil));
+            if (error) {
+                *error = unexpectedError;
+            }
+            return NO;
+        } else {
+            
+            if (status == errSecInteractionNotAllowed && floor(NSFoundationVersionNumber) <= floor(1140.11)) { // iOS 8.0.x
+                if ([self removeItemForKey:key error:error]) {
+                    return [self setData:data forKey:key label:label comment:comment error:error];
+                }
+            } else {
+                status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)attributes);
+            }
+            if (status != errSecSuccess) {
+                NSError *e = [self.class securityError:status];
+                if (error) {
+                    *error = e;
+                }
+                return NO;
+            }
+        }
+    } else if (status == errSecItemNotFound) {
+        NSError *unexpectedError = nil;
+        NSMutableDictionary *attributes = [self attributesWithKey:key value:data error:&unexpectedError];
+        
+        if (genericAttribute) {
+            attributes[(__bridge __strong id)kSecAttrGeneric] = genericAttribute;
+        }
+        if (label) {
+            attributes[(__bridge __strong id)kSecAttrLabel] = label;
+        }
+        if (comment) {
+            attributes[(__bridge __strong id)kSecAttrComment] = comment;
+        }
+        
+        if (unexpectedError) {
+            NSLog(@"error: [%@] %@", @(unexpectedError.code), NSLocalizedString(@"Unexpected error has occurred.", nil));
+            if (error) {
+                *error = unexpectedError;
+            }
+            return NO;
+        } else {
+            status = SecItemAdd((__bridge CFDictionaryRef)attributes, NULL);
+            if (status != errSecSuccess) {
+                NSError *e = [self.class securityError:status];
+                if (error) {
+                    *error = e;
+                }
+                return NO;
+            }
+        }
+    } else {
+        NSError *e = [self.class securityError:status];
+        if (error) {
+            *error = e;
+        }
+        return NO;
+    }
+    
+    return YES;
+}
+
+#pragma mark -
+
++ (BOOL)removeItemForKey:(NSString *)key
+{
+    return [self removeItemForKey:key service:nil accessGroup:nil error:nil];
+}
+
++ (BOOL)removeItemForKey:(NSString *)key error:(NSError *__autoreleasing *)error
+{
+    return [self removeItemForKey:key service:nil accessGroup:nil error:error];
+}
+
++ (BOOL)removeItemForKey:(NSString *)key service:(NSString *)service
+{
+    return [self removeItemForKey:key service:service accessGroup:nil error:nil];
+}
+
++ (BOOL)removeItemForKey:(NSString *)key service:(NSString *)service error:(NSError *__autoreleasing *)error
+{
+    return [self removeItemForKey:key service:service accessGroup:nil error:error];
+}
+
++ (BOOL)removeItemForKey:(NSString *)key service:(NSString *)service accessGroup:(NSString *)accessGroup
+{
+    return [self removeItemForKey:key service:service accessGroup:accessGroup error:nil];
+}
+
++ (BOOL)removeItemForKey:(NSString *)key service:(NSString *)service accessGroup:(NSString *)accessGroup error:(NSError *__autoreleasing *)error
+{
+    if (!key) {
+        NSError *e = [self.class argumentError:NSLocalizedString(@"the key must not to be nil", nil)];
+        if (error) {
+            *error = e;
+        }
+        return NO;
+    }
+    if (!service) {
+        service = [self defaultService];
+    }
+    
+    UICKeyChainStore *keychain = [UICKeyChainStore keyChainStoreWithService:service accessGroup:accessGroup];
+    return [keychain removeItemForKey:key error:error];
+}
+
+#pragma mark -
+
++ (BOOL)removeAllItems
+{
+    return [self removeAllItemsForService:nil accessGroup:nil error:nil];
+}
+
++ (BOOL)removeAllItemsWithError:(NSError *__autoreleasing *)error
+{
+    return [self removeAllItemsForService:nil accessGroup:nil error:error];
+}
+
++ (BOOL)removeAllItemsForService:(NSString *)service
+{
+    return [self removeAllItemsForService:service accessGroup:nil error:nil];
+}
+
++ (BOOL)removeAllItemsForService:(NSString *)service error:(NSError *__autoreleasing *)error
+{
+    return [self removeAllItemsForService:service accessGroup:nil error:error];
+}
+
++ (BOOL)removeAllItemsForService:(NSString *)service accessGroup:(NSString *)accessGroup
+{
+    return [self removeAllItemsForService:service accessGroup:accessGroup error:nil];
+}
+
++ (BOOL)removeAllItemsForService:(NSString *)service accessGroup:(NSString *)accessGroup error:(NSError *__autoreleasing *)error
+{
+    UICKeyChainStore *keychain = [UICKeyChainStore keyChainStoreWithService:service accessGroup:accessGroup];
+    return [keychain removeAllItemsWithError:error];
+}
+
+#pragma mark -
+
+- (BOOL)removeItemForKey:(NSString *)key
+{
+    return [self removeItemForKey:key error:nil];
+}
+
+- (BOOL)removeItemForKey:(NSString *)key error:(NSError *__autoreleasing *)error
+{
+    NSMutableDictionary *query = [self query];
+    query[(__bridge __strong id)kSecAttrAccount] = key;
+    
+    OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query);
+    if (status != errSecSuccess && status != errSecItemNotFound) {
+        NSError *e = [self.class securityError:status];
+        if (error) {
+            *error = e;
+        }
+        return NO;
+    }
+    
+    return YES;
+}
+
+#pragma mark -
+
+- (BOOL)removeAllItems
+{
+    return [self removeAllItemsWithError:nil];
+}
+
+- (BOOL)removeAllItemsWithError:(NSError *__autoreleasing *)error
+{
+    NSMutableDictionary *query = [self query];
+
+    
+    OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query);
+    if (status != errSecSuccess && status != errSecItemNotFound) {
+        NSError *e = [self.class securityError:status];
+        if (error) {
+            *error = e;
+        }
+        return NO;
+    }
+    
+    return YES;
+}
+
+#pragma mark -
+
+- (NSString *)objectForKeyedSubscript:(NSString <NSCopying> *)key
+{
+    return [self stringForKey:key];
+}
+
+- (void)setObject:(NSString *)obj forKeyedSubscript:(NSString <NSCopying> *)key
+{
+    if (!obj) {
+        [self removeItemForKey:key];
+    } else {
+        [self setString:obj forKey:key];
+    }
+}
+
+#pragma mark -
+
+- (NSArray *)allKeys
+{
+    NSArray *items = [self.class prettify:[self itemClassObject] items:[self items]];
+    NSMutableArray *keys = [[NSMutableArray alloc] init];
+    for (NSDictionary *item in items) {
+        [keys addObject:item[@"key"]];
+    }
+    return keys.copy;
+}
+
++ (NSArray *)allKeysWithItemClass:(UICKeyChainStoreItemClass)itemClass
+{
+    CFTypeRef itemClassObject = kSecClassGenericPassword;
+    if (itemClass == UICKeyChainStoreItemClassGenericPassword) {
+        itemClassObject = kSecClassGenericPassword;
+    } else if (itemClass == UICKeyChainStoreItemClassInternetPassword) {
+        itemClassObject = kSecClassInternetPassword;
+    }
+    
+    NSMutableDictionary *query = [[NSMutableDictionary alloc] init];
+    query[(__bridge __strong id)kSecClass] = (__bridge id)itemClassObject;
+    query[(__bridge __strong id)kSecMatchLimit] = (__bridge id)kSecMatchLimitAll;
+    query[(__bridge __strong id)kSecReturnAttributes] = (__bridge id)kCFBooleanTrue;
+    
+    CFArrayRef result = nil;
+    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query,(CFTypeRef *)&result);
+    
+    if (status == errSecSuccess) {
+        NSArray *items = [self prettify:itemClassObject items:(__bridge NSArray *)result];
+        NSMutableArray *keys = [[NSMutableArray alloc] init];
+        for (NSDictionary *item in items) {
+            if (itemClassObject == kSecClassGenericPassword) {
+                [keys addObject:@{@"service": item[@"service"] ?: @"", @"key": item[@"key"] ?: @""}];
+            } else if (itemClassObject == kSecClassInternetPassword) {
+                [keys addObject:@{@"server": item[@"service"] ?: @"", @"key": item[@"key"] ?: @""}];
+            }
+        }
+        return keys.copy;
+    } else if (status == errSecItemNotFound) {
+        return @[];
+    }
+    
+    return nil;
+}
+
++ (NSArray *)allItemsWithItemClass:(UICKeyChainStoreItemClass)itemClass
+{
+    CFTypeRef itemClassObject = kSecClassGenericPassword;
+    if (itemClass == UICKeyChainStoreItemClassGenericPassword) {
+        itemClassObject = kSecClassGenericPassword;
+    } else if (itemClass == UICKeyChainStoreItemClassInternetPassword) {
+        itemClassObject = kSecClassInternetPassword;
+    }
+    
+    NSMutableDictionary *query = [[NSMutableDictionary alloc] init];
+    query[(__bridge __strong id)kSecClass] = (__bridge id)itemClassObject;
+    query[(__bridge __strong id)kSecMatchLimit] = (__bridge id)kSecMatchLimitAll;
+    query[(__bridge __strong id)kSecReturnAttributes] = (__bridge id)kCFBooleanTrue;
+
+    query[(__bridge __strong id)kSecReturnData] = (__bridge id)kCFBooleanTrue;
+
+    
+    CFArrayRef result = nil;
+    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query,(CFTypeRef *)&result);
+    
+    if (status == errSecSuccess) {
+        return [self prettify:itemClassObject items:(__bridge NSArray *)result];
+    } else if (status == errSecItemNotFound) {
+        return @[];
+    }
+    
+    return nil;
+}
+
+- (NSArray *)allItems
+{
+    return [self.class prettify:[self itemClassObject] items:[self items]];
+}
+
+- (NSArray *)items
+{
+    NSMutableDictionary *query = [self query];
+    query[(__bridge __strong id)kSecMatchLimit] = (__bridge id)kSecMatchLimitAll;
+    query[(__bridge __strong id)kSecReturnAttributes] = (__bridge id)kCFBooleanTrue;
+
+    query[(__bridge __strong id)kSecReturnData] = (__bridge id)kCFBooleanTrue;
+
+    
+    CFArrayRef result = nil;
+    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query,(CFTypeRef *)&result);
+    
+    if (status == errSecSuccess) {
+        return CFBridgingRelease(result);
+    } else if (status == errSecItemNotFound) {
+        return @[];
+    }
+    
+    return nil;
+}
+
++ (NSArray *)prettify:(CFTypeRef)itemClass items:(NSArray *)items
+{
+    NSMutableArray *prettified = [[NSMutableArray alloc] init];
+    
+    for (NSDictionary *attributes in items) {
+        NSMutableDictionary *item = [[NSMutableDictionary alloc] init];
+        if (itemClass == kSecClassGenericPassword) {
+            item[@"class"] = @"GenericPassword";
+            id service = attributes[(__bridge id)kSecAttrService];
+            if (service) {
+                item[@"service"] = service;
+            }
+            id accessGroup = attributes[(__bridge id)kSecAttrAccessGroup];
+            if (accessGroup) {
+                item[@"accessGroup"] = accessGroup;
+            }
+        } else if (itemClass == kSecClassInternetPassword) {
+            item[@"class"] = @"InternetPassword";
+            id server = attributes[(__bridge id)kSecAttrServer];
+            if (server) {
+                item[@"server"] = server;
+            }
+            id protocolType = attributes[(__bridge id)kSecAttrProtocol];
+            if (protocolType) {
+                item[@"protocol"] = protocolType;
+            }
+            id authenticationType = attributes[(__bridge id)kSecAttrAuthenticationType];
+            if (authenticationType) {
+                item[@"authenticationType"] = authenticationType;
+            }
+        }
+        id key = attributes[(__bridge id)kSecAttrAccount];
+        if (key) {
+            item[@"key"] = key;
+        }
+        NSData *data = attributes[(__bridge id)kSecValueData];
+        NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+        if (string) {
+            item[@"value"] = string;
+        } else {
+            item[@"value"] = data;
+        }
+        
+        id accessible = attributes[(__bridge id)kSecAttrAccessible];
+        if (accessible) {
+            item[@"accessibility"] = accessible;
+        }
+        id synchronizable = attributes[(__bridge id)kSecAttrSynchronizable];
+        if (synchronizable) {
+            item[@"synchronizable"] = synchronizable;
+        }
+        
+        [prettified addObject:item];
+    }
+    
+    return prettified.copy;
+}
+
+#pragma mark -
+
+- (void)setSynchronizable:(BOOL)synchronizable
+{
+    _synchronizable = synchronizable;
+    if (_authenticationPolicy) {
+        NSLog(@"%@", @"Cannot specify both an authenticationPolicy and a synchronizable");
+    }
+}
+
+- (void)setAccessibility:(UICKeyChainStoreAccessibility)accessibility authenticationPolicy:(UICKeyChainStoreAuthenticationPolicy)authenticationPolicy
+{
+    _accessibility = accessibility;
+    _authenticationPolicy = authenticationPolicy;
+    if (_synchronizable) {
+        NSLog(@"%@", @"Cannot specify both an authenticationPolicy and a synchronizable");
+    }
+}
+
+#pragma mark -
+
+
+- (void)sharedPasswordWithCompletion:(void (^)(NSString *account, NSString *password, NSError *error))completion
+{
+    NSString *domain = self.server.host;
+    if (domain.length > 0) {
+        [self.class requestSharedWebCredentialForDomain:domain account:nil completion:^(NSArray *credentials, NSError *error) {
+            NSDictionary *credential = credentials.firstObject;
+            if (credential) {
+                NSString *account = credential[@"account"];
+                NSString *password = credential[@"password"];
+                if (completion) {
+                    completion(account, password, error);
+                }
+            } else {
+                if (completion) {
+                    completion(nil, nil, error);
+                }
+            }
+        }];
+    } else {
+        NSError *error = [self.class argumentError:NSLocalizedString(@"the server property must not to be nil, should use 'keyChainStoreWithServer:protocolType:' initializer to instantiate keychain store", nil)];
+        if (completion) {
+            completion(nil, nil, error);
+        }
+    }
+}
+
+- (void)sharedPasswordForAccount:(NSString *)account completion:(void (^)(NSString *password, NSError *error))completion
+{
+    NSString *domain = self.server.host;
+    if (domain.length > 0) {
+        [self.class requestSharedWebCredentialForDomain:domain account:account completion:^(NSArray *credentials, NSError *error) {
+            NSDictionary *credential = credentials.firstObject;
+            if (credential) {
+                NSString *password = credential[@"password"];
+                if (completion) {
+                    completion(password, error);
+                }
+            } else {
+                if (completion) {
+                    completion(nil, error);
+                }
+            }
+        }];
+    } else {
+        NSError *error = [self.class argumentError:NSLocalizedString(@"the server property must not to be nil, should use 'keyChainStoreWithServer:protocolType:' initializer to instantiate keychain store", nil)];
+        if (completion) {
+            completion(nil, error);
+        }
+    }
+}
+
+- (void)setSharedPassword:(NSString *)password forAccount:(NSString *)account completion:(void (^)(NSError *error))completion
+{
+    NSString *domain = self.server.host;
+    if (domain.length > 0) {
+        SecAddSharedWebCredential((__bridge CFStringRef)domain, (__bridge CFStringRef)account, (__bridge CFStringRef)password, ^(CFErrorRef error) {
+            if (completion) {
+                completion((__bridge NSError *)error);
+            }
+        });
+    } else {
+        NSError *error = [self.class argumentError:NSLocalizedString(@"the server property must not to be nil, should use 'keyChainStoreWithServer:protocolType:' initializer to instantiate keychain store", nil)];
+        if (completion) {
+            completion(error);
+        }
+    }
+}
+
+- (void)removeSharedPasswordForAccount:(NSString *)account completion:(void (^)(NSError *error))completion
+{
+    [self setSharedPassword:nil forAccount:account completion:completion];
+}
+
++ (void)requestSharedWebCredentialWithCompletion:(void (^)(NSArray *credentials, NSError *error))completion
+{
+    [self requestSharedWebCredentialForDomain:nil account:nil completion:completion];
+}
+
++ (void)requestSharedWebCredentialForDomain:(NSString *)domain account:(NSString *)account completion:(void (^)(NSArray *credentials, NSError *error))completion
+{
+    SecRequestSharedWebCredential((__bridge CFStringRef)domain, (__bridge CFStringRef)account, ^(CFArrayRef credentials, CFErrorRef error) {
+        if (error) {
+            NSError *e = (__bridge NSError *)error;
+            if (e.code != errSecItemNotFound) {
+                NSLog(@"error: [%@] %@", @(e.code), e.localizedDescription);
+            }
+        }
+        
+        NSMutableArray *sharedCredentials = [[NSMutableArray alloc] init];
+        for (NSDictionary *credential in (__bridge NSArray *)credentials) {
+            NSMutableDictionary *sharedCredential = [[NSMutableDictionary alloc] init];
+            NSString *server = credential[(__bridge __strong id)kSecAttrServer];
+            if (server) {
+                sharedCredential[@"server"] = server;
+            }
+            NSString *account = credential[(__bridge __strong id)kSecAttrAccount];
+            if (account) {
+                sharedCredential[@"account"] = account;
+            }
+            NSString *password = credential[(__bridge __strong id)kSecSharedPassword];
+            if (password) {
+                sharedCredential[@"password"] = password;
+            }
+            [sharedCredentials addObject:sharedCredential];
+        }
+        
+        if (completion) {
+            completion(sharedCredentials.copy, (__bridge NSError *)error);
+        }
+    });
+}
+
++ (NSString *)generatePassword
+{
+    return CFBridgingRelease(SecCreateSharedWebCredentialPassword());
+}
+
+
+#pragma mark -
+
+- (void)synchronize
+{
+    // Deprecated, calling this method is no longer required
+}
+
+- (BOOL)synchronizeWithError:(NSError *__autoreleasing *)error
+{
+    // Deprecated, calling this method is no longer required
+    return true;
+}
+
+#pragma mark -
+
+- (NSString *)description
+{
+    NSArray *items = [self allItems];
+    if (items.count == 0) {
+        return @"()";
+    }
+    NSMutableString *description = [[NSMutableString alloc] initWithString:@"(\n"];
+    for (NSDictionary *item in items) {
+        [description appendFormat:@"    %@", item];
+    }
+    [description appendString:@")"];
+    return description.copy;
+}
+
+- (NSString *)debugDescription
+{
+    return [NSString stringWithFormat:@"%@", [self items]];
+}
+
+#pragma mark -
+
+- (NSMutableDictionary *)query
+{
+    NSMutableDictionary *query = [[NSMutableDictionary alloc] init];
+    
+    CFTypeRef itemClass = [self itemClassObject];
+    query[(__bridge __strong id)kSecClass] =(__bridge id)itemClass;
+    if (floor(NSFoundationVersionNumber) > floor(993.00)) { // iOS 7+ (NSFoundationVersionNumber_iOS_6_1)
+        query[(__bridge __strong id)kSecAttrSynchronizable] = (__bridge id)kSecAttrSynchronizableAny;
+    }
+    
+    if (itemClass == kSecClassGenericPassword) {
+        query[(__bridge __strong id)(kSecAttrService)] = _service;
+#if !TARGET_IPHONE_SIMULATOR
+        if (_accessGroup) {
+            query[(__bridge __strong id)kSecAttrAccessGroup] = _accessGroup;
+        }
+#endif
+    } else {
+        if (_server.host) {
+            query[(__bridge __strong id)kSecAttrServer] = _server.host;
+        }
+        if (_server.port) {
+            query[(__bridge __strong id)kSecAttrPort] = _server.port;
+        }
+        CFTypeRef protocolTypeObject = [self protocolTypeObject];
+        if (protocolTypeObject) {
+            query[(__bridge __strong id)kSecAttrProtocol] = (__bridge id)protocolTypeObject;
+        }
+        CFTypeRef authenticationTypeObject = [self authenticationTypeObject];
+        if (authenticationTypeObject) {
+            query[(__bridge __strong id)kSecAttrAuthenticationType] = (__bridge id)authenticationTypeObject;
+        }
+    }
+    
+
+    if (_authenticationPrompt) {
+        if (floor(NSFoundationVersionNumber) > floor(1047.25)) { // iOS 8+ (NSFoundationVersionNumber_iOS_7_1)
+            query[(__bridge __strong id)kSecUseOperationPrompt] = _authenticationPrompt;
+        } else {
+            NSLog(@"%@", @"Unavailable 'authenticationPrompt' attribute on iOS versions prior to 8.0.");
+        }
+    }
+
+    
+    return query;
+}
+
+- (NSMutableDictionary *)attributesWithKey:(NSString *)key value:(NSData *)value error:(NSError *__autoreleasing *)error
+{
+    NSMutableDictionary *attributes;
+    
+    if (key) {
+        attributes = [self query];
+        attributes[(__bridge __strong id)kSecAttrAccount] = key;
+    } else {
+        attributes = [[NSMutableDictionary alloc] init];
+    }
+    
+    attributes[(__bridge __strong id)kSecValueData] = value;
+    
+    double iOS_7_1_or_10_9_2 = 1047.25; // NSFoundationVersionNumber_iOS_7_1
+
+    CFTypeRef accessibilityObject = [self accessibilityObject];
+    if (_authenticationPolicy && accessibilityObject) {
+        if (floor(NSFoundationVersionNumber) > floor(iOS_7_1_or_10_9_2)) { // iOS 8+ or OS X 10.10+
+            CFErrorRef securityError = NULL;
+            SecAccessControlRef accessControl = SecAccessControlCreateWithFlags(kCFAllocatorDefault, accessibilityObject, (SecAccessControlCreateFlags)_authenticationPolicy, &securityError);
+            if (securityError) {
+                NSError *e = (__bridge NSError *)securityError;
+                NSLog(@"error: [%@] %@", @(e.code), e.localizedDescription);
+                if (error) {
+                    *error = e;
+                    return nil;
+                }
+            }
+            if (!accessControl) {
+                NSString *message = NSLocalizedString(@"Unexpected error has occurred.", nil);
+                NSError *e = [self.class unexpectedError:message];
+                if (error) {
+                    *error = e;
+                }
+                return nil;
+            }
+            attributes[(__bridge __strong id)kSecAttrAccessControl] = (__bridge id)accessControl;
+        } else {
+
+            NSLog(@"%@", @"Unavailable 'Touch ID integration' on iOS versions prior to 8.0.");
+
+        }
+    } else {
+        if (floor(NSFoundationVersionNumber) <= floor(iOS_7_1_or_10_9_2) && _accessibility == UICKeyChainStoreAccessibilityWhenPasscodeSetThisDeviceOnly) {
+
+            NSLog(@"%@", @"Unavailable 'UICKeyChainStoreAccessibilityWhenPasscodeSetThisDeviceOnly' attribute on iOS versions prior to 8.0.");
+
+        } else {
+            if (accessibilityObject) {
+                attributes[(__bridge __strong id)kSecAttrAccessible] = (__bridge id)accessibilityObject;
+            }
+        }
+    }
+    
+    attributes[(__bridge __strong id)kSecAttrSynchronizable] = @(_synchronizable);
+    
+    return attributes;
+}
+
+#pragma mark -
+
+- (CFTypeRef)itemClassObject
+{
+    switch (_itemClass) {
+        case UICKeyChainStoreItemClassGenericPassword:
+            return kSecClassGenericPassword;
+        case UICKeyChainStoreItemClassInternetPassword:
+            return kSecClassInternetPassword;
+        default:
+            return nil;
+    }
+}
+
+- (CFTypeRef)protocolTypeObject
+{
+    switch (_protocolType) {
+        case UICKeyChainStoreProtocolTypeFTP:
+            return kSecAttrProtocolFTP;
+        case UICKeyChainStoreProtocolTypeFTPAccount:
+            return kSecAttrProtocolFTPAccount;
+        case UICKeyChainStoreProtocolTypeHTTP:
+            return kSecAttrProtocolHTTP;
+        case UICKeyChainStoreProtocolTypeIRC:
+            return kSecAttrProtocolIRC;
+        case UICKeyChainStoreProtocolTypeNNTP:
+            return kSecAttrProtocolNNTP;
+        case UICKeyChainStoreProtocolTypePOP3:
+            return kSecAttrProtocolPOP3;
+        case UICKeyChainStoreProtocolTypeSMTP:
+            return kSecAttrProtocolSMTP;
+        case UICKeyChainStoreProtocolTypeSOCKS:
+            return kSecAttrProtocolSOCKS;
+        case UICKeyChainStoreProtocolTypeIMAP:
+            return kSecAttrProtocolIMAP;
+        case UICKeyChainStoreProtocolTypeLDAP:
+            return kSecAttrProtocolLDAP;
+        case UICKeyChainStoreProtocolTypeAppleTalk:
+            return kSecAttrProtocolAppleTalk;
+        case UICKeyChainStoreProtocolTypeAFP:
+            return kSecAttrProtocolAFP;
+        case UICKeyChainStoreProtocolTypeTelnet:
+            return kSecAttrProtocolTelnet;
+        case UICKeyChainStoreProtocolTypeSSH:
+            return kSecAttrProtocolSSH;
+        case UICKeyChainStoreProtocolTypeFTPS:
+            return kSecAttrProtocolFTPS;
+        case UICKeyChainStoreProtocolTypeHTTPS:
+            return kSecAttrProtocolHTTPS;
+        case UICKeyChainStoreProtocolTypeHTTPProxy:
+            return kSecAttrProtocolHTTPProxy;
+        case UICKeyChainStoreProtocolTypeHTTPSProxy:
+            return kSecAttrProtocolHTTPSProxy;
+        case UICKeyChainStoreProtocolTypeFTPProxy:
+            return kSecAttrProtocolFTPProxy;
+        case UICKeyChainStoreProtocolTypeSMB:
+            return kSecAttrProtocolSMB;
+        case UICKeyChainStoreProtocolTypeRTSP:
+            return kSecAttrProtocolRTSP;
+        case UICKeyChainStoreProtocolTypeRTSPProxy:
+            return kSecAttrProtocolRTSPProxy;
+        case UICKeyChainStoreProtocolTypeDAAP:
+            return kSecAttrProtocolDAAP;
+        case UICKeyChainStoreProtocolTypeEPPC:
+            return kSecAttrProtocolEPPC;
+        case UICKeyChainStoreProtocolTypeNNTPS:
+            return kSecAttrProtocolNNTPS;
+        case UICKeyChainStoreProtocolTypeLDAPS:
+            return kSecAttrProtocolLDAPS;
+        case UICKeyChainStoreProtocolTypeTelnetS:
+            return kSecAttrProtocolTelnetS;
+        case UICKeyChainStoreProtocolTypeIRCS:
+            return kSecAttrProtocolIRCS;
+        case UICKeyChainStoreProtocolTypePOP3S:
+            return kSecAttrProtocolPOP3S;
+        default:
+            return nil;
+    }
+}
+
+- (CFTypeRef)authenticationTypeObject
+{
+    switch (_authenticationType) {
+        case UICKeyChainStoreAuthenticationTypeNTLM:
+            return kSecAttrAuthenticationTypeNTLM;
+        case UICKeyChainStoreAuthenticationTypeMSN:
+            return kSecAttrAuthenticationTypeMSN;
+        case UICKeyChainStoreAuthenticationTypeDPA:
+            return kSecAttrAuthenticationTypeDPA;
+        case UICKeyChainStoreAuthenticationTypeRPA:
+            return kSecAttrAuthenticationTypeRPA;
+        case UICKeyChainStoreAuthenticationTypeHTTPBasic:
+            return kSecAttrAuthenticationTypeHTTPBasic;
+        case UICKeyChainStoreAuthenticationTypeHTTPDigest:
+            return kSecAttrAuthenticationTypeHTTPDigest;
+        case UICKeyChainStoreAuthenticationTypeHTMLForm:
+            return kSecAttrAuthenticationTypeHTMLForm;
+        case UICKeyChainStoreAuthenticationTypeDefault:
+            return kSecAttrAuthenticationTypeDefault;
+        default:
+            return nil;
+    }
+}
+
+- (CFTypeRef)accessibilityObject
+{
+    switch (_accessibility) {
+        case UICKeyChainStoreAccessibilityWhenUnlocked:
+            return kSecAttrAccessibleWhenUnlocked;
+        case UICKeyChainStoreAccessibilityAfterFirstUnlock:
+            return kSecAttrAccessibleAfterFirstUnlock;
+        case UICKeyChainStoreAccessibilityAlways:
+            return kSecAttrAccessibleAlways;
+        case UICKeyChainStoreAccessibilityWhenPasscodeSetThisDeviceOnly:
+            return kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly;
+        case UICKeyChainStoreAccessibilityWhenUnlockedThisDeviceOnly:
+            return kSecAttrAccessibleWhenUnlockedThisDeviceOnly;
+        case UICKeyChainStoreAccessibilityAfterFirstUnlockThisDeviceOnly:
+            return kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly;
+        case UICKeyChainStoreAccessibilityAlwaysThisDeviceOnly:
+            return kSecAttrAccessibleAlwaysThisDeviceOnly;
+        default:
+            return nil;
+    }
+}
+
++ (NSError *)argumentError:(NSString *)message
+{
+    NSError *error = [NSError errorWithDomain:UICKeyChainStoreErrorDomain code:UICKeyChainStoreErrorInvalidArguments userInfo:@{NSLocalizedDescriptionKey: message}];
+    NSLog(@"error: [%@] %@", @(error.code), error.localizedDescription);
+    return error;
+}
+
++ (NSError *)conversionError:(NSString *)message
+{
+    NSError *error = [NSError errorWithDomain:UICKeyChainStoreErrorDomain code:-67594 userInfo:@{NSLocalizedDescriptionKey: message}];
+    NSLog(@"error: [%@] %@", @(error.code), error.localizedDescription);
+    return error;
+}
+
++ (NSError *)securityError:(OSStatus)status
+{
+    NSString *message = @"Security error has occurred.";
+
+    NSError *error = [NSError errorWithDomain:UICKeyChainStoreErrorDomain code:status userInfo:@{NSLocalizedDescriptionKey: message}];
+    NSLog(@"OSStatus error: [%@] %@", @(error.code), error.localizedDescription);
+    return error;
+}
+
++ (NSError *)unexpectedError:(NSString *)message
+{
+    NSError *error = [NSError errorWithDomain:UICKeyChainStoreErrorDomain code:-99999 userInfo:@{NSLocalizedDescriptionKey: message}];
+    NSLog(@"error: [%@] %@", @(error.code), error.localizedDescription);
+    return error;
+}
+
+@end
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/Parser/XMLParser.h
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/Parser/XMLParser.h	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/Parser/XMLParser.h	(working copy)
@@ -0,0 +1,85 @@
+//
+//  XMLParser.h
+//  MacTunnel
+//
+//  Created by wangxy on 2017/5/8.
+//  Copyright © 2017年 wangxy. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "GDataXMLNode.h"
+
+@interface XMLParser : NSObject
+
+/**
+ * @brief Parse the input XML string by tags that are included in the tags array.
+ *
+ * @param string : XML string.
+ * @param tags : XML tags array.
+ *
+ * @return String value of the first node for the xpath.
+ */
++ (NSString *)parseXMLString:(NSString *)string tags:(NSArray<NSString *> *)tags;
+
+/**
+ * @brief Parse the input XML string by tags and attributes that are included in the tags array.
+ *
+ * @param string : XML string.
+ * @param tags : XML tags array.
+ * @param attributes : XML attributes array.
+ *
+ * @return Array of dictionaries, keys of the dictionary items are from the attributes array.
+ */
++ (NSArray *)parseXMLString:(NSString *)string tags:(NSArray<NSString *> *)tags attributes:(NSArray<NSString *> *)attributes;
+
+/**
+ * @brief Parse the name of the root node.
+ *
+ * @param string : XML string.
+ *
+ * @return Name of root node.
+ */
++ (NSString *)getRootNodeNameWithXMLString:(NSString *)string;
+
+/**
+ * @brief Get nodes of tags.
+ *
+ * @param string : XML string.
+ * @param tags : XML tags array.
+ *
+ * @return Nodes array.
+ */
++ (NSArray *)getNodesWithXMLString:(NSString *)string tags:(NSArray<NSString *> *)tags;
+
+/**
+ * @brief Get a node's sub-nodes.
+ *
+ * @param node : XML node.
+ * @param tag : XML tag.
+ *
+ * @return Sub-nodes array.
+ */
++ (NSArray *)getSubNodesWithXMLNode:(GDataXMLElement *)node tag:(NSString *)tag;
+
+/**
+ * @brief Get a node's sub-nodes.
+ *
+ * @param node : XML node.
+ * @param attributes : XML attributes array.
+ *
+ * @return A dictionary whose keys are from the attributes array.
+ */
++ (NSDictionary *)parseXMLNode:(GDataXMLElement *)node attributes:(NSArray<NSString *> *)attributes;
+
+/**
+ * @brief Get a node's sub-nodes with tag and attributes.
+ *
+ * @param node : XML node.
+ * @param tag : XML tag.
+ * @param attributes : XML attributes array.
+ *
+ * @return A dictionary whose keys are from the attributes array.
+ */
++ (NSDictionary *)parseNode:(GDataXMLElement *)node tag:(NSArray<NSString *> *)tag attributes:(NSArray<NSString *> *)attributes;
+
+@end
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/Parser/XMLParser.m
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/Parser/XMLParser.m	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/Parser/XMLParser.m	(working copy)
@@ -0,0 +1,166 @@
+//
+//  XMLParser.m
+//  MacTunnel
+//
+//  Created by wangxy on 2017/5/8.
+//  Copyright © 2017年 wangxy. All rights reserved.
+//
+
+#import "ANLogger.h"
+#import "XMLParser.h"
+
+@implementation XMLParser
+
++ (NSString *)parseXMLString:(NSString *)string tags:(NSArray<NSString *> *)tags {
+    if (!tags || [tags count] == 0) {;
+        ANError(@"Empty tags input for xml parser.");
+        
+        return nil;
+    }
+    
+    NSArray *nodes = [self getNodesWithXMLString:string tags:tags];
+    if (nodes && nodes.count > 0) {
+        GDataXMLNode *node = nodes[0];
+        return node.stringValue;
+    }
+    
+    return nil;
+}
+
++ (NSArray *)parseXMLString:(NSString *)string tags:(NSArray *)tags attributes:(NSArray<NSString *> *)attributes {
+    if (!tags || [tags count] == 0) {
+        ANError(@"Empty tags input for xml parser.");
+        
+        return nil;
+    }
+    
+    if (!attributes || [attributes count] == 0) {
+        ANError(@"Empty attribute input for xml node parser.");
+        
+        return nil;
+    }
+
+    NSArray *nodes = [self getNodesWithXMLString:string tags:tags];
+    if (nodes == nil) {
+        ANError(@"Get XML nodes failed.");
+        
+        return nil;
+    }
+
+    NSMutableArray *results = [NSMutableArray arrayWithCapacity:nodes.count];
+    
+    for (GDataXMLElement *node in nodes) {
+        NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithCapacity:[attributes count]];
+        
+        for (NSString *key in attributes) {
+            NSString *value = [[node attributeForName:key] stringValue];
+            dict[key] = value;
+        }
+        
+        [results addObject:dict];
+    }
+    
+    return results;
+}
+
++ (NSString *)getRootNodeNameWithXMLString:(NSString *)string {
+    NSError *error = nil;
+    
+    if (!string) {
+        return nil;
+    }
+    
+    GDataXMLDocument *doc = [[GDataXMLDocument alloc] initWithXMLString:string error:&error];
+    if (doc == nil) {
+        ANError(@"Resource xml parse failed.");
+        
+        return nil;
+    }
+    
+    return doc.rootElement.name;
+}
+
++ (NSArray *)getNodesWithXMLString:(NSString *)string tags:(NSArray *)tags {
+    NSError *error;
+    NSString *xpath = @"//";
+    
+    if (!tags || tags.count == 0) {
+        ANError(@"Empty tags input for xml parser.");
+        
+        return nil;
+    }
+    
+    GDataXMLDocument *doc = [[GDataXMLDocument alloc] initWithXMLString:string error:&error];
+    if (doc == nil) {
+        ANError(@"Resource xml parse failed.");
+        
+        return nil;
+    }
+    
+    for (NSInteger i = 0; i < [tags count]; i++) {
+        xpath = [xpath stringByAppendingPathComponent:tags[i]];
+    }
+    
+    NSArray *nodes = [doc nodesForXPath:xpath error:&error];
+    if (error) {
+        ANError(@"Parse xml nodes failed with error: %@.", error.localizedDescription);
+        
+        return nil;
+    }
+    
+    return [[NSMutableArray alloc] initWithArray:nodes copyItems:YES];  // Deep copy.
+}
+
++ (NSArray *)getSubNodesWithXMLNode:(GDataXMLElement *)node tag:(NSString *)tag {
+    if (!tag) {
+        ANError(@"Empty tag input for xml node parser.");
+        
+        return nil;
+    }
+    
+    return [node elementsForName:tag];
+}
+
++ (NSDictionary *)parseXMLNode:(GDataXMLElement *)node attributes:(NSArray *)attributes {
+    if (!node) {
+        ANError(@"Empty node input for xml node parser.");
+        
+        return nil;
+    }
+    
+    if (!attributes || attributes.count == 0) {
+        ANError(@"Empty attribute input for xml node parser.");
+        
+        return nil;
+    }
+    
+    NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithCapacity:[attributes count]];
+        
+    for (NSString *key in attributes) {
+        NSString *value = [[node attributeForName:key] stringValue];
+        dict[key] = value;
+    }
+    
+    return dict;
+}
+
++ (NSDictionary *)parseNode:(GDataXMLElement *)node tag:(NSString *)tag attributes:(NSArray<NSString *> *)attributes {
+    if (node == nil || tag == nil) {
+        return nil;
+    }
+    
+    NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:attributes.count];
+    
+    NSArray *elements = [node elementsForName:tag];
+    if (elements != nil && elements.count > 0) {
+        GDataXMLElement *element = elements[0];
+        
+        for (NSString *attribute in attributes) {
+            dict[attribute] = [[element attributeForName:attribute] stringValue];
+        }
+    }
+    
+    return dict;
+}
+
+@end
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/URLSchemeParser/AutoLoginHandler.h
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/URLSchemeParser/AutoLoginHandler.h	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/URLSchemeParser/AutoLoginHandler.h	(working copy)
@@ -0,0 +1,33 @@
+//
+//  AutoLoginHandler.h
+//  MobileNow
+//
+//  Created by zhangqq on 14-7-15.
+//  Copyright (c) 2014年 ArrayNetworks. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "AAAManager.h"
+
+extern NSString * const kAutoLoginNotification;
+extern NSString * const kAutoLogoutNotification;
+
+@protocol AutoLoginHandlerDelegate <NSObject>
+
+- (void)didReceiveLoginRequest:(VPNAccount *)account;
+- (void)didReceiveLoginRequest:(VPNAccount *)account session:(NSString *)sess;
+- (void)didReceiveLoginRequest:(VPNAccount *)account oauthreq:(NSString *)request oauthcode:(NSString *)code;
+- (void)autoLoginFailed:(NSString *)errorMessage;
+- (void)didReceiveDisconnectRequest:(VPNAccount *)account IsLogout:(BOOL)islogout;
+
+@end
+
+@interface AutoLoginHandler : NSObject
+
+@property (nonatomic, weak) id<AutoLoginHandlerDelegate> delegate;
+@property (nonatomic, weak, readonly) NSURL *url;
+
+- (instancetype)initWithDelegate:(id<AutoLoginHandlerDelegate>)delegate;
+- (void)readyToAutoLogin;
+
+@end
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/URLSchemeParser/AutoLoginHandler.m
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/URLSchemeParser/AutoLoginHandler.m	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/URLSchemeParser/AutoLoginHandler.m	(working copy)
@@ -0,0 +1,84 @@
+//
+//  AutoLoginHandler.m
+//  MobileNow
+//
+//  Created by zhangqq on 14-7-15.
+//  Copyright (c) 2014年 ArrayNetworks. All rights reserved.
+//
+
+#import "AutoLoginHandler.h"
+#import "URLParser.h"
+#import "ANLogger.h"
+
+#import "AppDelegate.h"
+
+NSString * const kAutoLoginNotification = @"net.arraynetworks.autologin";
+NSString * const kAutoLogoutNotification = @"net.arraynetworks.autologout";
+
+@implementation AutoLoginHandler
+
+- (instancetype)initWithDelegate:(id<AutoLoginHandlerDelegate>)delegate {
+    self = [super init];
+    if (self) {
+        self.delegate = delegate;
+        [[NSNotificationCenter defaultCenter] addObserver:self
+                                                 selector:@selector(handleAutoLoginNotification:)
+                                                     name:kAutoLoginNotification
+                                                   object:nil];
+    }
+    
+    return self;
+}
+
+- (void)dealloc {
+    [[NSNotificationCenter defaultCenter] removeObserver:self];
+}
+
+- (NSURL *)url {
+
+    return ((AppDelegate *)[UIApplication sharedApplication].delegate).launchedURL;
+
+}
+
+- (void)readyToAutoLogin {
+    if ([self url]) {
+        [self doAutoLogin];
+    }
+}
+
+- (void)handleAutoLoginNotification:(NSNotification *)notification {
+    dispatch_async(dispatch_get_main_queue(), ^{
+        [self doAutoLogin];
+    });
+}
+
+- (void)doAutoLogin {
+    NSURL *url = [self url];
+    if (!url) {
+        return;
+    }
+    
+    NSString *path = url.path;
+    VPNAccount *account = [URLParser parseURL:url];
+    NSString *sess = [URLParser parseURLSession:url];
+    NSString *oauthRequest = [URLParser parseURLOauthReq:url];
+    NSString *oauthCode = [URLParser parseURLOauthCode:url];
+    
+    if ([path isEqualToString:@"/login"] && oauthRequest && oauthCode) {
+        ANInfo(@"scheme login with oauth, req=%@, code=%@", oauthRequest, oauthCode);
+        [_delegate didReceiveLoginRequest:account oauthreq:oauthRequest oauthcode:oauthCode];
+        return;
+    }
+
+    if ([path isEqualToString:@"/login"] && sess) {
+        [_delegate didReceiveLoginRequest:account session:sess];
+    } else if ([path isEqualToString:@"/login"]) {
+        [_delegate didReceiveLoginRequest:account];
+    } else if ([path isEqualToString:@"/logout"]) {
+        [_delegate didReceiveDisconnectRequest:account IsLogout:YES];
+    } else if ([path isEqualToString:@"/stopvpn"]) {
+        [_delegate didReceiveDisconnectRequest:account IsLogout:NO];
+    }
+}
+
+@end
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/URLSchemeParser/URLParser.h
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/URLSchemeParser/URLParser.h	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/URLSchemeParser/URLParser.h	(working copy)
@@ -0,0 +1,20 @@
+//
+//  URLParser.h
+//  MobileNow
+//
+//  Created by zhangqq on 14-7-15.
+//  Copyright (c) 2014年 ArrayNetworks. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@class VPNAccount;
+
+@interface URLParser : NSObject
+
++ (VPNAccount *)parseURL:(NSURL *)url;
++ (NSString *)parseURLSession:(NSURL *)url;
++ (NSString *)parseURLOauthReq:(NSURL *)url;
++ (NSString *)parseURLOauthCode:(NSURL *)url;
+
+@end
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/URLSchemeParser/URLParser.m
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/URLSchemeParser/URLParser.m	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Helpers/URLSchemeParser/URLParser.m	(working copy)
@@ -0,0 +1,122 @@
+//
+//  URLParser.m
+//  MobileNow
+//
+//  Created by zhangqq on 14-7-15.
+//  Copyright (c) 2014年 ArrayNetworks. All rights reserved.
+//
+
+#import "ANLogger.h"
+#import "AAAManager.h"
+#import "URLParser.h"
+
+@implementation URLParser
+
+#pragma mark - URL Process
+
+static NSDictionary *parseQueryString(NSURL *url) {
+    NSString *queryString = [url query];
+    NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
+    
+    for (NSString *param in [queryString componentsSeparatedByString:@"&"]) {
+        NSArray *elts = [param componentsSeparatedByString:@"="];
+        if([elts count] < 2) continue;
+        NSString *key = elts[0];
+        NSString *value = [elts[1] stringByRemovingPercentEncoding];
+        params[key] = value;
+    }
+    
+    return params;
+}
+
+static NSString * const kMPUsername = @"username";
+static NSString * const kMPPassword = @"password";  // kPassword is conflict with a key in SDK.
+static NSString * const kMPMethod = @"method";
+static NSString * const kMPCallback = @"callback=";
+static NSString * const kMPRequest = @"request";
+static NSString * const kMPCode = @"code";
+
++ (VPNAccount *)parseURL:(NSURL *)url {
+    NSString *host = url.host;
+    if (!host || host.length == 0) {
+        ANError(@"Fail to parse URL: %@", url);
+        return nil;
+    }
+    
+    NSNumber *port = url.port;
+    if (!port) {
+        port = @443;
+    }
+    
+    NSDictionary *params = parseQueryString(url);
+    
+    VPNAccount *account = [[VPNAccount alloc] init];
+    account.vsHost = host;
+    account.vsPort = [port stringValue];
+    account.userName = params[kMPUsername];
+    account.passWord = params[kMPPassword];
+    account.loginMethod = params[kMPMethod];
+    NSString *callback = [url absoluteString];
+    NSRange range = [callback rangeOfString:kMPCallback];
+    if (range.length > 0) {
+        callback = [callback substringFromIndex:range.location + range.length];
+    } else {
+        callback = @"";
+    }
+    
+    account.autoLoginCallback = callback;
+    
+    return account;
+}
+
++ (NSString *)decodeString:(NSString*)encodedString {
+    return [encodedString stringByRemovingPercentEncoding];
+}
+
++ (NSString *)parseURLSession:(NSURL *)url {
+    NSString *host = url.host;
+    if (!host || host.length == 0) {
+        ANError(@"Fail to parse URL session: %@", url);
+        return nil;
+    }
+    
+    NSDictionary *params = parseQueryString(url);
+    NSString *sess = params[@"session"];
+    if (sess) {
+        NSString *sessStr = [[NSString alloc] initWithString:sess];
+        NSString *decodedSess = [URLParser decodeString:sessStr];
+        return decodedSess;
+    }
+    
+    return nil;
+}
+
++ (NSString *)parseURLOauthReq:(NSURL *)url {
+    NSString *host = url.host;
+    if (!host || host.length == 0) {
+        ANError(@"Fail to parse Oauth Req: %@", url);
+        return nil;
+    }
+    
+    NSDictionary *params = parseQueryString(url);
+    
+    NSString *oauthRequest = params[kMPRequest];
+    
+    return oauthRequest;
+}
+
++ (NSString *)parseURLOauthCode:(NSURL *)url {
+    NSString *host = url.host;
+    if (!host || host.length == 0) {
+        ANError(@"Fail to parse Oauth Req: %@", url);
+        return nil;
+    }
+    
+    NSDictionary *params = parseQueryString(url);
+    
+    NSString *oauthCode = params[kMPCode];
+    
+    return oauthCode;
+}
+
+@end
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Log/ANCustomDDLogger.h
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Log/ANCustomDDLogger.h	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Log/ANCustomDDLogger.h	(working copy)
@@ -0,0 +1,27 @@
+//
+//  ANCustomDDLogger.h
+//  MacTunnel
+//
+//  Created by wangxy on 2017/4/17.
+//  Copyright © 2017年 wangxy. All rights reserved.
+//
+
+#import <CocoaLumberjack/CocoaLumberjack.h>
+
+@interface ANLogFileManager : DDLogFileManagerDefault
+
+@property (nonatomic, copy) NSString *logFileName;
+
+@end
+
+@interface ANLogFormatter : NSObject
+
+@end
+
+@interface ANFileLogFormatter : ANLogFormatter <DDLogFormatter>
+
+@end
+
+@interface ANConsoleLogFormatter : ANLogFormatter <DDLogFormatter>
+
+@end
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Log/ANCustomDDLogger.m
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Log/ANCustomDDLogger.m	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Log/ANCustomDDLogger.m	(working copy)
@@ -0,0 +1,143 @@
+//
+//  ANCustomDDLogger.m
+//  MacTunnel
+//
+//  Created by wangxy on 2017/4/17.
+//  Copyright © 2017年 wangxy. All rights reserved.
+//
+
+#import "ANLogger.h"
+#import "ANCustomDDLogger.h"
+
+
+NSString * const kAppGroupNameCustom = @"group.net.arraynetworks.MotionProPlus";
+
+
+BOOL doesAppRunInBackground(void);
+
+#pragma mark - File Manager
+@implementation ANLogFileManager
+
+- (NSString *)newLogFileName {
+    if (!self.logFileName) {
+        self.logFileName = kANLogFileName;
+    }
+    
+    return self.logFileName;
+}
+
+- (NSString *)logsDirectory {
+    NSFileManager *fileManager = [NSFileManager defaultManager];
+    NSURL *shareDirectory = [fileManager containerURLForSecurityApplicationGroupIdentifier:kAppGroupNameCustom];
+    
+    return [shareDirectory path];
+}
+
+- (NSString *)createNewLogFile {
+    NSString *filePath = [[self logsDirectory] stringByAppendingPathComponent:self.logFileName];
+    
+    if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
+        [[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil];
+        return filePath;
+    }
+    
+    return filePath;
+}
+
+- (BOOL)isLogFile:(NSString *)fileName {
+    return NO;
+}
+
+@end
+
+#pragma mark - Log Formatter
+@interface ANLogFormatter () {
+    NSDateFormatter *_formatter;
+}
+
+@end
+
+@implementation ANLogFormatter
+
+- (NSString *)logLevelStringWithFlag:(DDLogFlag)logFlag {
+    NSString *logLevel;
+    
+    switch (logFlag) {
+        case DDLogFlagDebug:
+            logLevel = @"DEBUG";
+            break;
+        case DDLogFlagInfo:
+            logLevel = @"INFO";
+            break;
+        case DDLogFlagWarning:
+            logLevel = @"WARN";
+            break;
+        case DDLogFlagError:
+            logLevel = @"ERROR";
+            break;
+            
+        default:
+            logLevel = @"VERBOSE";
+            break;
+    }
+    
+    return logLevel;
+}
+
+- (NSDateFormatter *)formatter {
+    if (!_formatter) {
+        _formatter = [[NSDateFormatter alloc] init];
+        [_formatter setDateFormat:@"YYYY-MM-dd HH:mm:ss.SSS"];
+    }
+    
+    return _formatter;
+}
+
+@end
+
+@implementation ANFileLogFormatter
+
+- (NSString *)formatLogMessage:(DDLogMessage *)logMessage {
+    NSString *logLevel = [self logLevelStringWithFlag:logMessage->_flag];
+    NSString *timeToShow = [self.formatter stringFromDate:logMessage->_timestamp];
+    
+    return [NSString stringWithFormat:@"%@ <%@>: %@ %@:%zi %@",
+            timeToShow, logLevel, logMessage->_queueLabel, logMessage->_fileName, logMessage->_line, logMessage->_message];
+}
+
+@end
+
+@implementation ANConsoleLogFormatter
+
+- (NSString *)formatLogMessage:(DDLogMessage *)logMessage {
+    NSString *logLevel = [self logLevelStringWithFlag:logMessage->_flag];
+    
+    return [NSString stringWithFormat:@"<%@>: %@", logLevel, logMessage->_message];
+}
+
+@end
+
+
+/**
+ * When creating log file on iOS we're setting NSFileProtectionKey attribute to NSFileProtectionCompleteUnlessOpen.
+ *
+ * But in case if app is able to launch from background we need to have an ability to open log file any time we
+ * want (even if device is locked). Thats why that attribute have to be changed to
+ * NSFileProtectionCompleteUntilFirstUserAuthentication.
+ */
+BOOL doesAppRunInBackground(void) {
+    BOOL answer = NO;
+    
+    NSArray *backgroundModes = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIBackgroundModes"];
+    
+    for (NSString *mode in backgroundModes) {
+        if (mode.length > 0) {
+            answer = YES;
+            break;
+        }
+    }
+    
+    return answer;
+}
+
+
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Log/ANCustomDDLogger.m.InfosecEnterprise
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Log/ANCustomDDLogger.m.InfosecEnterprise	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Log/ANCustomDDLogger.m.InfosecEnterprise	(working copy)
@@ -0,0 +1,145 @@
+//
+//  ANCustomDDLogger.m
+//  MacTunnel
+//
+//  Created by wangxy on 2017/4/17.
+//  Copyright © 2017年 wangxy. All rights reserved.
+//
+
+#import "ANLogger.h"
+#import "ANCustomDDLogger.h"
+
+#if TARGET_OS_IPHONE
+NSString * const kAppGroupNameCustom = @"group.net.infosec.groupenterprise";
+#elif TARGET_OS_MAC
+NSString * const kAppGroupNameCustom = @"5VZLZ3YQ9P.net.arraynetworks.MacTunnel";
+#endif
+
+BOOL doesAppRunInBackground();
+
+#pragma mark - File Manager
+@implementation ANLogFileManager
+
+- (NSString *)newLogFileName {
+    if (!self.logFileName) {
+        self.logFileName = kANLogFileName;
+    }
+    
+    return self.logFileName;
+}
+
+- (NSString *)logsDirectory {
+    NSFileManager *fileManager = [NSFileManager defaultManager];
+    NSURL *shareDirectory = [fileManager containerURLForSecurityApplicationGroupIdentifier:kAppGroupNameCustom];
+    
+    return [shareDirectory path];
+}
+
+- (NSString *)createNewLogFile {
+    NSString *filePath = [[self logsDirectory] stringByAppendingPathComponent:self.logFileName];
+    
+    if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
+        [[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil];
+        return filePath;
+    }
+    
+    return filePath;
+}
+
+- (BOOL)isLogFile:(NSString *)fileName {
+    return NO;
+}
+
+@end
+
+#pragma mark - Log Formatter
+@interface ANLogFormatter () {
+    NSDateFormatter *_formatter;
+}
+
+@end
+
+@implementation ANLogFormatter
+
+- (NSString *)logLevelStringWithFlag:(DDLogFlag)logFlag {
+    NSString *logLevel;
+    
+    switch (logFlag) {
+        case DDLogFlagDebug:
+            logLevel = @"DEBUG";
+            break;
+        case DDLogFlagInfo:
+            logLevel = @"INFO";
+            break;
+        case DDLogFlagWarning:
+            logLevel = @"WARN";
+            break;
+        case DDLogFlagError:
+            logLevel = @"ERROR";
+            break;
+            
+        default:
+            logLevel = @"VERBOSE";
+            break;
+    }
+    
+    return logLevel;
+}
+
+- (NSDateFormatter *)formatter {
+    if (!_formatter) {
+        _formatter = [[NSDateFormatter alloc] init];
+        [_formatter setDateFormat:@"YYYY-MM-dd HH:mm:ss.SSS"];
+    }
+    
+    return _formatter;
+}
+
+@end
+
+@implementation ANFileLogFormatter
+
+- (NSString *)formatLogMessage:(DDLogMessage *)logMessage {
+    NSString *logLevel = [self logLevelStringWithFlag:logMessage->_flag];
+    NSString *timeToShow = [self.formatter stringFromDate:logMessage->_timestamp];
+    
+    return [NSString stringWithFormat:@"%@ <%@>: %@ %@:%zi %@",
+            timeToShow, logLevel, logMessage->_queueLabel, logMessage->_fileName, logMessage->_line, logMessage->_message];
+}
+
+@end
+
+@implementation ANConsoleLogFormatter
+
+- (NSString *)formatLogMessage:(DDLogMessage *)logMessage {
+    NSString *logLevel = [self logLevelStringWithFlag:logMessage->_flag];
+    
+    return [NSString stringWithFormat:@"<%@>: %@", logLevel, logMessage->_message];
+}
+
+@end
+
+#if TARGET_OS_IPHONE
+/**
+ * When creating log file on iOS we're setting NSFileProtectionKey attribute to NSFileProtectionCompleteUnlessOpen.
+ *
+ * But in case if app is able to launch from background we need to have an ability to open log file any time we
+ * want (even if device is locked). Thats why that attribute have to be changed to
+ * NSFileProtectionCompleteUntilFirstUserAuthentication.
+ */
+BOOL doesAppRunInBackground() {
+    BOOL answer = NO;
+    
+    NSArray *backgroundModes = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIBackgroundModes"];
+    
+    for (NSString *mode in backgroundModes) {
+        if (mode.length > 0) {
+            answer = YES;
+            break;
+        }
+    }
+    
+    return answer;
+}
+
+#endif
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Log/ANCustomDDLogger.m.InfosecStore
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Log/ANCustomDDLogger.m.InfosecStore	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Log/ANCustomDDLogger.m.InfosecStore	(working copy)
@@ -0,0 +1,145 @@
+//
+//  ANCustomDDLogger.m
+//  MacTunnel
+//
+//  Created by wangxy on 2017/4/17.
+//  Copyright © 2017年 wangxy. All rights reserved.
+//
+
+#import "ANLogger.h"
+#import "ANCustomDDLogger.h"
+
+#if TARGET_OS_IPHONE
+NSString * const kAppGroupName = @"group.net.infosec.MotionPro";
+#elif TARGET_OS_MAC
+NSString * const kAppGroupName = @"5VZLZ3YQ9P.net.arraynetworks.MacTunnel";
+#endif
+
+BOOL doesAppRunInBackground();
+
+#pragma mark - File Manager
+@implementation ANLogFileManager
+
+- (NSString *)newLogFileName {
+    if (!self.logFileName) {
+        self.logFileName = kANLogFileName;
+    }
+    
+    return self.logFileName;
+}
+
+- (NSString *)logsDirectory {
+    NSFileManager *fileManager = [NSFileManager defaultManager];
+    NSURL *shareDirectory = [fileManager containerURLForSecurityApplicationGroupIdentifier:kAppGroupName];
+    
+    return [shareDirectory path];
+}
+
+- (NSString *)createNewLogFile {
+    NSString *filePath = [[self logsDirectory] stringByAppendingPathComponent:self.logFileName];
+    
+    if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
+        [[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil];
+        return filePath;
+    }
+    
+    return filePath;
+}
+
+- (BOOL)isLogFile:(NSString *)fileName {
+    return NO;
+}
+
+@end
+
+#pragma mark - Log Formatter
+@interface ANLogFormatter () {
+    NSDateFormatter *_formatter;
+}
+
+@end
+
+@implementation ANLogFormatter
+
+- (NSString *)logLevelStringWithFlag:(DDLogFlag)logFlag {
+    NSString *logLevel;
+    
+    switch (logFlag) {
+        case DDLogFlagDebug:
+            logLevel = @"DEBUG";
+            break;
+        case DDLogFlagInfo:
+            logLevel = @"INFO";
+            break;
+        case DDLogFlagWarning:
+            logLevel = @"WARN";
+            break;
+        case DDLogFlagError:
+            logLevel = @"ERROR";
+            break;
+            
+        default:
+            logLevel = @"VERBOSE";
+            break;
+    }
+    
+    return logLevel;
+}
+
+- (NSDateFormatter *)formatter {
+    if (!_formatter) {
+        _formatter = [[NSDateFormatter alloc] init];
+        [_formatter setDateFormat:@"YYYY-MM-dd HH:mm:ss.SSS"];
+    }
+    
+    return _formatter;
+}
+
+@end
+
+@implementation ANFileLogFormatter
+
+- (NSString *)formatLogMessage:(DDLogMessage *)logMessage {
+    NSString *logLevel = [self logLevelStringWithFlag:logMessage->_flag];
+    NSString *timeToShow = [self.formatter stringFromDate:logMessage->_timestamp];
+    
+    return [NSString stringWithFormat:@"%@ <%@>: %@ %@:%zi %@",
+            timeToShow, logLevel, logMessage->_queueLabel, logMessage->_fileName, logMessage->_line, logMessage->_message];
+}
+
+@end
+
+@implementation ANConsoleLogFormatter
+
+- (NSString *)formatLogMessage:(DDLogMessage *)logMessage {
+    NSString *logLevel = [self logLevelStringWithFlag:logMessage->_flag];
+    
+    return [NSString stringWithFormat:@"<%@>: %@", logLevel, logMessage->_message];
+}
+
+@end
+
+#if TARGET_OS_IPHONE
+/**
+ * When creating log file on iOS we're setting NSFileProtectionKey attribute to NSFileProtectionCompleteUnlessOpen.
+ *
+ * But in case if app is able to launch from background we need to have an ability to open log file any time we
+ * want (even if device is locked). Thats why that attribute have to be changed to
+ * NSFileProtectionCompleteUntilFirstUserAuthentication.
+ */
+BOOL doesAppRunInBackground() {
+    BOOL answer = NO;
+    
+    NSArray *backgroundModes = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIBackgroundModes"];
+    
+    for (NSString *mode in backgroundModes) {
+        if (mode.length > 0) {
+            answer = YES;
+            break;
+        }
+    }
+    
+    return answer;
+}
+
+#endif
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Log/ANCustomDDLogger.m.MotionPro
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Log/ANCustomDDLogger.m.MotionPro	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Log/ANCustomDDLogger.m.MotionPro	(working copy)
@@ -0,0 +1,145 @@
+//
+//  ANCustomDDLogger.m
+//  MacTunnel
+//
+//  Created by wangxy on 2017/4/17.
+//  Copyright © 2017年 wangxy. All rights reserved.
+//
+
+#import "ANLogger.h"
+#import "ANCustomDDLogger.h"
+
+#if TARGET_OS_IPHONE
+NSString * const kAppGroupName = @"group.net.arraynetworks.MotionPro";
+#elif TARGET_OS_MAC
+NSString * const kAppGroupName = @"5VZLZ3YQ9P.net.arraynetworks.MacTunnel";
+#endif
+
+BOOL doesAppRunInBackground();
+
+#pragma mark - File Manager
+@implementation ANLogFileManager
+
+- (NSString *)newLogFileName {
+    if (!self.logFileName) {
+        self.logFileName = kANLogFileName;
+    }
+    
+    return self.logFileName;
+}
+
+- (NSString *)logsDirectory {
+    NSFileManager *fileManager = [NSFileManager defaultManager];
+    NSURL *shareDirectory = [fileManager containerURLForSecurityApplicationGroupIdentifier:kAppGroupName];
+    
+    return [shareDirectory path];
+}
+
+- (NSString *)createNewLogFile {
+    NSString *filePath = [[self logsDirectory] stringByAppendingPathComponent:self.logFileName];
+    
+    if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
+        [[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil];
+        return filePath;
+    }
+    
+    return filePath;
+}
+
+- (BOOL)isLogFile:(NSString *)fileName {
+    return NO;
+}
+
+@end
+
+#pragma mark - Log Formatter
+@interface ANLogFormatter () {
+    NSDateFormatter *_formatter;
+}
+
+@end
+
+@implementation ANLogFormatter
+
+- (NSString *)logLevelStringWithFlag:(DDLogFlag)logFlag {
+    NSString *logLevel;
+    
+    switch (logFlag) {
+        case DDLogFlagDebug:
+            logLevel = @"DEBUG";
+            break;
+        case DDLogFlagInfo:
+            logLevel = @"INFO";
+            break;
+        case DDLogFlagWarning:
+            logLevel = @"WARN";
+            break;
+        case DDLogFlagError:
+            logLevel = @"ERROR";
+            break;
+            
+        default:
+            logLevel = @"VERBOSE";
+            break;
+    }
+    
+    return logLevel;
+}
+
+- (NSDateFormatter *)formatter {
+    if (!_formatter) {
+        _formatter = [[NSDateFormatter alloc] init];
+        [_formatter setDateFormat:@"YYYY-MM-dd HH:mm:ss.SSS"];
+    }
+    
+    return _formatter;
+}
+
+@end
+
+@implementation ANFileLogFormatter
+
+- (NSString *)formatLogMessage:(DDLogMessage *)logMessage {
+    NSString *logLevel = [self logLevelStringWithFlag:logMessage->_flag];
+    NSString *timeToShow = [self.formatter stringFromDate:logMessage->_timestamp];
+    
+    return [NSString stringWithFormat:@"%@ <%@>: %@ %@:%zi %@",
+            timeToShow, logLevel, logMessage->_queueLabel, logMessage->_fileName, logMessage->_line, logMessage->_message];
+}
+
+@end
+
+@implementation ANConsoleLogFormatter
+
+- (NSString *)formatLogMessage:(DDLogMessage *)logMessage {
+    NSString *logLevel = [self logLevelStringWithFlag:logMessage->_flag];
+    
+    return [NSString stringWithFormat:@"<%@>: %@", logLevel, logMessage->_message];
+}
+
+@end
+
+#if TARGET_OS_IPHONE
+/**
+ * When creating log file on iOS we're setting NSFileProtectionKey attribute to NSFileProtectionCompleteUnlessOpen.
+ *
+ * But in case if app is able to launch from background we need to have an ability to open log file any time we
+ * want (even if device is locked). Thats why that attribute have to be changed to
+ * NSFileProtectionCompleteUntilFirstUserAuthentication.
+ */
+BOOL doesAppRunInBackground() {
+    BOOL answer = NO;
+    
+    NSArray *backgroundModes = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIBackgroundModes"];
+    
+    for (NSString *mode in backgroundModes) {
+        if (mode.length > 0) {
+            answer = YES;
+            break;
+        }
+    }
+    
+    return answer;
+}
+
+#endif
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Log/ANCustomDDLogger.m.MotionProEnterprise
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Log/ANCustomDDLogger.m.MotionProEnterprise	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Log/ANCustomDDLogger.m.MotionProEnterprise	(working copy)
@@ -0,0 +1,145 @@
+//
+//  ANCustomDDLogger.m
+//  MacTunnel
+//
+//  Created by wangxy on 2017/4/17.
+//  Copyright © 2017年 wangxy. All rights reserved.
+//
+
+#import "ANLogger.h"
+#import "ANCustomDDLogger.h"
+
+#if TARGET_OS_IPHONE
+NSString * const kAppGroupNameCustom = @"group.net.arraynetworks.groupenterprise";
+#elif TARGET_OS_MAC
+NSString * const kAppGroupNameCustom = @"5VZLZ3YQ9P.net.arraynetworks.MacTunnel";
+#endif
+
+BOOL doesAppRunInBackground();
+
+#pragma mark - File Manager
+@implementation ANLogFileManager
+
+- (NSString *)newLogFileName {
+    if (!self.logFileName) {
+        self.logFileName = kANLogFileName;
+    }
+    
+    return self.logFileName;
+}
+
+- (NSString *)logsDirectory {
+    NSFileManager *fileManager = [NSFileManager defaultManager];
+    NSURL *shareDirectory = [fileManager containerURLForSecurityApplicationGroupIdentifier:kAppGroupNameCustom];
+    
+    return [shareDirectory path];
+}
+
+- (NSString *)createNewLogFile {
+    NSString *filePath = [[self logsDirectory] stringByAppendingPathComponent:self.logFileName];
+    
+    if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
+        [[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil];
+        return filePath;
+    }
+    
+    return filePath;
+}
+
+- (BOOL)isLogFile:(NSString *)fileName {
+    return NO;
+}
+
+@end
+
+#pragma mark - Log Formatter
+@interface ANLogFormatter () {
+    NSDateFormatter *_formatter;
+}
+
+@end
+
+@implementation ANLogFormatter
+
+- (NSString *)logLevelStringWithFlag:(DDLogFlag)logFlag {
+    NSString *logLevel;
+    
+    switch (logFlag) {
+        case DDLogFlagDebug:
+            logLevel = @"DEBUG";
+            break;
+        case DDLogFlagInfo:
+            logLevel = @"INFO";
+            break;
+        case DDLogFlagWarning:
+            logLevel = @"WARN";
+            break;
+        case DDLogFlagError:
+            logLevel = @"ERROR";
+            break;
+            
+        default:
+            logLevel = @"VERBOSE";
+            break;
+    }
+    
+    return logLevel;
+}
+
+- (NSDateFormatter *)formatter {
+    if (!_formatter) {
+        _formatter = [[NSDateFormatter alloc] init];
+        [_formatter setDateFormat:@"YYYY-MM-dd HH:mm:ss.SSS"];
+    }
+    
+    return _formatter;
+}
+
+@end
+
+@implementation ANFileLogFormatter
+
+- (NSString *)formatLogMessage:(DDLogMessage *)logMessage {
+    NSString *logLevel = [self logLevelStringWithFlag:logMessage->_flag];
+    NSString *timeToShow = [self.formatter stringFromDate:logMessage->_timestamp];
+    
+    return [NSString stringWithFormat:@"%@ <%@>: %@ %@:%zi %@",
+            timeToShow, logLevel, logMessage->_queueLabel, logMessage->_fileName, logMessage->_line, logMessage->_message];
+}
+
+@end
+
+@implementation ANConsoleLogFormatter
+
+- (NSString *)formatLogMessage:(DDLogMessage *)logMessage {
+    NSString *logLevel = [self logLevelStringWithFlag:logMessage->_flag];
+    
+    return [NSString stringWithFormat:@"<%@>: %@", logLevel, logMessage->_message];
+}
+
+@end
+
+#if TARGET_OS_IPHONE
+/**
+ * When creating log file on iOS we're setting NSFileProtectionKey attribute to NSFileProtectionCompleteUnlessOpen.
+ *
+ * But in case if app is able to launch from background we need to have an ability to open log file any time we
+ * want (even if device is locked). Thats why that attribute have to be changed to
+ * NSFileProtectionCompleteUntilFirstUserAuthentication.
+ */
+BOOL doesAppRunInBackground() {
+    BOOL answer = NO;
+    
+    NSArray *backgroundModes = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIBackgroundModes"];
+    
+    for (NSString *mode in backgroundModes) {
+        if (mode.length > 0) {
+            answer = YES;
+            break;
+        }
+    }
+    
+    return answer;
+}
+
+#endif
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Log/ANLogger.h
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Log/ANLogger.h	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Log/ANLogger.h	(working copy)
@@ -0,0 +1,71 @@
+//
+//  ANLogger.h
+//  MacTunnel
+//
+//  Created by wangxy on 2017/4/14.
+//  Copyright © 2017年 wangxy. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import <CocoaLumberjack/CocoaLumberjack.h>
+
+static NSString * const kANLogFileName = @"ANLog.log";
+static NSString * const kANTunnelFileName = @"ANTunnel.log";
+
+static const DDLogLevel ddLogLevel = DDLogLevelDebug;
+
+typedef NS_ENUM(NSInteger, ArrayLogLevel) {
+    ArrayLogLevelDebug = 0,
+    ArrayLogLevelInfo,
+    ArrayLogLevelWarn,
+    ArrayLogLevelError,
+    ArrayLogLevelDisabled,
+};
+
+extern NSInteger arrayLogLevel;
+
+#define ANDebug(fmt, ...)  \
+    do {  \
+        if (arrayLogLevel <= ArrayLogLevelDebug) {  \
+            DDLogDebug(fmt, ##__VA_ARGS__);  \
+        }  \
+    } while(0)
+
+#define ANInfo(fmt, ...)  \
+    do {  \
+        if (arrayLogLevel <= ArrayLogLevelInfo) {  \
+            DDLogInfo(fmt, ##__VA_ARGS__);  \
+        }  \
+    } while(0)
+
+#define ANWarn(fmt, ...)  \
+    do {  \
+        if (arrayLogLevel <= ArrayLogLevelWarn) {  \
+            DDLogWarn(fmt, ##__VA_ARGS__);  \
+        }  \
+    } while(0)
+
+#define ANError(fmt, ...)  \
+    do {  \
+        if (arrayLogLevel <= ArrayLogLevelError) {  \
+            DDLogError(fmt, ##__VA_ARGS__);  \
+        }  \
+    } while(0)
+
+
+void clog_callback(int level, const char *msg);  // Used by low-level sdk.
+
+@interface ANLogger : NSObject
+
+@property (nonatomic, copy) NSString *logFile;
+@property (nonatomic) BOOL enabled;
+
++ (instancetype)sharedInstance;
+
+- (void)setupWithLogFile:(NSString *)fileName;
+- (NSInteger)currentLogLevel;
+- (void)setLogLevel:(NSInteger)level;
+- (NSString *)pathToLogFile;
+- (NSString *)logDirectory;
+
+@end
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Log/ANLogger.m
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Log/ANLogger.m	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Log/ANLogger.m	(working copy)
@@ -0,0 +1,118 @@
+//
+//  ANLogger.m
+//  MacTunnel
+//
+//  Created by wangxy on 2017/4/14.
+//  Copyright © 2017年 wangxy. All rights reserved.
+//
+
+#import <CocoaLumberjack/CocoaLumberjack.h>
+#import "ANCustomDDLogger.h"
+#import "ANLogger.h"
+#import "arrayapi.h"
+
+#define GLOBAL_SETTING_LOG_LEVEL    @"log_level"
+
+NSInteger arrayLogLevel = ArrayLogLevelDebug;
+
+@implementation ANLogger
+
++ (instancetype)sharedInstance {
+    static id instance = nil;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        instance = [[self alloc] init];
+    });
+    return instance;
+}
+
+- (void)setupWithLogFile:(NSString *)fileName {
+    DDASLLogger *ASLLogger = [DDASLLogger sharedInstance];
+    ASLLogger.logFormatter = [[ANConsoleLogFormatter alloc] init];
+    [DDLog addLogger:ASLLogger];
+    
+    ANLogFileManager *docManager = [[ANLogFileManager alloc] initWithLogsDirectory:[self logDirectory]];
+    docManager.logFileName = fileName;
+    DDFileLogger *fileLogger = [[DDFileLogger alloc] initWithLogFileManager:docManager];
+    fileLogger.logFormatter = [[ANFileLogFormatter alloc] init];
+    fileLogger.rollingFrequency = 60 * 60 * 24;
+    fileLogger.maximumFileSize = 5 * 1024 * 1024;
+    fileLogger.logFileManager.maximumNumberOfLogFiles = 1;
+    [DDLog addLogger:fileLogger];
+    
+    self.logFile = fileName;
+}
+
+- (NSInteger)currentLogLevel {
+    NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
+    NSString *levelString = [userDefaults objectForKey:GLOBAL_SETTING_LOG_LEVEL];
+    if (levelString == nil) {
+        [self setLogLevel:ArrayLogLevelInfo];
+    } else {
+        arrayLogLevel = [levelString integerValue];
+    }
+    
+    return arrayLogLevel;
+}
+
+- (void)setLogLevel:(NSInteger)level {
+    NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
+    
+    if (level < ArrayLogLevelDebug || level > ArrayLogLevelDisabled) {
+        level = ArrayLogLevelInfo;
+    }
+    
+    arrayLogLevel = level;
+    
+    [userDefaults setInteger:level forKey:GLOBAL_SETTING_LOG_LEVEL];
+    [userDefaults synchronize];
+}
+
+- (NSString *)pathToLogFile {
+    return [[self logDirectory] stringByAppendingPathComponent:self.logFile];
+}
+
+- (NSString *)logDirectory {
+    return [[[ANLogFileManager alloc] init] logsDirectory];
+}
+
+- (void)setEnabled:(BOOL)enable {
+    if (enable) {
+        [self setLogLevel:ArrayLogLevelInfo];  // Restore as default.
+    } else {
+        [self setLogLevel:ArrayLogLevelDisabled];
+    }
+}
+
+- (BOOL)enabled {
+    return self.currentLogLevel != ArrayLogLevelDisabled;
+}
+
+@end
+
+/**
+ *  This function is used as a callback for ArraySDK. (C language)
+ */
+void clog_callback(int level, const char *msg) {
+    @autoreleasepool {
+        NSString *str = [NSString stringWithUTF8String:msg];
+        NSString *message = [NSString stringWithFormat:@"[ArraySDK] %@", str];
+        switch (level) {
+            case LOG_DEBUG:
+                ANDebug(@"%@", message);
+                break;
+            case LOG_INFO:
+                ANInfo(@"%@", message);
+                break;
+            case LOG_WARNING:
+                ANWarn(@"%@", message);
+                break;
+            case LOG_ERROR:
+                ANError(@"%@", message);
+                break;
+                
+            default:
+                break;
+        }
+    }
+}
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Networks/AFHTTPSessionManager+ANClientAuth.h
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Networks/AFHTTPSessionManager+ANClientAuth.h	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Networks/AFHTTPSessionManager+ANClientAuth.h	(working copy)
@@ -0,0 +1,22 @@
+//
+//  AFHTTPSessionManager+ANClientAuth.h
+//  MacTunnel
+//
+//  Created by wangxy on 2017/8/22.
+//  Copyright © 2017年 wangxy. All rights reserved.
+//
+
+#import <AFNetworking/AFNetworking.h>
+
+@interface AFHTTPSessionManager (ANClientAuth)
+
+/**
+ *  Config credential for AFHTTPRequestOperationManager.
+ *
+ *  This method will set certificate credential from Gateway configuration. It is used for supporting SSL Client Auth.
+ *
+ *  @param host Virtual site's IP address
+ */
+- (void)anConfigCredential:(NSString *)host andport:(NSString *)port;
+
+@end
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Networks/AFHTTPSessionManager+ANClientAuth.m
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Networks/AFHTTPSessionManager+ANClientAuth.m	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Networks/AFHTTPSessionManager+ANClientAuth.m	(working copy)
@@ -0,0 +1,35 @@
+//
+//  AFHTTPSessionManager+ANClientAuth.m
+//  MacTunnel
+//
+//  Created by wangxy on 2017/8/22.
+//  Copyright © 2017年 wangxy. All rights reserved.
+//
+
+#import <Security/Security.h>
+#import "GatewayManager.h"
+#import "CertificateManager.h"
+#import "AFHTTPSessionManager+ANClientAuth.h"
+
+@implementation AFHTTPSessionManager (ANClientAuth)
+
+- (void)anConfigCredential:(NSString *)host andport:(NSString *)port {
+    Gateway *gw = [[GatewayManager sharedInstance] gatewayOfUrl:host andport:port];
+    
+    if (!gw.certname || gw.certname.length == 0) {
+        return;
+    }
+    
+    // Get certificate
+    Certificate *cert= [[CertificateManager sharedInstance].certificates objectForKey:gw.certname];
+    if (!cert) {
+        return;
+    }
+    
+    NSData *certData = [[NSData alloc] initWithContentsOfFile:cert.path];
+    if (certData && certData.length > 0) {
+        self.securityPolicy.pinnedCertificates = [NSSet setWithObject:certData];
+    }
+}
+
+@end
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Networks/ANHttpClient.h
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Networks/ANHttpClient.h	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Networks/ANHttpClient.h	(working copy)
@@ -0,0 +1,62 @@
+//
+//  ANHttpClient.h
+//  MacTunnel
+//
+//  Created by wangxy on 2017/5/9.
+//  Copyright © 2017年 wangxy. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+typedef NSURLRequest *_Nullable(^RedirectCallback)(NSURLRequest *_Nullable);
+
+@interface ANHttpClient : NSObject
+
+- (void)getWithURL:(NSString *_Nonnull)URLString
+            params:(id _Nullable)params
+           success:(void (^_Nullable)(NSURLSessionDataTask * _Nullable task, id _Nullable response))success
+           failure:(void (^_Nullable)(NSURLSessionDataTask * _Nullable task, NSError * _Nullable error))failure;
+
+- (void)getFileWithURL:(NSString *_Nonnull)URLString
+                 param:(id _Nullable)params
+              progress:(void (^ _Nullable)(NSProgress * _Nullable))downloadProgress
+               success:(void (^ _Nullable)(NSURLSessionDataTask * _Nullable task, id _Nullable response))success
+               failure:(void (^ _Nullable)(NSURLSessionDataTask * _Nullable task, NSError * _Nullable error))failure;
+
+- (void)postWithURL:(NSString * _Nonnull)URLString
+             params:(id _Nullable)params
+            success:(void (^ _Nullable)(NSURLSessionDataTask * _Nullable task, id _Nullable response))success
+            failure:(void (^ _Nullable)(NSURLSessionDataTask * _Nullable task, NSError * _Nullable error))failure;
+
+- (void)putWithURL:(NSString * _Nonnull)URLString
+            params:(id _Nullable)params
+           success:(void (^ _Nullable)(NSURLSessionDataTask * _Nullable task, id _Nullable response))success
+           failure:(void (^ _Nullable)(NSURLSessionDataTask * _Nullable task, NSError * _Nullable error))failure;
+
+- (void)headWithURL:(NSString * _Nullable)URLString
+             params:(id _Nullable)params
+            success:(void (^ _Nullable)(NSURLSessionDataTask * _Nullable task))success
+            failure:(void (^ _Nullable)(NSURLSessionDataTask * _Nullable task, NSError * _Nullable error))failure;
+
+- (void)deleteWithURL:(NSString * _Nullable)URLString
+               params:(id _Nullable)params
+              success:(void (^ _Nullable)(NSURLSessionDataTask * _Nullable task, id _Nullable response))success
+              failure:(void (^ _Nullable)(NSURLSessionDataTask * _Nullable task, NSError * _Nullable error))failure;
+
+- (void)patchWithURL:(NSString * _Nullable)URLString
+              params:(id _Nullable)params
+             success:(void (^ _Nullable)(NSURLSessionDataTask * _Nullable task, id _Nullable response))success
+             failure:(void (^ _Nullable)(NSURLSessionDataTask * _Nullable task, NSError * _Nullable error))failure;
+
+- (void)getImageWithURL:(NSString *_Nullable)URLString
+                 params:(id _Nullable)params
+                success:(void (^ _Nullable)(NSURLSessionDataTask * _Nullable task, id _Nullable response))success
+                failure:(void (^ _Nullable)(NSURLSessionDataTask * _Nullable task, NSError * _Nullable error))failure;
+
+- (void)setRedirectCallback:(RedirectCallback _Nullable)callback;
+
+- (void)setCookieWithName:(NSString * _Nonnull)name andValue:(NSString * _Nonnull)value;
+
+- (void)setANSessionWithValue:(NSString * _Nonnull)value;
+
+@end
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Networks/ANHttpClient.m
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Networks/ANHttpClient.m	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Networks/ANHttpClient.m	(working copy)
@@ -0,0 +1,118 @@
+//
+//  ANHttpClient.m
+//  MacTunnel
+//
+//  Created by wangxy on 2017/5/9.
+//  Copyright © 2017年 wangxy. All rights reserved.
+//
+
+#import <AFNetworking/AFNetworking.h>
+#import "ANHttpClient.h"
+
+@interface ANHttpClient()
+
+@property (nonatomic, strong) AFHTTPSessionManager *manager;
+
+@end
+
+@implementation ANHttpClient
+
+@synthesize manager = _manager;
+
+- (AFHTTPSessionManager *)manager {
+    if (!_manager) {
+        _manager = [AFHTTPSessionManager manager];
+        
+        AFSecurityPolicy *policy = [AFSecurityPolicy defaultPolicy];  // Ignore untrusted SSL certificate.
+        policy.allowInvalidCertificates = YES;
+        policy.validatesDomainName = NO;
+        _manager.securityPolicy = policy;
+    }
+    
+    return _manager;
+}
+
+- (void)getImageWithURL:(NSString *)URLString
+                 params:(id)params
+                success:(void (^)(NSURLSessionDataTask *task, id response))success
+                failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure {
+    self.manager.responseSerializer = [[AFImageResponseSerializer alloc] init];
+    
+    [self getWithURL:URLString params:params success:success failure:failure];
+}
+
+- (void)getWithURL:(NSString *)URLString
+            params:(id)params
+           success:(void (^)(NSURLSessionDataTask *task, id response))success
+           failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure {
+    
+    [self.manager GET:URLString parameters:params headers:nil progress:nil success:success failure:failure];
+}
+
+- (void)getFileWithURL:(NSString *)URLString
+                 param:(id)params
+              progress:(void (^)(NSProgress *))downloadProgress
+               success:(void (^)(NSURLSessionDataTask *task, id response))success
+               failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure {
+    
+    [self.manager GET:URLString parameters:params headers:nil progress:downloadProgress success:success failure:failure];
+}
+
+- (void)postWithURL:(NSString *)URLString
+             params:(id)params
+            success:(void (^)(NSURLSessionDataTask *task, id response))success
+            failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure {
+    
+    [self.manager POST:URLString parameters:params headers:nil progress:nil success:success failure:failure];
+}
+
+- (void)putWithURL:(NSString *)URLString
+            params:(id)params
+           success:(void (^)(NSURLSessionDataTask *task, id response))success
+           failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure {
+    
+    [self.manager PUT:URLString parameters:params headers:nil success:success failure:failure];
+}
+
+- (void)headWithURL:(NSString *)URLString
+             params:(id)params
+            success:(void (^)(NSURLSessionDataTask *task))success
+            failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure {
+    
+    [self.manager HEAD:URLString parameters:params headers:nil success:success failure:failure];
+}
+
+- (void)deleteWithURL:(NSString *)URLString
+               params:(id)params
+              success:(void (^)(NSURLSessionDataTask *task, id response))success
+              failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure {
+    
+    [self.manager DELETE:URLString parameters:params headers:nil success:success failure:failure];
+}
+
+- (void)patchWithURL:(NSString *)URLString
+              params:(id)params
+             success:(void (^)(NSURLSessionDataTask *task, id response))success
+             failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure {
+    
+    [self.manager PATCH:URLString parameters:params headers:nil success:success failure:failure];
+}
+
+- (void)setRedirectCallback:(RedirectCallback)callback {
+    [self.manager setTaskWillPerformHTTPRedirectionBlock:^NSURLRequest *(NSURLSession *session, NSURLSessionTask *task,
+                                                                         NSURLResponse *response, NSURLRequest *request) {
+        return callback(request);
+    }];
+}
+
+- (void)setCookieWithName:(NSString *)name andValue:(NSString *)value {
+    NSString *cookieString = [NSString stringWithFormat:@"%@=%@", name, value];
+    [self.manager.requestSerializer setValue:cookieString forHTTPHeaderField:@"Cookie"];
+}
+
+- (void)setANSessionWithValue:(NSString *)value {
+    [self.manager setResponseSerializer:[AFHTTPResponseSerializer serializer]];
+    [self.manager.requestSerializer setValue:value forHTTPHeaderField:@"Cookie"];
+}
+
+@end
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Networks/GeneralTool.h
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Networks/GeneralTool.h	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Networks/GeneralTool.h	(working copy)
@@ -0,0 +1,15 @@
+//
+//  GeneralTool.h
+//  MacTunnel
+//
+//  Created by wangxy on 2017/7/4.
+//  Copyright © 2017年 wangxy. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface GeneralTool : NSObject
+
++ (NSString *)encodeURL:(NSString *)urlstring;
+
+@end
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Networks/GeneralTool.m
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Networks/GeneralTool.m	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Networks/GeneralTool.m	(working copy)
@@ -0,0 +1,21 @@
+//
+//  GeneralTool.m
+//  MacTunnel
+//
+//  Created by wangxy on 2017/7/4.
+//  Copyright © 2017年 wangxy. All rights reserved.
+//
+
+#import "GeneralTool.h"
+
+@implementation GeneralTool
+
++ (NSString *)encodeURL:(NSString *)query {
+    if (query == nil) return nil;
+
+    NSString *escapgedString = [query stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
+    
+    return escapgedString;
+}
+
+@end
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Networks/NSURLRequest+ForSSL.h
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Networks/NSURLRequest+ForSSL.h	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Networks/NSURLRequest+ForSSL.h	(working copy)
@@ -0,0 +1,15 @@
+//
+//  NSURLRequest+ForSSL.h
+//  MotionPro Plus
+//
+//  Created by wangxy on 2017/12/5.
+//  Copyright © 2017年 wangxy. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface NSURLRequest (ForSSL)
+
++(BOOL)allowsAnyHTTPSCertificateForHost:(NSString*)host;
+
+@end
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Networks/NSURLRequest+ForSSL.m
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Networks/NSURLRequest+ForSSL.m	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Networks/NSURLRequest+ForSSL.m	(working copy)
@@ -0,0 +1,17 @@
+//
+//  NSURLRequest+ForSSL.m
+//  MotionPro Plus
+//
+//  Created by wangxy on 2017/12/5.
+//  Copyright © 2017年 wangxy. All rights reserved.
+//
+
+#import "NSURLRequest+ForSSL.h"
+
+@implementation NSURLRequest (ForSSL)
+
++ (BOOL)allowsAnyHTTPSCertificateForHost:(NSString*)host {
+    return YES;
+}
+
+@end
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Networks/UDPHelper.h
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Networks/UDPHelper.h	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Networks/UDPHelper.h	(working copy)
@@ -0,0 +1,14 @@
+//
+//  UDPHelper.h
+//  MobileNow
+//
+//  Created by zhangqq on 14-5-9.
+//  Copyright (c) 2014年 ArrayNetworks. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface UDPHelper : NSObject
+
++ (NSData *)sendSyncPacketTo:(NSString *)ip port:(NSInteger)port withMessage:(NSData *)message;
+@end
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Networks/UDPHelper.m
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Networks/UDPHelper.m	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/Utils/Networks/UDPHelper.m	(working copy)
@@ -0,0 +1,63 @@
+//
+//  UDPHelper.m
+//  MobileNow
+//
+//  Created by zhangqq on 14-5-9.
+//  Copyright (c) 2014年 ArrayNetworks. All rights reserved.
+//
+
+#import "UDPHelper.h"
+
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <arpa/inet.h>
+
+@implementation UDPHelper
+
++ (NSData *)sendSyncPacketTo:(NSString *)ip port:(NSInteger)port withMessage:(NSData *)message
+{
+    // Open a socket
+    int sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+    if (sd <= 0) {
+        NSLog(@"Error: Could not open socket");
+        return nil;
+    }
+    
+    // Timeout 5s
+    struct timeval tv_out;
+    tv_out.tv_sec = 5;
+    tv_out.tv_usec = 0;
+    setsockopt(sd,SOL_SOCKET,SO_RCVTIMEO,&tv_out, sizeof(tv_out));
+    
+    struct sockaddr_in address;
+    memset(&address, 0, sizeof(address));
+    
+    uint address_len = sizeof(address);
+    
+    address.sin_family = AF_INET;
+    inet_pton(AF_INET, [ip UTF8String], &address.sin_addr);
+    address.sin_port = htons(port);
+    
+    const void *request = [message bytes];
+    ssize_t ret = sendto(sd, request, message.length, 0, (struct sockaddr*)&address, address_len);
+    if (ret < 0) {
+        NSLog(@"Error: Could not open send broadcast");
+        close(sd);
+        return nil;
+    }
+    
+    char buffer[1024] = {0};
+    
+    ssize_t len = recvfrom(sd, buffer, sizeof(buffer), 0, (struct sockaddr*)&address, &address_len);
+    
+    if (len <= 0) {
+        close(sd);
+        return nil;
+    }
+    
+    close(sd);
+    
+    return [NSData dataWithBytes:buffer length:len];
+}
+@end
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/VPNConfig/ArrayVPNManager.h
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/VPNConfig/ArrayVPNManager.h	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/VPNConfig/ArrayVPNManager.h	(working copy)
@@ -0,0 +1,217 @@
+//
+//  ArrayVPNManager.h
+//  ArraySSLVPN
+//
+//  Created by wangxy on 16/10/20.
+//  Copyright (c) 2016年 ArrayNetworks. All rights reserved.
+//
+
+#import "arrayapi.h"
+#import "vpnplugin.h"
+#import <Foundation/Foundation.h>
+#import <NetworkExtension/NetworkExtension.h>
+
+// VPN Type: net.arraynetworks.sslvpn.vpnplugin
+extern NSString * const kArrayVPNType;
+
+// Notification for VPN status changed
+extern NSString * const kArrayVPNStatusNotification;
+
+// Notification for VPN disconnected
+extern NSString * const kArrayVPNDisconnected;
+
+// Error domain
+extern NSString * const kArrayVPNErrorDomain;
+
+// VPN plugin log name
+extern NSString * const kVPNPluginLogName;
+
+typedef NS_ENUM(NSInteger, ArrayVPNErrorCode)
+{
+    ArrayVPNErrorInternal             = -100000,
+    ArrayVPNErrorLoadConfiguration    = -100001,
+    ArrayVPNErrorInvalidConfiguration = -100002,
+    ArrayVPNErrorNoConfiguration      = -100003,    // For app vpn.
+};
+
+typedef NS_ENUM(NSInteger, ArrayVPNStatus)
+{
+    ArrayVPNInavlid          = -1,
+    ArrayVPNDisconnected     = 0,
+    ArrayVPNConnecting       = 1,
+    ArrayVPNConnected        = 2,
+    ArrayVPNDisconnecting    = 3,
+    ArrayVPNReasserting      = 4,
+};
+
+@interface ArrayVPNManager : NSObject
+
+/*!
+ *  VPN status
+ *
+ *  You'd better observe kArrayVPNStatusNotification notification.
+ */
+@property (nonatomic) ArrayVPNStatus vpnStatus;
+
+@property (nonatomic, strong) NETunnelProviderManager *tunnelManager;
+
+/*!
+ *  VPN configuration's name
+ *
+ *  This name will be displayed in system "Settings" -> "VPN".
+ * 
+ *  Default value is "ArrayNetworks".
+ */
+@property (nonatomic, copy) NSString *vpnName;
+
+#pragma mark - Virtual Site Configuration
+
+@property (nonatomic, copy) NSString *host;
+@property (nonatomic, copy) NSString *port;
+
+#if !TARGET_OS_IPHONE
+#pragma mark - Proxy Settings
+
+@property (nonatomic, copy) NSString *proxyHost;
+@property (nonatomic, copy) NSString *proxyUsername;
+@property (nonatomic, copy) NSString *proxyPassword;
+
+#endif
+
+/*!
+ *  Device ID
+ *
+ *  This is for DeviceID Auth.
+ *
+ *  If you want to use DeviceID auth method, please set vpnFlag to VPN_FLAG_DEVID_LOGIN
+ */
+@property (nonatomic, copy) NSString *deviceID;
+
+/*!
+ *  Session ID
+ *
+ *  Login with Session ID.
+ */
+@property (nonatomic, copy) NSString *sessionID;
+
+
+@property (nonatomic, copy) NSString *customDnsListString;
+/*!
+ *  Client ID
+ */
+@property (nonatomic, copy) NSString *clientID;
+
+/*!
+ *  Certificate for Client Auth
+ */
+@property (nonatomic, copy) NSData *certData;
+
+/*!
+ *  Certificate import password
+ */
+@property (nonatomic, copy) NSString *certPassword;
+
+/*!
+ *  VPN flag
+ * 
+ *  This flag is used to start L3 VPN
+ * 
+ *  Default value is VPN_FLAG_DEVID_LOGIN
+ */
+@property (nonatomic) uint32_t vpnFlag;
+
+#pragma mark - VPN On Demand
+
+/*!
+ *  Enable VPN on Demand
+ *
+ *  Default value is NO
+ */
+@property (nonatomic, getter=isVPNOnDemandEnabled) BOOL enableVPNOnDemand;
+
+/*!
+ *  VPN on Demand domains
+ *
+ *  If user access these domains, VPN will be launched.
+ */
+@property (nonatomic, copy) NSArray *onDemandDomains;
+
+#pragma mark - Speed Tunnel
+
+/*!
+ *  Enable speed tunnel or not
+ *
+ *  Default value is YES
+ */
+@property (nonatomic, getter=isSpeedTunnelEnabled) BOOL enableSpeedTunnel;
+
+/*!
+ *  Speed tunnel dispatch
+ *  
+ *  Available values (refer to arrayapi.h)
+ *
+ *  @li VPN_DISP_AlltoTCP
+ *  @li VPN_DISP_TCPtoTCP
+ *  @li VPN_DISP_TCPtoUDP
+ *  @li VPN_DISP_AlltoUDP
+ *
+ *  Default value is VPN_DISP_AlltoUDP
+ */
+@property (nonatomic) NSInteger speedTunnelDispatch;
+
+/*!
+ *  Enable speed tunnel encrypt or not
+ * 
+ *  Default value is YES
+ */
+@property (nonatomic, getter=isSpeedTunnelEncryptEnabled) BOOL enableSpeedTunnelEncrypt;
+
+@property (nonatomic, getter=isSplitTunnelDnsSearch) BOOL enableSplitTunnelDnsSearch;
+
+#pragma mark - Reconnecting
+
+@property (nonatomic) NSInteger reconnectMaxTime;
+@property (nonatomic) NSInteger reconnectMaxCount;
+@property (nonatomic) NSInteger reconnectInterval;
+
+#pragma mark - VPN Type
+
+@property (nonatomic) vpn_device_type_t serverType;
+
+#pragma mark - Log
+
+@property (nonatomic) NSInteger logLevel;
+
+#pragma mark - Connection/Disconnection
+
+/*!
+ *  Connect to virtual site
+ *
+ *  @return nil or error
+ */
+- (NSError *)connect;
+
+/*!
+ *  Disconnect from virtual site
+ *
+ *  @return error or nil
+ */
+- (NSError *)disconnect;
+
+#pragma mark - VPN Configurations Operations
+/*!
+ *  Initialize VPN configuration
+ *  
+ *  Acquire or create a VPN configuration from system preferences.
+ *  This method will always be called when shared instance is inited.
+ */
+- (void)initializeVPNConfiguration:(BOOL)autoStart;
+
+- (BOOL)updateVPNPreference;
+
+/*!
+ *  Singleton
+ */
++ (instancetype)sharedInstance;
+
+@end
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/VPNConfig/ArrayVPNManager.m
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/VPNConfig/ArrayVPNManager.m	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/VPNConfig/ArrayVPNManager.m	(working copy)
@@ -0,0 +1,410 @@
+//
+//  ArrayVPNConfig.m
+//  ArraySSLVPN
+//
+//  Created by wangxy on 16/10/20.
+//  Copyright (c) 2016年 ArrayNetworks. All rights reserved.
+//
+
+#import "arrayapi.h"
+#import "ANLogger.h"
+
+#import "ArrayVPNManager.h"
+
+
+NSString * const kArrayVPNType = @"net.arraynetworks.MotionProPlus.iOSTunnel";
+
+NSString * const kArrayVPNName = @"ArrayNetworks SSL VPN";
+NSString * const kArrayVPNStatusNotification = @"net.arraynetworks.sslvpn.vpnstatus";
+NSString * const kArrayVPNDisconnected = @"net.arraynetworks.sslvpn.disconnected";
+NSString * const kArrayVPNErrorDomain = (__bridge NSString *)kPluginErrorDomain;
+
+@interface ArrayVPNManager()
+
+@property (nonatomic, strong) NSString *vpnType;
+@property (nonatomic, strong) NSMutableDictionary *vpnConfigDict;
+@property (nonatomic, strong) NSMutableDictionary *proxyConfigDict;
+@property (nonatomic) BOOL needReconnect;
+
+@end
+
+@implementation ArrayVPNManager
+
++ (instancetype)sharedInstance {
+    static ArrayVPNManager *instance;
+    static dispatch_once_t token;
+    dispatch_once(&token, ^{
+        // Init VPN config with default type and name.
+        instance = [[self alloc] init];
+        [instance initializeDefaultConfigurations];
+        [instance startObserveVPNStatus];
+    });
+    return instance;
+}
+
+- (void)initializeDefaultConfigurations {
+    // VPN name&type
+    self.vpnType = kArrayVPNType;
+    self.vpnName = kArrayVPNName;
+    
+    // VPN Flag
+    self.vpnFlag = VPN_FLAG_DEVID_LOGIN|VPN_FLAG_SKIP_LOGOUT;
+    
+    // Speed tunnel
+    self.enableSpeedTunnel = YES;
+    self.enableSpeedTunnelEncrypt = YES;
+    self.speedTunnelDispatch = VPN_DISP_AlltoUDP;
+    
+    // Log level
+    self.logLevel = [[ANLogger sharedInstance] currentLogLevel];
+    
+    // Reconnecting
+    self.reconnectMaxTime = 300; //default to 300s.
+    
+    // On demand
+    self.enableVPNOnDemand = NO;
+}
+
+- (void)dealloc {
+    if (_vpnConfigDict) {
+        [_vpnConfigDict removeAllObjects];
+    }
+    if (_proxyConfigDict) {
+        [_proxyConfigDict removeAllObjects];
+    }
+    
+    [self stopObserveVPNStatus];
+}
+
+#pragma mark - VPN Configuration
+
+- (void)startObserveVPNStatus {
+    [[NSNotificationCenter defaultCenter] addObserver:self
+                                             selector:@selector(VPNStatusDidChanged:)
+                                                 name:NEVPNStatusDidChangeNotification
+                                               object:nil];
+}
+
+- (void)stopObserveVPNStatus {
+    [[NSNotificationCenter defaultCenter] removeObserver:self
+                                                    name:NEVPNStatusDidChangeNotification
+                                                  object:nil];
+}
+
+- (void)setVpnName:(NSString *)vpnName {
+    _vpnName = [vpnName copy];
+}
+
+- (void)createVPNPreferenceWithType:(NSString *)type name:(NSString *)name autoStart:(BOOL)start {
+    NETunnelProviderManager *manager = [[NETunnelProviderManager alloc] init];
+    
+    __weak typeof(self) weakSelf = self;
+    [manager loadFromPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
+        if (error) {
+            ANError(@"Load from preference failed with error: %@", error.localizedDescription);
+            return;
+        }
+        
+        NETunnelProviderProtocol *protocol = [[NETunnelProviderProtocol alloc] init];
+        protocol.providerBundleIdentifier = type;
+        protocol.serverAddress = @"";
+        
+        manager.protocolConfiguration = protocol;
+        manager.localizedDescription = kArrayVPNName;
+        manager.enabled = YES;
+        [manager saveToPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
+            if (error) {
+                ANError(@"Save VPN preference failed with error: %@", error.localizedDescription);
+                return;
+            }
+            
+            ANInfo(@"New vpn preference named: \"%@\" created.", kArrayVPNName);
+            
+            [weakSelf initializeVPNConfiguration:start];
+        }];
+    }];
+}
+
+/**
+ Return Value:
+    YES means vpn preference should be updating.
+    NO means vpn prefernce has no changes.
+ */
+- (BOOL)updateVPNPreference {
+    NSString *originalHost = self.tunnelManager.protocolConfiguration.serverAddress;
+    if (![originalHost isEqualToString:_host]) {
+        ANInfo(@"Different server host found: original = %@, new = %@", originalHost, _host);
+        
+        self.tunnelManager.protocolConfiguration.serverAddress = _host;
+        [self.tunnelManager saveToPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
+            if (error) {
+                ANInfo(@"Update VPN preference failed with error: %@", error.localizedDescription);
+            }
+        }];
+        return YES;
+    }
+    ANInfo(@"The server host (%@) never changed, don't update.", originalHost);
+    
+    return NO;
+}
+
+- (void)initializeVPNConfiguration:(BOOL)autoStart {
+    __weak typeof(self) weakSelf = self;
+    [NETunnelProviderManager loadAllFromPreferencesWithCompletionHandler:^(NSArray<NETunnelProviderManager *> * _Nullable managers, NSError * _Nullable error) {
+        if (!managers || [managers count] == 0) {
+            ANInfo(@"There is not VPN preference in the system, should create a new one.");
+            
+            [weakSelf createVPNPreferenceWithType:_vpnType name:_vpnName autoStart:autoStart];
+        } else {
+            BOOL isAppVPNUsed = [self isAppVPNConfigurationExist:managers];
+            
+            for (NETunnelProviderManager *manager in managers) {
+                ANInfo(@"VPN configuration named \"%@\" has been found!", manager.localizedDescription);
+                
+                if (isAppVPNUsed && manager.routingMethod != NETunnelProviderRoutingMethodSourceApplication) {
+                    // App VPN is prior to L3VPN.
+                    continue;
+                }
+                
+                [manager loadFromPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
+                    if (error) {
+                        ANError( @"Tunnel provider manager load failed with error: %@", error.localizedDescription);
+                        return;
+                    }
+                    
+                    manager.enabled = YES;
+                    weakSelf.tunnelManager = manager;
+                    [manager saveToPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
+                        if (error) {
+                            ANError(@"Enable tunnel preference failed on saving it, error: %@", error.localizedDescription);
+                            return;
+                        }
+                        if (autoStart) {
+                            [weakSelf connect];
+                        }
+                    }];
+                    
+                }];
+                break;  // Only one vpn preference should be detected.
+            }
+        }
+    }];
+}
+
+- (void)removeVPNConfiguration {
+    if (self.tunnelManager != nil) {
+        [self.tunnelManager removeFromPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
+            if (error) {
+                ANError(@"Remove VPN preference failed with error: %@", error.localizedDescription);
+                return;
+            }
+        }];
+        self.tunnelManager = nil;
+    } else {
+        ANError(@"No VPN preference need to be removed.");
+    }
+}
+
+- (BOOL)isAppVPNConfigurationExist:(NSArray *)managers {
+    for (NETunnelProviderManager *manager in managers) {
+        if (manager.routingMethod == NETunnelProviderRoutingMethodSourceApplication) {
+            ANInfo(@"APP VPN configuration has been found in preference.");
+            return YES;
+        }
+    }
+    
+    return NO;
+}
+
+- (void)setVPNConfiguration {
+    if (_host.length==0 || _port.length==0) {
+        ANError(@"Invalid configuration for VPN, host: %@, port: %@", _host, _port);
+        return;
+    }
+    
+    if (!_vpnConfigDict) {
+        _vpnConfigDict = [[NSMutableDictionary alloc] init];
+    }
+    
+    NSMutableDictionary *vendorData = [[NSMutableDictionary alloc] initWithCapacity:10];
+    
+    // Server address
+    [vendorData setObject:_host forKey:(__bridge NSString *)kPluginArgHost];
+    
+    // Port
+    [vendorData setObject:_port forKey:(__bridge NSString *)kPluginArgPort];
+    
+    // Flag
+    [vendorData setObject:[@(_vpnFlag) stringValue] forKey:(__bridge NSString *)kPluginArgFlags];
+    
+    // Session
+    if (_sessionID) {
+        [vendorData setObject:_sessionID forKey:(__bridge NSString *)kPluginArgSession];
+    }
+    
+    if (_customDnsListString) {
+        [vendorData setObject:_customDnsListString forKey:(__bridge NSString *)kPluginArgCustomDNSSetting];
+    }
+    
+    // Client ID
+    if (_clientID) {
+        [vendorData setObject:_clientID forKey:(__bridge NSString *)kPluginArgClientID];
+    }
+    
+    // Device ID
+    if (_deviceID) {
+        [vendorData setObject:_deviceID forKey:(__bridge NSString *)kPluginArgDeviceID];
+    }
+    
+    // Certificate Data
+    if (_certData) {
+        [vendorData setObject:_certData forKey:(__bridge NSString *)kPluginArgCertificateData];
+    }
+    
+    // Certificate Password
+    if (_certPassword) {
+        [vendorData setObject:_certPassword forKey:(__bridge NSString *)kPluginArgCertificatePassword];
+    }
+    
+    // Speed tunnel
+    [vendorData setObject:[@(_enableSpeedTunnel) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgSpeedTunnelEnable];
+    [vendorData setObject:[@(_speedTunnelDispatch) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgSpeedTunnelDispatch];
+    [vendorData setObject:[@(_enableSpeedTunnelEncrypt) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgSpeedTunnelEncrypt];
+    [vendorData setObject:[@(_enableSplitTunnelDnsSearch) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgSplitTunnelEnableDnsSearch];
+    
+    // Log level
+    [vendorData setObject:[@(_logLevel) stringValue] forKey:(__bridge NSString *)kPluginArgLogLevel];
+    
+    // Reconnection
+    [vendorData setObject:[@(_reconnectInterval) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgReconnectInterval];
+    [vendorData setObject:[@(_reconnectMaxCount) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgReconnectMaxCount];
+    [vendorData setObject:[@(_reconnectMaxTime) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgReconnectMaxTime];
+    
+    // VPN Type
+    [vendorData setObject:[@(_serverType) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgServerType];
+    
+    [_vpnConfigDict setObject:vendorData forKey:@"VendorData"];
+    
+
+    
+    ANDebug(@"Set VPN configuration as below:\n%@", _vpnConfigDict);
+}
+
+#pragma mark - VPN Configuration Notification
+- (void)VPNStatusDidChanged:(NSNotification *)notification {
+    NEVPNStatus vpnStatus = self.tunnelManager.connection.status;
+    
+    ANInfo(@"VPN status has changed to %d.", (int)vpnStatus);
+    
+    [self updateVPNStatusWithTunnelConnectionStatus:vpnStatus];
+    
+    if (vpnStatus == NEVPNStatusDisconnected) {
+        // Connect after disconnect if the vpn plugin has been started by on-demand before.
+        if (self.needReconnect) {
+            self.needReconnect = NO;
+            [self connectInternal];
+        }
+    }
+}
+
+- (void)updateVPNStatusWithTunnelConnectionStatus:(NEVPNStatus)status {
+    ArrayVPNStatus vpnStatus = ArrayVPNInavlid;
+    
+    switch (status) {
+        case NEVPNStatusInvalid:
+            vpnStatus = ArrayVPNInavlid;
+            break;
+        case NEVPNStatusDisconnected:
+            vpnStatus = ArrayVPNDisconnected;
+            [[NSNotificationCenter defaultCenter] postNotificationName:kArrayVPNDisconnected object:nil];
+            break;
+        case NEVPNStatusConnecting:
+            vpnStatus = ArrayVPNConnecting;
+            break;
+        case NEVPNStatusConnected:
+            vpnStatus = ArrayVPNConnected;
+            break;
+        case NEVPNStatusReasserting:
+            vpnStatus = ArrayVPNReasserting;
+            break;
+        case NEVPNStatusDisconnecting:
+            vpnStatus = ArrayVPNDisconnecting;
+            break;
+            
+        default:
+            return;  // Don't update vpn status if connetion status is invalid.
+    }
+    
+    self.vpnStatus = vpnStatus;
+}
+
+- (void)setVpnStatus:(ArrayVPNStatus)vpnStatus {
+    if (_vpnStatus == vpnStatus) {
+        return;
+    }
+    
+    _vpnStatus = vpnStatus;
+    
+    [[NSNotificationCenter defaultCenter] postNotificationName:kArrayVPNStatusNotification object:nil];
+}
+
+// Connect/Disconnect
+
+- (NSError *)connect {
+    if (!self.tunnelManager || !self.tunnelManager.enabled) {
+        ANWarn(@"VPN tunnel manager is nil or not enabled, it may not be ready!");
+        [self initializeVPNConfiguration:YES];
+        return nil;
+    }
+
+    NEVPNStatus currentStatus = self.tunnelManager.connection.status;
+    if (currentStatus == NEVPNStatusConnecting || currentStatus == NEVPNStatusConnected) {
+        ANInfo(@"VPN is in status %ld, stop it first.", (long)currentStatus);
+        [self.tunnelManager.connection stopVPNTunnel];
+        self.needReconnect = YES;
+        return nil;
+    }
+    
+    return [self connectInternal];
+}
+
+- (NSError *)disconnect {
+    ANDebug(@"================>Try to stop network extension!<===============");
+    [self.tunnelManager.connection stopVPNTunnel];
+    
+    return nil;
+}
+
+- (NSError *)connectInternal {
+    BOOL ret = NO;
+    
+    if (array_vpn_is_mp_flag_set(MP_MOBILE_SPLIT_DNSSEARCH)) {
+        self.enableSplitTunnelDnsSearch = YES;
+    } else {
+        self.enableSplitTunnelDnsSearch = NO;
+    }
+    [self setVPNConfiguration];
+    
+    ANInfo(@"================>Try to start network extension!<===============");
+    
+    NSError *error = nil;
+    ret = [self.tunnelManager.connection startVPNTunnelWithOptions:_vpnConfigDict andReturnError:&error];
+    if (!ret) {
+        ANError(@"Start VPN tunnel failed with return value: %d.", ret);
+    }
+    if (error) {
+        ANError(@"Start VPN tunnel failed with error: %@", error);
+    }
+    
+    return error;
+}
+
+@end
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/VPNConfig/ArrayVPNManager.m.InfosecEnterprise
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/VPNConfig/ArrayVPNManager.m.InfosecEnterprise	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/VPNConfig/ArrayVPNManager.m.InfosecEnterprise	(working copy)
@@ -0,0 +1,429 @@
+//
+//  ArrayVPNConfig.m
+//  ArraySSLVPN
+//
+//  Created by wangxy on 16/10/20.
+//  Copyright (c) 2016年 ArrayNetworks. All rights reserved.
+//
+
+#import "arrayapi.h"
+#import "ANLogger.h"
+
+#import "ArrayVPNManager.h"
+
+#if TARGET_OS_IPHONE
+NSString * const kArrayVPNType = @"net.infosec.MotionProPlusEnterprise.iOSTunnel";
+#elif TARGET_OS_MAC
+NSString * const kArrayVPNType = @"net.arraynetworks.MacTunnel.ArrayTunnel";
+#endif
+NSString * const kArrayVPNName = @"Infosec Enterprise SSL VPN";
+NSString * const kArrayVPNStatusNotification = @"net.arraynetworks.sslvpn.vpnstatus";
+NSString * const kArrayVPNDisconnected = @"net.arraynetworks.sslvpn.disconnected";
+NSString * const kArrayVPNErrorDomain = (__bridge NSString *)kPluginErrorDomain;
+
+@interface ArrayVPNManager()
+
+@property (nonatomic, strong) NSString *vpnType;
+@property (nonatomic, strong) NSMutableDictionary *vpnConfigDict;
+@property (nonatomic, strong) NSMutableDictionary *proxyConfigDict;
+@property (nonatomic) BOOL needReconnect;
+
+@end
+
+@implementation ArrayVPNManager
+
++ (instancetype)sharedInstance {
+    static ArrayVPNManager *instance;
+    static dispatch_once_t token;
+    dispatch_once(&token, ^{
+        // Init VPN config with default type and name.
+        instance = [[self alloc] init];
+        [instance initializeDefaultConfigurations];
+        [instance startObserveVPNStatus];
+    });
+    return instance;
+}
+
+- (void)initializeDefaultConfigurations {
+    // VPN name&type
+    self.vpnType = kArrayVPNType;
+    self.vpnName = kArrayVPNName;
+    
+    // VPN Flag
+    self.vpnFlag = VPN_FLAG_DEVID_LOGIN|VPN_FLAG_SKIP_LOGOUT;
+    
+    // Speed tunnel
+    self.enableSpeedTunnel = YES;
+    self.enableSpeedTunnelEncrypt = YES;
+    self.speedTunnelDispatch = VPN_DISP_AlltoUDP;
+    
+    // Log level
+    self.logLevel = [[ANLogger sharedInstance] currentLogLevel];
+    
+    // Reconnecting
+    self.reconnectMaxTime = 300; //default to 300s.
+    
+    // On demand
+    self.enableVPNOnDemand = NO;
+}
+
+- (void)dealloc {
+    if (_vpnConfigDict) {
+        [_vpnConfigDict removeAllObjects];
+    }
+    if (_proxyConfigDict) {
+        [_proxyConfigDict removeAllObjects];
+    }
+    
+    [self stopObserveVPNStatus];
+}
+
+#pragma mark - VPN Configuration
+
+- (void)startObserveVPNStatus {
+    [[NSNotificationCenter defaultCenter] addObserver:self
+                                             selector:@selector(VPNStatusDidChanged:)
+                                                 name:NEVPNStatusDidChangeNotification
+                                               object:nil];
+}
+
+- (void)stopObserveVPNStatus {
+    [[NSNotificationCenter defaultCenter] removeObserver:self
+                                                    name:NEVPNStatusDidChangeNotification
+                                                  object:nil];
+}
+
+- (void)setVpnName:(NSString *)vpnName {
+    _vpnName = [vpnName copy];
+}
+
+- (void)createVPNPreferenceWithType:(NSString *)type name:(NSString *)name autoStart:(BOOL)start {
+    NETunnelProviderManager *manager = [[NETunnelProviderManager alloc] init];
+    
+    __weak typeof(self) weakSelf = self;
+    [manager loadFromPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
+        if (error) {
+            ANError(@"Load from preference failed with error: %@", error.localizedDescription);
+            return;
+        }
+        
+        NETunnelProviderProtocol *protocol = [[NETunnelProviderProtocol alloc] init];
+        protocol.providerBundleIdentifier = type;
+        protocol.serverAddress = @"";
+        
+        manager.protocolConfiguration = protocol;
+        manager.localizedDescription = kArrayVPNName;
+        manager.enabled = YES;
+        [manager saveToPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
+            if (error) {
+                ANError(@"Save VPN preference failed with error: %@", error.localizedDescription);
+                return;
+            }
+            
+            ANInfo(@"New vpn preference named: \"%@\" created.", kArrayVPNName);
+            
+            [weakSelf initializeVPNConfiguration:start];
+        }];
+    }];
+}
+
+/**
+ Return Value:
+    YES means vpn preference should be updating.
+    NO means vpn prefernce has no changes.
+ */
+- (BOOL)updateVPNPreference {
+    NSString *originalHost = self.tunnelManager.protocolConfiguration.serverAddress;
+    if (![originalHost isEqualToString:_host]) {
+        ANInfo(@"Different server host found: original = %@, new = %@", originalHost, _host);
+        
+        self.tunnelManager.protocolConfiguration.serverAddress = _host;
+        [self.tunnelManager saveToPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
+            if (error) {
+                ANInfo(@"Update VPN preference failed with error: %@", error.localizedDescription);
+            }
+        }];
+        return YES;
+    }
+    ANInfo(@"The server host (%@) never changed, don't update.", originalHost);
+    
+    return NO;
+}
+
+- (void)initializeVPNConfiguration:(BOOL)autoStart {
+    __weak typeof(self) weakSelf = self;
+    [NETunnelProviderManager loadAllFromPreferencesWithCompletionHandler:^(NSArray<NETunnelProviderManager *> * _Nullable managers, NSError * _Nullable error) {
+        if (!managers || [managers count] == 0) {
+            ANInfo(@"There is not VPN preference in the system, should create a new one.");
+            
+            [weakSelf createVPNPreferenceWithType:_vpnType name:_vpnName autoStart:autoStart];
+        } else {
+            BOOL isAppVPNUsed = [self isAppVPNConfigurationExist:managers];
+            
+            for (NETunnelProviderManager *manager in managers) {
+                ANInfo(@"VPN configuration named \"%@\" has been found!", manager.localizedDescription);
+                
+                if (isAppVPNUsed && manager.routingMethod != NETunnelProviderRoutingMethodSourceApplication) {
+                    // App VPN is prior to L3VPN.
+                    continue;
+                }
+                
+                [manager loadFromPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
+                    if (error) {
+                        ANError( @"Tunnel provider manager load failed with error: %@", error.localizedDescription);
+                        return;
+                    }
+                    
+                    manager.enabled = YES;
+                    weakSelf.tunnelManager = manager;
+                    [manager saveToPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
+                        if (error) {
+                            ANError(@"Enable tunnel preference failed on saving it, error: %@", error.localizedDescription);
+                            return;
+                        }
+                        if (autoStart) {
+                            [weakSelf connect];
+                        }
+                    }];
+                    
+                }];
+                break;  // Only one vpn preference should be detected.
+            }
+        }
+    }];
+}
+
+- (void)removeVPNConfiguration {
+    if (self.tunnelManager != nil) {
+        [self.tunnelManager removeFromPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
+            if (error) {
+                ANError(@"Remove VPN preference failed with error: %@", error.localizedDescription);
+                return;
+            }
+        }];
+        self.tunnelManager = nil;
+    } else {
+        ANError(@"No VPN preference need to be removed.");
+    }
+}
+
+- (BOOL)isAppVPNConfigurationExist:(NSArray *)managers {
+    for (NETunnelProviderManager *manager in managers) {
+        if (manager.routingMethod == NETunnelProviderRoutingMethodSourceApplication) {
+            ANInfo(@"APP VPN configuration has been found in preference.");
+            return YES;
+        }
+    }
+    
+    return NO;
+}
+
+- (void)setVPNConfiguration {
+    if (_host.length==0 || _port.length==0) {
+        ANError(@"Invalid configuration for VPN, host: %@, port: %@", _host, _port);
+        return;
+    }
+    
+    if (!_vpnConfigDict) {
+        _vpnConfigDict = [[NSMutableDictionary alloc] init];
+    }
+    
+    NSMutableDictionary *vendorData = [[NSMutableDictionary alloc] initWithCapacity:10];
+    
+    // Server address
+    [vendorData setObject:_host forKey:(__bridge NSString *)kPluginArgHost];
+    
+    // Port
+    [vendorData setObject:_port forKey:(__bridge NSString *)kPluginArgPort];
+    
+    // Flag
+    [vendorData setObject:[@(_vpnFlag) stringValue] forKey:(__bridge NSString *)kPluginArgFlags];
+    
+    // Session
+    if (_sessionID) {
+        [vendorData setObject:_sessionID forKey:(__bridge NSString *)kPluginArgSession];
+    }
+    
+    if (_customDnsListString) {
+        [vendorData setObject:_customDnsListString forKey:(__bridge NSString *)kPluginArgCustomDNSSetting];
+    }
+    
+    // Client ID
+    if (_clientID) {
+        [vendorData setObject:_clientID forKey:(__bridge NSString *)kPluginArgClientID];
+    }
+    
+    // Device ID
+    if (_deviceID) {
+        [vendorData setObject:_deviceID forKey:(__bridge NSString *)kPluginArgDeviceID];
+    }
+    
+    // Certificate Data
+    if (_certData) {
+        [vendorData setObject:_certData forKey:(__bridge NSString *)kPluginArgCertificateData];
+    }
+    
+    // Certificate Password
+    if (_certPassword) {
+        [vendorData setObject:_certPassword forKey:(__bridge NSString *)kPluginArgCertificatePassword];
+    }
+    
+    // Speed tunnel
+    [vendorData setObject:[@(_enableSpeedTunnel) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgSpeedTunnelEnable];
+    [vendorData setObject:[@(_speedTunnelDispatch) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgSpeedTunnelDispatch];
+    [vendorData setObject:[@(_enableSpeedTunnelEncrypt) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgSpeedTunnelEncrypt];
+    [vendorData setObject:[@(_enableSplitTunnelDnsSearch) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgSplitTunnelEnableDnsSearch];
+    
+    // Log level
+    [vendorData setObject:[@(_logLevel) stringValue] forKey:(__bridge NSString *)kPluginArgLogLevel];
+    
+    // Reconnection
+    [vendorData setObject:[@(_reconnectInterval) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgReconnectInterval];
+    [vendorData setObject:[@(_reconnectMaxCount) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgReconnectMaxCount];
+    [vendorData setObject:[@(_reconnectMaxTime) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgReconnectMaxTime];
+    
+    // VPN Type
+    [vendorData setObject:[@(_serverType) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgServerType];
+    
+    [_vpnConfigDict setObject:vendorData forKey:@"VendorData"];
+    
+#if !TARGET_OS_IPHONE
+    // Proxy
+    if (_proxyHost) {
+        [vendorData setObject:_proxyHost forKey:(__bridge NSString *)kPluginArgProxyAddress];
+    } else {
+        [vendorData setObject:@"" forKey:(__bridge NSString *)kPluginArgProxyAddress];
+    }
+    if (_proxyUsername) {
+        [vendorData setObject:_proxyUsername forKey:(__bridge NSString *)kPluginArgProxyUsername];
+    } else {
+        [vendorData setObject:@"" forKey:(__bridge NSString *)kPluginArgProxyUsername];
+    }
+    if (_proxyPassword) {
+        [vendorData setObject:_proxyPassword forKey:(__bridge NSString *)kPluginArgProxyPassword];
+    } else {
+        [vendorData setObject:@"" forKey:(__bridge NSString *)kPluginArgProxyPassword];
+    }
+#endif
+    
+    ANDebug(@"Set VPN configuration as below:\n%@", _vpnConfigDict);
+}
+
+#pragma mark - VPN Configuration Notification
+- (void)VPNStatusDidChanged:(NSNotification *)notification {
+    NEVPNStatus vpnStatus = self.tunnelManager.connection.status;
+    
+    ANInfo(@"VPN status has changed to %d.", (int)vpnStatus);
+    
+    [self updateVPNStatusWithTunnelConnectionStatus:vpnStatus];
+    
+    if (vpnStatus == NEVPNStatusDisconnected) {
+        // Connect after disconnect if the vpn plugin has been started by on-demand before.
+        if (self.needReconnect) {
+            self.needReconnect = NO;
+            [self connectInternal];
+        }
+    }
+}
+
+- (void)updateVPNStatusWithTunnelConnectionStatus:(NEVPNStatus)status {
+    ArrayVPNStatus vpnStatus = ArrayVPNInavlid;
+    
+    switch (status) {
+        case NEVPNStatusInvalid:
+            vpnStatus = ArrayVPNInavlid;
+            break;
+        case NEVPNStatusDisconnected:
+            vpnStatus = ArrayVPNDisconnected;
+            [[NSNotificationCenter defaultCenter] postNotificationName:kArrayVPNDisconnected object:nil];
+            break;
+        case NEVPNStatusConnecting:
+            vpnStatus = ArrayVPNConnecting;
+            break;
+        case NEVPNStatusConnected:
+            vpnStatus = ArrayVPNConnected;
+            break;
+        case NEVPNStatusReasserting:
+            vpnStatus = ArrayVPNReasserting;
+            break;
+        case NEVPNStatusDisconnecting:
+            vpnStatus = ArrayVPNDisconnecting;
+            break;
+            
+        default:
+            return;  // Don't update vpn status if connetion status is invalid.
+    }
+    
+    self.vpnStatus = vpnStatus;
+}
+
+- (void)setVpnStatus:(ArrayVPNStatus)vpnStatus {
+    if (_vpnStatus == vpnStatus) {
+        return;
+    }
+    
+    _vpnStatus = vpnStatus;
+    
+    [[NSNotificationCenter defaultCenter] postNotificationName:kArrayVPNStatusNotification object:nil];
+}
+
+// Connect/Disconnect
+
+- (NSError *)connect {
+    if (!self.tunnelManager || !self.tunnelManager.enabled) {
+        ANWarn(@"VPN tunnel manager is nil or not enabled, it may not be ready!");
+        [self initializeVPNConfiguration:YES];
+        return nil;
+    }
+
+    NEVPNStatus currentStatus = self.tunnelManager.connection.status;
+    if (currentStatus == NEVPNStatusConnecting || currentStatus == NEVPNStatusConnected) {
+        ANInfo(@"VPN is in status %ld, stop it first.", (long)currentStatus);
+        [self.tunnelManager.connection stopVPNTunnel];
+        self.needReconnect = YES;
+        return nil;
+    }
+    
+    return [self connectInternal];
+}
+
+- (NSError *)disconnect {
+    ANDebug(@"================>Try to stop network extension!<===============");
+    [self.tunnelManager.connection stopVPNTunnel];
+    
+    return nil;
+}
+
+- (NSError *)connectInternal {
+    BOOL ret = NO;
+    
+    if (array_vpn_is_mp_flag_set(MP_MOBILE_SPLIT_DNSSEARCH)) {
+        self.enableSplitTunnelDnsSearch = YES;
+    } else {
+        self.enableSplitTunnelDnsSearch = NO;
+    }
+    [self setVPNConfiguration];
+    
+    ANInfo(@"================>Try to start network extension!<===============");
+    
+    NSError *error = nil;
+    ret = [self.tunnelManager.connection startVPNTunnelWithOptions:_vpnConfigDict andReturnError:&error];
+    if (!ret) {
+        ANError(@"Start VPN tunnel failed with return value: %d.", ret);
+    }
+    if (error) {
+        ANError(@"Start VPN tunnel failed with error: %@", error);
+    }
+    
+    return error;
+}
+
+@end
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/VPNConfig/ArrayVPNManager.m.InfosecStore
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/VPNConfig/ArrayVPNManager.m.InfosecStore	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/VPNConfig/ArrayVPNManager.m.InfosecStore	(working copy)
@@ -0,0 +1,429 @@
+//
+//  ArrayVPNConfig.m
+//  ArraySSLVPN
+//
+//  Created by wangxy on 16/10/20.
+//  Copyright (c) 2016年 ArrayNetworks. All rights reserved.
+//
+
+#import "arrayapi.h"
+#import "ANLogger.h"
+
+#import "ArrayVPNManager.h"
+
+#if TARGET_OS_IPHONE
+NSString * const kArrayVPNType = @"net.infosec.MotionPro.iOSTunnel";
+#elif TARGET_OS_MAC
+NSString * const kArrayVPNType = @"net.arraynetworks.MacTunnel.ArrayTunnel";
+#endif
+NSString * const kArrayVPNName = @"MotionPro Infosec";
+NSString * const kArrayVPNStatusNotification = @"net.arraynetworks.sslvpn.vpnstatus";
+NSString * const kArrayVPNDisconnected = @"net.arraynetworks.sslvpn.disconnected";
+NSString * const kArrayVPNErrorDomain = (__bridge NSString *)kPluginErrorDomain;
+
+@interface ArrayVPNManager()
+
+@property (nonatomic, strong) NSString *vpnType;
+@property (nonatomic, strong) NSMutableDictionary *vpnConfigDict;
+@property (nonatomic, strong) NSMutableDictionary *proxyConfigDict;
+@property (nonatomic) BOOL needReconnect;
+
+@end
+
+@implementation ArrayVPNManager
+
++ (instancetype)sharedInstance {
+    static ArrayVPNManager *instance;
+    static dispatch_once_t token;
+    dispatch_once(&token, ^{
+        // Init VPN config with default type and name.
+        instance = [[self alloc] init];
+        [instance initializeDefaultConfigurations];
+        [instance startObserveVPNStatus];
+    });
+    return instance;
+}
+
+- (void)initializeDefaultConfigurations {
+    // VPN name&type
+    self.vpnType = kArrayVPNType;
+    self.vpnName = kArrayVPNName;
+    
+    // VPN Flag
+    self.vpnFlag = VPN_FLAG_DEVID_LOGIN|VPN_FLAG_SKIP_LOGOUT;
+    
+    // Speed tunnel
+    self.enableSpeedTunnel = YES;
+    self.enableSpeedTunnelEncrypt = YES;
+    self.speedTunnelDispatch = VPN_DISP_AlltoUDP;
+    
+    // Log level
+    self.logLevel = [[ANLogger sharedInstance] currentLogLevel];
+    
+    // Reconnecting
+    self.reconnectMaxTime = 300; //default to 300s.
+    
+    // On demand
+    self.enableVPNOnDemand = NO;
+}
+
+- (void)dealloc {
+    if (_vpnConfigDict) {
+        [_vpnConfigDict removeAllObjects];
+    }
+    if (_proxyConfigDict) {
+        [_proxyConfigDict removeAllObjects];
+    }
+    
+    [self stopObserveVPNStatus];
+}
+
+#pragma mark - VPN Configuration
+
+- (void)startObserveVPNStatus {
+    [[NSNotificationCenter defaultCenter] addObserver:self
+                                             selector:@selector(VPNStatusDidChanged:)
+                                                 name:NEVPNStatusDidChangeNotification
+                                               object:nil];
+}
+
+- (void)stopObserveVPNStatus {
+    [[NSNotificationCenter defaultCenter] removeObserver:self
+                                                    name:NEVPNStatusDidChangeNotification
+                                                  object:nil];
+}
+
+- (void)setVpnName:(NSString *)vpnName {
+    _vpnName = [vpnName copy];
+}
+
+- (void)createVPNPreferenceWithType:(NSString *)type name:(NSString *)name autoStart:(BOOL)start {
+    NETunnelProviderManager *manager = [[NETunnelProviderManager alloc] init];
+    
+    __weak typeof(self) weakSelf = self;
+    [manager loadFromPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
+        if (error) {
+            ANError(@"Load from preference failed with error: %@", error.localizedDescription);
+            return;
+        }
+        
+        NETunnelProviderProtocol *protocol = [[NETunnelProviderProtocol alloc] init];
+        protocol.providerBundleIdentifier = type;
+        protocol.serverAddress = @"";
+        
+        manager.protocolConfiguration = protocol;
+        manager.localizedDescription = kArrayVPNName;
+        manager.enabled = YES;
+        [manager saveToPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
+            if (error) {
+                ANError(@"Save VPN preference failed with error: %@", error.localizedDescription);
+                return;
+            }
+            
+            ANInfo(@"New vpn preference named: \"%@\" created.", kArrayVPNName);
+            
+            [weakSelf initializeVPNConfiguration:start];
+        }];
+    }];
+}
+
+/**
+ Return Value:
+    YES means vpn preference should be updating.
+    NO means vpn prefernce has no changes.
+ */
+- (BOOL)updateVPNPreference {
+    NSString *originalHost = self.tunnelManager.protocolConfiguration.serverAddress;
+    if (![originalHost isEqualToString:_host]) {
+        ANInfo(@"Different server host found: original = %@, new = %@", originalHost, _host);
+        
+        self.tunnelManager.protocolConfiguration.serverAddress = _host;
+        [self.tunnelManager saveToPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
+            if (error) {
+                ANInfo(@"Update VPN preference failed with error: %@", error.localizedDescription);
+            }
+        }];
+        return YES;
+    }
+    ANInfo(@"The server host (%@) never changed, don't update.", originalHost);
+    
+    return NO;
+}
+
+- (void)initializeVPNConfiguration:(BOOL)autoStart {
+    __weak typeof(self) weakSelf = self;
+    [NETunnelProviderManager loadAllFromPreferencesWithCompletionHandler:^(NSArray<NETunnelProviderManager *> * _Nullable managers, NSError * _Nullable error) {
+        if (!managers || [managers count] == 0) {
+            ANInfo(@"There is not VPN preference in the system, should create a new one.");
+            
+            [weakSelf createVPNPreferenceWithType:_vpnType name:_vpnName autoStart:autoStart];
+        } else {
+            BOOL isAppVPNUsed = [self isAppVPNConfigurationExist:managers];
+            
+            for (NETunnelProviderManager *manager in managers) {
+                ANInfo(@"VPN configuration named \"%@\" has been found!", manager.localizedDescription);
+                
+                if (isAppVPNUsed && manager.routingMethod != NETunnelProviderRoutingMethodSourceApplication) {
+                    // App VPN is prior to L3VPN.
+                    continue;
+                }
+                
+                [manager loadFromPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
+                    if (error) {
+                        ANError( @"Tunnel provider manager load failed with error: %@", error.localizedDescription);
+                        return;
+                    }
+                    
+                    manager.enabled = YES;
+                    weakSelf.tunnelManager = manager;
+                    [manager saveToPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
+                        if (error) {
+                            ANError(@"Enable tunnel preference failed on saving it, error: %@", error.localizedDescription);
+                            return;
+                        }
+                        if (autoStart) {
+                            [weakSelf connect];
+                        }
+                    }];
+                    
+                }];
+                break;  // Only one vpn preference should be detected.
+            }
+        }
+    }];
+}
+
+- (void)removeVPNConfiguration {
+    if (self.tunnelManager != nil) {
+        [self.tunnelManager removeFromPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
+            if (error) {
+                ANError(@"Remove VPN preference failed with error: %@", error.localizedDescription);
+                return;
+            }
+        }];
+        self.tunnelManager = nil;
+    } else {
+        ANError(@"No VPN preference need to be removed.");
+    }
+}
+
+- (BOOL)isAppVPNConfigurationExist:(NSArray *)managers {
+    for (NETunnelProviderManager *manager in managers) {
+        if (manager.routingMethod == NETunnelProviderRoutingMethodSourceApplication) {
+            ANInfo(@"APP VPN configuration has been found in preference.");
+            return YES;
+        }
+    }
+    
+    return NO;
+}
+
+- (void)setVPNConfiguration {
+    if (_host.length==0 || _port.length==0) {
+        ANError(@"Invalid configuration for VPN, host: %@, port: %@", _host, _port);
+        return;
+    }
+    
+    if (!_vpnConfigDict) {
+        _vpnConfigDict = [[NSMutableDictionary alloc] init];
+    }
+    
+    NSMutableDictionary *vendorData = [[NSMutableDictionary alloc] initWithCapacity:10];
+    
+    // Server address
+    [vendorData setObject:_host forKey:(__bridge NSString *)kPluginArgHost];
+    
+    // Port
+    [vendorData setObject:_port forKey:(__bridge NSString *)kPluginArgPort];
+    
+    // Flag
+    [vendorData setObject:[@(_vpnFlag) stringValue] forKey:(__bridge NSString *)kPluginArgFlags];
+    
+    // Session
+    if (_sessionID) {
+        [vendorData setObject:_sessionID forKey:(__bridge NSString *)kPluginArgSession];
+    }
+    
+    if (_customDnsListString) {
+        [vendorData setObject:_customDnsListString forKey:(__bridge NSString *)kPluginArgCustomDNSSetting];
+    }
+    
+    // Client ID
+    if (_clientID) {
+        [vendorData setObject:_clientID forKey:(__bridge NSString *)kPluginArgClientID];
+    }
+    
+    // Device ID
+    if (_deviceID) {
+        [vendorData setObject:_deviceID forKey:(__bridge NSString *)kPluginArgDeviceID];
+    }
+    
+    // Certificate Data
+    if (_certData) {
+        [vendorData setObject:_certData forKey:(__bridge NSString *)kPluginArgCertificateData];
+    }
+    
+    // Certificate Password
+    if (_certPassword) {
+        [vendorData setObject:_certPassword forKey:(__bridge NSString *)kPluginArgCertificatePassword];
+    }
+    
+    // Speed tunnel
+    [vendorData setObject:[@(_enableSpeedTunnel) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgSpeedTunnelEnable];
+    [vendorData setObject:[@(_speedTunnelDispatch) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgSpeedTunnelDispatch];
+    [vendorData setObject:[@(_enableSpeedTunnelEncrypt) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgSpeedTunnelEncrypt];
+    [vendorData setObject:[@(_enableSplitTunnelDnsSearch) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgSplitTunnelEnableDnsSearch];
+    
+    // Log level
+    [vendorData setObject:[@(_logLevel) stringValue] forKey:(__bridge NSString *)kPluginArgLogLevel];
+    
+    // Reconnection
+    [vendorData setObject:[@(_reconnectInterval) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgReconnectInterval];
+    [vendorData setObject:[@(_reconnectMaxCount) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgReconnectMaxCount];
+    [vendorData setObject:[@(_reconnectMaxTime) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgReconnectMaxTime];
+    
+    // VPN Type
+    [vendorData setObject:[@(_serverType) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgServerType];
+    
+    [_vpnConfigDict setObject:vendorData forKey:@"VendorData"];
+    
+#if !TARGET_OS_IPHONE
+    // Proxy
+    if (_proxyHost) {
+        [vendorData setObject:_proxyHost forKey:(__bridge NSString *)kPluginArgProxyAddress];
+    } else {
+        [vendorData setObject:@"" forKey:(__bridge NSString *)kPluginArgProxyAddress];
+    }
+    if (_proxyUsername) {
+        [vendorData setObject:_proxyUsername forKey:(__bridge NSString *)kPluginArgProxyUsername];
+    } else {
+        [vendorData setObject:@"" forKey:(__bridge NSString *)kPluginArgProxyUsername];
+    }
+    if (_proxyPassword) {
+        [vendorData setObject:_proxyPassword forKey:(__bridge NSString *)kPluginArgProxyPassword];
+    } else {
+        [vendorData setObject:@"" forKey:(__bridge NSString *)kPluginArgProxyPassword];
+    }
+#endif
+    
+    ANDebug(@"Set VPN configuration as below:\n%@", _vpnConfigDict);
+}
+
+#pragma mark - VPN Configuration Notification
+- (void)VPNStatusDidChanged:(NSNotification *)notification {
+    NEVPNStatus vpnStatus = self.tunnelManager.connection.status;
+    
+    ANInfo(@"VPN status has changed to %d.", (int)vpnStatus);
+    
+    [self updateVPNStatusWithTunnelConnectionStatus:vpnStatus];
+    
+    if (vpnStatus == NEVPNStatusDisconnected) {
+        // Connect after disconnect if the vpn plugin has been started by on-demand before.
+        if (self.needReconnect) {
+            self.needReconnect = NO;
+            [self connectInternal];
+        }
+    }
+}
+
+- (void)updateVPNStatusWithTunnelConnectionStatus:(NEVPNStatus)status {
+    ArrayVPNStatus vpnStatus = ArrayVPNInavlid;
+    
+    switch (status) {
+        case NEVPNStatusInvalid:
+            vpnStatus = ArrayVPNInavlid;
+            break;
+        case NEVPNStatusDisconnected:
+            vpnStatus = ArrayVPNDisconnected;
+            [[NSNotificationCenter defaultCenter] postNotificationName:kArrayVPNDisconnected object:nil];
+            break;
+        case NEVPNStatusConnecting:
+            vpnStatus = ArrayVPNConnecting;
+            break;
+        case NEVPNStatusConnected:
+            vpnStatus = ArrayVPNConnected;
+            break;
+        case NEVPNStatusReasserting:
+            vpnStatus = ArrayVPNReasserting;
+            break;
+        case NEVPNStatusDisconnecting:
+            vpnStatus = ArrayVPNDisconnecting;
+            break;
+            
+        default:
+            return;  // Don't update vpn status if connetion status is invalid.
+    }
+    
+    self.vpnStatus = vpnStatus;
+}
+
+- (void)setVpnStatus:(ArrayVPNStatus)vpnStatus {
+    if (_vpnStatus == vpnStatus) {
+        return;
+    }
+    
+    _vpnStatus = vpnStatus;
+    
+    [[NSNotificationCenter defaultCenter] postNotificationName:kArrayVPNStatusNotification object:nil];
+}
+
+// Connect/Disconnect
+
+- (NSError *)connect {
+    if (!self.tunnelManager || !self.tunnelManager.enabled) {
+        ANWarn(@"VPN tunnel manager is nil or not enabled, it may not be ready!");
+        [self initializeVPNConfiguration:YES];
+        return nil;
+    }
+
+    NEVPNStatus currentStatus = self.tunnelManager.connection.status;
+    if (currentStatus == NEVPNStatusConnecting || currentStatus == NEVPNStatusConnected) {
+        ANInfo(@"VPN is in status %ld, stop it first.", (long)currentStatus);
+        [self.tunnelManager.connection stopVPNTunnel];
+        self.needReconnect = YES;
+        return nil;
+    }
+    
+    return [self connectInternal];
+}
+
+- (NSError *)disconnect {
+    ANDebug(@"================>Try to stop network extension!<===============");
+    [self.tunnelManager.connection stopVPNTunnel];
+    
+    return nil;
+}
+
+- (NSError *)connectInternal {
+    BOOL ret = NO;
+    
+    if (array_vpn_is_mp_flag_set(MP_MOBILE_SPLIT_DNSSEARCH)) {
+        self.enableSplitTunnelDnsSearch = YES;
+    } else {
+        self.enableSplitTunnelDnsSearch = NO;
+    }
+    [self setVPNConfiguration];
+    
+    ANInfo(@"================>Try to start network extension!<===============");
+    
+    NSError *error = nil;
+    ret = [self.tunnelManager.connection startVPNTunnelWithOptions:_vpnConfigDict andReturnError:&error];
+    if (!ret) {
+        ANError(@"Start VPN tunnel failed with return value: %d.", ret);
+    }
+    if (error) {
+        ANError(@"Start VPN tunnel failed with error: %@", error);
+    }
+    
+    return error;
+}
+
+@end
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/VPNConfig/ArrayVPNManager.m.MotionPro
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/VPNConfig/ArrayVPNManager.m.MotionPro	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/VPNConfig/ArrayVPNManager.m.MotionPro	(working copy)
@@ -0,0 +1,429 @@
+//
+//  ArrayVPNConfig.m
+//  ArraySSLVPN
+//
+//  Created by wangxy on 16/10/20.
+//  Copyright (c) 2016年 ArrayNetworks. All rights reserved.
+//
+
+#import "arrayapi.h"
+#import "ANLogger.h"
+
+#import "ArrayVPNManager.h"
+
+#if TARGET_OS_IPHONE
+NSString * const kArrayVPNType = @"net.arraynetworks.MotionPro.iOSTunnel";
+#elif TARGET_OS_MAC
+NSString * const kArrayVPNType = @"net.arraynetworks.MacTunnel.ArrayTunnel";
+#endif
+NSString * const kArrayVPNName = @"MotionPro";
+NSString * const kArrayVPNStatusNotification = @"net.arraynetworks.sslvpn.vpnstatus";
+NSString * const kArrayVPNDisconnected = @"net.arraynetworks.sslvpn.disconnected";
+NSString * const kArrayVPNErrorDomain = (__bridge NSString *)kPluginErrorDomain;
+
+@interface ArrayVPNManager()
+
+@property (nonatomic, strong) NSString *vpnType;
+@property (nonatomic, strong) NSMutableDictionary *vpnConfigDict;
+@property (nonatomic, strong) NSMutableDictionary *proxyConfigDict;
+@property (nonatomic) BOOL needReconnect;
+
+@end
+
+@implementation ArrayVPNManager
+
++ (instancetype)sharedInstance {
+    static ArrayVPNManager *instance;
+    static dispatch_once_t token;
+    dispatch_once(&token, ^{
+        // Init VPN config with default type and name.
+        instance = [[self alloc] init];
+        [instance initializeDefaultConfigurations];
+        [instance startObserveVPNStatus];
+    });
+    return instance;
+}
+
+- (void)initializeDefaultConfigurations {
+    // VPN name&type
+    self.vpnType = kArrayVPNType;
+    self.vpnName = kArrayVPNName;
+    
+    // VPN Flag
+    self.vpnFlag = VPN_FLAG_DEVID_LOGIN|VPN_FLAG_SKIP_LOGOUT;
+    
+    // Speed tunnel
+    self.enableSpeedTunnel = YES;
+    self.enableSpeedTunnelEncrypt = YES;
+    self.speedTunnelDispatch = VPN_DISP_AlltoUDP;
+    
+    // Log level
+    self.logLevel = [[ANLogger sharedInstance] currentLogLevel];
+    
+    // Reconnecting
+    self.reconnectMaxTime = 300; //default to 300s.
+    
+    // On demand
+    self.enableVPNOnDemand = NO;
+}
+
+- (void)dealloc {
+    if (_vpnConfigDict) {
+        [_vpnConfigDict removeAllObjects];
+    }
+    if (_proxyConfigDict) {
+        [_proxyConfigDict removeAllObjects];
+    }
+    
+    [self stopObserveVPNStatus];
+}
+
+#pragma mark - VPN Configuration
+
+- (void)startObserveVPNStatus {
+    [[NSNotificationCenter defaultCenter] addObserver:self
+                                             selector:@selector(VPNStatusDidChanged:)
+                                                 name:NEVPNStatusDidChangeNotification
+                                               object:nil];
+}
+
+- (void)stopObserveVPNStatus {
+    [[NSNotificationCenter defaultCenter] removeObserver:self
+                                                    name:NEVPNStatusDidChangeNotification
+                                                  object:nil];
+}
+
+- (void)setVpnName:(NSString *)vpnName {
+    _vpnName = [vpnName copy];
+}
+
+- (void)createVPNPreferenceWithType:(NSString *)type name:(NSString *)name autoStart:(BOOL)start {
+    NETunnelProviderManager *manager = [[NETunnelProviderManager alloc] init];
+    
+    __weak typeof(self) weakSelf = self;
+    [manager loadFromPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
+        if (error) {
+            ANError(@"Load from preference failed with error: %@", error.localizedDescription);
+            return;
+        }
+        
+        NETunnelProviderProtocol *protocol = [[NETunnelProviderProtocol alloc] init];
+        protocol.providerBundleIdentifier = type;
+        protocol.serverAddress = @"";
+        
+        manager.protocolConfiguration = protocol;
+        manager.localizedDescription = kArrayVPNName;
+        manager.enabled = YES;
+        [manager saveToPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
+            if (error) {
+                ANError(@"Save VPN preference failed with error: %@", error.localizedDescription);
+                return;
+            }
+            
+            ANInfo(@"New vpn preference named: \"%@\" created.", kArrayVPNName);
+            
+            [weakSelf initializeVPNConfiguration:start];
+        }];
+    }];
+}
+
+/**
+ Return Value:
+    YES means vpn preference should be updating.
+    NO means vpn prefernce has no changes.
+ */
+- (BOOL)updateVPNPreference {
+    NSString *originalHost = self.tunnelManager.protocolConfiguration.serverAddress;
+    if (![originalHost isEqualToString:_host]) {
+        ANInfo(@"Different server host found: original = %@, new = %@", originalHost, _host);
+        
+        self.tunnelManager.protocolConfiguration.serverAddress = _host;
+        [self.tunnelManager saveToPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
+            if (error) {
+                ANInfo(@"Update VPN preference failed with error: %@", error.localizedDescription);
+            }
+        }];
+        return YES;
+    }
+    ANInfo(@"The server host (%@) never changed, don't update.", originalHost);
+    
+    return NO;
+}
+
+- (void)initializeVPNConfiguration:(BOOL)autoStart {
+    __weak typeof(self) weakSelf = self;
+    [NETunnelProviderManager loadAllFromPreferencesWithCompletionHandler:^(NSArray<NETunnelProviderManager *> * _Nullable managers, NSError * _Nullable error) {
+        if (!managers || [managers count] == 0) {
+            ANInfo(@"There is not VPN preference in the system, should create a new one.");
+            
+            [weakSelf createVPNPreferenceWithType:_vpnType name:_vpnName autoStart:autoStart];
+        } else {
+            BOOL isAppVPNUsed = [self isAppVPNConfigurationExist:managers];
+            
+            for (NETunnelProviderManager *manager in managers) {
+                ANInfo(@"VPN configuration named \"%@\" has been found!", manager.localizedDescription);
+                
+                if (isAppVPNUsed && manager.routingMethod != NETunnelProviderRoutingMethodSourceApplication) {
+                    // App VPN is prior to L3VPN.
+                    continue;
+                }
+                
+                [manager loadFromPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
+                    if (error) {
+                        ANError( @"Tunnel provider manager load failed with error: %@", error.localizedDescription);
+                        return;
+                    }
+                    
+                    manager.enabled = YES;
+                    weakSelf.tunnelManager = manager;
+                    [manager saveToPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
+                        if (error) {
+                            ANError(@"Enable tunnel preference failed on saving it, error: %@", error.localizedDescription);
+                            return;
+                        }
+                        if (autoStart) {
+                            [weakSelf connect];
+                        }
+                    }];
+                    
+                }];
+                break;  // Only one vpn preference should be detected.
+            }
+        }
+    }];
+}
+
+- (void)removeVPNConfiguration {
+    if (self.tunnelManager != nil) {
+        [self.tunnelManager removeFromPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
+            if (error) {
+                ANError(@"Remove VPN preference failed with error: %@", error.localizedDescription);
+                return;
+            }
+        }];
+        self.tunnelManager = nil;
+    } else {
+        ANError(@"No VPN preference need to be removed.");
+    }
+}
+
+- (BOOL)isAppVPNConfigurationExist:(NSArray *)managers {
+    for (NETunnelProviderManager *manager in managers) {
+        if (manager.routingMethod == NETunnelProviderRoutingMethodSourceApplication) {
+            ANInfo(@"APP VPN configuration has been found in preference.");
+            return YES;
+        }
+    }
+    
+    return NO;
+}
+
+- (void)setVPNConfiguration {
+    if (_host.length==0 || _port.length==0) {
+        ANError(@"Invalid configuration for VPN, host: %@, port: %@", _host, _port);
+        return;
+    }
+    
+    if (!_vpnConfigDict) {
+        _vpnConfigDict = [[NSMutableDictionary alloc] init];
+    }
+    
+    NSMutableDictionary *vendorData = [[NSMutableDictionary alloc] initWithCapacity:10];
+    
+    // Server address
+    [vendorData setObject:_host forKey:(__bridge NSString *)kPluginArgHost];
+    
+    // Port
+    [vendorData setObject:_port forKey:(__bridge NSString *)kPluginArgPort];
+    
+    // Flag
+    [vendorData setObject:[@(_vpnFlag) stringValue] forKey:(__bridge NSString *)kPluginArgFlags];
+    
+    // Session
+    if (_sessionID) {
+        [vendorData setObject:_sessionID forKey:(__bridge NSString *)kPluginArgSession];
+    }
+    
+    if (_customDnsListString) {
+        [vendorData setObject:_customDnsListString forKey:(__bridge NSString *)kPluginArgCustomDNSSetting];
+    }
+    
+    // Client ID
+    if (_clientID) {
+        [vendorData setObject:_clientID forKey:(__bridge NSString *)kPluginArgClientID];
+    }
+    
+    // Device ID
+    if (_deviceID) {
+        [vendorData setObject:_deviceID forKey:(__bridge NSString *)kPluginArgDeviceID];
+    }
+    
+    // Certificate Data
+    if (_certData) {
+        [vendorData setObject:_certData forKey:(__bridge NSString *)kPluginArgCertificateData];
+    }
+    
+    // Certificate Password
+    if (_certPassword) {
+        [vendorData setObject:_certPassword forKey:(__bridge NSString *)kPluginArgCertificatePassword];
+    }
+    
+    // Speed tunnel
+    [vendorData setObject:[@(_enableSpeedTunnel) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgSpeedTunnelEnable];
+    [vendorData setObject:[@(_speedTunnelDispatch) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgSpeedTunnelDispatch];
+    [vendorData setObject:[@(_enableSpeedTunnelEncrypt) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgSpeedTunnelEncrypt];
+    [vendorData setObject:[@(_enableSplitTunnelDnsSearch) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgSplitTunnelEnableDnsSearch];
+    
+    // Log level
+    [vendorData setObject:[@(_logLevel) stringValue] forKey:(__bridge NSString *)kPluginArgLogLevel];
+    
+    // Reconnection
+    [vendorData setObject:[@(_reconnectInterval) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgReconnectInterval];
+    [vendorData setObject:[@(_reconnectMaxCount) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgReconnectMaxCount];
+    [vendorData setObject:[@(_reconnectMaxTime) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgReconnectMaxTime];
+    
+    // VPN Type
+    [vendorData setObject:[@(_serverType) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgServerType];
+    
+    [_vpnConfigDict setObject:vendorData forKey:@"VendorData"];
+    
+#if !TARGET_OS_IPHONE
+    // Proxy
+    if (_proxyHost) {
+        [vendorData setObject:_proxyHost forKey:(__bridge NSString *)kPluginArgProxyAddress];
+    } else {
+        [vendorData setObject:@"" forKey:(__bridge NSString *)kPluginArgProxyAddress];
+    }
+    if (_proxyUsername) {
+        [vendorData setObject:_proxyUsername forKey:(__bridge NSString *)kPluginArgProxyUsername];
+    } else {
+        [vendorData setObject:@"" forKey:(__bridge NSString *)kPluginArgProxyUsername];
+    }
+    if (_proxyPassword) {
+        [vendorData setObject:_proxyPassword forKey:(__bridge NSString *)kPluginArgProxyPassword];
+    } else {
+        [vendorData setObject:@"" forKey:(__bridge NSString *)kPluginArgProxyPassword];
+    }
+#endif
+    
+    ANDebug(@"Set VPN configuration as below:\n%@", _vpnConfigDict);
+}
+
+#pragma mark - VPN Configuration Notification
+- (void)VPNStatusDidChanged:(NSNotification *)notification {
+    NEVPNStatus vpnStatus = self.tunnelManager.connection.status;
+    
+    ANInfo(@"VPN status has changed to %d.", (int)vpnStatus);
+    
+    [self updateVPNStatusWithTunnelConnectionStatus:vpnStatus];
+    
+    if (vpnStatus == NEVPNStatusDisconnected) {
+        // Connect after disconnect if the vpn plugin has been started by on-demand before.
+        if (self.needReconnect) {
+            self.needReconnect = NO;
+            [self connectInternal];
+        }
+    }
+}
+
+- (void)updateVPNStatusWithTunnelConnectionStatus:(NEVPNStatus)status {
+    ArrayVPNStatus vpnStatus = ArrayVPNInavlid;
+    
+    switch (status) {
+        case NEVPNStatusInvalid:
+            vpnStatus = ArrayVPNInavlid;
+            break;
+        case NEVPNStatusDisconnected:
+            vpnStatus = ArrayVPNDisconnected;
+            [[NSNotificationCenter defaultCenter] postNotificationName:kArrayVPNDisconnected object:nil];
+            break;
+        case NEVPNStatusConnecting:
+            vpnStatus = ArrayVPNConnecting;
+            break;
+        case NEVPNStatusConnected:
+            vpnStatus = ArrayVPNConnected;
+            break;
+        case NEVPNStatusReasserting:
+            vpnStatus = ArrayVPNReasserting;
+            break;
+        case NEVPNStatusDisconnecting:
+            vpnStatus = ArrayVPNDisconnecting;
+            break;
+            
+        default:
+            return;  // Don't update vpn status if connetion status is invalid.
+    }
+    
+    self.vpnStatus = vpnStatus;
+}
+
+- (void)setVpnStatus:(ArrayVPNStatus)vpnStatus {
+    if (_vpnStatus == vpnStatus) {
+        return;
+    }
+    
+    _vpnStatus = vpnStatus;
+    
+    [[NSNotificationCenter defaultCenter] postNotificationName:kArrayVPNStatusNotification object:nil];
+}
+
+// Connect/Disconnect
+
+- (NSError *)connect {
+    if (!self.tunnelManager || !self.tunnelManager.enabled) {
+        ANWarn(@"VPN tunnel manager is nil or not enabled, it may not be ready!");
+        [self initializeVPNConfiguration:YES];
+        return nil;
+    }
+
+    NEVPNStatus currentStatus = self.tunnelManager.connection.status;
+    if (currentStatus == NEVPNStatusConnecting || currentStatus == NEVPNStatusConnected) {
+        ANInfo(@"VPN is in status %ld, stop it first.", (long)currentStatus);
+        [self.tunnelManager.connection stopVPNTunnel];
+        self.needReconnect = YES;
+        return nil;
+    }
+    
+    return [self connectInternal];
+}
+
+- (NSError *)disconnect {
+    ANDebug(@"================>Try to stop network extension!<===============");
+    [self.tunnelManager.connection stopVPNTunnel];
+    
+    return nil;
+}
+
+- (NSError *)connectInternal {
+    BOOL ret = NO;
+    
+    if (array_vpn_is_mp_flag_set(MP_MOBILE_SPLIT_DNSSEARCH)) {
+        self.enableSplitTunnelDnsSearch = YES;
+    } else {
+        self.enableSplitTunnelDnsSearch = NO;
+    }
+    [self setVPNConfiguration];
+    
+    ANInfo(@"================>Try to start network extension!<===============");
+    
+    NSError *error = nil;
+    ret = [self.tunnelManager.connection startVPNTunnelWithOptions:_vpnConfigDict andReturnError:&error];
+    if (!ret) {
+        ANError(@"Start VPN tunnel failed with return value: %d.", ret);
+    }
+    if (error) {
+        ANError(@"Start VPN tunnel failed with error: %@", error);
+    }
+    
+    return error;
+}
+
+@end
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/VPNConfig/ArrayVPNManager.m.MotionProEnterprise
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/VPNConfig/ArrayVPNManager.m.MotionProEnterprise	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/VPNConfig/ArrayVPNManager.m.MotionProEnterprise	(working copy)
@@ -0,0 +1,429 @@
+//
+//  ArrayVPNConfig.m
+//  ArraySSLVPN
+//
+//  Created by wangxy on 16/10/20.
+//  Copyright (c) 2016年 ArrayNetworks. All rights reserved.
+//
+
+#import "arrayapi.h"
+#import "ANLogger.h"
+
+#import "ArrayVPNManager.h"
+
+#if TARGET_OS_IPHONE
+NSString * const kArrayVPNType = @"net.arraynetworks.MotionProPlusEnterprise.iOSTunnel";
+#elif TARGET_OS_MAC
+NSString * const kArrayVPNType = @"net.arraynetworks.MacTunnel.ArrayTunnel";
+#endif
+NSString * const kArrayVPNName = @"ArrayNetworks Enterprise SSL VPN";
+NSString * const kArrayVPNStatusNotification = @"net.arraynetworks.sslvpn.vpnstatus";
+NSString * const kArrayVPNDisconnected = @"net.arraynetworks.sslvpn.disconnected";
+NSString * const kArrayVPNErrorDomain = (__bridge NSString *)kPluginErrorDomain;
+
+@interface ArrayVPNManager()
+
+@property (nonatomic, strong) NSString *vpnType;
+@property (nonatomic, strong) NSMutableDictionary *vpnConfigDict;
+@property (nonatomic, strong) NSMutableDictionary *proxyConfigDict;
+@property (nonatomic) BOOL needReconnect;
+
+@end
+
+@implementation ArrayVPNManager
+
++ (instancetype)sharedInstance {
+    static ArrayVPNManager *instance;
+    static dispatch_once_t token;
+    dispatch_once(&token, ^{
+        // Init VPN config with default type and name.
+        instance = [[self alloc] init];
+        [instance initializeDefaultConfigurations];
+        [instance startObserveVPNStatus];
+    });
+    return instance;
+}
+
+- (void)initializeDefaultConfigurations {
+    // VPN name&type
+    self.vpnType = kArrayVPNType;
+    self.vpnName = kArrayVPNName;
+    
+    // VPN Flag
+    self.vpnFlag = VPN_FLAG_DEVID_LOGIN|VPN_FLAG_SKIP_LOGOUT;
+    
+    // Speed tunnel
+    self.enableSpeedTunnel = YES;
+    self.enableSpeedTunnelEncrypt = YES;
+    self.speedTunnelDispatch = VPN_DISP_AlltoUDP;
+    
+    // Log level
+    self.logLevel = [[ANLogger sharedInstance] currentLogLevel];
+    
+    // Reconnecting
+    self.reconnectMaxTime = 300; //default to 300s.
+    
+    // On demand
+    self.enableVPNOnDemand = NO;
+}
+
+- (void)dealloc {
+    if (_vpnConfigDict) {
+        [_vpnConfigDict removeAllObjects];
+    }
+    if (_proxyConfigDict) {
+        [_proxyConfigDict removeAllObjects];
+    }
+    
+    [self stopObserveVPNStatus];
+}
+
+#pragma mark - VPN Configuration
+
+- (void)startObserveVPNStatus {
+    [[NSNotificationCenter defaultCenter] addObserver:self
+                                             selector:@selector(VPNStatusDidChanged:)
+                                                 name:NEVPNStatusDidChangeNotification
+                                               object:nil];
+}
+
+- (void)stopObserveVPNStatus {
+    [[NSNotificationCenter defaultCenter] removeObserver:self
+                                                    name:NEVPNStatusDidChangeNotification
+                                                  object:nil];
+}
+
+- (void)setVpnName:(NSString *)vpnName {
+    _vpnName = [vpnName copy];
+}
+
+- (void)createVPNPreferenceWithType:(NSString *)type name:(NSString *)name autoStart:(BOOL)start {
+    NETunnelProviderManager *manager = [[NETunnelProviderManager alloc] init];
+    
+    __weak typeof(self) weakSelf = self;
+    [manager loadFromPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
+        if (error) {
+            ANError(@"Load from preference failed with error: %@", error.localizedDescription);
+            return;
+        }
+        
+        NETunnelProviderProtocol *protocol = [[NETunnelProviderProtocol alloc] init];
+        protocol.providerBundleIdentifier = type;
+        protocol.serverAddress = @"";
+        
+        manager.protocolConfiguration = protocol;
+        manager.localizedDescription = kArrayVPNName;
+        manager.enabled = YES;
+        [manager saveToPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
+            if (error) {
+                ANError(@"Save VPN preference failed with error: %@", error.localizedDescription);
+                return;
+            }
+            
+            ANInfo(@"New vpn preference named: \"%@\" created.", kArrayVPNName);
+            
+            [weakSelf initializeVPNConfiguration:start];
+        }];
+    }];
+}
+
+/**
+ Return Value:
+    YES means vpn preference should be updating.
+    NO means vpn prefernce has no changes.
+ */
+- (BOOL)updateVPNPreference {
+    NSString *originalHost = self.tunnelManager.protocolConfiguration.serverAddress;
+    if (![originalHost isEqualToString:_host]) {
+        ANInfo(@"Different server host found: original = %@, new = %@", originalHost, _host);
+        
+        self.tunnelManager.protocolConfiguration.serverAddress = _host;
+        [self.tunnelManager saveToPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
+            if (error) {
+                ANInfo(@"Update VPN preference failed with error: %@", error.localizedDescription);
+            }
+        }];
+        return YES;
+    }
+    ANInfo(@"The server host (%@) never changed, don't update.", originalHost);
+    
+    return NO;
+}
+
+- (void)initializeVPNConfiguration:(BOOL)autoStart {
+    __weak typeof(self) weakSelf = self;
+    [NETunnelProviderManager loadAllFromPreferencesWithCompletionHandler:^(NSArray<NETunnelProviderManager *> * _Nullable managers, NSError * _Nullable error) {
+        if (!managers || [managers count] == 0) {
+            ANInfo(@"There is not VPN preference in the system, should create a new one.");
+            
+            [weakSelf createVPNPreferenceWithType:_vpnType name:_vpnName autoStart:autoStart];
+        } else {
+            BOOL isAppVPNUsed = [self isAppVPNConfigurationExist:managers];
+            
+            for (NETunnelProviderManager *manager in managers) {
+                ANInfo(@"VPN configuration named \"%@\" has been found!", manager.localizedDescription);
+                
+                if (isAppVPNUsed && manager.routingMethod != NETunnelProviderRoutingMethodSourceApplication) {
+                    // App VPN is prior to L3VPN.
+                    continue;
+                }
+                
+                [manager loadFromPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
+                    if (error) {
+                        ANError( @"Tunnel provider manager load failed with error: %@", error.localizedDescription);
+                        return;
+                    }
+                    
+                    manager.enabled = YES;
+                    weakSelf.tunnelManager = manager;
+                    [manager saveToPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
+                        if (error) {
+                            ANError(@"Enable tunnel preference failed on saving it, error: %@", error.localizedDescription);
+                            return;
+                        }
+                        if (autoStart) {
+                            [weakSelf connect];
+                        }
+                    }];
+                    
+                }];
+                break;  // Only one vpn preference should be detected.
+            }
+        }
+    }];
+}
+
+- (void)removeVPNConfiguration {
+    if (self.tunnelManager != nil) {
+        [self.tunnelManager removeFromPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
+            if (error) {
+                ANError(@"Remove VPN preference failed with error: %@", error.localizedDescription);
+                return;
+            }
+        }];
+        self.tunnelManager = nil;
+    } else {
+        ANError(@"No VPN preference need to be removed.");
+    }
+}
+
+- (BOOL)isAppVPNConfigurationExist:(NSArray *)managers {
+    for (NETunnelProviderManager *manager in managers) {
+        if (manager.routingMethod == NETunnelProviderRoutingMethodSourceApplication) {
+            ANInfo(@"APP VPN configuration has been found in preference.");
+            return YES;
+        }
+    }
+    
+    return NO;
+}
+
+- (void)setVPNConfiguration {
+    if (_host.length==0 || _port.length==0) {
+        ANError(@"Invalid configuration for VPN, host: %@, port: %@", _host, _port);
+        return;
+    }
+    
+    if (!_vpnConfigDict) {
+        _vpnConfigDict = [[NSMutableDictionary alloc] init];
+    }
+    
+    NSMutableDictionary *vendorData = [[NSMutableDictionary alloc] initWithCapacity:10];
+    
+    // Server address
+    [vendorData setObject:_host forKey:(__bridge NSString *)kPluginArgHost];
+    
+    // Port
+    [vendorData setObject:_port forKey:(__bridge NSString *)kPluginArgPort];
+    
+    // Flag
+    [vendorData setObject:[@(_vpnFlag) stringValue] forKey:(__bridge NSString *)kPluginArgFlags];
+    
+    // Session
+    if (_sessionID) {
+        [vendorData setObject:_sessionID forKey:(__bridge NSString *)kPluginArgSession];
+    }
+    
+    if (_customDnsListString) {
+        [vendorData setObject:_customDnsListString forKey:(__bridge NSString *)kPluginArgCustomDNSSetting];
+    }
+    
+    // Client ID
+    if (_clientID) {
+        [vendorData setObject:_clientID forKey:(__bridge NSString *)kPluginArgClientID];
+    }
+    
+    // Device ID
+    if (_deviceID) {
+        [vendorData setObject:_deviceID forKey:(__bridge NSString *)kPluginArgDeviceID];
+    }
+    
+    // Certificate Data
+    if (_certData) {
+        [vendorData setObject:_certData forKey:(__bridge NSString *)kPluginArgCertificateData];
+    }
+    
+    // Certificate Password
+    if (_certPassword) {
+        [vendorData setObject:_certPassword forKey:(__bridge NSString *)kPluginArgCertificatePassword];
+    }
+    
+    // Speed tunnel
+    [vendorData setObject:[@(_enableSpeedTunnel) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgSpeedTunnelEnable];
+    [vendorData setObject:[@(_speedTunnelDispatch) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgSpeedTunnelDispatch];
+    [vendorData setObject:[@(_enableSpeedTunnelEncrypt) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgSpeedTunnelEncrypt];
+    [vendorData setObject:[@(_enableSplitTunnelDnsSearch) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgSplitTunnelEnableDnsSearch];
+    
+    // Log level
+    [vendorData setObject:[@(_logLevel) stringValue] forKey:(__bridge NSString *)kPluginArgLogLevel];
+    
+    // Reconnection
+    [vendorData setObject:[@(_reconnectInterval) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgReconnectInterval];
+    [vendorData setObject:[@(_reconnectMaxCount) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgReconnectMaxCount];
+    [vendorData setObject:[@(_reconnectMaxTime) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgReconnectMaxTime];
+    
+    // VPN Type
+    [vendorData setObject:[@(_serverType) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgServerType];
+    
+    [_vpnConfigDict setObject:vendorData forKey:@"VendorData"];
+    
+#if !TARGET_OS_IPHONE
+    // Proxy
+    if (_proxyHost) {
+        [vendorData setObject:_proxyHost forKey:(__bridge NSString *)kPluginArgProxyAddress];
+    } else {
+        [vendorData setObject:@"" forKey:(__bridge NSString *)kPluginArgProxyAddress];
+    }
+    if (_proxyUsername) {
+        [vendorData setObject:_proxyUsername forKey:(__bridge NSString *)kPluginArgProxyUsername];
+    } else {
+        [vendorData setObject:@"" forKey:(__bridge NSString *)kPluginArgProxyUsername];
+    }
+    if (_proxyPassword) {
+        [vendorData setObject:_proxyPassword forKey:(__bridge NSString *)kPluginArgProxyPassword];
+    } else {
+        [vendorData setObject:@"" forKey:(__bridge NSString *)kPluginArgProxyPassword];
+    }
+#endif
+    
+    ANDebug(@"Set VPN configuration as below:\n%@", _vpnConfigDict);
+}
+
+#pragma mark - VPN Configuration Notification
+- (void)VPNStatusDidChanged:(NSNotification *)notification {
+    NEVPNStatus vpnStatus = self.tunnelManager.connection.status;
+    
+    ANInfo(@"VPN status has changed to %d.", (int)vpnStatus);
+    
+    [self updateVPNStatusWithTunnelConnectionStatus:vpnStatus];
+    
+    if (vpnStatus == NEVPNStatusDisconnected) {
+        // Connect after disconnect if the vpn plugin has been started by on-demand before.
+        if (self.needReconnect) {
+            self.needReconnect = NO;
+            [self connectInternal];
+        }
+    }
+}
+
+- (void)updateVPNStatusWithTunnelConnectionStatus:(NEVPNStatus)status {
+    ArrayVPNStatus vpnStatus = ArrayVPNInavlid;
+    
+    switch (status) {
+        case NEVPNStatusInvalid:
+            vpnStatus = ArrayVPNInavlid;
+            break;
+        case NEVPNStatusDisconnected:
+            vpnStatus = ArrayVPNDisconnected;
+            [[NSNotificationCenter defaultCenter] postNotificationName:kArrayVPNDisconnected object:nil];
+            break;
+        case NEVPNStatusConnecting:
+            vpnStatus = ArrayVPNConnecting;
+            break;
+        case NEVPNStatusConnected:
+            vpnStatus = ArrayVPNConnected;
+            break;
+        case NEVPNStatusReasserting:
+            vpnStatus = ArrayVPNReasserting;
+            break;
+        case NEVPNStatusDisconnecting:
+            vpnStatus = ArrayVPNDisconnecting;
+            break;
+            
+        default:
+            return;  // Don't update vpn status if connetion status is invalid.
+    }
+    
+    self.vpnStatus = vpnStatus;
+}
+
+- (void)setVpnStatus:(ArrayVPNStatus)vpnStatus {
+    if (_vpnStatus == vpnStatus) {
+        return;
+    }
+    
+    _vpnStatus = vpnStatus;
+    
+    [[NSNotificationCenter defaultCenter] postNotificationName:kArrayVPNStatusNotification object:nil];
+}
+
+// Connect/Disconnect
+
+- (NSError *)connect {
+    if (!self.tunnelManager || !self.tunnelManager.enabled) {
+        ANWarn(@"VPN tunnel manager is nil or not enabled, it may not be ready!");
+        [self initializeVPNConfiguration:YES];
+        return nil;
+    }
+
+    NEVPNStatus currentStatus = self.tunnelManager.connection.status;
+    if (currentStatus == NEVPNStatusConnecting || currentStatus == NEVPNStatusConnected) {
+        ANInfo(@"VPN is in status %ld, stop it first.", (long)currentStatus);
+        [self.tunnelManager.connection stopVPNTunnel];
+        self.needReconnect = YES;
+        return nil;
+    }
+    
+    return [self connectInternal];
+}
+
+- (NSError *)disconnect {
+    ANDebug(@"================>Try to stop network extension!<===============");
+    [self.tunnelManager.connection stopVPNTunnel];
+    
+    return nil;
+}
+
+- (NSError *)connectInternal {
+    BOOL ret = NO;
+    
+    if (array_vpn_is_mp_flag_set(MP_MOBILE_SPLIT_DNSSEARCH)) {
+        self.enableSplitTunnelDnsSearch = YES;
+    } else {
+        self.enableSplitTunnelDnsSearch = NO;
+    }
+    [self setVPNConfiguration];
+    
+    ANInfo(@"================>Try to start network extension!<===============");
+    
+    NSError *error = nil;
+    ret = [self.tunnelManager.connection startVPNTunnelWithOptions:_vpnConfigDict andReturnError:&error];
+    if (!ret) {
+        ANError(@"Start VPN tunnel failed with return value: %d.", ret);
+    }
+    if (error) {
+        ANError(@"Start VPN tunnel failed with error: %@", error);
+    }
+    
+    return error;
+}
+
+@end
Index: /branches/ag_client_motionProGlobal_ios_new/Shared/VPNConfig/ArrayVPNManager.m.zrt
===================================================================
--- /branches/ag_client_motionProGlobal_ios_new/Shared/VPNConfig/ArrayVPNManager.m.zrt	(nonexistent)
+++ /branches/ag_client_motionProGlobal_ios_new/Shared/VPNConfig/ArrayVPNManager.m.zrt	(working copy)
@@ -0,0 +1,429 @@
+//
+//  ArrayVPNConfig.m
+//  ArraySSLVPN
+//
+//  Created by wangxy on 16/10/20.
+//  Copyright (c) 2016年 ArrayNetworks. All rights reserved.
+//
+
+#import "arrayapi.h"
+#import "ANLogger.h"
+
+#import "ArrayVPNManager.h"
+
+#if TARGET_OS_IPHONE
+NSString * const kArrayVPNType = @"net.infosec.MotionProZRTEnterprise.iOSTunnel";
+#elif TARGET_OS_MAC
+NSString * const kArrayVPNType = @"net.arraynetworks.MacTunnel.ArrayTunnel";
+#endif
+NSString * const kArrayVPNName = @"中融专网";
+NSString * const kArrayVPNStatusNotification = @"net.arraynetworks.sslvpn.vpnstatus";
+NSString * const kArrayVPNDisconnected = @"net.arraynetworks.sslvpn.disconnected";
+NSString * const kArrayVPNErrorDomain = (__bridge NSString *)kPluginErrorDomain;
+
+@interface ArrayVPNManager()
+
+@property (nonatomic, strong) NSString *vpnType;
+@property (nonatomic, strong) NSMutableDictionary *vpnConfigDict;
+@property (nonatomic, strong) NSMutableDictionary *proxyConfigDict;
+@property (nonatomic) BOOL needReconnect;
+
+@end
+
+@implementation ArrayVPNManager
+
++ (instancetype)sharedInstance {
+    static ArrayVPNManager *instance;
+    static dispatch_once_t token;
+    dispatch_once(&token, ^{
+        // Init VPN config with default type and name.
+        instance = [[self alloc] init];
+        [instance initializeDefaultConfigurations];
+        [instance startObserveVPNStatus];
+    });
+    return instance;
+}
+
+- (void)initializeDefaultConfigurations {
+    // VPN name&type
+    self.vpnType = kArrayVPNType;
+    self.vpnName = kArrayVPNName;
+    
+    // VPN Flag
+    self.vpnFlag = VPN_FLAG_DEVID_LOGIN|VPN_FLAG_SKIP_LOGOUT;
+    
+    // Speed tunnel
+    self.enableSpeedTunnel = YES;
+    self.enableSpeedTunnelEncrypt = YES;
+    self.speedTunnelDispatch = VPN_DISP_AlltoUDP;
+    
+    // Log level
+    self.logLevel = [[ANLogger sharedInstance] currentLogLevel];
+    
+    // Reconnecting
+    self.reconnectMaxTime = 300; //default to 300s.
+    
+    // On demand
+    self.enableVPNOnDemand = NO;
+}
+
+- (void)dealloc {
+    if (_vpnConfigDict) {
+        [_vpnConfigDict removeAllObjects];
+    }
+    if (_proxyConfigDict) {
+        [_proxyConfigDict removeAllObjects];
+    }
+    
+    [self stopObserveVPNStatus];
+}
+
+#pragma mark - VPN Configuration
+
+- (void)startObserveVPNStatus {
+    [[NSNotificationCenter defaultCenter] addObserver:self
+                                             selector:@selector(VPNStatusDidChanged:)
+                                                 name:NEVPNStatusDidChangeNotification
+                                               object:nil];
+}
+
+- (void)stopObserveVPNStatus {
+    [[NSNotificationCenter defaultCenter] removeObserver:self
+                                                    name:NEVPNStatusDidChangeNotification
+                                                  object:nil];
+}
+
+- (void)setVpnName:(NSString *)vpnName {
+    _vpnName = [vpnName copy];
+}
+
+- (void)createVPNPreferenceWithType:(NSString *)type name:(NSString *)name autoStart:(BOOL)start {
+    NETunnelProviderManager *manager = [[NETunnelProviderManager alloc] init];
+    
+    __weak typeof(self) weakSelf = self;
+    [manager loadFromPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
+        if (error) {
+            ANError(@"Load from preference failed with error: %@", error.localizedDescription);
+            return;
+        }
+        
+        NETunnelProviderProtocol *protocol = [[NETunnelProviderProtocol alloc] init];
+        protocol.providerBundleIdentifier = type;
+        protocol.serverAddress = @"";
+        
+        manager.protocolConfiguration = protocol;
+        manager.localizedDescription = kArrayVPNName;
+        manager.enabled = YES;
+        [manager saveToPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
+            if (error) {
+                ANError(@"Save VPN preference failed with error: %@", error.localizedDescription);
+                return;
+            }
+            
+            ANInfo(@"New vpn preference named: \"%@\" created.", kArrayVPNName);
+            
+            [weakSelf initializeVPNConfiguration:start];
+        }];
+    }];
+}
+
+/**
+ Return Value:
+    YES means vpn preference should be updating.
+    NO means vpn prefernce has no changes.
+ */
+- (BOOL)updateVPNPreference {
+    NSString *originalHost = self.tunnelManager.protocolConfiguration.serverAddress;
+    if (![originalHost isEqualToString:_host]) {
+        ANInfo(@"Different server host found: original = %@, new = %@", originalHost, _host);
+        
+        self.tunnelManager.protocolConfiguration.serverAddress = _host;
+        [self.tunnelManager saveToPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
+            if (error) {
+                ANInfo(@"Update VPN preference failed with error: %@", error.localizedDescription);
+            }
+        }];
+        return YES;
+    }
+    ANInfo(@"The server host (%@) never changed, don't update.", originalHost);
+    
+    return NO;
+}
+
+- (void)initializeVPNConfiguration:(BOOL)autoStart {
+    __weak typeof(self) weakSelf = self;
+    [NETunnelProviderManager loadAllFromPreferencesWithCompletionHandler:^(NSArray<NETunnelProviderManager *> * _Nullable managers, NSError * _Nullable error) {
+        if (!managers || [managers count] == 0) {
+            ANInfo(@"There is not VPN preference in the system, should create a new one.");
+            
+            [weakSelf createVPNPreferenceWithType:_vpnType name:_vpnName autoStart:autoStart];
+        } else {
+            BOOL isAppVPNUsed = [self isAppVPNConfigurationExist:managers];
+            
+            for (NETunnelProviderManager *manager in managers) {
+                ANInfo(@"VPN configuration named \"%@\" has been found!", manager.localizedDescription);
+                
+                if (isAppVPNUsed && manager.routingMethod != NETunnelProviderRoutingMethodSourceApplication) {
+                    // App VPN is prior to L3VPN.
+                    continue;
+                }
+                
+                [manager loadFromPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
+                    if (error) {
+                        ANError( @"Tunnel provider manager load failed with error: %@", error.localizedDescription);
+                        return;
+                    }
+                    
+                    manager.enabled = YES;
+                    weakSelf.tunnelManager = manager;
+                    [manager saveToPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
+                        if (error) {
+                            ANError(@"Enable tunnel preference failed on saving it, error: %@", error.localizedDescription);
+                            return;
+                        }
+                        if (autoStart) {
+                            [weakSelf connect];
+                        }
+                    }];
+                    
+                }];
+                break;  // Only one vpn preference should be detected.
+            }
+        }
+    }];
+}
+
+- (void)removeVPNConfiguration {
+    if (self.tunnelManager != nil) {
+        [self.tunnelManager removeFromPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
+            if (error) {
+                ANError(@"Remove VPN preference failed with error: %@", error.localizedDescription);
+                return;
+            }
+        }];
+        self.tunnelManager = nil;
+    } else {
+        ANError(@"No VPN preference need to be removed.");
+    }
+}
+
+- (BOOL)isAppVPNConfigurationExist:(NSArray *)managers {
+    for (NETunnelProviderManager *manager in managers) {
+        if (manager.routingMethod == NETunnelProviderRoutingMethodSourceApplication) {
+            ANInfo(@"APP VPN configuration has been found in preference.");
+            return YES;
+        }
+    }
+    
+    return NO;
+}
+
+- (void)setVPNConfiguration {
+    if (_host.length==0 || _port.length==0) {
+        ANError(@"Invalid configuration for VPN, host: %@, port: %@", _host, _port);
+        return;
+    }
+    
+    if (!_vpnConfigDict) {
+        _vpnConfigDict = [[NSMutableDictionary alloc] init];
+    }
+    
+    NSMutableDictionary *vendorData = [[NSMutableDictionary alloc] initWithCapacity:10];
+    
+    // Server address
+    [vendorData setObject:_host forKey:(__bridge NSString *)kPluginArgHost];
+    
+    // Port
+    [vendorData setObject:_port forKey:(__bridge NSString *)kPluginArgPort];
+    
+    // Flag
+    [vendorData setObject:[@(_vpnFlag) stringValue] forKey:(__bridge NSString *)kPluginArgFlags];
+    
+    // Session
+    if (_sessionID) {
+        [vendorData setObject:_sessionID forKey:(__bridge NSString *)kPluginArgSession];
+    }
+    
+    if (_customDnsListString) {
+        [vendorData setObject:_customDnsListString forKey:(__bridge NSString *)kPluginArgCustomDNSSetting];
+    }
+    
+    // Client ID
+    if (_clientID) {
+        [vendorData setObject:_clientID forKey:(__bridge NSString *)kPluginArgClientID];
+    }
+    
+    // Device ID
+    if (_deviceID) {
+        [vendorData setObject:_deviceID forKey:(__bridge NSString *)kPluginArgDeviceID];
+    }
+    
+    // Certificate Data
+    if (_certData) {
+        [vendorData setObject:_certData forKey:(__bridge NSString *)kPluginArgCertificateData];
+    }
+    
+    // Certificate Password
+    if (_certPassword) {
+        [vendorData setObject:_certPassword forKey:(__bridge NSString *)kPluginArgCertificatePassword];
+    }
+    
+    // Speed tunnel
+    [vendorData setObject:[@(_enableSpeedTunnel) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgSpeedTunnelEnable];
+    [vendorData setObject:[@(_speedTunnelDispatch) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgSpeedTunnelDispatch];
+    [vendorData setObject:[@(_enableSpeedTunnelEncrypt) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgSpeedTunnelEncrypt];
+    [vendorData setObject:[@(_enableSplitTunnelDnsSearch) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgSplitTunnelEnableDnsSearch];
+    
+    // Log level
+    [vendorData setObject:[@(_logLevel) stringValue] forKey:(__bridge NSString *)kPluginArgLogLevel];
+    
+    // Reconnection
+    [vendorData setObject:[@(_reconnectInterval) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgReconnectInterval];
+    [vendorData setObject:[@(_reconnectMaxCount) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgReconnectMaxCount];
+    [vendorData setObject:[@(_reconnectMaxTime) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgReconnectMaxTime];
+    
+    // VPN Type
+    [vendorData setObject:[@(_serverType) stringValue]
+                   forKey:(__bridge NSString *)kPluginArgServerType];
+    
+    [_vpnConfigDict setObject:vendorData forKey:@"VendorData"];
+    
+#if !TARGET_OS_IPHONE
+    // Proxy
+    if (_proxyHost) {
+        [vendorData setObject:_proxyHost forKey:(__bridge NSString *)kPluginArgProxyAddress];
+    } else {
+        [vendorData setObject:@"" forKey:(__bridge NSString *)kPluginArgProxyAddress];
+    }
+    if (_proxyUsername) {
+        [vendorData setObject:_proxyUsername forKey:(__bridge NSString *)kPluginArgProxyUsername];
+    } else {
+        [vendorData setObject:@"" forKey:(__bridge NSString *)kPluginArgProxyUsername];
+    }
+    if (_proxyPassword) {
+        [vendorData setObject:_proxyPassword forKey:(__bridge NSString *)kPluginArgProxyPassword];
+    } else {
+        [vendorData setObject:@"" forKey:(__bridge NSString *)kPluginArgProxyPassword];
+    }
+#endif
+    
+    ANDebug(@"Set VPN configuration as below:\n%@", _vpnConfigDict);
+}
+
+#pragma mark - VPN Configuration Notification
+- (void)VPNStatusDidChanged:(NSNotification *)notification {
+    NEVPNStatus vpnStatus = self.tunnelManager.connection.status;
+    
+    ANInfo(@"VPN status has changed to %d.", (int)vpnStatus);
+    
+    [self updateVPNStatusWithTunnelConnectionStatus:vpnStatus];
+    
+    if (vpnStatus == NEVPNStatusDisconnected) {
+        // Connect after disconnect if the vpn plugin has been started by on-demand before.
+        if (self.needReconnect) {
+            self.needReconnect = NO;
+            [self connectInternal];
+        }
+    }
+}
+
+- (void)updateVPNStatusWithTunnelConnectionStatus:(NEVPNStatus)status {
+    ArrayVPNStatus vpnStatus = ArrayVPNInavlid;
+    
+    switch (status) {
+        case NEVPNStatusInvalid:
+            vpnStatus = ArrayVPNInavlid;
+            break;
+        case NEVPNStatusDisconnected:
+            vpnStatus = ArrayVPNDisconnected;
+            [[NSNotificationCenter defaultCenter] postNotificationName:kArrayVPNDisconnected object:nil];
+            break;
+        case NEVPNStatusConnecting:
+            vpnStatus = ArrayVPNConnecting;
+            break;
+        case NEVPNStatusConnected:
+            vpnStatus = ArrayVPNConnected;
+            break;
+        case NEVPNStatusReasserting:
+            vpnStatus = ArrayVPNReasserting;
+            break;
+        case NEVPNStatusDisconnecting:
+            vpnStatus = ArrayVPNDisconnecting;
+            break;
+            
+        default:
+            return;  // Don't update vpn status if connetion status is invalid.
+    }
+    
+    self.vpnStatus = vpnStatus;
+}
+
+- (void)setVpnStatus:(ArrayVPNStatus)vpnStatus {
+    if (_vpnStatus == vpnStatus) {
+        return;
+    }
+    
+    _vpnStatus = vpnStatus;
+    
+    [[NSNotificationCenter defaultCenter] postNotificationName:kArrayVPNStatusNotification object:nil];
+}
+
+// Connect/Disconnect
+
+- (NSError *)connect {
+    if (!self.tunnelManager || !self.tunnelManager.enabled) {
+        ANWarn(@"VPN tunnel manager is nil or not enabled, it may not be ready!");
+        [self initializeVPNConfiguration:YES];
+        return nil;
+    }
+
+    NEVPNStatus currentStatus = self.tunnelManager.connection.status;
+    if (currentStatus == NEVPNStatusConnecting || currentStatus == NEVPNStatusConnected) {
+        ANInfo(@"VPN is in status %ld, stop it first.", (long)currentStatus);
+        [self.tunnelManager.connection stopVPNTunnel];
+        self.needReconnect = YES;
+        return nil;
+    }
+    
+    return [self connectInternal];
+}
+
+- (NSError *)disconnect {
+    ANDebug(@"================>Try to stop network extension!<===============");
+    [self.tunnelManager.connection stopVPNTunnel];
+    
+    return nil;
+}
+
+- (NSError *)connectInternal {
+    BOOL ret = NO;
+    
+    if (array_vpn_is_mp_flag_set(MP_MOBILE_SPLIT_DNSSEARCH)) {
+        self.enableSplitTunnelDnsSearch = YES;
+    } else {
+        self.enableSplitTunnelDnsSearch = NO;
+    }
+    [self setVPNConfiguration];
+    
+    ANInfo(@"================>Try to start network extension!<===============");
+    
+    NSError *error = nil;
+    ret = [self.tunnelManager.connection startVPNTunnelWithOptions:_vpnConfigDict andReturnError:&error];
+    if (!ret) {
+        ANError(@"Start VPN tunnel failed with return value: %d.", ret);
+    }
+    if (error) {
+        ANError(@"Start VPN tunnel failed with error: %@", error);
+    }
+    
+    return error;
+}
+
+@end
