├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── SwiftDP.podspec ├── WORKSPACE ├── build_framework_objc.sh ├── build_framework_swift.sh ├── build_frameworks.sh ├── clang_format.sh ├── src ├── OCDP │ ├── BUILD │ ├── OCDP.h │ ├── OCDPBoundingReport.h │ ├── OCDPBoundingReport.mm │ ├── OCDPCarrotReporter.h │ ├── OCDPCarrotReporter.mm │ ├── OCDPConfidenceInterval.h │ ├── OCDPConfidenceInterval.mm │ ├── OCDPErrorReport.h │ ├── OCDPErrorReport.mm │ ├── OCDPOutput.h │ ├── OCDPOutput.mm │ ├── OCDPStatus.h │ ├── OCDPStatus.mm │ ├── OCDPStatusOr.h │ ├── OCDPStatusOr.mm │ └── OCDPUtil.mm └── SwiftDP │ ├── BUILD │ └── fake.swift ├── start.sh └── xcode ├── Gemfile ├── app ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── OCDP-Bridging-Header.h ├── Sandbox.h ├── Sandbox.mm ├── SceneDelegate.swift └── ViewController.swift ├── carthage └── OCDP.json ├── fastlane └── Fastfile ├── project.yml └── tests └── SwiftDPTests.swift /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: {} 4 | pull_request: {} 5 | jobs: 6 | setup: 7 | runs-on: macOS-latest 8 | name: select xcode ${{ matrix.xcode }} 9 | strategy: 10 | matrix: 11 | xcode: ["11.4"] 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Set Xcode 15 | run: | 16 | echo "Available Xcode versions:" 17 | ls /Applications | grep Xcode 18 | echo "Choosing Xcode_${{ matrix.xcode }}.app" 19 | sudo xcode-select -s /Applications/Xcode_${{ matrix.xcode }}.app 20 | xcodebuild -version 21 | swift --version 22 | swift package --version 23 | 24 | framework: 25 | name: build bazel framework 26 | runs-on: macOS-latest 27 | steps: 28 | - uses: actions/checkout@v2 29 | - name: Run Build 30 | run: | 31 | curl -LO https://github.com/bazelbuild/bazel/releases/download/3.0.0/bazel-3.0.0-darwin-x86_64 32 | mkdir -p "${GITHUB_WORKSPACE}/bin/" 33 | mv bazel-3.0.0-darwin-x86_64 "${GITHUB_WORKSPACE}/bin/bazel" 34 | chmod +x "${GITHUB_WORKSPACE}/bin/bazel" 35 | ${GITHUB_WORKSPACE}/bin/bazel --version 36 | export PATH=${GITHUB_WORKSPACE}/bin/:$PATH 37 | which bazel 38 | bazel --version 39 | set -o pipefail 40 | source ./build_frameworks.sh 41 | 42 | build: 43 | name: build iOS app 44 | runs-on: macOS-latest 45 | steps: 46 | - uses: actions/checkout@v2 47 | - name: Install Homebrew Dependencies 48 | run: | 49 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" 50 | brew install clang-format xcodegen 51 | - name: Install Fastlane 52 | run: | 53 | gem install bundler 54 | cd xcode 55 | bundle install 56 | - name: Run Build 57 | run: | 58 | curl -LO https://github.com/bazelbuild/bazel/releases/download/3.0.0/bazel-3.0.0-darwin-x86_64 59 | mkdir -p "${GITHUB_WORKSPACE}/bin/" 60 | mv bazel-3.0.0-darwin-x86_64 "${GITHUB_WORKSPACE}/bin/bazel" 61 | chmod +x "${GITHUB_WORKSPACE}/bin/bazel" 62 | ${GITHUB_WORKSPACE}/bin/bazel --version 63 | export PATH=${GITHUB_WORKSPACE}/bin/:$PATH 64 | which bazel 65 | bazel --version 66 | set -o pipefail 67 | source ./build_frameworks.sh 68 | cd xcode 69 | xcodegen generate 70 | bundle exec fastlane build 71 | test: 72 | name: run iOS tests 73 | runs-on: macOS-latest 74 | steps: 75 | - name: Checkout repo 76 | uses: actions/checkout@v2 77 | - name: Fetch all branches 78 | run: | 79 | git fetch --prune --unshallow 80 | - name: Install Homebrew Dependencies 81 | run: | 82 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" 83 | brew install clang-format xcodegen 84 | - name: Install fastlane 85 | run: | 86 | gem install bundler 87 | cd xcode 88 | bundle install 89 | - name: Run Tests 90 | run: | 91 | curl -LO https://github.com/bazelbuild/bazel/releases/download/3.0.0/bazel-3.0.0-darwin-x86_64 92 | mkdir -p "${GITHUB_WORKSPACE}/bin/" 93 | mv bazel-3.0.0-darwin-x86_64 "${GITHUB_WORKSPACE}/bin/bazel" 94 | chmod +x "${GITHUB_WORKSPACE}/bin/bazel" 95 | ${GITHUB_WORKSPACE}/bin/bazel --version 96 | export PATH=${GITHUB_WORKSPACE}/bin/:$PATH 97 | which bazel 98 | bazel --version 99 | set -o pipefail 100 | source ./build_frameworks.sh 101 | cd xcode 102 | xcodegen generate 103 | bundle exec fastlane tests 104 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bazel-bin 2 | /bazel-out 3 | /bazel-SwiftDP 4 | /bazel-testlogs 5 | 6 | # generated by XcodeGen 7 | /xcode/SwiftDP-App.xcodeproj 8 | /xcode/dummy 9 | /xcode/build -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Tests](https://github.com/OpenMined/SwiftDP/workflows/CI/badge.svg) 2 | [![Chat on Slack](https://img.shields.io/badge/chat-on%20slack-7A5979.svg)](https://openmined.slack.com/messages/lib_swift_dp) 3 | 4 | # SwiftDP 5 | 6 | Swift wrapper for Google's Differential Privacy Project via an Objective-C++ bridge. 7 | 8 | ## TODO 9 | 10 | - [x] Bazel Build 11 | - [x] C++ -> Objective-C -> Swift 12 | - [x] Xcodegen Project 13 | - [x] clang-format 14 | - [x] fastlane 15 | - [x] Carrot Hello World 16 | - [x] GitHub CI Unit Tests 17 | - [x] OCDP Objective-C Wrapper Framework 18 | - [ ] SwiftDP Wrapper Framework 19 | - [ ] Cocopods Support 20 | - [ ] Carthage Support 21 | - [ ] SwiftPM Support 22 | - [ ] Native iOS Example 23 | - [ ] React Native Example 24 | - [ ] Flutter Example 25 | 26 | ## Prerequisites 27 | 28 | - Xcode 29 | - Brew 30 | 31 | ``` 32 | $ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" 33 | ``` 34 | 35 | - Bazel 36 | 37 | ``` 38 | $ brew tap bazelbuild/tap 39 | $ brew install bazelbuild/tap/bazel 40 | ``` 41 | 42 | - XcodeGen 43 | 44 | ``` 45 | $ brew install xcodegen 46 | ``` 47 | 48 | - clang-format 49 | 50 | ``` 51 | $ brew install clang-format 52 | ``` 53 | 54 | # Setup 55 | 56 | There is a start script which will fetch the bazel dependencies build the framework and 57 | create an xcode project for use in development. 58 | 59 | ``` 60 | $ ./start.sh 61 | ``` 62 | 63 | # Packaging Overview 64 | 65 | ## Build Framework 66 | 67 | ``` 68 | $ ./build_framework.sh 69 | ``` 70 | 71 | ## Xcode Development 72 | 73 | All development can be done inside of xcode. We have the Objective-C source code linked a dummy framework to allow for "Jump to Definition" as well as the google absl and dp libraries linked but with no target membership to make it easy to navigate the source. 74 | 75 | ## Build Example App 76 | 77 | ``` 78 | $ cd xcode 79 | $ xcodegen generate 80 | ``` 81 | 82 | Open SwiftDP-App.xcodeproj and add your Team Signing Identity and hit run. 83 | 84 | ## Useful Commands 85 | 86 | ### Bazel Clean 87 | 88 | ``` 89 | $ bazel clean --expunge 90 | ``` 91 | 92 | ### Inspect iOS Library Files 93 | 94 | Show which architectures are available: 95 | 96 | ``` 97 | $ lipo -info ./bazel-bin/src/SwiftDP_archive-root/SwiftDP.framework/SwiftDP 98 | ``` 99 | 100 | ``` 101 | $ otool -hv -arch all $FILENAME 102 | ``` 103 | 104 | ``` 105 | $ nm $FILENAME 106 | ``` 107 | 108 | # Namespaces 109 | 110 | ## Objective-C 111 | 112 | We are using the namespace OCDP in the Objective-C code. 113 | 114 | ## Swift 115 | 116 | We plan to use NS_SWIFT_UNAVAILABLE and NS_SWIFT_NAME to selectively expose and rename the Objective-C API to Swift. 117 | 118 | # How to Objective-C++ 119 | 120 | ## Utility Library 121 | 122 | We have a utility library of functions which help converting from C++ to Objective-C and back as needed. 123 | 124 | The file is called DPUtil.mm and has no header, which allows it to freely mix C++ and Objective-C into the *franken*code that is Objective-C++. 125 | 126 | To make the file available for import its listed in the Bazel BUILD objc_library hdrs. 127 | 128 | ## Initializers 129 | 130 | Mark types with nonnull if you want to return an Implicitly Unwrapped Optional (IOU) in Swift. 131 | 132 | ``` 133 | - (nonnull instancetype) initWithEpsilon: (double) epsilon; // return ! IUO 134 | ``` 135 | 136 | To mark as optional use nullable: 137 | 138 | ``` 139 | - (nullable instancetype) initWithEpsilon: (double) epsilon; // return ? Optional 140 | ``` 141 | 142 | Hide unused init / new: 143 | 144 | ``` 145 | - (instancetype)init NS_UNAVAILABLE; // disable default initializers 146 | + (instancetype)new NS_UNAVAILABLE; // disable default new aka [[NSObject alloc] init] 147 | ``` 148 | 149 | ## Toolbox 150 | 151 | ### C++ 152 | 153 | - Templates 154 | - C++ auto in range-based for-loops 155 | 156 | ### Objective-C 157 | 158 | - Categories 159 | - Objective-C++ .mm files 160 | - `__kindof` 161 | - isMemberOfClass 162 | - isKindOfClass 163 | - Protocols 164 | - Light-weight Generics 165 | - NS_UNAVAILABLE 166 | 167 | ### Swift 168 | 169 | - NS_SWIFT_NAME 170 | - NS_SWIFT_UNAVAILABLE 171 | 172 | ## Resources 173 | 174 | These links have really useful patterns and ideas on ways to mix C++ and Objective-C in novel ways. 175 | 176 | https://philjordan.eu/article/strategies-for-using-c++-in-objective-c-projects 177 | https://philjordan.eu/article/mixing-objective-c-c++-and-objective-c++ 178 | https://useyourloaf.com/blog/using-nullable-to-annotate-objective-c/ 179 | https://useyourloaf.com/blog/using-objective-c-lightweight-generics/ 180 | https://developer.apple.com/documentation/swift/objective-c_and_c_code_customization 181 | http://mirror.informatimago.com/next/developer.apple.com/releasenotes/Cocoa/Objective-C++.html 182 | https://gist.github.com/dodikk/4a0f1d98faa7c1336551 183 | https://medium.com/@husain.amri/objective-c-deliver-us-from-swift-3a44d3ac00e7 184 | https://medium.com/@husain.amri/creating-objective-c-classes-at-runtime-1f02b3a3a1db 185 | https://academy.realm.io/posts/altconf-peter-steinberger-objective-c++-what-could-possibly-go-wrong/ 186 | https://github.com/EmbeddedSources/JFFLibrary/tree/master/lib/JFFUtils 187 | https://www.netguru.com/codestories/objective-c-generics 188 | -------------------------------------------------------------------------------- /SwiftDP.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "SwiftDP" 3 | s.version = "0.0.1" 4 | s.summary = "SwiftDP" 5 | 6 | s.description = <<-DESC 7 | Swift wrapper for Google's Differential Privacy Project via an Objective-C++ bridge. 8 | DESC 9 | 10 | s.homepage = "https://www.openmined.org/" 11 | s.license = { :type => "Apache 2.0", :file => "LICENSE" } 12 | s.authors = { "OpenMined" => "@openminedorg", 13 | "Madhava Jay" => "@madhavajay", 14 | "Madalin Mamuleanu" => "@mmamuleanu" } 15 | 16 | s.social_media_url = "https://twitter.com/openminedorg" 17 | 18 | s.source = { 19 | :git => "https://github.com/OpenMined/SwiftDP.git", 20 | :branch => "dev", 21 | } 22 | 23 | s.cocoapods_version = ">= 1.4.0" 24 | s.module_name = "SwiftDP" 25 | 26 | s.prepare_command = <<-CMD 27 | echo "here `pwd`" 28 | mkdir SwiftDP.framework 29 | mkdir OCDP.framework 30 | CMD 31 | 32 | s.script_phases = [ 33 | { :name => "Bazel Build Frameworks", :script => "echo 'Building SwiftDP in `pwd`' && cd SwiftDP && source build_frameworks.sh", :execution_position => :before_compile }, 34 | { :name => "Copying Frameworks", :script => "cd SwiftDP && rm -rf SwiftDP.framework || true && rm -rf OCDP.framework || true && cp -R bazel-bin/src/SwiftDP/SwiftDP_archive-root/SwiftDP.framework ./SwiftDP.framework && cp -R bazel-bin/src/OCDP/OCDP_archive-root/OCDP.framework ./OCDP.framework", :execution_position => :before_compile }, 35 | ] 36 | 37 | s.ios.vendored_frameworks = "SwiftDP.framework", "OCDP.framework" 38 | 39 | s.preserve_paths = "**/*" 40 | s.source_files = "src/fake.swift" 41 | 42 | s.ios.deployment_target = "13.0" 43 | s.platforms = { ios: "13.0" } 44 | end 45 | -------------------------------------------------------------------------------- /WORKSPACE: -------------------------------------------------------------------------------- 1 | workspace(name = "SwiftDP") 2 | 3 | load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") 4 | 5 | git_repository( 6 | name = "build_bazel_rules_swift", 7 | commit = "1f2201706d6b4dcf3675c4c5393fe42a30948859", 8 | remote = "https://github.com/bazelbuild/rules_swift.git", 9 | ) 10 | 11 | load( 12 | "@build_bazel_rules_swift//swift:repositories.bzl", 13 | "swift_rules_dependencies", 14 | ) 15 | 16 | swift_rules_dependencies() 17 | 18 | git_repository( 19 | name = "build_bazel_rules_apple", 20 | commit = "313eeb838497e230f01a7367ae4555aaf0cac62e", 21 | remote = "https://github.com/bazelbuild/rules_apple.git", 22 | ) 23 | 24 | load( 25 | "@build_bazel_rules_apple//apple:repositories.bzl", 26 | "apple_rules_dependencies", 27 | ) 28 | 29 | apple_rules_dependencies() 30 | 31 | load( 32 | "@com_google_protobuf//:protobuf_deps.bzl", 33 | "protobuf_deps", 34 | ) 35 | 36 | protobuf_deps() 37 | 38 | git_repository( 39 | name = "google_differential_privacy", 40 | commit = "14f26fb91570cce384c2715d3adcaa4e92eec605", 41 | remote = "https://github.com/google/differential-privacy", 42 | ) 43 | 44 | load("@google_differential_privacy//:differential_privacy_deps.bzl", "differential_privacy_deps") 45 | 46 | differential_privacy_deps() 47 | -------------------------------------------------------------------------------- /build_framework_objc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if ! [ -x "$(command -v bazel)" ]; then 4 | echo 'Error: bazel is not installed. Downloading:' >&2 5 | curl -Lo ./bazel https://github.com/bazelbuild/bazel/releases/download/3.0.0/bazel-3.0.0-darwin-x86_64 6 | chmod +x "./bazel" 7 | export PATH=`pwd`/:$PATH 8 | fi 9 | echo "Building Framework with: `which bazel`" 10 | bazel build //src/OCDP:OCDP --ios_multi_cpus=x86_64,arm64 --apple_bitcode=embedded --copt=-fembed-bitcode -------------------------------------------------------------------------------- /build_framework_swift.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if ! [ -x "$(command -v bazel)" ]; then 4 | echo 'Error: bazel is not installed. Downloading:' >&2 5 | curl -Lo ./bazel https://github.com/bazelbuild/bazel/releases/download/3.0.0/bazel-3.0.0-darwin-x86_64 6 | chmod +x "./bazel" 7 | export PATH=`pwd`/:$PATH 8 | fi 9 | echo "Building Framework with: `which bazel`" 10 | bazel build //src/SwiftDP:SwiftDP --ios_multi_cpus=x86_64,arm64 --apple_bitcode=embedded --copt=-fembed-bitcode -------------------------------------------------------------------------------- /build_frameworks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source build_framework_objc.sh 4 | source build_framework_swift.sh -------------------------------------------------------------------------------- /clang_format.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [ -z "$PROJECT_DIR" ]; 3 | then 4 | PROJECT_DIR=`pwd`/xcode 5 | fi 6 | cd $PROJECT_DIR/../src 7 | find ./ -name "*.[hm]*" | xargs clang-format -i 8 | -------------------------------------------------------------------------------- /src/OCDP/BUILD: -------------------------------------------------------------------------------- 1 | load("@rules_cc//cc:defs.bzl", "cc_library", "cc_proto_library", "objc_library") 2 | load("@build_bazel_rules_apple//apple:versioning.bzl", "apple_bundle_version") 3 | load("@build_bazel_rules_apple//apple:ios.bzl", "ios_static_framework") 4 | 5 | # Build the proto file 6 | cc_proto_library( 7 | name = "dp-proto", 8 | deps = ["@google_differential_privacy//differential_privacy/proto:data-proto"], 9 | ) 10 | 11 | # Wrap the proto file in a c++ library so we can use it as a dependency 12 | cc_library( 13 | name = "dp-proto-cc", 14 | hdrs = [], 15 | copts = ["-Wno-sign-compare"], 16 | deps = [ 17 | ":dp-proto", 18 | ], 19 | ) 20 | 21 | # Build the Objective-C Library which bridges the C++ 22 | objc_library( 23 | name = "dp-objc", 24 | module_name = "OCDP", # this is the name space for @import OCDP 25 | srcs = [ 26 | "OCDPUtil.mm", 27 | "OCDPCarrotReporter.mm", 28 | "OCDPStatus.mm", 29 | "OCDPStatusOr.mm", 30 | "OCDPOutput.mm", 31 | "OCDPErrorReport.mm", 32 | "OCDPBoundingReport.mm", 33 | "OCDPConfidenceInterval.mm", 34 | ], 35 | hdrs = [ 36 | "OCDPUtil.mm", # .mm's are needed in hdrs to import into other .mm files 37 | "OCDPStatus.mm", 38 | "OCDPStatusOr.mm", 39 | "OCDPOutput.mm", 40 | "OCDPErrorReport.mm", 41 | "OCDPBoundingReport.mm", 42 | "OCDPConfidenceInterval.mm", 43 | "OCDPCarrotReporter.h", 44 | "OCDPStatus.h", 45 | "OCDPStatusOr.h", 46 | "OCDPOutput.h", 47 | "OCDPErrorReport.h", 48 | "OCDPBoundingReport.h", 49 | "OCDPConfidenceInterval.h", 50 | ], 51 | alwayslink = True, 52 | enable_modules = True, 53 | deps = [ 54 | ":dp-proto-cc", 55 | "@google_differential_privacy//differential_privacy/algorithms:bounded-sum", 56 | "@google_differential_privacy//differential_privacy/algorithms:bounded-mean", 57 | "@google_differential_privacy//differential_privacy/algorithms:count", 58 | "@google_differential_privacy//differential_privacy/algorithms:order-statistics", 59 | "@google_differential_privacy//differential_privacy/algorithms:rand", 60 | "@google_differential_privacy//differential_privacy/base:status", 61 | "@google_differential_privacy//differential_privacy/base:statusor", 62 | "@google_differential_privacy//differential_privacy/base:canonical_errors" 63 | ], 64 | visibility = ["//visibility:public"] 65 | ) 66 | 67 | # FRAMEWORKS 68 | # Define the version of the framework 69 | apple_bundle_version( 70 | name = "framework-version", 71 | build_version = "0.1", 72 | short_version_string = "0.1", 73 | ) 74 | 75 | # Objective-C Only Framework 76 | ios_static_framework( 77 | name = "OCDP", 78 | bundle_name = "OCDP", 79 | hdrs = [ 80 | "OCDPCarrotReporter.h", 81 | "OCDPStatus.h", 82 | "OCDPStatusOr.h", 83 | "OCDPOutput.h", 84 | "OCDPErrorReport.h", 85 | "OCDPBoundingReport.h", 86 | "OCDPConfidenceInterval.h", 87 | ], 88 | families = [ 89 | "iphone", 90 | "ipad", 91 | ], 92 | minimum_os_version = "13.0", 93 | umbrella_header = "OCDP.h", 94 | version = ":framework-version", 95 | deps = [":dp-objc"], 96 | visibility = ["//visibility:public"] 97 | ) -------------------------------------------------------------------------------- /src/OCDP/OCDP.h: -------------------------------------------------------------------------------- 1 | #import "OCDPBoundingReport.h" 2 | #import "OCDPCarrotReporter.h" 3 | #import "OCDPConfidenceInterval.h" 4 | #import "OCDPErrorReport.h" 5 | #import "OCDPOutput.h" 6 | #import "OCDPStatus.h" 7 | #import "OCDPStatusOr.h" 8 | -------------------------------------------------------------------------------- /src/OCDP/OCDPBoundingReport.h: -------------------------------------------------------------------------------- 1 | // 2 | // OCDPBoundingReport.h 3 | // SwiftDP 4 | // 5 | // Created by Madhava Jay on 6/5/20. 6 | // 7 | 8 | #import 9 | @interface OCDPBoundingReport : NSObject 10 | 11 | - (int)lower_bound; 12 | - (int)upper_bound; 13 | - (double)num_inputs; 14 | - (double)num_outside; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /src/OCDP/OCDPBoundingReport.mm: -------------------------------------------------------------------------------- 1 | // 2 | // OCDPBoundingReport.m 3 | // SwiftDP 4 | // 5 | // Created by Madhava Jay on 6/5/20. 6 | // 7 | 8 | #import "OCDPBoundingReport.h" 9 | #import 10 | 11 | #include "differential_privacy/proto/data.pb.h" 12 | #include "differential_privacy/proto/util.h" 13 | 14 | using namespace differential_privacy; 15 | 16 | @interface OCDPBoundingReport () { 17 | BoundingReport *cpp_Boundingreport; 18 | } 19 | - (instancetype)initWithCppBoundingReport:(BoundingReport)bounding_report; 20 | @end 21 | 22 | @implementation OCDPBoundingReport 23 | 24 | - (instancetype)initWithCppBoundingReport:(BoundingReport)bounding_report { 25 | self = [super init]; 26 | if (self) { 27 | // Start PIMPL 28 | cpp_Boundingreport = new BoundingReport(bounding_report); 29 | if (!cpp_Boundingreport) { 30 | self = nil; // destroy Wrapper 31 | } 32 | // End PIMPL 33 | } 34 | return self; 35 | } 36 | 37 | - (int)lower_bound { 38 | ValueType lb = cpp_Boundingreport->lower_bound(); 39 | return GetValue(lb); 40 | } 41 | 42 | - (int)upper_bound { 43 | ValueType ub = cpp_Boundingreport->upper_bound(); 44 | return GetValue(ub); 45 | } 46 | 47 | - (double)num_inputs { 48 | return cpp_Boundingreport->num_inputs(); 49 | } 50 | 51 | - (double)num_outside { 52 | return cpp_Boundingreport->num_outside(); 53 | } 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /src/OCDP/OCDPCarrotReporter.h: -------------------------------------------------------------------------------- 1 | // 2 | // OCDPCarrotReporter.h 3 | // SwiftDP-App 4 | // 5 | // Created by Madhava Jay on 20/4/20. 6 | // 7 | 8 | #import "OCDPStatusOr.h" 9 | #import 10 | 11 | @interface OCDPCarrotReporter : NSObject 12 | - (nonnull instancetype)init; 13 | - (nonnull instancetype)initWithEpsilon:(double)epsilon 14 | AndData:(NSDictionary *) 15 | data; 16 | 17 | // Replace CSV for convenience 18 | - (NSDictionary *)getCarrotsData; 19 | - (void)setCarrotsData:(NSDictionary *)carrotsAnimal; 20 | 21 | // True sum of all the carrots eaten. 22 | - (int)Sum; 23 | 24 | // True mean of carrots eaten. 25 | - (double)Mean; 26 | 27 | - (int)CountAbove:(int)limit; 28 | 29 | // True maximum of the number of carrots eaten by any one animal. 30 | - (int)Max; 31 | 32 | // Returns the remaining privacy budget. Animals should check this to see if 33 | // they should answer any more of Farmer Fred's questions. 34 | - (double)PrivacyBudget; 35 | 36 | - (nonnull OCDPStatusOr *)PrivateSum:(double)privacy_budget; 37 | - (nonnull OCDPStatusOr *)PrivateMean:(double)privacy_budget; 38 | - (nonnull OCDPStatusOr *)PrivateCountAbove:(double)privacy_budget 39 | limit:(int)max; 40 | - (nonnull OCDPStatusOr *)PrivateMax:(double)privacy_budget; 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /src/OCDP/OCDPCarrotReporter.mm: -------------------------------------------------------------------------------- 1 | // 2 | // OCDPCarrotReporter.mm 3 | // SwiftDP 4 | // 5 | // Created by Madhava Jay on 20/4/20. 6 | // 7 | 8 | #import "OCDPCarrotReporter.h" 9 | #import "OCDPStatus.mm" 10 | #import "OCDPStatusOr.mm" 11 | #import "OCDPUtil.mm" 12 | #import 13 | 14 | #import "differential_privacy/algorithms/bounded-mean.h" 15 | #import "differential_privacy/algorithms/bounded-sum.h" 16 | #import "differential_privacy/algorithms/count.h" 17 | #include "differential_privacy/algorithms/order-statistics.h" 18 | #include "differential_privacy/base/canonical_errors.h" 19 | #include "differential_privacy/base/statusor.h" 20 | #include "differential_privacy/proto/data.pb.h" 21 | #include 22 | #include 23 | 24 | using namespace differential_privacy; 25 | 26 | // class extension to hold iVars for PIMPL 27 | @interface OCDPCarrotReporter () { 28 | std::map carrots_per_animal_; 29 | double epsilon_; 30 | double privacy_budget_; 31 | } 32 | 33 | @end 34 | 35 | @implementation OCDPCarrotReporter 36 | 37 | - (nonnull instancetype)init { 38 | return [self initWithEpsilon:1 AndData:@{}]; // default value of epsilon 39 | } 40 | 41 | - (nonnull instancetype)initWithEpsilon:(double)epsilon 42 | AndData:(NSDictionary *) 43 | data { 44 | self = [super init]; 45 | if (self) { 46 | // Start PIMPL 47 | self->epsilon_ = epsilon; 48 | self->privacy_budget_ = 1; 49 | self->carrots_per_animal_ = [data toCppMap]; 50 | // End PIMPL 51 | } 52 | return self; 53 | } 54 | - (NSDictionary *)getCarrotsData { 55 | return DPUtil::convertToDictionary(carrots_per_animal_); 56 | } 57 | 58 | - (void)setCarrotsData:(NSDictionary *)carrotsAnimal { 59 | self->carrots_per_animal_ = DPUtil::convertFromDictionary(carrotsAnimal); 60 | } 61 | 62 | - (int)Sum { 63 | int sum = 0; 64 | for (const auto &pair : self->carrots_per_animal_) { 65 | sum += pair.second; 66 | } 67 | return sum; 68 | } 69 | 70 | // True mean of carrots eaten. 71 | - (double)Mean { 72 | return static_cast([self Sum]) / self->carrots_per_animal_.size(); 73 | } 74 | 75 | - (int)CountAbove:(int)limit { 76 | int count = 0; 77 | for (const auto &pair : self->carrots_per_animal_) { 78 | if (pair.second > limit) { 79 | ++count; 80 | } 81 | } 82 | return count; 83 | } 84 | 85 | // True maximum of the number of carrots eaten by any one animal. 86 | - (int)Max { 87 | int max = 0; 88 | for (const auto &pair : carrots_per_animal_) { 89 | max = std::max(pair.second, max); 90 | } 91 | return max; 92 | } 93 | 94 | // Returns the remaining privacy budget. Animals should check this to see if 95 | // they should answer any more of Farmer Fred's questions. 96 | - (double)PrivacyBudget { 97 | return self->privacy_budget_; 98 | } 99 | 100 | - (nonnull OCDPStatusOr *)PrivateSum:(double)privacy_budget { 101 | base::StatusOr result = [self _PrivateSum:privacy_budget]; 102 | OCDPStatusOr *statusOr = [[OCDPStatusOr alloc] initWithCppStatusOr:result]; 103 | return statusOr; 104 | } 105 | 106 | - (base::StatusOr)_PrivateSum:(double)privacy_budget { 107 | privacy_budget_ -= privacy_budget; 108 | 109 | if (privacy_budget_ < privacy_budget) { 110 | return base::InvalidArgumentError("Not enough privacy budget."); 111 | } 112 | 113 | ASSIGN_OR_RETURN(std::unique_ptr> sum_algorithm, 114 | BoundedSum::Builder() 115 | .SetEpsilon(epsilon_) 116 | .SetLower(0) 117 | .SetUpper(150) 118 | .Build()); 119 | 120 | for (const auto &pair : carrots_per_animal_) { 121 | sum_algorithm->AddEntry(pair.second); 122 | } 123 | return sum_algorithm->PartialResult(privacy_budget); 124 | } 125 | 126 | - (nonnull OCDPStatusOr *)PrivateMean:(double)privacy_budget { 127 | base::StatusOr result = [self _PrivateMean:privacy_budget]; 128 | OCDPStatusOr *statusOr = [[OCDPStatusOr alloc] initWithCppStatusOr:result]; 129 | return statusOr; 130 | } 131 | 132 | - (base::StatusOr)_PrivateMean:(double)privacy_budget { 133 | if (privacy_budget_ < privacy_budget) { 134 | return base::InvalidArgumentError("Not enough privacy budget."); 135 | } 136 | privacy_budget_ -= privacy_budget; 137 | ASSIGN_OR_RETURN(std::unique_ptr> mean_algorithm, 138 | BoundedMean::Builder().SetEpsilon(epsilon_).Build()); 139 | for (const auto &pair : carrots_per_animal_) { 140 | mean_algorithm->AddEntry(pair.second); 141 | } 142 | return mean_algorithm->PartialResult(privacy_budget); 143 | } 144 | 145 | - (nonnull OCDPStatusOr *)PrivateCountAbove:(double)privacy_budget 146 | limit:(int)max { 147 | base::StatusOr result = [self _PrivateCountAbove:privacy_budget 148 | limit:max]; 149 | OCDPStatusOr *status = [[OCDPStatusOr alloc] initWithCppStatusOr:result]; 150 | return status; 151 | } 152 | 153 | - (base::StatusOr)_PrivateCountAbove:(double)privacy_budget 154 | limit:(int)max { 155 | if (privacy_budget_ < privacy_budget) { 156 | return base::InvalidArgumentError("Not enough privacy budget."); 157 | } 158 | privacy_budget_ -= privacy_budget; 159 | ASSIGN_OR_RETURN(std::unique_ptr> count_algorithm, 160 | Count::Builder().SetEpsilon(epsilon_).Build()); 161 | 162 | for (const auto &pair : carrots_per_animal_) { 163 | if (pair.second > max) { 164 | count_algorithm->AddEntry(pair.first); 165 | } 166 | } 167 | return count_algorithm->PartialResult(privacy_budget); 168 | } 169 | 170 | - (nonnull OCDPStatusOr *)PrivateMax:(double)privacy_budget { 171 | base::StatusOr result = [self _PrivateMax:privacy_budget]; 172 | OCDPStatusOr *status = [[OCDPStatusOr alloc] initWithCppStatusOr:result]; 173 | return status; 174 | } 175 | 176 | - (base::StatusOr)_PrivateMax:(double)privacy_budget { 177 | if (privacy_budget_ < privacy_budget) { 178 | return base::InvalidArgumentError("Not enough privacy budget."); 179 | } 180 | privacy_budget_ -= privacy_budget; 181 | ASSIGN_OR_RETURN(std::unique_ptr> max_algorithm, 182 | continuous::Max::Builder() 183 | .SetEpsilon(epsilon_) 184 | .SetLower(0) 185 | .SetUpper(150) 186 | .Build()); 187 | for (const auto &pair : carrots_per_animal_) { 188 | max_algorithm->AddEntry(pair.second); 189 | } 190 | return max_algorithm->PartialResult(privacy_budget); 191 | } 192 | 193 | @end 194 | -------------------------------------------------------------------------------- /src/OCDP/OCDPConfidenceInterval.h: -------------------------------------------------------------------------------- 1 | // 2 | // OCDPConfidenceInterval.h 3 | // SwiftDP 4 | // 5 | // Created by Madhava Jay on 6/5/20. 6 | // 7 | 8 | @interface OCDPConfidenceInterval : NSObject 9 | 10 | - (double)lower_bound; 11 | - (double)upper_bound; 12 | - (double)confidence_level; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /src/OCDP/OCDPConfidenceInterval.mm: -------------------------------------------------------------------------------- 1 | // 2 | // OCDPConfidenceInterval.m 3 | // SwiftDP 4 | // 5 | // Created by Madhava Jay on 6/5/20. 6 | // 7 | 8 | #import 9 | 10 | #import "OCDPConfidenceInterval.h" 11 | 12 | #include "differential_privacy/proto/data.pb.h" 13 | #include "differential_privacy/proto/util.h" 14 | 15 | using namespace differential_privacy; 16 | 17 | @interface OCDPConfidenceInterval () { 18 | ConfidenceInterval *cpp_ConfidenceInterval; 19 | } 20 | - (instancetype)initWithCppConfidenceInterval: 21 | (ConfidenceInterval)confidence_interval; 22 | @end 23 | 24 | @implementation OCDPConfidenceInterval 25 | 26 | - (instancetype)initWithCppConfidenceInterval: 27 | (ConfidenceInterval)confidence_interval { 28 | self = [super init]; 29 | if (self) { 30 | // Start PIMPL 31 | cpp_ConfidenceInterval = new ConfidenceInterval(confidence_interval); 32 | if (!cpp_ConfidenceInterval) { 33 | self = nil; // destroy Wrapper 34 | } 35 | // End PIMPL 36 | } 37 | return self; 38 | } 39 | 40 | - (double)lower_bound { 41 | return cpp_ConfidenceInterval->lower_bound(); 42 | } 43 | 44 | - (double)upper_bound { 45 | return cpp_ConfidenceInterval->upper_bound(); 46 | } 47 | 48 | - (double)confidence_level { 49 | return cpp_ConfidenceInterval->confidence_level(); 50 | } 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /src/OCDP/OCDPErrorReport.h: -------------------------------------------------------------------------------- 1 | // 2 | // OCDPErrorReport.h 3 | // SwiftDP 4 | // 5 | // Created by Madhava Jay on 6/5/20. 6 | // 7 | 8 | #import "OCDPBoundingReport.h" 9 | #import "OCDPConfidenceInterval.h" 10 | 11 | @interface OCDPErrorReport : NSObject 12 | 13 | - (nullable OCDPBoundingReport *)bounding_report; 14 | - (nullable OCDPConfidenceInterval *)noise_confidence_interval; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /src/OCDP/OCDPErrorReport.mm: -------------------------------------------------------------------------------- 1 | // 2 | // OCDPErrorReport.m 3 | // SwiftDP 4 | // 5 | // Created by Madhava Jay on 6/5/20. 6 | // 7 | 8 | #import "OCDPErrorReport.h" 9 | #import "OCDPBoundingReport.mm" 10 | #import "OCDPConfidenceInterval.mm" 11 | #import 12 | 13 | #include "differential_privacy/proto/data.pb.h" 14 | #include "differential_privacy/proto/util.h" 15 | 16 | using namespace differential_privacy; 17 | 18 | @interface OCDPErrorReport () { 19 | Output_ErrorReport *cpp_Errorreport; 20 | } 21 | - (instancetype)initWithCppErrorReport:(Output_ErrorReport)errorreport; 22 | @end 23 | 24 | @implementation OCDPErrorReport 25 | 26 | - (instancetype)initWithCppErrorReport:(Output_ErrorReport)errorreport { 27 | self = [super init]; 28 | if (self) { 29 | // Start PIMPL 30 | cpp_Errorreport = new Output_ErrorReport(errorreport); 31 | if (!cpp_Errorreport) { 32 | self = nil; // destroy Wrapper 33 | } 34 | // End PIMPL 35 | } 36 | return self; 37 | } 38 | 39 | - (nullable OCDPBoundingReport *)bounding_report { 40 | BoundingReport bounding_report = cpp_Errorreport->bounding_report(); 41 | OCDPBoundingReport *objcBoundingReport = 42 | [[OCDPBoundingReport alloc] initWithCppBoundingReport:bounding_report]; 43 | return objcBoundingReport; 44 | } 45 | 46 | - (nullable OCDPConfidenceInterval *)noise_confidence_interval { 47 | ConfidenceInterval confidence_interval = 48 | cpp_Errorreport->noise_confidence_interval(); 49 | OCDPConfidenceInterval *objcConfidenceInterval = 50 | [[OCDPConfidenceInterval alloc] 51 | initWithCppConfidenceInterval:confidence_interval]; 52 | return objcConfidenceInterval; 53 | } 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /src/OCDP/OCDPOutput.h: -------------------------------------------------------------------------------- 1 | // 2 | // OCDPOutput.h 3 | // SwiftDP 4 | // 5 | // Created by Madhava Jay on 5/5/20. 6 | // 7 | 8 | #import "OCDPErrorReport.h" 9 | #import 10 | 11 | @interface OCDPOutput : NSObject 12 | 13 | - (double)getDouble; 14 | - (int)getInt; 15 | - (nonnull NSString *)DebugString; 16 | - (nullable OCDPErrorReport *)error_report; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /src/OCDP/OCDPOutput.mm: -------------------------------------------------------------------------------- 1 | // 2 | // OCDPOutput.mm 3 | // SwiftDP 4 | // 5 | // Created by Madhava Jay on 5/5/20. 6 | // 7 | 8 | #import "OCDPOutput.h" 9 | #import "OCDPErrorReport.mm" 10 | #import "OCDPUtil.mm" 11 | #import 12 | 13 | #include "differential_privacy/proto/data.pb.h" 14 | #include "differential_privacy/proto/util.h" 15 | 16 | using namespace differential_privacy; 17 | using differential_privacy::GetValue; 18 | 19 | @interface OCDPOutput () { 20 | Output *cpp_Output; 21 | } 22 | - (instancetype)initWithCppOutput:(Output)output; 23 | @end 24 | 25 | @implementation OCDPOutput 26 | 27 | - (instancetype)initWithCppOutput:(Output)output { 28 | self = [super init]; 29 | if (self) { 30 | // Start PIMPL 31 | cpp_Output = new Output(output); 32 | if (!cpp_Output) { 33 | self = nil; // destroy Wrapper 34 | } 35 | // End PIMPL 36 | } 37 | return self; 38 | } 39 | 40 | - (double)getDouble { 41 | return GetValue(*cpp_Output); 42 | } 43 | 44 | - (int)getInt { 45 | return GetValue(*cpp_Output); 46 | } 47 | 48 | - (nonnull NSString *)DebugString { 49 | return [NSString fromCpp:cpp_Output->DebugString()]; 50 | } 51 | 52 | - (nullable OCDPErrorReport *)error_report { 53 | Output_ErrorReport cpp_Errorreport = cpp_Output->error_report(); 54 | OCDPErrorReport *objcErrorReport = 55 | [[OCDPErrorReport alloc] initWithCppErrorReport:cpp_Errorreport]; 56 | return objcErrorReport; 57 | } 58 | 59 | @end 60 | -------------------------------------------------------------------------------- /src/OCDP/OCDPStatus.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | typedef NS_ENUM(NSUInteger, DPStatus) { 4 | DPStatuskOk = 0, 5 | DPStatuskCancelled = 1, 6 | DPStatuskUnknown = 2, 7 | DPStatuskInvalidArgument = 3, 8 | DPStatuskDeadlineExceeded = 4, 9 | DPStatuskNotFound = 5, 10 | DPStatuskAlreadyExists = 6, 11 | DPStatuskPermissionDenied = 7, 12 | DPStatuskResourceExhausted = 8, 13 | DPStatuskFailedPrecondition = 9, 14 | DPStatuskAborted = 10, 15 | DPStatuskOutOfRange = 11, 16 | DPStatuskUnimplemented = 12, 17 | DPStatuskInternal = 13, 18 | DPStatuskUnavailable = 14, 19 | DPStatuskDataLoss = 15, 20 | DPStatuskUnauthenticated = 17, 21 | DPStatuskDoNotUseReservedForFutureExpansionUseDefaultInSwitchInstead_ = 20, 22 | DPStatuskSWIFTDPEnumMatchNotFound = 255 23 | }; 24 | 25 | @interface OCDPStatus : NSObject 26 | - (nonnull NSString *)toString; 27 | - (nonnull NSString *)message; 28 | - (DPStatus)code; 29 | - (nullable instancetype)initWithStatus:(DPStatus)code 30 | AndMessage:(NSString *)message; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /src/OCDP/OCDPStatus.mm: -------------------------------------------------------------------------------- 1 | #import "OCDPStatus.h" 2 | #import "OCDPUtil.mm" 3 | #import "differential_privacy/base/status.h" 4 | 5 | using namespace differential_privacy; 6 | 7 | @interface OCDPStatus () { 8 | base::Status *cpp_Status; 9 | } 10 | - (nullable instancetype)initWithCppStatus:(base::Status)status; 11 | @end 12 | 13 | @implementation OCDPStatus 14 | 15 | - (nullable instancetype)initWithCppStatus:(base::Status)status { 16 | self = [super init]; 17 | if (self) { 18 | // Start PIMPL 19 | cpp_Status = new base::Status(status); 20 | if (!cpp_Status) { 21 | self = nil; // destroy Wrapper 22 | } 23 | // End PIMPL 24 | } 25 | return self; 26 | } 27 | 28 | - (nullable instancetype)initWithStatus:(DPStatus)code 29 | AndMessage:(NSString *)message { 30 | self = [super init]; 31 | if (self) { 32 | // Start PIMPL 33 | cpp_Status = 34 | new base::Status([OCDPStatus dpStatusToCode:code], [message toCpp]); 35 | if (!cpp_Status) { 36 | self = nil; // destroy Wrapper 37 | } 38 | // End PIMPL 39 | } 40 | return self; 41 | } 42 | 43 | - (nonnull NSString *)toString { 44 | return [NSString fromCppStringView:cpp_Status->ToString()]; 45 | } 46 | 47 | - (DPStatus)code { 48 | return [OCDPStatus dpStatusFromCode:cpp_Status->code()]; 49 | } 50 | 51 | - (nonnull NSString *)message { 52 | return [NSString fromCppStringView:cpp_Status->message()]; 53 | } 54 | 55 | - (void)dealloc { 56 | delete cpp_Status; 57 | } 58 | 59 | @end 60 | -------------------------------------------------------------------------------- /src/OCDP/OCDPStatusOr.h: -------------------------------------------------------------------------------- 1 | // 2 | // OCDPStatusOr.h 3 | // SwiftDP 4 | // 5 | // Created by Madhava Jay on 1/5/20. 6 | // 7 | 8 | #import "OCDPOutput.h" 9 | #import "OCDPStatus.h" 10 | #import 11 | 12 | @interface OCDPStatusOr : NSObject 13 | 14 | - (BOOL)ok; 15 | - (nonnull OCDPStatus *)status; 16 | - (id)boundingReport; 17 | - (nullable OCDPOutput *)value; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /src/OCDP/OCDPStatusOr.mm: -------------------------------------------------------------------------------- 1 | // 2 | // OCDPStatusOr.m 3 | // SwiftDP 4 | // 5 | // Created by Madhava Jay on 1/5/20. 6 | // 7 | 8 | #import "OCDPStatusOr.h" 9 | #import "OCDPOutput.mm" 10 | #import "OCDPStatus.mm" 11 | #import 12 | 13 | #import "differential_privacy/base/status.h" 14 | #include "differential_privacy/base/statusor.h" 15 | #include "differential_privacy/proto/data.pb.h" 16 | #include "differential_privacy/proto/util.h" 17 | 18 | using namespace differential_privacy; 19 | using differential_privacy::GetValue; 20 | 21 | @interface OCDPStatusOr () { 22 | OCDPStatus *status; 23 | base::StatusOr *cpp_StatusOr; 24 | } 25 | - (instancetype)initWithCppStatusOr:(base::StatusOr)statusOr; 26 | @end 27 | 28 | @implementation OCDPStatusOr 29 | 30 | - (instancetype)initWithCppStatusOr:(base::StatusOr)statusOr { 31 | self = [super init]; 32 | if (self) { 33 | // Start PIMPL 34 | cpp_StatusOr = new base::StatusOr(statusOr); 35 | base::Status cpp_Status = statusOr.status(); 36 | status = [[OCDPStatus alloc] initWithCppStatus:cpp_Status]; 37 | if (!cpp_StatusOr) { 38 | self = nil; // destroy Wrapper 39 | } 40 | // End PIMPL 41 | } 42 | return self; 43 | } 44 | 45 | - (BOOL)ok { 46 | if (status != nil && status.code == DPStatuskOk) { 47 | return YES; 48 | } 49 | return NO; 50 | } 51 | 52 | - (OCDPStatus *)status { 53 | return status; 54 | } 55 | 56 | - (double)getDouble { 57 | Output output = cpp_StatusOr->ValueOrDie(); 58 | return GetValue(output); 59 | } 60 | 61 | - (nullable OCDPOutput *)value { 62 | Output output = cpp_StatusOr->ValueOrDie(); 63 | OCDPOutput *objcOutput = [[OCDPOutput alloc] initWithCppOutput:output]; 64 | return objcOutput; 65 | } 66 | 67 | @end 68 | -------------------------------------------------------------------------------- /src/OCDP/OCDPUtil.mm: -------------------------------------------------------------------------------- 1 | // 2 | // OCDPUtil.mm 3 | // SwiftDP 4 | // 5 | // Created by Madhava Jay on 22/4/20. 6 | // 7 | 8 | #import "OCDPStatus.h" 9 | #include "absl/strings/string_view.h" 10 | #include "differential_privacy/base/statusor.h" 11 | #import 12 | #include 13 | #include 14 | 15 | using namespace std; 16 | using namespace differential_privacy; 17 | 18 | #pragma mark - Static C Utils 19 | 20 | class DPUtil { 21 | public: 22 | static NSNumber *convertToNSInteger(int cpp_int) { 23 | return [NSNumber numberWithInt:cpp_int]; 24 | } 25 | 26 | static NSString *convertToNSString(string cpp_str) { 27 | return [NSString stringWithCString:cpp_str.c_str() 28 | encoding:[NSString defaultCStringEncoding]]; 29 | } 30 | 31 | static NSDictionary * 32 | convertToDictionary(map cpp_map) { 33 | NSMutableDictionary *dict = [NSMutableDictionary new]; 34 | for (auto element = cpp_map.begin(); element != cpp_map.end(); element++) { 35 | [dict setObject:DPUtil::convertToNSInteger(element->second) 36 | forKey:DPUtil::convertToNSString(element->first)]; 37 | } 38 | return dict; 39 | } 40 | 41 | static map convertFromDictionary(NSDictionary *objcDict) { 42 | map cpp_map; 43 | for (id key in objcDict) { 44 | NSNumber *num = [objcDict objectForKey:key]; 45 | cpp_map.insert( 46 | pair(string([key UTF8String]), [num intValue])); 47 | } 48 | return cpp_map; 49 | } 50 | }; 51 | 52 | #pragma mark - NSString 53 | 54 | @interface NSString (DPUtils) 55 | + (instancetype)fromCpp:(string)cpp_str; 56 | + (instancetype)fromCppStringView:(absl::string_view)cpp_str; 57 | - (string)toCpp; 58 | @end 59 | 60 | @implementation NSString (DPUtils) 61 | + (instancetype)fromCpp:(string)cpp_str { 62 | return [NSString stringWithCString:cpp_str.c_str() 63 | encoding:[NSString defaultCStringEncoding]]; 64 | } 65 | + (instancetype)fromCppStringView:(absl::string_view)cpp_str { 66 | return [NSString fromCpp:static_cast(cpp_str)]; 67 | } 68 | - (string)toCpp { 69 | return string([self UTF8String]); 70 | } 71 | @end 72 | 73 | #pragma mark - NSNumber 74 | 75 | @interface NSNumber (DPUtils) 76 | + (instancetype)fromCppInt:(int)cpp_str; 77 | - (int)toCppInt; 78 | @end 79 | 80 | @implementation NSNumber (DPUtils) 81 | + (instancetype)fromCppInt:(int)cpp_int { 82 | return [NSNumber numberWithInt:cpp_int]; 83 | } 84 | - (int)toCppInt { 85 | return [self intValue]; 86 | } 87 | @end 88 | 89 | #pragma mark - NSDictionary 90 | 91 | @interface NSDictionary (DPUtils) 92 | + (instancetype)fromCppMap:(map)cpp_map; 93 | - (map)toCppMap; 94 | @end 95 | 96 | @implementation NSDictionary (DPUtils) 97 | + (instancetype)fromCppMap:(map)cpp_map { 98 | NSMutableDictionary *dict = [NSMutableDictionary new]; 99 | for (auto element = cpp_map.begin(); element != cpp_map.end(); element++) { 100 | [dict setObject:DPUtil::convertToNSInteger(element->second) 101 | forKey:DPUtil::convertToNSString(element->first)]; 102 | } 103 | return dict; 104 | } 105 | - (map)toCppMap { 106 | map cpp_map; 107 | for (id key in self) { 108 | NSNumber *num = [self objectForKey:key]; 109 | cpp_map.insert(pair(string([key UTF8String]), [num intValue])); 110 | } 111 | return cpp_map; 112 | } 113 | @end 114 | 115 | #pragma mark - OCDPStatus 116 | 117 | @interface OCDPStatus (DPUtils) 118 | + (instancetype)fromCppStatus:(base::Status)cpp_status; 119 | + (DPStatus)dpStatusFromInt:(NSUInteger)cpp_int; 120 | @end 121 | 122 | @implementation OCDPStatus (DPUtils) 123 | + (instancetype)fromCppStatus:(base::Status)cpp_status { 124 | DPStatus statusCode = [OCDPStatus dpStatusFromCode:cpp_status.code()]; 125 | NSString *message = [NSString fromCppStringView:cpp_status.message()]; 126 | return [[OCDPStatus alloc] initWithStatus:statusCode AndMessage:message]; 127 | } 128 | 129 | + (DPStatus)dpStatusFromCode:(base::StatusCode)code { 130 | int cpp_int = static_cast(code); 131 | if (cpp_int < 0 || 20 < cpp_int) { 132 | cpp_int = 255; // bad enum 133 | } 134 | return [self dpStatusFromInt:cpp_int]; 135 | } 136 | 137 | + (DPStatus)dpStatusFromInt:(int)cpp_int { 138 | return (DPStatus)cpp_int; 139 | } 140 | 141 | + (base::StatusCode)dpStatusToCode:(DPStatus)code { 142 | return static_cast(code); 143 | } 144 | 145 | @end 146 | -------------------------------------------------------------------------------- /src/SwiftDP/BUILD: -------------------------------------------------------------------------------- 1 | load("@build_bazel_rules_apple//apple:ios.bzl", "ios_static_framework") 2 | load("@build_bazel_rules_apple//apple:versioning.bzl", "apple_bundle_version") 3 | load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") 4 | 5 | # FRAMEWORKS 6 | # Define the version of the framework 7 | apple_bundle_version( 8 | name = "framework-version", 9 | build_version = "0.1", 10 | short_version_string = "0.1", 11 | ) 12 | 13 | # Swift Only Framework 14 | swift_library( 15 | name = "dp-swift", 16 | module_name = "SwiftDP", # this is the name space for import SwiftDP 17 | srcs = [ 18 | "fake.swift" 19 | ], 20 | deps = [ 21 | "//src/OCDP:dp-objc" 22 | ], 23 | alwayslink = True, 24 | linkopts = ["-lc++"], 25 | visibility = ["//visibility:public"] 26 | ) 27 | 28 | ios_static_framework( 29 | name = "SwiftDP", 30 | bundle_name = "SwiftDP", 31 | families = [ 32 | "iphone", 33 | "ipad", 34 | ], 35 | linkopts = ["-lc++"], 36 | minimum_os_version = "13.0", 37 | version = ":framework-version", 38 | deps = [":dp-swift"], 39 | visibility = ["//visibility:public"] 40 | ) -------------------------------------------------------------------------------- /src/SwiftDP/fake.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import OCDP 3 | 4 | public struct Fake { 5 | func fake() { 6 | let carrotData: [String: NSNumber] = ["Aardvark": 1, "Albatross": 88, "Alligator": 35, "Alpaca": 99, "Ant": 69, "Anteater": 14, "Antelope": 77, "Ape": 53, "Armadillo": 94, "Baboon": 67, "Badger": 92, "Barracuda": 87, "Bat": 70, "Bear": 31, "Beaver": 14, "Bee": 14, "Bison": 61, "Boar": 57, "Buffalo": 68, "Butterfly": 13, "Camel": 21, "Caribou": 38, "Cat": 92, "Caterpillar": 39, "Cattle": 46, "Chamois": 36, "Cheetah": 23, "Chicken": 76, "Chimpanzee": 8, "Chinchilla": 69, "Chough": 35, "Clam": 83, "Capybara": 40, "Cobra": 74, "Cockroach": 17, "Cod": 77, "Cormorant": 52, "Coyote": 31, "Crab": 14, "Crane": 40, "Crocodile": 46, "Crow": 99, "Curlew": 44, "Deer": 15, "Dinosaur": 89, "Dog": 36, "Dogfish": 98, "Dolphin": 20, "Donkey": 56, "Dove": 90, "Dragonfly": 5, "Duck": 75, "Dugong": 56, "Eagle": 23, "Echidna": 49, "Eel": 83, "Elephant": 55, "Elk": 22, "Emu": 7, "Falcon": 16, "Ferret": 91, "Finch": 80, "Fish": 21, "Flamingo": 56, "Fly": 10, "Fox": 28, "Frog": 29, "Gazelle": 19, "Gerbil": 73, "Giraffe": 45, "Gnat": 5, "Goat": 20, "Goldfinch": 28, "Goldfish": 45, "Goose": 39, "Gorilla": 64, "Grasshopper": 22, "Gull": 7, "Hamster": 30, "Hare": 10, "Hawk": 48, "Hedgehog": 60, "Heron": 73, "Herring": 82, "Hippopotamus": 96, "Hornet": 82, "Horse": 38, "Hummingbird": 84, "Hyena": 39, "Jackal": 12, "Jaguar": 75, "Jay": 75, "Kangaroo": 45, "Koala": 87, "Lark": 91, "Lemur": 33, "Leopard": 40, "Lion": 0, "Llama": 67, "Lobster": 63, "Locust": 16, "Mallard": 93, "Manatee": 19, "Marten": 72, "Meerkat": 46, "Mole": 73, "Monkey": 98, "Moose": 86, "Mosquito": 3, "Mouse": 64, "Mule": 94, "Narwhal": 75, "Newt": 2, "Nightingale": 87, "Octopus": 74, "Opossum": 79, "Ostrich": 56, "Otter": 51, "Owl": 77, "Ox": 81, "Oyster": 42, "Panther": 90, "Parrot": 96, "Peafowl": 4, "Pelican": 58, "Penguin": 73, "Pheasant": 27, "Pig": 56, "Pigeon": 80, "Porcupine": 10, "Porpoise": 35, "Quail": 86, "Rabbit": 100, "Raccoon": 16, "Rail": 7, "Ram": 30, "Rat": 84, "Raven": 50, "Rhinoceros": 86, "Salamander": 21, "Salmon": 15, "Sardine": 66, "Scorpion": 75, "Seahorse": 71, "Seal": 56, "Shark": 52, "Sheep": 99, "Shrew": 45, "Shrimp": 84, "Skunk": 99, "Snail": 51, "Snake": 37, "Spider": 96, "Squid": 90, "Squirrel": 92, "Starling": 80, "Stingray": 96, "Stinkbug": 31, "Stork": 39, "Swallow": 2, "Swan": 68, "Tapir": 53, "Tiger": 47, "Toad": 82, "Trout": 51, "Turkey": 57, "Turtle": 10, "Viper": 28, "Vulture": 91, "Walrus": 94, "Wasp": 51, "Weasel": 20, "Whale": 87, "Wolf": 81, "Wolverine": 36, "Wombat": 84, "Woodcock": 54, "Woodpecker": 7, "Worm": 42, "Wren": 55, "Yak": 60, "Zebra": 7] 7 | 8 | let defaultEpsilon = log2(3.0) 9 | let epsilon = defaultEpsilon * 4 10 | let reporter = OCDPCarrotReporter(epsilon: epsilon, andData: carrotData) 11 | } 12 | } -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "===========================================" 3 | echo "Running: bazel clean --expunge" 4 | echo "===========================================" 5 | bazel clean --expunge 6 | echo "===========================================" 7 | echo "Running: build_frameworks.sh" 8 | source build_frameworks.sh 9 | echo "Bazel Dependencies Fetched and Built" 10 | echo "===========================================" 11 | echo "Running: xcodegen" 12 | cd xcode && xcodegen generate 13 | echo "Xcode Project Generated 🔧" 14 | echo "===========================================" 15 | echo "" 16 | echo "Setup Complete 🚀" 17 | echo "Run:" 18 | echo "open ./xcode/SwiftDP-App.xcodeproj" 19 | echo "" -------------------------------------------------------------------------------- /xcode/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "fastlane" 4 | -------------------------------------------------------------------------------- /xcode/app/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SwiftDP_Example 4 | // 5 | // Created by Madalin Mamuleanu on 09/04/2020. 6 | // Copyright © 2020 OpenMined. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 15 | // Override point for customization after application launch. 16 | return true 17 | } 18 | 19 | // MARK: UISceneSession Lifecycle 20 | 21 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 22 | // Called when a new scene session is being created. 23 | // Use this method to select a configuration to create the new scene with. 24 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 25 | } 26 | 27 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 28 | // Called when the user discards a scene session. 29 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 30 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /xcode/app/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /xcode/app/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /xcode/app/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /xcode/app/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /xcode/app/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | UISceneStoryboardFile 37 | Main 38 | 39 | 40 | 41 | 42 | UILaunchStoryboardName 43 | LaunchScreen 44 | UIMainStoryboardFile 45 | Main 46 | UIRequiredDeviceCapabilities 47 | 48 | armv7 49 | 50 | UISupportedInterfaceOrientations 51 | 52 | UIInterfaceOrientationPortrait 53 | UIInterfaceOrientationLandscapeLeft 54 | UIInterfaceOrientationLandscapeRight 55 | 56 | UISupportedInterfaceOrientations~ipad 57 | 58 | UIInterfaceOrientationPortrait 59 | UIInterfaceOrientationPortraitUpsideDown 60 | UIInterfaceOrientationLandscapeLeft 61 | UIInterfaceOrientationLandscapeRight 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /xcode/app/OCDP-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to 3 | // expose to Swift. 4 | // 5 | 6 | #import "Sandbox.h" 7 | #import 8 | -------------------------------------------------------------------------------- /xcode/app/Sandbox.h: -------------------------------------------------------------------------------- 1 | // 2 | // Sandbox.h 3 | // SwiftDP-App 4 | // 5 | // Created by Madhava Jay on 14/4/20. 6 | // 7 | 8 | #import 9 | 10 | @interface Sandbox : NSObject 11 | 12 | - (void)test; 13 | @end 14 | -------------------------------------------------------------------------------- /xcode/app/Sandbox.mm: -------------------------------------------------------------------------------- 1 | // 2 | // Sandbox.m 3 | // SwiftDP-App 4 | // 5 | // Created by Madhava Jay on 14/4/20. 6 | // 7 | 8 | #import "Sandbox.h" 9 | #import "string" 10 | #import 11 | 12 | template T myMax(T x, T y) { return (x > y) ? x : y; } 13 | 14 | @interface Sandbox () 15 | @end 16 | 17 | @implementation Sandbox 18 | 19 | - (void)test { 20 | NSLog(@"%@", @"sandbox"); 21 | int a = myMax(3, 7); 22 | double b = myMax(3.0, 7.0); 23 | char c = myMax('g', 'e'); 24 | NSLog(@"got int %i got float %f got char %s", a, b, &c); 25 | } 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /xcode/app/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // SwiftDP_Example 4 | // 5 | // Created by Madalin Mamuleanu on 09/04/2020. 6 | // Copyright © 2020 OpenMined. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 12 | 13 | var window: UIWindow? 14 | 15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 16 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 17 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 18 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 19 | guard let _ = (scene as? UIWindowScene) else { return } 20 | } 21 | 22 | func sceneDidDisconnect(_ scene: UIScene) { 23 | // Called as the scene is being released by the system. 24 | // This occurs shortly after the scene enters the background, or when its session is discarded. 25 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 26 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 27 | } 28 | 29 | func sceneDidBecomeActive(_ scene: UIScene) { 30 | // Called when the scene has moved from an inactive state to an active state. 31 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 32 | } 33 | 34 | func sceneWillResignActive(_ scene: UIScene) { 35 | // Called when the scene will move from an active state to an inactive state. 36 | // This may occur due to temporary interruptions (ex. an incoming phone call). 37 | } 38 | 39 | func sceneWillEnterForeground(_ scene: UIScene) { 40 | // Called as the scene transitions from the background to the foreground. 41 | // Use this method to undo the changes made on entering the background. 42 | } 43 | 44 | func sceneDidEnterBackground(_ scene: UIScene) { 45 | // Called as the scene transitions from the foreground to the background. 46 | // Use this method to save data, release shared resources, and store enough scene-specific state information 47 | // to restore the scene back to its current state. 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /xcode/app/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // SwiftDP_Example 4 | // 5 | // Created by Madalin Mamuleanu on 09/04/2020. 6 | // Copyright © 2020 OpenMined. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | // Do any additional setup after loading the view. 16 | 17 | let sandbox = Sandbox() 18 | print("Running objective-c++ sandbox") 19 | sandbox.test() 20 | 21 | // test status 22 | let status = DPStatus.statuskUnknown // 2 23 | let message = "Test Status" 24 | if let status = OCDPStatus(status: status, andMessage: message) { 25 | print("Status Code: \(status.code())") 26 | print("Status Code: \(String(describing: status.message()))") 27 | } 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /xcode/carthage/OCDP.json: -------------------------------------------------------------------------------- 1 | { 2 | "0.0.1": "https://github.com/OpenMined/SwiftDP/releases/download/0.0.1/OCDP.zip" 3 | } 4 | -------------------------------------------------------------------------------- /xcode/fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | # This file contains the fastlane.tools configuration 2 | # You can find the documentation at https://docs.fastlane.tools 3 | # 4 | # For a list of all available actions, check out 5 | # 6 | # https://docs.fastlane.tools/actions 7 | # 8 | # For a list of all available plugins, check out 9 | # 10 | # https://docs.fastlane.tools/plugins/available-plugins 11 | # 12 | 13 | # Uncomment the line if you want fastlane to automatically update itself 14 | # update_fastlane 15 | 16 | default_platform(:ios) 17 | 18 | platform :ios do 19 | 20 | desc "Run Unit Tests" 21 | lane :tests do 22 | run_tests(project: "SwiftDP-App.xcodeproj", 23 | devices: ["iPhone 11"], 24 | code_coverage: true, 25 | derived_data_path: "build", 26 | scheme: "SwiftDP-App") 27 | end 28 | 29 | desc "Build Only" 30 | lane :build do 31 | build_app(project: "SwiftDP-App.xcodeproj", 32 | scheme: "SwiftDP-App", 33 | skip_codesigning: true, 34 | skip_archive: true) 35 | end 36 | 37 | end -------------------------------------------------------------------------------- /xcode/project.yml: -------------------------------------------------------------------------------- 1 | name: SwiftDP-App 2 | options: 3 | bundleIdPrefix: org.openmined.SwiftDP-App 4 | fileGroups: 5 | - "../bazel-SwiftDP/external/com_google_absl" 6 | - "../bazel-SwiftDP/external/google_differential_privacy" 7 | targets: 8 | SwiftDP-App: 9 | type: application 10 | platform: iOS 11 | deploymentTarget: "13.0" 12 | sources: [app] 13 | preBuildScripts: 14 | - path: ../clang_format.sh 15 | name: clang-format 16 | - script: | 17 | exec "${PROJECT_DIR}/../build_framework_objc.sh" 18 | name: Bazel Build Objective-C Framework 19 | - script: | 20 | exec "${PROJECT_DIR}/../build_framework_swift.sh" 21 | name: Bazel Build Swift Framework 22 | settings: 23 | base: 24 | INFOPLIST_FILE: app/Info.plist 25 | OTHER_LDFLAGS: -lc++ 26 | MARKETING_VERSION: 1 27 | SWIFT_OBJC_BRIDGING_HEADER: app/OCDP-Bridging-Header.h 28 | dependencies: 29 | - framework: ../bazel-bin/src/OCDP/OCDP_archive-root/OCDP.framework 30 | embed: false 31 | 32 | Dummy: 33 | type: framework 34 | platform: iOS 35 | deploymentTarget: "13.0" 36 | sources: ["../src"] 37 | settings: 38 | USER_HEADER_SEARCH_PATHS: "$PROJECT_DIR/../bazel-SwiftDP/external/**" 39 | 40 | SwiftDP-Unit-Tests: 41 | type: bundle.unit-test 42 | platform: iOS 43 | deploymentTarget: "13.0" 44 | sources: ["tests"] 45 | dependencies: 46 | - target: SwiftDP-App 47 | settings: 48 | base: 49 | SWIFT_OBJC_BRIDGING_HEADER: app/OCDP-Bridging-Header.h 50 | FRAMEWORK_SEARCH_PATHS: ../bazel-bin/src/OCDP/OCDP_archive-root 51 | 52 | schemes: 53 | SwiftDP-App: 54 | build: 55 | targets: 56 | "SwiftDP-App": SwiftDP-App 57 | test: 58 | gatherCoverageData: true 59 | targets: 60 | - name: SwiftDP-Unit-Tests 61 | parallelizable: true 62 | randomExecutionOrder: true 63 | -------------------------------------------------------------------------------- /xcode/tests/SwiftDPTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftDPTests.swift 3 | // SwiftDP 4 | // 5 | // Created by Madhava Jay on 6/5/20. 6 | // 7 | 8 | import XCTest 9 | 10 | class SwiftDPTests: XCTestCase { 11 | 12 | override func setUpWithError() throws { 13 | // Put setup code here. This method is called before the invocation of each test method in the class. 14 | } 15 | 16 | override func tearDownWithError() throws { 17 | // Put teardown code here. This method is called after the invocation of each test method in the class. 18 | } 19 | 20 | func testCarrots() throws { 21 | // create a carrot reporter 22 | let carrotData: [String: NSNumber] = ["Aardvark": 1, "Albatross": 88, "Alligator": 35, "Alpaca": 99, "Ant": 69, "Anteater": 14, "Antelope": 77, "Ape": 53, "Armadillo": 94, "Baboon": 67, "Badger": 92, "Barracuda": 87, "Bat": 70, "Bear": 31, "Beaver": 14, "Bee": 14, "Bison": 61, "Boar": 57, "Buffalo": 68, "Butterfly": 13, "Camel": 21, "Caribou": 38, "Cat": 92, "Caterpillar": 39, "Cattle": 46, "Chamois": 36, "Cheetah": 23, "Chicken": 76, "Chimpanzee": 8, "Chinchilla": 69, "Chough": 35, "Clam": 83, "Capybara": 40, "Cobra": 74, "Cockroach": 17, "Cod": 77, "Cormorant": 52, "Coyote": 31, "Crab": 14, "Crane": 40, "Crocodile": 46, "Crow": 99, "Curlew": 44, "Deer": 15, "Dinosaur": 89, "Dog": 36, "Dogfish": 98, "Dolphin": 20, "Donkey": 56, "Dove": 90, "Dragonfly": 5, "Duck": 75, "Dugong": 56, "Eagle": 23, "Echidna": 49, "Eel": 83, "Elephant": 55, "Elk": 22, "Emu": 7, "Falcon": 16, "Ferret": 91, "Finch": 80, "Fish": 21, "Flamingo": 56, "Fly": 10, "Fox": 28, "Frog": 29, "Gazelle": 19, "Gerbil": 73, "Giraffe": 45, "Gnat": 5, "Goat": 20, "Goldfinch": 28, "Goldfish": 45, "Goose": 39, "Gorilla": 64, "Grasshopper": 22, "Gull": 7, "Hamster": 30, "Hare": 10, "Hawk": 48, "Hedgehog": 60, "Heron": 73, "Herring": 82, "Hippopotamus": 96, "Hornet": 82, "Horse": 38, "Hummingbird": 84, "Hyena": 39, "Jackal": 12, "Jaguar": 75, "Jay": 75, "Kangaroo": 45, "Koala": 87, "Lark": 91, "Lemur": 33, "Leopard": 40, "Lion": 0, "Llama": 67, "Lobster": 63, "Locust": 16, "Mallard": 93, "Manatee": 19, "Marten": 72, "Meerkat": 46, "Mole": 73, "Monkey": 98, "Moose": 86, "Mosquito": 3, "Mouse": 64, "Mule": 94, "Narwhal": 75, "Newt": 2, "Nightingale": 87, "Octopus": 74, "Opossum": 79, "Ostrich": 56, "Otter": 51, "Owl": 77, "Ox": 81, "Oyster": 42, "Panther": 90, "Parrot": 96, "Peafowl": 4, "Pelican": 58, "Penguin": 73, "Pheasant": 27, "Pig": 56, "Pigeon": 80, "Porcupine": 10, "Porpoise": 35, "Quail": 86, "Rabbit": 100, "Raccoon": 16, "Rail": 7, "Ram": 30, "Rat": 84, "Raven": 50, "Rhinoceros": 86, "Salamander": 21, "Salmon": 15, "Sardine": 66, "Scorpion": 75, "Seahorse": 71, "Seal": 56, "Shark": 52, "Sheep": 99, "Shrew": 45, "Shrimp": 84, "Skunk": 99, "Snail": 51, "Snake": 37, "Spider": 96, "Squid": 90, "Squirrel": 92, "Starling": 80, "Stingray": 96, "Stinkbug": 31, "Stork": 39, "Swallow": 2, "Swan": 68, "Tapir": 53, "Tiger": 47, "Toad": 82, "Trout": 51, "Turkey": 57, "Turtle": 10, "Viper": 28, "Vulture": 91, "Walrus": 94, "Wasp": 51, "Weasel": 20, "Whale": 87, "Wolf": 81, "Wolverine": 36, "Wombat": 84, "Woodcock": 54, "Woodpecker": 7, "Worm": 42, "Wren": 55, "Yak": 60, "Zebra": 7] 23 | 24 | PrintF("\nIt is a new day. Farmer Fred is ready to ask the animals about their " + 25 | "carrot consumption.\n") 26 | 27 | let defaultEpsilon = log2(3.0) 28 | let epsilon = defaultEpsilon * 4 29 | let reporter = OCDPCarrotReporter(epsilon: epsilon, andData: carrotData) 30 | 31 | // Query for the total number of carrots. Notice that we explicitly use 25% of 32 | // our privacy budget. 33 | let privacyBudget1 = reporter.privacyBudget() 34 | PrintF("\nFarmer Fred asks the animals how many total carrots they have " + 35 | "eaten. The animals know the true sum but report the " + 36 | "differentially private sum to Farmer Fred. But first, they ensure " + 37 | "that Farmer Fred still has privacy budget left.\n") 38 | PrintF("\nPrivacy budget remaining: %.2f\n", privacyBudget1) 39 | XCTAssertEqual(privacyBudget1, 1.0) 40 | let trueSum = reporter.sum() 41 | XCTAssertEqual(trueSum, 9649) 42 | 43 | PrintF("True sum: %d\n", trueSum) 44 | var dpSumCompare: Int32 = 0 45 | if let dpsum = reporter.privateSum(0.25).value()?.getInt() { 46 | PrintF("DP sum: %d\n", dpsum) 47 | dpSumCompare = dpsum 48 | } 49 | XCTAssertGreaterThan(dpSumCompare, 9300) 50 | XCTAssertLessThan(dpSumCompare, 9900) 51 | 52 | let privacyBudget2 = reporter.privacyBudget() 53 | PrintF("\nFarmer Fred catches on that the animals are giving him DP results. " + 54 | "He asks for the mean number of carrots eaten, but this time, he wants " + 55 | "some additional accuracy information to build his intuition.\n") 56 | PrintF("\nPrivacy budget remaining: %.2f\n", privacyBudget2) 57 | XCTAssertEqual(privacyBudget2, 0.75) 58 | 59 | let trueMean = reporter.mean() 60 | PrintF("True mean: %.2f\n", trueMean) 61 | XCTAssertEqual(trueMean, 53.02, accuracy: 0.01) 62 | 63 | let mean_status = reporter.privateMean(0.25) 64 | 65 | if !mean_status.ok() { 66 | // note we must use %@ not %s because %s is for c strings 67 | PrintF("Error obtaining mean: %@ - %@\n", mean_status.status().message()) 68 | PrintF( 69 | "The animals were not able to get the private mean with the current " + 70 | "privacy parameters. This is due to the small size of the dataset and " + 71 | "random chance. Please re-run report_the_carrots to try again.\n") 72 | } else { 73 | XCTAssertEqual(mean_status.status().code(), DPStatus.statuskOk) 74 | if let mean_output = mean_status.value(), 75 | let report = mean_output.error_report()?.bounding_report() { 76 | 77 | let mean = mean_output.getDouble() 78 | XCTAssertEqual(mean, 60.00, accuracy: 10.0) 79 | let lower_bound = report.lower_bound() 80 | XCTAssertEqual(lower_bound, 32) 81 | let upper_bound = report.upper_bound() 82 | XCTAssertEqual(upper_bound, 128) 83 | let num_inputs = report.num_inputs() 84 | XCTAssertEqual(num_inputs, 176, accuracy: 50) 85 | let num_outside = report.num_outside() 86 | XCTAssertEqual(num_outside, 46, accuracy: 50) 87 | PrintF("DP mean output:\n%@\n", mean_output.debugString()) 88 | PrintF( 89 | "The animals help Fred interpret the results. %.2f is the DP mean. " + 90 | "Since no bounds were set for the DP mean algorithm, bounds on the " + 91 | "input data were automatically determined. Most of the data fell " + 92 | "between [%d, %d]. Thus, these bounds were used to determine clamping " + 93 | "and global sensitivity. In addition, around %.0f input values fell " + 94 | "inside of these bounds, and around %.0f inputs fell outside of these " + 95 | "bounds. num_inputs and num_outside are themselves DP counts.\n", 96 | mean, lower_bound, upper_bound, num_inputs, num_outside) 97 | } else { 98 | XCTFail("Unable to unpack Mean Status and Bounding Report") 99 | } 100 | } 101 | 102 | // Query for the count with a noise confidence interval. 103 | let privacyBudget3 = reporter.privacyBudget() 104 | PrintF( 105 | "\nFred wonders how many gluttons are in his zoo. How many animals ate " + 106 | "over 90 carrots? And how accurate is the result?\n") 107 | PrintF("\nPrivacy budget remaining: %.2f\n", privacyBudget3) 108 | XCTAssertEqual(privacyBudget3, 0.5) 109 | if let count_output = reporter.privateCount(above: 0.25, limit: 90).value(), 110 | let ci = count_output.error_report()?.noise_confidence_interval() { 111 | 112 | let count = count_output.getInt() 113 | XCTAssertLessThan(count - 20, 5) 114 | let confidence_level = ci.confidence_level() 115 | XCTAssertEqual(confidence_level, 0.95) 116 | let lower_bound = ci.lower_bound() 117 | let range = 2.0 118 | XCTAssertEqual(lower_bound, range * -1, accuracy: 0.2) 119 | let upper_bound = ci.upper_bound() 120 | XCTAssertEqual(upper_bound, range, accuracy: 1.0) 121 | PrintF("True count: %d\n", reporter.count(above: 90)) 122 | PrintF("DP count output:\n%@\n", count_output.debugString()) 123 | PrintF( 124 | "The animals tell Fred that %d is the DP count. [%.2f, %.2f] is the " + 125 | "%.2f confidence interval of the noise added to the count.\n", 126 | count, lower_bound, upper_bound, confidence_level) 127 | } else { 128 | XCTFail("Unable to unpack Private Count and Noise Confidence Interval") 129 | } 130 | 131 | // Query for the maximum. 132 | let privacyBudget4 = reporter.privacyBudget() 133 | let max2 = reporter.max() 134 | PrintF("\n'And how gluttonous is the biggest glutton of them all?' Fred " + 135 | "exclaims. He asks for the maximum number of carrots any animal has " + 136 | "eaten.\n") 137 | PrintF("\nPrivacy budget remaining: %.2f\n", privacyBudget4) 138 | PrintF("True max: %d\n", max2) 139 | XCTAssertEqual(privacyBudget4, 0.25) 140 | XCTAssertEqual(max2, 100) 141 | if let dpmax = reporter.privateMax(0.25).value()?.getInt() { 142 | PrintF("DP max: %d\n", dpmax) 143 | XCTAssertLessThan(abs(dpmax - 100), 30) 144 | } 145 | 146 | // Refuse to query for the count of animals who didn't eat carrots. 147 | let privacyBudget5 = reporter.privacyBudget() 148 | PrintF("\nFred also wonders how many animals are not eating any carrots at " + 149 | "all.\n") 150 | PrintF("\nPrivacy budget remaining: %.2f\n", privacyBudget5) 151 | XCTAssertEqual(privacyBudget5, 0.0) 152 | let status = reporter.privateCount(above: 0.25, limit: 0).status() 153 | XCTAssertEqual(status.code(), DPStatus.statuskInvalidArgument) 154 | PrintF("Error querying for count: %@\n", status.message()) 155 | PrintF("The animals notice that the privacy budget is depleted. They refuse " + 156 | "to answer any more of Fred's questions for risk of violating " + 157 | "privacy.\n") 158 | } 159 | } 160 | 161 | func PrintF(_ params: CVarArg...) { 162 | if let format = params.first as? String { 163 | let args = Array(params.dropFirst()) 164 | print(String(format: format, arguments: args)) 165 | } 166 | } 167 | --------------------------------------------------------------------------------