Просмотр исходного кода

Finish creating first cut of iOS library and sample app

Adam Pritchard 9 лет назад
Родитель
Сommit
bcfd96bea1
51 измененных файлов с 5699 добавлено и 2558 удалено
  1. 192 0
      MobileLibrary/iOS/.gitignore
  2. 37 0
      MobileLibrary/iOS/BUILD.md
  3. 540 0
      MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel.xcodeproj/project.pbxproj
  4. 1 1
      MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel.xcodeproj/project.xcworkspace/contents.xcworkspacedata
  5. 0 0
      MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/Info.plist
  6. 256 0
      MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/PsiphonTunnel.h
  7. 500 0
      MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/PsiphonTunnel.m
  8. 0 0
      MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/Reachability/Reachability.h
  9. 0 0
      MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/Reachability/Reachability.m
  10. 35 0
      MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/json-framework/SBJson4.h
  11. 248 0
      MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/json-framework/SBJson4Parser.h
  12. 287 0
      MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/json-framework/SBJson4Parser.m
  13. 131 0
      MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/json-framework/SBJson4StreamParser.h
  14. 317 0
      MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/json-framework/SBJson4StreamParser.m
  15. 82 0
      MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/json-framework/SBJson4StreamParserState.h
  16. 355 0
      MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/json-framework/SBJson4StreamParserState.m
  17. 40 0
      MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/json-framework/SBJson4StreamTokeniser.h
  18. 395 0
      MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/json-framework/SBJson4StreamTokeniser.m
  19. 210 0
      MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/json-framework/SBJson4StreamWriter.h
  20. 358 0
      MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/json-framework/SBJson4StreamWriter.m
  21. 69 0
      MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/json-framework/SBJson4StreamWriterState.h
  22. 147 0
      MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/json-framework/SBJson4StreamWriterState.m
  23. 103 0
      MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/json-framework/SBJson4Writer.h
  24. 102 0
      MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/json-framework/SBJson4Writer.m
  25. 22 0
      MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnelTests/Info.plist
  26. 39 0
      MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnelTests/PsiphonTunnelTests.m
  27. 0 65
      MobileLibrary/iOS/PsiphonTunnelController/.gitignore
  28. 0 380
      MobileLibrary/iOS/PsiphonTunnelController/PsiphonTunnelController.xcodeproj/project.pbxproj
  29. 0 114
      MobileLibrary/iOS/PsiphonTunnelController/PsiphonTunnelController.xcodeproj/project.xcworkspace/xcshareddata/PsiphonTunnelController.xcscmblueprint
  30. 0 22
      MobileLibrary/iOS/PsiphonTunnelController/PsiphonTunnelController.xcodeproj/project.xcworkspace/xcuserdata/eugene.xcuserdatad/WorkspaceSettings.xcsettings
  31. 0 80
      MobileLibrary/iOS/PsiphonTunnelController/PsiphonTunnelController.xcodeproj/xcuserdata/eugene.xcuserdatad/xcschemes/PsiphonTunnelController.xcscheme
  32. 0 22
      MobileLibrary/iOS/PsiphonTunnelController/PsiphonTunnelController.xcodeproj/xcuserdata/eugene.xcuserdatad/xcschemes/xcschememanagement.plist
  33. 0 2
      MobileLibrary/iOS/PsiphonTunnelController/PsiphonTunnelController/.gitignore
  34. 0 589
      MobileLibrary/iOS/PsiphonTunnelController/PsiphonTunnelController/PsiphonTunnel.swift
  35. 0 1245
      MobileLibrary/iOS/PsiphonTunnelController/PsiphonTunnelController/SwiftyJSON.swift
  36. 5 30
      MobileLibrary/iOS/README.md
  37. 579 0
      MobileLibrary/iOS/SampleApps/TunneledWebRequest/TunneledWebRequest.xcodeproj/project.pbxproj
  38. 7 0
      MobileLibrary/iOS/SampleApps/TunneledWebRequest/TunneledWebRequest.xcodeproj/project.xcworkspace/contents.xcworkspacedata
  39. 43 0
      MobileLibrary/iOS/SampleApps/TunneledWebRequest/TunneledWebRequest/AppDelegate.swift
  40. 68 0
      MobileLibrary/iOS/SampleApps/TunneledWebRequest/TunneledWebRequest/Assets.xcassets/AppIcon.appiconset/Contents.json
  41. 27 0
      MobileLibrary/iOS/SampleApps/TunneledWebRequest/TunneledWebRequest/Base.lproj/LaunchScreen.storyboard
  42. 26 0
      MobileLibrary/iOS/SampleApps/TunneledWebRequest/TunneledWebRequest/Base.lproj/Main.storyboard
  43. 45 0
      MobileLibrary/iOS/SampleApps/TunneledWebRequest/TunneledWebRequest/Info.plist
  44. 210 0
      MobileLibrary/iOS/SampleApps/TunneledWebRequest/TunneledWebRequest/ViewController.swift
  45. 13 0
      MobileLibrary/iOS/SampleApps/TunneledWebRequest/TunneledWebRequest/psiphon-config.json.stub
  46. 22 0
      MobileLibrary/iOS/SampleApps/TunneledWebRequest/TunneledWebRequestTests/Info.plist
  47. 36 0
      MobileLibrary/iOS/SampleApps/TunneledWebRequest/TunneledWebRequestTests/TunneledWebRequestTests.swift
  48. 22 0
      MobileLibrary/iOS/SampleApps/TunneledWebRequest/TunneledWebRequestUITests/Info.plist
  49. 36 0
      MobileLibrary/iOS/SampleApps/TunneledWebRequest/TunneledWebRequestUITests/TunneledWebRequestUITests.swift
  50. 33 0
      MobileLibrary/iOS/USAGE.md
  51. 61 8
      MobileLibrary/iOS/build-psiphon-framework.sh

+ 192 - 0
MobileLibrary/iOS/.gitignore

@@ -1,3 +1,195 @@
+# Don't commit build artifacts
 go-ios-build
 build
+Psi.framework
+PsiphonTunnel.framework
+rootCAs.txt
+psiphon-config.json
 
+#
+# Xcode
+#
+# https://github.com/github/gitignore/blob/master/Global/Xcode.gitignore
+
+## Build generated
+build/
+DerivedData/
+
+## Various settings
+*.pbxuser
+!default.pbxuser
+*.mode1v3
+!default.mode1v3
+*.mode2v3
+!default.mode2v3
+*.perspectivev3
+!default.perspectivev3
+xcuserdata/
+
+## Other
+*.moved-aside
+*.xccheckout
+*.xcscmblueprint
+
+#
+# Objective-C
+#
+# https://github.com/github/gitignore/blob/master/Objective-C.gitignore
+
+## Build generated
+build/
+DerivedData/
+
+## Various settings
+*.pbxuser
+!default.pbxuser
+*.mode1v3
+!default.mode1v3
+*.mode2v3
+!default.mode2v3
+*.perspectivev3
+!default.perspectivev3
+xcuserdata/
+
+## Other
+*.moved-aside
+*.xcuserstate
+
+## Obj-C/Swift specific
+*.hmap
+*.ipa
+*.dSYM.zip
+*.dSYM
+
+# CocoaPods
+#
+# We recommend against adding the Pods directory to your .gitignore. However
+# you should judge for yourself, the pros and cons are mentioned at:
+# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
+#
+# Pods/
+
+# Carthage
+#
+# Add this line if you want to avoid checking in source code from Carthage dependencies.
+# Carthage/Checkouts
+
+Carthage/Build
+
+# fastlane
+#
+# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
+# screenshots whenever they are needed.
+# For more information about the recommended setup visit:
+# https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
+
+fastlane/report.xml
+fastlane/Preview.html
+fastlane/screenshots
+fastlane/test_output
+
+# Code Injection
+#
+# After new code Injection tools there's a generated folder /iOSInjectionProject
+# https://github.com/johnno1962/injectionforxcode
+
+iOSInjectionProject/
+
+#
+# Swift
+#
+# https://github.com/github/gitignore/blob/master/Swift.gitignore
+
+## Build generated
+build/
+DerivedData/
+
+## Various settings
+*.pbxuser
+!default.pbxuser
+*.mode1v3
+!default.mode1v3
+*.mode2v3
+!default.mode2v3
+*.perspectivev3
+!default.perspectivev3
+xcuserdata/
+
+## Other
+*.moved-aside
+*.xcuserstate
+
+## Obj-C/Swift specific
+*.hmap
+*.ipa
+*.dSYM.zip
+*.dSYM
+
+## Playgrounds
+timeline.xctimeline
+playground.xcworkspace
+
+# Swift Package Manager
+#
+# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
+# Packages/
+.build/
+
+# CocoaPods
+#
+# We recommend against adding the Pods directory to your .gitignore. However
+# you should judge for yourself, the pros and cons are mentioned at:
+# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
+#
+# Pods/
+
+# Carthage
+#
+# Add this line if you want to avoid checking in source code from Carthage dependencies.
+# Carthage/Checkouts
+
+Carthage/Build
+
+# fastlane
+#
+# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
+# screenshots whenever they are needed.
+# For more information about the recommended setup visit:
+# https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
+
+fastlane/report.xml
+fastlane/Preview.html
+fastlane/screenshots
+fastlane/test_output
+
+#
+# MacOS
+#
+# https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
+
+*.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk

+ 37 - 0
MobileLibrary/iOS/BUILD.md

@@ -0,0 +1,37 @@
+# Building the Psiphon iOS Library
+
+**Note:** If you want to use the Psiphon library, you must use the pre-built binary. See [USAGE.md](USAGE.md) for instructions. (This building doc is for Psiphon devs' reference.)
+
+## Manual Build
+
+### Prerequisites
+
+* xcode `xcode-select --install`
+
+* [git](https://git-scm.com/download/mac)
+
+* [homebrew](http://brew.sh/)
+
+* golang
+  - `brew install go`
+
+### Build Steps
+
+* Run `build-psiphon-framework.sh`.
+  - If this fails, especially in the `gomobile` step, try re-running it.
+
+* The result will be in `MobileLibrary/iOS/build`.
+
+
+## Automatic Build -- Jenkins
+
+Build artifacts can be found in Jenkins.
+
+
+## Deployment
+
+* Version numbers are arbitrary, but should be [semver](http://semver.org/)-compatible.
+
+* iOS and Android Library builds should be done at the same time, from the code, with the same version number. (There may be exceptions to this, where only one platform release makes sense.)
+
+* Use Github Releases to publish the Library binaries. Create a tag on the correct commit hash with the name of the version. Create a Release with a zip file containing the `.framework` directory and the `USAGE.md` file. Attach the Android Library binary

+ 540 - 0
MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel.xcodeproj/project.pbxproj

@@ -0,0 +1,540 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 46;
+	objects = {
+
+/* Begin PBXBuildFile section */
+		662659271DD270E900872F6C /* Reachability.h in Headers */ = {isa = PBXBuildFile; fileRef = 662659251DD270E900872F6C /* Reachability.h */; };
+		662659281DD270E900872F6C /* Reachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 662659261DD270E900872F6C /* Reachability.m */; };
+		66BDB02A1DA6BFCC0079384C /* PsiphonTunnel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 66BDB0201DA6BFCC0079384C /* PsiphonTunnel.framework */; };
+		66BDB02F1DA6BFCC0079384C /* PsiphonTunnelTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 66BDB02E1DA6BFCC0079384C /* PsiphonTunnelTests.m */; };
+		66BDB0311DA6BFCC0079384C /* PsiphonTunnel.h in Headers */ = {isa = PBXBuildFile; fileRef = 66BDB0231DA6BFCC0079384C /* PsiphonTunnel.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		66BDB03B1DA6C4A70079384C /* Psi.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 66BDB03A1DA6C4A70079384C /* Psi.framework */; };
+		66BDB03E1DA6C79E0079384C /* rootCAs.txt in Resources */ = {isa = PBXBuildFile; fileRef = 66BDB03D1DA6C79E0079384C /* rootCAs.txt */; };
+		66BDB0441DA6C7DD0079384C /* PsiphonTunnel.m in Sources */ = {isa = PBXBuildFile; fileRef = 66BDB0431DA6C7DD0079384C /* PsiphonTunnel.m */; };
+		66BDB0491DA6D7050079384C /* Psi.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 66BDB03A1DA6C4A70079384C /* Psi.framework */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
+		66BDB05A1DC26CCC0079384C /* SBJson4.h in Headers */ = {isa = PBXBuildFile; fileRef = 66BDB04B1DC26CCC0079384C /* SBJson4.h */; };
+		66BDB05B1DC26CCC0079384C /* SBJson4Parser.h in Headers */ = {isa = PBXBuildFile; fileRef = 66BDB04C1DC26CCC0079384C /* SBJson4Parser.h */; };
+		66BDB05C1DC26CCC0079384C /* SBJson4Parser.m in Sources */ = {isa = PBXBuildFile; fileRef = 66BDB04D1DC26CCC0079384C /* SBJson4Parser.m */; };
+		66BDB05D1DC26CCC0079384C /* SBJson4StreamParser.h in Headers */ = {isa = PBXBuildFile; fileRef = 66BDB04E1DC26CCC0079384C /* SBJson4StreamParser.h */; };
+		66BDB05E1DC26CCC0079384C /* SBJson4StreamParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 66BDB04F1DC26CCC0079384C /* SBJson4StreamParser.m */; };
+		66BDB05F1DC26CCC0079384C /* SBJson4StreamParserState.h in Headers */ = {isa = PBXBuildFile; fileRef = 66BDB0501DC26CCC0079384C /* SBJson4StreamParserState.h */; };
+		66BDB0601DC26CCC0079384C /* SBJson4StreamParserState.m in Sources */ = {isa = PBXBuildFile; fileRef = 66BDB0511DC26CCC0079384C /* SBJson4StreamParserState.m */; };
+		66BDB0611DC26CCC0079384C /* SBJson4StreamTokeniser.h in Headers */ = {isa = PBXBuildFile; fileRef = 66BDB0521DC26CCC0079384C /* SBJson4StreamTokeniser.h */; };
+		66BDB0621DC26CCC0079384C /* SBJson4StreamTokeniser.m in Sources */ = {isa = PBXBuildFile; fileRef = 66BDB0531DC26CCC0079384C /* SBJson4StreamTokeniser.m */; };
+		66BDB0631DC26CCC0079384C /* SBJson4StreamWriter.h in Headers */ = {isa = PBXBuildFile; fileRef = 66BDB0541DC26CCC0079384C /* SBJson4StreamWriter.h */; };
+		66BDB0641DC26CCC0079384C /* SBJson4StreamWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = 66BDB0551DC26CCC0079384C /* SBJson4StreamWriter.m */; };
+		66BDB0651DC26CCC0079384C /* SBJson4StreamWriterState.h in Headers */ = {isa = PBXBuildFile; fileRef = 66BDB0561DC26CCC0079384C /* SBJson4StreamWriterState.h */; };
+		66BDB0661DC26CCC0079384C /* SBJson4StreamWriterState.m in Sources */ = {isa = PBXBuildFile; fileRef = 66BDB0571DC26CCC0079384C /* SBJson4StreamWriterState.m */; };
+		66BDB0671DC26CCC0079384C /* SBJson4Writer.h in Headers */ = {isa = PBXBuildFile; fileRef = 66BDB0581DC26CCC0079384C /* SBJson4Writer.h */; };
+		66BDB0681DC26CCC0079384C /* SBJson4Writer.m in Sources */ = {isa = PBXBuildFile; fileRef = 66BDB0591DC26CCC0079384C /* SBJson4Writer.m */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+		66BDB02B1DA6BFCC0079384C /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = 66BDB0171DA6BFCC0079384C /* Project object */;
+			proxyType = 1;
+			remoteGlobalIDString = 66BDB01F1DA6BFCC0079384C;
+			remoteInfo = PsiphonTunnel;
+		};
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+		66BDB0481DA6D6FA0079384C /* CopyFiles */ = {
+			isa = PBXCopyFilesBuildPhase;
+			buildActionMask = 2147483647;
+			dstPath = "";
+			dstSubfolderSpec = 10;
+			files = (
+				66BDB0491DA6D7050079384C /* Psi.framework in CopyFiles */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+		662659251DD270E900872F6C /* Reachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Reachability.h; sourceTree = "<group>"; };
+		662659261DD270E900872F6C /* Reachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Reachability.m; sourceTree = "<group>"; };
+		66BDB0201DA6BFCC0079384C /* PsiphonTunnel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PsiphonTunnel.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+		66BDB0231DA6BFCC0079384C /* PsiphonTunnel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PsiphonTunnel.h; sourceTree = "<group>"; };
+		66BDB0241DA6BFCC0079384C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+		66BDB0291DA6BFCC0079384C /* PsiphonTunnelTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PsiphonTunnelTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+		66BDB02E1DA6BFCC0079384C /* PsiphonTunnelTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PsiphonTunnelTests.m; sourceTree = "<group>"; };
+		66BDB0301DA6BFCC0079384C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+		66BDB03A1DA6C4A70079384C /* Psi.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Psi.framework; sourceTree = "<group>"; };
+		66BDB03D1DA6C79E0079384C /* rootCAs.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = rootCAs.txt; path = PsiphonTunnel/rootCAs.txt; sourceTree = "<group>"; };
+		66BDB0431DA6C7DD0079384C /* PsiphonTunnel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PsiphonTunnel.m; sourceTree = "<group>"; };
+		66BDB04B1DC26CCC0079384C /* SBJson4.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJson4.h; sourceTree = "<group>"; };
+		66BDB04C1DC26CCC0079384C /* SBJson4Parser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJson4Parser.h; sourceTree = "<group>"; };
+		66BDB04D1DC26CCC0079384C /* SBJson4Parser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJson4Parser.m; sourceTree = "<group>"; };
+		66BDB04E1DC26CCC0079384C /* SBJson4StreamParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJson4StreamParser.h; sourceTree = "<group>"; };
+		66BDB04F1DC26CCC0079384C /* SBJson4StreamParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJson4StreamParser.m; sourceTree = "<group>"; };
+		66BDB0501DC26CCC0079384C /* SBJson4StreamParserState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJson4StreamParserState.h; sourceTree = "<group>"; };
+		66BDB0511DC26CCC0079384C /* SBJson4StreamParserState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJson4StreamParserState.m; sourceTree = "<group>"; };
+		66BDB0521DC26CCC0079384C /* SBJson4StreamTokeniser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJson4StreamTokeniser.h; sourceTree = "<group>"; };
+		66BDB0531DC26CCC0079384C /* SBJson4StreamTokeniser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJson4StreamTokeniser.m; sourceTree = "<group>"; };
+		66BDB0541DC26CCC0079384C /* SBJson4StreamWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJson4StreamWriter.h; sourceTree = "<group>"; };
+		66BDB0551DC26CCC0079384C /* SBJson4StreamWriter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJson4StreamWriter.m; sourceTree = "<group>"; };
+		66BDB0561DC26CCC0079384C /* SBJson4StreamWriterState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJson4StreamWriterState.h; sourceTree = "<group>"; };
+		66BDB0571DC26CCC0079384C /* SBJson4StreamWriterState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJson4StreamWriterState.m; sourceTree = "<group>"; };
+		66BDB0581DC26CCC0079384C /* SBJson4Writer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJson4Writer.h; sourceTree = "<group>"; };
+		66BDB0591DC26CCC0079384C /* SBJson4Writer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJson4Writer.m; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+		66BDB01C1DA6BFCC0079384C /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				66BDB03B1DA6C4A70079384C /* Psi.framework in Frameworks */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		66BDB0261DA6BFCC0079384C /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				66BDB02A1DA6BFCC0079384C /* PsiphonTunnel.framework in Frameworks */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+		662659241DD270E900872F6C /* Reachability */ = {
+			isa = PBXGroup;
+			children = (
+				662659251DD270E900872F6C /* Reachability.h */,
+				662659261DD270E900872F6C /* Reachability.m */,
+			);
+			path = Reachability;
+			sourceTree = "<group>";
+		};
+		66BDB0161DA6BFCC0079384C = {
+			isa = PBXGroup;
+			children = (
+				66BDB03C1DA6C7940079384C /* Resources */,
+				66BDB0221DA6BFCC0079384C /* PsiphonTunnel */,
+				66BDB02D1DA6BFCC0079384C /* PsiphonTunnelTests */,
+				66BDB0211DA6BFCC0079384C /* Products */,
+			);
+			sourceTree = "<group>";
+		};
+		66BDB0211DA6BFCC0079384C /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				66BDB0201DA6BFCC0079384C /* PsiphonTunnel.framework */,
+				66BDB0291DA6BFCC0079384C /* PsiphonTunnelTests.xctest */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		66BDB0221DA6BFCC0079384C /* PsiphonTunnel */ = {
+			isa = PBXGroup;
+			children = (
+				662659241DD270E900872F6C /* Reachability */,
+				66BDB04A1DC26CCC0079384C /* json-framework */,
+				66BDB0231DA6BFCC0079384C /* PsiphonTunnel.h */,
+				66BDB0431DA6C7DD0079384C /* PsiphonTunnel.m */,
+				66BDB0241DA6BFCC0079384C /* Info.plist */,
+				66BDB03A1DA6C4A70079384C /* Psi.framework */,
+			);
+			path = PsiphonTunnel;
+			sourceTree = "<group>";
+		};
+		66BDB02D1DA6BFCC0079384C /* PsiphonTunnelTests */ = {
+			isa = PBXGroup;
+			children = (
+				66BDB02E1DA6BFCC0079384C /* PsiphonTunnelTests.m */,
+				66BDB0301DA6BFCC0079384C /* Info.plist */,
+			);
+			path = PsiphonTunnelTests;
+			sourceTree = "<group>";
+		};
+		66BDB03C1DA6C7940079384C /* Resources */ = {
+			isa = PBXGroup;
+			children = (
+				66BDB03D1DA6C79E0079384C /* rootCAs.txt */,
+			);
+			name = Resources;
+			sourceTree = "<group>";
+		};
+		66BDB04A1DC26CCC0079384C /* json-framework */ = {
+			isa = PBXGroup;
+			children = (
+				66BDB04B1DC26CCC0079384C /* SBJson4.h */,
+				66BDB04C1DC26CCC0079384C /* SBJson4Parser.h */,
+				66BDB04D1DC26CCC0079384C /* SBJson4Parser.m */,
+				66BDB04E1DC26CCC0079384C /* SBJson4StreamParser.h */,
+				66BDB04F1DC26CCC0079384C /* SBJson4StreamParser.m */,
+				66BDB0501DC26CCC0079384C /* SBJson4StreamParserState.h */,
+				66BDB0511DC26CCC0079384C /* SBJson4StreamParserState.m */,
+				66BDB0521DC26CCC0079384C /* SBJson4StreamTokeniser.h */,
+				66BDB0531DC26CCC0079384C /* SBJson4StreamTokeniser.m */,
+				66BDB0541DC26CCC0079384C /* SBJson4StreamWriter.h */,
+				66BDB0551DC26CCC0079384C /* SBJson4StreamWriter.m */,
+				66BDB0561DC26CCC0079384C /* SBJson4StreamWriterState.h */,
+				66BDB0571DC26CCC0079384C /* SBJson4StreamWriterState.m */,
+				66BDB0581DC26CCC0079384C /* SBJson4Writer.h */,
+				66BDB0591DC26CCC0079384C /* SBJson4Writer.m */,
+			);
+			path = "json-framework";
+			sourceTree = "<group>";
+		};
+/* End PBXGroup section */
+
+/* Begin PBXHeadersBuildPhase section */
+		66BDB01D1DA6BFCC0079384C /* Headers */ = {
+			isa = PBXHeadersBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				66BDB05D1DC26CCC0079384C /* SBJson4StreamParser.h in Headers */,
+				66BDB05F1DC26CCC0079384C /* SBJson4StreamParserState.h in Headers */,
+				66BDB0311DA6BFCC0079384C /* PsiphonTunnel.h in Headers */,
+				66BDB0651DC26CCC0079384C /* SBJson4StreamWriterState.h in Headers */,
+				66BDB05B1DC26CCC0079384C /* SBJson4Parser.h in Headers */,
+				66BDB05A1DC26CCC0079384C /* SBJson4.h in Headers */,
+				66BDB0611DC26CCC0079384C /* SBJson4StreamTokeniser.h in Headers */,
+				66BDB0631DC26CCC0079384C /* SBJson4StreamWriter.h in Headers */,
+				66BDB0671DC26CCC0079384C /* SBJson4Writer.h in Headers */,
+				662659271DD270E900872F6C /* Reachability.h in Headers */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXHeadersBuildPhase section */
+
+/* Begin PBXNativeTarget section */
+		66BDB01F1DA6BFCC0079384C /* PsiphonTunnel */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 66BDB0341DA6BFCC0079384C /* Build configuration list for PBXNativeTarget "PsiphonTunnel" */;
+			buildPhases = (
+				66BDB01B1DA6BFCC0079384C /* Sources */,
+				66BDB01C1DA6BFCC0079384C /* Frameworks */,
+				66BDB01D1DA6BFCC0079384C /* Headers */,
+				66BDB01E1DA6BFCC0079384C /* Resources */,
+				66BDB0481DA6D6FA0079384C /* CopyFiles */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = PsiphonTunnel;
+			productName = PsiphonTunnel;
+			productReference = 66BDB0201DA6BFCC0079384C /* PsiphonTunnel.framework */;
+			productType = "com.apple.product-type.framework";
+		};
+		66BDB0281DA6BFCC0079384C /* PsiphonTunnelTests */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 66BDB0371DA6BFCC0079384C /* Build configuration list for PBXNativeTarget "PsiphonTunnelTests" */;
+			buildPhases = (
+				66BDB0251DA6BFCC0079384C /* Sources */,
+				66BDB0261DA6BFCC0079384C /* Frameworks */,
+				66BDB0271DA6BFCC0079384C /* Resources */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+				66BDB02C1DA6BFCC0079384C /* PBXTargetDependency */,
+			);
+			name = PsiphonTunnelTests;
+			productName = PsiphonTunnelTests;
+			productReference = 66BDB0291DA6BFCC0079384C /* PsiphonTunnelTests.xctest */;
+			productType = "com.apple.product-type.bundle.unit-test";
+		};
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+		66BDB0171DA6BFCC0079384C /* Project object */ = {
+			isa = PBXProject;
+			attributes = {
+				LastUpgradeCheck = 0800;
+				ORGANIZATIONNAME = "Psiphon Inc.";
+				TargetAttributes = {
+					66BDB01F1DA6BFCC0079384C = {
+						CreatedOnToolsVersion = 8.0;
+						DevelopmentTeam = Q6HLNEX92A;
+						ProvisioningStyle = Automatic;
+					};
+					66BDB0281DA6BFCC0079384C = {
+						CreatedOnToolsVersion = 8.0;
+						DevelopmentTeam = Q6HLNEX92A;
+						ProvisioningStyle = Automatic;
+					};
+				};
+			};
+			buildConfigurationList = 66BDB01A1DA6BFCC0079384C /* Build configuration list for PBXProject "PsiphonTunnel" */;
+			compatibilityVersion = "Xcode 3.2";
+			developmentRegion = English;
+			hasScannedForEncodings = 0;
+			knownRegions = (
+				en,
+			);
+			mainGroup = 66BDB0161DA6BFCC0079384C;
+			productRefGroup = 66BDB0211DA6BFCC0079384C /* Products */;
+			projectDirPath = "";
+			projectRoot = "";
+			targets = (
+				66BDB01F1DA6BFCC0079384C /* PsiphonTunnel */,
+				66BDB0281DA6BFCC0079384C /* PsiphonTunnelTests */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+		66BDB01E1DA6BFCC0079384C /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				66BDB03E1DA6C79E0079384C /* rootCAs.txt in Resources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		66BDB0271DA6BFCC0079384C /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+		66BDB01B1DA6BFCC0079384C /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				66BDB05E1DC26CCC0079384C /* SBJson4StreamParser.m in Sources */,
+				66BDB0641DC26CCC0079384C /* SBJson4StreamWriter.m in Sources */,
+				66BDB0661DC26CCC0079384C /* SBJson4StreamWriterState.m in Sources */,
+				66BDB05C1DC26CCC0079384C /* SBJson4Parser.m in Sources */,
+				66BDB0681DC26CCC0079384C /* SBJson4Writer.m in Sources */,
+				66BDB0621DC26CCC0079384C /* SBJson4StreamTokeniser.m in Sources */,
+				66BDB0441DA6C7DD0079384C /* PsiphonTunnel.m in Sources */,
+				662659281DD270E900872F6C /* Reachability.m in Sources */,
+				66BDB0601DC26CCC0079384C /* SBJson4StreamParserState.m in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		66BDB0251DA6BFCC0079384C /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				66BDB02F1DA6BFCC0079384C /* PsiphonTunnelTests.m in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+		66BDB02C1DA6BFCC0079384C /* PBXTargetDependency */ = {
+			isa = PBXTargetDependency;
+			target = 66BDB01F1DA6BFCC0079384C /* PsiphonTunnel */;
+			targetProxy = 66BDB02B1DA6BFCC0079384C /* PBXContainerItemProxy */;
+		};
+/* End PBXTargetDependency section */
+
+/* Begin XCBuildConfiguration section */
+		66BDB0321DA6BFCC0079384C /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_SUSPICIOUS_MOVES = YES;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+				COPY_PHASE_STRIP = NO;
+				CURRENT_PROJECT_VERSION = 1;
+				DEBUG_INFORMATION_FORMAT = dwarf;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				ENABLE_TESTABILITY = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_DYNAMIC_NO_PIC = NO;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_OPTIMIZATION_LEVEL = 0;
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"DEBUG=1",
+					"$(inherited)",
+				);
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				IPHONEOS_DEPLOYMENT_TARGET = 10.0;
+				MTL_ENABLE_DEBUG_INFO = YES;
+				ONLY_ACTIVE_ARCH = YES;
+				SDKROOT = iphoneos;
+				TARGETED_DEVICE_FAMILY = "1,2";
+				VERSIONING_SYSTEM = "apple-generic";
+				VERSION_INFO_PREFIX = "";
+			};
+			name = Debug;
+		};
+		66BDB0331DA6BFCC0079384C /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_SUSPICIOUS_MOVES = YES;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+				COPY_PHASE_STRIP = NO;
+				CURRENT_PROJECT_VERSION = 1;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				IPHONEOS_DEPLOYMENT_TARGET = 10.0;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				SDKROOT = iphoneos;
+				TARGETED_DEVICE_FAMILY = "1,2";
+				VALIDATE_PRODUCT = YES;
+				VERSIONING_SYSTEM = "apple-generic";
+				VERSION_INFO_PREFIX = "";
+			};
+			name = Release;
+		};
+		66BDB0351DA6BFCC0079384C /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CODE_SIGN_IDENTITY = "";
+				DEFINES_MODULE = YES;
+				DEVELOPMENT_TEAM = Q6HLNEX92A;
+				DYLIB_COMPATIBILITY_VERSION = 1;
+				DYLIB_CURRENT_VERSION = 1;
+				DYLIB_INSTALL_NAME_BASE = "@rpath";
+				ENABLE_BITCODE = NO;
+				FRAMEWORK_SEARCH_PATHS = (
+					"$(inherited)",
+					"$(PROJECT_DIR)/PsiphonTunnel",
+				);
+				INFOPLIST_FILE = PsiphonTunnel/Info.plist;
+				INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+				IPHONEOS_DEPLOYMENT_TARGET = 9.3;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+				PRODUCT_BUNDLE_IDENTIFIER = com.psiphon3.ios.PsiphonTunnel;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SKIP_INSTALL = YES;
+			};
+			name = Debug;
+		};
+		66BDB0361DA6BFCC0079384C /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CODE_SIGN_IDENTITY = "";
+				DEFINES_MODULE = YES;
+				DEVELOPMENT_TEAM = Q6HLNEX92A;
+				DYLIB_COMPATIBILITY_VERSION = 1;
+				DYLIB_CURRENT_VERSION = 1;
+				DYLIB_INSTALL_NAME_BASE = "@rpath";
+				ENABLE_BITCODE = NO;
+				FRAMEWORK_SEARCH_PATHS = (
+					"$(inherited)",
+					"$(PROJECT_DIR)/PsiphonTunnel",
+				);
+				INFOPLIST_FILE = PsiphonTunnel/Info.plist;
+				INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+				IPHONEOS_DEPLOYMENT_TARGET = 9.3;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+				PRODUCT_BUNDLE_IDENTIFIER = com.psiphon3.ios.PsiphonTunnel;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SKIP_INSTALL = YES;
+			};
+			name = Release;
+		};
+		66BDB0381DA6BFCC0079384C /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				DEVELOPMENT_TEAM = Q6HLNEX92A;
+				INFOPLIST_FILE = PsiphonTunnelTests/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+				PRODUCT_BUNDLE_IDENTIFIER = com.psiphon3.ios.PsiphonTunnelTests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+			};
+			name = Debug;
+		};
+		66BDB0391DA6BFCC0079384C /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				DEVELOPMENT_TEAM = Q6HLNEX92A;
+				INFOPLIST_FILE = PsiphonTunnelTests/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+				PRODUCT_BUNDLE_IDENTIFIER = com.psiphon3.ios.PsiphonTunnelTests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+			};
+			name = Release;
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		66BDB01A1DA6BFCC0079384C /* Build configuration list for PBXProject "PsiphonTunnel" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				66BDB0321DA6BFCC0079384C /* Debug */,
+				66BDB0331DA6BFCC0079384C /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		66BDB0341DA6BFCC0079384C /* Build configuration list for PBXNativeTarget "PsiphonTunnel" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				66BDB0351DA6BFCC0079384C /* Debug */,
+				66BDB0361DA6BFCC0079384C /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		66BDB0371DA6BFCC0079384C /* Build configuration list for PBXNativeTarget "PsiphonTunnelTests" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				66BDB0381DA6BFCC0079384C /* Debug */,
+				66BDB0391DA6BFCC0079384C /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+/* End XCConfigurationList section */
+	};
+	rootObject = 66BDB0171DA6BFCC0079384C /* Project object */;
+}

