Browse Source

Add state tracking to library

Adam Pritchard 8 years ago
parent
commit
8f2783bb81

+ 45 - 0
MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/PsiphonTunnel.h

@@ -33,6 +33,28 @@ FOUNDATION_EXPORT double PsiphonTunnelVersionNumber;
 //! Project version string for PsiphonTunnel.
 //! Project version string for PsiphonTunnel.
 FOUNDATION_EXPORT const unsigned char PsiphonTunnelVersionString[];
 FOUNDATION_EXPORT const unsigned char PsiphonTunnelVersionString[];
 
 
+
+/*!
+ The set of possible connection states the tunnel can be in.
+ Swift:
+ @code
+ public enum PsiphonConnectionState : Int {
+     case disconnected
+     case connecting
+     case connected
+     case waitingForNetwork
+ }
+ @endcode
+ */
+typedef NS_ENUM(NSInteger, PsiphonConnectionState)
+{
+    PsiphonConnectionStateDisconnected,
+    PsiphonConnectionStateConnecting,
+    PsiphonConnectionStateConnected,
+    PsiphonConnectionStateWaitingForNetwork
+};
+
+
 /*!
 /*!
  @protocol TunneledAppDelegate
  @protocol TunneledAppDelegate
  Used to communicate with the application that is using the PsiphonTunnel framework,
  Used to communicate with the application that is using the PsiphonTunnel framework,
@@ -125,9 +147,21 @@ FOUNDATION_EXPORT const unsigned char PsiphonTunnelVersionString[];
  Called when the tunnel notices that the device has no network connectivity and
  Called when the tunnel notices that the device has no network connectivity and
  begins waiting to regain it. When connecitvity is regained, `onConnecting`
  begins waiting to regain it. When connecitvity is regained, `onConnecting`
  will be called.
  will be called.
+ Swift: @code func onStartedWaitingForNetworkConnectivity @endcode
  */
  */
 - (void)onStartedWaitingForNetworkConnectivity;
 - (void)onStartedWaitingForNetworkConnectivity;
 
 
+/*!
+ Called when the tunnel's connection state changes.
+ Note that this will be called _in addition to, but before_ `onConnecting`, etc.
+ Also note that this will not be called for the initial disconnected state
+ (since it didn't change from anything).
+ @param oldState  The previous connection state.
+ @param newState  The new connection state.
+ Swift: @code func onConnectionStateChanged(from oldState: PsiphonConnectionState, to newState: PsiphonConnectionState) @endcode
+ */
+- (void)onConnectionStateChangedFrom:(PsiphonConnectionState)oldState to:(PsiphonConnectionState)newState;
+
 /*!
 /*!
  Called to indicate that tunnel-core is exiting imminently (usually do to
  Called to indicate that tunnel-core is exiting imminently (usually do to
  a `stop()` call, but could be due to an unexpected error).
  a `stop()` call, but could be due to an unexpected error).
@@ -248,6 +282,7 @@ FOUNDATION_EXPORT const unsigned char PsiphonTunnelVersionString[];
  Returns an instance of PsiphonTunnel. This is either a new instance or the pre-existing singleton. If an instance already exists, it will be stopped when this function is called.
  Returns an instance of PsiphonTunnel. This is either a new instance or the pre-existing singleton. If an instance already exists, it will be stopped when this function is called.
  @param tunneledAppDelegate  The delegate implementation to use for callbacks.
  @param tunneledAppDelegate  The delegate implementation to use for callbacks.
  @return  The PsiphonTunnel instance.
  @return  The PsiphonTunnel instance.
+ Swift: @code open class func newPsiphonTunnel(_ tunneledAppDelegate: TunneledAppDelegate) -> Self @endcode
  */
  */
 + (PsiphonTunnel * _Nonnull)newPsiphonTunnel:(id<TunneledAppDelegate> _Nonnull)tunneledAppDelegate;
 + (PsiphonTunnel * _Nonnull)newPsiphonTunnel:(id<TunneledAppDelegate> _Nonnull)tunneledAppDelegate;
 
 
