mirror of
https://github.com/bitwarden/mobile
synced 2025-12-05 23:53:33 +00:00
Compare commits
4 Commits
renovate/l
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b543459f8d | ||
|
|
d0609024df | ||
|
|
a798ae0761 | ||
|
|
7f8b19a0a7 |
1
.github/renovate.json
vendored
1
.github/renovate.json
vendored
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"enabled": false,
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:base",
|
||||
|
||||
18
.github/workflows/build-beta.yml
vendored
18
.github/workflows/build-beta.yml
vendored
@@ -47,9 +47,9 @@ jobs:
|
||||
runs-on: macos-14
|
||||
needs: setup
|
||||
env:
|
||||
ios_folder_path: src/App/Platforms/iOS
|
||||
app_output_name: App
|
||||
app_ci_output_filename: App_x64_Debug
|
||||
_IOS_FOLDER_PATH: src/App/Platforms/iOS
|
||||
_APP_OUTPUT_NAME: App
|
||||
_APP_CI_OUTPUT_FILENAME: App_x64_Debug
|
||||
steps:
|
||||
- name: Set XCode version
|
||||
uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1.6.0
|
||||
@@ -135,7 +135,7 @@ jobs:
|
||||
|
||||
echo "### CFBundleVersion $BUILD_NUMBER" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./${{ env.ios_folder_path }}/Info.plist
|
||||
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./${{ env._IOS_FOLDER_PATH }}/Info.plist
|
||||
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.Extension/Info.plist
|
||||
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.Autofill/Info.plist
|
||||
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.ShareExtension/Info.plist
|
||||
@@ -145,7 +145,7 @@ jobs:
|
||||
- name: Update Entitlements
|
||||
run: |
|
||||
echo "##### Updating Entitlements"
|
||||
perl -0777 -pi.bak -e 's/<key>aps-environment<\/key>\s*<string>development<\/string>/<key>aps-environment<\/key>\n\t<string>beta<\/string>/' ./${{ env.ios_folder_path }}/Entitlements.plist
|
||||
perl -0777 -pi.bak -e 's/<key>aps-environment<\/key>\s*<string>development<\/string>/<key>aps-environment<\/key>\n\t<string>beta<\/string>/' ./${{ env._IOS_FOLDER_PATH }}/Entitlements.plist
|
||||
|
||||
- name: Get certificates
|
||||
run: |
|
||||
@@ -245,8 +245,8 @@ jobs:
|
||||
ARCHIVE_PATH: ./${{ env.main_app_folder_path }}/bin/Debug/${{ env.target-net-version }}-ios/iossimulator-x64
|
||||
EXPORT_PATH: ./bitwarden-export
|
||||
run: |
|
||||
zip -r -q ${{ env.app_ci_output_filename }}.app.zip $ARCHIVE_PATH
|
||||
mv ${{ env.app_ci_output_filename }}.app.zip $EXPORT_PATH
|
||||
zip -r -q ${{ env._APP_CI_OUTPUT_FILENAME }}.app.zip $ARCHIVE_PATH
|
||||
mv ${{ env._APP_CI_OUTPUT_FILENAME }}.app.zip $EXPORT_PATH
|
||||
|
||||
- name: Show Bitwarden Export
|
||||
shell: bash
|
||||
@@ -276,8 +276,8 @@ jobs:
|
||||
- name: Upload .app file for Automation CI
|
||||
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
|
||||
with:
|
||||
name: ${{ env.app_ci_output_filename }}.app.zip
|
||||
path: ./bitwarden-export/${{ env.app_ci_output_filename }}.app.zip
|
||||
name: ${{ env._APP_CI_OUTPUT_FILENAME }}.app.zip
|
||||
path: ./bitwarden-export/${{ env._APP_CI_OUTPUT_FILENAME }}.app.zip
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Install AppCenter CLI
|
||||
|
||||
54
.github/workflows/build.yml
vendored
54
.github/workflows/build.yml
vendored
@@ -1,12 +1,6 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- "l10n_master"
|
||||
- "gh-pages"
|
||||
paths-ignore:
|
||||
- ".github/workflows/**"
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
@@ -70,8 +64,8 @@ jobs:
|
||||
matrix:
|
||||
variant: ["prod", "qa"]
|
||||
env:
|
||||
android_folder_path: src\App\Platforms\Android
|
||||
android_folder_path_bash: src/App/Platforms/Android
|
||||
_ANDROID_FOLDER_PATH: src\App\Platforms\Android
|
||||
_ANDROID_FOLDER_PATH_BASH: src/App/Platforms/Android
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
@@ -126,9 +120,9 @@ jobs:
|
||||
mkdir -p $HOME/secrets
|
||||
|
||||
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \
|
||||
--name app_play-keystore.jks --file ./${{ env.android_folder_path_bash }}/app_play-keystore.jks --output none
|
||||
--name app_play-keystore.jks --file ./${{ env._ANDROID_FOLDER_PATH_BASH }}/app_play-keystore.jks --output none
|
||||
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \
|
||||
--name app_upload-keystore.jks --file ./${{ env.android_folder_path_bash }}/app_upload-keystore.jks --output none
|
||||
--name app_upload-keystore.jks --file ./${{ env._ANDROID_FOLDER_PATH_BASH }}/app_upload-keystore.jks --output none
|
||||
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \
|
||||
--name play_creds.json --file $HOME/secrets/play_creds.json --output none
|
||||
shell: bash
|
||||
@@ -140,7 +134,7 @@ jobs:
|
||||
CONTAINER_NAME: mobile
|
||||
run: |
|
||||
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \
|
||||
--name google-services.json --file ./${{ env.android_folder_path_bash }}/google-services.json --output none
|
||||
--name google-services.json --file ./${{ env._ANDROID_FOLDER_PATH_BASH }}/google-services.json --output none
|
||||
shell: bash
|
||||
|
||||
- name: Increment version
|
||||
@@ -149,7 +143,7 @@ jobs:
|
||||
echo "##### Setting Android Version Code to $BUILD_NUMBER" | tee -a $GITHUB_STEP_SUMMARY
|
||||
|
||||
sed -i "s/android:versionCode=\"1\"/android:versionCode=\"$BUILD_NUMBER\"/" \
|
||||
./${{ env.android_folder_path_bash }}/AndroidManifest.xml
|
||||
./${{ env._ANDROID_FOLDER_PATH_BASH }}/AndroidManifest.xml
|
||||
shell: bash
|
||||
|
||||
- name: Restore packages
|
||||
@@ -193,7 +187,7 @@ jobs:
|
||||
}
|
||||
Write-Output "##### Sign Google Play Bundle Release Configuration"
|
||||
|
||||
$signingUploadKeyStore = "$($env:GITHUB_WORKSPACE)\${{ env.android_folder_path }}\app_upload-keystore.jks"
|
||||
$signingUploadKeyStore = "$($env:GITHUB_WORKSPACE)\${{ env._ANDROID_FOLDER_PATH }}\app_upload-keystore.jks"
|
||||
dotnet publish $projToBuild -c Release -f ${{ env.target-net-version }}-android `
|
||||
/p:AndroidPackageFormats=aab `
|
||||
/p:AndroidKeyStore=true `
|
||||
@@ -210,7 +204,7 @@ jobs:
|
||||
|
||||
Write-Output "##### Sign APK Release Configuration"
|
||||
|
||||
$signingPlayKeyStore = "$($env:GITHUB_WORKSPACE)\${{ env.android_folder_path }}\app_play-keystore.jks"
|
||||
$signingPlayKeyStore = "$($env:GITHUB_WORKSPACE)\${{ env._ANDROID_FOLDER_PATH }}\app_play-keystore.jks"
|
||||
dotnet publish $projToBuild -c Release -f ${{ env.target-net-version }}-android `
|
||||
/p:AndroidKeyStore=true `
|
||||
/p:AndroidSigningKeyStore=$signingPlayKeyStore `
|
||||
@@ -295,9 +289,9 @@ jobs:
|
||||
name: F-Droid Build
|
||||
runs-on: windows-2022
|
||||
env:
|
||||
android_folder_path: src\App\Platforms\Android
|
||||
android_folder_path_bash: src/App/Platforms/Android
|
||||
android_manifest_path: src/App/Platforms/Android/AndroidManifest.xml
|
||||
_ANDROID_FOLDER_PATH: src\App\Platforms\Android
|
||||
_ANDROID_FOLDER_PATH_BASH: src/App/Platforms/Android
|
||||
_ANDROID_MANIFEST_PATH: src/App/Platforms/Android/AndroidManifest.xml
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
@@ -350,7 +344,7 @@ jobs:
|
||||
FILE: app_fdroid-keystore.jks
|
||||
run: |
|
||||
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME --name $FILE \
|
||||
--file ${{ env.android_folder_path_bash }}/$FILE --output none
|
||||
--file ${{ env._ANDROID_FOLDER_PATH_BASH }}/$FILE --output none
|
||||
shell: bash
|
||||
|
||||
- name: Increment version
|
||||
@@ -359,14 +353,14 @@ jobs:
|
||||
echo "##### Setting F-Droid Version Code to $BUILD_NUMBER" | tee -a $GITHUB_STEP_SUMMARY
|
||||
|
||||
sed -i "s/android:versionCode=\"1\"/android:versionCode=\"$BUILD_NUMBER\"/" \
|
||||
./${{ env.android_manifest_path }}
|
||||
./${{ env._ANDROID_MANIFEST_PATH }}
|
||||
shell: bash
|
||||
|
||||
- name: Clean for F-Droid
|
||||
run: |
|
||||
$directoryBuildProps = $($env:GITHUB_WORKSPACE + "/Directory.Build.props");
|
||||
|
||||
$androidManifest = $($env:GITHUB_WORKSPACE + "/${{ env.android_manifest_path }}");
|
||||
$androidManifest = $($env:GITHUB_WORKSPACE + "/${{ env._ANDROID_MANIFEST_PATH }}");
|
||||
|
||||
Write-Output "##### Back up project files"
|
||||
|
||||
@@ -399,7 +393,7 @@ jobs:
|
||||
|
||||
Write-Output "##### Sign FDroid"
|
||||
|
||||
$signingFdroidKeyStore = "$($env:GITHUB_WORKSPACE)\${{ env.android_folder_path }}\app_fdroid-keystore.jks"
|
||||
$signingFdroidKeyStore = "$($env:GITHUB_WORKSPACE)\${{ env._ANDROID_FOLDER_PATH }}\app_fdroid-keystore.jks"
|
||||
dotnet build $projToBuild -c Release -f ${{ env.target-net-version }}-android `
|
||||
/p:AndroidKeyStore=true `
|
||||
/p:AndroidSigningKeyStore=$signingFdroidKeyStore `
|
||||
@@ -439,9 +433,9 @@ jobs:
|
||||
runs-on: macos-14
|
||||
needs: setup
|
||||
env:
|
||||
ios_folder_path: src/App/Platforms/iOS
|
||||
app_output_name: App
|
||||
app_ci_output_filename: App_x64_Debug
|
||||
_IOS_FOLDER_PATH: src/App/Platforms/iOS
|
||||
_APP_OUTPUT_NAME: App
|
||||
_APP_CI_OUTPUT_FILENAME: App_x64_Debug
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
@@ -521,7 +515,7 @@ jobs:
|
||||
BUILD_NUMBER=$((8000 + $GITHUB_RUN_NUMBER))
|
||||
echo "##### Setting iOS CFBundleVersion to $BUILD_NUMBER" | tee -a $GITHUB_STEP_SUMMARY
|
||||
|
||||
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./${{ env.ios_folder_path }}/Info.plist
|
||||
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./${{ env._IOS_FOLDER_PATH }}/Info.plist
|
||||
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.Extension/Info.plist
|
||||
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.Autofill/Info.plist
|
||||
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.ShareExtension/Info.plist
|
||||
@@ -531,7 +525,7 @@ jobs:
|
||||
- name: Update Entitlements
|
||||
run: |
|
||||
echo "##### Updating Entitlements"
|
||||
perl -0777 -pi.bak -e 's/<key>aps-environment<\/key>\s*<string>development<\/string>/<key>aps-environment<\/key>\n\t<string>production<\/string>/' ./${{ env.ios_folder_path }}/Entitlements.plist
|
||||
perl -0777 -pi.bak -e 's/<key>aps-environment<\/key>\s*<string>development<\/string>/<key>aps-environment<\/key>\n\t<string>production<\/string>/' ./${{ env._IOS_FOLDER_PATH }}/Entitlements.plist
|
||||
|
||||
- name: Get certificates
|
||||
run: |
|
||||
@@ -615,8 +609,8 @@ jobs:
|
||||
ARCHIVE_PATH: ./${{ env.main_app_folder_path }}/bin/Debug/${{ env.target-net-version }}-ios/iossimulator-x64
|
||||
EXPORT_PATH: ./bitwarden-export
|
||||
run: |
|
||||
zip -r -q ${{ env.app_ci_output_filename }}.app.zip $ARCHIVE_PATH
|
||||
mv ${{ env.app_ci_output_filename }}.app.zip $EXPORT_PATH
|
||||
zip -r -q ${{ env._APP_CI_OUTPUT_FILENAME }}.app.zip $ARCHIVE_PATH
|
||||
mv ${{ env._APP_CI_OUTPUT_FILENAME }}.app.zip $EXPORT_PATH
|
||||
|
||||
- name: Copy all dSYMs files to upload
|
||||
env:
|
||||
@@ -641,8 +635,8 @@ jobs:
|
||||
- name: Upload .app file for Automation CI
|
||||
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
|
||||
with:
|
||||
name: ${{ env.app_ci_output_filename }}.app.zip
|
||||
path: ./bitwarden-export/${{ env.app_ci_output_filename }}.app.zip
|
||||
name: ${{ env._APP_CI_OUTPUT_FILENAME }}.app.zip
|
||||
path: ./bitwarden-export/${{ env._APP_CI_OUTPUT_FILENAME }}.app.zip
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Install AppCenter CLI
|
||||
|
||||
17
.github/workflows/crowdin-pull.yml
vendored
17
.github/workflows/crowdin-pull.yml
vendored
@@ -3,18 +3,25 @@ name: Crowdin Sync
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs: {}
|
||||
schedule:
|
||||
- cron: '0 0 * * 5'
|
||||
|
||||
jobs:
|
||||
crowdin-sync:
|
||||
name: Autosync
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
env:
|
||||
_CROWDIN_PROJECT_ID: "269690"
|
||||
steps:
|
||||
- name: Generate GH App token
|
||||
uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ secrets.BW_GHAPP_ID }}
|
||||
private-key: ${{ secrets.BW_GHAPP_KEY }}
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
|
||||
- name: Login to Azure - CI Subscription
|
||||
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
||||
@@ -31,7 +38,7 @@ jobs:
|
||||
- name: Download translations
|
||||
uses: crowdin/github-action@61ac8b980551f674046220c3e104bddae2916ac5 # v2.0.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
|
||||
with:
|
||||
config: crowdin.yml
|
||||
|
||||
3
.github/workflows/pr-labeler.yml
vendored
3
.github/workflows/pr-labeler.yml
vendored
@@ -11,6 +11,7 @@ jobs:
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5.0.0
|
||||
- name: Label PR
|
||||
uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5.0.0
|
||||
with:
|
||||
sync-labels: true
|
||||
|
||||
16
.gitignore
vendored
16
.gitignore
vendored
@@ -296,17 +296,11 @@ iOSInjectionProject/
|
||||
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/
|
||||
# Package.pins
|
||||
# Package.resolved
|
||||
# *.xcodeproj
|
||||
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
|
||||
# hence it is not needed unless you have added a package configuration file to your project
|
||||
# .swiftpm
|
||||
|
||||
.build/
|
||||
# xcode / swift package manager - used by the MessagePack lib
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
.swiftpm
|
||||
|
||||
# CocoaPods
|
||||
# We recommend against adding the Pods directory to your .gitignore. However
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,3 +0,0 @@
|
||||
[submodule "lib/MessagePack"]
|
||||
path = lib/MessagePack
|
||||
url = https://github.com/bitwarden/MessagePack.git
|
||||
@@ -16,7 +16,7 @@ The Bitwarden mobile application is written in C# using .NET MAUI.
|
||||
|
||||
# Build/Run
|
||||
|
||||
Please refer to the [Mobile section](https://contributing.bitwarden.com/getting-started/mobile/) of the [Contributing Documentation](https://contributing.bitwarden.com/) for build instructions, recommended tooling, code style tips, and lots of other great information to get you started.
|
||||
Please refer to the [Legacy Contributing Documentation](https://github.com/bitwarden/mobile/tree/main/docs/) for build instructions, recommended tooling, code style tips, and lots of other great information to get you started.
|
||||
|
||||
# We're Hiring!
|
||||
|
||||
|
||||
79
docs/architecture/index.mdx
Normal file
79
docs/architecture/index.mdx
Normal file
@@ -0,0 +1,79 @@
|
||||
---
|
||||
sidebar_position: 5
|
||||
---
|
||||
|
||||
# .NET MAUI (legacy)
|
||||
|
||||
:::warning Legacy
|
||||
|
||||
This represents the **legacy** mobile app architecture done in .NET MAUI.
|
||||
|
||||
:::
|
||||
|
||||
The mobile .NET MAUI clients are Android and iOS applications with extensions and watchOS. They are
|
||||
all located at https://github.com/bitwarden/mobile.
|
||||
|
||||
Principal structure is a follows:
|
||||
|
||||
- `App`: Main .NET MAUI project that shares code between both platforms (Android & iOS). One can see
|
||||
specific platform code under the `Platforms` folder.
|
||||
- `Core`: Shared code having both logical and UI parts of the app. Several classes are a port from
|
||||
the Web Clients to C#. Here one can find most of the UI and logic since it's shared between App
|
||||
and the iOS extensions.
|
||||
- `iOS.Core`: Shared code used by the main iOS app and its extensions
|
||||
- `iOS.Autofill`: iOS extension that handles Autofill
|
||||
- `iOS.Extensions`: iOS extension that handles Autofill from the bottom sheet extension
|
||||
- `iOS.ShareExtension`: iOS extension that handles sharing files through Send
|
||||
- `watchOS`: All the code specific to the watchOS platform
|
||||
- `bitwarden`: Stub iOS app so that the watchOS app has a companion app on Xcode
|
||||
- `bitwarden WatchKit App`: Main Watch app where we set assets.
|
||||
- `bitwarden WatchKit Extension`: All the logic and presentation logic for the Watch app is here
|
||||
|
||||
## Dependencies diagram
|
||||
|
||||
Below is a simplified dependencies diagram of the mobile repository.
|
||||
|
||||
```kroki type=plantuml
|
||||
@startuml
|
||||
skinparam BackgroundColor transparent
|
||||
skinparam componentStyle rectangle
|
||||
skinparam linetype ortho
|
||||
|
||||
title Simplified Dependencies Diagram
|
||||
|
||||
component "Core"
|
||||
component "App"
|
||||
component "iOS.Core"
|
||||
component "iOS.Autofill"
|
||||
component "iOS.Extension"
|
||||
component "iOS.ShareExtension"
|
||||
component "watchOS" {
|
||||
component "bitwarden"
|
||||
component "bitwarden WatchKit App"
|
||||
component "bitwarden WatchKit Extension"
|
||||
}
|
||||
|
||||
[App] --> [Core]
|
||||
|
||||
[iOS.Core] --> [App]
|
||||
|
||||
[App] --> [iOS.Core]
|
||||
[App] --> [iOS.Autofill]
|
||||
[App] --> [iOS.Extension]
|
||||
[App] --> [iOS.ShareExtension]
|
||||
[App] --> [bitwarden WatchKit App]
|
||||
|
||||
[iOS.Autofill] --> [Core]
|
||||
[iOS.Autofill] --> [iOS.Core]
|
||||
|
||||
[iOS.Extension] --> [Core]
|
||||
[iOS.Extension] --> [iOS.Core]
|
||||
|
||||
[iOS.ShareExtension] --> [Core]
|
||||
[iOS.ShareExtension] --> [iOS.Core]
|
||||
|
||||
[bitwarden] --> [bitwarden WatchKit App]
|
||||
[bitwarden WatchKit App] --> [bitwarden WatchKit Extension]
|
||||
|
||||
@enduml
|
||||
```
|
||||
26
docs/architecture/overview.md
Normal file
26
docs/architecture/overview.md
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Overview
|
||||
|
||||
:::warning Legacy
|
||||
|
||||
This represents the **legacy** mobile app overview architecture done in .NET MAUI.
|
||||
|
||||
:::
|
||||
|
||||
The overall architecture of the mobile applications is pretty similar to the
|
||||
[web clients](../../clients/overview.md) one following a layered architecture:
|
||||
|
||||
- State
|
||||
- Services
|
||||
- Presentation
|
||||
|
||||
Even though the State and Services layers are pretty similar to the web ones the Presentation layer
|
||||
differs:
|
||||
|
||||
## Presentation
|
||||
|
||||
The presentation layer is implemented using .NET MAUI for the mobile apps, except for the watchOS
|
||||
one which uses SwiftUI [see ADR](../../adr/0017-watchOS-use-swift.md)
|
||||
186
docs/architecture/watchOS.md
Normal file
186
docs/architecture/watchOS.md
Normal file
@@ -0,0 +1,186 @@
|
||||
# watchOS
|
||||
|
||||
:::warning Legacy
|
||||
|
||||
This represents the **legacy** watchOS app architecture done in .NET MAUI.
|
||||
|
||||
:::
|
||||
|
||||
## Overall architecture
|
||||
|
||||
The watchOS application is organized as follows:
|
||||
|
||||
- `src/watchOS`: All the code specific to the watchOS platform
|
||||
- `bitwarden`: Stub iOS app so that the watchOS app has a companion app on Xcode
|
||||
- `bitwarden WatchKit App`: Main Watch app where we set assets.
|
||||
- `bitwarden WatchKit Extension`: All the logic and presentation logic for the Watch app is here
|
||||
|
||||
So almost all the things related to the watch app will be in the **WatchKit Extension**, the
|
||||
WatchKit App one will be only for assets and some configs.
|
||||
|
||||
Then in the Extension we have a layered architecture:
|
||||
|
||||
- State (it's a really simplified version of the iOS state)
|
||||
- Persistence (here we use `CoreData` to interact with the Database)
|
||||
- Services (totp generation, crypto services and business logic)
|
||||
- Presentation (use `SwiftUI` for the UI with an MVVM pattern)
|
||||
|
||||
## Integration with iOS
|
||||
|
||||
The watchOS app is developed using `Xcode` and `Swift` and we need to integrate it to the .NET MAUI
|
||||
iOS application.
|
||||
|
||||
For this, the `iOS.csproj` has been adapted taking a
|
||||
[solution](https://github.com/xamarin/xamarin-macios/issues/10070#issuecomment-1033428823) provided
|
||||
in the `Xamarin.Forms` GitHub repository and modified to our needs:
|
||||
|
||||
```xml
|
||||
<PropertyGroup>
|
||||
<WatchAppBuildPath Condition=" '$(Configuration)' == 'Debug' ">$(Home)/Library/Developer/Xcode/DerivedData/bitwarden-cbtqsueryycvflfzbsoteofskiyr/Build/Products</WatchAppBuildPath>
|
||||
<WatchAppBuildPath Condition=" '$(Configuration)' != 'Debug' ">$([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)\..'))/watchOS/bitwarden.xcarchive/Products/Applications/bitwarden.app/Watch</WatchAppBuildPath>
|
||||
<WatchAppBundle>Bitwarden.app</WatchAppBundle>
|
||||
<WatchAppConfiguration Condition=" '$(Platform)' == 'iPhoneSimulator' ">watchsimulator</WatchAppConfiguration>
|
||||
<WatchAppConfiguration Condition=" '$(Platform)' == 'iPhone' ">watchos</WatchAppConfiguration>
|
||||
<WatchAppBundleFullPath Condition=" '$(Configuration)' == 'Debug' ">$(WatchAppBuildPath)/$(Configuration)-$(WatchAppConfiguration)/$(WatchAppBundle)</WatchAppBundleFullPath>
|
||||
<WatchAppBundleFullPath Condition=" '$(Configuration)' != 'Debug' ">$(WatchAppBuildPath)/$(WatchAppBundle)</WatchAppBundleFullPath>
|
||||
</PropertyGroup>
|
||||
|
||||
...
|
||||
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' AND Exists('$(WatchAppBundleFullPath)') ">
|
||||
<_ResolvedWatchAppReferences Include="$(WatchAppBundleFullPath)" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition=" '$(Configuration)' != 'Debug' ">
|
||||
<_ResolvedWatchAppReferences Include="$(WatchAppBundleFullPath)" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Condition=" '$(_ResolvedWatchAppReferences)' != '' ">
|
||||
<CodesignExtraArgs>--deep</CodesignExtraArgs>
|
||||
</PropertyGroup>
|
||||
<Target Name="PrintWatchAppBundleStatus" BeforeTargets="Build">
|
||||
<Message Text="WatchAppBundleFullPath: '$(WatchAppBundleFullPath)' exists" Condition=" Exists('$(WatchAppBundleFullPath)') " />
|
||||
<Message Text="WatchAppBundleFullPath: '$(WatchAppBundleFullPath)' does NOT exist" Condition=" !Exists('$(WatchAppBundleFullPath)') " />
|
||||
</Target>
|
||||
```
|
||||
|
||||
So on the `PropertyGroup` the `WatchAppBundleFullPath` is assembled together depending on the
|
||||
Configuration and the Platform taking the output of the Xcode watchOS app build. Then there are some
|
||||
`ItemGroup` to include the watch app depending on if it exists and the Configuration. The task
|
||||
`_ResolvedWatchAppReferences` is the one responsible to peek into the `Bitwarden.app` built by Xcode
|
||||
and if it finds a Watch app, it will just bundle it to the Xamarin iOS application. Finally, if the
|
||||
Watch app is bundled, deep signing is enabled and the build path is printed.
|
||||
|
||||
:::caution
|
||||
|
||||
As one can see in the csproj, to bundle the watchOS app into the iOS app one needs to target the
|
||||
correct platform. So if one is going to use a device, target the device on Xcode to build the
|
||||
watchOS app and after the build is done one can go to VS4M to build the iOS app (which will bundle
|
||||
the watchOS one) and run it on the device.
|
||||
|
||||
:::
|
||||
|
||||
## Synchronization between iPhone and Watch
|
||||
|
||||
In order to sync data between the iPhone and the Watch apps the
|
||||
[Watch Connectivity Framework](https://developer.apple.com/documentation/watchconnectivity) is used.
|
||||
|
||||
So there is a Watch Connectivity Manager on each side that is the interface used for the services on
|
||||
each platform to communicate.
|
||||
|
||||
For the sync communication, mainly
|
||||
[updateApplicationContext](https://developer.apple.com/documentation/watchconnectivity/wcsession/1615621-updateapplicationcontext)
|
||||
is used given that it always have the latest data sent available, it's sent in the background and
|
||||
the counterpart device doesn't necessarily needs to be in range (so it's cached until it can be
|
||||
delivered). Additionally,
|
||||
[sendMessage](https://developer.apple.com/documentation/watchconnectivity/wcsession/1615687-sendmessage)
|
||||
is also used to signal the counterpart of some action to take quickly (like triggering a sync from
|
||||
the Watch).
|
||||
|
||||
The `WatchDTO` is the object that is sent in the synchronization that has all the information for
|
||||
the Watch.
|
||||
|
||||
```kroki type=plantuml
|
||||
title= iOS part
|
||||
@startuml
|
||||
|
||||
title iOS
|
||||
|
||||
participant C as "Caller"
|
||||
participant BWDS as "BaseWatchDeviceService"
|
||||
participant WDS as "WatchDeviceService"
|
||||
participant WCSM as "WCSessionManager"
|
||||
boundary WCF as "Watch Connectivity Framework"
|
||||
|
||||
group Sync
|
||||
C->>BWDS: SyncDataToWatchAsync(...)
|
||||
BWDS->BWDS: GetStateAsync(...)
|
||||
BWDS->>WDS: SendDataToWatchAsync(...)
|
||||
WDS->>WCSM: SendBackgroundHighPriorityMessage(...)
|
||||
WCSM->>WCF: UpdateApplicationContext(...)
|
||||
end
|
||||
@enduml
|
||||
```
|
||||
|
||||
```kroki type=plantuml
|
||||
title= iOS part
|
||||
@startuml
|
||||
|
||||
title watchOS
|
||||
|
||||
boundary WCF as "Watch Connectivity Framework"
|
||||
participant WCM as "WatchConnectivityManager"
|
||||
participant SS as "StateService"
|
||||
participant ES as "EnvironmentService"
|
||||
participant CS as "CipherService"
|
||||
participant WCS as "watchConnectivitySubject"
|
||||
|
||||
group Sync
|
||||
WCF->>WCM: didReceiveApplicationContext(...)
|
||||
WCM->>SS: update state
|
||||
WCM->>ES: update environment
|
||||
WCM->>CS: saveCiphers(...)
|
||||
WCM->>WCS: fire notification change to subscribers
|
||||
end
|
||||
@enduml
|
||||
```
|
||||
|
||||
## States
|
||||
|
||||
The next ones are the states in which the Watch application can be at a given time:
|
||||
|
||||
- **Valid:** Everything it's ok and the user can see the vault ciphers with TOTP
|
||||
- **Need Login:** The user needs to log in using the iPhone
|
||||
- **Need Setup:** The user needs to set up an account with "Connect to Watch" enabled on their
|
||||
iPhone
|
||||
- **Need Premium:** The current account is not a premium account
|
||||
- **Need 2FA item:** The current account doesn't have any cipher with TOTP set up
|
||||
- **Syncing:** Displayed when changing accounts and syncing the new vault TOTPs
|
||||
- **Need Device Owner Auth:** The user needs to set up an Apple Watch Passcode in order to use the
|
||||
app
|
||||
|
||||
## Persistence and encryption
|
||||
|
||||
On the Watch [CoreData](https://developer.apple.com/documentation/coredata) is used as persistence
|
||||
for the ciphers. So in order to encrypt the data in them a Value Transformer in each encrypted
|
||||
attribute is used: `StringEncryptionTransformer`.
|
||||
|
||||
Inside the transformer a call to the `CryptoService` is used that ends up using
|
||||
[AES.GCM](https://developer.apple.com/documentation/cryptokit/aes/gcm) to encrypt the data with a
|
||||
256 bits [SymmetricKey](https://developer.apple.com/documentation/cryptokit/symmetrickey). The key
|
||||
is generated/loaded the first time something needs to be encrypted and stored in the device
|
||||
Keychain.
|
||||
|
||||
## Crash reporting
|
||||
|
||||
On all the other mobile applications, [AppCenter](https://appcenter.ms/) is being used as Crash
|
||||
reporting tool. However, it doesn't have support for watchOS (nor its internal library to handle
|
||||
crashes).
|
||||
|
||||
So, on the watchOS app [Firebase Crashlytics](https://firebase.google.com/docs/crashlytics) is used
|
||||
with basic crash reporting enabled (there is no handled error logging here yet). For this to work a
|
||||
`GoogleService-Info.plist` file is needed which is injected on the CI.
|
||||
|
||||
At the moment of writing this document, no plist is configured for dev environment so `Crashlytics`
|
||||
is enabled on **non-DEBUG** configurations.
|
||||
|
||||
There is a `Log` class to log errors happened in the app, but it's only enabled in **DEBUG**
|
||||
configuration.
|
||||
BIN
docs/getting-started/android/android-sdk.png
Normal file
BIN
docs/getting-started/android/android-sdk.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 350 KiB |
177
docs/getting-started/android/index.md
Normal file
177
docs/getting-started/android/index.md
Normal file
@@ -0,0 +1,177 @@
|
||||
# Android
|
||||
|
||||
:::warning Legacy
|
||||
|
||||
Getting started the **legacy** Android app done in .NET MAUI.
|
||||
|
||||
:::
|
||||
|
||||
## Requirements
|
||||
|
||||
Before you start, you should have the recommended [Tools and Libraries](../../../tools/index.md)
|
||||
installed. You will also need to install:
|
||||
|
||||
1. Visual Studio 2022 / VS Code
|
||||
2. [.NET 8 (latest)](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)
|
||||
- Note: Even if you have an ARM64 based Mac (M1, M2, M3, etc.), you can install all x64 SDKs to
|
||||
run Android
|
||||
- On Visual Studio for Mac you may need to turn on the feature for .NET 8 by going to Visual
|
||||
Studio > Preferences > Preview Features > Use the .NET 8 SDK
|
||||
3. .NET MAUI Workload
|
||||
- You can install this by running `dotnet workload install maui`
|
||||
4. Android SDK 34
|
||||
- You can use the SDK manager in [Visual Studio][xamarin-vs], or [Android
|
||||
Studio][android-studio] to install this
|
||||
|
||||
To make sure you have the Android SDK and Emulator installed:
|
||||
|
||||
1. Open Visual Studio
|
||||
2. Click Tools > SDK Manager (under the Android subheading)
|
||||
3. Click the Tools tab
|
||||
4. Make sure the following items are installed:
|
||||
|
||||
- Android SDK tools (at least one version of the command-line tools)
|
||||
- Android SDK Platform-Tools
|
||||
- Android SDK Build Tools (at least one version)
|
||||
- Android Emulator
|
||||
|
||||
5. Click Apply Changes if you've marked anything for installation
|
||||
|
||||
If you've missed anything, Visual Studio should prompt you anyway.
|
||||
|
||||
## Android Development Setup
|
||||
|
||||
To set up a new virtual Android device for debugging:
|
||||
|
||||
1. Click Tools > Device Manager (under the Android subheading)
|
||||
2. Click New Device
|
||||
3. Set up the device you want to emulate - you can just choose the Base Device and leave the
|
||||
default settings if you're unsure
|
||||
4. Visual Studio will then download the image for that device. The download progress is shown in
|
||||
the progress in the Android Device Manager dialog.
|
||||
5. Once this has completed, the emulated Android device will be available as a build target under
|
||||
App > Debug > (name of device)
|
||||
|
||||
### ARM64 Macs
|
||||
|
||||
1. Install and open Android Studio
|
||||
2. In the top navbar, click on Android Studio > Settings > Appearance & Behavior (tab) > System
|
||||
Settings > Android SDK
|
||||
3. In the SDK Platforms tab, ensure the "Show Package Details" checkbox is checked (located in the
|
||||
bottom-right)
|
||||
4. Bellow each Android API you'll see several System Images, pick one of the `ARM 64 v8a` and wait
|
||||
for it to download
|
||||
5. Go to View > Tool Windows > Device Manager
|
||||
6. Inside Device Manager, create a device using the previously downloaded system image
|
||||
|
||||

|
||||
|
||||
## F-Droid
|
||||
|
||||
On `App.csproj` and `Core.csproj` we can now pass `/p:CustomConstants=FDROID` when
|
||||
building/releasing so that the `FDROID` constant is added to the defined ones at the project level
|
||||
and we can use that with precompiler directives, e.g.:
|
||||
|
||||
```c#
|
||||
#if FDROID
|
||||
// perform operation only for FDROID.
|
||||
#endif
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
There are currently a few problems on Visual Studio for Mac for building correctly the projects, so
|
||||
if you encounter some errors build using the CLI (previously removing bin/obj folders):
|
||||
|
||||
```
|
||||
dotnet build -f net8.0-android -c Debug
|
||||
```
|
||||
|
||||
## Testing and Debugging
|
||||
|
||||
### Using the Android Emulator
|
||||
|
||||
In order to access `localhost:<port>` resources in the Android Emulator when debugging using Visual
|
||||
Studio on your Mac natively, you'll need to configure the endpoint addresses using
|
||||
`<http://10.0.2.2:<port>`\> in order to access `localhost`, which maps the Android proxy by design.
|
||||
|
||||
[xamarin-vs]: https://learn.microsoft.com/en-us/xamarin/android/get-started/installation/android-sdk
|
||||
[android-studio]: https://developer.android.com/studio/releases/platforms
|
||||
|
||||
### Using Server Tunneling
|
||||
|
||||
Instead of configuring your device or emulator, you can instead use a
|
||||
[proxy tunnel to your local server](../../../server/tunnel.md) and have your app connect to it
|
||||
directly.
|
||||
|
||||
### Push Notifications
|
||||
|
||||
The default configuration for the Android app is to register itself to the same environment as
|
||||
Bitwarden's QA Cloud. This means that if you try to debug the app using the production endpoints you
|
||||
won't be able to receive Live Sync updates or Passwordless login requests.
|
||||
|
||||
<Bitwarden>
|
||||
|
||||
So, in order to receive notifications while debugging, you have two options:
|
||||
|
||||
- Use QA Cloud endpoints for the Api and Identity, or
|
||||
- Use a local server setup where the Api is connected to QA Azure Notification Hub
|
||||
|
||||
</Bitwarden>
|
||||
|
||||
### Testing Passwordless Locally
|
||||
|
||||
Before you can start testing and debugging passwordless logins, make sure your local server setup is
|
||||
running correctly ([server setup](../../../server/guide.md)). You should also be able to deploy your
|
||||
Android app to your device or emulator.
|
||||
|
||||
:::note
|
||||
|
||||
Debugging and testing passwordless authentication is limited by
|
||||
[push notifications](#push-notifications).
|
||||
|
||||
:::
|
||||
|
||||
Testing passwordless notifications:
|
||||
|
||||
1. Start your local server (`Api`, `Identity`, `Notifications`)
|
||||
2. Make sure your mobile device can [connect to your local server](#using-server-tunneling)
|
||||
3. [Start the web client](../../../clients/web-vault/index.mdx), as you will need it to make login
|
||||
requests
|
||||
4. Deploy the Android app to your device or emulator
|
||||
5. After deployment, open the app, login to your QA account and activate passwordless login requests
|
||||
in settings
|
||||
6. Open the web vault using your preferred browser (ex: http://localhost:8080)
|
||||
7. Enter the email address of an account that has previously authenticated on that device (i.e. is a
|
||||
"known device") and click Continue. When presented with the login options, click click Login with
|
||||
Device.
|
||||
8. Check mobile device for the notification
|
||||
|
||||
<Bitwarden>
|
||||
|
||||
## AndroidX Credentials
|
||||
|
||||
Currently, the
|
||||
[androidx.credentials](https://developer.android.com/jetpack/androidx/releases/credentials) official
|
||||
binding has some bugs and we cannot use it yet. Because of this, we made a binding ourselves which
|
||||
is located in here:
|
||||
[Xamarin.AndroidX.Credentials](https://github.com/bitwarden/xamarin.androidx.credentials).
|
||||
|
||||
As of today, we are using version 1.2.0.
|
||||
|
||||
In the projects, the package is added as a local NuGet package located in
|
||||
`lib/android/Xamarin.AndroidX.Credentials` and this source is already configured in the
|
||||
`nuget.config` file.
|
||||
|
||||
In the case a change is needed on the binding, create a new local NuGet package and replace it in
|
||||
the aforementioned source.
|
||||
|
||||
:::warning
|
||||
|
||||
Do not add the project to the solution and as a project reference to the `App.csproj` /
|
||||
`Core.csproj` this will strangely make the iOS app crash on start because of solution configuration.
|
||||
Even though we couldn't find the root cause, this is the effect caused by this action.
|
||||
|
||||
:::
|
||||
|
||||
</Bitwarden>
|
||||
107
docs/getting-started/index.md
Normal file
107
docs/getting-started/index.md
Normal file
@@ -0,0 +1,107 @@
|
||||
---
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
# .NET MAUI (legacy)
|
||||
|
||||
:::warning Legacy
|
||||
|
||||
Getting started the **legacy** mobile app done in .NET MAUI.
|
||||
|
||||
:::
|
||||
|
||||
## Configure Git blame
|
||||
|
||||
We recommend that you configure git to ignore the Prettier revision:
|
||||
|
||||
```bash
|
||||
git config blame.ignoreRevsFile .git-blame-ignore-revs
|
||||
```
|
||||
|
||||
## Android Development
|
||||
|
||||
See the [Android Mobile app](./android/index.md) page to set up an Android development environment.
|
||||
|
||||
## iOS Development
|
||||
|
||||
<Bitwarden>
|
||||
|
||||
See the [iOS Mobile app](./ios/index.mdx) page to set up an iOS development environment.
|
||||
|
||||
</Bitwarden>
|
||||
|
||||
<Community>
|
||||
|
||||
Unfortunately, iOS development requires provisioning profiles and other capabilities only available
|
||||
to internal team members. We do not have any documentation for community developers at this time.
|
||||
|
||||
</Community>
|
||||
|
||||
## watchOS Development
|
||||
|
||||
<Bitwarden>
|
||||
|
||||
See the [watchOS app](./watchos) page to set up an watchOS development environment.
|
||||
|
||||
</Bitwarden>
|
||||
|
||||
<Community>
|
||||
|
||||
Unfortunately, watchOS development requires provisioning profiles and other capabilities only
|
||||
available to internal team members. We do not have any documentation for community developers at
|
||||
this time.
|
||||
|
||||
</Community>
|
||||
|
||||
## Unit tests
|
||||
|
||||
:::info TL;DR;
|
||||
|
||||
In order to run unit tests add the argument `/p:CustomConstants=UT` on the `dotnet` command for
|
||||
building/running. To work on Unit testing or use a Test runner uncomment the `CustomConstants` line
|
||||
on the `Directory.Build.props`
|
||||
|
||||
:::
|
||||
|
||||
Given that the `Core.csproj` is a MAUI project with `net8.0-android;net8.0-ios` target frameworks
|
||||
and we need `net8.0` for the tests we need a way to add that. The `Core.Test.csproj` has `net8.0` as
|
||||
a target so by adding the the argument `/p:CustomConstants=UT` we add `UT` as a constant to use in
|
||||
the projects. With that in place the next things happen:
|
||||
|
||||
- `UT` is added as a constant to use by precompiler directives
|
||||
- `Core.csproj` is changed to add `net8.0` as a target framework for unit tests
|
||||
- `FFImageLoading` is removed as a reference given that it doesn't support `net8.0`. Because of
|
||||
this, now we have a wrapped `CachedImage` that uses the library one if it's not `UT` and a custom
|
||||
one with NOOP implementation for `UT`
|
||||
|
||||
So if one wants to build the test project, one needs to go to `test/Core.Test` and run:
|
||||
|
||||
```bash
|
||||
dotnet build -f net8.0 /p:CustomConstants=UT
|
||||
```
|
||||
|
||||
and to run the tests go to the same folder and run:
|
||||
|
||||
```bash
|
||||
dotnet test -f net8.0 /p:CustomConstants=UT
|
||||
```
|
||||
|
||||
Finally, when working on the `Core.Test` project or when wanting to use a Test runner, go to the
|
||||
`Directory.Build.props` (located in the root) and uncomment the line referencing `CustomConstants`
|
||||
so that everything is loaded accordingly in the project. Because of some issues, the referenced
|
||||
projects, e.g. `Core`, are only included when the `UT` constant is in place. By uncommenting this
|
||||
line the projects will be referenced and one can work on that project or run the tests from a Test
|
||||
runner.
|
||||
|
||||
## Custom constants
|
||||
|
||||
There are custom constants to be used by the parameter `/p:CustomConstants={Value}` when
|
||||
building/running/releasing:
|
||||
|
||||
- `FDROID`: This is used to indicate that it's and F-Droid build/release
|
||||
([want to know more?](./android/index.md#f-droid))
|
||||
- `UT`: This is used when building/running the test projects or when working on one of them
|
||||
([want to know more?](#unit-tests))
|
||||
|
||||
These constants are added to the defined ones, so anyone can use them in the code with precompiler
|
||||
directives.
|
||||
440
docs/getting-started/ios/index.mdx
Normal file
440
docs/getting-started/ios/index.mdx
Normal file
@@ -0,0 +1,440 @@
|
||||
---
|
||||
sidebar_custom_props:
|
||||
access: bitwarden
|
||||
---
|
||||
|
||||
import Tabs from "@theme/Tabs";
|
||||
import TabItem from "@theme/TabItem";
|
||||
|
||||
# iOS
|
||||
|
||||
:::warning Legacy
|
||||
|
||||
Getting started the **legacy** iOS app done in .NET MAUI.
|
||||
|
||||
:::
|
||||
|
||||
## Requirements
|
||||
|
||||
1. Visual Studio 2022 / VS Code
|
||||
2. [.NET 8 (latest)](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)
|
||||
- On Visual Studio for Mac you may need to turn on the feature for .NET 8 by going to Visual
|
||||
Studio > Preferences > Preview Features > Use the .NET 8 SDK
|
||||
3. .NET MAUI Workload
|
||||
- You can install this by running `dotnet workload install maui`
|
||||
4. A Mac with Xcode 15.0 installed
|
||||
|
||||
## Apple Developer Account Setup
|
||||
|
||||
1. Accept your invite to the Bitwarden Apple Developer team. You should get a request in your email
|
||||
with the subject "You're invited to join a development team." Click the link, "Accept Invitation"
|
||||
and you'll be prompted to create an Apple ID for your Bitwarden email address. If you didn't
|
||||
receive this email, contact the IT department (@IT in slack). Accept the terms and conditions and
|
||||
complete the sign up flow
|
||||
|
||||
2. Go to [Apple ID Online](https://appleid.apple.com/) and log in with your new Apple ID. Set up
|
||||
2-factor authentication (using mobile phone and/or trusted device) - this is critical because
|
||||
Apple no longer allows "developer" accounts without MFA, but it won't tell you that when your
|
||||
build fails locally
|
||||
|
||||
3. Go to [App Store Connect](https://appstoreconnect.apple.com/) and accept the terms and conditions
|
||||
|
||||
4. Ensure you have access to the Bitwarden team and team app profiles
|
||||
|
||||
5. Go to [Apple Developer Account](https://developer.apple.com/account/) and go to the
|
||||
"Certificates, IDs & Profiles" menu item. Check that you can see the 8bit Solutions LLC
|
||||
Certificates in the Certificates section, and the Bitwarden profiles in the Profiles section. If
|
||||
any of this is missing, ask the IT department (@IT #tech-support in slack) for the additional
|
||||
roles / permissions
|
||||
|
||||
## macOS Setup
|
||||
|
||||
Next, you need to get your Mac environment set up for building and running the Bitwarden iOS mobile
|
||||
project. This requires creating the necessary developer provisioning profiles for code signing and
|
||||
execution on your Mac through Xcode. Visual Studio has a simple process to get all of the
|
||||
provisioning profiles, however this is prone to fail without much feedback. Try the Visual Studio
|
||||
instructions ("The Easy Way") first, and fallback to the Xcode instructions (“The Hard Way”) if
|
||||
required.
|
||||
|
||||
### Visual Studio: The Easy Way
|
||||
|
||||
1. Open Visual Studio for Mac
|
||||
|
||||
2. Go to Preferences > Publishing > Apple Developer Accounts
|
||||
|
||||
3. Click “Add”, choose "Enterprise Account", and sign in with your previously configured Apple
|
||||
Developer account
|
||||
|
||||
:::note
|
||||
|
||||
If you receive a "Failed to synchronize with Apple Developer Portal" error, you’re missing
|
||||
additional roles / permissions.
|
||||
|
||||
:::
|
||||
|
||||
After signing in successfully, you should see your account in the list and “Bitwarden Inc” in
|
||||
the account teams list
|
||||
|
||||
4. Click “View Details…”
|
||||
|
||||
5. If you don’t have a valid Apple Development certificate, click Create certificate > Apple
|
||||
Development
|
||||
|
||||
6. Click “Download All Profiles”
|
||||
|
||||
7. You should now be able to run the app by setting
|
||||
`iOS > Debug | iPhone Simulator > [pick any iOS Simulator]` in the top left corner and pressing
|
||||
Play
|
||||
|
||||

|
||||
|
||||
If this worked, you can skip the next section.
|
||||
|
||||
If you only have the option "Generic Simulator", with a message to lower the 'Deployment Target',
|
||||
your version of MAUI may not yet support the version of Xcode that you are using (as discussed
|
||||
[here](https://github.com/xamarin/xamarin-macios/issues/15954#issuecomment-1246025735)).
|
||||
|
||||

|
||||
|
||||
To work around this issue, try [downloading](https://developer.apple.com/download/all/) and
|
||||
installing an older version of Xcode from Apple (you can look for guidance on which Xcode version to
|
||||
use from the Xamarin.iOS [release notes](https://github.com/xamarin/xamarin-macios/releases) (this
|
||||
applies to MAUI as well). After installing the new version of Xcode, restart Visual Studio and load
|
||||
your project to verify your available simulator options.
|
||||
|
||||
:::note
|
||||
|
||||
If you need multiple versions of Xcode installed on your development machine, you can rename the
|
||||
`Xcode.app` file extracted from your download to something else (e.g. "Xcode_14_2.app") before
|
||||
placing it in your Applications folder. You can then switch between Xcode versions by using
|
||||
`xcode-select` from the command line. e.g.:
|
||||
|
||||
```shell
|
||||
sudo xcode-select -s /Applications/Xcode_14_2.app
|
||||
```
|
||||
|
||||
You may achieve similar results with tooling such as
|
||||
[Xcodes.app](https://github.com/XcodesOrg/XcodesApp)
|
||||
|
||||
:::
|
||||
|
||||
### Xcode: The Hard Way
|
||||
|
||||
:::note
|
||||
|
||||
If you're the next person to follow these instructions, please commit and upload the Xcode project
|
||||
files you create so we can streamline this process.
|
||||
|
||||
:::
|
||||
|
||||
Only try these instructions if the Visual Studio instructions above didn't work for you.
|
||||
|
||||
1. Open Xcode
|
||||
|
||||
2. Accept any defaults, ensure any extensions/add-ons have been installed, etc.
|
||||
|
||||
3. Create new project... > iOS > App
|
||||
|
||||
4. Use the following options for your new project:
|
||||
|
||||
- Product Name: "bitwarden"
|
||||
|
||||
- Team: Bitwarden Inc (if this is missing, double check your Apple Developer Account setup
|
||||
above)
|
||||
|
||||
- Organization Identifier: "com.8bit"
|
||||
|
||||
- Bundle Identifier (automatically generated): "com.8bit.bitwarden"
|
||||
|
||||
- Language: Objective-C
|
||||
|
||||
- User Interface: Storyboard
|
||||
|
||||
- Leave all other checkboxes unchecked (or uncheck them)
|
||||
|
||||

|
||||
|
||||
5. Click Next, save to the default location and then click "Create"
|
||||
|
||||
6. On the project configuration page, click the "Signing & Capabilities" tab
|
||||
|
||||
7. Make sure you have the following defaults:
|
||||
|
||||
- Automatically manage signing: (checked)
|
||||
|
||||
- Team: Bitwarden Inc
|
||||
|
||||
- Provisioning Profile: Xcode Managed Profile
|
||||
|
||||
- Signing Certificate: your Apple ID/Name
|
||||
|
||||

|
||||
|
||||
8. From the menu bar, click Product > Build
|
||||
|
||||
9. Repeat Steps 3-8, with the following changes in step 4:
|
||||
|
||||
- Product Name: "find-login-action-extension"
|
||||
|
||||
- Organization Identifier: "com.8bit.bitwarden"
|
||||
|
||||
- Bundle Identifier (automatically generated): "com.8bit.bitwarden.find-login-action-extension"
|
||||
|
||||
10. Repeat Steps 3-8, with the following changes in step 4:
|
||||
|
||||
- Product Name: "autofill"
|
||||
|
||||
- Organization Identifier: "com.8bit.bitwarden"
|
||||
|
||||
- Bundle Identifier (automatically generated): "com.8bit.bitwarden.autofill"
|
||||
|
||||
11. Repeat Steps 3-8, with the following changes in step 4:
|
||||
|
||||
- Product Name: "share-extension"
|
||||
|
||||
- Organization Identifier: "com.8bit.bitwarden"
|
||||
|
||||
- Bundle Identifier (automatically generated): "com.8bit.bitwarden.share-extension"
|
||||
|
||||
12. If you have a physical device (e.g. iPhone or iPad) that you want to use for testing, you will
|
||||
also need to do the following for each of the Xcode projects you just created:
|
||||
|
||||
- connect the device with a cable
|
||||
|
||||
- select your device as as the build target in Xcode
|
||||
|
||||
- from the menu bar, click Product > Build
|
||||
|
||||
- agree to register your device if asked
|
||||
|
||||
:::note
|
||||
|
||||
Sometimes these profiles can mess up. If you have issues running on your physical device (or
|
||||
simulator) try running `rm -r ~/Library/MobileDevice/Provisioning\ Profiles` to clear them out.
|
||||
Build each Xcode project again to regenerate them.
|
||||
|
||||
:::
|
||||
|
||||
## Visual Studio
|
||||
|
||||
Next, we need to configure your Visual Studio environment for development.
|
||||
|
||||
<Tabs groupId="os">
|
||||
<TabItem value="win" label="Windows" default>
|
||||
|
||||
1. Connect to the Mac that you just completed the above steps on
|
||||
|
||||
2. Open Visual Studio and click Tools > iOS > Pair to Mac
|
||||
|
||||
3. Scan for and select your machine. If you don't see it, click the "Add Mac..." button and put in
|
||||
the Mac name or IP address. If you don't know your Mac name (or you're in a Windows VM on your
|
||||
Mac), go to your Mac and open System Preferences > Sharing and look for the ".local" address of
|
||||
your machine
|
||||
|
||||
4. Provide your Username and Password for macOS when prompted
|
||||
|
||||
5. Once paired, close the Pair Mac window
|
||||
|
||||
6. Change your active build profile to Debug > iPhoneSimulator > iOS
|
||||
|
||||
7. Rebuild the iOS project from Solution Explorer
|
||||
|
||||
8. You can now debug using the iOS Simulator
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="mac" label="macOS">
|
||||
|
||||
1. Check that command line tools are installed:
|
||||
|
||||
1. Open Xcode
|
||||
|
||||
2. From the menu bar, click Xcode > Preferences > Locations
|
||||
|
||||
3. Make sure an Xcode version is selected under "Command Line Tools"
|
||||
|
||||
2. Open Visual Studio for Mac
|
||||
|
||||
3. Open the mobile solution file (`bitwarden-mobile.sln`) in the root of your local mobile
|
||||
repository
|
||||
|
||||
4. In the top bar, you should be able to select App > Debug > select your model and click run (or
|
||||
your physical device if you set one up)
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Building
|
||||
|
||||
To build from the CLI, navigate to the application directory:
|
||||
|
||||
For device:
|
||||
|
||||
```
|
||||
cd src/App
|
||||
dotnet build -f net8.0-ios -c Debug -r ios-arm64
|
||||
```
|
||||
|
||||
For simulator:
|
||||
|
||||
```
|
||||
cd src/App
|
||||
dotnet build -f net8.0-ios -c Debug -r iossimulator-x64
|
||||
```
|
||||
|
||||
You can also use the IDE but keep in mind:
|
||||
|
||||
:::tip Visual Studio for Mac
|
||||
|
||||
There are currently a few problems on Visual Studio for Mac for building correctly the projects, so
|
||||
if you encounter some errors, build using the CLI being into the `src/App` folder (previously
|
||||
removing bin/obj folders).
|
||||
|
||||
:::
|
||||
|
||||
:::tip Argon2Id
|
||||
|
||||
If you find any errors regarding argon2Id library when building for simulator, please be sure that
|
||||
you are building for runtime identifier `iossimulator-x64` as currently the library doesn't support
|
||||
`iossimulator-arm64`.
|
||||
|
||||
:::
|
||||
|
||||
:::tip Troubleshooting common mistakes
|
||||
|
||||
If you find the next error:
|
||||
|
||||
> `error NETSDK1134`: Building a solution with a specific RuntimeIdentifier is not supported. If you
|
||||
> would like to publish for a single RID, specify the RID at the individual project level instead
|
||||
|
||||
you almost surely are trying to build the app from the root folder. Instead go to `src/App` and try
|
||||
building again.
|
||||
|
||||
:::
|
||||
|
||||
### Argon2Id library loading
|
||||
|
||||
The Argon2Id library (`libargon2.a`) is loaded using `MTouchExtraArgs` in almost all projects of the
|
||||
solution. In order to make this simpler a property was added into **Directory.Build.props** called
|
||||
`Argon2IdLoadMtouchExtraArgs` which has the code to fill in the extra args parameter. Each project
|
||||
is configured with this property so this is only added on the correct runtime identifiers and we can
|
||||
build the app successfully on each case.
|
||||
|
||||
### Ignoring extensions / watchOS app
|
||||
|
||||
Sometimes we need to quickly build the app or maybe some configuration on the iOS extensions or the
|
||||
watchOS app gets in the way. In order to have a fast way to only care about the main app two
|
||||
properties were added to the **Directory.Build.Props** to help with this:
|
||||
|
||||
- `IncludeBitwardeniOSExtensions`: If `True` then all the iOS extensions will be included on the
|
||||
building of the main app, otherwise they will be skipped.
|
||||
- `IncludeBitwardenWatchOSApp`: If `True` then the watchOS app will be included on the building of
|
||||
the main app, otherwise it will be skipped.
|
||||
|
||||
:::warning Shared code
|
||||
|
||||
Toggling these off can provide a faster developer experience which is really useful in a lot of
|
||||
scenarios, but always bear in mind that a lot of things are shared between the main app and the
|
||||
extensions so before pushing your work, test again with everything enabled just in case.
|
||||
|
||||
:::
|
||||
|
||||
### Release mode locally
|
||||
|
||||
There are some issues that require us to build the app on **Release** configuration but locally
|
||||
without going through the CI/CD pipeline. The problem is that we don't have the code signing details
|
||||
for Distribution locally. To overcome this we can use the same `CodesignProvision` and `CodesignKey`
|
||||
we use for **Debug** but on the **Release** config. The thing is that it's a bit cumbersome to
|
||||
change that on every project so two properties were added to the **Directory.Build.Props** to help
|
||||
with this:
|
||||
|
||||
- `ReleaseCodesignProvision`: `CodesignProvision` for Release config on all projects
|
||||
- `ReleaseCodesignKey`: `CodesignKey` for Release config on all projects
|
||||
|
||||
By replacing their values, all projects will have their values applied so it's easier to build the
|
||||
app in **Release** mode locally.
|
||||
|
||||
## Debugging
|
||||
|
||||
### iPhone Simulator
|
||||
|
||||
The iPhone Simulator has access to localhost and you can point the client at your local dev server
|
||||
as usual. However, the app will require https by default. To allow http for testing purposes, follow
|
||||
these steps.
|
||||
|
||||
1. Open `src/App/Platforms/iOS/Info.plist` in Visual Studio Code or another editor so that you can
|
||||
edit the raw XML. (Don't use the Property List Editor in Visual Studio.)
|
||||
|
||||
2. Add the following code in the top-level `<dict>` element:
|
||||
|
||||
```xml
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<false/>
|
||||
<key>NSExceptionDomains</key>
|
||||
<dict>
|
||||
<key>localhost</key>
|
||||
<dict>
|
||||
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
||||
<true/>
|
||||
<key>NSIncludesSubdomains</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
```
|
||||
|
||||
3. Save and exit `Info.plist`
|
||||
|
||||
4. Press <kbd>Command</kbd> + <kbd>B</kbd> to force a new build before launching
|
||||
|
||||
5. Don't push these changes :)
|
||||
|
||||
### iPhone device
|
||||
|
||||
The device doesn’t have direct access to your Mac’s localhost, so you can follow
|
||||
[this guide to connect them](https://ymoondhra.medium.com/how-to-run-localhost-on-your-iphone-4110a54d1896).
|
||||
|
||||
After you do that, you’ll have to also modify the `Info.plist` to allow http for testing purposes as
|
||||
explained before on the simulator testing.
|
||||
|
||||
It’s also highly likely that you need to change the `launchSettings.json` on Server, on `Properties`
|
||||
of each project. There you need to change the `applicationUrl` of `iisSettings -> iisExpress` and of
|
||||
`profiles -> Identify` so that instead of `localhost` it says `name.local` where `name` is the
|
||||
computer name you set on Mac’s Sharing config.
|
||||
|
||||
Before you actually test on the app, open a browser and try to connect to the `Api` by going to
|
||||
`http://name.local:4000/alive` . If this doesn’t work then review the steps on the guide or the
|
||||
server configuration. Make sure you have your `User secrets` up to date as well.
|
||||
|
||||
Finally, you’ll have to configure the `Api` and `Identity` urls on the phone to use
|
||||
`http://name.local:4000` and `http://name.local:33656` where `name` is the computer name you set on
|
||||
Mac’s Sharing config.
|
||||
|
||||
### iOS Extensions
|
||||
|
||||
1. Set the iOS Extension project as Startup project
|
||||
|
||||
2. Press Run
|
||||
|
||||
3. You will receive a popup saying "Waiting for the debugger to connect..."
|
||||
|
||||
4. Don’t open the Bitwarden app (otherwise the debugger will connect to it instead of the
|
||||
extension). Instead trigger the extension
|
||||
|
||||
5. Your extension breakpoints should now be hit
|
||||
|
||||
For example: if you want to debug the **iOS.Autofill** extension, you would complete steps 1 - 3,
|
||||
then go to your iOS device, open a browser, go to a login, tap the key icon and open Bitwarden from
|
||||
the bottom popup.
|
||||
|
||||
### Using Server Tunneling
|
||||
|
||||
Instead of configuring your device or emulator to ignore SSL certificates, you can instead use a
|
||||
[proxy tunnel to your local server](../../../server/tunnel.md) and have your app connect to it
|
||||
directly.
|
||||
|
||||
### Push Notifications (Live Sync & Passwordless)
|
||||
|
||||
Push notifications are not currently available for debug deployments. They are only supported on
|
||||
TestFlight and production builds.
|
||||
BIN
docs/getting-started/ios/new-project-options.png
Normal file
BIN
docs/getting-started/ios/new-project-options.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 95 KiB |
BIN
docs/getting-started/ios/run-debug.png
Normal file
BIN
docs/getting-started/ios/run-debug.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
BIN
docs/getting-started/ios/signing-and-capabilities.png
Normal file
BIN
docs/getting-started/ios/signing-and-capabilities.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 307 KiB |
BIN
docs/getting-started/ios/troubleshoot-generic-simulator.png
Normal file
BIN
docs/getting-started/ios/troubleshoot-generic-simulator.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
101
docs/getting-started/watchos/index.mdx
Normal file
101
docs/getting-started/watchos/index.mdx
Normal file
@@ -0,0 +1,101 @@
|
||||
---
|
||||
sidebar_custom_props:
|
||||
access: bitwarden
|
||||
---
|
||||
|
||||
# watchOS
|
||||
|
||||
:::warning Legacy
|
||||
|
||||
Getting started the **legacy** watchOS app done in .NET MAUI.
|
||||
|
||||
:::
|
||||
|
||||
## Requirements
|
||||
|
||||
Follow the [iOS Setup](../ios/index.mdx).
|
||||
|
||||
In order for everything to work properly **devices** are needed. On simulators the synchronization
|
||||
won't work (and some other parts may not work as well). Also, have Bluetooth enabled if possible to
|
||||
ease the sync between devices and the debugging communication.
|
||||
|
||||
It's recommended to read the
|
||||
[Watch Architecture](../../../../../architecture/mobile-clients/net-maui-legacy/watchOS) as well.
|
||||
|
||||
## macOS Setup
|
||||
|
||||
Having followed the macOS setup of iOS, no additional configuration is needed given that when the
|
||||
project is opened in Xcode it will automatically have the provisioning profiles set up.
|
||||
|
||||
## Debugging
|
||||
|
||||
There are two parts from where to debug:
|
||||
|
||||
- From Visual Studio for Mac (the iOS app)
|
||||
- From Xcode (the watchOS app)
|
||||
|
||||
For now, there is no way to debug both apps (iOS and watchOS) at the same time given that from MAUI
|
||||
there is no access to debug information of the watchOS app and from Xcode an iOS stub app is
|
||||
installed on the iPhone to debug it. So, at the moment of debugging one needs to choose which part
|
||||
to have information about, therefore whether to debug from VS4M or from Xcode.
|
||||
|
||||
:::caution
|
||||
|
||||
When debugging from Xcode the MAUI iOS app will be replaced with the stub one from Xcode. So any
|
||||
configuration on the iOS app will be lost (like server urls)
|
||||
|
||||
When debugging from VS4M, uninstall the previous watchOS app (if any) from the Apple Watch in
|
||||
between builds to have it always up to date (there are times that if one doesn't uninstall the
|
||||
previous watchOS app it doesn't get updated)
|
||||
|
||||
:::
|
||||
|
||||
:::tip
|
||||
|
||||
If one needs to get the logs or use the _Console_ app to see the logs from the watch then one needs
|
||||
to install the `sysdiagnose` profile for watchOS from Apple Developer site
|
||||
[here](https://developer.apple.com/bug-reporting/profiles-and-logs/?name=sysdiagnose) into the
|
||||
paired iPhone and after that restart both devices in order for the logs to work.
|
||||
|
||||
:::
|
||||
|
||||
### Building
|
||||
|
||||
Given that the MAUI iOS app needs the output of the build of Xcode, one needs to build the watchOS
|
||||
app from Xcode first and then from VS4M build the iOS app to run it on the device.
|
||||
|
||||
The output of Xcode build is stored in a location pretty similar to the next one that is configured
|
||||
in the `iOS.csproj`:
|
||||
|
||||
```xml
|
||||
<PropertyGroup>
|
||||
<WatchAppBuildPath Condition=" '$(Configuration)' == 'Debug' ">$(Home)/Library/Developer/Xcode/DerivedData/bitwarden-cbtqsueryycvflfzbsoteofskiyr/Build/Products</WatchAppBuildPath>
|
||||
```
|
||||
|
||||
It's highly likely that the folder `bitwarden-cbtqsueryycvflfzbsoteofskiyr` won't be the same on
|
||||
every Mac. So one needs to change that part in `iOS.csproj` to the one created automatically by
|
||||
Xcode locally.
|
||||
|
||||
To know exactly which is the path: Open the Project in Xcode -> Go to Product -> Show Build Folder
|
||||
in Finder.
|
||||
|
||||
_This needs to be improved to have a fixed location or an easier way to get it automatically._
|
||||
|
||||
:::caution
|
||||
|
||||
One needs to take special attention to target the same platform on both IDEs. Therefore when running
|
||||
on a device, target the device both in Xcode and on VS4M when building so that the watchOS app is
|
||||
bundled correctly. Also one needs to make sure that "bitwarden WatchKit app" scheme is selected.
|
||||
|
||||
:::
|
||||
|
||||
### Synchronization
|
||||
|
||||
There is no way to debug the synchronization completely at the same time for the reasons
|
||||
aforementioned.
|
||||
|
||||
So one can debug one end (iOS) or the other (watchOS).
|
||||
|
||||
If needed to check something on both ends "at the same time" (like to check why a message is not
|
||||
sent/arrived), one needs to use console logging or adapt part of the MAUI code to the iOS stub app
|
||||
on Xcode and debug the synchronization from Xcode.
|
||||
Submodule lib/MessagePack deleted from 1ecb15e311
19
lib/MessagePack/LICENSE.md
Normal file
19
lib/MessagePack/LICENSE.md
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright 2018 Read Evaluate Press, LLC
|
||||
|
||||
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.
|
||||
32
lib/MessagePack/MessagePack-FlightSchool.podspec
Normal file
32
lib/MessagePack/MessagePack-FlightSchool.podspec
Normal file
@@ -0,0 +1,32 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'MessagePack-FlightSchool'
|
||||
s.module_name = 'MessagePack'
|
||||
s.version = '1.2.4'
|
||||
s.summary = 'A MessagePack encoder and decoder for Codable types.'
|
||||
|
||||
s.description = <<-DESC
|
||||
This functionality is discussed in Chapter 7 of
|
||||
Flight School Guide to Swift Codable.
|
||||
DESC
|
||||
|
||||
s.homepage = 'https://flight.school/books/codable/'
|
||||
|
||||
s.license = { type: 'MIT', file: 'LICENSE.md' }
|
||||
|
||||
s.author = { 'Mattt' => 'mattt@flight.school' }
|
||||
|
||||
s.social_media_url = 'https://twitter.com/mattt'
|
||||
|
||||
s.ios.deployment_target = '8.0'
|
||||
s.osx.deployment_target = '10.10'
|
||||
s.watchos.deployment_target = '2.0'
|
||||
s.tvos.deployment_target = '9.0'
|
||||
|
||||
s.source = { git: 'https://github.com/Flight-School/MessagePack.git',
|
||||
tag: s.version.to_s }
|
||||
|
||||
s.source_files = 'Sources/**/*.swift'
|
||||
|
||||
s.swift_version = '4.2'
|
||||
s.static_framework = true
|
||||
end
|
||||
13
lib/MessagePack/MessagePack.playground/Contents.swift
Normal file
13
lib/MessagePack/MessagePack.playground/Contents.swift
Normal file
@@ -0,0 +1,13 @@
|
||||
import MessagePack
|
||||
|
||||
let encoder = MessagePackEncoder()
|
||||
|
||||
let value: String = "hello"
|
||||
let encodedData = try encoder.encode(value)
|
||||
|
||||
print("Bytes: ", encodedData.map{ String($0, radix: 16, uppercase: true) })
|
||||
|
||||
let decoder = MessagePackDecoder()
|
||||
let decodedValue = try decoder.decode(String.self, from: encodedData)
|
||||
|
||||
decodedValue == value
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<playground version='5.0' target-platform='macos' executeOnSourceChanges='false'>
|
||||
<timeline fileName='timeline.xctimeline'/>
|
||||
</playground>
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</plist>
|
||||
25
lib/MessagePack/MessagePack.xcodeproj/MessagePack_Info.plist
Normal file
25
lib/MessagePack/MessagePack.xcodeproj/MessagePack_Info.plist
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</plist>
|
||||
543
lib/MessagePack/MessagePack.xcodeproj/project.pbxproj
Normal file
543
lib/MessagePack/MessagePack.xcodeproj/project.pbxproj
Normal file
@@ -0,0 +1,543 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 46;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXAggregateTarget section */
|
||||
"MessagePack::MessagePackPackageTests::ProductTarget" /* MessagePackPackageTests */ = {
|
||||
isa = PBXAggregateTarget;
|
||||
buildConfigurationList = OBJ_57 /* Build configuration list for PBXAggregateTarget "MessagePackPackageTests" */;
|
||||
buildPhases = (
|
||||
);
|
||||
dependencies = (
|
||||
OBJ_60 /* PBXTargetDependency */,
|
||||
);
|
||||
name = MessagePackPackageTests;
|
||||
productName = MessagePackPackageTests;
|
||||
};
|
||||
/* End PBXAggregateTarget section */
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
1BC312FF2992DE9C00177F2A /* DataSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BC312FE2992DE9C00177F2A /* DataSpec.swift */; };
|
||||
OBJ_38 /* AnyCodingKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_9 /* AnyCodingKey.swift */; };
|
||||
OBJ_39 /* Box.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_10 /* Box.swift */; };
|
||||
OBJ_40 /* KeyedDecodingContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_12 /* KeyedDecodingContainer.swift */; };
|
||||
OBJ_41 /* MessagePackDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_13 /* MessagePackDecoder.swift */; };
|
||||
OBJ_42 /* SingleValueDecodingContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_14 /* SingleValueDecodingContainer.swift */; };
|
||||
OBJ_43 /* UnkeyedDecodingContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_15 /* UnkeyedDecodingContainer.swift */; };
|
||||
OBJ_44 /* KeyedEncodingContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_17 /* KeyedEncodingContainer.swift */; };
|
||||
OBJ_45 /* MessagePackEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_18 /* MessagePackEncoder.swift */; };
|
||||
OBJ_46 /* SingleValueEncodingContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_19 /* SingleValueEncodingContainer.swift */; };
|
||||
OBJ_47 /* UnkeyedEncodingContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_20 /* UnkeyedEncodingContainer.swift */; };
|
||||
OBJ_48 /* FixedWidthInteger+Bytes.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_21 /* FixedWidthInteger+Bytes.swift */; };
|
||||
OBJ_55 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_6 /* Package.swift */; };
|
||||
OBJ_66 /* Airport.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_24 /* Airport.swift */; };
|
||||
OBJ_67 /* MessagePackDecodingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_25 /* MessagePackDecodingTests.swift */; };
|
||||
OBJ_68 /* MessagePackEncodingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_26 /* MessagePackEncodingTests.swift */; };
|
||||
OBJ_69 /* MessagePackPerformanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_27 /* MessagePackPerformanceTests.swift */; };
|
||||
OBJ_70 /* MessagePackRoundTripTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_28 /* MessagePackRoundTripTests.swift */; };
|
||||
OBJ_72 /* MessagePack.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "MessagePack::MessagePack::Product" /* MessagePack.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
1BC312FC2989A1AD00177F2A /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = OBJ_1 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = "MessagePack::MessagePack";
|
||||
remoteInfo = MessagePack;
|
||||
};
|
||||
1BC312FD2989A1B200177F2A /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = OBJ_1 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = "MessagePack::MessagePackTests";
|
||||
remoteInfo = MessagePackTests;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
1BC312FE2992DE9C00177F2A /* DataSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataSpec.swift; sourceTree = "<group>"; };
|
||||
"MessagePack::MessagePack::Product" /* MessagePack.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = MessagePack.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
"MessagePack::MessagePackTests::Product" /* MessagePackTests.xctest */ = {isa = PBXFileReference; lastKnownFileType = file; path = MessagePackTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
OBJ_10 /* Box.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Box.swift; sourceTree = "<group>"; };
|
||||
OBJ_12 /* KeyedDecodingContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyedDecodingContainer.swift; sourceTree = "<group>"; };
|
||||
OBJ_13 /* MessagePackDecoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagePackDecoder.swift; sourceTree = "<group>"; };
|
||||
OBJ_14 /* SingleValueDecodingContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleValueDecodingContainer.swift; sourceTree = "<group>"; };
|
||||
OBJ_15 /* UnkeyedDecodingContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnkeyedDecodingContainer.swift; sourceTree = "<group>"; };
|
||||
OBJ_17 /* KeyedEncodingContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyedEncodingContainer.swift; sourceTree = "<group>"; };
|
||||
OBJ_18 /* MessagePackEncoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagePackEncoder.swift; sourceTree = "<group>"; };
|
||||
OBJ_19 /* SingleValueEncodingContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleValueEncodingContainer.swift; sourceTree = "<group>"; };
|
||||
OBJ_20 /* UnkeyedEncodingContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnkeyedEncodingContainer.swift; sourceTree = "<group>"; };
|
||||
OBJ_21 /* FixedWidthInteger+Bytes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FixedWidthInteger+Bytes.swift"; sourceTree = "<group>"; };
|
||||
OBJ_24 /* Airport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Airport.swift; sourceTree = "<group>"; };
|
||||
OBJ_25 /* MessagePackDecodingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagePackDecodingTests.swift; sourceTree = "<group>"; };
|
||||
OBJ_26 /* MessagePackEncodingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagePackEncodingTests.swift; sourceTree = "<group>"; };
|
||||
OBJ_27 /* MessagePackPerformanceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagePackPerformanceTests.swift; sourceTree = "<group>"; };
|
||||
OBJ_28 /* MessagePackRoundTripTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagePackRoundTripTests.swift; sourceTree = "<group>"; };
|
||||
OBJ_29 /* MessagePack.xcworkspace */ = {isa = PBXFileReference; lastKnownFileType = wrapper.workspace; path = MessagePack.xcworkspace; sourceTree = SOURCE_ROOT; };
|
||||
OBJ_6 /* Package.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; path = Package.swift; sourceTree = "<group>"; };
|
||||
OBJ_9 /* AnyCodingKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyCodingKey.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
OBJ_49 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 0;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
OBJ_71 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 0;
|
||||
files = (
|
||||
OBJ_72 /* MessagePack.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
OBJ_11 /* Decoder */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
OBJ_12 /* KeyedDecodingContainer.swift */,
|
||||
OBJ_13 /* MessagePackDecoder.swift */,
|
||||
OBJ_14 /* SingleValueDecodingContainer.swift */,
|
||||
OBJ_15 /* UnkeyedDecodingContainer.swift */,
|
||||
);
|
||||
path = Decoder;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
OBJ_16 /* Encoder */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
OBJ_17 /* KeyedEncodingContainer.swift */,
|
||||
OBJ_18 /* MessagePackEncoder.swift */,
|
||||
OBJ_19 /* SingleValueEncodingContainer.swift */,
|
||||
OBJ_20 /* UnkeyedEncodingContainer.swift */,
|
||||
);
|
||||
path = Encoder;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
OBJ_22 /* Tests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
OBJ_23 /* MessagePackTests */,
|
||||
);
|
||||
name = Tests;
|
||||
sourceTree = SOURCE_ROOT;
|
||||
};
|
||||
OBJ_23 /* MessagePackTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
OBJ_24 /* Airport.swift */,
|
||||
OBJ_25 /* MessagePackDecodingTests.swift */,
|
||||
OBJ_26 /* MessagePackEncodingTests.swift */,
|
||||
OBJ_27 /* MessagePackPerformanceTests.swift */,
|
||||
OBJ_28 /* MessagePackRoundTripTests.swift */,
|
||||
);
|
||||
name = MessagePackTests;
|
||||
path = Tests/MessagePackTests;
|
||||
sourceTree = SOURCE_ROOT;
|
||||
};
|
||||
OBJ_30 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
"MessagePack::MessagePackTests::Product" /* MessagePackTests.xctest */,
|
||||
"MessagePack::MessagePack::Product" /* MessagePack.framework */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
OBJ_5 /* */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
OBJ_6 /* Package.swift */,
|
||||
OBJ_7 /* Sources */,
|
||||
OBJ_22 /* Tests */,
|
||||
OBJ_29 /* MessagePack.xcworkspace */,
|
||||
OBJ_30 /* Products */,
|
||||
);
|
||||
name = "";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
OBJ_7 /* Sources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
OBJ_8 /* MessagePack */,
|
||||
);
|
||||
name = Sources;
|
||||
sourceTree = SOURCE_ROOT;
|
||||
};
|
||||
OBJ_8 /* MessagePack */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
OBJ_9 /* AnyCodingKey.swift */,
|
||||
OBJ_10 /* Box.swift */,
|
||||
OBJ_11 /* Decoder */,
|
||||
OBJ_16 /* Encoder */,
|
||||
OBJ_21 /* FixedWidthInteger+Bytes.swift */,
|
||||
1BC312FE2992DE9C00177F2A /* DataSpec.swift */,
|
||||
);
|
||||
name = MessagePack;
|
||||
path = Sources/MessagePack;
|
||||
sourceTree = SOURCE_ROOT;
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
"MessagePack::MessagePack" /* MessagePack */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = OBJ_34 /* Build configuration list for PBXNativeTarget "MessagePack" */;
|
||||
buildPhases = (
|
||||
OBJ_37 /* Sources */,
|
||||
OBJ_49 /* Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = MessagePack;
|
||||
productName = MessagePack;
|
||||
productReference = "MessagePack::MessagePack::Product" /* MessagePack.framework */;
|
||||
productType = "com.apple.product-type.framework";
|
||||
};
|
||||
"MessagePack::MessagePackTests" /* MessagePackTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = OBJ_62 /* Build configuration list for PBXNativeTarget "MessagePackTests" */;
|
||||
buildPhases = (
|
||||
OBJ_65 /* Sources */,
|
||||
OBJ_71 /* Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
OBJ_73 /* PBXTargetDependency */,
|
||||
);
|
||||
name = MessagePackTests;
|
||||
productName = MessagePackTests;
|
||||
productReference = "MessagePack::MessagePackTests::Product" /* MessagePackTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
"MessagePack::SwiftPMPackageDescription" /* MessagePackPackageDescription */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = OBJ_51 /* Build configuration list for PBXNativeTarget "MessagePackPackageDescription" */;
|
||||
buildPhases = (
|
||||
OBJ_54 /* Sources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = MessagePackPackageDescription;
|
||||
productName = MessagePackPackageDescription;
|
||||
productType = "com.apple.product-type.framework";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
OBJ_1 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 9999;
|
||||
};
|
||||
buildConfigurationList = OBJ_2 /* Build configuration list for PBXProject "MessagePack" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
developmentRegion = English;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
English,
|
||||
en,
|
||||
);
|
||||
mainGroup = OBJ_5 /* */;
|
||||
productRefGroup = OBJ_30 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
"MessagePack::MessagePack" /* MessagePack */,
|
||||
"MessagePack::SwiftPMPackageDescription" /* MessagePackPackageDescription */,
|
||||
"MessagePack::MessagePackPackageTests::ProductTarget" /* MessagePackPackageTests */,
|
||||
"MessagePack::MessagePackTests" /* MessagePackTests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
OBJ_37 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 0;
|
||||
files = (
|
||||
OBJ_38 /* AnyCodingKey.swift in Sources */,
|
||||
OBJ_39 /* Box.swift in Sources */,
|
||||
OBJ_40 /* KeyedDecodingContainer.swift in Sources */,
|
||||
OBJ_41 /* MessagePackDecoder.swift in Sources */,
|
||||
OBJ_42 /* SingleValueDecodingContainer.swift in Sources */,
|
||||
OBJ_43 /* UnkeyedDecodingContainer.swift in Sources */,
|
||||
OBJ_44 /* KeyedEncodingContainer.swift in Sources */,
|
||||
OBJ_45 /* MessagePackEncoder.swift in Sources */,
|
||||
OBJ_46 /* SingleValueEncodingContainer.swift in Sources */,
|
||||
OBJ_47 /* UnkeyedEncodingContainer.swift in Sources */,
|
||||
1BC312FF2992DE9C00177F2A /* DataSpec.swift in Sources */,
|
||||
OBJ_48 /* FixedWidthInteger+Bytes.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
OBJ_54 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 0;
|
||||
files = (
|
||||
OBJ_55 /* Package.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
OBJ_65 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 0;
|
||||
files = (
|
||||
OBJ_66 /* Airport.swift in Sources */,
|
||||
OBJ_67 /* MessagePackDecodingTests.swift in Sources */,
|
||||
OBJ_68 /* MessagePackEncodingTests.swift in Sources */,
|
||||
OBJ_69 /* MessagePackPerformanceTests.swift in Sources */,
|
||||
OBJ_70 /* MessagePackRoundTripTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
OBJ_60 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = "MessagePack::MessagePackTests" /* MessagePackTests */;
|
||||
targetProxy = 1BC312FD2989A1B200177F2A /* PBXContainerItemProxy */;
|
||||
};
|
||||
OBJ_73 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = "MessagePack::MessagePack" /* MessagePack */;
|
||||
targetProxy = 1BC312FC2989A1AD00177F2A /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
OBJ_3 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
ENABLE_NS_ASSERTIONS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
OTHER_SWIFT_FLAGS = "-DXcode";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = macosx;
|
||||
SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "SWIFT_PACKAGE DEBUG";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
USE_HEADERMAP = NO;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
OBJ_35 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ENABLE_TESTABILITY = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
|
||||
);
|
||||
HEADER_SEARCH_PATHS = "$(inherited)";
|
||||
INFOPLIST_FILE = MessagePack.xcodeproj/MessagePack_Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) $(TOOLCHAIN_DIR)/usr/lib/swift/macosx";
|
||||
OTHER_CFLAGS = "$(inherited)";
|
||||
OTHER_LDFLAGS = "$(inherited)";
|
||||
OTHER_SWIFT_FLAGS = "$(inherited)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = MessagePack;
|
||||
PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)";
|
||||
SWIFT_VERSION = 4.0;
|
||||
TARGET_NAME = MessagePack;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
OBJ_36 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ENABLE_TESTABILITY = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
|
||||
);
|
||||
HEADER_SEARCH_PATHS = "$(inherited)";
|
||||
INFOPLIST_FILE = MessagePack.xcodeproj/MessagePack_Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) $(TOOLCHAIN_DIR)/usr/lib/swift/macosx";
|
||||
OTHER_CFLAGS = "$(inherited)";
|
||||
OTHER_LDFLAGS = "$(inherited)";
|
||||
OTHER_SWIFT_FLAGS = "$(inherited)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = MessagePack;
|
||||
PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)";
|
||||
SWIFT_VERSION = 4.0;
|
||||
TARGET_NAME = MessagePack;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
OBJ_4 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
COPY_PHASE_STRIP = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
GCC_OPTIMIZATION_LEVEL = s;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
||||
OTHER_SWIFT_FLAGS = "-DXcode";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = macosx;
|
||||
SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = SWIFT_PACKAGE;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
USE_HEADERMAP = NO;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
OBJ_52 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
LD = /usr/bin/true;
|
||||
OTHER_SWIFT_FLAGS = "-swift-version 4 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4 -target x86_64-apple-macosx10.10 -sdk /Applications/Xcode-beta.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk";
|
||||
SWIFT_VERSION = 4.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
OBJ_53 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
LD = /usr/bin/true;
|
||||
OTHER_SWIFT_FLAGS = "-swift-version 4 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4 -target x86_64-apple-macosx10.10 -sdk /Applications/Xcode-beta.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk";
|
||||
SWIFT_VERSION = 4.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
OBJ_58 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
OBJ_59 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
OBJ_63 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
|
||||
);
|
||||
HEADER_SEARCH_PATHS = "$(inherited)";
|
||||
INFOPLIST_FILE = MessagePack.xcodeproj/MessagePackTests_Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @loader_path/../Frameworks @loader_path/Frameworks";
|
||||
OTHER_CFLAGS = "$(inherited)";
|
||||
OTHER_LDFLAGS = "$(inherited)";
|
||||
OTHER_SWIFT_FLAGS = "$(inherited)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)";
|
||||
SWIFT_VERSION = 4.0;
|
||||
TARGET_NAME = MessagePackTests;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
OBJ_64 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
|
||||
);
|
||||
HEADER_SEARCH_PATHS = "$(inherited)";
|
||||
INFOPLIST_FILE = MessagePack.xcodeproj/MessagePackTests_Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @loader_path/../Frameworks @loader_path/Frameworks";
|
||||
OTHER_CFLAGS = "$(inherited)";
|
||||
OTHER_LDFLAGS = "$(inherited)";
|
||||
OTHER_SWIFT_FLAGS = "$(inherited)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)";
|
||||
SWIFT_VERSION = 4.0;
|
||||
TARGET_NAME = MessagePackTests;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
OBJ_2 /* Build configuration list for PBXProject "MessagePack" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
OBJ_3 /* Debug */,
|
||||
OBJ_4 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
OBJ_34 /* Build configuration list for PBXNativeTarget "MessagePack" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
OBJ_35 /* Debug */,
|
||||
OBJ_36 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
OBJ_51 /* Build configuration list for PBXNativeTarget "MessagePackPackageDescription" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
OBJ_52 /* Debug */,
|
||||
OBJ_53 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
OBJ_57 /* Build configuration list for PBXAggregateTarget "MessagePackPackageTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
OBJ_58 /* Debug */,
|
||||
OBJ_59 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
OBJ_62 /* Build configuration list for PBXNativeTarget "MessagePackTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
OBJ_63 /* Debug */,
|
||||
OBJ_64 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = OBJ_1 /* Project object */;
|
||||
}
|
||||
7
lib/MessagePack/MessagePack.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
7
lib/MessagePack/MessagePack.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?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>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?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>IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,99 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1010"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "MessagePack::MessagePack"
|
||||
BuildableName = "MessagePack.framework"
|
||||
BlueprintName = "MessagePack"
|
||||
ReferencedContainer = "container:MessagePack.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "MessagePack::MessagePackTests"
|
||||
BuildableName = "MessagePackTests.xctest"
|
||||
BlueprintName = "MessagePackTests"
|
||||
ReferencedContainer = "container:MessagePack.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "MessagePack::MessagePack"
|
||||
BuildableName = "MessagePack.framework"
|
||||
BlueprintName = "MessagePack"
|
||||
ReferencedContainer = "container:MessagePack.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
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 = "MessagePack::MessagePack"
|
||||
BuildableName = "MessagePack.framework"
|
||||
BlueprintName = "MessagePack"
|
||||
ReferencedContainer = "container:MessagePack.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "MessagePack::MessagePack"
|
||||
BuildableName = "MessagePack.framework"
|
||||
BlueprintName = "MessagePack"
|
||||
ReferencedContainer = "container:MessagePack.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
10
lib/MessagePack/MessagePack.xcworkspace/contents.xcworkspacedata
generated
Normal file
10
lib/MessagePack/MessagePack.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:MessagePack.playground">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:MessagePack.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?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>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?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>IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
28
lib/MessagePack/Package.swift
Normal file
28
lib/MessagePack/Package.swift
Normal file
@@ -0,0 +1,28 @@
|
||||
// swift-tools-version:4.0
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "MessagePack",
|
||||
products: [
|
||||
// Products define the executables and libraries produced by a package, and make them visible to other packages.
|
||||
.library(
|
||||
name: "MessagePack",
|
||||
targets: ["MessagePack"]),
|
||||
],
|
||||
dependencies: [
|
||||
// Dependencies declare other packages that this package depends on.
|
||||
// .package(url: /* package url */, from: "1.0.0"),
|
||||
],
|
||||
targets: [
|
||||
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
||||
// Targets can depend on other targets in this package, and on products in packages which this package depends on.
|
||||
.target(
|
||||
name: "MessagePack",
|
||||
dependencies: []),
|
||||
.testTarget(
|
||||
name: "MessagePackTests",
|
||||
dependencies: ["MessagePack"]),
|
||||
]
|
||||
)
|
||||
87
lib/MessagePack/README.md
Normal file
87
lib/MessagePack/README.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# MessagePack
|
||||
|
||||
[![Build Status][build status badge]][build status]
|
||||
|
||||
A [MessagePack](https://msgpack.org/) encoder and decoder for `Codable` types.
|
||||
|
||||
This functionality is discussed in Chapter 7 of
|
||||
[Flight School Guide to Swift Codable](https://flight.school/books/codable).
|
||||
|
||||
## Requirements
|
||||
|
||||
- Swift 4.2+
|
||||
|
||||
## Usage
|
||||
|
||||
### Encoding Messages
|
||||
|
||||
```swift
|
||||
import MessagePack
|
||||
|
||||
let encoder = MessagePackEncoder()
|
||||
let value = try! encoder.encode(["a": 1, "b": 2, "c": 3])
|
||||
// [0x83, 0xA1, 0x62, 0x02, 0xA1, 0x61, 0x01, 0xA1, 0x63, 0x03]
|
||||
```
|
||||
|
||||
### Decoding Messages
|
||||
|
||||
```swift
|
||||
import MessagePack
|
||||
|
||||
let decoder = MessagePackDecoder()
|
||||
let data = Data(bytes: [0xCB, 0x40, 0x09, 0x21, 0xF9, 0xF0, 0x1B, 0x86, 0x6E])
|
||||
let value = try! decoder.decode(Double.self, from: data)
|
||||
// 3.14159
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
### Swift Package Manager
|
||||
|
||||
Add the MessagePack package to your target dependencies in `Package.swift`:
|
||||
|
||||
```swift
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "YourProject",
|
||||
dependencies: [
|
||||
.package(
|
||||
url: "https://github.com/Flight-School/MessagePack",
|
||||
from: "1.2.3"
|
||||
),
|
||||
]
|
||||
)
|
||||
```
|
||||
|
||||
Then run the `swift build` command to build your project.
|
||||
|
||||
### CocoaPods
|
||||
|
||||
You can install `MessagePack` via CocoaPods,
|
||||
by adding the following line to your `Podfile`:
|
||||
|
||||
```ruby
|
||||
pod 'MessagePack-FlightSchool', '~> 1.2.4'
|
||||
```
|
||||
|
||||
Run the `pod install` command to download the library
|
||||
and integrate it into your Xcode project.
|
||||
|
||||
> **Note**
|
||||
> The module name for this library is "MessagePack" ---
|
||||
> that is, to use it, you add `import MessagePack` to the top of your Swift code
|
||||
> just as you would by any other installation method.
|
||||
> The pod is called "MessagePack-FlightSchool"
|
||||
> because there's an existing pod with the name "MessagePack".
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
## Contact
|
||||
|
||||
Mattt ([@mattt](https://twitter.com/mattt))
|
||||
|
||||
[build status]: https://github.com/Flight-School/MessagePack/actions?query=workflow%3ACI
|
||||
[build status badge]: https://github.com/Flight-School/MessagePack/workflows/CI/badge.svg
|
||||
28
lib/MessagePack/Sources/MessagePack/AnyCodingKey.swift
Normal file
28
lib/MessagePack/Sources/MessagePack/AnyCodingKey.swift
Normal file
@@ -0,0 +1,28 @@
|
||||
struct AnyCodingKey: CodingKey, Equatable {
|
||||
var stringValue: String
|
||||
var intValue: Int?
|
||||
|
||||
init?(stringValue: String) {
|
||||
self.stringValue = stringValue
|
||||
self.intValue = nil
|
||||
}
|
||||
|
||||
init?(intValue: Int) {
|
||||
self.stringValue = "\(intValue)"
|
||||
self.intValue = intValue
|
||||
}
|
||||
|
||||
init<Key>(_ base: Key) where Key : CodingKey {
|
||||
if let intValue = base.intValue {
|
||||
self.init(intValue: intValue)!
|
||||
} else {
|
||||
self.init(stringValue: base.stringValue)!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension AnyCodingKey: Hashable {
|
||||
var hashValue: Int {
|
||||
return self.intValue?.hashValue ?? self.stringValue.hashValue
|
||||
}
|
||||
}
|
||||
44
lib/MessagePack/Sources/MessagePack/Box.swift
Normal file
44
lib/MessagePack/Sources/MessagePack/Box.swift
Normal file
@@ -0,0 +1,44 @@
|
||||
import Foundation
|
||||
|
||||
struct Box<Value> {
|
||||
let value: Value
|
||||
init(_ value: Value) {
|
||||
self.value = value
|
||||
}
|
||||
}
|
||||
|
||||
extension Box: Encodable where Value: Encodable {
|
||||
func encode(to encoder: Encoder) throws {
|
||||
try self.value.encode(to: encoder)
|
||||
}
|
||||
}
|
||||
|
||||
extension Box: Decodable where Value: Decodable {
|
||||
init(from decoder: Decoder) throws {
|
||||
self.init(try Value(from: decoder))
|
||||
}
|
||||
}
|
||||
|
||||
extension Box where Value == Data {
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
self.init(try container.decode(Value.self))
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
try container.encode(self.value)
|
||||
}
|
||||
}
|
||||
|
||||
extension Box where Value == Date {
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
self.init(try container.decode(Value.self))
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
try container.encode(self.value)
|
||||
}
|
||||
}
|
||||
60
lib/MessagePack/Sources/MessagePack/DataSpec.swift
Normal file
60
lib/MessagePack/Sources/MessagePack/DataSpec.swift
Normal file
@@ -0,0 +1,60 @@
|
||||
import Foundation
|
||||
|
||||
public struct DataSpec {
|
||||
let name: String
|
||||
let isObj: Bool
|
||||
let isArray: Bool
|
||||
let dataSpecBuilder: DataSpecBuilder?
|
||||
|
||||
init(_ name: String, _ isObj: Bool, _ isArray: Bool, _ dataSpecBuilder: DataSpecBuilder?) {
|
||||
self.name = name
|
||||
self.isObj = isObj
|
||||
self.isArray = isArray
|
||||
self.dataSpecBuilder = dataSpecBuilder
|
||||
}
|
||||
}
|
||||
|
||||
public class DataSpecBuilder : NSCopying {
|
||||
var specs: [DataSpec] = []
|
||||
var specsIterator: IndexingIterator<[DataSpec]>
|
||||
|
||||
init() {
|
||||
specsIterator = IndexingIterator(_elements: [])
|
||||
}
|
||||
|
||||
func append(_ name: String) -> DataSpecBuilder {
|
||||
return append(DataSpec(name, false, false, nil))
|
||||
}
|
||||
|
||||
func appendObj(_ name: String, _ dataSpecBuilder: DataSpecBuilder) -> DataSpecBuilder {
|
||||
return append(DataSpec(name, true, false, dataSpecBuilder))
|
||||
}
|
||||
|
||||
func appendArray(_ name: String) -> DataSpecBuilder {
|
||||
return append(DataSpec(name, false, true, nil))
|
||||
}
|
||||
|
||||
func appendArray(_ name: String, _ dataSpecBuilder: DataSpecBuilder) -> DataSpecBuilder {
|
||||
return append(DataSpec(name, false, true, dataSpecBuilder))
|
||||
}
|
||||
|
||||
func append(_ spec: DataSpec) -> DataSpecBuilder {
|
||||
specs.append(spec)
|
||||
return self
|
||||
}
|
||||
|
||||
func build() -> DataSpecBuilder {
|
||||
specsIterator = specs.makeIterator()
|
||||
return self
|
||||
}
|
||||
|
||||
func next() -> DataSpec {
|
||||
return specsIterator.next()!
|
||||
}
|
||||
|
||||
public func copy(with zone: NSZone? = nil) -> Any {
|
||||
let b = DataSpecBuilder()
|
||||
b.specs = specs
|
||||
return b.build()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
import Foundation
|
||||
|
||||
extension _MessagePackDecoder {
|
||||
final class KeyedContainer<Key> where Key: CodingKey {
|
||||
lazy var nestedContainers: [String: MessagePackDecodingContainer] = {
|
||||
guard let count = self.count else {
|
||||
return [:]
|
||||
}
|
||||
|
||||
var nestedContainers: [String: MessagePackDecodingContainer] = [:]
|
||||
|
||||
let unkeyedContainer = UnkeyedContainer(data: self.data.suffix(from: self.index), codingPath: self.codingPath, userInfo: self.userInfo)
|
||||
if currentSpec != nil && currentSpec!.isObj {
|
||||
unkeyedContainer.count = count
|
||||
} else {
|
||||
unkeyedContainer.count = count * 2
|
||||
}
|
||||
|
||||
do {
|
||||
var iterator = unkeyedContainer.nestedContainers.makeIterator()
|
||||
|
||||
for _ in 0..<count {
|
||||
var key: String = ""
|
||||
if currentSpec == nil || !currentSpec!.isObj {
|
||||
guard let keyContainer = iterator.next() as? _MessagePackDecoder.SingleValueContainer else {
|
||||
fatalError() // FIXME
|
||||
}
|
||||
|
||||
key = try keyContainer.decode(String.self)
|
||||
}
|
||||
|
||||
guard let container = iterator.next() else {
|
||||
fatalError() // FIXME
|
||||
}
|
||||
|
||||
|
||||
if currentSpec != nil && currentSpec!.isObj {
|
||||
key = container.currentSpec!.name
|
||||
}
|
||||
|
||||
container.codingPath += [AnyCodingKey(stringValue: key)!]
|
||||
nestedContainers[key] = container
|
||||
}
|
||||
} catch {
|
||||
fatalError("\(error)") // FIXME
|
||||
}
|
||||
|
||||
self.index = unkeyedContainer.index
|
||||
|
||||
return nestedContainers
|
||||
}()
|
||||
|
||||
lazy var count: Int? = {
|
||||
do {
|
||||
let format = try self.readByte()
|
||||
|
||||
if currentSpec != nil && currentSpec!.isObj && 0x90...0x9f ~= format {
|
||||
return Int(format & 0x0F)
|
||||
}
|
||||
|
||||
switch format {
|
||||
case 0x80...0x8f:
|
||||
return Int(format & 0x0F)
|
||||
case 0xde:
|
||||
return Int(try read(UInt16.self))
|
||||
case 0xdf:
|
||||
return Int(try read(UInt32.self))
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
}()
|
||||
|
||||
var data: Data
|
||||
var index: Data.Index
|
||||
var codingPath: [CodingKey]
|
||||
var userInfo: [CodingUserInfoKey: Any]
|
||||
var currentSpec: DataSpec?
|
||||
|
||||
func nestedCodingPath(forKey key: CodingKey) -> [CodingKey] {
|
||||
return self.codingPath + [key]
|
||||
}
|
||||
|
||||
init(data: Data, codingPath: [CodingKey], userInfo: [CodingUserInfoKey : Any]) {
|
||||
self.codingPath = codingPath
|
||||
self.userInfo = userInfo
|
||||
self.data = data
|
||||
self.index = self.data.startIndex
|
||||
}
|
||||
|
||||
func checkCanDecodeValue(forKey key: Key) throws {
|
||||
guard self.contains(key) else {
|
||||
let context = DecodingError.Context(codingPath: self.codingPath, debugDescription: "key not found: \(key)")
|
||||
throw DecodingError.keyNotFound(key, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension _MessagePackDecoder.KeyedContainer: KeyedDecodingContainerProtocol {
|
||||
var allKeys: [Key] {
|
||||
return self.nestedContainers.keys.map{ Key(stringValue: $0)! }
|
||||
}
|
||||
|
||||
func contains(_ key: Key) -> Bool {
|
||||
return self.nestedContainers.keys.contains(key.stringValue)
|
||||
}
|
||||
|
||||
func decodeNil(forKey key: Key) throws -> Bool {
|
||||
try checkCanDecodeValue(forKey: key)
|
||||
|
||||
let nestedContainer = self.nestedContainers[key.stringValue]
|
||||
|
||||
switch nestedContainer {
|
||||
case let singleValueContainer as _MessagePackDecoder.SingleValueContainer:
|
||||
return singleValueContainer.decodeNil()
|
||||
case is _MessagePackDecoder.UnkeyedContainer,
|
||||
is _MessagePackDecoder.KeyedContainer<AnyCodingKey>:
|
||||
return false
|
||||
default:
|
||||
let context = DecodingError.Context(codingPath: self.codingPath, debugDescription: "cannot decode nil for key: \(key)")
|
||||
throw DecodingError.typeMismatch(Any?.self, context)
|
||||
}
|
||||
}
|
||||
|
||||
func decode<T>(_ type: T.Type, forKey key: Key) throws -> T where T : Decodable {
|
||||
try checkCanDecodeValue(forKey: key)
|
||||
|
||||
let container = self.nestedContainers[key.stringValue]!
|
||||
let decoder = MessagePackDecoder()
|
||||
|
||||
if userInfo.keys.contains(MessagePackDecoder.dataSpecKey) {
|
||||
decoder.userInfo[MessagePackDecoder.dataSpecKey] = container.currentSpec!.dataSpecBuilder?.copy() as? DataSpecBuilder
|
||||
if container.currentSpec!.isArray {
|
||||
decoder.userInfo[MessagePackDecoder.isArrayDataSpecKey] = true
|
||||
}
|
||||
}
|
||||
|
||||
let value = try decoder.decode(T.self, from: container.data)
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer {
|
||||
try checkCanDecodeValue(forKey: key)
|
||||
|
||||
guard let unkeyedContainer = self.nestedContainers[key.stringValue] as? _MessagePackDecoder.UnkeyedContainer else {
|
||||
throw DecodingError.dataCorruptedError(forKey: key, in: self, debugDescription: "cannot decode nested container for key: \(key)")
|
||||
}
|
||||
|
||||
return unkeyedContainer
|
||||
}
|
||||
|
||||
func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer<NestedKey> where NestedKey : CodingKey {
|
||||
try checkCanDecodeValue(forKey: key)
|
||||
|
||||
guard let keyedContainer = self.nestedContainers[key.stringValue] as? _MessagePackDecoder.KeyedContainer<NestedKey> else {
|
||||
throw DecodingError.dataCorruptedError(forKey: key, in: self, debugDescription: "cannot decode nested container for key: \(key)")
|
||||
}
|
||||
|
||||
return KeyedDecodingContainer(keyedContainer)
|
||||
}
|
||||
|
||||
func superDecoder() throws -> Decoder {
|
||||
return _MessagePackDecoder(data: self.data)
|
||||
}
|
||||
|
||||
func superDecoder(forKey key: Key) throws -> Decoder {
|
||||
let decoder = _MessagePackDecoder(data: self.data)
|
||||
decoder.codingPath = [key]
|
||||
|
||||
return decoder
|
||||
}
|
||||
}
|
||||
|
||||
extension _MessagePackDecoder.KeyedContainer: MessagePackDecodingContainer {}
|
||||
@@ -0,0 +1,168 @@
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
An object that decodes instances of a data type from MessagePack objects.
|
||||
*/
|
||||
final public class MessagePackDecoder {
|
||||
public init() {}
|
||||
|
||||
/**
|
||||
A dictionary you use to customize the decoding process
|
||||
by providing contextual information.
|
||||
*/
|
||||
public var userInfo: [CodingUserInfoKey : Any] = [:]
|
||||
|
||||
/**
|
||||
Returns a value of the type you specify,
|
||||
decoded from a MessagePack object.
|
||||
|
||||
- Parameters:
|
||||
- type: The type of the value to decode
|
||||
from the supplied MessagePack object.
|
||||
- data: The MessagePack object to decode.
|
||||
- Throws: `DecodingError.dataCorrupted(_:)`
|
||||
if the data is not valid MessagePack.
|
||||
*/
|
||||
public func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable {
|
||||
let decoder = _MessagePackDecoder(data: data)
|
||||
decoder.userInfo = self.userInfo
|
||||
decoder.userInfo[MessagePackDecoder.nonMatchingFloatDecodingStrategyKey] = nonMatchingFloatDecodingStrategy
|
||||
|
||||
switch type {
|
||||
case is Data.Type:
|
||||
let box = try Box<Data>(from: decoder)
|
||||
return box.value as! T
|
||||
case is Date.Type:
|
||||
let box = try Box<Date>(from: decoder)
|
||||
return box.value as! T
|
||||
default:
|
||||
return try T(from: decoder)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
The strategy used by a decoder when it encounters format mismatches for floating point values.
|
||||
*/
|
||||
public var nonMatchingFloatDecodingStrategy: NonMatchingFloatDecodingStrategy = .strict
|
||||
|
||||
/**
|
||||
The strategies for decoding floating point values when their format doesn't match.
|
||||
*/
|
||||
public enum NonMatchingFloatDecodingStrategy {
|
||||
|
||||
/// Throws a DecodingError.typeMismatch
|
||||
case strict
|
||||
|
||||
/// Performs a cast
|
||||
case cast
|
||||
}
|
||||
|
||||
internal static var nonMatchingFloatDecodingStrategyKey: CodingUserInfoKey {
|
||||
return CodingUserInfoKey(rawValue: "nonMatchingFloatDecodingStrategyKey")!
|
||||
}
|
||||
|
||||
static var dataSpecKey : CodingUserInfoKey {
|
||||
return CodingUserInfoKey(rawValue: "dataSpecKey")!
|
||||
}
|
||||
|
||||
static var isArrayDataSpecKey : CodingUserInfoKey {
|
||||
return CodingUserInfoKey(rawValue: "isArrayDataSpecKey")!
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - TopLevelDecoder
|
||||
|
||||
#if canImport(Combine)
|
||||
import Combine
|
||||
|
||||
extension MessagePackDecoder: TopLevelDecoder {
|
||||
public typealias Input = Data
|
||||
}
|
||||
#endif
|
||||
|
||||
// MARK: -
|
||||
|
||||
final class _MessagePackDecoder {
|
||||
var codingPath: [CodingKey] = []
|
||||
|
||||
var userInfo: [CodingUserInfoKey : Any] = [:]
|
||||
|
||||
var container: MessagePackDecodingContainer?
|
||||
fileprivate var data: Data
|
||||
|
||||
init(data: Data) {
|
||||
self.data = data
|
||||
}
|
||||
}
|
||||
|
||||
extension _MessagePackDecoder: Decoder {
|
||||
fileprivate func assertCanCreateContainer() {
|
||||
precondition(self.container == nil)
|
||||
}
|
||||
|
||||
func container<Key>(keyedBy type: Key.Type) -> KeyedDecodingContainer<Key> where Key : CodingKey {
|
||||
assertCanCreateContainer()
|
||||
|
||||
let container = KeyedContainer<Key>(data: self.data, codingPath: self.codingPath, userInfo: self.userInfo)
|
||||
|
||||
if userInfo.keys.contains(MessagePackDecoder.dataSpecKey) {
|
||||
container.currentSpec = DataSpec("", true, false, nil)
|
||||
}
|
||||
|
||||
self.container = container
|
||||
|
||||
return KeyedDecodingContainer(container)
|
||||
}
|
||||
|
||||
func unkeyedContainer() -> UnkeyedDecodingContainer {
|
||||
assertCanCreateContainer()
|
||||
|
||||
let container = UnkeyedContainer(data: self.data, codingPath: self.codingPath, userInfo: self.userInfo)
|
||||
self.container = container
|
||||
|
||||
return container
|
||||
}
|
||||
|
||||
func singleValueContainer() -> SingleValueDecodingContainer {
|
||||
assertCanCreateContainer()
|
||||
|
||||
let container = SingleValueContainer(data: self.data, codingPath: self.codingPath, userInfo: self.userInfo)
|
||||
self.container = container
|
||||
|
||||
return container
|
||||
}
|
||||
}
|
||||
|
||||
protocol MessagePackDecodingContainer: class {
|
||||
var codingPath: [CodingKey] { get set }
|
||||
|
||||
var userInfo: [CodingUserInfoKey : Any] { get }
|
||||
|
||||
var data: Data { get set }
|
||||
var index: Data.Index { get set }
|
||||
|
||||
var currentSpec: DataSpec? { get set }
|
||||
}
|
||||
|
||||
extension MessagePackDecodingContainer {
|
||||
func readByte() throws -> UInt8 {
|
||||
return try read(1).first!
|
||||
}
|
||||
|
||||
func read(_ length: Int) throws -> Data {
|
||||
let nextIndex = self.index.advanced(by: length)
|
||||
guard nextIndex <= self.data.endIndex else {
|
||||
let context = DecodingError.Context(codingPath: self.codingPath, debugDescription: "Unexpected end of data")
|
||||
throw DecodingError.dataCorrupted(context)
|
||||
}
|
||||
defer { self.index = nextIndex }
|
||||
|
||||
return self.data.subdata(in: self.index..<nextIndex)
|
||||
}
|
||||
|
||||
func read<T>(_ type: T.Type) throws -> T where T : FixedWidthInteger {
|
||||
let stride = MemoryLayout<T>.stride
|
||||
let bytes = [UInt8](try read(stride))
|
||||
return T(bytes: bytes)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
import Foundation
|
||||
|
||||
#if os(Linux)
|
||||
let NSEC_PER_SEC: UInt64 = 1000000000
|
||||
#endif
|
||||
|
||||
extension _MessagePackDecoder {
|
||||
final class SingleValueContainer {
|
||||
var codingPath: [CodingKey]
|
||||
var userInfo: [CodingUserInfoKey: Any]
|
||||
var data: Data
|
||||
var index: Data.Index
|
||||
var currentSpec: DataSpec?
|
||||
|
||||
init(data: Data, codingPath: [CodingKey], userInfo: [CodingUserInfoKey : Any]) {
|
||||
self.codingPath = codingPath
|
||||
self.userInfo = userInfo
|
||||
self.data = data
|
||||
self.index = self.data.startIndex
|
||||
}
|
||||
|
||||
func checkCanDecode<T>(_ type: T.Type, format: UInt8) throws {
|
||||
guard self.index <= self.data.endIndex else {
|
||||
throw DecodingError.dataCorruptedError(in: self, debugDescription: "Unexpected end of data")
|
||||
}
|
||||
|
||||
guard self.data[self.index] == format else {
|
||||
let context = DecodingError.Context(codingPath: self.codingPath, debugDescription: "Invalid format: \(format)")
|
||||
throw DecodingError.typeMismatch(type, context)
|
||||
}
|
||||
}
|
||||
|
||||
var nonMatchingFloatDecodingStrategy: MessagePackDecoder.NonMatchingFloatDecodingStrategy {
|
||||
return userInfo[MessagePackDecoder.nonMatchingFloatDecodingStrategyKey] as? MessagePackDecoder.NonMatchingFloatDecodingStrategy ?? .strict
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension _MessagePackDecoder.SingleValueContainer: SingleValueDecodingContainer {
|
||||
func decodeNil() -> Bool {
|
||||
let format = try? readByte()
|
||||
return format == 0xc0
|
||||
}
|
||||
|
||||
func decode(_ type: Bool.Type) throws -> Bool {
|
||||
let format = try readByte()
|
||||
switch format {
|
||||
case 0xc2: return false
|
||||
case 0xc3: return true
|
||||
default:
|
||||
let context = DecodingError.Context(codingPath: self.codingPath, debugDescription: "Invalid format: \(format)")
|
||||
throw DecodingError.typeMismatch(Bool.self, context)
|
||||
}
|
||||
}
|
||||
|
||||
func decode(_ type: String.Type) throws -> String {
|
||||
let length: Int
|
||||
let format = try readByte()
|
||||
switch format {
|
||||
case 0xa0...0xbf:
|
||||
length = Int(format - 0xa0)
|
||||
case 0xd9:
|
||||
length = Int(try read(UInt8.self))
|
||||
case 0xda:
|
||||
length = Int(try read(UInt16.self))
|
||||
case 0xdb:
|
||||
length = Int(try read(UInt32.self))
|
||||
default:
|
||||
throw DecodingError.dataCorruptedError(in: self, debugDescription: "Invalid format for String length: \(format)")
|
||||
}
|
||||
|
||||
let data = try read(length)
|
||||
guard let string = String(data: data, encoding: .utf8) else {
|
||||
let context = DecodingError.Context(codingPath: self.codingPath, debugDescription: "Couldn't decode string with UTF-8 encoding")
|
||||
throw DecodingError.dataCorrupted(context)
|
||||
}
|
||||
|
||||
return string
|
||||
}
|
||||
|
||||
func decode(_ type: Double.Type) throws -> Double {
|
||||
let format = try readByte()
|
||||
switch format {
|
||||
case 0xca:
|
||||
switch nonMatchingFloatDecodingStrategy {
|
||||
case .strict:
|
||||
break
|
||||
case .cast:
|
||||
let bitPattern = try read(UInt32.self)
|
||||
return Double(Float(bitPattern: bitPattern))
|
||||
}
|
||||
case 0xcb:
|
||||
let bitPattern = try read(UInt64.self)
|
||||
return Double(bitPattern: bitPattern)
|
||||
default:
|
||||
break
|
||||
}
|
||||
let context = DecodingError.Context(codingPath: self.codingPath, debugDescription: "Invalid format: \(format)")
|
||||
throw DecodingError.typeMismatch(Double.self, context)
|
||||
}
|
||||
|
||||
func decode(_ type: Float.Type) throws -> Float {
|
||||
let format = try readByte()
|
||||
switch format {
|
||||
case 0xca:
|
||||
let bitPattern = try read(UInt32.self)
|
||||
return Float(bitPattern: bitPattern)
|
||||
case 0xcb:
|
||||
switch nonMatchingFloatDecodingStrategy {
|
||||
case .strict:
|
||||
break
|
||||
case .cast:
|
||||
let bitPattern = try read(UInt64.self)
|
||||
return Float(Double(bitPattern: bitPattern))
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
let context = DecodingError.Context(codingPath: self.codingPath, debugDescription: "Invalid format: \(format)")
|
||||
throw DecodingError.typeMismatch(Float.self, context)
|
||||
}
|
||||
|
||||
func decode<T>(_ type: T.Type) throws -> T where T : BinaryInteger & Decodable {
|
||||
let format = try readByte()
|
||||
var t: T?
|
||||
|
||||
switch format {
|
||||
case 0x00...0x7f:
|
||||
t = T(format)
|
||||
case 0xcc:
|
||||
t = T(exactly: try read(UInt8.self))
|
||||
case 0xcd:
|
||||
t = T(exactly: try read(UInt16.self))
|
||||
case 0xce:
|
||||
t = T(exactly: try read(UInt32.self))
|
||||
case 0xcf:
|
||||
t = T(exactly: try read(UInt64.self))
|
||||
case 0xd0:
|
||||
t = T(exactly: try read(Int8.self))
|
||||
case 0xd1:
|
||||
t = T(exactly: try read(Int16.self))
|
||||
case 0xd2:
|
||||
t = T(exactly: try read(Int32.self))
|
||||
case 0xd3:
|
||||
t = T(exactly: try read(Int64.self))
|
||||
case 0xe0...0xff:
|
||||
t = T(exactly: Int8(bitPattern: format))
|
||||
default:
|
||||
t = nil
|
||||
}
|
||||
|
||||
guard let value = t else {
|
||||
let context = DecodingError.Context(codingPath: self.codingPath, debugDescription: "Invalid format: \(format)")
|
||||
throw DecodingError.typeMismatch(T.self, context)
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
func decode(_ type: Date.Type) throws -> Date {
|
||||
let format = try readByte()
|
||||
|
||||
var seconds: TimeInterval
|
||||
var nanoseconds: TimeInterval
|
||||
|
||||
switch format {
|
||||
case 0xd6:
|
||||
_ = try read(Int8.self) // -1
|
||||
nanoseconds = 0
|
||||
seconds = TimeInterval(try read(UInt32.self))
|
||||
case 0xd7:
|
||||
_ = try read(Int8.self) // -1
|
||||
let bitPattern = try read(UInt64.self)
|
||||
nanoseconds = TimeInterval(UInt32(bitPattern >> 34))
|
||||
seconds = TimeInterval(UInt32(bitPattern & 0x03_FF_FF_FF_FF))
|
||||
case 0xc7:
|
||||
_ = try read(Int8.self) // 12
|
||||
_ = try read(Int8.self) // -1
|
||||
nanoseconds = TimeInterval(try read(UInt32.self))
|
||||
seconds = TimeInterval(try read(Int64.self))
|
||||
default:
|
||||
let context = DecodingError.Context(codingPath: self.codingPath, debugDescription: "Invalid format: \(format)")
|
||||
throw DecodingError.typeMismatch(Date.self, context)
|
||||
}
|
||||
|
||||
let timeInterval = TimeInterval(seconds) + nanoseconds / Double(NSEC_PER_SEC)
|
||||
|
||||
return Date(timeIntervalSince1970: timeInterval)
|
||||
}
|
||||
|
||||
func decode(_ type: Data.Type) throws -> Data {
|
||||
let length: Int
|
||||
let format = try readByte()
|
||||
switch format {
|
||||
case 0xc4:
|
||||
length = Int(try read(UInt8.self))
|
||||
case 0xc5:
|
||||
length = Int(try read(UInt16.self))
|
||||
case 0xc6:
|
||||
length = Int(try read(UInt32.self))
|
||||
default:
|
||||
throw DecodingError.dataCorruptedError(in: self, debugDescription: "Invalid format for Data length: \(format)")
|
||||
}
|
||||
|
||||
return self.data.subdata(in: self.index..<self.index.advanced(by: length))
|
||||
}
|
||||
|
||||
func decode<T>(_ type: T.Type) throws -> T where T : Decodable {
|
||||
switch type {
|
||||
case is Data.Type:
|
||||
return try decode(Data.self) as! T
|
||||
case is Date.Type:
|
||||
return try decode(Date.self) as! T
|
||||
default:
|
||||
let decoder = _MessagePackDecoder(data: self.data)
|
||||
let value = try T(from: decoder)
|
||||
if let nextIndex = decoder.container?.index {
|
||||
self.index = nextIndex
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension _MessagePackDecoder.SingleValueContainer: MessagePackDecodingContainer {}
|
||||
@@ -0,0 +1,235 @@
|
||||
import Foundation
|
||||
|
||||
extension _MessagePackDecoder {
|
||||
final class UnkeyedContainer {
|
||||
var codingPath: [CodingKey]
|
||||
|
||||
var nestedCodingPath: [CodingKey] {
|
||||
return self.codingPath + [AnyCodingKey(intValue: self.count ?? 0)!]
|
||||
}
|
||||
|
||||
var userInfo: [CodingUserInfoKey: Any]
|
||||
|
||||
var data: Data
|
||||
var index: Data.Index
|
||||
var currentSpec: DataSpec?
|
||||
|
||||
lazy var count: Int? = {
|
||||
do {
|
||||
let format = try self.readByte()
|
||||
switch format {
|
||||
case 0x90...0x9f:
|
||||
return Int(format & 0x0F)
|
||||
case 0xdc:
|
||||
return Int(try read(UInt16.self))
|
||||
case 0xdd:
|
||||
return Int(try read(UInt32.self))
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
}()
|
||||
|
||||
var currentIndex: Int = 0
|
||||
|
||||
lazy var nestedContainers: [MessagePackDecodingContainer] = {
|
||||
guard let count = self.count else {
|
||||
return []
|
||||
}
|
||||
|
||||
var nestedContainers: [MessagePackDecodingContainer] = []
|
||||
|
||||
do {
|
||||
for _ in 0..<count {
|
||||
let container = try self.decodeContainer()
|
||||
nestedContainers.append(container)
|
||||
}
|
||||
} catch {
|
||||
fatalError("\(error)") // FIXME
|
||||
}
|
||||
|
||||
self.currentIndex = 0
|
||||
|
||||
return nestedContainers
|
||||
}()
|
||||
|
||||
init(data: Data, codingPath: [CodingKey], userInfo: [CodingUserInfoKey : Any]) {
|
||||
self.codingPath = codingPath
|
||||
self.userInfo = userInfo
|
||||
self.data = data
|
||||
self.index = self.data.startIndex
|
||||
}
|
||||
|
||||
var isAtEnd: Bool {
|
||||
guard let count = self.count else {
|
||||
return true
|
||||
}
|
||||
|
||||
return currentIndex >= count
|
||||
}
|
||||
|
||||
func checkCanDecodeValue() throws {
|
||||
guard !self.isAtEnd else {
|
||||
throw DecodingError.dataCorruptedError(in: self, debugDescription: "Unexpected end of data")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension _MessagePackDecoder.UnkeyedContainer: UnkeyedDecodingContainer {
|
||||
func decodeNil() throws -> Bool {
|
||||
try checkCanDecodeValue()
|
||||
defer { self.currentIndex += 1 }
|
||||
|
||||
let nestedContainer = self.nestedContainers[self.currentIndex]
|
||||
|
||||
switch nestedContainer {
|
||||
case let singleValueContainer as _MessagePackDecoder.SingleValueContainer:
|
||||
return singleValueContainer.decodeNil()
|
||||
case is _MessagePackDecoder.UnkeyedContainer,
|
||||
is _MessagePackDecoder.KeyedContainer<AnyCodingKey>:
|
||||
return false
|
||||
default:
|
||||
let context = DecodingError.Context(codingPath: self.codingPath, debugDescription: "cannot decode nil for index: \(self.currentIndex)")
|
||||
throw DecodingError.typeMismatch(Any?.self, context)
|
||||
}
|
||||
}
|
||||
|
||||
func decode<T>(_ type: T.Type) throws -> T where T : Decodable {
|
||||
try checkCanDecodeValue()
|
||||
defer { self.currentIndex += 1 }
|
||||
|
||||
if userInfo.keys.contains(MessagePackDecoder.isArrayDataSpecKey) {
|
||||
currentSpec = DataSpec("", false, true, (userInfo[MessagePackDecoder.dataSpecKey] as? DataSpecBuilder)?.copy() as? DataSpecBuilder)
|
||||
}
|
||||
|
||||
let container = self.nestedContainers[self.currentIndex]
|
||||
let decoder = MessagePackDecoder()
|
||||
|
||||
if userInfo.keys.contains(MessagePackDecoder.dataSpecKey) {
|
||||
decoder.userInfo[MessagePackDecoder.dataSpecKey] = (userInfo[MessagePackDecoder.dataSpecKey] as? DataSpecBuilder)?.copy() as? DataSpecBuilder
|
||||
}
|
||||
|
||||
let value = try decoder.decode(T.self, from: container.data)
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer {
|
||||
try checkCanDecodeValue()
|
||||
defer { self.currentIndex += 1 }
|
||||
|
||||
let container = self.nestedContainers[self.currentIndex] as! _MessagePackDecoder.UnkeyedContainer
|
||||
|
||||
return container
|
||||
}
|
||||
|
||||
func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer<NestedKey> where NestedKey : CodingKey {
|
||||
try checkCanDecodeValue()
|
||||
defer { self.currentIndex += 1 }
|
||||
|
||||
let container = self.nestedContainers[self.currentIndex] as! _MessagePackDecoder.KeyedContainer<NestedKey>
|
||||
|
||||
return KeyedDecodingContainer(container)
|
||||
}
|
||||
|
||||
func superDecoder() throws -> Decoder {
|
||||
return _MessagePackDecoder(data: self.data)
|
||||
}
|
||||
}
|
||||
|
||||
extension _MessagePackDecoder.UnkeyedContainer {
|
||||
func decodeContainer() throws -> MessagePackDecodingContainer {
|
||||
try checkCanDecodeValue()
|
||||
defer { self.currentIndex += 1 }
|
||||
|
||||
let startIndex = self.index
|
||||
|
||||
var currDataSpec: DataSpec? = nil
|
||||
if currentSpec != nil && currentSpec!.isArray && currentSpec!.dataSpecBuilder != nil {
|
||||
currDataSpec = DataSpec("", true, false, currentSpec!.dataSpecBuilder!.copy() as? DataSpecBuilder)
|
||||
} else {
|
||||
let dataSpec = self.userInfo[MessagePackDecoder.dataSpecKey] as? DataSpecBuilder
|
||||
if let currDS = dataSpec?.next() {
|
||||
currDataSpec = DataSpec(currDS.name, currDS.isObj, currDS.isArray, currDS.dataSpecBuilder?.copy() as? DataSpecBuilder)
|
||||
}
|
||||
}
|
||||
|
||||
let length: Int
|
||||
let format = try self.readByte()
|
||||
switch format {
|
||||
case 0x00...0x7f,
|
||||
0xc0, 0xc2, 0xc3,
|
||||
0xe0...0xff:
|
||||
length = 0
|
||||
case 0xcc, 0xd0, 0xd4:
|
||||
length = 1
|
||||
case 0xcd, 0xd1, 0xd5:
|
||||
length = 2
|
||||
case 0xca, 0xce, 0xd2:
|
||||
length = 4
|
||||
case 0xcb, 0xcf, 0xd3:
|
||||
length = 8
|
||||
case 0xd6:
|
||||
length = 5
|
||||
case 0xd7:
|
||||
length = 9
|
||||
case 0xd8:
|
||||
length = 16
|
||||
case 0xa0...0xbf:
|
||||
length = Int(format - 0xa0)
|
||||
case 0xc4, 0xc7, 0xd9:
|
||||
length = Int(try read(UInt8.self))
|
||||
case 0xc5, 0xc8, 0xda:
|
||||
length = Int(try read(UInt16.self))
|
||||
case 0xc6, 0xc9, 0xdb:
|
||||
length = Int(try read(UInt32.self))
|
||||
case 0x80...0x8f, 0xde, 0xdf:
|
||||
let container = _MessagePackDecoder.KeyedContainer<AnyCodingKey>(data: self.data.suffix(from: startIndex), codingPath: self.nestedCodingPath, userInfo: self.userInfo)
|
||||
container.currentSpec = currDataSpec
|
||||
_ = container.nestedContainers // FIXME
|
||||
self.index = container.index
|
||||
|
||||
return container
|
||||
case 0x90...0x9f, 0xdc, 0xdd:
|
||||
if currDataSpec != nil && currDataSpec!.isObj {
|
||||
var objUserInfo = self.userInfo
|
||||
objUserInfo[MessagePackDecoder.dataSpecKey] = currDataSpec!.dataSpecBuilder!
|
||||
|
||||
let container = _MessagePackDecoder.KeyedContainer<AnyCodingKey>(data: self.data.suffix(from: startIndex), codingPath: self.nestedCodingPath, userInfo: objUserInfo)
|
||||
container.currentSpec = currDataSpec
|
||||
_ = container.nestedContainers // FIXME
|
||||
self.index = container.index
|
||||
|
||||
return container
|
||||
}
|
||||
|
||||
var arrUserInfo = self.userInfo
|
||||
if currDataSpec != nil && currDataSpec!.isArray {
|
||||
arrUserInfo[MessagePackDecoder.dataSpecKey] = currDataSpec!.dataSpecBuilder!
|
||||
}
|
||||
|
||||
let container = _MessagePackDecoder.UnkeyedContainer(data: self.data.suffix(from: startIndex), codingPath: self.nestedCodingPath, userInfo: arrUserInfo)
|
||||
container.currentSpec = currDataSpec
|
||||
_ = container.nestedContainers // FIXME
|
||||
|
||||
self.index = container.index
|
||||
|
||||
return container
|
||||
default:
|
||||
throw DecodingError.dataCorruptedError(in: self, debugDescription: "Invalid format: \(format)")
|
||||
}
|
||||
|
||||
let range: Range<Data.Index> = startIndex..<self.index.advanced(by: length)
|
||||
self.index = range.upperBound
|
||||
|
||||
let container = _MessagePackDecoder.SingleValueContainer(data: self.data.subdata(in: range), codingPath: self.codingPath, userInfo: self.userInfo)
|
||||
container.currentSpec = currDataSpec
|
||||
|
||||
return container
|
||||
}
|
||||
}
|
||||
|
||||
extension _MessagePackDecoder.UnkeyedContainer: MessagePackDecodingContainer {}
|
||||
@@ -0,0 +1,90 @@
|
||||
import Foundation
|
||||
|
||||
extension _MessagePackEncoder {
|
||||
final class KeyedContainer<Key> where Key: CodingKey {
|
||||
private var storage: [AnyCodingKey: _MessagePackEncodingContainer] = [:]
|
||||
|
||||
var codingPath: [CodingKey]
|
||||
var userInfo: [CodingUserInfoKey: Any]
|
||||
|
||||
func nestedCodingPath(forKey key: CodingKey) -> [CodingKey] {
|
||||
return self.codingPath + [key]
|
||||
}
|
||||
|
||||
init(codingPath: [CodingKey], userInfo: [CodingUserInfoKey : Any]) {
|
||||
self.codingPath = codingPath
|
||||
self.userInfo = userInfo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension _MessagePackEncoder.KeyedContainer: KeyedEncodingContainerProtocol {
|
||||
func encodeNil(forKey key: Key) throws {
|
||||
var container = self.nestedSingleValueContainer(forKey: key)
|
||||
try container.encodeNil()
|
||||
}
|
||||
|
||||
func encode<T>(_ value: T, forKey key: Key) throws where T : Encodable {
|
||||
var container = self.nestedSingleValueContainer(forKey: key)
|
||||
try container.encode(value)
|
||||
}
|
||||
|
||||
private func nestedSingleValueContainer(forKey key: Key) -> SingleValueEncodingContainer {
|
||||
let container = _MessagePackEncoder.SingleValueContainer(codingPath: self.nestedCodingPath(forKey: key), userInfo: self.userInfo)
|
||||
self.storage[AnyCodingKey(key)] = container
|
||||
return container
|
||||
}
|
||||
|
||||
func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer {
|
||||
let container = _MessagePackEncoder.UnkeyedContainer(codingPath: self.nestedCodingPath(forKey: key), userInfo: self.userInfo)
|
||||
self.storage[AnyCodingKey(key)] = container
|
||||
|
||||
return container
|
||||
}
|
||||
|
||||
func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer<NestedKey> where NestedKey : CodingKey {
|
||||
let container = _MessagePackEncoder.KeyedContainer<NestedKey>(codingPath: self.nestedCodingPath(forKey: key), userInfo: self.userInfo)
|
||||
self.storage[AnyCodingKey(key)] = container
|
||||
|
||||
return KeyedEncodingContainer(container)
|
||||
}
|
||||
|
||||
func superEncoder() -> Encoder {
|
||||
fatalError("Unimplemented") // FIXME
|
||||
}
|
||||
|
||||
func superEncoder(forKey key: Key) -> Encoder {
|
||||
fatalError("Unimplemented") // FIXME
|
||||
}
|
||||
}
|
||||
|
||||
extension _MessagePackEncoder.KeyedContainer: _MessagePackEncodingContainer {
|
||||
var data: Data {
|
||||
var data = Data()
|
||||
|
||||
let length = storage.count
|
||||
if let uint16 = UInt16(exactly: length) {
|
||||
if length <= 15 {
|
||||
data.append(0x80 + UInt8(length))
|
||||
} else {
|
||||
data.append(0xde)
|
||||
data.append(contentsOf: uint16.bytes)
|
||||
}
|
||||
} else if let uint32 = UInt32(exactly: length) {
|
||||
data.append(0xdf)
|
||||
data.append(contentsOf: uint32.bytes)
|
||||
} else {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
for (key, container) in self.storage {
|
||||
let keyContainer = _MessagePackEncoder.SingleValueContainer(codingPath: self.codingPath, userInfo: self.userInfo)
|
||||
try! keyContainer.encode(key.stringValue)
|
||||
data.append(keyContainer.data)
|
||||
|
||||
data.append(container.data)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
An object that encodes instances of a data type as MessagePack objects.
|
||||
*/
|
||||
final public class MessagePackEncoder {
|
||||
public init() {}
|
||||
|
||||
/**
|
||||
A dictionary you use to customize the encoding process
|
||||
by providing contextual information.
|
||||
*/
|
||||
public var userInfo: [CodingUserInfoKey : Any] = [:]
|
||||
|
||||
/**
|
||||
Returns a MessagePack-encoded representation of the value you supply.
|
||||
|
||||
- Parameters:
|
||||
- value: The value to encode as MessagePack.
|
||||
- Throws: `EncodingError.invalidValue(_:_:)`
|
||||
if the value can't be encoded as a MessagePack object.
|
||||
*/
|
||||
public func encode<T>(_ value: T) throws -> Data where T : Encodable {
|
||||
let encoder = _MessagePackEncoder()
|
||||
encoder.userInfo = self.userInfo
|
||||
|
||||
switch value {
|
||||
case let data as Data:
|
||||
try Box<Data>(data).encode(to: encoder)
|
||||
case let date as Date:
|
||||
try Box<Date>(date).encode(to: encoder)
|
||||
default:
|
||||
try value.encode(to: encoder)
|
||||
}
|
||||
|
||||
return encoder.data
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - TopLevelEncoder
|
||||
|
||||
#if canImport(Combine)
|
||||
import Combine
|
||||
|
||||
extension MessagePackEncoder: TopLevelEncoder {
|
||||
public typealias Input = Data
|
||||
}
|
||||
#endif
|
||||
|
||||
// MARK: -
|
||||
|
||||
protocol _MessagePackEncodingContainer {
|
||||
var data: Data { get }
|
||||
}
|
||||
|
||||
class _MessagePackEncoder {
|
||||
var codingPath: [CodingKey] = []
|
||||
|
||||
var userInfo: [CodingUserInfoKey : Any] = [:]
|
||||
|
||||
fileprivate var container: _MessagePackEncodingContainer?
|
||||
|
||||
var data: Data {
|
||||
return container?.data ?? Data()
|
||||
}
|
||||
}
|
||||
|
||||
extension _MessagePackEncoder: Encoder {
|
||||
fileprivate func assertCanCreateContainer() {
|
||||
precondition(self.container == nil)
|
||||
}
|
||||
|
||||
func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> where Key : CodingKey {
|
||||
assertCanCreateContainer()
|
||||
|
||||
let container = KeyedContainer<Key>(codingPath: self.codingPath, userInfo: self.userInfo)
|
||||
self.container = container
|
||||
|
||||
return KeyedEncodingContainer(container)
|
||||
}
|
||||
|
||||
func unkeyedContainer() -> UnkeyedEncodingContainer {
|
||||
assertCanCreateContainer()
|
||||
|
||||
let container = UnkeyedContainer(codingPath: self.codingPath, userInfo: self.userInfo)
|
||||
self.container = container
|
||||
|
||||
return container
|
||||
}
|
||||
|
||||
func singleValueContainer() -> SingleValueEncodingContainer {
|
||||
assertCanCreateContainer()
|
||||
|
||||
let container = SingleValueContainer(codingPath: self.codingPath, userInfo: self.userInfo)
|
||||
self.container = container
|
||||
|
||||
return container
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
import Foundation
|
||||
|
||||
extension _MessagePackEncoder {
|
||||
final class SingleValueContainer {
|
||||
private var storage: Data = Data()
|
||||
|
||||
fileprivate var canEncodeNewValue = true
|
||||
fileprivate func checkCanEncode(value: Any?) throws {
|
||||
guard self.canEncodeNewValue else {
|
||||
let context = EncodingError.Context(codingPath: self.codingPath, debugDescription: "Attempt to encode value through single value container when previously value already encoded.")
|
||||
throw EncodingError.invalidValue(value as Any, context)
|
||||
}
|
||||
}
|
||||
|
||||
var codingPath: [CodingKey]
|
||||
var userInfo: [CodingUserInfoKey: Any]
|
||||
|
||||
init(codingPath: [CodingKey], userInfo: [CodingUserInfoKey : Any]) {
|
||||
self.codingPath = codingPath
|
||||
self.userInfo = userInfo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension _MessagePackEncoder.SingleValueContainer: SingleValueEncodingContainer {
|
||||
func encodeNil() throws {
|
||||
try checkCanEncode(value: nil)
|
||||
defer { self.canEncodeNewValue = false }
|
||||
|
||||
self.storage.append(0xc0)
|
||||
}
|
||||
|
||||
func encode(_ value: Bool) throws {
|
||||
try checkCanEncode(value: nil)
|
||||
defer { self.canEncodeNewValue = false }
|
||||
|
||||
switch value {
|
||||
case false:
|
||||
self.storage.append(0xc2)
|
||||
case true:
|
||||
self.storage.append(0xc3)
|
||||
}
|
||||
}
|
||||
|
||||
func encode(_ value: String) throws {
|
||||
try checkCanEncode(value: value)
|
||||
defer { self.canEncodeNewValue = false }
|
||||
|
||||
guard let data = value.data(using: .utf8) else {
|
||||
let context = EncodingError.Context(codingPath: self.codingPath, debugDescription: "Cannot encode string using UTF-8 encoding.")
|
||||
throw EncodingError.invalidValue(value, context)
|
||||
}
|
||||
|
||||
let length = data.count
|
||||
if let uint8 = UInt8(exactly: length) {
|
||||
if (uint8 <= 31) {
|
||||
self.storage.append(0xa0 + uint8)
|
||||
} else {
|
||||
self.storage.append(0xd9)
|
||||
self.storage.append(contentsOf: uint8.bytes)
|
||||
}
|
||||
} else if let uint16 = UInt16(exactly: length) {
|
||||
self.storage.append(0xda)
|
||||
self.storage.append(contentsOf: uint16.bytes)
|
||||
} else if let uint32 = UInt32(exactly: length) {
|
||||
self.storage.append(0xdb)
|
||||
self.storage.append(contentsOf: uint32.bytes)
|
||||
} else {
|
||||
let context = EncodingError.Context(codingPath: self.codingPath, debugDescription: "Cannot encode string with length \(length).")
|
||||
throw EncodingError.invalidValue(value, context)
|
||||
}
|
||||
|
||||
self.storage.append(data)
|
||||
}
|
||||
|
||||
func encode(_ value: Double) throws {
|
||||
try checkCanEncode(value: value)
|
||||
defer { self.canEncodeNewValue = false }
|
||||
|
||||
self.storage.append(0xcb)
|
||||
self.storage.append(contentsOf: value.bitPattern.bytes)
|
||||
}
|
||||
|
||||
func encode(_ value: Float) throws {
|
||||
try checkCanEncode(value: value)
|
||||
defer { self.canEncodeNewValue = false }
|
||||
|
||||
self.storage.append(0xca)
|
||||
self.storage.append(contentsOf: value.bitPattern.bytes)
|
||||
}
|
||||
|
||||
func encode<T>(_ value: T) throws where T : BinaryInteger & Encodable {
|
||||
try checkCanEncode(value: value)
|
||||
defer { self.canEncodeNewValue = false }
|
||||
|
||||
if value < 0 {
|
||||
if let int8 = Int8(exactly: value) {
|
||||
return try encode(int8)
|
||||
} else if let int16 = Int16(exactly: value) {
|
||||
return try encode(int16)
|
||||
} else if let int32 = Int32(exactly: value) {
|
||||
return try encode(int32)
|
||||
} else if let int64 = Int64(exactly: value) {
|
||||
return try encode(int64)
|
||||
}
|
||||
} else {
|
||||
if let uint8 = UInt8(exactly: value) {
|
||||
return try encode(uint8)
|
||||
} else if let uint16 = UInt16(exactly: value) {
|
||||
return try encode(uint16)
|
||||
} else if let uint32 = UInt32(exactly: value) {
|
||||
return try encode(uint32)
|
||||
} else if let uint64 = UInt64(exactly: value) {
|
||||
return try encode(uint64)
|
||||
}
|
||||
}
|
||||
|
||||
let context = EncodingError.Context(codingPath: self.codingPath, debugDescription: "Cannot encode integer \(value).")
|
||||
throw EncodingError.invalidValue(value, context)
|
||||
}
|
||||
|
||||
func encode(_ value: Int8) throws {
|
||||
try checkCanEncode(value: value)
|
||||
defer { self.canEncodeNewValue = false }
|
||||
|
||||
if (value >= 0 && value <= 127) {
|
||||
self.storage.append(UInt8(value))
|
||||
} else if (value < 0 && value >= -31) {
|
||||
self.storage.append(0xe0 + (0x1f & UInt8(truncatingIfNeeded: value)))
|
||||
} else {
|
||||
self.storage.append(0xd0)
|
||||
self.storage.append(contentsOf: value.bytes)
|
||||
}
|
||||
}
|
||||
|
||||
func encode(_ value: Int16) throws {
|
||||
try checkCanEncode(value: value)
|
||||
defer { self.canEncodeNewValue = false }
|
||||
|
||||
self.storage.append(0xd1)
|
||||
self.storage.append(contentsOf: value.bytes)
|
||||
}
|
||||
|
||||
func encode(_ value: Int32) throws {
|
||||
try checkCanEncode(value: value)
|
||||
defer { self.canEncodeNewValue = false }
|
||||
|
||||
self.storage.append(0xd2)
|
||||
self.storage.append(contentsOf: value.bytes)
|
||||
}
|
||||
|
||||
func encode(_ value: Int64) throws {
|
||||
try checkCanEncode(value: value)
|
||||
defer { self.canEncodeNewValue = false }
|
||||
|
||||
self.storage.append(0xd3)
|
||||
self.storage.append(contentsOf: value.bytes)
|
||||
}
|
||||
|
||||
func encode(_ value: UInt8) throws {
|
||||
try checkCanEncode(value: value)
|
||||
defer { self.canEncodeNewValue = false }
|
||||
|
||||
if (value <= 127) {
|
||||
self.storage.append(value)
|
||||
} else {
|
||||
self.storage.append(0xcc)
|
||||
self.storage.append(contentsOf: value.bytes)
|
||||
}
|
||||
}
|
||||
|
||||
func encode(_ value: UInt16) throws {
|
||||
try checkCanEncode(value: value)
|
||||
defer { self.canEncodeNewValue = false }
|
||||
|
||||
self.storage.append(0xcd)
|
||||
self.storage.append(contentsOf: value.bytes)
|
||||
}
|
||||
|
||||
func encode(_ value: UInt32) throws {
|
||||
try checkCanEncode(value: value)
|
||||
defer { self.canEncodeNewValue = false }
|
||||
|
||||
self.storage.append(0xce)
|
||||
self.storage.append(contentsOf: value.bytes)
|
||||
}
|
||||
|
||||
func encode(_ value: UInt64) throws {
|
||||
try checkCanEncode(value: value)
|
||||
defer { self.canEncodeNewValue = false }
|
||||
|
||||
self.storage.append(0xcf)
|
||||
self.storage.append(contentsOf: value.bytes)
|
||||
}
|
||||
|
||||
func encode(_ value: Date) throws {
|
||||
try checkCanEncode(value: value)
|
||||
defer { self.canEncodeNewValue = false }
|
||||
|
||||
let timeInterval = value.timeIntervalSince1970
|
||||
let (integral, fractional) = modf(timeInterval)
|
||||
|
||||
let seconds = Int64(integral)
|
||||
let nanoseconds = UInt32(fractional * Double(NSEC_PER_SEC))
|
||||
|
||||
if seconds < 0 || seconds > UInt32.max {
|
||||
self.storage.append(0xc7)
|
||||
self.storage.append(0x0C)
|
||||
self.storage.append(0xFF)
|
||||
self.storage.append(contentsOf: nanoseconds.bytes)
|
||||
self.storage.append(contentsOf: seconds.bytes)
|
||||
} else if nanoseconds > 0 {
|
||||
self.storage.append(0xd7)
|
||||
self.storage.append(0xFF)
|
||||
self.storage.append(contentsOf: ((UInt64(nanoseconds) << 34) + UInt64(seconds)).bytes)
|
||||
} else {
|
||||
self.storage.append(0xd6)
|
||||
self.storage.append(0xFF)
|
||||
self.storage.append(contentsOf: UInt32(seconds).bytes)
|
||||
}
|
||||
}
|
||||
|
||||
func encode(_ value: Data) throws {
|
||||
let length = value.count
|
||||
if let uint8 = UInt8(exactly: length) {
|
||||
self.storage.append(0xc4)
|
||||
self.storage.append(uint8)
|
||||
self.storage.append(value)
|
||||
} else if let uint16 = UInt16(exactly: length) {
|
||||
self.storage.append(0xc5)
|
||||
self.storage.append(contentsOf: uint16.bytes)
|
||||
self.storage.append(value)
|
||||
} else if let uint32 = UInt32(exactly: length) {
|
||||
self.storage.append(0xc6)
|
||||
self.storage.append(contentsOf: uint32.bytes)
|
||||
self.storage.append(value)
|
||||
} else {
|
||||
let context = EncodingError.Context(codingPath: self.codingPath, debugDescription: "Cannot encode data of length \(value.count).")
|
||||
throw EncodingError.invalidValue(value, context)
|
||||
}
|
||||
}
|
||||
|
||||
func encode<T>(_ value: T) throws where T : Encodable {
|
||||
try checkCanEncode(value: value)
|
||||
defer { self.canEncodeNewValue = false }
|
||||
|
||||
switch value {
|
||||
case let data as Data:
|
||||
try self.encode(data)
|
||||
case let date as Date:
|
||||
try self.encode(date)
|
||||
default:
|
||||
let encoder = _MessagePackEncoder()
|
||||
try value.encode(to: encoder)
|
||||
self.storage.append(encoder.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension _MessagePackEncoder.SingleValueContainer: _MessagePackEncodingContainer {
|
||||
var data: Data {
|
||||
return storage
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import Foundation
|
||||
|
||||
extension _MessagePackEncoder {
|
||||
final class UnkeyedContainer {
|
||||
private var storage: [_MessagePackEncodingContainer] = []
|
||||
|
||||
var count: Int {
|
||||
return storage.count
|
||||
}
|
||||
|
||||
var codingPath: [CodingKey]
|
||||
|
||||
var nestedCodingPath: [CodingKey] {
|
||||
return self.codingPath + [AnyCodingKey(intValue: self.count)!]
|
||||
}
|
||||
|
||||
var userInfo: [CodingUserInfoKey: Any]
|
||||
|
||||
init(codingPath: [CodingKey], userInfo: [CodingUserInfoKey : Any]) {
|
||||
self.codingPath = codingPath
|
||||
self.userInfo = userInfo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension _MessagePackEncoder.UnkeyedContainer: UnkeyedEncodingContainer {
|
||||
func encodeNil() throws {
|
||||
var container = self.nestedSingleValueContainer()
|
||||
try container.encodeNil()
|
||||
}
|
||||
|
||||
func encode<T>(_ value: T) throws where T : Encodable {
|
||||
var container = self.nestedSingleValueContainer()
|
||||
try container.encode(value)
|
||||
}
|
||||
|
||||
private func nestedSingleValueContainer() -> SingleValueEncodingContainer {
|
||||
let container = _MessagePackEncoder.SingleValueContainer(codingPath: self.nestedCodingPath, userInfo: self.userInfo)
|
||||
self.storage.append(container)
|
||||
|
||||
return container
|
||||
}
|
||||
|
||||
func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer<NestedKey> where NestedKey : CodingKey {
|
||||
let container = _MessagePackEncoder.KeyedContainer<NestedKey>(codingPath: self.nestedCodingPath, userInfo: self.userInfo)
|
||||
self.storage.append(container)
|
||||
|
||||
return KeyedEncodingContainer(container)
|
||||
}
|
||||
|
||||
func nestedUnkeyedContainer() -> UnkeyedEncodingContainer {
|
||||
let container = _MessagePackEncoder.UnkeyedContainer(codingPath: self.nestedCodingPath, userInfo: self.userInfo)
|
||||
self.storage.append(container)
|
||||
|
||||
return container
|
||||
}
|
||||
|
||||
func superEncoder() -> Encoder {
|
||||
fatalError("Unimplemented") // FIXME
|
||||
}
|
||||
}
|
||||
|
||||
extension _MessagePackEncoder.UnkeyedContainer: _MessagePackEncodingContainer {
|
||||
var data: Data {
|
||||
var data = Data()
|
||||
|
||||
let length = storage.count
|
||||
if let uint16 = UInt16(exactly: length) {
|
||||
if uint16 <= 15 {
|
||||
data.append(UInt8(0x90 + uint16))
|
||||
} else {
|
||||
data.append(0xdc)
|
||||
data.append(contentsOf: uint16.bytes)
|
||||
}
|
||||
} else if let uint32 = UInt32(exactly: length) {
|
||||
data.append(0xdd)
|
||||
data.append(contentsOf: uint32.bytes)
|
||||
} else {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
for container in storage {
|
||||
data.append(container.data)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
extension FixedWidthInteger {
|
||||
init(bytes: [UInt8]) {
|
||||
self = bytes.withUnsafeBufferPointer {
|
||||
$0.baseAddress!.withMemoryRebound(to: Self.self, capacity: 1) {
|
||||
$0.pointee
|
||||
}
|
||||
}.bigEndian
|
||||
}
|
||||
|
||||
var bytes: [UInt8] {
|
||||
let capacity = MemoryLayout<Self>.size
|
||||
var mutableValue = self.bigEndian
|
||||
return withUnsafePointer(to: &mutableValue) {
|
||||
return $0.withMemoryRebound(to: UInt8.self, capacity: capacity) {
|
||||
return Array(UnsafeBufferPointer(start: $0, count: capacity))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
8
lib/MessagePack/Tests/LinuxMain.swift
Normal file
8
lib/MessagePack/Tests/LinuxMain.swift
Normal file
@@ -0,0 +1,8 @@
|
||||
import XCTest
|
||||
@testable import MessagePackTests
|
||||
|
||||
XCTMain([
|
||||
testCase(MessagePackDecodingTests.allTests),
|
||||
testCase(MessagePackEncodingTests.allTests),
|
||||
testCase(MessagePackRoundTripTests.allTests),
|
||||
])
|
||||
63
lib/MessagePack/Tests/MessagePackTests/Airport.swift
Normal file
63
lib/MessagePack/Tests/MessagePackTests/Airport.swift
Normal file
@@ -0,0 +1,63 @@
|
||||
struct Airport: Codable, Equatable {
|
||||
let name: String
|
||||
let iata: String
|
||||
let icao: String
|
||||
let coordinates: [Double]
|
||||
|
||||
struct Runway: Codable, Equatable {
|
||||
enum Surface: String, Codable, Equatable {
|
||||
case rigid, flexible, gravel, sealed, unpaved, other
|
||||
}
|
||||
|
||||
let direction: String
|
||||
let distance: Int
|
||||
let surface: Surface
|
||||
}
|
||||
|
||||
let runways: [Runway]
|
||||
|
||||
let instrumentApproachProcedures: [String]
|
||||
|
||||
static var example: Airport {
|
||||
return Airport(
|
||||
name: "Portland International Airport",
|
||||
iata: "PDX",
|
||||
icao: "KPDX",
|
||||
coordinates: [-122.5975,
|
||||
45.5886111111111],
|
||||
runways: [
|
||||
Airport.Runway(
|
||||
direction: "3/21",
|
||||
distance: 1829,
|
||||
surface: .flexible
|
||||
)
|
||||
],
|
||||
instrumentApproachProcedures: [
|
||||
"HI-ILS OR LOC RWY 28",
|
||||
"HI-ILS OR LOC/DME RWY 10",
|
||||
"ILS OR LOC RWY 10L",
|
||||
"ILS OR LOC RWY 10R",
|
||||
"ILS OR LOC RWY 28L",
|
||||
"ILS OR LOC RWY 28R",
|
||||
"ILS RWY 10R (SA CAT I)",
|
||||
"ILS RWY 10R (CAT II - III)",
|
||||
"RNAV (RNP) Y RWY 28L",
|
||||
"RNAV (RNP) Y RWY 28R",
|
||||
"RNAV (RNP) Z RWY 10L",
|
||||
"RNAV (RNP) Z RWY 10R",
|
||||
"RNAV (RNP) Z RWY 28L",
|
||||
"RNAV (RNP) Z RWY 28R",
|
||||
"RNAV (GPS) X RWY 28L",
|
||||
"RNAV (GPS) X RWY 28R",
|
||||
"RNAV (GPS) Y RWY 10L",
|
||||
"RNAV (GPS) Y RWY 10R",
|
||||
"LOC/DME RWY 21",
|
||||
"VOR-A",
|
||||
"HI-TACAN RWY 10",
|
||||
"TACAN RWY 28",
|
||||
"COLUMBIA VISUAL RWY 10L/",
|
||||
"MILL VISUAL RWY 28L/R"
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
import XCTest
|
||||
@testable import MessagePack
|
||||
|
||||
class MessagePackDecodingTests: XCTestCase {
|
||||
var decoder: MessagePackDecoder!
|
||||
|
||||
override func setUp() {
|
||||
self.decoder = MessagePackDecoder()
|
||||
}
|
||||
|
||||
func assertTypeMismatch<T>(_ expression: @autoclosure () throws -> T,
|
||||
_ message: @autoclosure () -> String = "",
|
||||
file: StaticString = #file,
|
||||
line: UInt = #line) -> Any.Type? {
|
||||
var error: Error?
|
||||
XCTAssertThrowsError(expression, message,
|
||||
file: file, line: line) {
|
||||
error = $0
|
||||
}
|
||||
guard case .typeMismatch(let type, _) = error as? DecodingError else {
|
||||
XCTFail(file: file, line: line)
|
||||
return nil
|
||||
}
|
||||
return type
|
||||
}
|
||||
|
||||
func testDecodeNil() {
|
||||
let data = Data(bytes: [0xC0])
|
||||
let value = try! decoder.decode(Int?.self, from: data)
|
||||
XCTAssertNil(value)
|
||||
}
|
||||
|
||||
func testDecodeFalse() {
|
||||
let data = Data(bytes: [0xc2])
|
||||
let value = try! decoder.decode(Bool.self, from: data)
|
||||
XCTAssertEqual(value, false)
|
||||
}
|
||||
|
||||
func testDecodeTrue() {
|
||||
let data = Data(bytes: [0xc3])
|
||||
let value = try! decoder.decode(Bool.self, from: data)
|
||||
XCTAssertEqual(value, true)
|
||||
}
|
||||
|
||||
func testDecodeInt() {
|
||||
let data = Data(bytes: [0x2A])
|
||||
let value = try! decoder.decode(Int.self, from: data)
|
||||
XCTAssertEqual(value, 42)
|
||||
}
|
||||
|
||||
func testDecodeNegativeInt() {
|
||||
let data = Data(bytes: [0xFF])
|
||||
let value = try! decoder.decode(Int.self, from: data)
|
||||
XCTAssertEqual(value, -1)
|
||||
}
|
||||
|
||||
func testDecodeUInt() {
|
||||
let data = Data(bytes: [0xCC, 0x80])
|
||||
let value = try! decoder.decode(Int.self, from: data)
|
||||
XCTAssertEqual(value, 128)
|
||||
}
|
||||
|
||||
func testDecodeFloat() {
|
||||
let data = Data(bytes: [0xCA, 0x40, 0x48, 0xF5, 0xC3])
|
||||
let value = try! decoder.decode(Float.self, from: data)
|
||||
XCTAssertEqual(value, 3.14)
|
||||
}
|
||||
|
||||
func testDecodeFloatToDouble() {
|
||||
let data = Data(bytes: [0xCA, 0x40, 0x48, 0xF5, 0xC3])
|
||||
let type = assertTypeMismatch(try decoder.decode(Double.self, from: data))
|
||||
XCTAssertTrue(type is Double.Type)
|
||||
decoder.nonMatchingFloatDecodingStrategy = .cast
|
||||
let value = try! decoder.decode(Double.self, from: data)
|
||||
XCTAssertEqual(value, 3.14, accuracy: 1e-6)
|
||||
}
|
||||
|
||||
func testDecodeDouble() {
|
||||
let data = Data(bytes: [0xCB, 0x40, 0x09, 0x21, 0xF9, 0xF0, 0x1B, 0x86, 0x6E])
|
||||
let value = try! decoder.decode(Double.self, from: data)
|
||||
XCTAssertEqual(value, 3.14159)
|
||||
}
|
||||
|
||||
func testDecodeDoubleToFloat() {
|
||||
let data = Data(bytes: [0xCB, 0x40, 0x09, 0x21, 0xF9, 0xF0, 0x1B, 0x86, 0x6E])
|
||||
let type = assertTypeMismatch(try decoder.decode(Float.self, from: data))
|
||||
XCTAssertTrue(type is Float.Type)
|
||||
decoder.nonMatchingFloatDecodingStrategy = .cast
|
||||
let value = try! decoder.decode(Float.self, from: data)
|
||||
XCTAssertEqual(value, 3.14159)
|
||||
}
|
||||
|
||||
func testDecodeFixedArray() {
|
||||
let data = Data(bytes: [0x93, 0x01, 0x02, 0x03])
|
||||
let value = try! decoder.decode([Int].self, from: data)
|
||||
XCTAssertEqual(value, [1, 2, 3])
|
||||
}
|
||||
|
||||
func testDecodeVariableArray() {
|
||||
let data = Data(bytes: [0xdc] + [0x00, 0x10] + Array(0x01...0x10))
|
||||
let value = try! decoder.decode([Int].self, from: data)
|
||||
XCTAssertEqual(value, Array(1...16))
|
||||
}
|
||||
|
||||
func testDecodeFixedDictionary() {
|
||||
let data = Data(bytes: [0x83, 0xA1, 0x62, 0x02, 0xA1, 0x61, 0x01, 0xA1, 0x63, 0x03])
|
||||
let value = try! decoder.decode([String: Int].self, from: data)
|
||||
XCTAssertEqual(value, ["a": 1, "b": 2, "c": 3])
|
||||
}
|
||||
|
||||
func testDecodeData() {
|
||||
let data = Data(bytes: [0xC4, 0x05, 0x68, 0x65, 0x6C, 0x6C, 0x6F])
|
||||
let value = try! decoder.decode(Data.self, from: data)
|
||||
XCTAssertEqual(value, "hello".data(using: .utf8))
|
||||
}
|
||||
|
||||
func testDecodeDate() {
|
||||
let data = Data(bytes: [0xD6, 0xFF, 0x00, 0x00, 0x00, 0x01])
|
||||
let date = Date(timeIntervalSince1970: 1)
|
||||
let value = try! decoder.decode(Date.self, from: data)
|
||||
XCTAssertEqual(value, date)
|
||||
}
|
||||
|
||||
func testDecodeDistantPast() {
|
||||
let data = Data(bytes: [0xC7, 0x0C, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xF1, 0x88, 0x6B, 0x66, 0x00])
|
||||
let date = Date.distantPast
|
||||
let value = try! decoder.decode(Date.self, from: data)
|
||||
XCTAssertEqual(value, date)
|
||||
}
|
||||
|
||||
func testDecodeDistantFuture() {
|
||||
let data = Data(bytes: [0xC7, 0x0C, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0xEC, 0x31, 0x88, 0x00])
|
||||
let date = Date.distantFuture
|
||||
let value = try! decoder.decode(Date.self, from: data)
|
||||
XCTAssertEqual(value, date)
|
||||
}
|
||||
|
||||
func testDecodeArrayWithDate() {
|
||||
let data = Data(bytes: [0x91, 0xD6, 0xFF, 0x00, 0x00, 0x00, 0x01])
|
||||
let date = Date(timeIntervalSince1970: 1)
|
||||
let value = try! decoder.decode([Date].self, from: data)
|
||||
XCTAssertEqual(value, [date])
|
||||
}
|
||||
|
||||
func testDecodeDictionaryWithDate() {
|
||||
let data = Data(bytes: [0x81, 0xA1, 0x31, 0xD6, 0xFF, 0x00, 0x00, 0x00, 0x01])
|
||||
let date = Date(timeIntervalSince1970: 1)
|
||||
let value = try! decoder.decode([String: Date].self, from: data)
|
||||
XCTAssertEqual(value, ["1": date])
|
||||
}
|
||||
|
||||
func testDecodeBv() {
|
||||
let b64 = "lK50ZXN0aW5nIHN0cmluZyeSpnF3ZXF3ZagxMjNpY29uc5OU2SRlMTZkYTYwMi0zMjE1LTRiZDYtYjY5MC00Y2Q4NmEwZmU3NjSoQ2lwaGVyIDEBk61jaXBodXNlcm5hbWUxrWFkZmFmZHcyMzQxMzGSkblodHRwczovL3d3dy5nb29nbGUuY29tLmFykbVodHRwczovL3d3dy5hcHBsZS5jb22U2SRhNjExMWU2Ny1hMTMwLTRiM2ItODM5NS0xZjIzMDFjNjk3ZjeoQ2lwaGVyIDIBk6g0MzEzMjEzMatqbGpsbHl1bHVpecCU2SRiOGIwODM3MC0xNGU0LTQzZmUtYjBkOS04ZjJlMDlmODJkYzWoQ2lwaGVyIDMBk6twaW9waW9waXBpb6x6eGN6eHZ6eHZ4enaSkbdodHRwczovL3d3dy52aXNhLmNvbS5hcpG1aHR0cHM6Ly93d3cuZG9ja3MuY29t" // array mode with envData and ciphers
|
||||
|
||||
// let b64 = "hKFirnRlc3Rpbmcgc3RyaW5noWMnp2VudkRhdGGCpGJhc2WmcXdlcXdlpWljb25zqDEyM2ljb25zp2NpcGhlcnOThKJpZNkkMDA4YmE0NDctZjU0Mi00OWVjLWJjYTktMDMzZTQ2OTU0YTBipG5hbWWoQ2lwaGVyIDGkdHlwZQGlbG9naW6DqHVzZXJuYW1lrWNpcGh1c2VybmFtZTGkdG90cK1hZGZhZmR3MjM0MTMxpHVyaXOSgaN1cmm5aHR0cHM6Ly93d3cuZ29vZ2xlLmNvbS5hcoGjdXJptWh0dHBzOi8vd3d3LmFwcGxlLmNvbYSiaWTZJDQ1ZTBhODJiLTgyZGQtNDJiZi05ODhhLTAyYTkyNGM4Yzg5M6RuYW1lqENpcGhlciAypHR5cGUBpWxvZ2lug6h1c2VybmFtZag0MzEzMjEzMaR0b3Rwq2psamxseXVsdWl5pHVyaXPAhKJpZNkkZTBjZWU5NDEtZDI1Ni00MjdiLWJkNWUtNDMxMmMwN2U1NDI5pG5hbWWoQ2lwaGVyIDOkdHlwZQGlbG9naW6DqHVzZXJuYW1lq3Bpb3Bpb3BpcGlvpHRvdHCsenhjenh2enh2eHp2pHVyaXOSgaN1cmm3aHR0cHM6Ly93d3cudmlzYS5jb20uYXKBo3VyabVodHRwczovL3d3dy5kb2Nrcy5jb20=" // dict mode with envData and ciphers
|
||||
|
||||
do {
|
||||
if let d = Data(base64Encoded: b64) {
|
||||
let decoder = MessagePackDecoder()
|
||||
decoder.userInfo[MessagePackDecoder.dataSpecKey] = DataSpecBuilder()
|
||||
.append("b")
|
||||
.append("c")
|
||||
.appendObj("envData", DataSpecBuilder()
|
||||
.append("base")
|
||||
.append("icons")
|
||||
.build())
|
||||
.appendArray("ciphers", DataSpecBuilder()
|
||||
.append("id")
|
||||
.append("name")
|
||||
.append("type")
|
||||
.appendObj("login", DataSpecBuilder()
|
||||
.append("username")
|
||||
.append("totp")
|
||||
.appendArray("uris", DataSpecBuilder()
|
||||
.append("uri")
|
||||
.build())
|
||||
.build())
|
||||
.build())
|
||||
.build()
|
||||
|
||||
let codTest = try decoder.decode(CodableTest.self, from: d)
|
||||
|
||||
XCTAssertEqual(codTest.b, "testing string")
|
||||
XCTAssertEqual(codTest.envData.base, "qweqwe")
|
||||
XCTAssertEqual(codTest.envData.icons, "123icons")
|
||||
XCTAssertTrue(codTest.ciphers!.count > 1)
|
||||
} else {
|
||||
XCTAssertEqual(1, 0)
|
||||
}
|
||||
} catch let error {
|
||||
XCTFail("E: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
static var allTests = [
|
||||
("testDecodeNil", testDecodeNil),
|
||||
("testDecodeFalse", testDecodeFalse),
|
||||
("testDecodeTrue", testDecodeTrue),
|
||||
("testDecodeInt", testDecodeInt),
|
||||
("testDecodeUInt", testDecodeUInt),
|
||||
("testDecodeFloat", testDecodeFloat),
|
||||
("testDecodeFloatToDouble", testDecodeFloatToDouble),
|
||||
("testDecodeDouble", testDecodeDouble),
|
||||
("testDecodeDoubleToFloat", testDecodeDoubleToFloat),
|
||||
("testDecodeFixedArray", testDecodeFixedArray),
|
||||
("testDecodeFixedDictionary", testDecodeFixedDictionary),
|
||||
("testDecodeData", testDecodeData),
|
||||
("testDecodeDistantPast", testDecodeDistantPast),
|
||||
("testDecodeDistantFuture", testDecodeDistantFuture),
|
||||
("testDecodeArrayWithDate", testDecodeArrayWithDate),
|
||||
("testDecodeDictionaryWithDate", testDecodeDictionaryWithDate),
|
||||
("testDecodeBv", testDecodeBv)
|
||||
]
|
||||
}
|
||||
|
||||
struct CodableTest : Codable {
|
||||
enum CodingKeys: Int, CodingKey {
|
||||
case b
|
||||
case c
|
||||
case envData
|
||||
case ciphers
|
||||
}
|
||||
|
||||
var b: String
|
||||
var c: Int
|
||||
var envData: EnvironmentUrlDataDto
|
||||
var ciphers: [Cipher]?
|
||||
|
||||
func printt() {
|
||||
print("B: \(b)")
|
||||
print("C: \(c)")
|
||||
print("ENVDATA")
|
||||
envData.printt()
|
||||
|
||||
if let cs = ciphers {
|
||||
print("CIPHERS")
|
||||
for c in cs {
|
||||
c.printt()
|
||||
print("----------------------------")
|
||||
}
|
||||
}
|
||||
|
||||
print("###########################")
|
||||
}
|
||||
}
|
||||
|
||||
struct EnvironmentUrlDataDto : Codable {
|
||||
var base: String?
|
||||
var icons: String?
|
||||
|
||||
func printt() {
|
||||
print("Base: \(base ?? "")")
|
||||
print("Icons: \(icons ?? "")")
|
||||
}
|
||||
}
|
||||
|
||||
struct Cipher:Identifiable,Codable{
|
||||
enum CodingKeys: Int, CodingKey {
|
||||
case id
|
||||
case name
|
||||
case login
|
||||
}
|
||||
|
||||
var id:String
|
||||
var name:String?
|
||||
var userId:String?
|
||||
var login:Login
|
||||
|
||||
func printt() {
|
||||
print("id: \(id)")
|
||||
print("name: \(name ?? "")")
|
||||
print("LOGIN")
|
||||
login.printt()
|
||||
}
|
||||
}
|
||||
|
||||
struct Login:Codable{
|
||||
var username:String?
|
||||
var totp:String?
|
||||
var uris:[LoginUri]?
|
||||
|
||||
func printt() {
|
||||
print("username: \(username ?? "")")
|
||||
print("totp: \(totp ?? "")")
|
||||
print("URIS")
|
||||
if let us = uris {
|
||||
for u in us {
|
||||
u.printt()
|
||||
print("----------------------------")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct LoginUri:Codable{
|
||||
var uri:String?
|
||||
|
||||
func printt() {
|
||||
print("Uri: \(uri ?? "")")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
import XCTest
|
||||
@testable import MessagePack
|
||||
|
||||
class MessagePackEncodingTests: XCTestCase {
|
||||
var encoder: MessagePackEncoder!
|
||||
|
||||
override func setUp() {
|
||||
self.encoder = MessagePackEncoder()
|
||||
}
|
||||
|
||||
func testEncodeNil() {
|
||||
let value = try! encoder.encode(nil as Int?)
|
||||
XCTAssertEqual(value, Data(bytes: [0xc0]))
|
||||
}
|
||||
|
||||
func testEncodeFalse() {
|
||||
let value = try! encoder.encode(false)
|
||||
XCTAssertEqual(value, Data(bytes: [0xc2]))
|
||||
}
|
||||
|
||||
func testEncodeTrue() {
|
||||
let value = try! encoder.encode(true)
|
||||
XCTAssertEqual(value, Data(bytes: [0xc3]))
|
||||
}
|
||||
|
||||
func testEncodeInt() {
|
||||
let value = try! encoder.encode(42 as Int)
|
||||
XCTAssertEqual(value, Data(bytes: [0x2A]))
|
||||
}
|
||||
|
||||
func testEncodeUInt() {
|
||||
let value = try! encoder.encode(128 as UInt)
|
||||
XCTAssertEqual(value, Data(bytes: [0xCC, 0x80]))
|
||||
}
|
||||
|
||||
func testEncodeFloat() {
|
||||
let value = try! encoder.encode(3.14 as Float)
|
||||
XCTAssertEqual(value, Data(bytes: [0xCA, 0x40, 0x48, 0xF5, 0xC3]))
|
||||
}
|
||||
|
||||
func testEncodeDouble() {
|
||||
let value = try! encoder.encode(3.14159 as Double)
|
||||
XCTAssertEqual(value, Data(bytes: [0xCB, 0x40, 0x09, 0x21, 0xF9, 0xF0, 0x1B, 0x86, 0x6E]))
|
||||
}
|
||||
|
||||
func testEncodeString() {
|
||||
let value = try! encoder.encode("hello")
|
||||
XCTAssertEqual(value, Data(bytes: [0xA5, 0x68, 0x65, 0x6C, 0x6C, 0x6F]))
|
||||
}
|
||||
|
||||
func testEncodeFixedArray() {
|
||||
let value = try! encoder.encode([1, 2, 3])
|
||||
XCTAssertEqual(value, Data(bytes: [0x93, 0x01, 0x02, 0x03]))
|
||||
}
|
||||
|
||||
func testEncodeVariableArray() {
|
||||
let value = try! encoder.encode(Array(1...16))
|
||||
XCTAssertEqual(value, Data(bytes: [0xdc] + [0x00, 0x10] + Array(0x01...0x10)))
|
||||
}
|
||||
|
||||
func testEncodeFixedDictionary() {
|
||||
let value = try! encoder.encode(["a": 1])
|
||||
XCTAssertEqual(value, Data(bytes: [0x81, 0xA1, 0x61, 0x01]))
|
||||
}
|
||||
|
||||
func testEncodeVariableDictionary() {
|
||||
let letters = "abcdefghijklmnopqrstuvwxyz".unicodeScalars
|
||||
let dictionary = Dictionary(uniqueKeysWithValues: zip(letters.map { String($0) }, 1...26))
|
||||
let value = try! encoder.encode(dictionary)
|
||||
XCTAssertEqual(value.count, 81)
|
||||
XCTAssert(value.starts(with: [0xde] + [0x00, 0x1A]))
|
||||
}
|
||||
|
||||
func testEncodeData() {
|
||||
let data = "hello".data(using: .utf8)
|
||||
let value = try! encoder.encode(data)
|
||||
XCTAssertEqual(value, Data(bytes: [0xC4, 0x05, 0x68, 0x65, 0x6C, 0x6C, 0x6F]))
|
||||
}
|
||||
|
||||
func testEncodeDate() {
|
||||
let date = Date(timeIntervalSince1970: 1)
|
||||
let value = try! encoder.encode(date)
|
||||
XCTAssertEqual(value, Data(bytes: [0xD6, 0xFF, 0x00, 0x00, 0x00, 0x01]))
|
||||
}
|
||||
|
||||
func testEncodeDistantPast() {
|
||||
let date = Date.distantPast
|
||||
let value = try! encoder.encode(date)
|
||||
XCTAssertEqual(value, Data(bytes: [0xC7, 0x0C, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xF1, 0x88, 0x6B, 0x66, 0x00]))
|
||||
}
|
||||
|
||||
func testEncodeDistantFuture() {
|
||||
let date = Date.distantFuture
|
||||
let value = try! encoder.encode(date)
|
||||
XCTAssertEqual(value, Data(bytes: [0xC7, 0x0C, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0xEC, 0x31, 0x88, 0x00]))
|
||||
}
|
||||
|
||||
func testEncodeArrayWithDate() {
|
||||
let date = Date(timeIntervalSince1970: 1)
|
||||
let value = try! encoder.encode([date])
|
||||
XCTAssertEqual(value, Data(bytes: [0x91, 0xD6, 0xFF, 0x00, 0x00, 0x00, 0x01]))
|
||||
}
|
||||
|
||||
func testEncodeDictionaryWithDate() {
|
||||
let date = Date(timeIntervalSince1970: 1)
|
||||
let value = try! encoder.encode(["1": date])
|
||||
XCTAssertEqual(value, Data(bytes: [0x81, 0xA1, 0x31, 0xD6, 0xFF, 0x00, 0x00, 0x00, 0x01]))
|
||||
}
|
||||
|
||||
static var allTests = [
|
||||
("testEncodeFalse", testEncodeFalse),
|
||||
("testEncodeTrue", testEncodeTrue),
|
||||
("testEncodeInt", testEncodeInt),
|
||||
("testEncodeUInt", testEncodeUInt),
|
||||
("testEncodeFloat", testEncodeFloat),
|
||||
("testEncodeDouble", testEncodeDouble),
|
||||
("testEncodeFixedArray", testEncodeFixedArray),
|
||||
("testEncodeVariableArray", testEncodeVariableArray),
|
||||
("testEncodeFixedDictionary", testEncodeFixedDictionary),
|
||||
("testEncodeVariableDictionary", testEncodeVariableDictionary),
|
||||
("testEncodeDate", testEncodeDate),
|
||||
("testEncodeDistantPast", testEncodeDistantPast),
|
||||
("testEncodeDistantFuture", testEncodeDistantFuture),
|
||||
("testEncodeArrayWithDate", testEncodeArrayWithDate),
|
||||
("testEncodeDictionaryWithDate", testEncodeDictionaryWithDate)
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import XCTest
|
||||
@testable import MessagePack
|
||||
|
||||
class MessagePackPerformanceTests: XCTestCase {
|
||||
var encoder: MessagePackEncoder!
|
||||
var decoder: MessagePackDecoder!
|
||||
|
||||
override func setUp() {
|
||||
self.encoder = MessagePackEncoder()
|
||||
self.decoder = MessagePackDecoder()
|
||||
}
|
||||
|
||||
func testPerformance() {
|
||||
let count = 100
|
||||
let values = [Airport](repeating: .example, count: count)
|
||||
|
||||
self.measure {
|
||||
let encoded = try! encoder.encode(values)
|
||||
let decoded = try! decoder.decode([Airport].self, from: encoded)
|
||||
XCTAssertEqual(decoded.count, count)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import XCTest
|
||||
@testable import MessagePack
|
||||
|
||||
class MessagePackRoundTripTests: XCTestCase {
|
||||
var encoder: MessagePackEncoder!
|
||||
var decoder: MessagePackDecoder!
|
||||
|
||||
override func setUp() {
|
||||
self.encoder = MessagePackEncoder()
|
||||
self.decoder = MessagePackDecoder()
|
||||
}
|
||||
|
||||
func testRoundTripAirport() {
|
||||
let value = Airport.example
|
||||
let encoded = try! encoder.encode(value)
|
||||
let decoded = try! decoder.decode(Airport.self, from: encoded)
|
||||
|
||||
XCTAssertEqual(value.name, decoded.name)
|
||||
XCTAssertEqual(value.iata, decoded.iata)
|
||||
XCTAssertEqual(value.icao, decoded.icao)
|
||||
XCTAssertEqual(value.coordinates[0], decoded.coordinates[0], accuracy: 0.01)
|
||||
XCTAssertEqual(value.coordinates[1], decoded.coordinates[1], accuracy: 0.01)
|
||||
XCTAssertEqual(value.runways[0].direction, decoded.runways[0].direction)
|
||||
XCTAssertEqual(value.runways[0].distance, decoded.runways[0].distance)
|
||||
XCTAssertEqual(value.runways[0].surface, decoded.runways[0].surface)
|
||||
}
|
||||
|
||||
func testRoundTripParachutePack() {
|
||||
struct Parachute: Codable, Equatable {
|
||||
enum Canopy: String, Codable, Equatable {
|
||||
case round, cruciform, rogalloWing, annular, ramAir
|
||||
}
|
||||
|
||||
let canpoy: Canopy
|
||||
let surfaceArea: Double
|
||||
}
|
||||
|
||||
struct ParachutePack: Codable, Equatable {
|
||||
let main: Parachute?
|
||||
let reserve: Parachute?
|
||||
}
|
||||
|
||||
let value = ParachutePack(main: Parachute(canpoy: .ramAir, surfaceArea: 200), reserve: nil)
|
||||
let encoded = try! encoder.encode(value)
|
||||
let decoded = try! decoder.decode(ParachutePack.self, from: encoded)
|
||||
|
||||
XCTAssertEqual(value, decoded)
|
||||
}
|
||||
|
||||
func testRoundTripArray() {
|
||||
let count: UInt8 = 100
|
||||
var bytes: [UInt8] = [0xdc, 0x00, count]
|
||||
var encoded: [Int] = []
|
||||
for n in 1...count {
|
||||
bytes.append(n)
|
||||
encoded.append(Int(n))
|
||||
}
|
||||
|
||||
let data = Data(bytes: bytes)
|
||||
let decoded = try! decoder.decode([Int].self, from: data)
|
||||
XCTAssertEqual(encoded, decoded)
|
||||
}
|
||||
|
||||
func testRoundTripDictionary() {
|
||||
let (a, z): (UInt8, UInt8) = (0x61, 0x7a)
|
||||
var bytes: [UInt8] = [0xde, 0x00, 0x1A]
|
||||
var encoded: [String: Int] = [:]
|
||||
for n in a...z {
|
||||
bytes.append(contentsOf: [0xA1, n, n])
|
||||
encoded[String(Unicode.Scalar(n))] = Int(n)
|
||||
}
|
||||
|
||||
let data = Data(bytes: bytes)
|
||||
let decoded = try! decoder.decode([String: Int].self, from: data)
|
||||
XCTAssertEqual(encoded, decoded)
|
||||
}
|
||||
|
||||
func testRoundTripDate() {
|
||||
var bytes: [UInt8] = [0xD6, 0xFF]
|
||||
|
||||
let dateComponents = DateComponents(year: 2018, month: 4, day: 20)
|
||||
let encoded = Calendar.current.date(from: dateComponents)!
|
||||
|
||||
let secondsSince1970 = UInt32(encoded.timeIntervalSince1970)
|
||||
bytes.append(contentsOf: secondsSince1970.bytes)
|
||||
|
||||
let data = Data(bytes: bytes)
|
||||
let decoded = try! decoder.decode(Date.self, from: data)
|
||||
XCTAssertEqual(encoded, decoded)
|
||||
}
|
||||
|
||||
func testRoundTripDateWithNanoseconds() {
|
||||
let encoded = Date()
|
||||
let data = try! self.encoder.encode(encoded)
|
||||
let decoded = try! self.decoder.decode(Date.self, from: data)
|
||||
XCTAssertEqual(encoded.timeIntervalSinceReferenceDate, decoded.timeIntervalSinceReferenceDate, accuracy: 0.0001)
|
||||
}
|
||||
|
||||
static var allTests = [
|
||||
("testRoundTripAirport", testRoundTripAirport),
|
||||
("testRoundTripArray", testRoundTripArray),
|
||||
("testRoundTripDictionary", testRoundTripDictionary),
|
||||
("testRoundTripDate", testRoundTripDate),
|
||||
("testRoundTripDateWithNanoseconds", testRoundTripDateWithNanoseconds)
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user