+ 1 - 1
MobileLibrary/iOS/PsiphonTunnelController/PsiphonTunnelController.xcodeproj/project.xcworkspace/contents.xcworkspacedata → MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel.xcodeproj/project.xcworkspace/contents.xcworkspacedata

@@ -2,6 +2,6 @@
 <Workspace
    version = "1.0">
    <FileRef
-      location = "self:PsiphonTunnelController.xcodeproj">
+      location = "self:PsiphonTunnel.xcodeproj">
    </FileRef>
 </Workspace>

+ 0 - 0
MobileLibrary/iOS/PsiphonTunnelController/PsiphonTunnelController/Info.plist → MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/Info.plist


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

@@ -0,0 +1,256 @@
+//
+//  PsiphonTunnel.h
+//  PsiphonTunnel
+//
+
+/*
+ * Copyright (c) 2016, Psiphon Inc.
+ * All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#import <UIKit/UIKit.h>
+
+//! Project version number for PsiphonTunnel.
+FOUNDATION_EXPORT double PsiphonTunnelVersionNumber;
+
+//! Project version string for PsiphonTunnel.
+FOUNDATION_EXPORT const unsigned char PsiphonTunnelVersionString[];
+
+/*!
+ @protocol TunneledAppDelegate
+ Used to communicate with the application that is using the PsiphonTunnel framework,
+ and retrieve config info from it.
+ */
+@protocol TunneledAppDelegate
+
+/*!
+ Called when tunnel is started to get the library consumer's desired configuration.
+
+ @code
+ Required fields:
+ - `PropagationChannelId`
+ - `SponsorId`
+ - Remote server list functionality is not strictly required, but absence greatly undermines circumvention ability.
+   - `RemoteServerListUrl`
+   - `RemoteServerListSignaturePublicKey`
+
+ Optional fields (if you don't need them, don't set them):
+ - `DataStoreDirectory`: If not set, the library will use a sane location. Override if the client wants to restrict where operational data is kept.
+ - `RemoteServerListDownloadFilename`: See comment for `DataStoreDirectory`.
+ - `ClientPlatform`: Should not be set by most library consumers.
+ - `UpstreamProxyUrl`
+ - `EmitDiagnosticNotices`
+ - `LocalHttpProxyPort` // TODO: Should this be set-able for iOS?
+ - `LocalSocksProxyPort` // TODO: Should this be set-able for iOS?
+ - `EgressRegion`
+ - `EstablishTunnelTimeoutSeconds`
+ - `TunnelWholeDevice`: For stats purposes, but must be accurate. Defaults to 0 (false).
+ - Should only be set if the Psiphon library is handling upgrade downloading (which it usually is _not_):
+   - `UpgradeDownloadUrl`
+   - `UpgradeDownloadClientVersionHeader`
+   - `UpgradeDownloadFilename`
+ - Only set if disabling timeouts (for very slow network connections):
+   - `TunnelConnectTimeoutSeconds`
+   - `TunnelPortForwardDialTimeoutSeconds`
+   - `TunnelSshKeepAliveProbeTimeoutSeconds`
+   - `TunnelSshKeepAlivePeriodicTimeoutSeconds`
+   - `FetchRemoteServerListTimeoutSeconds`
+   - `PsiphonApiServerTimeoutSeconds`
+   - `FetchRoutesTimeoutSeconds`
+   - `HttpProxyOriginServerTimeoutSeconds`
+ @endcode
+
+ @note All other config fields must not be set.
+
+ See the tunnel-core config code for details about the fields.
+ https://github.com/Psiphon-Labs/psiphon-tunnel-core/blob/master/psiphon/config.go
+ 
+ @return NSString  JSON string with config that should used to run the Psiphon tunnel, or nil on error.
+ 
+ Swift: @code func getPsiphonConfig() -> String? @endcode
+ */
+- (NSString * _Nullable)getPsiphonConfig;
+
+/*!
+ Gets runtime errors info that may be useful for debugging.
+ @param message  The diagnostic message string.
+ Swift: @code func onDiagnosticMessage(_ message: String) @endcode
+ */
+- (void) onDiagnosticMessage: (NSString * _Nonnull)message;
+
+/*! 
+ Called when the tunnel is in the process of connecting.
+ Swift: @code func onConnecting() @endcode
+ */
+- (void) onConnecting;
+/*!
+ Called when the tunnel has successfully connected.
+ Swift: @code func onConnected() @endcode
+ */
+- (void) onConnected;
+
+/*!
+ Called to indicate that tunnel-core is exiting imminently (usually do to
+ a `stop()` call, but could be due to an unexpected error).
+ Swift: @code func onExiting() @endcode
+ */
+- (void) onExiting;
+
+/*!
+ Called when tunnel-core determines which server egress regions are available
+ for use. This can be used for updating the UI which provides the options to
+ the user.
+ @param regions  A string array containing the available egress region country codes.
+ Swift: @code func onAvailableEgressRegions(_ regions: [Any]) @endcode
+ */
+- (void) onAvailableEgressRegions: (NSArray * _Nonnull)regions;
+
+/*!
+ If the tunnel is started with a fixed SOCKS proxy port, and that port is
+ already in use, this will be called.
+ @param port  The port number.
+ Swift: @code func onSocksProxyPort(inUse port: Int) @endcode
+ */
+- (void) onSocksProxyPortInUse: (NSInteger)port;
+/*!
+ If the tunnel is started with a fixed HTTP proxy port, and that port is
+ already in use, this will be called.
+ @param port  The port number.
+ Swift: @code func onHttpProxyPort(inUse port: Int) @endcode
+ */
+- (void) onHttpProxyPortInUse: (NSInteger)port;
+
+/*!
+ Called when tunnel-core determines what port will be used for the local SOCKS proxy.
+ @param port  The port number.
+ Swift: @code func onListeningSocksProxyPort(_ port: Int) @endcode
+ */
+- (void) onListeningSocksProxyPort: (NSInteger)port;
+/*!
+ Called when tunnel-core determines what port will be used for the local HTTP proxy.
+ @param port  The port number.
+ Swift: @code func onListeningHttpProxyPort(_ port: Int) @endcode
+ */
+- (void) onListeningHttpProxyPort: (NSInteger)port;
+
+/*!
+ Called when a error occurs when trying to utilize a configured upstream proxy.
+ @param message  A message giving additional info about the error.
+ Swift: @code func onUpstreamProxyError(_ message: String) @endcode
+ */
+- (void) onUpstreamProxyError: (NSString * _Nonnull)message;
+
+/*!
+ Called after the handshake with the Psiphon server, with the client region as determined by the server.
+ @param region  The country code of the client, as determined by the server.
+ Swift: @code func onClientRegion(_ region: String) @endcode
+ */
+- (void) onClientRegion: (NSString * _Nonnull)region;
+
+/*!
+ Called to report that split tunnel is on for the given region.
+ @param region  The region split tunnel is on for.
+ Swift: @code func onSplitTunnelRegion(_ region: String) @endcode
+ */
+- (void) onSplitTunnelRegion: (NSString * _Nonnull)region;
+
+/*!
+ Called to indicate that an address has been classified as being within the
+ split tunnel region and therefore is being access directly rather than tunneled.
+ Note: `address` should remain private; this notice should be used for alerting
+ users, not for diagnotics logs.
+ @param address  The IP or hostname that is not being tunneled.
+ Swift: @code func onUntunneledAddress(_ address: String) @endcode
+ */
+- (void) onUntunneledAddress: (NSString * _Nonnull) address;
+
+/*!
+ Called to report how many bytes have been transferred since the last time
+ this function was called.
+ @param sent  The number of bytes sent.
+ @param received  The number of bytes received.
+ Swift: @code func onBytesTransferred(_ sent: Int64, _ received: Int64) @endcode
+ */
+- (void) onBytesTransferred: (int64_t)sent : (int64_t)received;
+
+// TODO: Only applicable to Psiphon proper?
+/*!
+ Called when tunnel-core discovers a home page associated with this client.
+ If there are no home pages, it will not be called. May be called more than
+ once, for multiple home pages.
+ @param url  The URL of the home page.
+ Swift: @code func onHomepage(_ url: String) @endcode
+ */
+- (void) onHomepage: (NSString * _Nonnull)url;
+
+// TODO: Only applicable to Psiphon proper?
+/*!
+ Called if the current version of the client is the latest (i.e., there is no upgrade available).
+ Swift: @code func onClientIsLatestVersion() @endcode
+ */
+- (void) onClientIsLatestVersion;
+
+// TODO: Only applicable to Psiphon proper?
+/*!
+ Called when a client upgrade has been downloaded.
+ @param filename  The name of the file containing the upgrade.
+ Swift: @code func onClientUpgradeDownloaded(_ filename: String) @endcode
+ */
+- (void) onClientUpgradeDownloaded: (NSString * _Nonnull)filename;
+
+// TODO: Applies to iOS?
+//func onClientVerificationRequired(nonce: String, ttlSeconds: Int, resetCache: Bool)
+
+@end
+
+/*!
+ The interface for managing the Psiphon tunnel -- set up, tear down, receive info about.
+ */
+@interface PsiphonTunnel : NSObject
+
+/*!
+ 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.
+ */
++(PsiphonTunnel * _Nonnull) newPsiphonTunnel:(id<TunneledAppDelegate> _Nonnull)tunneledAppDelegate;
+
+/*!
+ 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.
+ */
+-(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.
+ */
+-(void) stop;
+
+/*!
+ 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.
+ @param connectionConfigJson  This function may create a tunnel to perform the upload, and this configuration is used to create that tunnel.
+ @param diagnosticsJson  The feedback and diagnostics data to upload.
+ @param b64EncodedPublicKey  The key that will be used to encrypt the payload before uploading.
+ @param uploadServer  The server to which the data will be uploaded.
+ @param uploadPath  The path on the server to which the data will be loaded.
+ @param uploadServerHeaders  The request headers that will be used when uploading.
+ */
++ (void)sendFeedback:(NSString * _Nonnull)connectionConfigJson diagnostics:(NSString * _Nonnull)diagnosticsJson publicKey:(NSString * _Nonnull)b64EncodedPublicKey uploadServer:(NSString * _Nonnull)uploadServer uploadPath:(NSString * _Nonnull)uploadPath uploadServerHeaders:(NSString * _Nonnull)uploadServerHeaders;
+
+@end

Разница между файлами не показана из-за своего большого размера
+ 500 - 0
MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/PsiphonTunnel.m


+ 0 - 0
MobileLibrary/iOS/PsiphonTunnelController/PsiphonTunnelController/Reachability.h → MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/Reachability/Reachability.h


+ 0 - 0
MobileLibrary/iOS/PsiphonTunnelController/PsiphonTunnelController/Reachability.m → MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/Reachability/Reachability.m


+ 35 - 0
MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/json-framework/SBJson4.h

@@ -0,0 +1,35 @@
+/*
+ Copyright (C) 2009-2011 Stig Brautaset. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+ * Neither the name of the author nor the names of its contributors may be used
+   to endorse or promote products derived from this software without specific
+   prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "SBJson4Writer.h"
+#import "SBJson4StreamParser.h"
+#import "SBJson4Parser.h"
+#import "SBJson4StreamWriter.h"
+#import "SBJson4StreamTokeniser.h"
+

+ 248 - 0
MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/json-framework/SBJson4Parser.h

@@ -0,0 +1,248 @@
+/*
+ Copyright (c) 2010-2013, Stig Brautaset.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+   Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+
+   Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+
+   Neither the name of the the author nor the names of its contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <Foundation/Foundation.h>
+#import "SBJson4StreamParser.h"
+
+/**
+ Block called when the parser has parsed an item. This could be once
+ for each root document parsed, or once for each unwrapped root array element.
+
+ @param item contains the parsed item.
+ @param stop set to YES if you want the parser to stop
+ */
+typedef void (^SBJson4ValueBlock)(id item, BOOL* stop);
+
+/**
+ Block called if an error occurs.
+ @param error the error.
+ */
+typedef void (^SBJson4ErrorBlock)(NSError* error);
+
+/**
+ Block used to process parsed tokens as they are encountered. You can use this
+ to transform strings containing dates into NSDate, for example.
+ @param item the parsed token
+ @param path the JSON Path of the token
+ */
+typedef id (^SBJson4ProcessBlock)(id item, NSString* path);
+
+
+/**
+ Parse one or more chunks of JSON data.
+
+ Using this class directly you can reduce the apparent latency for each
+ download/parse cycle of documents over a slow connection. You can start
+ parsing *and return chunks of the parsed document* before the entire
+ document is downloaded.
+
+ Using this class is also useful to parse huge documents on disk
+ bit by bit so you don't have to keep them all in memory.
+
+ JSON is mapped to Objective-C types in the following way:
+
+ - null    -> NSNull
+ - string  -> NSString
+ - array   -> NSMutableArray
+ - object  -> NSMutableDictionary
+ - true    -> NSNumber's -numberWithBool:YES
+ - false   -> NSNumber's -numberWithBool:NO
+ - number -> NSNumber
+
+ Since Objective-C doesn't have a dedicated class for boolean values,
+ these turns into NSNumber instances. However, since these are
+ initialised with the -initWithBool: method they round-trip back to JSON
+ properly. In other words, they won't silently suddenly become 0 or 1;
+ they'll be represented as 'true' and 'false' again.
+
+ Integers are parsed into either a `long long` or `unsigned long long`
+ type if they fit, else a `double` is used. All real & exponential numbers
+ are represented using a `double`. Previous versions of this library used
+ an NSDecimalNumber in some cases, but this is no longer the case.
+
+ The default behaviour is that your passed-in block is only called once the
+ entire input is parsed. If you set supportManyDocuments to YES and your input
+ contains multiple (whitespace limited) JSON documents your block will be called
+ for each document:
+
+    SBJson4ValueBlock block = ^(id v, BOOL *stop) {
+        BOOL isArray = [v isKindOfClass:[NSArray class]];
+        NSLog(@"Found: %@", isArray ? @"Array" : @"Object");
+    };
+
+    SBJson4ErrorBlock eh = ^(NSError* err) {
+        NSLog(@"OOPS: %@", err);
+    };
+
+    id parser = [SBJson4Parser multiRootParserWithBlock:block
+                                           errorHandler:eh];
+
+    // Note that this input contains multiple top-level JSON documents
+    id data = [@"[]{}" dataWithEncoding:NSUTF8StringEncoding];
+    [parser parse:data];
+    [parser parse:data];
+
+ The above example will print:
+
+ - Found: Array
+ - Found: Object
+ - Found: Array
+ - Found: Object
+
+ Often you won't have control over the input you're parsing, so can't make use
+ of this feature. But, all is not lost: if you are parsing a long array you can
+ get the same effect by setting  rootArrayItems to YES:
+
+    id parser = [SBJson4Parser unwrapRootArrayParserWithBlock:block
+                                                 errorHandler:eh];
+
+    // Note that this input contains A SINGLE top-level document
+    id data = [@"[[],{},[],{}]" dataWithEncoding:NSUTF8StringEncoding];
+    [parser parse:data];
+
+ @note Stream based parsing does mean that you lose some of the correctness
+ verification you would have with a parser that considered the entire input
+ before returning an answer. It is technically possible to have some parts
+ of a document returned *as if they were correct* but then encounter an error
+ in a later part of the document. You should keep this in mind when
+ considering whether it would suit your application.
+
+
+*/
+@interface SBJson4Parser : NSObject
+
+/**
+ Create a JSON Parser.
+
+ This can be used to create a parser that accepts only one document, or one that parses
+ many documents any
+
+ @param block Called for each element. Set *stop to `YES` if you have seen
+ enough and would like to skip the rest of the elements.
+
+ @param allowMultiRoot Indicate that you are expecting multiple whitespace-separated
+ JSON documents, similar to what Twitter uses.
+
+ @param unwrapRootArray If set the parser will pretend an root array does not exist
+ and the enumerator block will be called once for each item in it. This option
+ does nothing if the the JSON has an object at its root.
+
+ @param eh Called if the parser encounters an error.
+
+ @see -unwrapRootArrayParserWithBlock:errorHandler:
+ @see -multiRootParserWithBlock:errorHandler:
+ @see -initWithBlock:processBlock:multiRoot:unwrapRootArray:maxDepth:errorHandler:
+
+ */
++ (id)parserWithBlock:(SBJson4ValueBlock)block
+       allowMultiRoot:(BOOL)allowMultiRoot
+      unwrapRootArray:(BOOL)unwrapRootArray
+         errorHandler:(SBJson4ErrorBlock)eh;
+
+
+/**
+ Create a JSON Parser that parses multiple whitespace separated documents.
+ This is useful for something like Twitter's feed, which gives you one JSON
+ document per line.
+
+ @param block Called for each element. Set *stop to `YES` if you have seen
+ enough and would like to skip the rest of the elements.
+
+ @param eh Called if the parser encounters an error.
+
+ @see +unwrapRootArrayParserWithBlock:errorHandler:
+ @see +parserWithBlock:allowMultiRoot:unwrapRootArray:errorHandler:
+ @see -initWithBlock:processBlock:multiRoot:unwrapRootArray:maxDepth:errorHandler:
+ */
++ (id)multiRootParserWithBlock:(SBJson4ValueBlock)block
+                  errorHandler:(SBJson4ErrorBlock)eh;
+
+/**
+ Create a JSON Parser that parses a huge array and calls for the value block for
+ each element in the outermost array.
+
+ @param block Called for each element. Set *stop to `YES` if you have seen
+ enough and would like to skip the rest of the elements.
+
+ @param eh Called if the parser encounters an error.
+
+ @see +multiRootParserWithBlock:errorHandler:
+ @see +parserWithBlock:allowMultiRoot:unwrapRootArray:errorHandler:
+ @see -initWithBlock:processBlock:multiRoot:unwrapRootArray:maxDepth:errorHandler:
+ */
++ (id)unwrapRootArrayParserWithBlock:(SBJson4ValueBlock)block
+                        errorHandler:(SBJson4ErrorBlock)eh;
+
+/**
+ Create a JSON Parser.
+
+ @param block Called for each element. Set *stop to `YES` if you have seen
+ enough and would like to skip the rest of the elements.
+
+ @param processBlock A block that allows you to process individual values before being
+ returned.
+
+ @param multiRoot Indicate that you are expecting multiple whitespace-separated
+ JSON documents, similar to what Twitter uses.
+
+ @param unwrapRootArray If set the parser will pretend an root array does not exist
+ and the enumerator block will be called once for each item in it. This option
+ does nothing if the the JSON has an object at its root.
+
+ @param maxDepth The max recursion depth of the parser. Defaults to 32.
+
+ @param eh Called if the parser encounters an error.
+
+ */
+- (id)initWithBlock:(SBJson4ValueBlock)block
+       processBlock:(SBJson4ProcessBlock)processBlock
+          multiRoot:(BOOL)multiRoot
+    unwrapRootArray:(BOOL)unwrapRootArray
+           maxDepth:(NSUInteger)maxDepth
+       errorHandler:(SBJson4ErrorBlock)eh;
+
+/**
+ Parse some JSON
+
+ The JSON is assumed to be UTF8 encoded. This can be a full JSON document, or a part of one.
+
+ @param data An NSData object containing the next chunk of JSON
+
+ @return
+ - SBJson4ParserComplete if a full document was found
+ - SBJson4ParserWaitingForData if a partial document was found and more data is required to complete it
+ - SBJson4ParserError if an error occurred.
+
+ */
+- (SBJson4ParserStatus)parse:(NSData*)data;
+
+@end

+ 287 - 0
MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/json-framework/SBJson4Parser.m

@@ -0,0 +1,287 @@
+/*
+ Copyright (c) 2010-2013, Stig Brautaset.
+ All rights reserved.
+ 
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+ 
+   Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+  
+   Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+ 
+   Neither the name of the the author nor the names of its contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+ 
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if !__has_feature(objc_arc)
+#error "This source file must be compiled with ARC enabled!"
+#endif
+
+#import "SBJson4Parser.h"
+
+@interface SBJson4Parser () <SBJson4StreamParserDelegate>
+
+- (void)pop;
+
+@end
+
+typedef enum {
+    SBJson4ChunkNone,
+    SBJson4ChunkArray,
+    SBJson4ChunkObject,
+} SBJson4ChunkType;
+
+@implementation SBJson4Parser {
+    SBJson4StreamParser *_parser;
+    NSUInteger depth;
+    NSMutableArray *array;
+    NSMutableDictionary *dict;
+    NSMutableArray *keyStack;
+    NSMutableArray *stack;
+    NSMutableArray *path;
+    SBJson4ProcessBlock processBlock;
+    SBJson4ErrorBlock errorHandler;
+    SBJson4ValueBlock valueBlock;
+    SBJson4ChunkType currentType;
+    BOOL supportManyDocuments;
+    BOOL supportPartialDocuments;
+    NSUInteger _maxDepth;
+}
+
+#pragma mark Housekeeping
+
+- (id)init {
+    @throw @"Not Implemented";
+}
+
++ (id)multiRootParserWithBlock:(SBJson4ValueBlock)block errorHandler:(SBJson4ErrorBlock)eh {
+    return [self parserWithBlock:block
+                  allowMultiRoot:YES
+                 unwrapRootArray:NO
+                    errorHandler:eh];
+}
+
++ (id)unwrapRootArrayParserWithBlock:(SBJson4ValueBlock)block errorHandler:(SBJson4ErrorBlock)eh {
+    return [self parserWithBlock:block
+                  allowMultiRoot:NO
+                 unwrapRootArray:YES
+                    errorHandler:eh];
+}
+
++ (id)parserWithBlock:(SBJson4ValueBlock)block
+       allowMultiRoot:(BOOL)allowMultiRoot
+      unwrapRootArray:(BOOL)unwrapRootArray
+         errorHandler:(SBJson4ErrorBlock)eh {
+
+    return [[self alloc] initWithBlock:block
+                          processBlock:nil
+                             multiRoot:allowMultiRoot
+                       unwrapRootArray:unwrapRootArray
+                              maxDepth:32
+                          errorHandler:eh];
+}
+
+- (id)initWithBlock:(SBJson4ValueBlock)block
+       processBlock:(SBJson4ProcessBlock)initialProcessBlock
+          multiRoot:(BOOL)multiRoot
+    unwrapRootArray:(BOOL)unwrapRootArray
+           maxDepth:(NSUInteger)maxDepth
+       errorHandler:(SBJson4ErrorBlock)eh {
+
+	self = [super init];
+	if (self) {
+        _parser = [[SBJson4StreamParser alloc] init];
+        _parser.delegate = self;
+
+        supportManyDocuments = multiRoot;
+        supportPartialDocuments = unwrapRootArray;
+
+        valueBlock = block;
+		keyStack = [[NSMutableArray alloc] initWithCapacity:32];
+		stack = [[NSMutableArray alloc] initWithCapacity:32];
+        if (initialProcessBlock)
+            path = [[NSMutableArray alloc] initWithCapacity:32];
+        processBlock = initialProcessBlock;
+        errorHandler = eh ? eh : ^(NSError*err) { NSLog(@"%@", err); };
+		currentType = SBJson4ChunkNone;
+        _maxDepth = maxDepth;
+	}
+	return self;
+}
+
+
+#pragma mark Private methods
+
+- (void)pop {
+	[stack removeLastObject];
+	array = nil;
+	dict = nil;
+	currentType = SBJson4ChunkNone;
+	
+	id value = [stack lastObject];
+	
+	if ([value isKindOfClass:[NSArray class]]) {
+		array = value;
+		currentType = SBJson4ChunkArray;
+	} else if ([value isKindOfClass:[NSDictionary class]]) {
+		dict = value;
+		currentType = SBJson4ChunkObject;
+	}
+}
+
+- (void)parserFound:(id)obj isValue:(BOOL)isValue {
+	NSParameterAssert(obj);
+	
+    if(processBlock&&path) {
+        if(isValue) {
+            obj = processBlock(obj,[NSString stringWithFormat:@"%@.%@",[self pathString],[keyStack lastObject]]);
+        }
+        else {
+            [path removeLastObject];
+        }
+    }
+
+	switch (currentType) {
+		case SBJson4ChunkArray:
+			[array addObject:obj];
+			break;
+
+		case SBJson4ChunkObject:
+			NSParameterAssert(keyStack.count);
+			[dict setObject:obj forKey:[keyStack lastObject]];
+			[keyStack removeLastObject];
+			break;
+
+		case SBJson4ChunkNone: {
+            __block BOOL stop = NO;
+            valueBlock(obj, &stop);
+            if (stop) [_parser stop];
+        }
+			break;
+
+		default:
+			break;
+	}
+}
+
+
+#pragma mark Delegate methods
+
+- (void)parserFoundObjectStart {
+    ++depth;
+    if (depth > _maxDepth)
+        [self maxDepthError];
+
+    if (path)
+        [self addToPath];
+    dict = [NSMutableDictionary new];
+	[stack addObject:dict];
+    currentType = SBJson4ChunkObject;
+}
+
+- (void)parserFoundObjectKey:(NSString *)key_ {
+    [keyStack addObject:key_];
+}
+
+- (void)parserFoundObjectEnd {
+    depth--;
+	id value = dict;
+	[self pop];
+    [self parserFound:value isValue:NO ];
+}
+
+- (void)parserFoundArrayStart {
+    depth++;
+    if (depth > _maxDepth)
+        [self maxDepthError];
+
+    if (depth > 1 || !supportPartialDocuments) {
+        if(path)
+            [self addToPath];
+		array = [NSMutableArray new];
+		[stack addObject:array];
+		currentType = SBJson4ChunkArray;
+    }
+}
+
+- (void)parserFoundArrayEnd {
+    depth--;
+    if (depth > 1 || !supportPartialDocuments) {
+		id value = array;
+		[self pop];
+        [self parserFound:value isValue:NO ];
+    }
+}
+
+- (void)maxDepthError {
+    id ui = @{ NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Input depth exceeds max depth of %lu", (unsigned long)_maxDepth]};
+    errorHandler([NSError errorWithDomain:@"org.sbjson.parser" code:3 userInfo:ui]);
+    [_parser stop];
+}
+
+- (void)parserFoundBoolean:(BOOL)x {
+	[self parserFound:[NSNumber numberWithBool:x] isValue:YES ];
+}
+
+- (void)parserFoundNull {
+    [self parserFound:[NSNull null] isValue:YES ];
+}
+
+- (void)parserFoundNumber:(NSNumber *)num {
+    [self parserFound:num isValue:YES ];
+}
+
+- (void)parserFoundString:(NSString *)string {
+    [self parserFound:string isValue:YES ];
+}
+
+- (void)parserFoundError:(NSError *)err {
+    errorHandler(err);
+}
+
+- (void)addToPath {
+    if([path count]==0)
+        [path addObject:@"$"];
+    else if([[stack lastObject] isKindOfClass:[NSArray class]])
+        [path addObject:@([[stack lastObject] count])];
+    else
+        [path addObject:[keyStack lastObject]];
+}
+
+- (NSString *)pathString {
+    NSMutableString *pathString = [NSMutableString stringWithString:@"$"];
+    for(NSUInteger i=1;i<[path count];i++) {
+        if([[path objectAtIndex:i] isKindOfClass:[NSNumber class]])
+            [pathString appendString:[NSString stringWithFormat:@"[%@]",[path objectAtIndex:i]]];
+        else
+            [pathString appendString:[NSString stringWithFormat:@".%@",[path objectAtIndex:i]]];
+    }
+    return pathString;
+}
+
+- (BOOL)parserShouldSupportManyDocuments {
+    return supportManyDocuments;
+}
+
+- (SBJson4ParserStatus)parse:(NSData *)data {
+    return [_parser parse:data];
+}
+
+@end

+ 131 - 0
MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/json-framework/SBJson4StreamParser.h

@@ -0,0 +1,131 @@
+/*
+ Copyright (c) 2010-2013, Stig Brautaset.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+   Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+
+   Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+
+   Neither the name of the the author nor the names of its contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <Foundation/Foundation.h>
+
+@class SBJson4StreamParser;
+@class SBJson4StreamParserState;
+
+typedef enum {
+    SBJson4ParserComplete,
+    SBJson4ParserStopped,
+    SBJson4ParserWaitingForData,
+    SBJson4ParserError,
+} SBJson4ParserStatus;
+
+
+/**
+ Delegate for interacting directly with the low-level parser
+
+ You will most likely find it much more convenient to use the SBJson4Parser instead.
+ */
+@protocol SBJson4StreamParserDelegate < NSObject >
+
+/// Called when object start is found
+- (void)parserFoundObjectStart;
+
+/// Called when object key is found
+- (void)parserFoundObjectKey:(NSString *)key;
+
+/// Called when object end is found
+- (void)parserFoundObjectEnd;
+
+/// Called when array start is found
+- (void)parserFoundArrayStart;
+
+/// Called when array end is found
+- (void)parserFoundArrayEnd;
+
+/// Called when a boolean value is found
+- (void)parserFoundBoolean:(BOOL)x;
+
+/// Called when a null value is found
+- (void)parserFoundNull;
+
+/// Called when a number is found
+- (void)parserFoundNumber:(NSNumber *)num;
+
+/// Called when a string is found
+- (void)parserFoundString:(NSString *)string;
+
+/// Called when an error occurs
+- (void)parserFoundError:(NSError *)err;
+
+@optional
+
+/// Called to determine whether to allow multiple whitespace-separated documents
+- (BOOL)parserShouldSupportManyDocuments;
+
+@end
+
+/**
+ Low-level Stream parser
+
+ You most likely want to use the SBJson4Parser instead, but if you
+ really need low-level access to tokens one-by-one you can use this class.
+ */
+@interface SBJson4StreamParser : NSObject
+
+@property (nonatomic, weak) SBJson4StreamParserState *state; // Private
+
+/**
+ Delegate to receive messages
+
+ The object set here receives a series of messages as the parser breaks down the JSON stream
+ into valid tokens.
+
+ Usually this should be an instance of SBJson4Parser, but you can
+ substitute your own implementation of the SBJson4StreamParserDelegate protocol if you need to.
+ */
+@property (nonatomic, weak) id<SBJson4StreamParserDelegate> delegate;
+
+/**
+ Parse some JSON
+
+ The JSON is assumed to be UTF8 encoded. This can be a full JSON document, or a part of one.
+
+ @param data An NSData object containing the next chunk of JSON
+
+ @return
+ - SBJson4ParserComplete if a full document was found
+ - SBJson4ParserWaitingForData if a partial document was found and more data is required to complete it
+ - SBJson4ParserError if an error occurred.
+
+ */
+- (SBJson4ParserStatus)parse:(NSData*)data;
+
+/**
+ Call this to cause parsing to stop.
+ */
+- (void)stop;
+
+@end

+ 317 - 0
MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/json-framework/SBJson4StreamParser.m

@@ -0,0 +1,317 @@
+/*
+ Copyright (c) 2010, Stig Brautaset.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+ Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ Neither the name of the the author nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if !__has_feature(objc_arc)
+#error "This source file must be compiled with ARC enabled!"
+#endif
+
+#import "SBJson4StreamParser.h"
+#import "SBJson4StreamTokeniser.h"
+#import "SBJson4StreamParserState.h"
+
+#define SBStringIsSurrogateHighCharacter(character) ((character >= 0xD800UL) && (character <= 0xDBFFUL))
+
+@implementation SBJson4StreamParser {
+@private
+  SBJson4StreamTokeniser *tokeniser;
+  BOOL stopped;
+  NSMutableArray *_stateStack;
+}
+
+#pragma mark Housekeeping
+
+- (id)init {
+	self = [super init];
+	if (self) {
+        _stateStack = [[NSMutableArray alloc] initWithCapacity:32];
+        _state = [SBJson4StreamParserStateStart sharedInstance];
+		tokeniser = [[SBJson4StreamTokeniser alloc] init];
+	}
+	return self;
+}
+
+
+#pragma mark Methods
+
+- (NSString*)tokenName:(sbjson4_token_t)token {
+	switch (token) {
+		case sbjson4_token_array_open:
+			return @"start of array";
+
+		case sbjson4_token_array_close:
+			return @"end of array";
+
+        case sbjson4_token_integer:
+        case sbjson4_token_real:
+			return @"number";
+
+        case sbjson4_token_string:
+        case sbjson4_token_encoded:
+			return @"string";
+
+        case sbjson4_token_bool:
+			return @"boolean";
+
+		case sbjson4_token_null:
+			return @"null";
+
+        case sbjson4_token_entry_sep:
+			return @"key-value separator";
+
+        case sbjson4_token_value_sep:
+			return @"value separator";
+
+		case sbjson4_token_object_open:
+			return @"start of object";
+
+		case sbjson4_token_object_close:
+			return @"end of object";
+
+		case sbjson4_token_eof:
+		case sbjson4_token_error:
+			break;
+	}
+	NSAssert(NO, @"Should not get here");
+	return @"<aaiiie!>";
+}
+
+- (void)handleObjectStart {
+    [_delegate parserFoundObjectStart];
+    [_stateStack addObject:_state];
+    _state = [SBJson4StreamParserStateObjectStart sharedInstance];
+}
+
+- (void)handleObjectEnd: (sbjson4_token_t) tok  {
+    _state = [_stateStack lastObject];
+    [_stateStack removeLastObject];
+    [_state parser:self shouldTransitionTo:tok];
+    [_delegate parserFoundObjectEnd];
+}
+
+- (void)handleArrayStart {
+	[_delegate parserFoundArrayStart];
+    [_stateStack addObject:_state];
+    _state = [SBJson4StreamParserStateArrayStart sharedInstance];
+}
+
+- (void)handleArrayEnd: (sbjson4_token_t) tok  {
+    _state = [_stateStack lastObject];
+    [_stateStack removeLastObject];
+    [_state parser:self shouldTransitionTo:tok];
+    [_delegate parserFoundArrayEnd];
+}
+
+- (void) handleTokenNotExpectedHere: (sbjson4_token_t) tok  {
+    NSString *tokenName = [self tokenName:tok];
+    NSString *stateName = [_state name];
+
+    _state = [SBJson4StreamParserStateError sharedInstance];
+    id ui = @{ NSLocalizedDescriptionKey : [NSString stringWithFormat:@"Token '%@' not expected %@", tokenName, stateName]};
+    [_delegate parserFoundError:[NSError errorWithDomain:@"org.sbjson.parser" code:2 userInfo:ui]];
+}
+
+- (SBJson4ParserStatus)parse:(NSData *)data_ {
+    @autoreleasepool {
+        [tokeniser appendData:data_];
+        
+        for (;;) {
+
+            if (stopped)
+                return SBJson4ParserStopped;
+            
+            if ([_state isError])
+                return SBJson4ParserError;
+
+            char *token;
+            NSUInteger token_len;
+            sbjson4_token_t tok = [tokeniser getToken:&token length:&token_len];
+            
+            switch (tok) {
+                case sbjson4_token_eof:
+                    return [_state parserShouldReturn:self];
+
+                case sbjson4_token_error:
+                    _state = [SBJson4StreamParserStateError sharedInstance];
+                    [_delegate parserFoundError:[NSError errorWithDomain:@"org.sbjson.parser" code:3
+                                                                userInfo:@{NSLocalizedDescriptionKey : tokeniser.error}]];
+                    return SBJson4ParserError;
+
+                default:
+                    
+                    if (![_state parser:self shouldAcceptToken:tok]) {
+                        [self handleTokenNotExpectedHere: tok];
+                        return SBJson4ParserError;
+                    }
+                    
+                    switch (tok) {
+                        case sbjson4_token_object_open:
+                            [self handleObjectStart];
+                            break;
+                            
+                        case sbjson4_token_object_close:
+                            [self handleObjectEnd: tok];
+                            break;
+                            
+                        case sbjson4_token_array_open:
+                            [self handleArrayStart];
+                            break;
+                            
+                        case sbjson4_token_array_close:
+                            [self handleArrayEnd: tok];
+                            break;
+                            
+                        case sbjson4_token_value_sep:
+                        case sbjson4_token_entry_sep:
+                            [_state parser:self shouldTransitionTo:tok];
+                            break;
+                            
+                        case sbjson4_token_bool:
+                            [_delegate parserFoundBoolean:token[0] == 't'];
+                            [_state parser:self shouldTransitionTo:tok];
+                            break;
+                            
+
+                        case sbjson4_token_null:
+                            [_delegate parserFoundNull];
+                            [_state parser:self shouldTransitionTo:tok];
+                            break;
+
+                        case sbjson4_token_integer: {
+                            const int UNSIGNED_LONG_LONG_MAX_DIGITS = 20;
+                            if (token_len <= UNSIGNED_LONG_LONG_MAX_DIGITS) {
+                                if (*token == '-')
+                                    [_delegate parserFoundNumber:@(strtoll(token, NULL, 10))];
+                                else
+                                    [_delegate parserFoundNumber:@(strtoull(token, NULL, 10))];
+                                
+                                [_state parser:self shouldTransitionTo:tok];
+                                break;
+                            }
+                        }
+                            // FALL THROUGH
+
+                        case sbjson4_token_real: {
+                            [_delegate parserFoundNumber:@(strtod(token, NULL))];
+                            [_state parser:self shouldTransitionTo:tok];
+                            break;
+                        }
+
+                        case sbjson4_token_string: {
+                            NSString *string = [[NSString alloc] initWithBytes:token length:token_len encoding:NSUTF8StringEncoding];
+                            if ([_state needKey])
+                                [_delegate parserFoundObjectKey:string];
+                            else
+                                [_delegate parserFoundString:string];
+                            [_state parser:self shouldTransitionTo:tok];
+                            break;
+                        }
+
+                        case sbjson4_token_encoded: {
+                            NSString *string = [self decodeStringToken:token length:token_len];
+                            if ([_state needKey])
+                                [_delegate parserFoundObjectKey:string];
+                            else
+                                [_delegate parserFoundString:string];
+                            [_state parser:self shouldTransitionTo:tok];
+                            break;
+                        }
+
+                        default:
+                            break;
+                    }
+                    break;
+            }
+        }
+        return SBJson4ParserComplete;
+    }
+}
+
+- (unichar)decodeHexQuad:(char *)quad {
+    unichar ch = 0;
+    for (NSUInteger i = 0; i < 4; i++) {
+        int c = quad[i];
+        ch *= 16;
+        switch (c) {
+            case '0' ... '9': ch += c - '0'; break;
+            case 'a' ... 'f': ch += 10 + c - 'a'; break;
+            case 'A' ... 'F': ch += 10 + c - 'A'; break;
+            default: @throw @"FUT FUT FUT";
+        }
+    }
+    return ch;
+}
+
+- (NSString*)decodeStringToken:(char*)bytes length:(NSUInteger)len {
+    NSMutableData *buf = [NSMutableData dataWithCapacity:len];
+    for (NSUInteger i = 0; i < len;) {
+        switch ((unsigned char)bytes[i]) {
+            case '\\': {
+                switch ((unsigned char)bytes[++i]) {
+                    case '"': [buf appendBytes:"\"" length:1]; i++; break;
+                    case '/': [buf appendBytes:"/" length:1]; i++; break;
+                    case '\\': [buf appendBytes:"\\" length:1]; i++; break;
+                    case 'b': [buf appendBytes:"\b" length:1]; i++; break;
+                    case 'f': [buf appendBytes:"\f" length:1]; i++; break;
+                    case 'n': [buf appendBytes:"\n" length:1]; i++; break;
+                    case 'r': [buf appendBytes:"\r" length:1]; i++; break;
+                    case 't': [buf appendBytes:"\t" length:1]; i++; break;
+                    case 'u': {
+                        unichar hi = [self decodeHexQuad:bytes + i + 1];
+                        i += 5;
+                        if (SBStringIsSurrogateHighCharacter(hi)) {
+                            // Skip past \u that we know is there..
+                            unichar lo = [self decodeHexQuad:bytes + i + 2];
+                            i += 6;
+                            [buf appendData:[[NSString stringWithFormat:@"%C%C", hi, lo] dataUsingEncoding:NSUTF8StringEncoding]];
+                        } else {
+                            [buf appendData:[[NSString stringWithFormat:@"%C", hi] dataUsingEncoding:NSUTF8StringEncoding]];
+                        }
+                        break;
+                    }
+                    default: @throw @"FUT FUT FUT";
+                }
+                break;
+            }
+            default:
+                [buf appendBytes:bytes + i length:1];
+                i++;
+                break;
+        }
+    }
+    return [[NSString alloc] initWithData:buf encoding:NSUTF8StringEncoding];
+}
+
+- (void)stop {
+    stopped = YES;
+}
+
+@end

+ 82 - 0
MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/json-framework/SBJson4StreamParserState.h

@@ -0,0 +1,82 @@
+/*
+ Copyright (c) 2010, Stig Brautaset.
+ All rights reserved.
+ 
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+ 
+   Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+  
+   Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+ 
+   Neither the name of the the author nor the names of its contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+ 
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <Foundation/Foundation.h>
+
+#import "SBJson4StreamTokeniser.h"
+#import "SBJson4StreamParser.h"
+
+@interface SBJson4StreamParserState : NSObject
++ (id)sharedInstance;
+
+- (BOOL)parser:(SBJson4StreamParser *)parser shouldAcceptToken:(sbjson4_token_t)token;
+- (SBJson4ParserStatus)parserShouldReturn:(SBJson4StreamParser *)parser;
+- (void)parser:(SBJson4StreamParser *)parser shouldTransitionTo:(sbjson4_token_t)tok;
+- (BOOL)needKey;
+- (BOOL)isError;
+
+- (NSString*)name;
+
+@end
+
+@interface SBJson4StreamParserStateStart : SBJson4StreamParserState
+@end
+
+@interface SBJson4StreamParserStateComplete : SBJson4StreamParserState
+@end
+
+@interface SBJson4StreamParserStateError : SBJson4StreamParserState
+@end
+
+@interface SBJson4StreamParserStateObjectStart : SBJson4StreamParserState
+@end
+
+@interface SBJson4StreamParserStateObjectGotKey : SBJson4StreamParserState
+@end
+
+@interface SBJson4StreamParserStateObjectSeparator : SBJson4StreamParserState
+@end
+
+@interface SBJson4StreamParserStateObjectGotValue : SBJson4StreamParserState
+@end
+
+@interface SBJson4StreamParserStateObjectNeedKey : SBJson4StreamParserState
+@end
+
+@interface SBJson4StreamParserStateArrayStart : SBJson4StreamParserState
+@end
+
+@interface SBJson4StreamParserStateArrayGotValue : SBJson4StreamParserState
+@end
+
+@interface SBJson4StreamParserStateArrayNeedValue : SBJson4StreamParserState
+@end

+ 355 - 0
MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/json-framework/SBJson4StreamParserState.m

@@ -0,0 +1,355 @@
+/*
+ Copyright (c) 2010-2013, Stig Brautaset.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+   Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+
+   Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+
+   Neither the name of the the author nor the names of its contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if !__has_feature(objc_arc)
+#error "This source file must be compiled with ARC enabled!"
+#endif
+
+#import "SBJson4StreamParserState.h"
+
+#define SINGLETON \
++ (id)sharedInstance { \
+    static id state = nil; \
+    if (!state) { \
+        @synchronized(self) { \
+            if (!state) state = [[self alloc] init]; \
+        } \
+    } \
+    return state; \
+}
+
+@implementation SBJson4StreamParserState
+
++ (id)sharedInstance { return nil; }
+
+- (BOOL)parser:(SBJson4StreamParser *)parser shouldAcceptToken:(sbjson4_token_t)token {
+	return NO;
+}
+
+- (SBJson4ParserStatus)parserShouldReturn:(SBJson4StreamParser *)parser {
+	return SBJson4ParserWaitingForData;
+}
+
+- (void)parser:(SBJson4StreamParser *)parser shouldTransitionTo:(sbjson4_token_t)tok {}
+
+- (BOOL)needKey {
+	return NO;
+}
+
+- (NSString*)name {
+	return @"<aaiie!>";
+}
+
+- (BOOL)isError {
+    return NO;
+}
+
+@end
+
+#pragma mark -
+
+@implementation SBJson4StreamParserStateStart
+
+SINGLETON
+
+- (BOOL)parser:(SBJson4StreamParser *)parser shouldAcceptToken:(sbjson4_token_t)token {
+	return token == sbjson4_token_array_open || token == sbjson4_token_object_open;
+}
+
+- (void)parser:(SBJson4StreamParser *)parser shouldTransitionTo:(sbjson4_token_t)tok {
+
+	SBJson4StreamParserState *state = nil;
+	switch (tok) {
+		case sbjson4_token_array_open:
+			state = [SBJson4StreamParserStateArrayStart sharedInstance];
+			break;
+
+		case sbjson4_token_object_open:
+			state = [SBJson4StreamParserStateObjectStart sharedInstance];
+			break;
+
+		case sbjson4_token_array_close:
+		case sbjson4_token_object_close:
+			if ([parser.delegate respondsToSelector:@selector(parserShouldSupportManyDocuments)] && [parser.delegate parserShouldSupportManyDocuments])
+				state = parser.state;
+			else
+				state = [SBJson4StreamParserStateComplete sharedInstance];
+			break;
+
+		case sbjson4_token_eof:
+			return;
+
+		default:
+			state = [SBJson4StreamParserStateError sharedInstance];
+			break;
+	}
+
+
+	parser.state = state;
+}
+
+- (NSString*)name { return @"before outer-most array or object"; }
+
+@end
+
+#pragma mark -
+
+@implementation SBJson4StreamParserStateComplete
+
+SINGLETON
+
+- (NSString*)name { return @"after outer-most array or object"; }
+
+- (SBJson4ParserStatus)parserShouldReturn:(SBJson4StreamParser *)parser {
+	return SBJson4ParserComplete;
+}
+
+@end
+
+#pragma mark -
+
+@implementation SBJson4StreamParserStateError
+
+SINGLETON
+
+- (NSString*)name { return @"in error"; }
+
+- (SBJson4ParserStatus)parserShouldReturn:(SBJson4StreamParser *)parser {
+	return SBJson4ParserError;
+}
+
+- (BOOL)isError {
+    return YES;
+}
+
+@end
+
+#pragma mark -
+
+@implementation SBJson4StreamParserStateObjectStart
+
+SINGLETON
+
+- (NSString*)name { return @"at beginning of object"; }
+
+- (BOOL)parser:(SBJson4StreamParser *)parser shouldAcceptToken:(sbjson4_token_t)token {
+	switch (token) {
+		case sbjson4_token_object_close:
+		case sbjson4_token_string:
+        case sbjson4_token_encoded:
+			return YES;
+		default:
+			return NO;
+	}
+}
+
+- (void)parser:(SBJson4StreamParser *)parser shouldTransitionTo:(sbjson4_token_t)tok {
+	parser.state = [SBJson4StreamParserStateObjectGotKey sharedInstance];
+}
+
+- (BOOL)needKey {
+	return YES;
+}
+
+@end
+
+#pragma mark -
+
+@implementation SBJson4StreamParserStateObjectGotKey
+
+SINGLETON
+
+- (NSString*)name { return @"after object key"; }
+
+- (BOOL)parser:(SBJson4StreamParser *)parser shouldAcceptToken:(sbjson4_token_t)token {
+	return token == sbjson4_token_entry_sep;
+}
+
+- (void)parser:(SBJson4StreamParser *)parser shouldTransitionTo:(sbjson4_token_t)tok {
+	parser.state = [SBJson4StreamParserStateObjectSeparator sharedInstance];
+}
+
+@end
+
+#pragma mark -
+
+@implementation SBJson4StreamParserStateObjectSeparator
+
+SINGLETON
+
+- (NSString*)name { return @"as object value"; }
+
+- (BOOL)parser:(SBJson4StreamParser *)parser shouldAcceptToken:(sbjson4_token_t)token {
+	switch (token) {
+		case sbjson4_token_object_open:
+		case sbjson4_token_array_open:
+		case sbjson4_token_bool:
+		case sbjson4_token_null:
+        case sbjson4_token_integer:
+        case sbjson4_token_real:
+        case sbjson4_token_string:
+        case sbjson4_token_encoded:
+			return YES;
+
+		default:
+			return NO;
+	}
+}
+
+- (void)parser:(SBJson4StreamParser *)parser shouldTransitionTo:(sbjson4_token_t)tok {
+	parser.state = [SBJson4StreamParserStateObjectGotValue sharedInstance];
+}
+
+@end
+
+#pragma mark -
+
+@implementation SBJson4StreamParserStateObjectGotValue
+
+SINGLETON
+
+- (NSString*)name { return @"after object value"; }
+
+- (BOOL)parser:(SBJson4StreamParser *)parser shouldAcceptToken:(sbjson4_token_t)token {
+	switch (token) {
+		case sbjson4_token_object_close:
+        case sbjson4_token_value_sep:
+			return YES;
+
+		default:
+			return NO;
+	}
+}
+
+- (void)parser:(SBJson4StreamParser *)parser shouldTransitionTo:(sbjson4_token_t)tok {
+	parser.state = [SBJson4StreamParserStateObjectNeedKey sharedInstance];
+}
+
+
+@end
+
+#pragma mark -
+
+@implementation SBJson4StreamParserStateObjectNeedKey
+
+SINGLETON
+
+- (NSString*)name { return @"in place of object key"; }
+
+- (BOOL)parser:(SBJson4StreamParser *)parser shouldAcceptToken:(sbjson4_token_t)token {
+    return sbjson4_token_string == token || sbjson4_token_encoded == token;
+}
+
+- (void)parser:(SBJson4StreamParser *)parser shouldTransitionTo:(sbjson4_token_t)tok {
+	parser.state = [SBJson4StreamParserStateObjectGotKey sharedInstance];
+}
+
+- (BOOL)needKey {
+	return YES;
+}
+
+@end
+
+#pragma mark -
+
+@implementation SBJson4StreamParserStateArrayStart
+
+SINGLETON
+
+- (NSString*)name { return @"at array start"; }
+
+- (BOOL)parser:(SBJson4StreamParser *)parser shouldAcceptToken:(sbjson4_token_t)token {
+	switch (token) {
+		case sbjson4_token_object_close:
+        case sbjson4_token_entry_sep:
+        case sbjson4_token_value_sep:
+			return NO;
+
+		default:
+			return YES;
+	}
+}
+
+- (void)parser:(SBJson4StreamParser *)parser shouldTransitionTo:(sbjson4_token_t)tok {
+	parser.state = [SBJson4StreamParserStateArrayGotValue sharedInstance];
+}
+
+@end
+
+#pragma mark -
+
+@implementation SBJson4StreamParserStateArrayGotValue
+
+SINGLETON
+
+- (NSString*)name { return @"after array value"; }
+
+
+- (BOOL)parser:(SBJson4StreamParser *)parser shouldAcceptToken:(sbjson4_token_t)token {
+	return token == sbjson4_token_array_close || token == sbjson4_token_value_sep;
+}
+
+- (void)parser:(SBJson4StreamParser *)parser shouldTransitionTo:(sbjson4_token_t)tok {
+	if (tok == sbjson4_token_value_sep)
+		parser.state = [SBJson4StreamParserStateArrayNeedValue sharedInstance];
+}
+
+@end
+
+#pragma mark -
+
+@implementation SBJson4StreamParserStateArrayNeedValue
+
+SINGLETON
+
+- (NSString*)name { return @"as array value"; }
+
+
+- (BOOL)parser:(SBJson4StreamParser *)parser shouldAcceptToken:(sbjson4_token_t)token {
+	switch (token) {
+		case sbjson4_token_array_close:
+        case sbjson4_token_entry_sep:
+		case sbjson4_token_object_close:
+		case sbjson4_token_value_sep:
+			return NO;
+
+		default:
+			return YES;
+	}
+}
+
+- (void)parser:(SBJson4StreamParser *)parser shouldTransitionTo:(sbjson4_token_t)tok {
+	parser.state = [SBJson4StreamParserStateArrayGotValue sharedInstance];
+}
+
+@end
+

+ 40 - 0
MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/json-framework/SBJson4StreamTokeniser.h

@@ -0,0 +1,40 @@
+//
+// Created by SuperPappi on 09/01/2013.
+//
+// To change the template use AppCode | Preferences | File Templates.
+//
+
+#import <Foundation/Foundation.h>
+
+typedef enum {
+    sbjson4_token_error = -1,
+    sbjson4_token_eof,
+
+    sbjson4_token_array_open,
+    sbjson4_token_array_close,
+    sbjson4_token_value_sep,
+
+    sbjson4_token_object_open,
+    sbjson4_token_object_close,
+    sbjson4_token_entry_sep,
+
+    sbjson4_token_bool,
+    sbjson4_token_null,
+
+    sbjson4_token_integer,
+    sbjson4_token_real,
+
+    sbjson4_token_string,
+    sbjson4_token_encoded,
+} sbjson4_token_t;
+
+
+@interface SBJson4StreamTokeniser : NSObject
+
+@property (nonatomic, readonly, copy) NSString *error;
+
+- (void)appendData:(NSData*)data_;
+- (sbjson4_token_t)getToken:(char**)tok length:(NSUInteger*)len;
+
+@end
+

+ 395 - 0
MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/json-framework/SBJson4StreamTokeniser.m

@@ -0,0 +1,395 @@
+//
+// Created by SuperPappi on 09/01/2013.
+//
+// To change the template use AppCode | Preferences | File Templates.
+//
+
+
+#import "SBJson4StreamTokeniser.h"
+
+#define SBStringIsIllegalSurrogateHighCharacter(character) (((character) >= 0xD800UL) && ((character) <= 0xDFFFUL))
+#define SBStringIsSurrogateLowCharacter(character) ((character >= 0xDC00UL) && (character <= 0xDFFFUL))
+#define SBStringIsSurrogateHighCharacter(character) ((character >= 0xD800UL) && (character <= 0xDBFFUL))
+
+@implementation SBJson4StreamTokeniser {
+    NSMutableData *data;
+    const char *bytes;
+    NSUInteger index;
+    NSUInteger offset;
+}
+
+- (void)setError:(NSString *)error {
+    _error = [NSString stringWithFormat:@"%@ at index %lu", error, (unsigned long)(offset + index)];
+}
+
+- (void)appendData:(NSData *)data_ {
+    if (!data) {
+        data = [data_ mutableCopy];
+
+    } else if (index) {
+        // Discard data we've already parsed
+        [data replaceBytesInRange:NSMakeRange(0, index) withBytes:"" length:0];
+        [data appendData:data_];
+
+        // Add to the offset for reporting
+        offset += index;
+
+        // Reset index to point to current position
+        index = 0u;
+
+    }
+    else {
+       [data appendData:data_];
+    }
+
+    bytes = [data bytes];
+}
+
+- (void)skipWhitespace {
+    while (index < data.length) {
+        switch (bytes[index]) {
+            case ' ':
+            case '\t':
+            case '\r':
+            case '\n':
+                index++;
+            break;
+            default:
+                return;
+        }
+    }
+}
+
+- (BOOL)getUnichar:(unichar *)ch {
+    if ([self haveRemainingCharacters:1]) {
+        *ch = (unichar) bytes[index];
+        return YES;
+    }
+    return NO;
+}
+
+- (BOOL)haveOneMoreCharacter {
+    return [self haveRemainingCharacters:1];
+}
+
+- (BOOL)haveRemainingCharacters:(NSUInteger)length {
+    return data.length - index >= length;
+}
+
+- (sbjson4_token_t)match:(char *)str retval:(sbjson4_token_t)tok token:(char **)token length:(NSUInteger *)length {
+    NSUInteger len = strlen(str);
+    if ([self haveRemainingCharacters:len]) {
+        if (!memcmp(bytes + index, str, len)) {
+            *token = str;
+            *length = len;
+            index += len;
+            return tok;
+        }
+        [self setError: [NSString stringWithFormat:@"Expected '%s' after initial '%.1s'", str, str]];
+        return sbjson4_token_error;
+    }
+
+    return sbjson4_token_eof;
+}
+
+- (BOOL)decodeHexQuad:(unichar*)quad {
+    unichar tmp = 0;
+
+    for (int i = 0; i < 4; i++, index++) {
+        unichar c = (unichar)bytes[index];
+        tmp *= 16;
+        switch (c) {
+            case '0' ... '9':
+                tmp += c - '0';
+                break;
+
+            case 'a' ... 'f':
+                tmp += 10 + c - 'a';
+                break;
+
+            case 'A' ... 'F':
+                tmp += 10 + c - 'A';
+                break;
+
+            default:
+                return NO;
+        }
+    }
+    *quad = tmp;
+    return YES;
+}
+
+- (sbjson4_token_t)getStringToken:(char **)token length:(NSUInteger *)length {
+
+    // Skip initial "
+    index++;
+
+    NSUInteger string_start = index;
+    sbjson4_token_t tok = sbjson4_token_string;
+
+    for (;;) {
+        if (![self haveOneMoreCharacter])
+            return sbjson4_token_eof;
+
+        switch (bytes[index]) {
+            case 0 ... 0x1F:
+                [self setError:[NSString stringWithFormat:@"Unescaped control character [0x%0.2X] in string", bytes[index]]];
+                return sbjson4_token_error;
+
+            case '"':
+                *token = (char *)(bytes + string_start);
+                *length = index - string_start;
+                index++;
+                return tok;
+
+            case '\\':
+                tok = sbjson4_token_encoded;
+                index++;
+                if (![self haveOneMoreCharacter])
+                    return sbjson4_token_eof;
+
+                if (bytes[index] == 'u') {
+                    index++;
+                    if (![self haveRemainingCharacters:4])
+                        return sbjson4_token_eof;
+
+                    unichar hi;
+                    if (![self decodeHexQuad:&hi]) {
+                        [self setError:@"Invalid hex quad"];
+                        return sbjson4_token_error;
+                    }
+
+                    if (SBStringIsSurrogateHighCharacter(hi)) {
+                        if (![self haveRemainingCharacters:6])
+                            return sbjson4_token_eof;
+
+                        unichar lo;
+                        if (bytes[index++] != '\\' || bytes[index++] != 'u' || ![self decodeHexQuad:&lo]) {
+                            [self setError:@"Missing low character in surrogate pair"];
+                            return sbjson4_token_error;
+                        }
+
+                        if (!SBStringIsSurrogateLowCharacter(lo)) {
+                            [self setError:@"Invalid low character in surrogate pair"];
+                            return sbjson4_token_error;
+                        }
+
+                    } else if (SBStringIsIllegalSurrogateHighCharacter(hi)) {
+                        [self setError:@"Invalid high character in surrogate pair"];
+                        return sbjson4_token_error;
+
+                    }
+
+
+                } else {
+                    switch (bytes[index]) {
+                        case '\\':
+                        case '/':
+                        case '"':
+                        case 'b':
+                        case 'n':
+                        case 'r':
+                        case 't':
+                        case 'f':
+                            index++;
+                            break;
+
+                        default:
+                            [self setError:[NSString stringWithFormat:@"Illegal escape character [%x]", bytes[index]]];
+                            return sbjson4_token_error;
+                    }
+                }
+
+                break;
+
+            default:
+                index++;
+                break;
+        }
+    }
+}
+
+- (sbjson4_token_t)getNumberToken:(char **)token length:(NSUInteger *)length {
+    NSUInteger num_start = index;
+    if (bytes[index] == '-') {
+        index++;
+
+        if (![self haveOneMoreCharacter])
+            return sbjson4_token_eof;
+    }
+
+    sbjson4_token_t tok = sbjson4_token_integer;
+    if (bytes[index] == '0') {
+        index++;
+
+        if (![self haveOneMoreCharacter])
+            return sbjson4_token_eof;
+
+        if (isdigit(bytes[index])) {
+            [self setError:@"Leading zero is illegal in number"];
+            return sbjson4_token_error;
+        }
+    }
+
+    while (isdigit(bytes[index])) {
+        index++;
+        if (![self haveOneMoreCharacter])
+            return sbjson4_token_eof;
+    }
+
+    if (![self haveOneMoreCharacter])
+        return sbjson4_token_eof;
+
+
+    if (bytes[index] == '.') {
+        index++;
+        tok = sbjson4_token_real;
+
+        if (![self haveOneMoreCharacter])
+            return sbjson4_token_eof;
+
+        NSUInteger fraction_start = index;
+        while (isdigit(bytes[index])) {
+            index++;
+            if (![self haveOneMoreCharacter])
+                return sbjson4_token_eof;
+        }
+
+        if (fraction_start == index) {
+            [self setError:@"No digits after decimal point"];
+            return sbjson4_token_error;
+        }
+    }
+
+    if (bytes[index] == 'e' || bytes[index] == 'E') {
+        index++;
+        tok = sbjson4_token_real;
+
+        if (![self haveOneMoreCharacter])
+            return sbjson4_token_eof;
+
+        if (bytes[index] == '-' || bytes[index] == '+') {
+            index++;
+            if (![self haveOneMoreCharacter])
+                return sbjson4_token_eof;
+        }
+
+        NSUInteger exp_start = index;
+        while (isdigit(bytes[index])) {
+            index++;
+            if (![self haveOneMoreCharacter])
+                return sbjson4_token_eof;
+        }
+
+        if (exp_start == index) {
+            [self setError:@"No digits in exponent"];
+            return sbjson4_token_error;
+        }
+
+    }
+
+    if (num_start + 1 == index && bytes[num_start] == '-') {
+        [self setError:@"No digits after initial minus"];
+        return sbjson4_token_error;
+    }
+
+    *token = (char *)(bytes + num_start);
+    *length = index - num_start;
+    return tok;
+}
+
+
+- (sbjson4_token_t)getToken:(char **)token length:(NSUInteger *)length {
+    [self skipWhitespace];
+    NSUInteger copyOfIndex = index;
+
+    unichar ch;
+    if (![self getUnichar:&ch])
+        return sbjson4_token_eof;
+
+    sbjson4_token_t tok;
+    switch (ch) {
+        case '{': {
+            index++;
+            tok = sbjson4_token_object_open;
+            break;
+        }
+        case '}': {
+            index++;
+            tok = sbjson4_token_object_close;
+            break;
+
+        }
+        case '[': {
+            index++;
+            tok = sbjson4_token_array_open;
+            break;
+
+        }
+        case ']': {
+            index++;
+            tok = sbjson4_token_array_close;
+            break;
+
+        }
+        case 't': {
+            tok = [self match:"true" retval:sbjson4_token_bool token:token length:length];
+            break;
+
+        }
+        case 'f': {
+            tok = [self match:"false" retval:sbjson4_token_bool token:token length:length];
+            break;
+
+        }
+        case 'n': {
+            tok = [self match:"null" retval:sbjson4_token_null token:token length:length];
+            break;
+
+        }
+        case ',': {
+            index++;
+            tok = sbjson4_token_value_sep;
+            break;
+
+        }
+        case ':': {
+            index++;
+            tok = sbjson4_token_entry_sep;
+            break;
+
+        }
+        case '"': {
+            tok = [self getStringToken:token length:length];
+            break;
+
+        }
+        case '-':
+        case '0' ... '9': {
+            tok = [self getNumberToken:token length:length];
+            break;
+
+        }
+        case '+': {
+            self.error = @"Leading + is illegal in number";
+            tok = sbjson4_token_error;
+            break;
+
+        }
+        default: {
+            self.error = [NSString stringWithFormat:@"Illegal start of token [%c]", ch];
+            tok = sbjson4_token_error;
+            break;
+        }
+    }
+
+    if (tok == sbjson4_token_eof) {
+        // We ran out of bytes before we could finish parsing the current token.
+        // Back up to the start & wait for more data.
+        index = copyOfIndex;
+    }
+
+    return tok;
+}
+
+@end

+ 210 - 0
MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/json-framework/SBJson4StreamWriter.h

@@ -0,0 +1,210 @@
+/*
+ Copyright (c) 2010, Stig Brautaset.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+   Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+
+   Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+
+   Neither the name of the the author nor the names of its contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <Foundation/Foundation.h>
+
+/// Enable JSON writing for non-native objects
+@interface NSObject (SBProxyForJson)
+
+/**
+ Allows generation of JSON for otherwise unsupported classes.
+
+ If you have a custom class that you want to create a JSON representation
+ for you can implement this method in your class. It should return a
+ representation of your object defined in terms of objects that can be
+ translated into JSON. For example, a Person object might implement it like this:
+
+     - (id)proxyForJson {
+        return [NSDictionary dictionaryWithObjectsAndKeys:
+        name, @"name",
+        phone, @"phone",
+        email, @"email",
+        nil];
+     }
+
+ */
+- (id)proxyForJson;
+
+@end
+
+@class SBJson4StreamWriter;
+
+@protocol SBJson4StreamWriterDelegate
+
+- (void)writer:(SBJson4StreamWriter *)writer appendBytes:(const void *)bytes length:(NSUInteger)length;
+
+@end
+
+@class SBJson4StreamWriterState;
+
+/**
+ The Stream Writer class.
+
+ Accepts a stream of messages and writes JSON of these to its delegate object.
+
+ This class provides a range of high-, mid- and low-level methods. You can mix
+ and match calls to these. For example, you may want to call -writeArrayOpen
+ to start an array and then repeatedly call -writeObject: with various objects
+ before finishing off with a -writeArrayClose call.
+
+ Objective-C types are mapped to JSON types in the following way:
+
+ - NSNull        -> null
+ - NSString      -> string
+ - NSArray       -> array
+ - NSDictionary  -> object
+ - NSNumber's -initWithBool:YES -> true
+ - NSNumber's -initWithBool:NO  -> false
+ - NSNumber      -> number
+
+ NSNumber instances created with the -numberWithBool: method are
+ converted into the JSON boolean "true" and "false" values, and vice
+ versa. Any other NSNumber instances are converted to a JSON number the
+ way you would expect.
+
+ @warning: In JSON the keys of an object must be strings. NSDictionary
+ keys need not be, but attempting to convert an NSDictionary with
+ non-string keys into JSON will throw an exception.*
+
+ */
+
+@interface SBJson4StreamWriter : NSObject {
+    NSMutableDictionary *cache;
+}
+
+@property (nonatomic, weak) SBJson4StreamWriterState *state; // Internal
+@property (nonatomic, readonly, strong) NSMutableArray *stateStack; // Internal
+
+/**
+ delegate to receive JSON output
+ Delegate that will receive messages with output.
+ */
+@property (nonatomic, weak) id<SBJson4StreamWriterDelegate> delegate;
+
+/**
+ The maximum depth.
+
+ Defaults to 512. If the input is nested deeper than this the input will be deemed to be
+ malicious and the parser returns nil, signalling an error. ("Nested too deep".) You can
+ turn off this security feature by setting the maxDepth value to 0.
+ */
+@property(nonatomic) NSUInteger maxDepth;
+
+/**
+ Whether we are generating human-readable (multi line) JSON.
+
+ Set whether or not to generate human-readable JSON. The default is NO, which produces
+ JSON without any whitespace between tokens. If set to YES, generates human-readable
+ JSON with line breaks after each array value and dictionary key/value pair, indented two
+ spaces per nesting level.
+ */
+@property(nonatomic) BOOL humanReadable;
+
+/**
+ Whether or not to sort the dictionary keys in the output.
+
+ If this is set to YES, the dictionary keys in the JSON output will be in sorted order.
+ (This is useful if you need to compare two structures, for example.) The default is NO.
+ */
+@property(nonatomic) BOOL sortKeys;
+
+/**
+ An optional comparator to be used if sortKeys is YES.
+
+ If this is nil, sorting will be done via @selector(compare:).
+ */
+@property (nonatomic, copy) NSComparator sortKeysComparator;
+
+/// Contains the error description after an error has occurred.
+@property (nonatomic, copy) NSString *error;
+
+/**
+ Write an NSDictionary to the JSON stream.
+ @return YES if successful, or NO on failure
+ */
+- (BOOL)writeObject:(NSDictionary*)dict;
+
+/**
+ Write an NSArray to the JSON stream.
+ @return YES if successful, or NO on failure
+ */
+- (BOOL)writeArray:(NSArray *)array;
+
+/**
+ Start writing an Object to the stream
+ @return YES if successful, or NO on failure
+*/
+- (BOOL)writeObjectOpen;
+
+/**
+ Close the current object being written
+ @return YES if successful, or NO on failure
+*/
+- (BOOL)writeObjectClose;
+
+/** Start writing an Array to the stream
+ @return YES if successful, or NO on failure
+*/
+- (BOOL)writeArrayOpen;
+
+/** Close the current Array being written
+ @return YES if successful, or NO on failure
+*/
+- (BOOL)writeArrayClose;
+
+/** Write a null to the stream
+ @return YES if successful, or NO on failure
+*/
+- (BOOL)writeNull;
+
+/** Write a boolean to the stream
+ @return YES if successful, or NO on failure
+*/
+- (BOOL)writeBool:(BOOL)x;
+
+/** Write a Number to the stream
+ @return YES if successful, or NO on failure
+*/
+- (BOOL)writeNumber:(NSNumber*)n;
+
+/** Write a String to the stream
+ @return YES if successful, or NO on failure
+*/
+- (BOOL)writeString:(NSString*)s;
+
+@end
+
+@interface SBJson4StreamWriter (Private)
+- (BOOL)writeValue:(id)v;
+- (void)appendBytes:(const void *)bytes length:(NSUInteger)length;
+@end
+

+ 358 - 0
MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/json-framework/SBJson4StreamWriter.m

@@ -0,0 +1,358 @@
+/*
+ Copyright (c) 2010, Stig Brautaset.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+   Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+
+   Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+
+   Neither the name of the the author nor the names of its contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if !__has_feature(objc_arc)
+#error "This source file must be compiled with ARC enabled!"
+#endif
+
+#import "SBJson4StreamWriter.h"
+#import "SBJson4StreamWriterState.h"
+
+static NSNumber *kTrue;
+static NSNumber *kFalse;
+static NSNumber *kPositiveInfinity;
+static NSNumber *kNegativeInfinity;
+
+
+@implementation SBJson4StreamWriter
+
++ (void)initialize {
+    kPositiveInfinity = [NSNumber numberWithDouble:+HUGE_VAL];
+    kNegativeInfinity = [NSNumber numberWithDouble:-HUGE_VAL];
+    kTrue = [NSNumber numberWithBool:YES];
+    kFalse = [NSNumber numberWithBool:NO];
+}
+
+#pragma mark Housekeeping
+
+- (id)init {
+	self = [super init];
+	if (self) {
+		_maxDepth = 32u;
+        _stateStack = [[NSMutableArray alloc] initWithCapacity:_maxDepth];
+        _state = [SBJson4StreamWriterStateStart sharedInstance];
+        cache = [[NSMutableDictionary alloc] initWithCapacity:32];
+    }
+	return self;
+}
+
+#pragma mark Methods
+
+- (void)appendBytes:(const void *)bytes length:(NSUInteger)length {
+    [_delegate writer:self appendBytes:bytes length:length];
+}
+
+- (BOOL)writeObject:(NSDictionary *)dict {
+	if (![self writeObjectOpen])
+		return NO;
+
+	NSArray *keys = [dict allKeys];
+
+	if (_sortKeys) {
+		if (_sortKeysComparator) {
+			keys = [keys sortedArrayWithOptions:NSSortStable usingComparator:_sortKeysComparator];
+		}
+		else{
+			keys = [keys sortedArrayUsingSelector:@selector(compare:)];
+		}
+	}
+
+	for (id k in keys) {
+		if (![k isKindOfClass:[NSString class]]) {
+			self.error = [NSString stringWithFormat:@"JSON object key must be string: %@", k];
+			return NO;
+		}
+
+		if (![self writeString:k])
+			return NO;
+		if (![self writeValue:[dict objectForKey:k]])
+			return NO;
+	}
+
+	return [self writeObjectClose];
+}
+
+- (BOOL)writeArray:(NSArray*)array {
+	if (![self writeArrayOpen])
+		return NO;
+	for (id v in array)
+		if (![self writeValue:v])
+			return NO;
+	return [self writeArrayClose];
+}
+
+
+- (BOOL)writeObjectOpen {
+	if ([_state isInvalidState:self]) return NO;
+	if ([_state expectingKey:self]) return NO;
+	[_state appendSeparator:self];
+	if (_humanReadable && _stateStack.count) [_state appendWhitespace:self];
+
+    [_stateStack addObject:_state];
+    self.state = [SBJson4StreamWriterStateObjectStart sharedInstance];
+
+	if (_maxDepth && _stateStack.count > _maxDepth) {
+		self.error = @"Nested too deep";
+		return NO;
+	}
+
+	[_delegate writer:self appendBytes:"{" length:1];
+	return YES;
+}
+
+- (BOOL)writeObjectClose {
+	if ([_state isInvalidState:self]) return NO;
+
+    SBJson4StreamWriterState *prev = _state;
+
+    self.state = [_stateStack lastObject];
+    [_stateStack removeLastObject];
+
+	if (_humanReadable) [prev appendWhitespace:self];
+	[_delegate writer:self appendBytes:"}" length:1];
+
+	[_state transitionState:self];
+	return YES;
+}
+
+- (BOOL)writeArrayOpen {
+	if ([_state isInvalidState:self]) return NO;
+	if ([_state expectingKey:self]) return NO;
+	[_state appendSeparator:self];
+	if (_humanReadable && _stateStack.count) [_state appendWhitespace:self];
+
+    [_stateStack addObject:_state];
+	self.state = [SBJson4StreamWriterStateArrayStart sharedInstance];
+
+	if (_maxDepth && _stateStack.count > _maxDepth) {
+		self.error = @"Nested too deep";
+		return NO;
+	}
+
+	[_delegate writer:self appendBytes:"[" length:1];
+	return YES;
+}
+
+- (BOOL)writeArrayClose {
+	if ([_state isInvalidState:self]) return NO;
+	if ([_state expectingKey:self]) return NO;
+
+    SBJson4StreamWriterState *prev = _state;
+
+    self.state = [_stateStack lastObject];
+    [_stateStack removeLastObject];
+
+	if (_humanReadable) [prev appendWhitespace:self];
+	[_delegate writer:self appendBytes:"]" length:1];
+
+	[_state transitionState:self];
+	return YES;
+}
+
+- (BOOL)writeNull {
+	if ([_state isInvalidState:self]) return NO;
+	if ([_state expectingKey:self]) return NO;
+	[_state appendSeparator:self];
+	if (_humanReadable) [_state appendWhitespace:self];
+
+	[_delegate writer:self appendBytes:"null" length:4];
+	[_state transitionState:self];
+	return YES;
+}
+
+- (BOOL)writeBool:(BOOL)x {
+	if ([_state isInvalidState:self]) return NO;
+	if ([_state expectingKey:self]) return NO;
+	[_state appendSeparator:self];
+	if (_humanReadable) [_state appendWhitespace:self];
+
+	if (x)
+		[_delegate writer:self appendBytes:"true" length:4];
+	else
+		[_delegate writer:self appendBytes:"false" length:5];
+	[_state transitionState:self];
+	return YES;
+}
+
+
+- (BOOL)writeValue:(id)o {
+	if ([o isKindOfClass:[NSDictionary class]]) {
+		return [self writeObject:o];
+
+	} else if ([o isKindOfClass:[NSArray class]]) {
+		return [self writeArray:o];
+
+	} else if ([o isKindOfClass:[NSString class]]) {
+		[self writeString:o];
+		return YES;
+
+	} else if ([o isKindOfClass:[NSNumber class]]) {
+		return [self writeNumber:o];
+
+	} else if ([o isKindOfClass:[NSNull class]]) {
+		return [self writeNull];
+
+	} else if ([o respondsToSelector:@selector(proxyForJson)]) {
+		return [self writeValue:[o proxyForJson]];
+
+	}
+
+	self.error = [NSString stringWithFormat:@"JSON serialisation not supported for %@", [o class]];
+	return NO;
+}
+
+static const char *strForChar(int c) {
+	switch (c) {
+		case 0: return "\\u0000";
+		case 1: return "\\u0001";
+		case 2: return "\\u0002";
+		case 3: return "\\u0003";
+		case 4: return "\\u0004";
+		case 5: return "\\u0005";
+		case 6: return "\\u0006";
+		case 7: return "\\u0007";
+		case 8: return "\\b";
+		case 9: return "\\t";
+		case 10: return "\\n";
+		case 11: return "\\u000b";
+		case 12: return "\\f";
+		case 13: return "\\r";
+		case 14: return "\\u000e";
+		case 15: return "\\u000f";
+		case 16: return "\\u0010";
+		case 17: return "\\u0011";
+		case 18: return "\\u0012";
+		case 19: return "\\u0013";
+		case 20: return "\\u0014";
+		case 21: return "\\u0015";
+		case 22: return "\\u0016";
+		case 23: return "\\u0017";
+		case 24: return "\\u0018";
+		case 25: return "\\u0019";
+		case 26: return "\\u001a";
+		case 27: return "\\u001b";
+		case 28: return "\\u001c";
+		case 29: return "\\u001d";
+		case 30: return "\\u001e";
+		case 31: return "\\u001f";
+		case 34: return "\\\"";
+		case 92: return "\\\\";
+		default:
+			[NSException raise:@"Illegal escape char" format:@"-->%c<-- is not a legal escape character", c];
+			return NULL;
+	}
+}
+
+- (BOOL)writeString:(NSString*)string {
+	if ([_state isInvalidState:self]) return NO;
+	[_state appendSeparator:self];
+	if (_humanReadable) [_state appendWhitespace:self];
+
+	NSMutableData *buf = [cache objectForKey:string];
+	if (!buf) {
+
+        NSUInteger len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
+        const char *utf8 = [string UTF8String];
+        NSUInteger written = 0, i = 0;
+
+        buf = [NSMutableData dataWithCapacity:(NSUInteger)(len * 1.1f)];
+        [buf appendBytes:"\"" length:1];
+
+        for (i = 0; i < len; i++) {
+            int c = utf8[i];
+            BOOL isControlChar = c >= 0 && c < 32;
+            if (isControlChar || c == '"' || c == '\\') {
+                if (i - written)
+                    [buf appendBytes:utf8 + written length:i - written];
+                written = i + 1;
+
+                const char *t = strForChar(c);
+                [buf appendBytes:t length:strlen(t)];
+            }
+        }
+
+        if (i - written)
+            [buf appendBytes:utf8 + written length:i - written];
+
+        [buf appendBytes:"\"" length:1];
+        [cache setObject:buf forKey:string];
+    }
+
+	[_delegate writer:self appendBytes:[buf bytes] length:[buf length]];
+	[_state transitionState:self];
+	return YES;
+}
+
+- (BOOL)writeNumber:(NSNumber*)number {
+	if (number == kTrue || number == kFalse)
+		return [self writeBool:[number boolValue]];
+
+	if ([_state isInvalidState:self]) return NO;
+	if ([_state expectingKey:self]) return NO;
+	[_state appendSeparator:self];
+	if (_humanReadable) [_state appendWhitespace:self];
+
+	if ([kPositiveInfinity isEqualToNumber:number]) {
+		self.error = @"+Infinity is not a valid number in JSON";
+		return NO;
+
+	} else if ([kNegativeInfinity isEqualToNumber:number]) {
+		self.error = @"-Infinity is not a valid number in JSON";
+		return NO;
+
+	} else if (isnan([number doubleValue])) {
+		self.error = @"NaN is not a valid number in JSON";
+		return NO;
+	}
+
+	const char *objcType = [number objCType];
+	char num[128];
+	size_t len;
+
+	switch (objcType[0]) {
+		case 'c': case 'i': case 's': case 'l': case 'q':
+			len = snprintf(num, sizeof num, "%lld", [number longLongValue]);
+			break;
+		case 'C': case 'I': case 'S': case 'L': case 'Q':
+			len = snprintf(num, sizeof num, "%llu", [number unsignedLongLongValue]);
+			break;
+		case 'f': case 'd': default: {
+            len = snprintf(num, sizeof num, "%.17g", [number doubleValue]);
+			break;
+        }
+	}
+	[_delegate writer:self appendBytes:num length: len];
+	[_state transitionState:self];
+	return YES;
+}
+
+@end

+ 69 - 0
MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/json-framework/SBJson4StreamWriterState.h

@@ -0,0 +1,69 @@
+/*
+ Copyright (c) 2010, Stig Brautaset.
+ All rights reserved.
+ 
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+ 
+   Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+  
+   Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+ 
+   Neither the name of the the author nor the names of its contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+ 
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <Foundation/Foundation.h>
+
+@class SBJson4StreamWriter;
+
+@interface SBJson4StreamWriterState : NSObject
++ (id)sharedInstance;
+- (BOOL)isInvalidState:(SBJson4StreamWriter *)writer;
+- (void)appendSeparator:(SBJson4StreamWriter *)writer;
+- (BOOL)expectingKey:(SBJson4StreamWriter *)writer;
+- (void)transitionState:(SBJson4StreamWriter *)writer;
+- (void)appendWhitespace:(SBJson4StreamWriter *)writer;
+@end
+
+@interface SBJson4StreamWriterStateObjectStart : SBJson4StreamWriterState
+@end
+
+@interface SBJson4StreamWriterStateObjectKey : SBJson4StreamWriterStateObjectStart
+@end
+
+@interface SBJson4StreamWriterStateObjectValue : SBJson4StreamWriterState
+@end
+
+@interface SBJson4StreamWriterStateArrayStart : SBJson4StreamWriterState
+@end
+
+@interface SBJson4StreamWriterStateArrayValue : SBJson4StreamWriterState
+@end
+
+@interface SBJson4StreamWriterStateStart : SBJson4StreamWriterState
+@end
+
+@interface SBJson4StreamWriterStateComplete : SBJson4StreamWriterState
+@end
+
+@interface SBJson4StreamWriterStateError : SBJson4StreamWriterState
+@end
+

+ 147 - 0
MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/json-framework/SBJson4StreamWriterState.m

@@ -0,0 +1,147 @@
+/*
+ Copyright (c) 2010, Stig Brautaset.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+   Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+
+   Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+
+   Neither the name of the the author nor the names of its contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if !__has_feature(objc_arc)
+#error "This source file must be compiled with ARC enabled!"
+#endif
+
+#import "SBJson4StreamWriterState.h"
+#import "SBJson4StreamWriter.h"
+
+#define SINGLETON \
++ (id)sharedInstance { \
+    static id state = nil; \
+    if (!state) { \
+        @synchronized(self) { \
+            if (!state) state = [[self alloc] init]; \
+        } \
+    } \
+    return state; \
+}
+
+
+@implementation SBJson4StreamWriterState
++ (id)sharedInstance { return nil; }
+- (BOOL)isInvalidState:(SBJson4StreamWriter *)writer { return NO; }
+- (void)appendSeparator:(SBJson4StreamWriter *)writer {}
+- (BOOL)expectingKey:(SBJson4StreamWriter *)writer { return NO; }
+- (void)transitionState:(SBJson4StreamWriter *)writer {}
+- (void)appendWhitespace:(SBJson4StreamWriter *)writer {
+	[writer appendBytes:"\n" length:1];
+	for (NSUInteger i = 0; i < writer.stateStack.count; i++)
+	    [writer appendBytes:"  " length:2];
+}
+@end
+
+@implementation SBJson4StreamWriterStateObjectStart
+
+SINGLETON
+
+- (void)transitionState:(SBJson4StreamWriter *)writer {
+	writer.state = [SBJson4StreamWriterStateObjectValue sharedInstance];
+}
+- (BOOL)expectingKey:(SBJson4StreamWriter *)writer {
+	writer.error = @"JSON object key must be string";
+	return YES;
+}
+@end
+
+@implementation SBJson4StreamWriterStateObjectKey
+
+SINGLETON
+
+- (void)appendSeparator:(SBJson4StreamWriter *)writer {
+	[writer appendBytes:"," length:1];
+}
+@end
+
+@implementation SBJson4StreamWriterStateObjectValue
+
+SINGLETON
+
+- (void)appendSeparator:(SBJson4StreamWriter *)writer {
+	[writer appendBytes:":" length:1];
+}
+- (void)transitionState:(SBJson4StreamWriter *)writer {
+    writer.state = [SBJson4StreamWriterStateObjectKey sharedInstance];
+}
+- (void)appendWhitespace:(SBJson4StreamWriter *)writer {
+	[writer appendBytes:" " length:1];
+}
+@end
+
+@implementation SBJson4StreamWriterStateArrayStart
+
+SINGLETON
+
+- (void)transitionState:(SBJson4StreamWriter *)writer {
+    writer.state = [SBJson4StreamWriterStateArrayValue sharedInstance];
+}
+@end
+
+@implementation SBJson4StreamWriterStateArrayValue
+
+SINGLETON
+
+- (void)appendSeparator:(SBJson4StreamWriter *)writer {
+	[writer appendBytes:"," length:1];
+}
+@end
+
+@implementation SBJson4StreamWriterStateStart
+
+SINGLETON
+
+
+- (void)transitionState:(SBJson4StreamWriter *)writer {
+    writer.state = [SBJson4StreamWriterStateComplete sharedInstance];
+}
+- (void)appendSeparator:(SBJson4StreamWriter *)writer {
+}
+@end
+
+@implementation SBJson4StreamWriterStateComplete
+
+SINGLETON
+
+- (BOOL)isInvalidState:(SBJson4StreamWriter *)writer {
+	writer.error = @"Stream is closed";
+	return YES;
+}
+@end
+
+@implementation SBJson4StreamWriterStateError
+
+SINGLETON
+
+@end
+

+ 103 - 0
MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/json-framework/SBJson4Writer.h

@@ -0,0 +1,103 @@
+/*
+ Copyright (C) 2009 Stig Brautaset. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+ * Neither the name of the author nor the names of its contributors may be used
+   to endorse or promote products derived from this software without specific
+   prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <Foundation/Foundation.h>
+
+/**
+ The JSON writer class.
+
+ This uses SBJson4StreamWriter internally.
+
+ */
+
+@interface SBJson4Writer : NSObject
+
+/**
+ The maximum depth.
+
+ Defaults to 32. If the input is nested deeper than this the input will be deemed to be
+ malicious and the parser returns nil, signalling an error. ("Nested too deep".) You can
+ turn off this security feature by setting the maxDepth value to 0.
+ */
+@property(nonatomic) NSUInteger maxDepth;
+
+/**
+ Return an error trace, or nil if there was no errors.
+
+ Note that this method returns the trace of the last method that failed.
+ You need to check the return value of the call you're making to figure out
+ if the call actually failed, before you know call this method.
+ */
+@property (nonatomic, readonly, copy) NSString *error;
+
+/**
+ Whether we are generating human-readable (multi line) JSON.
+
+ Set whether or not to generate human-readable JSON. The default is NO, which produces
+ JSON without any whitespace. (Except inside strings.) If set to YES, generates human-readable
+ JSON with line breaks after each array value and dictionary key/value pair, indented two
+ spaces per nesting level.
+ */
+@property(nonatomic) BOOL humanReadable;
+
+/**
+ Whether or not to sort the dictionary keys in the output.
+
+ If this is set to YES, the dictionary keys in the JSON output will be in sorted order.
+ (This is useful if you need to compare two structures, for example.) The default is NO.
+ */
+@property(nonatomic) BOOL sortKeys;
+
+/**
+ An optional comparator to be used if sortKeys is YES.
+
+ If this is nil, sorting will be done via @selector(compare:).
+ */
+@property (nonatomic, copy) NSComparator sortKeysComparator;
+
+/**
+ Generates string with JSON representation for the given object.
+
+ Returns a string containing JSON representation of the passed in value, or nil on error.
+ If nil is returned and error is not NULL, *error can be interrogated to find the cause of the error.
+
+ @param value any instance that can be represented as JSON text.
+ */
+- (NSString*)stringWithObject:(id)value;
+
+/**
+ Generates JSON representation for the given object.
+
+ Returns an NSData object containing JSON represented as UTF8 text, or nil on error.
+
+ @param value any instance that can be represented as JSON text.
+ */
+- (NSData*)dataWithObject:(id)value;
+
+@end

+ 102 - 0
MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/json-framework/SBJson4Writer.m

@@ -0,0 +1,102 @@
+/*
+ Copyright (C) 2009 Stig Brautaset. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+ * Neither the name of the author nor the names of its contributors may be used
+   to endorse or promote products derived from this software without specific
+   prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if !__has_feature(objc_arc)
+#error "This source file must be compiled with ARC enabled!"
+#endif
+
+#import "SBJson4Writer.h"
+#import "SBJson4StreamWriter.h"
+
+
+@interface SBJson4Writer () < SBJson4StreamWriterDelegate >
+@property (nonatomic, copy) NSString *error;
+@property (nonatomic, strong) NSMutableData *acc;
+@end
+
+@implementation SBJson4Writer
+
+- (id)init {
+    self = [super init];
+    if (self) {
+        self.maxDepth = 32u;
+    }
+    return self;
+}
+
+
+- (NSString*)stringWithObject:(id)value {
+	NSData *data = [self dataWithObject:value];
+	if (data)
+		return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+	return nil;
+}
+
+- (NSData*)dataWithObject:(id)object {
+    self.error = nil;
+
+    self.acc = [[NSMutableData alloc] initWithCapacity:8096u];
+
+    SBJson4StreamWriter *streamWriter = [[SBJson4StreamWriter alloc] init];
+	streamWriter.sortKeys = self.sortKeys;
+	streamWriter.maxDepth = self.maxDepth;
+	streamWriter.sortKeysComparator = self.sortKeysComparator;
+	streamWriter.humanReadable = self.humanReadable;
+    streamWriter.delegate = self;
+
+	BOOL ok = NO;
+	if ([object isKindOfClass:[NSDictionary class]])
+		ok = [streamWriter writeObject:object];
+
+	else if ([object isKindOfClass:[NSArray class]])
+		ok = [streamWriter writeArray:object];
+
+	else if ([object respondsToSelector:@selector(proxyForJson)])
+		return [self dataWithObject:[object proxyForJson]];
+	else {
+		self.error = @"Not valid type for JSON";
+		return nil;
+	}
+
+	if (ok)
+		return self.acc;
+
+	self.error = streamWriter.error;
+	return nil;
+}
+
+#pragma mark SBJson4StreamWriterDelegate
+
+- (void)writer:(SBJson4StreamWriter *)writer appendBytes:(const void *)bytes length:(NSUInteger)length {
+    [self.acc appendBytes:bytes length:length];
+}
+
+
+
+@end

+ 22 - 0
MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnelTests/Info.plist

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>en</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>$(PRODUCT_NAME)</string>
+	<key>CFBundlePackageType</key>
+	<string>BNDL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.0</string>
+	<key>CFBundleVersion</key>
+	<string>1</string>
+</dict>
+</plist>

+ 39 - 0
MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnelTests/PsiphonTunnelTests.m

@@ -0,0 +1,39 @@
+//
+//  PsiphonTunnelTests.m
+//  PsiphonTunnelTests
+//
+//  Created by Adam Pritchard on 2016-10-06.
+//  Copyright © 2016 Psiphon Inc. All rights reserved.
+//
+
+#import <XCTest/XCTest.h>
+
+@interface PsiphonTunnelTests : XCTestCase
+
+@end
+
+@implementation PsiphonTunnelTests
+
+- (void)setUp {
+    [super setUp];
+    // Put setup code here. This method is called before the invocation of each test method in the class.
+}
+
+- (void)tearDown {
+    // Put teardown code here. This method is called after the invocation of each test method in the class.
+    [super tearDown];
+}
+
+- (void)testExample {
+    // This is an example of a functional test case.
+    // Use XCTAssert and related functions to verify your tests produce the correct results.
+}
+
+- (void)testPerformanceExample {
+    // This is an example of a performance test case.
+    [self measureBlock:^{
+        // Put the code you want to measure the time of here.
+    }];
+}
+
+@end

+ 0 - 65
MobileLibrary/iOS/PsiphonTunnelController/.gitignore

@@ -1,65 +0,0 @@
-# Xcode
-#
-# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
-
-## Build generated
-build/
-DerivedData/
-
-## Various settings
-*.pbxuser
-!default.pbxuser
-*.mode1v3
-!default.mode1v3
-*.mode2v3
-!default.mode2v3
-*.perspectivev3
-!default.perspectivev3
-xcuserdata/
-
-## Other
-*.moved-aside
-*.xcuserstate
-
-## Obj-C/Swift specific
-*.hmap
-*.ipa
-*.dSYM.zip
-*.dSYM
-
-## Playgrounds
-timeline.xctimeline
-playground.xcworkspace
-
-# Swift Package Manager
-#
-# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
-# Packages/
-.build/
-
-# CocoaPods
-#
-# We recommend against adding the Pods directory to your .gitignore. However
-# you should judge for yourself, the pros and cons are mentioned at:
-# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
-#
-# Pods/
-
-# Carthage
-#
-# Add this line if you want to avoid checking in source code from Carthage dependencies.
-# Carthage/Checkouts
-
-Carthage/Build
-
-# fastlane
-#
-# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
-# screenshots whenever they are needed.
-# For more information about the recommended setup visit:
-# https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
-
-fastlane/report.xml
-fastlane/Preview.html
-fastlane/screenshots
-fastlane/test_output

+ 0 - 380
MobileLibrary/iOS/PsiphonTunnelController/PsiphonTunnelController.xcodeproj/project.pbxproj

@@ -1,380 +0,0 @@
-// !$*UTF8*$!
-{
-	archiveVersion = 1;
-	classes = {
-	};
-	objectVersion = 46;
-	objects = {
-
-/* Begin PBXBuildFile section */
-		4445243B1D8B356E00AF7B5B /* Psi.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4445243A1D8B356E00AF7B5B /* Psi.framework */; };
-		44980ACC1D63A3EA00B78274 /* Reachability.h in Headers */ = {isa = PBXBuildFile; fileRef = 44980AC91D63A3EA00B78274 /* Reachability.h */; settings = {ATTRIBUTES = (Public, ); }; };
-		44980ACD1D63A3EA00B78274 /* Reachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 44980ACA1D63A3EA00B78274 /* Reachability.m */; };
-		44CC93D61D6CBD740082F743 /* rootCAs.txt in Resources */ = {isa = PBXBuildFile; fileRef = 44CC93D51D6CBD740082F743 /* rootCAs.txt */; };
-		66ED503D1D8C73EE00417FE8 /* PsiphonTunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66ED503B1D8C73EE00417FE8 /* PsiphonTunnel.swift */; };
-		66ED503E1D8C73EE00417FE8 /* SwiftyJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66ED503C1D8C73EE00417FE8 /* SwiftyJSON.swift */; };
-		66ED50531D9043B800417FE8 /* Psi.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4445243A1D8B356E00AF7B5B /* Psi.framework */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
-/* End PBXBuildFile section */
-
-/* Begin PBXCopyFilesBuildPhase section */
-		66ED50521D9043A800417FE8 /* CopyFiles */ = {
-			isa = PBXCopyFilesBuildPhase;
-			buildActionMask = 2147483647;
-			dstPath = "";
-			dstSubfolderSpec = 10;
-			files = (
-				66ED50531D9043B800417FE8 /* Psi.framework in CopyFiles */,
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-		};
-/* End PBXCopyFilesBuildPhase section */
-
-/* Begin PBXFileReference section */
-		4445243A1D8B356E00AF7B5B /* Psi.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Psi.framework; path = PsiphonTunnelController/Psi.framework; sourceTree = "<group>"; };
-		44980ABD1D63A3B300B78274 /* PsiphonTunnelController.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PsiphonTunnelController.framework; sourceTree = BUILT_PRODUCTS_DIR; };
-		44980AC11D63A3B300B78274 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
-		44980AC91D63A3EA00B78274 /* Reachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Reachability.h; sourceTree = "<group>"; };
-		44980ACA1D63A3EA00B78274 /* Reachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Reachability.m; sourceTree = "<group>"; };
-		44CC93D51D6CBD740082F743 /* rootCAs.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = rootCAs.txt; path = PsiphonTunnelController/rootCAs.txt; sourceTree = "<group>"; };
-		66ED503B1D8C73EE00417FE8 /* PsiphonTunnel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PsiphonTunnel.swift; sourceTree = "<group>"; };
-		66ED503C1D8C73EE00417FE8 /* SwiftyJSON.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftyJSON.swift; sourceTree = "<group>"; };
-/* End PBXFileReference section */
-
-/* Begin PBXFrameworksBuildPhase section */
-		44980AB91D63A3B300B78274 /* Frameworks */ = {
-			isa = PBXFrameworksBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-				4445243B1D8B356E00AF7B5B /* Psi.framework in Frameworks */,
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-		};
-/* End PBXFrameworksBuildPhase section */
-
-/* Begin PBXGroup section */
-		44980AB31D63A3B300B78274 = {
-			isa = PBXGroup;
-			children = (
-				4445243A1D8B356E00AF7B5B /* Psi.framework */,
-				44CC93D41D6CBD260082F743 /* Resources */,
-				44980ABF1D63A3B300B78274 /* PsiphonTunnelController */,
-				44980ABE1D63A3B300B78274 /* Products */,
-			);
-			sourceTree = "<group>";
-		};
-		44980ABE1D63A3B300B78274 /* Products */ = {
-			isa = PBXGroup;
-			children = (
-				44980ABD1D63A3B300B78274 /* PsiphonTunnelController.framework */,
-			);
-			name = Products;
-			sourceTree = "<group>";
-		};
-		44980ABF1D63A3B300B78274 /* PsiphonTunnelController */ = {
-			isa = PBXGroup;
-			children = (
-				66ED503B1D8C73EE00417FE8 /* PsiphonTunnel.swift */,
-				66ED503C1D8C73EE00417FE8 /* SwiftyJSON.swift */,
-				44980AC91D63A3EA00B78274 /* Reachability.h */,
-				44980ACA1D63A3EA00B78274 /* Reachability.m */,
-				44980AC11D63A3B300B78274 /* Info.plist */,
-			);
-			path = PsiphonTunnelController;
-			sourceTree = "<group>";
-		};
-		44CC93D41D6CBD260082F743 /* Resources */ = {
-			isa = PBXGroup;
-			children = (
-				44CC93D51D6CBD740082F743 /* rootCAs.txt */,
-			);
-			name = Resources;
-			sourceTree = "<group>";
-		};
-/* End PBXGroup section */
-
-/* Begin PBXHeadersBuildPhase section */
-		44980ABA1D63A3B300B78274 /* Headers */ = {
-			isa = PBXHeadersBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-				44980ACC1D63A3EA00B78274 /* Reachability.h in Headers */,
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-		};
-/* End PBXHeadersBuildPhase section */
-
-/* Begin PBXNativeTarget section */
-		44980ABC1D63A3B300B78274 /* PsiphonTunnelController */ = {
-			isa = PBXNativeTarget;
-			buildConfigurationList = 44980AC51D63A3B300B78274 /* Build configuration list for PBXNativeTarget "PsiphonTunnelController" */;
-			buildPhases = (
-				44980AB81D63A3B300B78274 /* Sources */,
-				44980AB91D63A3B300B78274 /* Frameworks */,
-				44980ABA1D63A3B300B78274 /* Headers */,
-				44980ABB1D63A3B300B78274 /* Resources */,
-				66ED50521D9043A800417FE8 /* CopyFiles */,
-			);
-			buildRules = (
-			);
-			dependencies = (
-			);
-			name = PsiphonTunnelController;
-			productName = PsiphonTunnelController;
-			productReference = 44980ABD1D63A3B300B78274 /* PsiphonTunnelController.framework */;
-			productType = "com.apple.product-type.framework";
-		};
-/* End PBXNativeTarget section */
-
-/* Begin PBXProject section */
-		44980AB41D63A3B300B78274 /* Project object */ = {
-			isa = PBXProject;
-			attributes = {
-				LastUpgradeCheck = 0800;
-				ORGANIZATIONNAME = "Psiphon Inc.";
-				TargetAttributes = {
-					44980ABC1D63A3B300B78274 = {
-						CreatedOnToolsVersion = 8.0;
-						LastSwiftMigration = 0800;
-						ProvisioningStyle = Manual;
-					};
-				};
-			};
-			buildConfigurationList = 44980AB71D63A3B300B78274 /* Build configuration list for PBXProject "PsiphonTunnelController" */;
-			compatibilityVersion = "Xcode 3.2";
-			developmentRegion = English;
-			hasScannedForEncodings = 0;
-			knownRegions = (
-				en,
-			);
-			mainGroup = 44980AB31D63A3B300B78274;
-			productRefGroup = 44980ABE1D63A3B300B78274 /* Products */;
-			projectDirPath = "";
-			projectRoot = "";
-			targets = (
-				44980ABC1D63A3B300B78274 /* PsiphonTunnelController */,
-			);
-		};
-/* End PBXProject section */
-
-/* Begin PBXResourcesBuildPhase section */
-		44980ABB1D63A3B300B78274 /* Resources */ = {
-			isa = PBXResourcesBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-				44CC93D61D6CBD740082F743 /* rootCAs.txt in Resources */,
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-		};
-/* End PBXResourcesBuildPhase section */
-
-/* Begin PBXSourcesBuildPhase section */
-		44980AB81D63A3B300B78274 /* Sources */ = {
-			isa = PBXSourcesBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-				66ED503E1D8C73EE00417FE8 /* SwiftyJSON.swift in Sources */,
-				44980ACD1D63A3EA00B78274 /* Reachability.m in Sources */,
-				66ED503D1D8C73EE00417FE8 /* PsiphonTunnel.swift in Sources */,
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-		};
-/* End PBXSourcesBuildPhase section */
-
-/* Begin XCBuildConfiguration section */
-		44980AC31D63A3B300B78274 /* Debug */ = {
-			isa = XCBuildConfiguration;
-			buildSettings = {
-				ALWAYS_SEARCH_USER_PATHS = NO;
-				CLANG_ANALYZER_NONNULL = YES;
-				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
-				CLANG_CXX_LIBRARY = "libc++";
-				CLANG_ENABLE_MODULES = YES;
-				CLANG_ENABLE_OBJC_ARC = YES;
-				CLANG_WARN_BOOL_CONVERSION = YES;
-				CLANG_WARN_CONSTANT_CONVERSION = YES;
-				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
-				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
-				CLANG_WARN_EMPTY_BODY = YES;
-				CLANG_WARN_ENUM_CONVERSION = YES;
-				CLANG_WARN_INFINITE_RECURSION = YES;
-				CLANG_WARN_INT_CONVERSION = YES;
-				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
-				CLANG_WARN_SUSPICIOUS_MOVES = YES;
-				CLANG_WARN_UNREACHABLE_CODE = YES;
-				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
-				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
-				COPY_PHASE_STRIP = NO;
-				CURRENT_PROJECT_VERSION = 1;
-				DEBUG_INFORMATION_FORMAT = dwarf;
-				ENABLE_BITCODE = NO;
-				ENABLE_STRICT_OBJC_MSGSEND = YES;
-				ENABLE_TESTABILITY = YES;
-				GCC_C_LANGUAGE_STANDARD = gnu99;
-				GCC_DYNAMIC_NO_PIC = NO;
-				GCC_NO_COMMON_BLOCKS = YES;
-				GCC_OPTIMIZATION_LEVEL = 0;
-				GCC_PREPROCESSOR_DEFINITIONS = (
-					"DEBUG=1",
-					"$(inherited)",
-				);
-				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
-				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
-				GCC_WARN_UNDECLARED_SELECTOR = YES;
-				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
-				GCC_WARN_UNUSED_FUNCTION = YES;
-				GCC_WARN_UNUSED_VARIABLE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
-				MTL_ENABLE_DEBUG_INFO = YES;
-				ONLY_ACTIVE_ARCH = YES;
-				OTHER_LDFLAGS = (
-					"-read_only_relocs",
-					suppress,
-				);
-				SDKROOT = iphoneos;
-				TARGETED_DEVICE_FAMILY = "1,2";
-				VERSIONING_SYSTEM = "apple-generic";
-				VERSION_INFO_PREFIX = "";
-			};
-			name = Debug;
-		};
-		44980AC41D63A3B300B78274 /* Release */ = {
-			isa = XCBuildConfiguration;
-			buildSettings = {
-				ALWAYS_SEARCH_USER_PATHS = NO;
-				CLANG_ANALYZER_NONNULL = YES;
-				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
-				CLANG_CXX_LIBRARY = "libc++";
-				CLANG_ENABLE_MODULES = YES;
-				CLANG_ENABLE_OBJC_ARC = YES;
-				CLANG_WARN_BOOL_CONVERSION = YES;
-				CLANG_WARN_CONSTANT_CONVERSION = YES;
-				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
-				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
-				CLANG_WARN_EMPTY_BODY = YES;
-				CLANG_WARN_ENUM_CONVERSION = YES;
-				CLANG_WARN_INFINITE_RECURSION = YES;
-				CLANG_WARN_INT_CONVERSION = YES;
-				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
-				CLANG_WARN_SUSPICIOUS_MOVES = YES;
-				CLANG_WARN_UNREACHABLE_CODE = YES;
-				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
-				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
-				COPY_PHASE_STRIP = NO;
-				CURRENT_PROJECT_VERSION = 1;
-				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
-				ENABLE_BITCODE = NO;
-				ENABLE_NS_ASSERTIONS = NO;
-				ENABLE_STRICT_OBJC_MSGSEND = YES;
-				GCC_C_LANGUAGE_STANDARD = gnu99;
-				GCC_DYNAMIC_NO_PIC = NO;
-				GCC_NO_COMMON_BLOCKS = YES;
-				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
-				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
-				GCC_WARN_UNDECLARED_SELECTOR = YES;
-				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
-				GCC_WARN_UNUSED_FUNCTION = YES;
-				GCC_WARN_UNUSED_VARIABLE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
-				MTL_ENABLE_DEBUG_INFO = NO;
-				OTHER_LDFLAGS = (
-					"-read_only_relocs",
-					suppress,
-				);
-				SDKROOT = iphoneos;
-				TARGETED_DEVICE_FAMILY = "1,2";
-				VALIDATE_PRODUCT = YES;
-				VERSIONING_SYSTEM = "apple-generic";
-				VERSION_INFO_PREFIX = "";
-			};
-			name = Release;
-		};
-		44980AC61D63A3B300B78274 /* Debug */ = {
-			isa = XCBuildConfiguration;
-			buildSettings = {
-				ARCHS = "$(ARCHS_STANDARD)";
-				CODE_SIGN_IDENTITY = "";
-				DEFINES_MODULE = NO;
-				DEVELOPMENT_TEAM = "";
-				DYLIB_COMPATIBILITY_VERSION = 1;
-				DYLIB_CURRENT_VERSION = 1;
-				DYLIB_INSTALL_NAME_BASE = "@rpath";
-				ENABLE_BITCODE = NO;
-				FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/PsiphonTunnelController";
-				INFOPLIST_FILE = PsiphonTunnelController/Info.plist;
-				INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
-				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
-				ONLY_ACTIVE_ARCH = NO;
-				OTHER_LDFLAGS = (
-					"-read_only_relocs",
-					suppress,
-					"-sub_umbrella",
-					Psi,
-				);
-				PRODUCT_BUNDLE_IDENTIFIER = com.psiphon.PsiphonTunnelController;
-				PRODUCT_NAME = "$(TARGET_NAME)";
-				SKIP_INSTALL = YES;
-				SWIFT_OBJC_INTERFACE_HEADER_NAME = "$(SWIFT_MODULE_NAME).h";
-				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
-				SWIFT_VERSION = 3.0;
-				TARGETED_DEVICE_FAMILY = "1,2";
-				VALID_ARCHS = "arm64 armv7 armv7s";
-			};
-			name = Debug;
-		};
-		44980AC71D63A3B300B78274 /* Release */ = {
-			isa = XCBuildConfiguration;
-			buildSettings = {
-				ARCHS = "$(ARCHS_STANDARD)";
-				CODE_SIGN_IDENTITY = "";
-				DEFINES_MODULE = NO;
-				DEVELOPMENT_TEAM = "";
-				DYLIB_COMPATIBILITY_VERSION = 1;
-				DYLIB_CURRENT_VERSION = 1;
-				DYLIB_INSTALL_NAME_BASE = "@rpath";
-				ENABLE_BITCODE = NO;
-				FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/PsiphonTunnelController";
-				INFOPLIST_FILE = PsiphonTunnelController/Info.plist;
-				INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
-				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
-				ONLY_ACTIVE_ARCH = NO;
-				OTHER_LDFLAGS = (
-					"-read_only_relocs",
-					suppress,
-					"-sub_umbrella",
-					Psi,
-				);
-				PRODUCT_BUNDLE_IDENTIFIER = com.psiphon.PsiphonTunnelController;
-				PRODUCT_NAME = "$(TARGET_NAME)";
-				SKIP_INSTALL = YES;
-				SWIFT_OBJC_INTERFACE_HEADER_NAME = "$(SWIFT_MODULE_NAME).h";
-				SWIFT_VERSION = 3.0;
-				TARGETED_DEVICE_FAMILY = "1,2";
-				VALID_ARCHS = "arm64 armv7 armv7s";
-			};
-			name = Release;
-		};
-/* End XCBuildConfiguration section */
-
-/* Begin XCConfigurationList section */
-		44980AB71D63A3B300B78274 /* Build configuration list for PBXProject "PsiphonTunnelController" */ = {
-			isa = XCConfigurationList;
-			buildConfigurations = (
-				44980AC31D63A3B300B78274 /* Debug */,
-				44980AC41D63A3B300B78274 /* Release */,
-			);
-			defaultConfigurationIsVisible = 0;
-			defaultConfigurationName = Release;
-		};
-		44980AC51D63A3B300B78274 /* Build configuration list for PBXNativeTarget "PsiphonTunnelController" */ = {
-			isa = XCConfigurationList;
-			buildConfigurations = (
-				44980AC61D63A3B300B78274 /* Debug */,
-				44980AC71D63A3B300B78274 /* Release */,
-			);
-			defaultConfigurationIsVisible = 0;
-			defaultConfigurationName = Release;
-		};
-/* End XCConfigurationList section */
-	};
-	rootObject = 44980AB41D63A3B300B78274 /* Project object */;
-}

