mirror of
https://github.com/bitwarden/mobile
synced 2025-12-11 05:43:30 +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",
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
"extends": [
|
"extends": [
|
||||||
"config:base",
|
"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
|
runs-on: macos-14
|
||||||
needs: setup
|
needs: setup
|
||||||
env:
|
env:
|
||||||
ios_folder_path: src/App/Platforms/iOS
|
_IOS_FOLDER_PATH: src/App/Platforms/iOS
|
||||||
app_output_name: App
|
_APP_OUTPUT_NAME: App
|
||||||
app_ci_output_filename: App_x64_Debug
|
_APP_CI_OUTPUT_FILENAME: App_x64_Debug
|
||||||
steps:
|
steps:
|
||||||
- name: Set XCode version
|
- name: Set XCode version
|
||||||
uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1.6.0
|
uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1.6.0
|
||||||
@@ -135,7 +135,7 @@ jobs:
|
|||||||
|
|
||||||
echo "### CFBundleVersion $BUILD_NUMBER" >> $GITHUB_STEP_SUMMARY
|
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.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.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
|
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
|
- name: Update Entitlements
|
||||||
run: |
|
run: |
|
||||||
echo "##### Updating Entitlements"
|
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
|
- name: Get certificates
|
||||||
run: |
|
run: |
|
||||||
@@ -245,8 +245,8 @@ jobs:
|
|||||||
ARCHIVE_PATH: ./${{ env.main_app_folder_path }}/bin/Debug/${{ env.target-net-version }}-ios/iossimulator-x64
|
ARCHIVE_PATH: ./${{ env.main_app_folder_path }}/bin/Debug/${{ env.target-net-version }}-ios/iossimulator-x64
|
||||||
EXPORT_PATH: ./bitwarden-export
|
EXPORT_PATH: ./bitwarden-export
|
||||||
run: |
|
run: |
|
||||||
zip -r -q ${{ env.app_ci_output_filename }}.app.zip $ARCHIVE_PATH
|
zip -r -q ${{ env._APP_CI_OUTPUT_FILENAME }}.app.zip $ARCHIVE_PATH
|
||||||
mv ${{ env.app_ci_output_filename }}.app.zip $EXPORT_PATH
|
mv ${{ env._APP_CI_OUTPUT_FILENAME }}.app.zip $EXPORT_PATH
|
||||||
|
|
||||||
- name: Show Bitwarden Export
|
- name: Show Bitwarden Export
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -276,8 +276,8 @@ jobs:
|
|||||||
- name: Upload .app file for Automation CI
|
- name: Upload .app file for Automation CI
|
||||||
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
|
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
|
||||||
with:
|
with:
|
||||||
name: ${{ 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
|
path: ./bitwarden-export/${{ env._APP_CI_OUTPUT_FILENAME }}.app.zip
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Install AppCenter CLI
|
- name: Install AppCenter CLI
|
||||||
|
|||||||
54
.github/workflows/build.yml
vendored
54
.github/workflows/build.yml
vendored
@@ -1,12 +1,6 @@
|
|||||||
name: Build
|
name: Build
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
branches-ignore:
|
|
||||||
- "l10n_master"
|
|
||||||
- "gh-pages"
|
|
||||||
paths-ignore:
|
|
||||||
- ".github/workflows/**"
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
@@ -70,8 +64,8 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
variant: ["prod", "qa"]
|
variant: ["prod", "qa"]
|
||||||
env:
|
env:
|
||||||
android_folder_path: src\App\Platforms\Android
|
_ANDROID_FOLDER_PATH: src\App\Platforms\Android
|
||||||
android_folder_path_bash: src/App/Platforms/Android
|
_ANDROID_FOLDER_PATH_BASH: src/App/Platforms/Android
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
@@ -126,9 +120,9 @@ jobs:
|
|||||||
mkdir -p $HOME/secrets
|
mkdir -p $HOME/secrets
|
||||||
|
|
||||||
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \
|
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 \
|
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 \
|
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \
|
||||||
--name play_creds.json --file $HOME/secrets/play_creds.json --output none
|
--name play_creds.json --file $HOME/secrets/play_creds.json --output none
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -140,7 +134,7 @@ jobs:
|
|||||||
CONTAINER_NAME: mobile
|
CONTAINER_NAME: mobile
|
||||||
run: |
|
run: |
|
||||||
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \
|
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
|
shell: bash
|
||||||
|
|
||||||
- name: Increment version
|
- name: Increment version
|
||||||
@@ -149,7 +143,7 @@ jobs:
|
|||||||
echo "##### Setting Android Version Code to $BUILD_NUMBER" | tee -a $GITHUB_STEP_SUMMARY
|
echo "##### Setting Android Version Code to $BUILD_NUMBER" | tee -a $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
sed -i "s/android:versionCode=\"1\"/android:versionCode=\"$BUILD_NUMBER\"/" \
|
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
|
shell: bash
|
||||||
|
|
||||||
- name: Restore packages
|
- name: Restore packages
|
||||||
@@ -193,7 +187,7 @@ jobs:
|
|||||||
}
|
}
|
||||||
Write-Output "##### Sign Google Play Bundle Release Configuration"
|
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 `
|
dotnet publish $projToBuild -c Release -f ${{ env.target-net-version }}-android `
|
||||||
/p:AndroidPackageFormats=aab `
|
/p:AndroidPackageFormats=aab `
|
||||||
/p:AndroidKeyStore=true `
|
/p:AndroidKeyStore=true `
|
||||||
@@ -210,7 +204,7 @@ jobs:
|
|||||||
|
|
||||||
Write-Output "##### Sign APK Release Configuration"
|
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 `
|
dotnet publish $projToBuild -c Release -f ${{ env.target-net-version }}-android `
|
||||||
/p:AndroidKeyStore=true `
|
/p:AndroidKeyStore=true `
|
||||||
/p:AndroidSigningKeyStore=$signingPlayKeyStore `
|
/p:AndroidSigningKeyStore=$signingPlayKeyStore `
|
||||||
@@ -295,9 +289,9 @@ jobs:
|
|||||||
name: F-Droid Build
|
name: F-Droid Build
|
||||||
runs-on: windows-2022
|
runs-on: windows-2022
|
||||||
env:
|
env:
|
||||||
android_folder_path: src\App\Platforms\Android
|
_ANDROID_FOLDER_PATH: src\App\Platforms\Android
|
||||||
android_folder_path_bash: src/App/Platforms/Android
|
_ANDROID_FOLDER_PATH_BASH: src/App/Platforms/Android
|
||||||
android_manifest_path: src/App/Platforms/Android/AndroidManifest.xml
|
_ANDROID_MANIFEST_PATH: src/App/Platforms/Android/AndroidManifest.xml
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
@@ -350,7 +344,7 @@ jobs:
|
|||||||
FILE: app_fdroid-keystore.jks
|
FILE: app_fdroid-keystore.jks
|
||||||
run: |
|
run: |
|
||||||
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME --name $FILE \
|
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
|
shell: bash
|
||||||
|
|
||||||
- name: Increment version
|
- name: Increment version
|
||||||
@@ -359,14 +353,14 @@ jobs:
|
|||||||
echo "##### Setting F-Droid Version Code to $BUILD_NUMBER" | tee -a $GITHUB_STEP_SUMMARY
|
echo "##### Setting F-Droid Version Code to $BUILD_NUMBER" | tee -a $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
sed -i "s/android:versionCode=\"1\"/android:versionCode=\"$BUILD_NUMBER\"/" \
|
sed -i "s/android:versionCode=\"1\"/android:versionCode=\"$BUILD_NUMBER\"/" \
|
||||||
./${{ env.android_manifest_path }}
|
./${{ env._ANDROID_MANIFEST_PATH }}
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Clean for F-Droid
|
- name: Clean for F-Droid
|
||||||
run: |
|
run: |
|
||||||
$directoryBuildProps = $($env:GITHUB_WORKSPACE + "/Directory.Build.props");
|
$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"
|
Write-Output "##### Back up project files"
|
||||||
|
|
||||||
@@ -399,7 +393,7 @@ jobs:
|
|||||||
|
|
||||||
Write-Output "##### Sign FDroid"
|
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 `
|
dotnet build $projToBuild -c Release -f ${{ env.target-net-version }}-android `
|
||||||
/p:AndroidKeyStore=true `
|
/p:AndroidKeyStore=true `
|
||||||
/p:AndroidSigningKeyStore=$signingFdroidKeyStore `
|
/p:AndroidSigningKeyStore=$signingFdroidKeyStore `
|
||||||
@@ -439,9 +433,9 @@ jobs:
|
|||||||
runs-on: macos-14
|
runs-on: macos-14
|
||||||
needs: setup
|
needs: setup
|
||||||
env:
|
env:
|
||||||
ios_folder_path: src/App/Platforms/iOS
|
_IOS_FOLDER_PATH: src/App/Platforms/iOS
|
||||||
app_output_name: App
|
_APP_OUTPUT_NAME: App
|
||||||
app_ci_output_filename: App_x64_Debug
|
_APP_CI_OUTPUT_FILENAME: App_x64_Debug
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
@@ -521,7 +515,7 @@ jobs:
|
|||||||
BUILD_NUMBER=$((8000 + $GITHUB_RUN_NUMBER))
|
BUILD_NUMBER=$((8000 + $GITHUB_RUN_NUMBER))
|
||||||
echo "##### Setting iOS CFBundleVersion to $BUILD_NUMBER" | tee -a $GITHUB_STEP_SUMMARY
|
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.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.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
|
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
|
- name: Update Entitlements
|
||||||
run: |
|
run: |
|
||||||
echo "##### Updating Entitlements"
|
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
|
- name: Get certificates
|
||||||
run: |
|
run: |
|
||||||
@@ -615,8 +609,8 @@ jobs:
|
|||||||
ARCHIVE_PATH: ./${{ env.main_app_folder_path }}/bin/Debug/${{ env.target-net-version }}-ios/iossimulator-x64
|
ARCHIVE_PATH: ./${{ env.main_app_folder_path }}/bin/Debug/${{ env.target-net-version }}-ios/iossimulator-x64
|
||||||
EXPORT_PATH: ./bitwarden-export
|
EXPORT_PATH: ./bitwarden-export
|
||||||
run: |
|
run: |
|
||||||
zip -r -q ${{ env.app_ci_output_filename }}.app.zip $ARCHIVE_PATH
|
zip -r -q ${{ env._APP_CI_OUTPUT_FILENAME }}.app.zip $ARCHIVE_PATH
|
||||||
mv ${{ env.app_ci_output_filename }}.app.zip $EXPORT_PATH
|
mv ${{ env._APP_CI_OUTPUT_FILENAME }}.app.zip $EXPORT_PATH
|
||||||
|
|
||||||
- name: Copy all dSYMs files to upload
|
- name: Copy all dSYMs files to upload
|
||||||
env:
|
env:
|
||||||
@@ -641,8 +635,8 @@ jobs:
|
|||||||
- name: Upload .app file for Automation CI
|
- name: Upload .app file for Automation CI
|
||||||
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
|
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
|
||||||
with:
|
with:
|
||||||
name: ${{ 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
|
path: ./bitwarden-export/${{ env._APP_CI_OUTPUT_FILENAME }}.app.zip
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Install AppCenter CLI
|
- 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:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs: {}
|
inputs: {}
|
||||||
schedule:
|
|
||||||
- cron: '0 0 * * 5'
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
crowdin-sync:
|
crowdin-sync:
|
||||||
name: Autosync
|
name: Autosync
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-24.04
|
||||||
env:
|
env:
|
||||||
_CROWDIN_PROJECT_ID: "269690"
|
_CROWDIN_PROJECT_ID: "269690"
|
||||||
steps:
|
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
|
- 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
|
- name: Login to Azure - CI Subscription
|
||||||
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
||||||
@@ -31,7 +38,7 @@ jobs:
|
|||||||
- name: Download translations
|
- name: Download translations
|
||||||
uses: crowdin/github-action@61ac8b980551f674046220c3e104bddae2916ac5 # v2.0.0
|
uses: crowdin/github-action@61ac8b980551f674046220c3e104bddae2916ac5 # v2.0.0
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||||
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
|
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
|
||||||
with:
|
with:
|
||||||
config: crowdin.yml
|
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
|
pull-requests: write
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5.0.0
|
- name: Label PR
|
||||||
|
uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5.0.0
|
||||||
with:
|
with:
|
||||||
sync-labels: true
|
sync-labels: true
|
||||||
|
|||||||
16
.gitignore
vendored
16
.gitignore
vendored
@@ -296,17 +296,11 @@ iOSInjectionProject/
|
|||||||
timeline.xctimeline
|
timeline.xctimeline
|
||||||
playground.xcworkspace
|
playground.xcworkspace
|
||||||
|
|
||||||
# Swift Package Manager
|
# xcode / swift package manager - used by the MessagePack lib
|
||||||
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
|
/.build
|
||||||
# Packages/
|
/Packages
|
||||||
# Package.pins
|
/*.xcodeproj
|
||||||
# Package.resolved
|
.swiftpm
|
||||||
# *.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/
|
|
||||||
|
|
||||||
# CocoaPods
|
# CocoaPods
|
||||||
# We recommend against adding the Pods directory to your .gitignore. However
|
# 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
|
# 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!
|
# 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