@@ -255,14 +290,23 @@ FOUNDATION_EXPORT const unsigned char PsiphonTunnelVersionString[];
  Start connecting the PsiphonTunnel. Returns before connection is complete -- delegate callbacks (such as `onConnected`) are used to indicate progress and state.
  Start connecting the PsiphonTunnel. Returns before connection is complete -- delegate callbacks (such as `onConnected`) are used to indicate progress and state.
  @param embeddedServerEntries  Pre-existing server entries to use when attempting to connect to a server. May be null if there are no embedded server entries.
  @param embeddedServerEntries  Pre-existing server entries to use when attempting to connect to a server. May be null if there are no embedded server entries.
  @return TRUE if the connection start was successful, FALSE otherwise.
  @return TRUE if the connection start was successful, FALSE otherwise.
+ Swift: @code open func start(_ embeddedServerEntries: String?) -> Bool @endcode
  */
  */
 - (BOOL)start:(NSString * _Nullable)embeddedServerEntries;
 - (BOOL)start:(NSString * _Nullable)embeddedServerEntries;
 
 
 /*!
 /*!
  Stop the tunnel (regardless of its current connection state). Returns before full stop is complete -- `TunneledAppDelegate::onExiting` is called when complete.
  Stop the tunnel (regardless of its current connection state). Returns before full stop is complete -- `TunneledAppDelegate::onExiting` is called when complete.
+ Swift: @code open func stop() @endcode
  */
  */
 - (void)stop;
 - (void)stop;
 
 
+/*!
+ Returns the current tunnel connection state.
+ @return  The current connection state.
+ Swift: @code open func getConnectionState() -> PsiphonConnectionState @endcode
+ */
+- (PsiphonConnectionState)getConnectionState;
+
 /*!
 /*!
  Upload a feedback package to Psiphon Inc. The app collects feedback and diagnostics information in a particular format, then calls this function to upload it for later investigation.
  Upload a feedback package to Psiphon Inc. The app collects feedback and diagnostics information in a particular format, then calls this function to upload it for later investigation.
  @note The key, server, path, and headers must be provided by Psiphon Inc.
  @note The key, server, path, and headers must be provided by Psiphon Inc.
@@ -270,6 +314,7 @@ FOUNDATION_EXPORT const unsigned char PsiphonTunnelVersionString[];
  @param b64EncodedPublicKey  The key that will be used to encrypt the payload before uploading.
  @param b64EncodedPublicKey  The key that will be used to encrypt the payload before uploading.
  @param uploadServer  The server and path to which the data will be uploaded.
  @param uploadServer  The server and path to which the data will be uploaded.
  @param uploadServerHeaders  The request headers that will be used when uploading.
  @param uploadServerHeaders  The request headers that will be used when uploading.
+ Swift: @code open func sendFeedback(_ feedbackJson: String, publicKey b64EncodedPublicKey: String, uploadServer: String, uploadServerHeaders: String) @endcode
  */
  */
 - (void)sendFeedback:(NSString * _Nonnull)feedbackJson
 - (void)sendFeedback:(NSString * _Nonnull)feedbackJson
            publicKey:(NSString * _Nonnull)b64EncodedPublicKey
            publicKey:(NSString * _Nonnull)b64EncodedPublicKey

+ 39 - 4
MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/PsiphonTunnel.m

@@ -35,9 +35,16 @@
 @end
 @end
 
 
 @implementation PsiphonTunnel {
 @implementation PsiphonTunnel {
-    _Atomic BOOL _isWaitingForNetworkConnectivity;
+    _Atomic BOOL isWaitingForNetworkConnectivity;
+    _Atomic PsiphonConnectionState connectionState;
+
 }
 }
 
 
+- (id)init {
+    atomic_init(&isWaitingForNetworkConnectivity, NO);
+    atomic_init(&connectionState, PsiphonConnectionStateDisconnected);
+    return self;
+}
 
 
 #pragma mark - PsiphonTunnel public methods
 #pragma mark - PsiphonTunnel public methods
 
 
@@ -55,7 +62,7 @@
         
         
         [sharedInstance stop];
         [sharedInstance stop];
         sharedInstance.tunneledAppDelegate = tunneledAppDelegate;
         sharedInstance.tunneledAppDelegate = tunneledAppDelegate;
-        
+
         return sharedInstance;
         return sharedInstance;
     }
     }
 }
 }
@@ -77,6 +84,8 @@
             return FALSE;
             return FALSE;
         }
         }
 
 