+ 0 - 114
MobileLibrary/iOS/PsiphonTunnelController/PsiphonTunnelController.xcodeproj/project.xcworkspace/xcshareddata/PsiphonTunnelController.xcscmblueprint

@@ -1,114 +0,0 @@
-{
-  "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "654BDFA72D0BD5C4AAA375C3F6D7EA32AC58A304+++F92F6D3",
-  "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : {
-
-  },
-  "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : {
-    "8B61C8B48DB4B96AD94F67018EFB8A9684F3EFD6+++DCC608E" : 9223372036854775807,
-    "654BDFA72D0BD5C4AAA375C3F6D7EA32AC58A304+++38A1A30" : 9223372036854775807,
-    "A5AD174D8B9671567C38590D166034173C866E1D+++C7AE692" : 9223372036854775807,
-    "18F8E8F32D4612669B2AC73CE391ED54473F75DA+++ED25EBA" : 9223372036854775807,
-    "3CDC0364FAC963F881E15357C59A43097543A382+++74D281F" : 9223372036854775807,
-    "654BDFA72D0BD5C4AAA375C3F6D7EA32AC58A304+++F92F6D3" : 9223372036854775807,
-    "18F8E8F32D4612669B2AC73CE391ED54473F75DA+++D9DC38C" : 9223372036854775807,
-    "D059B056F0F8D707428CFA53B26B54CB05124896+++41B0777" : 9223372036854775807,
-    "C811301B9BA5BED994AD79D58835794EFE2C4BAD+++5A1EDEB" : 9223372036854775807,
-    "18F8E8F32D4612669B2AC73CE391ED54473F75DA+++739CA66" : 9223372036854775807,
-    "A5AD174D8B9671567C38590D166034173C866E1D+++795FDF9" : 9223372036854775807,
-    "18F8E8F32D4612669B2AC73CE391ED54473F75DA+++0719F03" : 9223372036854775807,
-    "0EDA2F64A27382CC2BF1F85AB9A4AEECDF2AEE03+++14C946E" : 9223372036854775807,
-    "0559510868EA25C63F4143C7E150BF185B7A091F+++F4FA2CA" : 9223372036854775807
-  },
-  "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "7D3F11EA-BDE9-4C77-841B-858842838E65",
-  "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : {
-    "8B61C8B48DB4B96AD94F67018EFB8A9684F3EFD6+++DCC608E" : "psiphon-tunnel-core\/MobileLibrary\/iOS\/go-ios-build\/src\/github.com\/Psiphon-Inc\/bolt\/",
-    "654BDFA72D0BD5C4AAA375C3F6D7EA32AC58A304+++38A1A30" : "psiphon-tunnel-core\/MobileLibrary\/iOS\/go-ios-build\/src\/github.com\/Psiphon-Labs\/psiphon-tunnel-core\/",
-    "A5AD174D8B9671567C38590D166034173C866E1D+++C7AE692" : "psiphon-tunnel-core\/MobileLibrary\/iOS\/go-ios-build\/src\/github.com\/spacemonkeygo\/openssl\/",
-    "18F8E8F32D4612669B2AC73CE391ED54473F75DA+++ED25EBA" : "psiphon-tunnel-core\/MobileLibrary\/iOS\/go-ios-build\/src\/golang.org\/x\/net\/",
-    "3CDC0364FAC963F881E15357C59A43097543A382+++74D281F" : "psiphon-tunnel-core\/MobileLibrary\/iOS\/go-ios-build\/src\/github.com\/Psiphon-Inc\/dns\/",
-    "654BDFA72D0BD5C4AAA375C3F6D7EA32AC58A304+++F92F6D3" : "psiphon-tunnel-core\/",
-    "18F8E8F32D4612669B2AC73CE391ED54473F75DA+++D9DC38C" : "psiphon-tunnel-core\/MobileLibrary\/iOS\/go-ios-build\/src\/golang.org\/x\/net\/",
-    "D059B056F0F8D707428CFA53B26B54CB05124896+++41B0777" : "psiphon-tunnel-core\/MobileLibrary\/iOS\/go-ios-build\/src\/github.com\/Psiphon-Inc\/goptlib\/",
-    "C811301B9BA5BED994AD79D58835794EFE2C4BAD+++5A1EDEB" : "psiphon-tunnel-core\/MobileLibrary\/iOS\/go-ios-build\/src\/github.com\/Psiphon-Inc\/goregen\/",
-    "18F8E8F32D4612669B2AC73CE391ED54473F75DA+++739CA66" : "psiphon-tunnel-core\/MobileLibrary\/iOS\/go-ios-build\/src\/golang.org\/x\/net\/",
-    "A5AD174D8B9671567C38590D166034173C866E1D+++795FDF9" : "psiphon-tunnel-core\/MobileLibrary\/iOS\/go-ios-build\/src\/github.com\/spacemonkeygo\/openssl\/",
-    "18F8E8F32D4612669B2AC73CE391ED54473F75DA+++0719F03" : "psiphon-tunnel-core\/MobileLibrary\/iOS\/go-ios-build\/src\/golang.org\/x\/net\/",
-    "0EDA2F64A27382CC2BF1F85AB9A4AEECDF2AEE03+++14C946E" : "psiphon-tunnel-core\/MobileLibrary\/iOS\/go-ios-build\/src\/github.com\/Psiphon-Inc\/ratelimit\/",
-    "0559510868EA25C63F4143C7E150BF185B7A091F+++F4FA2CA" : "psiphon-tunnel-core\/MobileLibrary\/iOS\/go-ios-build\/src\/github.com\/spacemonkeygo\/spacelog\/"
-  },
-  "DVTSourceControlWorkspaceBlueprintNameKey" : "PsiphonTunnelController",
-  "DVTSourceControlWorkspaceBlueprintVersion" : 204,
-  "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "MobileLibrary\/iOS\/PsiphonTunnelController\/PsiphonTunnelController.xcodeproj",
-  "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [
-    {
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/spacemonkeygo\/spacelog",
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "0559510868EA25C63F4143C7E150BF185B7A091F+++F4FA2CA"
-    },
-    {
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/Psiphon-Inc\/ratelimit",
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "0EDA2F64A27382CC2BF1F85AB9A4AEECDF2AEE03+++14C946E"
-    },
-    {
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/go.googlesource.com\/net",
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "18F8E8F32D4612669B2AC73CE391ED54473F75DA+++0719F03"
-    },
-    {
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/go.googlesource.com\/crypto",
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "18F8E8F32D4612669B2AC73CE391ED54473F75DA+++739CA66"
-    },
-    {
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/go.googlesource.com\/mobile",
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "18F8E8F32D4612669B2AC73CE391ED54473F75DA+++D9DC38C"
-    },
-    {
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/Psiphon-Inc\/crypto",
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "18F8E8F32D4612669B2AC73CE391ED54473F75DA+++ED25EBA"
-    },
-    {
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/Psiphon-Inc\/dns",
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "3CDC0364FAC963F881E15357C59A43097543A382+++74D281F"
-    },
-    {
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:efryntov\/psiphon-tunnel-core.git",
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "654BDFA72D0BD5C4AAA375C3F6D7EA32AC58A304+++38A1A30"
-    },
-    {
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:efryntov\/psiphon-tunnel-core.git",
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "654BDFA72D0BD5C4AAA375C3F6D7EA32AC58A304+++F92F6D3"
-    },
-    {
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/Psiphon-Inc\/bolt",
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "8B61C8B48DB4B96AD94F67018EFB8A9684F3EFD6+++DCC608E"
-    },
-    {
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/spacemonkeygo\/openssl",
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "A5AD174D8B9671567C38590D166034173C866E1D+++795FDF9"
-    },
-    {
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/Psiphon-Inc\/openssl",
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "A5AD174D8B9671567C38590D166034173C866E1D+++C7AE692"
-    },
-    {
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/Psiphon-Inc\/goregen",
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "C811301B9BA5BED994AD79D58835794EFE2C4BAD+++5A1EDEB"
-    },
-    {
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/Psiphon-Inc\/goptlib",
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
-      "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "D059B056F0F8D707428CFA53B26B54CB05124896+++41B0777"
-    }
-  ]
-}

