Przeglądaj źródła

Add state tracking to library

Adam Pritchard 8 lat temu
rodzic
commit
8f2783bb81

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

@@ -33,6 +33,28 @@ FOUNDATION_EXPORT double PsiphonTunnelVersionNumber;
 //! Project version string for PsiphonTunnel.
 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
  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
  begins waiting to regain it. When connecitvity is regained, `onConnecting`
  will be called.
+ Swift: @code func onStartedWaitingForNetworkConnectivity @endcode
  */
 - (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
  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.
  @param tunneledAppDelegate  The delegate implementation to use for callbacks.
  @return  The PsiphonTunnel instance.
+ Swift: @code open class func newPsiphonTunnel(_ tunneledAppDelegate: TunneledAppDelegate) -> Self @endcode
  */
 + (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.
  @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.
+ Swift: @code open func start(_ embeddedServerEntries: String?) -> Bool @endcode
  */
 - (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.
+ Swift: @code open func stop() @endcode
  */
 - (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.
  @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 uploadServer  The server and path to which the data will be uploaded.
  @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
            publicKey:(NSString * _Nonnull)b64EncodedPublicKey

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

@@ -35,9 +35,16 @@
 @end
 
 @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
 
@@ -55,7 +62,7 @@
         
         [sharedInstance stop];
         sharedInstance.tunneledAppDelegate = tunneledAppDelegate;
-        
+
         return sharedInstance;
     }
 }
@@ -77,6 +84,8 @@
             return FALSE;
         }
 
+        [self changeConnectionStateTo:PsiphonConnectionStateConnecting];
+
         @try {
             NSError *e = nil;
             
@@ -92,12 +101,16 @@
             
             if (e != nil) {
                 [self logMessage:[NSString stringWithFormat: @"Psiphon tunnel start failed: %@", e.localizedDescription]];
+                [self changeConnectionStateTo:PsiphonConnectionStateDisconnected];
                 return FALSE;
             }
         }
         @catch(NSException *exception) {
             [self logMessage:[NSString stringWithFormat: @"Failed to start Psiphon library: %@", exception.reason]];
+            [self changeConnectionStateTo:PsiphonConnectionStateDisconnected];
+            return FALSE;
         }
+
         [self logMessage:@"Psiphon tunnel started"];
         
         return TRUE;
@@ -111,10 +124,16 @@
         GoPsiStop();
         [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.
 - (void)sendFeedback:(NSString * _Nonnull)feedbackJson
            publicKey:(NSString * _Nonnull)b64EncodedPublicKey
@@ -412,10 +431,12 @@
 
         if ([count integerValue] > 0) {
             if ([self.tunneledAppDelegate respondsToSelector:@selector(onConnected)]) {
+                [self changeConnectionStateTo:PsiphonConnectionStateConnected];
                 [self.tunneledAppDelegate onConnected];
             }
         } else {
             if ([self.tunneledAppDelegate respondsToSelector:@selector(onConnecting)]) {
+                [self changeConnectionStateTo:PsiphonConnectionStateConnecting];
                 [self.tunneledAppDelegate onConnecting];
             }
         }
@@ -615,8 +636,10 @@
     BOOL hasConnectivity = [reachability currentReachabilityStatus] != NotReachable;
 
     // 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) {
+        [self changeConnectionStateTo:PsiphonConnectionStateWaitingForNetwork];
+
         // HasNetworkConnectivity may be called many times, but only call
         // onStartedWaitingForNetworkConnectivity once per loss of connectivity,
         // 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.
  @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/>
 	<key>LSRequiresIPhoneOS</key>
 	<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>
 	<string>LaunchScreen</string>
 	<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().
                 DispatchQueue.global(qos: .default).async {
-                    let url = "http://ipinfo.io/json"
+                    let url = "http://ifconfig.co/json"
                     self.makeRequestViaUrlProxy(url) {
                         (_ result: String?) in
                         
@@ -276,9 +276,14 @@ extension ViewController: TunneledAppDelegate {
                             return
                         }
                         
+						// Do a little pretty-printing.
+						let prettyResult = result?.replacingOccurrences(of: ",", with: ",\n  ")
+							.replacingOccurrences(of: "{", with: "{\n  ")
+							.replacingOccurrences(of: "}", with: "\n}")
+
                         DispatchQueue.main.sync {
                             // 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.
@@ -290,7 +295,7 @@ extension ViewController: TunneledAppDelegate {
             }
         }
     }
-    
+
     func onListeningSocksProxyPort(_ port: Int) {
         NSLog("onListeningSocksProxyPort: %d", port)
         // Record the port being used so that we can proxy through it later.