+        [self changeConnectionStateTo:PsiphonConnectionStateConnecting];
+
         @try {
         @try {
             NSError *e = nil;
             NSError *e = nil;
             
             
@@ -92,12 +101,16 @@
             
             
             if (e != nil) {
             if (e != nil) {
                 [self logMessage:[NSString stringWithFormat: @"Psiphon tunnel start failed: %@", e.localizedDescription]];
                 [self logMessage:[NSString stringWithFormat: @"Psiphon tunnel start failed: %@", e.localizedDescription]];
+                [self changeConnectionStateTo:PsiphonConnectionStateDisconnected];
                 return FALSE;
                 return FALSE;
             }
             }
         }
         }
         @catch(NSException *exception) {
         @catch(NSException *exception) {
             [self logMessage:[NSString stringWithFormat: @"Failed to start Psiphon library: %@", exception.reason]];
             [self logMessage:[NSString stringWithFormat: @"Failed to start Psiphon library: %@", exception.reason]];
+            [self changeConnectionStateTo:PsiphonConnectionStateDisconnected];
+            return FALSE;
         }
         }
+
         [self logMessage:@"Psiphon tunnel started"];
         [self logMessage:@"Psiphon tunnel started"];
         
         
         return TRUE;
         return TRUE;
@@ -111,10 +124,16 @@
         GoPsiStop();
         GoPsiStop();
         [self logMessage: @"Psiphon library stopped"];
         [self logMessage: @"Psiphon library stopped"];
 
 
-        atomic_init(&_isWaitingForNetworkConnectivity, NO);
+        atomic_store(&isWaitingForNetworkConnectivity, NO);
+        [self changeConnectionStateTo:PsiphonConnectionStateDisconnected];
     }
     }
 }
 }
 
 
+// See comment in header.
+-(PsiphonConnectionState) getConnectionState {
+    return atomic_load(&connectionState);
+}
+
 // See comment in header.
 // See comment in header.
 - (void)sendFeedback:(NSString * _Nonnull)feedbackJson
 - (void)sendFeedback:(NSString * _Nonnull)feedbackJson
            publicKey:(NSString * _Nonnull)b64EncodedPublicKey
            publicKey:(NSString * _Nonnull)b64EncodedPublicKey
@@ -412,10 +431,12 @@
 
 
         if ([count integerValue] > 0) {
         if ([count integerValue] > 0) {
             if ([self.tunneledAppDelegate respondsToSelector:@selector(onConnected)]) {
             if ([self.tunneledAppDelegate respondsToSelector:@selector(onConnected)]) {
+                [self changeConnectionStateTo:PsiphonConnectionStateConnected];
                 [self.tunneledAppDelegate onConnected];
                 [self.tunneledAppDelegate onConnected];
             }
             }
         } else {
         } else {
             if ([self.tunneledAppDelegate respondsToSelector:@selector(onConnecting)]) {
             if ([self.tunneledAppDelegate respondsToSelector:@selector(onConnecting)]) {
+                [self changeConnectionStateTo:PsiphonConnectionStateConnecting];
                 [self.tunneledAppDelegate onConnecting];
                 [self.tunneledAppDelegate onConnecting];
             }
             }
         }
         }
@@ -615,8 +636,10 @@
     BOOL hasConnectivity = [reachability currentReachabilityStatus] != NotReachable;
     BOOL hasConnectivity = [reachability currentReachabilityStatus] != NotReachable;
 
 
     // If we had connectivity and now we've lost it, let the app know by calling onStartedWaitingForNetworkConnectivity.
     // If we had connectivity and now we've lost it, let the app know by calling onStartedWaitingForNetworkConnectivity.
-    BOOL wasWaitingForNetworkConnectivity = atomic_exchange(&_isWaitingForNetworkConnectivity, !hasConnectivity);
+    BOOL wasWaitingForNetworkConnectivity = atomic_exchange(&isWaitingForNetworkConnectivity, !hasConnectivity);
     if (!hasConnectivity && !wasWaitingForNetworkConnectivity) {
     if (!hasConnectivity && !wasWaitingForNetworkConnectivity) {
+        [self changeConnectionStateTo:PsiphonConnectionStateWaitingForNetwork];
+
         // HasNetworkConnectivity may be called many times, but only call
         // HasNetworkConnectivity may be called many times, but only call
         // onStartedWaitingForNetworkConnectivity once per loss of connectivity,
         // onStartedWaitingForNetworkConnectivity once per loss of connectivity,
         // so the library consumer may log a single message.
         // so the library consumer may log a single message.
@@ -652,6 +675,18 @@
     }
     }
 }
 }
 
 