+ 0 - 22
MobileLibrary/iOS/PsiphonTunnelController/PsiphonTunnelController.xcodeproj/project.xcworkspace/xcuserdata/eugene.xcuserdatad/WorkspaceSettings.xcsettings

@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-	<key>BuildLocationStyle</key>
-	<string>CustomLocation</string>
-	<key>CustomBuildIntermediatesPath</key>
-	<string>Build/Intermediates</string>
-	<key>CustomBuildLocationType</key>
-	<string>RelativeToWorkspace</string>
-	<key>CustomBuildProductsPath</key>
-	<string>Build/Products</string>
-	<key>DerivedDataCustomLocation</key>
-	<string>/Users/eugene/Library/Developer/Xcode/DerivedData</string>
-	<key>DerivedDataLocationStyle</key>
-	<string>AbsolutePath</string>
-	<key>IssueFilterStyle</key>
-	<string>ShowActiveSchemeOnly</string>
-	<key>LiveSourceIssuesEnabled</key>
-	<true/>
-</dict>
-</plist>

+ 0 - 80
MobileLibrary/iOS/PsiphonTunnelController/PsiphonTunnelController.xcodeproj/xcuserdata/eugene.xcuserdatad/xcschemes/PsiphonTunnelController.xcscheme

@@ -1,80 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<Scheme
-   LastUpgradeVersion = "0800"
-   version = "1.3">
-   <BuildAction
-      parallelizeBuildables = "YES"
-      buildImplicitDependencies = "YES">
-      <BuildActionEntries>
-         <BuildActionEntry
-            buildForTesting = "YES"
-            buildForRunning = "YES"
-            buildForProfiling = "YES"
-            buildForArchiving = "YES"
-            buildForAnalyzing = "YES">
-            <BuildableReference
-               BuildableIdentifier = "primary"
-               BlueprintIdentifier = "44980ABC1D63A3B300B78274"
-               BuildableName = "PsiphonTunnelController.framework"
-               BlueprintName = "PsiphonTunnelController"
-               ReferencedContainer = "container:PsiphonTunnelController.xcodeproj">
-            </BuildableReference>
-         </BuildActionEntry>
-      </BuildActionEntries>
-   </BuildAction>
-   <TestAction
-      buildConfiguration = "Debug"
-      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
-      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
-      shouldUseLaunchSchemeArgsEnv = "YES">
-      <Testables>
-      </Testables>
-      <AdditionalOptions>
-      </AdditionalOptions>
-   </TestAction>
-   <LaunchAction
-      buildConfiguration = "Release"
-      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
-      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
-      launchStyle = "0"
-      useCustomWorkingDirectory = "NO"
-      ignoresPersistentStateOnLaunch = "NO"
-      debugDocumentVersioning = "YES"
-      debugServiceExtension = "internal"
-      allowLocationSimulation = "YES">
-      <MacroExpansion>
-         <BuildableReference
-            BuildableIdentifier = "primary"
-            BlueprintIdentifier = "44980ABC1D63A3B300B78274"
-            BuildableName = "PsiphonTunnelController.framework"
-            BlueprintName = "PsiphonTunnelController"
-            ReferencedContainer = "container:PsiphonTunnelController.xcodeproj">
-         </BuildableReference>
-      </MacroExpansion>
-      <AdditionalOptions>
-      </AdditionalOptions>
-   </LaunchAction>
-   <ProfileAction
-      buildConfiguration = "Release"
-      shouldUseLaunchSchemeArgsEnv = "YES"
-      savedToolIdentifier = ""
-      useCustomWorkingDirectory = "NO"
-      debugDocumentVersioning = "YES">
-      <MacroExpansion>
-         <BuildableReference
-            BuildableIdentifier = "primary"
-            BlueprintIdentifier = "44980ABC1D63A3B300B78274"
-            BuildableName = "PsiphonTunnelController.framework"
-            BlueprintName = "PsiphonTunnelController"
-            ReferencedContainer = "container:PsiphonTunnelController.xcodeproj">
-         </BuildableReference>
-      </MacroExpansion>
-   </ProfileAction>
-   <AnalyzeAction
-      buildConfiguration = "Debug">
-   </AnalyzeAction>
-   <ArchiveAction
-      buildConfiguration = "Release"
-      revealArchiveInOrganizer = "YES">
-   </ArchiveAction>
-</Scheme>

+ 0 - 22
MobileLibrary/iOS/PsiphonTunnelController/PsiphonTunnelController.xcodeproj/xcuserdata/eugene.xcuserdatad/xcschemes/xcschememanagement.plist

@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-	<key>SchemeUserState</key>
-	<dict>
-		<key>PsiphonTunnelController.xcscheme</key>
-		<dict>
-			<key>orderHint</key>
-			<integer>0</integer>
-		</dict>
-	</dict>
-	<key>SuppressBuildableAutocreation</key>
-	<dict>
-		<key>44980ABC1D63A3B300B78274</key>
-		<dict>
-			<key>primary</key>
-			<true/>
-		</dict>
-	</dict>
-</dict>
-</plist>

+ 0 - 2
MobileLibrary/iOS/PsiphonTunnelController/PsiphonTunnelController/.gitignore

@@ -1,2 +0,0 @@
-Psi.framework
-rootCAs.txt

Разница между файлами не показана из-за своего большого размера
+ 0 - 589
MobileLibrary/iOS/PsiphonTunnelController/PsiphonTunnelController/PsiphonTunnel.swift


+ 0 - 1245
MobileLibrary/iOS/PsiphonTunnelController/PsiphonTunnelController/SwiftyJSON.swift

@@ -1,1245 +0,0 @@
-//  SwiftyJSON.swift
-//
-//  Copyright (c) 2014 Ruoyu Fu, Pinglin Tang
-//
-//  Permission is hereby granted, free of charge, to any person obtaining a copy
-//  of this software and associated documentation files (the "Software"), to deal
-//  in the Software without restriction, including without limitation the rights
-//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-//  copies of the Software, and to permit persons to whom the Software is
-//  furnished to do so, subject to the following conditions:
-//
-//  The above copyright notice and this permission notice shall be included in
-//  all copies or substantial portions of the Software.
-//
-//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-//  THE SOFTWARE.
-
-import Foundation
-
-// MARK: - Error
-
-///Error domain
-public let ErrorDomain: String = "SwiftyJSONErrorDomain"
-
-///Error code
-public let ErrorUnsupportedType: Int = 999
-public let ErrorIndexOutOfBounds: Int = 900
-public let ErrorWrongType: Int = 901
-public let ErrorNotExist: Int = 500
-public let ErrorInvalidJSON: Int = 490
-
-// MARK: - JSON Type
-
-/**
-JSON's type definitions.
-
-See http://www.json.org
-*/
-public enum Type :Int{
-
-    case number
-    case string
-    case bool
-    case array
-    case dictionary
-    case null
-    case unknown
-}
-
-// MARK: - JSON Base
-
-public struct JSON {
-
-    /**
-    Creates a JSON using the data.
-
-    - parameter data:  The NSData used to convert to json.Top level object in data is an NSArray or NSDictionary
-    - parameter opt:   The JSON serialization reading options. `.AllowFragments` by default.
-    - parameter error: error The NSErrorPointer used to return the error. `nil` by default.
-
-    - returns: The created JSON
-    */
-    public init(data:Data, options opt: JSONSerialization.ReadingOptions = .allowFragments, error: NSErrorPointer? = nil) {
-        do {
-            let object: Any = try JSONSerialization.jsonObject(with: data, options: opt)
-            self.init(object)
-        } catch let aError as NSError {
-            if error != nil {
-                error??.pointee = aError
-            }
-            self.init(NSNull())
-        }
-    }
-
-    /**
-     Create a JSON from JSON string
-    - parameter string: Normal json string like '{"a":"b"}'
-
-    - returns: The created JSON
-    */
-    public static func parse(_ string:String) -> JSON {
-        return string.data(using: String.Encoding.utf8)
-            .flatMap({JSON(data: $0)}) ?? JSON(NSNull())
-    }
-
-    /**
-    Creates a JSON using the object.
-
-    - parameter object:  The object must have the following properties: All objects are NSString/String, NSNumber/Int/Float/Double/Bool, NSArray/Array, NSDictionary/Dictionary, or NSNull; All dictionary keys are NSStrings/String; NSNumbers are not NaN or infinity.
-
-    - returns: The created JSON
-    */
-    public init(_ object: Any) {
-        self.object = object
-    }
-
-    /**
-    Creates a JSON from a [JSON]
-
-    - parameter jsonArray: A Swift array of JSON objects
-
-    - returns: The created JSON
-    */
-    public init(_ jsonArray:[JSON]) {
-        self.init(jsonArray.map { $0.object })
-    }
-
-    /**
-    Creates a JSON from a [String: JSON]
-
-    - parameter jsonDictionary: A Swift dictionary of JSON objects
-
-    - returns: The created JSON
-    */
-    public init(_ jsonDictionary:[String: JSON]) {
-        var dictionary = [String: Any](minimumCapacity: jsonDictionary.count)
-        for (key, json) in jsonDictionary {
-            dictionary[key] = json.object
-        }
-        self.init(dictionary)
-    }
-
-    /// fileprivate object
-    fileprivate var rawArray: [Any] = []
-    fileprivate var rawDictionary: [String : Any] = [:]
-    fileprivate var rawString: String = ""
-    fileprivate var rawNumber: NSNumber = 0
-    fileprivate var rawNull: NSNull = NSNull()
-    fileprivate var _type: Type = .null
-    fileprivate var _error: NSError? = nil
-
-    /// Object in JSON
-    public var object: Any {
-        get {
-            switch self.type {
-            case .array:
-                return self.rawArray
-            case .dictionary:
-                return self.rawDictionary
-            case .string:
-                return self.rawString
-            case .number:
-                return self.rawNumber
-            case .bool:
-                return self.rawNumber
-            default:
-                return self.rawNull
-            }
-        }
-        set {
-            _error = nil
-            switch newValue {
-            case let number as NSNumber:
-                if number.isBool {
-                    _type = .bool
-                } else {
-                    _type = .number
-                }
-                self.rawNumber = number
-            case  let string as String:
-                _type = .string
-                self.rawString = string
-            case  _ as NSNull:
-                _type = .null
-            case let array as [Any]:
-                _type = .array
-                self.rawArray = array
-            case let dictionary as [String : Any]:
-                _type = .dictionary
-                self.rawDictionary = dictionary
-            default:
-                _type = .unknown
-                _error = NSError(domain: ErrorDomain, code: ErrorUnsupportedType, userInfo: [NSLocalizedDescriptionKey: "It is a unsupported type"])
-            }
-        }
-    }
-
-    /// json type
-    public var type: Type { get { return _type } }
-
-    /// Error in JSON
-    public var error: NSError? { get { return self._error } }
-
-    /// The static null json
-    @available(*, unavailable, renamed:"null")
-    public static var nullJSON: JSON { get { return null } }
-    public static var null: JSON { get { return JSON(NSNull()) } }
-}
-
-public enum JSONIndex:Comparable {
-    case array(Int)
-    case dictionary(DictionaryIndex<String, JSON>)
-    case null
-}
-
-public func ==(lhs: JSONIndex, rhs: JSONIndex) -> Bool {
-    switch (lhs, rhs) {
-    case (.array(let left), .array(let right)):
-        return left == right
-    case (.dictionary(let left), .dictionary(let right)):
-        return left == right
-    default:
-        return false
-    }
-}
-
-public func <(lhs: JSONIndex, rhs: JSONIndex) -> Bool {
-    switch (lhs, rhs) {
-    case (.array(let left), .array(let right)):
-        return left < right
-    case (.dictionary(let left), .dictionary(let right)):
-        return left < right
-    default:
-        return false
-    }
-}
-
-
-extension JSON: Collection {
-
-    public typealias Index = JSONIndex
-
-    public var startIndex: Index{
-        switch type {
-        case .array:
-            return .array(rawArray.startIndex)
-        case .dictionary:
-            return .dictionary(dictionaryValue.startIndex)
-        default:
-            return .null
-        }
-    }
-
-    public var endIndex: Index{
-        switch type {
-        case .array:
-            return .array(rawArray.endIndex)
-        case .dictionary:
-            return .dictionary(dictionaryValue.endIndex)
-        default:
-            return .null
-        }
-    }
-
-    public func index(after i: Index) -> Index {
-        switch i {
-        case .array(let idx):
-            return .array(rawArray.index(after: idx))
-        case .dictionary(let idx):
-            return .dictionary(dictionaryValue.index(after: idx))
-        default:
-            return .null
-        }
-
-    }
-
-    public subscript (position: Index) -> (String, JSON) {
-        switch position {
-        case .array(let idx):
-            return (String(idx), JSON(self.rawArray[idx]))
-        case .dictionary(let idx):
-            return dictionaryValue[idx]
-        default:
-            return ("", JSON.null)
-        }
-    }
-
-
-}
-
-// MARK: - Subscript
-
-/**
-*  To mark both String and Int can be used in subscript.
-*/
-public enum JSONKey {
-    case index(Int)
-    case key(String)
-}
-
-public protocol JSONSubscriptType {
-    var jsonKey:JSONKey { get }
-}
-
-extension Int: JSONSubscriptType {
-    public var jsonKey:JSONKey {
-        return JSONKey.index(self)
-    }
-}
-
-extension String: JSONSubscriptType {
-    public var jsonKey:JSONKey {
-        return JSONKey.key(self)
-    }
-}
-
-extension JSON {
-
-    /// If `type` is `.Array`, return json whose object is `array[index]`, otherwise return null json with error.
-    fileprivate subscript(index index: Int) -> JSON {
-        get {
-            if self.type != .array {
-                var r = JSON.null
-                r._error = self._error ?? NSError(domain: ErrorDomain, code: ErrorWrongType, userInfo: [NSLocalizedDescriptionKey: "Array[\(index)] failure, It is not an array"])
-                return r
-            } else if index >= 0 && index < self.rawArray.count {
-                return JSON(self.rawArray[index])
-            } else {
-                var r = JSON.null
-                r._error = NSError(domain: ErrorDomain, code:ErrorIndexOutOfBounds , userInfo: [NSLocalizedDescriptionKey: "Array[\(index)] is out of bounds"])
-                return r
-            }
-        }
-        set {
-            if self.type == .array {
-                if self.rawArray.count > index && newValue.error == nil {
-                    self.rawArray[index] = newValue.object
-                }
-            }
-        }
-    }
-
-    /// If `type` is `.Dictionary`, return json whose object is `dictionary[key]` , otherwise return null json with error.
-    fileprivate subscript(key key: String) -> JSON {
-        get {
-            var r = JSON.null
-            if self.type == .dictionary {
-                if let o = self.rawDictionary[key] {
-                    r = JSON(o)
-                } else {
-                    r._error = NSError(domain: ErrorDomain, code: ErrorNotExist, userInfo: [NSLocalizedDescriptionKey: "Dictionary[\"\(key)\"] does not exist"])
-                }
-            } else {
-                r._error = self._error ?? NSError(domain: ErrorDomain, code: ErrorWrongType, userInfo: [NSLocalizedDescriptionKey: "Dictionary[\"\(key)\"] failure, It is not an dictionary"])
-            }
-            return r
-        }
-        set {
-            if self.type == .dictionary && newValue.error == nil {
-                self.rawDictionary[key] = newValue.object
-            }
-        }
-    }
-
-    /// If `sub` is `Int`, return `subscript(index:)`; If `sub` is `String`,  return `subscript(key:)`.
-    fileprivate subscript(sub sub: JSONSubscriptType) -> JSON {
-        get {
-            switch sub.jsonKey {
-            case .index(let index): return self[index: index]
-            case .key(let key): return self[key: key]
-            }
-        }
-        set {
-            switch sub.jsonKey {
-            case .index(let index): self[index: index] = newValue
-            case .key(let key): self[key: key] = newValue
-            }
-        }
-    }
-
-    /**
-    Find a json in the complex data structuresby using the Int/String's array.
-
-    - parameter path: The target json's path. Example:
-
-    let json = JSON[data]
-    let path = [9,"list","person","name"]
-    let name = json[path]
-
-    The same as: let name = json[9]["list"]["person"]["name"]
-
-    - returns: Return a json found by the path or a null json with error
-    */
-    public subscript(path: [JSONSubscriptType]) -> JSON {
-        get {
-            return path.reduce(self) { $0[sub: $1] }
-        }
-        set {
-            switch path.count {
-            case 0:
-                return
-            case 1:
-                self[sub:path[0]].object = newValue.object
-            default:
-                var aPath = path; aPath.remove(at: 0)
-                var nextJSON = self[sub: path[0]]
-                nextJSON[aPath] = newValue
-                self[sub: path[0]] = nextJSON
-            }
-        }
-    }
-
-    /**
-    Find a json in the complex data structures by using the Int/String's array.
-
-    - parameter path: The target json's path. Example:
-
-    let name = json[9,"list","person","name"]
-
-    The same as: let name = json[9]["list"]["person"]["name"]
-
-    - returns: Return a json found by the path or a null json with error
-    */
-    public subscript(path: JSONSubscriptType...) -> JSON {
-        get {
-            return self[path]
-        }
-        set {
-            self[path] = newValue
-        }
-    }
-}
-
-// MARK: - LiteralConvertible
-
-extension JSON: Swift.StringLiteralConvertible {
-
-    public init(stringLiteral value: StringLiteralType) {
-        self.init(value)
-    }
-
-    public init(extendedGraphemeClusterLiteral value: StringLiteralType) {
-        self.init(value)
-    }
-
-    public init(unicodeScalarLiteral value: StringLiteralType) {
-        self.init(value)
-    }
-}
-
-extension JSON: Swift.IntegerLiteralConvertible {
-
-    public init(integerLiteral value: IntegerLiteralType) {
-        self.init(value)
-    }
-}
-
-extension JSON: Swift.BooleanLiteralConvertible {
-
-    public init(booleanLiteral value: BooleanLiteralType) {
-        self.init(value)
-    }
-}
-
-extension JSON: Swift.FloatLiteralConvertible {
-
-    public init(floatLiteral value: FloatLiteralType) {
-        self.init(value)
-    }
-}
-
-extension JSON: Swift.DictionaryLiteralConvertible {
-
-    public init(dictionaryLiteral elements: (String, Any)...) {
-        self.init(elements.reduce([String : Any](minimumCapacity: elements.count)){(dictionary: [String : Any], element:(String, Any)) -> [String : Any] in
-            var d = dictionary
-            d[element.0] = element.1
-            return d
-            })
-    }
-}
-
-extension JSON: Swift.ArrayLiteralConvertible {
-
-    public init(arrayLiteral elements: Any...) {
-        self.init(elements)
-    }
-}
-
-extension JSON: Swift.NilLiteralConvertible {
-
-    public init(nilLiteral: ()) {
-        self.init(NSNull())
-    }
-}
-
-// MARK: - Raw
-
-extension JSON: Swift.RawRepresentable {
-
-    public init?(rawValue: Any) {
-        if JSON(rawValue).type == .unknown {
-            return nil
-        } else {
-            self.init(rawValue)
-        }
-    }
-
-    public var rawValue: Any {
-        return self.object
-    }
-
-    public func rawData(options opt: JSONSerialization.WritingOptions = JSONSerialization.WritingOptions(rawValue: 0)) throws -> Data {
-        guard JSONSerialization.isValidJSONObject(self.object) else {
-            throw NSError(domain: ErrorDomain, code: ErrorInvalidJSON, userInfo: [NSLocalizedDescriptionKey: "JSON is invalid"])
-        }
-
-        return try JSONSerialization.data(withJSONObject: self.object, options: opt)
-    }
-
-    public func rawString(_ encoding: String.Encoding = String.Encoding.utf8, options opt: JSONSerialization.WritingOptions = .prettyPrinted) -> String? {
-        switch self.type {
-        case .array, .dictionary:
-            do {
-                let data = try self.rawData(options: opt)
-                return String(data: data, encoding: encoding)
-            } catch _ {
-                return nil
-            }
-        case .string:
-            return self.rawString
-        case .number:
-            return self.rawNumber.stringValue
-        case .bool:
-            return self.rawNumber.boolValue.description
-        case .null:
-            return "null"
-        default:
-            return nil
-        }
-    }
-}
-
-// MARK: - Printable, DebugPrintable
-
-extension JSON: Swift.CustomStringConvertible, Swift.CustomDebugStringConvertible {
-
-    public var description: String {
-        if let string = self.rawString(options:.prettyPrinted) {
-            return string
-        } else {
-            return "unknown"
-        }
-    }
-
-    public var debugDescription: String {
-        return description
-    }
-}
-
-// MARK: - Array
-
-extension JSON {
-
-    //Optional [JSON]
-    public var array: [JSON]? {
-        get {
-            if self.type == .array {
-                return self.rawArray.map{ JSON($0) }
-            } else {
-                return nil
-            }
-        }
-    }
-
-    //Non-optional [JSON]
-    public var arrayValue: [JSON] {
-        get {
-            return self.array ?? []
-        }
-    }
-
-    //Optional [Any]
-    public var arrayObject: [Any]? {
-        get {
-            switch self.type {
-            case .array:
-                return self.rawArray
-            default:
-                return nil
-            }
-        }
-        set {
-            if let array = newValue {
-                self.object = array
-            } else {
-                self.object = NSNull()
-            }
-        }
-    }
-}
-
-// MARK: - Dictionary
-
-extension JSON {
-
-    //Optional [String : JSON]
-    public var dictionary: [String : JSON]? {
-        if self.type == .dictionary {
-
-            return self.rawDictionary.reduce([String : JSON]()) { (dictionary: [String : JSON], element: (String, Any)) -> [String : JSON] in
-                var d = dictionary
-                d[element.0] = JSON(element.1)
-                return d
-            }
-        } else {
-            return nil
-        }
-    }
-
-    //Non-optional [String : JSON]
-    public var dictionaryValue: [String : JSON] {
-        return self.dictionary ?? [:]
-    }
-
-    //Optional [String : Any]
-    public var dictionaryObject: [String : Any]? {
-        get {
-            switch self.type {
-            case .dictionary:
-                return self.rawDictionary
-            default:
-                return nil
-            }
-        }
-        set {
-            if let v = newValue {
-                self.object = v
-            } else {
-                self.object = NSNull()
-            }
-        }
-    }
-}
-
-// MARK: - Bool
-
-extension JSON {
-
-    //Optional bool
-    public var bool: Bool? {
-        get {
-            switch self.type {
-            case .bool:
-                return self.rawNumber.boolValue
-            default:
-                return nil
-            }
-        }
-        set {
-            if let newValue = newValue {
-                self.object = NSNumber(value: newValue)
-            } else {
-                self.object = NSNull()
-            }
-        }
-    }
-
-    //Non-optional bool
-    public var boolValue: Bool {
-        get {
-            switch self.type {
-            case .bool, .number, .string:
-                return (self.object as AnyObject).boolValue
-            default:
-                return false
-            }
-        }
-        set {
-            self.object = NSNumber(value: newValue)
-        }
-    }
-}
-
-// MARK: - String
-
-extension JSON {
-
-    //Optional string
-    public var string: String? {
-        get {
-            switch self.type {
-            case .string:
-                return self.object as? String
-            default:
-                return nil
-            }
-        }
-        set {
-            if let newValue = newValue {
-                self.object = NSString(string:newValue)
-            } else {
-                self.object = NSNull()
-            }
-        }
-    }
-
-    //Non-optional string
-    public var stringValue: String {
-        get {
-            switch self.type {
-            case .string:
-                return self.object as? String ?? ""
-            case .number:
-                return (self.object as AnyObject).stringValue
-            case .bool:
-                return (self.object as? Bool).map { String($0) } ?? ""
-            default:
-                return ""
-            }
-        }
-        set {
-            self.object = NSString(string:newValue)
-        }
-    }
-}
-
-// MARK: - Number
-extension JSON {
-
-    //Optional number
-    public var number: NSNumber? {
-        get {
-            switch self.type {
-            case .number, .bool:
-                return self.rawNumber
-            default:
-                return nil
-            }
-        }
-        set {
-            self.object = newValue ?? NSNull()
-        }
-    }
-
-    //Non-optional number
-    public var numberValue: NSNumber {
-        get {
-            switch self.type {
-            case .string:
-                let decimal = NSDecimalNumber(string: self.object as? String)
-                if decimal == NSDecimalNumber.notANumber {  // indicates parse error
-                    return NSDecimalNumber.zero
-                }
-                return decimal
-            case .number, .bool:
-                return self.object as? NSNumber ?? NSNumber(value: 0)
-            default:
-                return NSNumber(value: 0.0)
-            }
-        }
-        set {
-            self.object = newValue
-        }
-    }
-}
-
-//MARK: - Null
-extension JSON {
-
-    public var null: NSNull? {
-        get {
-            switch self.type {
-            case .null:
-                return self.rawNull
-            default:
-                return nil
-            }
-        }
-        set {
-            self.object = NSNull()
-        }
-    }
-    public func exists() -> Bool{
-        if let errorValue = error, errorValue.code == ErrorNotExist{
-            return false
-        }
-        return true
-    }
-}
-
-//MARK: - URL
-extension JSON {
-
-    //Optional URL
-    public var URL: Foundation.URL? {
-        get {
-            switch self.type {
-            case .string:
-                if let encodedString_ = self.rawString.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) {
-                    return Foundation.URL(string: encodedString_)
-                } else {
-                    return nil
-                }
-            default:
-                return nil
-            }
-        }
-        set {
-            self.object = newValue?.absoluteString ?? NSNull()
-        }
-    }
-}
-
-// MARK: - Int, Double, Float, Int8, Int16, Int32, Int64
-
-extension JSON {
-
-    public var double: Double? {
-        get {
-            return self.number?.doubleValue
-        }
-        set {
-            if let newValue = newValue {
-                self.object = NSNumber(value: newValue)
-            } else {
-                self.object = NSNull()
-            }
-        }
-    }
-
-    public var doubleValue: Double {
-        get {
-            return self.numberValue.doubleValue
-        }
-        set {
-            self.object = NSNumber(value: newValue)
-        }
-    }
-
-    public var float: Float? {
-        get {
-            return self.number?.floatValue
-        }
-        set {
-            if let newValue = newValue {
-                self.object = NSNumber(value: newValue)
-            } else {
-                self.object = NSNull()
-            }
-        }
-    }
-
-    public var floatValue: Float {
-        get {
-            return self.numberValue.floatValue
-        }
-        set {
-            self.object = NSNumber(value: newValue)
-        }
-    }
-
-    public var int: Int? {
-        get {
-            return self.number?.intValue
-        }
-        set {
-            if let newValue = newValue {
-                self.object = NSNumber(value: newValue)
-            } else {
-                self.object = NSNull()
-            }
-        }
-    }
-
-    public var intValue: Int {
-        get {
-            return self.numberValue.intValue
-        }
-        set {
-            self.object = NSNumber(value: newValue)
-        }
-    }
-
-    public var uInt: UInt? {
-        get {
-            return self.number?.uintValue
-        }
-        set {
-            if let newValue = newValue {
-                self.object = NSNumber(value: newValue)
-            } else {
-                self.object = NSNull()
-            }
-        }
-    }
-
-    public var uIntValue: UInt {
-        get {
-            return self.numberValue.uintValue
-        }
-        set {
-            self.object = NSNumber(value: newValue)
-        }
-    }
-
-    public var int8: Int8? {
-        get {
-            return self.number?.int8Value
-        }
-        set {
-            if let newValue = newValue {
-                self.object = NSNumber(value: newValue)
-            } else {
-                self.object =  NSNull()
-            }
-        }
-    }
-
-    public var int8Value: Int8 {
-        get {
-            return self.numberValue.int8Value
-        }
-        set {
-            self.object = NSNumber(value: newValue)
-        }
-    }
-
-    public var uInt8: UInt8? {
-        get {
-            return self.number?.uint8Value
-        }
-        set {
-            if let newValue = newValue {
-                self.object = NSNumber(value: newValue)
-            } else {
-                self.object =  NSNull()
-            }
-        }
-    }
-
-    public var uInt8Value: UInt8 {
-        get {
-            return self.numberValue.uint8Value
-        }
-        set {
-            self.object = NSNumber(value: newValue)
-        }
-    }
-
-    public var int16: Int16? {
-        get {
-            return self.number?.int16Value
-        }
-        set {
-            if let newValue = newValue {
-                self.object = NSNumber(value: newValue)
-            } else {
-                self.object =  NSNull()
-            }
-        }
-    }
-
-    public var int16Value: Int16 {
-        get {
-            return self.numberValue.int16Value
-        }
-        set {
-            self.object = NSNumber(value: newValue)
-        }
-    }
-
-    public var uInt16: UInt16? {
-        get {
-            return self.number?.uint16Value
-        }
-        set {
-            if let newValue = newValue {
-                self.object = NSNumber(value: newValue)
-            } else {
-                self.object =  NSNull()
-            }
-        }
-    }
-
-    public var uInt16Value: UInt16 {
-        get {
-            return self.numberValue.uint16Value
-        }
-        set {
-            self.object = NSNumber(value: newValue)
-        }
-    }
-
-    public var int32: Int32? {
-        get {
-            return self.number?.int32Value
-        }
-        set {
-            if let newValue = newValue {
-                self.object = NSNumber(value: newValue)
-            } else {
-                self.object =  NSNull()
-            }
-        }
-    }
-
-    public var int32Value: Int32 {
-        get {
-            return self.numberValue.int32Value
-        }
-        set {
-            self.object = NSNumber(value: newValue)
-        }
-    }
-
-    public var uInt32: UInt32? {
-        get {
-            return self.number?.uint32Value
-        }
-        set {
-            if let newValue = newValue {
-                self.object = NSNumber(value: newValue)
-            } else {
-                self.object =  NSNull()
-            }
-        }
-    }
-
-    public var uInt32Value: UInt32 {
-        get {
-            return self.numberValue.uint32Value
-        }
-        set {
-            self.object = NSNumber(value: newValue)
-        }
-    }
-
-    public var int64: Int64? {
-        get {
-            return self.number?.int64Value
-        }
-        set {
-            if let newValue = newValue {
-                self.object = NSNumber(value: newValue)
-            } else {
-                self.object =  NSNull()
-            }
-        }
-    }
-
-    public var int64Value: Int64 {
-        get {
-            return self.numberValue.int64Value
-        }
-        set {
-            self.object = NSNumber(value: newValue)
-        }
-    }
-
-    public var uInt64: UInt64? {
-        get {
-            return self.number?.uint64Value
-        }
-        set {
-            if let newValue = newValue {
-                self.object = NSNumber(value: newValue)
-            } else {
-                self.object =  NSNull()
-            }
-        }
-    }
-
-    public var uInt64Value: UInt64 {
-        get {
-            return self.numberValue.uint64Value
-        }
-        set {
-            self.object = NSNumber(value: newValue)
-        }
-    }
-}
-
-//MARK: - Comparable
-extension JSON : Swift.Comparable {}
-
-public func ==(lhs: JSON, rhs: JSON) -> Bool {
-
-    switch (lhs.type, rhs.type) {
-    case (.number, .number):
-        return lhs.rawNumber == rhs.rawNumber
-    case (.string, .string):
-        return lhs.rawString == rhs.rawString
-    case (.bool, .bool):
-        return lhs.rawNumber.boolValue == rhs.rawNumber.boolValue
-    case (.array, .array):
-        return lhs.rawArray as NSArray == rhs.rawArray as NSArray
-    case (.dictionary, .dictionary):
-        return lhs.rawDictionary as NSDictionary == rhs.rawDictionary as NSDictionary
-    case (.null, .null):
-        return true
-    default:
-        return false
-    }
-}
-
-public func <=(lhs: JSON, rhs: JSON) -> Bool {
-
-    switch (lhs.type, rhs.type) {
-    case (.number, .number):
-        return lhs.rawNumber <= rhs.rawNumber
-    case (.string, .string):
-        return lhs.rawString <= rhs.rawString
-    case (.bool, .bool):
-        return lhs.rawNumber.boolValue == rhs.rawNumber.boolValue
-    case (.array, .array):
-        return lhs.rawArray as NSArray == rhs.rawArray as NSArray
-    case (.dictionary, .dictionary):
-        return lhs.rawDictionary as NSDictionary == rhs.rawDictionary as NSDictionary
-    case (.null, .null):
-        return true
-    default:
-        return false
-    }
-}
-
-public func >=(lhs: JSON, rhs: JSON) -> Bool {
-
-    switch (lhs.type, rhs.type) {
-    case (.number, .number):
-        return lhs.rawNumber >= rhs.rawNumber
-    case (.string, .string):
-        return lhs.rawString >= rhs.rawString
-    case (.bool, .bool):
-        return lhs.rawNumber.boolValue == rhs.rawNumber.boolValue
-    case (.array, .array):
-        return lhs.rawArray as NSArray == rhs.rawArray as NSArray
-    case (.dictionary, .dictionary):
-        return lhs.rawDictionary as NSDictionary == rhs.rawDictionary as NSDictionary
-    case (.null, .null):
-        return true
-    default:
-        return false
-    }
-}
-
-public func >(lhs: JSON, rhs: JSON) -> Bool {
-
-    switch (lhs.type, rhs.type) {
-    case (.number, .number):
-        return lhs.rawNumber > rhs.rawNumber
-    case (.string, .string):
-        return lhs.rawString > rhs.rawString
-    default:
-        return false
-    }
-}
-
-public func <(lhs: JSON, rhs: JSON) -> Bool {
-
-    switch (lhs.type, rhs.type) {
-    case (.number, .number):
-        return lhs.rawNumber < rhs.rawNumber
-    case (.string, .string):
-        return lhs.rawString < rhs.rawString
-    default:
-        return false
-    }
-}
-
-fileprivate let trueNumber = NSNumber(value: true)
-fileprivate let falseNumber = NSNumber(value: false)
-fileprivate let trueObjCType = String(cString: trueNumber.objCType)
-fileprivate let falseObjCType = String(cString: falseNumber.objCType)
-
-
-//A C++ bool or a C99 _Bool
-//Do Not Know Why self.objCType is "B", maybe a bug.
-fileprivate let cppBoolType = "B"
-
-// MARK: - NSNumber: Comparable
-
-extension NSNumber {
-    var isBool:Bool {
-        get {
-
-            let objCType = String(cString: self.objCType)
-
-            if (self.compare(trueNumber) == ComparisonResult.orderedSame && (objCType == trueObjCType || objCType == cppBoolType))
-                || (self.compare(falseNumber) == ComparisonResult.orderedSame && (objCType == falseObjCType || objCType == cppBoolType)){
-                    return true
-            } else {
-                return false
-            }
-        }
-    }
-}
-
-func ==(lhs: NSNumber, rhs: NSNumber) -> Bool {
-    switch (lhs.isBool, rhs.isBool) {
-    case (false, true):
-        return false
-    case (true, false):
-        return false
-    default:
-        return lhs.compare(rhs) == ComparisonResult.orderedSame
-    }
-}
-
-func !=(lhs: NSNumber, rhs: NSNumber) -> Bool {
-    return !(lhs == rhs)
-}
-
-func <(lhs: NSNumber, rhs: NSNumber) -> Bool {
-
-    switch (lhs.isBool, rhs.isBool) {
-    case (false, true):
-        return false
-    case (true, false):
-        return false
-    default:
-        return lhs.compare(rhs) == ComparisonResult.orderedAscending
-    }
-}
-
-func >(lhs: NSNumber, rhs: NSNumber) -> Bool {
-
-    switch (lhs.isBool, rhs.isBool) {
-    case (false, true):
-        return false
-    case (true, false):
-        return false
-    default:
-        return lhs.compare(rhs) == ComparisonResult.orderedDescending
-    }
-}
-
-func <=(lhs: NSNumber, rhs: NSNumber) -> Bool {
-
-    switch (lhs.isBool, rhs.isBool) {
-    case (false, true):
-        return false
-    case (true, false):
-        return false
-    default:
-        return lhs.compare(rhs) != ComparisonResult.orderedDescending
-    }
-}
-
-func >=(lhs: NSNumber, rhs: NSNumber) -> Bool {
-
-    switch (lhs.isBool, rhs.isBool) {
-    case (false, true):
-        return false
-    case (true, false):
-        return false
-    default:
-        return lhs.compare(rhs) != ComparisonResult.orderedAscending
-    }
-}