+- (void)changeConnectionStateTo:(PsiphonConnectionState)newState {
+    // Store the new state and get the old state.
+    PsiphonConnectionState oldState = atomic_exchange(&connectionState, newState);
+
+    // If the state has changed, inform the app.
+    if (oldState != newState) {
+        if ([self.tunneledAppDelegate respondsToSelector:@selector(onConnectionStateChangedFrom:to:)]) {
+            [self.tunneledAppDelegate onConnectionStateChangedFrom:oldState to:newState];
+        }
+    }
+}
+
 /*!
 /*!
  Determine the device's region. Makes a best guess based on available info.
  Determine the device's region. Makes a best guess based on available info.
  @returns The two-letter country code that the device is probably located in.
  @returns The two-letter country code that the device is probably located in.

+ 12 - 0
MobileLibrary/iOS/SampleApps/TunneledWebRequest/TunneledWebRequest/Info.plist

@@ -22,6 +22,18 @@
 	<false/>
 	<false/>
 	<key>LSRequiresIPhoneOS</key>
 	<key>LSRequiresIPhoneOS</key>
 	<true/>
 	<true/>
+	<key>NSAppTransportSecurity</key>
+	<dict>
+		<key>NSExceptionDomains</key>
+		<dict>
+			<key>ifconfig.co</key>
+			<string>YES</string>
+			<key>ip-api.com</key>
+			<string>YES</string>
+			<key>ipinfo.io</key>
+			<string>YES</string>
+		</dict>
+	</dict>
 	<key>UILaunchStoryboardName</key>
 	<key>UILaunchStoryboardName</key>
 	<string>LaunchScreen</string>
 	<string>LaunchScreen</string>
 	<key>UIMainStoryboardFile</key>
 	<key>UIMainStoryboardFile</key>

+ 8 - 3
MobileLibrary/iOS/SampleApps/TunneledWebRequest/TunneledWebRequest/ViewController.swift

@@ -267,7 +267,7 @@ extension ViewController: TunneledAppDelegate {
                 
                 
                 // Then we'll make a different "what is my IP" request via makeRequestViaUrlProxy().
                 // Then we'll make a different "what is my IP" request via makeRequestViaUrlProxy().
                 DispatchQueue.global(qos: .default).async {
                 DispatchQueue.global(qos: .default).async {
-                    let url = "http://ipinfo.io/json"
+                    let url = "http://ifconfig.co/json"
                     self.makeRequestViaUrlProxy(url) {
                     self.makeRequestViaUrlProxy(url) {
                         (_ result: String?) in
                         (_ result: String?) in
                         
                         
@@ -276,9 +276,14 @@ extension ViewController: TunneledAppDelegate {
                             return
                             return
                         }
                         }
                         
                         
+						// Do a little pretty-printing.
+						let prettyResult = result?.replacingOccurrences(of: ",", with: ",\n  ")
+							.replacingOccurrences(of: "{", with: "{\n  ")
+							.replacingOccurrences(of: "}", with: "\n}")
+
                         DispatchQueue.main.sync {
                         DispatchQueue.main.sync {
                             // Load the result into the view.
                             // Load the result into the view.
-                            self.appendToView("Result from \(url):\n\(result!)")
+                            self.appendToView("Result from \(url):\n\(prettyResult!)")
                         }
                         }
                         
                         
                         // We're done with the Psiphon tunnel, so stop it.
                         // We're done with the Psiphon tunnel, so stop it.
@@ -290,7 +295,7 @@ extension ViewController: TunneledAppDelegate {
             }
             }
         }
         }
     }
     }
-    
+
     func onListeningSocksProxyPort(_ port: Int) {
     func onListeningSocksProxyPort(_ port: Int) {
         NSLog("onListeningSocksProxyPort: %d", port)
         NSLog("onListeningSocksProxyPort: %d", port)
         // Record the port being used so that we can proxy through it later.
         // Record the port being used so that we can proxy through it later.