+ 5 - 30
MobileLibrary/iOS/README.md

@@ -1,40 +1,15 @@
-##Psiphon iOS Library README
+# Psiphon iOS Library Meta-README
 
-###Overview
+## Usage
 
-Psiphon Library for iOS enables you to easily embed Psiphon in your iOS
-app. The Psiphon Library for iOS is implemented in Go and follows the standard
-conventions for using a Go library in an iOS app.
+If you are using the Library in your app, please read the [USAGE.md](USAGE.md) instructions.
 
-###Building
-
-####Prerequisites
-
-* xcode `xcode-select --install`
-* [git](https://git-scm.com/download/mac)
-* homebrew
-  * Install from terminal: `/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"`
-* golang
-  * Install from terminal: `brew install go`
-
-####Build Steps
-
-* run `build-psiphon-framework.sh`
-
-###Using the Library and Sample Apps
-
-Coming soon
-
-* `$(PROJECT_DIR)/PsiphonTunnelController.framework` (or wherever the framework is located) must be added to the project's "Framework Search Paths" setting.
-* Add framework as Embedded Binary in General settings.
-* New user-defined value in project settings: `STRIP_BITCODE_FROM_COPIED_FILES: NO`.
-
-###Acknowledgements
+## Acknowledgements
 
 Psiphon iOS Library uses:
 * [OpenSSL-for-iPhone](https://github.com/x2on/OpenSSL-for-iPhone)
 
-####OpenSSL-for-iPhone Changes
+### OpenSSL-for-iPhone Changes
 
 `build-libssl.sh` rebuilds openssl on every run.  Modifications were made to
 not run unless required, they are:

+ 579 - 0
MobileLibrary/iOS/SampleApps/TunneledWebRequest/TunneledWebRequest.xcodeproj/project.pbxproj

@@ -0,0 +1,579 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 46;
+	objects = {
+
+/* Begin PBXBuildFile section */
+		662658EE1DCB8CF300872F6C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 662658ED1DCB8CF300872F6C /* AppDelegate.swift */; };
+		662658F01DCB8CF300872F6C /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 662658EF1DCB8CF300872F6C /* ViewController.swift */; };
+		662658F31DCB8CF300872F6C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 662658F11DCB8CF300872F6C /* Main.storyboard */; };
+		662658F51DCB8CF300872F6C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 662658F41DCB8CF300872F6C /* Assets.xcassets */; };
+		662658F81DCB8CF300872F6C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 662658F61DCB8CF300872F6C /* LaunchScreen.storyboard */; };
+		662659031DCB8CF400872F6C /* TunneledWebRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 662659021DCB8CF400872F6C /* TunneledWebRequestTests.swift */; };
+		6626590E1DCB8CF400872F6C /* TunneledWebRequestUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6626590D1DCB8CF400872F6C /* TunneledWebRequestUITests.swift */; };
+		662659211DCBC7C300872F6C /* PsiphonTunnel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 662659201DCBC7C300872F6C /* PsiphonTunnel.framework */; };
+		662659231DCBC8D800872F6C /* PsiphonTunnel.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 662659201DCBC7C300872F6C /* PsiphonTunnel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+		6688DBB61DCD684B00721A9E /* psiphon-config.json in Resources */ = {isa = PBXBuildFile; fileRef = 6688DBB51DCD684B00721A9E /* psiphon-config.json */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+		662658FF1DCB8CF400872F6C /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = 662658E21DCB8CF300872F6C /* Project object */;
+			proxyType = 1;
+			remoteGlobalIDString = 662658E91DCB8CF300872F6C;
+			remoteInfo = TunneledWebRequest;
+		};
+		6626590A1DCB8CF400872F6C /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = 662658E21DCB8CF300872F6C /* Project object */;
+			proxyType = 1;
+			remoteGlobalIDString = 662658E91DCB8CF300872F6C;
+			remoteInfo = TunneledWebRequest;
+		};
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+		662659221DCBC8CB00872F6C /* CopyFiles */ = {
+			isa = PBXCopyFilesBuildPhase;
+			buildActionMask = 2147483647;
+			dstPath = "";
+			dstSubfolderSpec = 10;
+			files = (
+				662659231DCBC8D800872F6C /* PsiphonTunnel.framework in CopyFiles */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+		662658EA1DCB8CF300872F6C /* TunneledWebRequest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TunneledWebRequest.app; sourceTree = BUILT_PRODUCTS_DIR; };
+		662658ED1DCB8CF300872F6C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
+		662658EF1DCB8CF300872F6C /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
+		662658F21DCB8CF300872F6C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
+		662658F41DCB8CF300872F6C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
+		662658F71DCB8CF300872F6C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
+		662658F91DCB8CF300872F6C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+		662658FE1DCB8CF400872F6C /* TunneledWebRequestTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TunneledWebRequestTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+		662659021DCB8CF400872F6C /* TunneledWebRequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunneledWebRequestTests.swift; sourceTree = "<group>"; };
+		662659041DCB8CF400872F6C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+		662659091DCB8CF400872F6C /* TunneledWebRequestUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TunneledWebRequestUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+		6626590D1DCB8CF400872F6C /* TunneledWebRequestUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunneledWebRequestUITests.swift; sourceTree = "<group>"; };
+		6626590F1DCB8CF400872F6C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+		662659201DCBC7C300872F6C /* PsiphonTunnel.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = PsiphonTunnel.framework; sourceTree = "<group>"; };
+		6688DBB51DCD684B00721A9E /* psiphon-config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "psiphon-config.json"; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+		662658E71DCB8CF300872F6C /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				662659211DCBC7C300872F6C /* PsiphonTunnel.framework in Frameworks */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		662658FB1DCB8CF400872F6C /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		662659061DCB8CF400872F6C /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+		662658E11DCB8CF300872F6C = {
+			isa = PBXGroup;
+			children = (
+				662658EC1DCB8CF300872F6C /* TunneledWebRequest */,
+				662659011DCB8CF400872F6C /* TunneledWebRequestTests */,
+				6626590C1DCB8CF400872F6C /* TunneledWebRequestUITests */,
+				662658EB1DCB8CF300872F6C /* Products */,
+			);
+			sourceTree = "<group>";
+		};
+		662658EB1DCB8CF300872F6C /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				662658EA1DCB8CF300872F6C /* TunneledWebRequest.app */,
+				662658FE1DCB8CF400872F6C /* TunneledWebRequestTests.xctest */,
+				662659091DCB8CF400872F6C /* TunneledWebRequestUITests.xctest */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		662658EC1DCB8CF300872F6C /* TunneledWebRequest */ = {
+			isa = PBXGroup;
+			children = (
+				662658ED1DCB8CF300872F6C /* AppDelegate.swift */,
+				662658EF1DCB8CF300872F6C /* ViewController.swift */,
+				662658F11DCB8CF300872F6C /* Main.storyboard */,
+				662658F41DCB8CF300872F6C /* Assets.xcassets */,
+				662658F61DCB8CF300872F6C /* LaunchScreen.storyboard */,
+				662658F91DCB8CF300872F6C /* Info.plist */,
+				6688DBB51DCD684B00721A9E /* psiphon-config.json */,
+				662659201DCBC7C300872F6C /* PsiphonTunnel.framework */,
+			);
+			path = TunneledWebRequest;
+			sourceTree = "<group>";
+		};
+		662659011DCB8CF400872F6C /* TunneledWebRequestTests */ = {
+			isa = PBXGroup;
+			children = (
+				662659021DCB8CF400872F6C /* TunneledWebRequestTests.swift */,
+				662659041DCB8CF400872F6C /* Info.plist */,
+			);
+			path = TunneledWebRequestTests;
+			sourceTree = "<group>";
+		};
+		6626590C1DCB8CF400872F6C /* TunneledWebRequestUITests */ = {
+			isa = PBXGroup;
+			children = (
+				6626590D1DCB8CF400872F6C /* TunneledWebRequestUITests.swift */,
+				6626590F1DCB8CF400872F6C /* Info.plist */,
+			);
+			path = TunneledWebRequestUITests;
+			sourceTree = "<group>";
+		};
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+		662658E91DCB8CF300872F6C /* TunneledWebRequest */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 662659121DCB8CF400872F6C /* Build configuration list for PBXNativeTarget "TunneledWebRequest" */;
+			buildPhases = (
+				662658E61DCB8CF300872F6C /* Sources */,
+				662658E71DCB8CF300872F6C /* Frameworks */,
+				662658E81DCB8CF300872F6C /* Resources */,
+				662659221DCBC8CB00872F6C /* CopyFiles */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = TunneledWebRequest;
+			productName = TunneledWebRequest;
+			productReference = 662658EA1DCB8CF300872F6C /* TunneledWebRequest.app */;
+			productType = "com.apple.product-type.application";
+		};
+		662658FD1DCB8CF400872F6C /* TunneledWebRequestTests */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 662659151DCB8CF400872F6C /* Build configuration list for PBXNativeTarget "TunneledWebRequestTests" */;
+			buildPhases = (
+				662658FA1DCB8CF400872F6C /* Sources */,
+				662658FB1DCB8CF400872F6C /* Frameworks */,
+				662658FC1DCB8CF400872F6C /* Resources */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+				662659001DCB8CF400872F6C /* PBXTargetDependency */,
+			);
+			name = TunneledWebRequestTests;
+			productName = TunneledWebRequestTests;
+			productReference = 662658FE1DCB8CF400872F6C /* TunneledWebRequestTests.xctest */;
+			productType = "com.apple.product-type.bundle.unit-test";
+		};
+		662659081DCB8CF400872F6C /* TunneledWebRequestUITests */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 662659181DCB8CF400872F6C /* Build configuration list for PBXNativeTarget "TunneledWebRequestUITests" */;
+			buildPhases = (
+				662659051DCB8CF400872F6C /* Sources */,
+				662659061DCB8CF400872F6C /* Frameworks */,
+				662659071DCB8CF400872F6C /* Resources */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+				6626590B1DCB8CF400872F6C /* PBXTargetDependency */,
+			);
+			name = TunneledWebRequestUITests;
+			productName = TunneledWebRequestUITests;
+			productReference = 662659091DCB8CF400872F6C /* TunneledWebRequestUITests.xctest */;
+			productType = "com.apple.product-type.bundle.ui-testing";
+		};
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+		662658E21DCB8CF300872F6C /* Project object */ = {
+			isa = PBXProject;
+			attributes = {
+				LastSwiftUpdateCheck = 0800;
+				LastUpgradeCheck = 0810;
+				ORGANIZATIONNAME = "Psiphon Inc.";
+				TargetAttributes = {
+					662658E91DCB8CF300872F6C = {
+						CreatedOnToolsVersion = 8.0;
+						DevelopmentTeam = Q6HLNEX92A;
+						ProvisioningStyle = Automatic;
+					};
+					662658FD1DCB8CF400872F6C = {
+						CreatedOnToolsVersion = 8.0;
+						DevelopmentTeam = Q6HLNEX92A;
+						ProvisioningStyle = Automatic;
+						TestTargetID = 662658E91DCB8CF300872F6C;
+					};
+					662659081DCB8CF400872F6C = {
+						CreatedOnToolsVersion = 8.0;
+						DevelopmentTeam = Q6HLNEX92A;
+						ProvisioningStyle = Automatic;
+						TestTargetID = 662658E91DCB8CF300872F6C;
+					};
+				};
+			};
+			buildConfigurationList = 662658E51DCB8CF300872F6C /* Build configuration list for PBXProject "TunneledWebRequest" */;
+			compatibilityVersion = "Xcode 3.2";
+			developmentRegion = English;
+			hasScannedForEncodings = 0;
+			knownRegions = (
+				en,
+				Base,
+			);
+			mainGroup = 662658E11DCB8CF300872F6C;
+			productRefGroup = 662658EB1DCB8CF300872F6C /* Products */;
+			projectDirPath = "";
+			projectRoot = "";
+			targets = (
+				662658E91DCB8CF300872F6C /* TunneledWebRequest */,
+				662658FD1DCB8CF400872F6C /* TunneledWebRequestTests */,
+				662659081DCB8CF400872F6C /* TunneledWebRequestUITests */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+		662658E81DCB8CF300872F6C /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				662658F81DCB8CF300872F6C /* LaunchScreen.storyboard in Resources */,
+				662658F51DCB8CF300872F6C /* Assets.xcassets in Resources */,
+				662658F31DCB8CF300872F6C /* Main.storyboard in Resources */,
+				6688DBB61DCD684B00721A9E /* psiphon-config.json in Resources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		662658FC1DCB8CF400872F6C /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		662659071DCB8CF400872F6C /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+		662658E61DCB8CF300872F6C /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				662658F01DCB8CF300872F6C /* ViewController.swift in Sources */,
+				662658EE1DCB8CF300872F6C /* AppDelegate.swift in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		662658FA1DCB8CF400872F6C /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				662659031DCB8CF400872F6C /* TunneledWebRequestTests.swift in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		662659051DCB8CF400872F6C /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				6626590E1DCB8CF400872F6C /* TunneledWebRequestUITests.swift in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+		662659001DCB8CF400872F6C /* PBXTargetDependency */ = {
+			isa = PBXTargetDependency;
+			target = 662658E91DCB8CF300872F6C /* TunneledWebRequest */;
+			targetProxy = 662658FF1DCB8CF400872F6C /* PBXContainerItemProxy */;
+		};
+		6626590B1DCB8CF400872F6C /* PBXTargetDependency */ = {
+			isa = PBXTargetDependency;
+			target = 662658E91DCB8CF300872F6C /* TunneledWebRequest */;
+			targetProxy = 6626590A1DCB8CF400872F6C /* PBXContainerItemProxy */;
+		};
+/* End PBXTargetDependency section */
+
+/* Begin PBXVariantGroup section */
+		662658F11DCB8CF300872F6C /* Main.storyboard */ = {
+			isa = PBXVariantGroup;
+			children = (
+				662658F21DCB8CF300872F6C /* Base */,
+			);
+			name = Main.storyboard;
+			sourceTree = "<group>";
+		};
+		662658F61DCB8CF300872F6C /* LaunchScreen.storyboard */ = {
+			isa = PBXVariantGroup;
+			children = (
+				662658F71DCB8CF300872F6C /* Base */,
+			);
+			name = LaunchScreen.storyboard;
+			sourceTree = "<group>";
+		};
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+		662659101DCB8CF400872F6C /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_SUSPICIOUS_MOVES = YES;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = dwarf;
+				ENABLE_BITCODE = YES;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				ENABLE_TESTABILITY = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_DYNAMIC_NO_PIC = NO;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_OPTIMIZATION_LEVEL = 0;
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"DEBUG=1",
+					"$(inherited)",
+				);
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				IPHONEOS_DEPLOYMENT_TARGET = 9.3;
+				MTL_ENABLE_DEBUG_INFO = YES;
+				ONLY_ACTIVE_ARCH = YES;
+				SDKROOT = iphoneos;
+				SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+				TARGETED_DEVICE_FAMILY = "1,2";
+			};
+			name = Debug;
+		};
+		662659111DCB8CF400872F6C /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_SUSPICIOUS_MOVES = YES;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_BITCODE = YES;
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				IPHONEOS_DEPLOYMENT_TARGET = 9.3;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				SDKROOT = iphoneos;
+				SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
+				TARGETED_DEVICE_FAMILY = "1,2";
+				VALIDATE_PRODUCT = YES;
+			};
+			name = Release;
+		};
+		662659131DCB8CF400872F6C /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				DEVELOPMENT_TEAM = Q6HLNEX92A;
+				ENABLE_BITCODE = NO;
+				FRAMEWORK_SEARCH_PATHS = (
+					"$(inherited)",
+					"$(PROJECT_DIR)/TunneledWebRequest",
+				);
+				INFOPLIST_FILE = TunneledWebRequest/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+				PRODUCT_BUNDLE_IDENTIFIER = com.psiphon3.ios.TunneledWebRequest;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				STRIP_BITCODE_FROM_COPIED_FILES = NO;
+				SWIFT_VERSION = 3.0;
+			};
+			name = Debug;
+		};
+		662659141DCB8CF400872F6C /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				DEVELOPMENT_TEAM = Q6HLNEX92A;
+				ENABLE_BITCODE = NO;
+				FRAMEWORK_SEARCH_PATHS = (
+					"$(inherited)",
+					"$(PROJECT_DIR)/TunneledWebRequest",
+				);
+				INFOPLIST_FILE = TunneledWebRequest/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+				PRODUCT_BUNDLE_IDENTIFIER = com.psiphon3.ios.TunneledWebRequest;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				STRIP_BITCODE_FROM_COPIED_FILES = NO;
+				SWIFT_VERSION = 3.0;
+			};
+			name = Release;
+		};
+		662659161DCB8CF400872F6C /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
+				BUNDLE_LOADER = "$(TEST_HOST)";
+				DEVELOPMENT_TEAM = Q6HLNEX92A;
+				INFOPLIST_FILE = TunneledWebRequestTests/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+				PRODUCT_BUNDLE_IDENTIFIER = com.psiphon3.ios.TunneledWebRequestTests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_VERSION = 3.0;
+				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TunneledWebRequest.app/TunneledWebRequest";
+			};
+			name = Debug;
+		};
+		662659171DCB8CF400872F6C /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
+				BUNDLE_LOADER = "$(TEST_HOST)";
+				DEVELOPMENT_TEAM = Q6HLNEX92A;
+				INFOPLIST_FILE = TunneledWebRequestTests/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+				PRODUCT_BUNDLE_IDENTIFIER = com.psiphon3.ios.TunneledWebRequestTests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_VERSION = 3.0;
+				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TunneledWebRequest.app/TunneledWebRequest";
+			};
+			name = Release;
+		};
+		662659191DCB8CF400872F6C /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
+				DEVELOPMENT_TEAM = Q6HLNEX92A;
+				INFOPLIST_FILE = TunneledWebRequestUITests/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+				PRODUCT_BUNDLE_IDENTIFIER = com.psiphon3.ios.TunneledWebRequestUITests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_VERSION = 3.0;
+				TEST_TARGET_NAME = TunneledWebRequest;
+			};
+			name = Debug;
+		};
+		6626591A1DCB8CF400872F6C /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
+				DEVELOPMENT_TEAM = Q6HLNEX92A;
+				INFOPLIST_FILE = TunneledWebRequestUITests/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+				PRODUCT_BUNDLE_IDENTIFIER = com.psiphon3.ios.TunneledWebRequestUITests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_VERSION = 3.0;
+				TEST_TARGET_NAME = TunneledWebRequest;
+			};
+			name = Release;
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		662658E51DCB8CF300872F6C /* Build configuration list for PBXProject "TunneledWebRequest" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				662659101DCB8CF400872F6C /* Debug */,
+				662659111DCB8CF400872F6C /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		662659121DCB8CF400872F6C /* Build configuration list for PBXNativeTarget "TunneledWebRequest" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				662659131DCB8CF400872F6C /* Debug */,
+				662659141DCB8CF400872F6C /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		662659151DCB8CF400872F6C /* Build configuration list for PBXNativeTarget "TunneledWebRequestTests" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				662659161DCB8CF400872F6C /* Debug */,
+				662659171DCB8CF400872F6C /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		662659181DCB8CF400872F6C /* Build configuration list for PBXNativeTarget "TunneledWebRequestUITests" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				662659191DCB8CF400872F6C /* Debug */,
+				6626591A1DCB8CF400872F6C /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+/* End XCConfigurationList section */
+	};
+	rootObject = 662658E21DCB8CF300872F6C /* Project object */;
+}

+ 7 - 0
MobileLibrary/iOS/SampleApps/TunneledWebRequest/TunneledWebRequest.xcodeproj/project.xcworkspace/contents.xcworkspacedata

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "self:TunneledWebRequest.xcodeproj">
+   </FileRef>
+</Workspace>

+ 43 - 0
MobileLibrary/iOS/SampleApps/TunneledWebRequest/TunneledWebRequest/AppDelegate.swift

@@ -0,0 +1,43 @@
+//
+//  AppDelegate.swift
+//  TunneledWebRequest
+//
+
+import UIKit
+
+@UIApplicationMain
+class AppDelegate: UIResponder, UIApplicationDelegate {
+
+    var window: UIWindow?
+
+
+    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
+        // Override point for customization after application launch.
+        return true
+    }
+
+    func applicationWillResignActive(_ application: UIApplication) {
+        // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
+        // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
+    }
+
+    func applicationDidEnterBackground(_ application: UIApplication) {
+        // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
+        // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
+    }
+
+    func applicationWillEnterForeground(_ application: UIApplication) {
+        // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
+    }
+
+    func applicationDidBecomeActive(_ application: UIApplication) {
+        // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
+    }
+
+    func applicationWillTerminate(_ application: UIApplication) {
+        // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
+    }
+
+
+}
+

+ 68 - 0
MobileLibrary/iOS/SampleApps/TunneledWebRequest/TunneledWebRequest/Assets.xcassets/AppIcon.appiconset/Contents.json

@@ -0,0 +1,68 @@
+{
+  "images" : [
+    {
+      "idiom" : "iphone",
+      "size" : "29x29",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "iphone",
+      "size" : "29x29",
+      "scale" : "3x"
+    },
+    {
+      "idiom" : "iphone",
+      "size" : "40x40",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "iphone",
+      "size" : "40x40",
+      "scale" : "3x"
+    },
+    {
+      "idiom" : "iphone",
+      "size" : "60x60",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "iphone",
+      "size" : "60x60",
+      "scale" : "3x"
+    },
+    {
+      "idiom" : "ipad",
+      "size" : "29x29",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "ipad",
+      "size" : "29x29",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "ipad",
+      "size" : "40x40",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "ipad",
+      "size" : "40x40",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "ipad",
+      "size" : "76x76",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "ipad",
+      "size" : "76x76",
+      "scale" : "2x"
+    }
+  ],
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  }
+}

+ 27 - 0
MobileLibrary/iOS/SampleApps/TunneledWebRequest/TunneledWebRequest/Base.lproj/LaunchScreen.storyboard

@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11134" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
+    <dependencies>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11106"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <scenes>
+        <!--View Controller-->
+        <scene sceneID="EHf-IW-A2E">
+            <objects>
+                <viewController id="01J-lp-oVM" sceneMemberID="viewController">
+                    <layoutGuides>
+                        <viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/>
+                        <viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
+                    </layoutGuides>
+                    <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
+                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                    </view>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="53" y="375"/>
+        </scene>
+    </scenes>
+</document>

+ 26 - 0
MobileLibrary/iOS/SampleApps/TunneledWebRequest/TunneledWebRequest/Base.lproj/Main.storyboard

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11134" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
+    <dependencies>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11106"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <scenes>
+        <!--View Controller-->
+        <scene sceneID="tne-QT-ifu">
+            <objects>
+                <viewController id="BYZ-38-t0r" customClass="ViewController" customModuleProvider="target" sceneMemberID="viewController">
+                    <layoutGuides>
+                        <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
+                        <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
+                    </layoutGuides>
+                    <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
+                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                    </view>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
+            </objects>
+        </scene>
+    </scenes>
+</document>

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

@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>en</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>$(PRODUCT_NAME)</string>
+	<key>CFBundlePackageType</key>
+	<string>APPL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.0</string>
+	<key>CFBundleVersion</key>
+	<string>1</string>
+	<key>LSRequiresIPhoneOS</key>
+	<true/>
+	<key>UILaunchStoryboardName</key>
+	<string>LaunchScreen</string>
+	<key>UIMainStoryboardFile</key>
+	<string>Main</string>
+	<key>UIRequiredDeviceCapabilities</key>
+	<array>
+		<string>armv7</string>
+	</array>
+	<key>UISupportedInterfaceOrientations</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+	</array>
+	<key>UISupportedInterfaceOrientations~ipad</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationPortraitUpsideDown</string>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+	</array>
+</dict>
+</plist>

+ 210 - 0
MobileLibrary/iOS/SampleApps/TunneledWebRequest/TunneledWebRequest/ViewController.swift

@@ -0,0 +1,210 @@
+//
+//  ViewController.swift
+//  TunneledWebView
+//
+
+import UIKit
+
+import PsiphonTunnel
+
+class ViewController: UIViewController {
+    
+    var webView: UIWebView!
+    
+    // The instance of PsiphonTunnel we'll use for connecting.
+    var psiphonTunnel: PsiphonTunnel?
+    
+    // This are the ports that we can proxy through.
+    var socksProxyPort = -1
+    var httpProxyPort = -1
+    
+    override func loadView() {
+        // Make our whole view the webview.
+        webView = UIWebView()
+        view = webView
+        
+        self.psiphonTunnel = PsiphonTunnel.newPsiphonTunnel(self)
+    }
+    
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        // Do any additional setup after loading the view, typically from a nib.
+        
+        // Start up the tunnel and begin connecting.
+        // This could be started elsewhere or earlier.
+        NSLog("Starting tunnel")
+        
+        let embeddedServerEntries = ""
+        guard let success = self.psiphonTunnel?.start(embeddedServerEntries), success else {
+            NSLog("psiphonTunnel.start returned false")
+            return
+        }
+    }
+    
+    override func didReceiveMemoryWarning() {
+        super.didReceiveMemoryWarning()
+        // Dispose of any resources that can be recreated.
+    }
+}
+
+// MARK: TunneledAppDelegate implementation
+// See the protocol definition for details about the functions.
+extension ViewController: TunneledAppDelegate {
+    func getPsiphonConfig() -> String? {
+        // In this example, we're going to retrieve our Psiphon config from a file in the app bundle.
+        // Alternatively, it could be a string literal in the code, or whatever makes sense.
+        
+        guard let psiphonConfigUrl = Bundle.main.url(forResource: "psiphon-config", withExtension: "json") else {
+            NSLog("Error getting Psiphon config resource file URL!")
+            return nil
+        }
+        
+        do {
+            return try String.init(contentsOf: psiphonConfigUrl)
+        } catch {
+            NSLog("Error getting Psiphon config resource file URL!")
+            return nil
+        }
+    }
+    
+    func onDiagnosticMessage(_ message: String) {
+        NSLog("onDiagnosticMessage: %@", message)
+    }
+    
+    func onConnecting() {
+        NSLog("onConnecting")
+    }
+    
+    func onConnected() {
+        NSLog("onConnected")
+        
+        // After we're connected, make a tunneled request and populate the webview.
+        
+        DispatchQueue.main.async {
+            assert(self.httpProxyPort > 0)
+            
+            // We'll check out IP to make sure we're tunneled.
+            let urlPath: String = "https://freegeoip.net/csv/"
+            let url = URL(string: urlPath)!
+            let request = URLRequest(url: url)
+            
+            let config = URLSessionConfiguration.ephemeral
+            config.requestCachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
+            config.connectionProxyDictionary = [AnyHashable: Any]()
+            
+            // Enable and set the SOCKS proxy values.
+            config.connectionProxyDictionary?[kCFStreamPropertySOCKSProxy as String] = 1
+            config.connectionProxyDictionary?[kCFStreamPropertySOCKSProxyHost as String] = "127.0.0.1"
+            config.connectionProxyDictionary?[kCFStreamPropertySOCKSProxyPort as String] = self.socksProxyPort
+            
+            // Alternatively, the HTTP proxy can be used. Below are the settings for that.
+            // The HTTPS key constants are mismatched and Xcode gives deprecation warnings, but they seem to be necessary to proxy HTTPS requests. This is probably a bug on Apple's side; see: https://forums.developer.apple.com/thread/19356#131446
+            // config.connectionProxyDictionary?[kCFNetworkProxiesHTTPEnable as String] = 1
+            // config.connectionProxyDictionary?[kCFNetworkProxiesHTTPProxy as String] = "127.0.0.1"
+            // config.connectionProxyDictionary?[kCFNetworkProxiesHTTPPort as String] = self.httpProxyPort
+            // config.connectionProxyDictionary?[kCFStreamPropertyHTTPSProxyHost as String] = "127.0.0.1"
+            // config.connectionProxyDictionary?[kCFStreamPropertyHTTPSProxyPort as String] = self.httpProxyPort
+            
+            let session = URLSession.init(configuration: config, delegate: nil, delegateQueue: OperationQueue.current)
+            
+            // Create the URLSession task that will make the request via the tunnel proxy.
+            let task = session.dataTask(with: request) {
+                (data: Data?, response: URLResponse?, error: Error?) in
+                if error != nil {
+                    NSLog("Client-side error in request to \(urlPath): \(error)")
+                    return
+                }
+                
+                let httpResponse = response as? HTTPURLResponse
+                if httpResponse?.statusCode != 200 {
+                    NSLog("Server-side error in request to \(urlPath): \(httpResponse)")
+                    return
+                }
+                
+                let encodingName = response?.textEncodingName != nil ? response?.textEncodingName : "utf-8"
+                let encoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding(encodingName as CFString!))
+                
+                var stringData = String(data: data!, encoding: String.Encoding(rawValue: UInt(encoding)))
+                stringData = stringData?.replacingOccurrences(of: ",", with: "\n")
+                
+                // Load the IP info result into the web view.
+                self.webView.loadHTMLString("<br><pre>\(stringData!)</pre>", baseURL: url)
+                
+                // Make sure the session is cleaned up.
+                session.invalidateAndCancel()
+                
+                // We're done with the Psiphon tunnel, so stop it.
+                // In a real app, we would keep this alive for as long as we need it.
+                self.psiphonTunnel?.stop()
+            }
+            
+            // Start the request task.
+            task.resume()
+        }
+    }
+    
+    func onExiting() {
+        // TODO: After updating tunnel-core, make sure this is getting hit.
+        NSLog("onExiting")
+    }
+    
+    func onAvailableEgressRegions(_ regions: [Any]) {
+        NSLog("onAvailableEgressRegions: %@", regions)
+    }
+    
+    func onSocksProxyPort(inUse port: Int) {
+        NSLog("onSocksProxyPort: %d", port)
+    }
+    
+    func onHttpProxyPort(inUse port: Int) {
+        NSLog("onHttpProxyPort: %d", port)
+    }
+    
+    func onListeningSocksProxyPort(_ port: Int) {
+        NSLog("onListeningSocksProxyPort: %d", port)
+        // Record the port being used so that we can proxy through it later.
+        DispatchQueue.main.async {
+            self.socksProxyPort = port
+        }
+    }
+    
+    func onListeningHttpProxyPort(_ port: Int) {
+        NSLog("onListeningHttpProxyPort: %d", port)
+        // Record the port being used so that we can proxy through it later.
+        DispatchQueue.main.async {
+            self.httpProxyPort = port
+        }
+    }
+    
+    func onUpstreamProxyError(_ message: String) {
+        NSLog("onUpstreamProxyError: %@", message)
+    }
+    
+    func onClientRegion(_ region: String) {
+        NSLog("onClientRegion: %@", region)
+    }
+    
+    func onSplitTunnelRegion(_ region: String) {
+        NSLog("onSplitTunnelRegion: %@", region)
+    }
+    
+    func onUntunneledAddress(_ address: String) {
+        NSLog("onUntunneledAddress: %@", address)
+    }
+    
+    func onBytesTransferred(_ sent: Int64, _ received: Int64) {
+        NSLog("onBytesTransferred: sent:%d, received:%d", sent, received)
+    }
+    
+    func onHomepage(_ url: String) {
+        NSLog("onHomepage: %@", url)
+    }
+    
+    func onClientIsLatestVersion() {
+        NSLog("onClientIsLatestVersion")
+    }
+    
+    func onClientUpgradeDownloaded(_ filename: String) {
+        NSLog("onClientUpgradeDownloaded: %@", filename)
+    }
+}

+ 13 - 0
MobileLibrary/iOS/SampleApps/TunneledWebRequest/TunneledWebRequest/psiphon-config.json.stub

@@ -0,0 +1,13 @@
+/*
+These are the minimum values required to use the Psiphon Tunnel.
+ClientVersion is the version number of your software, which will be tracked in Psiphon stats.
+All other values will be provided to you by Psiphon Inc.
+*/
+
+{
+  "ClientVersion": "123", /* Must be a number in a string. */
+  "PropagationChannelId": "...",
+  "SponsorId": "...",
+  "RemoteServerListSignaturePublicKey": "...",
+  "RemoteServerListUrl": "..."
+}

+ 22 - 0
MobileLibrary/iOS/SampleApps/TunneledWebRequest/TunneledWebRequestTests/Info.plist

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>en</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>$(PRODUCT_NAME)</string>
+	<key>CFBundlePackageType</key>
+	<string>BNDL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.0</string>
+	<key>CFBundleVersion</key>
+	<string>1</string>
+</dict>
+</plist>

+ 36 - 0
MobileLibrary/iOS/SampleApps/TunneledWebRequest/TunneledWebRequestTests/TunneledWebRequestTests.swift

@@ -0,0 +1,36 @@
+//
+//  TunneledWebRequestTests.swift
+//  TunneledWebRequestTests
+//
+//  Created by Adam Pritchard on 2016-11-03.
+//  Copyright © 2016 Psiphon Inc. All rights reserved.
+//
+
+import XCTest
+@testable import TunneledWebRequest
+
+class TunneledWebRequestTests: XCTestCase {
+    
+    override func setUp() {
+        super.setUp()
+        // Put setup code here. This method is called before the invocation of each test method in the class.
+    }
+    
+    override func tearDown() {
+        // Put teardown code here. This method is called after the invocation of each test method in the class.
+        super.tearDown()
+    }
+    
+    func testExample() {
+        // This is an example of a functional test case.
+        // Use XCTAssert and related functions to verify your tests produce the correct results.
+    }
+    
+    func testPerformanceExample() {
+        // This is an example of a performance test case.
+        self.measure {
+            // Put the code you want to measure the time of here.
+        }
+    }
+    
+}

+ 22 - 0
MobileLibrary/iOS/SampleApps/TunneledWebRequest/TunneledWebRequestUITests/Info.plist

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>en</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>$(PRODUCT_NAME)</string>
+	<key>CFBundlePackageType</key>
+	<string>BNDL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.0</string>
+	<key>CFBundleVersion</key>
+	<string>1</string>
+</dict>
+</plist>

+ 36 - 0
MobileLibrary/iOS/SampleApps/TunneledWebRequest/TunneledWebRequestUITests/TunneledWebRequestUITests.swift

@@ -0,0 +1,36 @@
+//
+//  TunneledWebRequestUITests.swift
+//  TunneledWebRequestUITests
+//
+//  Created by Adam Pritchard on 2016-11-03.
+//  Copyright © 2016 Psiphon Inc. All rights reserved.
+//
+
+import XCTest
+
+class TunneledWebRequestUITests: XCTestCase {
+        
+    override func setUp() {
+        super.setUp()
+        
+        // Put setup code here. This method is called before the invocation of each test method in the class.
+        
+        // In UI tests it is usually best to stop immediately when a failure occurs.
+        continueAfterFailure = false
+        // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method.
+        XCUIApplication().launch()
+
+        // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
+    }
+    
+    override func tearDown() {
+        // Put teardown code here. This method is called after the invocation of each test method in the class.
+        super.tearDown()
+    }
+    
+    func testExample() {
+        // Use recording to get started writing UI tests.
+        // Use XCTAssert and related functions to verify your tests produce the correct results.
+    }
+    
+}

+ 33 - 0
MobileLibrary/iOS/USAGE.md

@@ -0,0 +1,33 @@
+# Using the Psiphon iOS Library
+
+## Overview
+
+Psiphon Library for iOS enables you to easily embed Psiphon in your iOS app.
+You can then tunnel requests through Psiphon, ensuring that your app can't be
+blocked by censors.
+
+The Psiphon Library is available as a `.framework` that can be easily included
+in your project using these instructions.
+
+## Using the Library in your App
+
+**First step:** Review the sample app, located under `SampleApps`.
+This code is a canonical guide for integrating the Library.
+
+**Second step:** Review the comments in [`PsiphonTunnel.h`](PsiphonTunnel/PsiphonTunnel/PsiphonTunnel.h). They describe the interface and delegate requirements.
+
+### Setting up your project
+
+1. Add `PsiphonTunnel.framework` to project (drag into project tree).
+
+2. In the "General" settings for the target, set "Deployment Target" to 9.3.
+
+3. In the "Build Settings" for the target, under "Build Options", set "Enable Bitcode" to "No".
+
+4. In the "Build Settings" for the target, click the `+` at the top, then "Add User-Defined Setting". Name the new setting `STRIP_BITCODE_FROM_COPIED_FILES` and set it to `NO`.
+
+5. In target Build Phases, add a "Copy Files" phase. Set "Destination" to "Frameworks". Add `PsiphonTunnel.framework` to the list. Ensure "Code Sign on Copy" is checked.
+
+## Compiling and testing
+
+Only phone targets are compiled into the Library, so you must compile for and test on an actual device. If you don't do this, you'll get a linker error that says "missing required architecture x86_64 in file".

+ 61 - 8
MobileLibrary/iOS/build-psiphon-framework.sh

@@ -1,7 +1,13 @@
 #!/usr/bin/env bash
+
+set -e
+
 BASE_DIR=$(cd "$(dirname "$0")" ; pwd -P)
 cd ${BASE_DIR}
 
+# The location of the final framework build
+BUILD_DIR="${BASE_DIR}/build"
+
 # Ensure go is installed
 which go 2>&1 > /dev/null
 if [ $? -ne 0 ]; then
@@ -11,19 +17,24 @@ fi
 
 VALID_ARCHS="arm64 armv7 armv7s"
 FRAMEWORK="Psi"
-INTERMEDIATE_OUPUT_DIR="${BASE_DIR}/PsiphonTunnelController/PsiphonTunnelController"
+INTERMEDIATE_OUPUT_DIR="${BASE_DIR}/PsiphonTunnel/PsiphonTunnel"
 INTERMEDIATE_OUPUT_FILE="${FRAMEWORK}.framework"
 FRAMEWORK_BINARY="${INTERMEDIATE_OUPUT_DIR}/${INTERMEDIATE_OUPUT_FILE}/Versions/A/${FRAMEWORK}"
 
 LIBSSL=${BASE_DIR}/OpenSSL-for-iPhone/lib/libssl.a
 LIBCRYPTO=${BASE_DIR}/OpenSSL-for-iPhone/lib/libcrypto.a
 OPENSSL_INCLUDE=${BASE_DIR}/OpenSSL-for-iPhone/include/
-UMBRELLA_FRAMEWORK_XCODE_PROJECT=${BASE_DIR}/PsiphonTunnelController/PsiphonTunnelController.xcodeproj/
-TRUSTED_ROOT_CA_FILE=${BASE_DIR}/PsiphonTunnelController/PsiphonTunnelController/rootCAs.txt
+UMBRELLA_FRAMEWORK_XCODE_PROJECT=${BASE_DIR}/PsiphonTunnel/PsiphonTunnel.xcodeproj/
+TRUSTED_ROOT_CA_FILE=${BASE_DIR}/PsiphonTunnel/PsiphonTunnel/rootCAs.txt
 
-#Download trustedroot CAs off curl website, see https://curl.haxx.se/docs/caextract.html for details
+# Download trustedroot CAs off curl website, see https://curl.haxx.se/docs/caextract.html for details
 curl -o $TRUSTED_ROOT_CA_FILE https://curl.haxx.se/ca/cacert.pem
 
+rc=$?; if [[ $rc != 0 ]]; then
+  echo "FAILURE: curl -o $TRUSTED_ROOT_CA_FILE https://curl.haxx.se/ca/cacert.pem"
+  exit $rc
+fi
+
 # Not exporting this breaks go commands later if run via jenkins
 export GOPATH=${PWD}/go-ios-build
 
@@ -37,7 +48,16 @@ OPENSSL_SRC_DIR=${GOPATH}/src/github.com/Psiphon-Inc/openssl
 PATH=${PATH}:${GOPATH}/bin
 
 mkdir -p ${GOPATH}
+rc=$?; if [[ $rc != 0 ]]; then
+  echo "FAILURE: mkdir -p ${GOPATH}"
+  exit $rc
+fi
+
 mkdir -p ${INTERMEDIATE_OUPUT_DIR}
+rc=$?; if [[ $rc != 0 ]]; then
+  echo "FAILURE: mkdir -p ${INTERMEDIATE_OUPUT_DIR}"
+  exit $rc
+fi
 
 if [ ! -e ${IOS_SRC_DIR} ]; then
   echo "iOS source directory (${IOS_SRC_DIR}) not found, creating link"
@@ -52,7 +72,16 @@ fi
 cd OpenSSL-for-iPhone && ./build-libssl.sh; cd -
 
 go get -d  -u -v github.com/Psiphon-Inc/openssl
+rc=$?; if [[ $rc != 0 ]]; then
+  echo "FAILURE: go get -d  -u -v github.com/Psiphon-Inc/openssl"
+  exit $rc
+fi
+
 go get -d -u -v github.com/Psiphon-Labs/psiphon-tunnel-core/MobileLibrary/psi
+rc=$?; if [[ $rc != 0 ]]; then
+  echo "FAILURE: go get -d -u -v github.com/Psiphon-Labs/psiphon-tunnel-core/MobileLibrary/psi"
+  exit $rc
+fi
 
 function check_pinned_version() {
   echo "Checking for gomobile revision: '${GOMOBILE_PINNED_REV}'"
@@ -77,7 +106,7 @@ check_pinned_version
 if [ $? -ne 0 ]; then
     go get -u golang.org/x/mobile/cmd/gomobile
     cd ${GOPATH}/src/golang.org/x/mobile/cmd/gomobile
-    git checkout master 
+    git checkout master
     git branch -d pinned
     git checkout -b pinned ${GOMOBILE_PINNED_REV}
     go install
@@ -122,14 +151,38 @@ IOS_CGO_BUILD_FLAGS='// #cgo darwin CFLAGS: -I'"${OPENSSL_INCLUDE}"'\
 
 LC_ALL=C sed -i -- "s|// #cgo pkg-config: libssl|${IOS_CGO_BUILD_FLAGS}|" "${OPENSSL_SRC_DIR}/build.go"
 
-gomobile bind  -target ios -ldflags="${LDFLAGS}" -o ${INTERMEDIATE_OUPUT_DIR}/${INTERMEDIATE_OUPUT_FILE} github.com/Psiphon-Labs/psiphon-tunnel-core/MobileLibrary/psi
+gomobile init
+
+gomobile bind -target ios -ldflags="${LDFLAGS}" -o ${INTERMEDIATE_OUPUT_DIR}/${INTERMEDIATE_OUPUT_FILE} github.com/Psiphon-Labs/psiphon-tunnel-core/MobileLibrary/psi
+rc=$?; if [[ $rc != 0 ]]; then
+  echo "FAILURE: gomobile bind"
+  exit $rc
+fi
+
 ARCHS="$(lipo -info "${FRAMEWORK_BINARY}" | rev | cut -d ':' -f1 | rev)"
 for ARCH in $ARCHS; do
   if ! [[ "${VALID_ARCHS}" == *"$ARCH"* ]]; then
     echo "Stripping ARCH ${ARCH} from ${FRAMEWORK_BINARY}"
-    lipo -remove "$ARCH" -output "$FRAMEWORK_BINARY" "$FRAMEWORK_BINARY" || exit 1
+    lipo -remove "$ARCH" -output "$FRAMEWORK_BINARY" "$FRAMEWORK_BINARY"
+    rc=$?; if [[ $rc != 0 ]]; then
+      echo "FAILURE: lipo"
+      exit $rc
+    fi
   fi
 done
 
-xcodebuild clean build CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO -configuration Release -sdk iphoneos -project ${UMBRELLA_FRAMEWORK_XCODE_PROJECT} CONFIGURATION_BUILD_DIR="${BASE_DIR}/build"
+#
+# Do the outer framework build using Xcode
+#
+
+# Clean previous output
+rm -rf "${BUILD_DIR}"
+
+# Build the outer framework
+xcodebuild clean build CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO -configuration Release -sdk iphoneos ONLY_ACTIVE_ARCH=NO -project ${UMBRELLA_FRAMEWORK_XCODE_PROJECT} CONFIGURATION_BUILD_DIR="${BUILD_DIR}"
+rc=$?; if [[ $rc != 0 ]]; then
+  echo "FAILURE: xcodebuild"
+  exit $rc
+fi
+
 echo "Done."

Некоторые файлы не были показаны из-за большого количества измененных файлов