├── .github
└── workflows
│ ├── android_integration.yaml
│ ├── merged.yaml
│ ├── released.yaml
│ └── review.yaml
├── .gitignore
├── .kotlin
└── metadata
│ └── kotlinTransformedMetadataLibraries
│ ├── org.jetbrains.kotlin-kotlin-stdlib-2.0.0-commonMain-2bbUHA.klib
│ ├── org.jetbrains.kotlin-kotlin-test-2.0.0-annotationsCommonMain-24eTFQ.klib
│ ├── org.jetbrains.kotlin-kotlin-test-2.0.0-assertionsCommonMain-24eTFQ.klib
│ ├── org.jetbrains.kotlinx-atomicfu-0.23.1-commonMain-wFq7cg.klib
│ ├── org.jetbrains.kotlinx-atomicfu-0.23.1-nativeMain-wFq7cg.klib
│ ├── org.jetbrains.kotlinx-kotlinx-coroutines-core-1.8.1-commonMain-XanZ2w.klib
│ ├── org.jetbrains.kotlinx-kotlinx-coroutines-core-1.8.1-concurrentMain-XanZ2w.klib
│ ├── org.jetbrains.kotlinx-kotlinx-coroutines-core-1.8.1-nativeDarwinMain-sy5nKg.klib
│ ├── org.jetbrains.kotlinx-kotlinx-coroutines-core-1.8.1-nativeMain-XanZ2w.klib
│ └── org.jetbrains.kotlinx-kotlinx-coroutines-core-1.8.1-nativeOtherMain-XanZ2w.klib
├── LICENSE.md
├── Readme.md
├── build.gradle.kts
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── karma.config.d
└── karma.conf.js
├── kotlin-js-store
└── yarn.lock
├── settings.gradle.kts
├── src
├── androidInstrumentedTest
│ ├── AndroidManifest.xml
│ ├── aidl
│ │ └── com
│ │ │ └── ditchoom
│ │ │ └── buffer
│ │ │ └── BufferIpcTestAidl.aidl
│ └── kotlin
│ │ └── com
│ │ └── ditchoom
│ │ └── buffer
│ │ ├── IPCSimpleService.kt
│ │ ├── IPCTest.kt
│ │ └── ParcelableTests.kt
├── androidMain
│ ├── AndroidManifest.xml
│ ├── aidl
│ │ └── com
│ │ │ └── ditchoom
│ │ │ └── buffer
│ │ │ └── JvmBuffer.aidl
│ └── kotlin
│ │ └── com
│ │ └── ditchoom
│ │ └── buffer
│ │ ├── BaseJvmBuffer.kt
│ │ ├── BufferFactory.kt
│ │ ├── CharsetEncoderHelper.kt
│ │ ├── JvmBuffer.kt
│ │ ├── Parcelable.kt
│ │ └── ParcelableSharedMemoryBuffer.kt
├── appleMain
│ └── kotlin
│ │ └── com
│ │ └── ditchoom
│ │ └── buffer
│ │ ├── BufferFactory.kt
│ │ ├── CharsetEncoderHelper.kt
│ │ ├── DataBuffer.kt
│ │ └── MutableDataBuffer.kt
├── commonMain
│ └── kotlin
│ │ └── com
│ │ └── ditchoom
│ │ └── buffer
│ │ ├── AllocationZone.kt
│ │ ├── BufferFactory.kt
│ │ ├── ByteOrder.kt
│ │ ├── Charset.kt
│ │ ├── FragmentedReadBuffer.kt
│ │ ├── Parcelable.kt
│ │ ├── PlatformBuffer.kt
│ │ ├── PositionBuffer.kt
│ │ ├── ReadBuffer.kt
│ │ ├── SuspendCloseable.kt
│ │ ├── TransformedReadBuffer.kt
│ │ └── WriteBuffer.kt
├── commonTest
│ └── kotlin
│ │ └── com
│ │ └── ditchoom
│ │ └── buffer
│ │ ├── BufferTests.kt
│ │ ├── FragmentedReadBufferTests.kt
│ │ └── TransformedReadBufferTest.kt
├── jsMain
│ └── kotlin
│ │ └── com
│ │ └── ditchoom
│ │ └── buffer
│ │ ├── BufferFactory.kt
│ │ ├── JsBuffer.kt
│ │ └── Parcelable.kt
├── jvmMain
│ └── kotlin
│ │ └── com
│ │ └── ditchoom
│ │ └── buffer
│ │ ├── BaseJvmBuffer.kt
│ │ ├── BufferFactory.kt
│ │ ├── CharsetEncoderHelper.kt
│ │ ├── JvmBuffer.kt
│ │ └── Parcelable.kt
├── linuxMain
│ └── kotlin
│ │ └── com
│ │ └── ditchoom
│ │ └── buffer
│ │ └── BufferFactory.kt
└── nativeMain
│ └── kotlin
│ └── buffer
│ ├── NativeBuffer.kt
│ └── Parcelable.kt
└── webpack.config.d
└── config.js
/.github/workflows/android_integration.yaml:
--------------------------------------------------------------------------------
1 | name: "Android Emulator Integration Test"
2 | on:
3 | pull_request:
4 | paths-ignore:
5 | - '*.md'
6 | types:
7 | - synchronize
8 | - opened
9 | jobs:
10 | test:
11 | runs-on: ubuntu-latest
12 | strategy:
13 | matrix:
14 | api-level: [19, 29]
15 | steps:
16 | - name: checkout
17 | uses: actions/checkout@v4
18 | - name: Set up JDK 19
19 | uses: actions/setup-java@v4
20 | with:
21 | distribution: 'zulu'
22 | java-version: '19'
23 | cache: gradle
24 | - name: Enable KVM
25 | run: |
26 | echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
27 | sudo udevadm control --reload-rules
28 | sudo udevadm trigger --name-match=kvm
29 |
30 | - name: Gradle cache
31 | uses: gradle/actions/setup-gradle@v3
32 |
33 | - name: AVD cache
34 | uses: actions/cache@v4
35 | id: avd-cache
36 | with:
37 | path: |
38 | ~/.android/avd/*
39 | ~/.android/adb*
40 | key: avd-${{ matrix.api-level }}
41 |
42 | - name: create AVD and generate snapshot for caching
43 | if: steps.avd-cache.outputs.cache-hit != 'true'
44 | uses: reactivecircus/android-emulator-runner@v2
45 | with:
46 | api-level: ${{ matrix.api-level }}
47 | force-avd-creation: false
48 | emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
49 | disable-animations: false
50 | script: echo "Generated AVD snapshot for caching."
51 |
52 | - name: run tests
53 | uses: reactivecircus/android-emulator-runner@v2
54 | with:
55 | api-level: ${{ matrix.api-level }}
56 | force-avd-creation: false
57 | emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
58 | disable-animations: true
59 | script: ./gradlew connectedCheck
--------------------------------------------------------------------------------
/.github/workflows/merged.yaml:
--------------------------------------------------------------------------------
1 | name: "Build Test and Deploy"
2 | on:
3 | pull_request_target:
4 | paths-ignore:
5 | - '*.md'
6 | branches: [ main ]
7 | types:
8 | - closed
9 | env:
10 | username: ${{ secrets.SONATYPE_NEXUS_USERNAME }}
11 | password: ${{ secrets.SONATYPE_NEXUS_PASSWORD }}
12 | jobs:
13 | create-staging-repository:
14 | if: github.event.pull_request.merged == true
15 | name: Create staging repository
16 | runs-on: ubuntu-latest
17 | outputs:
18 | repository_id: ${{ steps.create.outputs.repository_id }}
19 | steps:
20 | - id: create
21 | run: |
22 | jsonOutput=$(curl -s --request POST -u "$username:$password" \
23 | --url https://oss.sonatype.org/service/local/staging/profiles/3abaab4608e7e/start \
24 | --header 'Accept: application/json' \
25 | --header 'Content-Type: application/json' \
26 | --data '{ "data": {"description" : "Buffer"} }')
27 | stagingRepositoryId=$(echo "$jsonOutput" | jq -r '.data.stagedRepositoryId')
28 | if [ -z "$stagingRepositoryId" ]; then
29 | echo "Error while creating the staging repository."
30 | exit 1
31 | else
32 | echo "repository_id=$stagingRepositoryId" >> $GITHUB_OUTPUT
33 | fi
34 | macos:
35 | name: MacOS Build & Upload artifacts to sonatype
36 | if: github.event.pull_request.merged == true
37 | needs: create-staging-repository
38 | runs-on: macOS-latest
39 | env:
40 | SONATYPE_REPOSITORY_ID: ${{ needs.create-staging-repository.outputs.repository_id }}
41 | SONATYPE_USERNAME: ${{ secrets.SONATYPE_NEXUS_USERNAME }}
42 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_NEXUS_PASSWORD }}
43 | GPG_SECRET: ${{ secrets.GPG_SECRET }}
44 | GPG_SIGNING_PASSWORD: ${{ secrets.GPG_SIGNING_PASSWORD }}
45 | NPM_ACCESS_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
46 | steps:
47 | - uses: actions/checkout@v4
48 | - name: Set up JDK 19
49 | uses: actions/setup-java@v4
50 | with:
51 | distribution: 'zulu'
52 | java-version: '19'
53 | cache: gradle
54 | - name: Gradle cache
55 | uses: gradle/actions/setup-gradle@v3
56 | - name: Setup Chrome
57 | uses: browser-actions/setup-chrome@v1
58 | - name: Setup Android SDK
59 | uses: android-actions/setup-android@v2
60 | - name: Import GPG Key
61 | id: import_gpg
62 | uses: crazy-max/ghaction-import-gpg@v5
63 | with:
64 | gpg_private_key: ${{ secrets.GPG_SECRET }}
65 | passphrase: ${{ secrets.GPG_SIGNING_PASSWORD }}
66 | - name: Test and deploy with Gradle Major
67 | if: ${{ contains(github.event.pull_request.labels.*.name, 'major') }}
68 | env:
69 | SONATYPE_NEXUS_USERNAME: ${{ secrets.SONATYPE_NEXUS_USERNAME }}
70 | SONATYPE_NEXUS_PASSWORD: ${{ secrets.SONATYPE_NEXUS_PASSWORD }}
71 | GPG_SECRET: ${{ secrets.GPG_SECRET }}
72 | GPG_SIGNING_PASSWORD: ${{ secrets.GPG_SIGNING_PASSWORD }}
73 | NPM_ACCESS_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
74 | run: ./gradlew publishAllPublicationsToSonatypeRepository -PincrementMajor=true
75 | - name: Test and deploy with Gradle Minor
76 | if: ${{ contains(github.event.pull_request.labels.*.name, 'minor') }}
77 | env:
78 | SONATYPE_NEXUS_USERNAME: ${{ secrets.SONATYPE_NEXUS_USERNAME }}
79 | SONATYPE_NEXUS_PASSWORD: ${{ secrets.SONATYPE_NEXUS_PASSWORD }}
80 | GPG_SECRET: ${{ secrets.GPG_SECRET }}
81 | GPG_SIGNING_PASSWORD: ${{ secrets.GPG_SIGNING_PASSWORD }}
82 | NPM_ACCESS_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
83 | run: ./gradlew publishAllPublicationsToSonatypeRepository -PincrementMinor=true
84 | - name: Test and deploy with Gradle Patch
85 | if: ${{ !contains(github.event.pull_request.labels.*.name, 'major') && !contains(github.event.pull_request.labels.*.name, 'minor') }}
86 | env:
87 | SONATYPE_NEXUS_USERNAME: ${{ secrets.SONATYPE_NEXUS_USERNAME }}
88 | SONATYPE_NEXUS_PASSWORD: ${{ secrets.SONATYPE_NEXUS_PASSWORD }}
89 | GPG_SECRET: ${{ secrets.GPG_SECRET }}
90 | GPG_SIGNING_PASSWORD: ${{ secrets.GPG_SIGNING_PASSWORD }}
91 | NPM_ACCESS_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
92 | run: ./gradlew publishAllPublicationsToSonatypeRepository
93 | - uses: actions/upload-artifact@v3
94 | with:
95 | name: Package
96 | path: build/libs
97 | finalize:
98 | runs-on: ubuntu-latest
99 | needs: [create-staging-repository, macos]
100 | if: ${{ always() && needs.create-staging-repository.result == 'success' && github.event.pull_request.merged == true }}
101 | steps:
102 | - name: Discard
103 | if: ${{ needs.macos.result != 'success' }}
104 | uses: nexus-actions/drop-nexus-staging-repo@main
105 | with:
106 | username: ${{ secrets.SONATYPE_NEXUS_USERNAME }}
107 | password: ${{ secrets.SONATYPE_NEXUS_PASSWORD }}
108 | staging_repository_id: ${{ needs.create-staging-repository.outputs.repository-id }}
109 | - name: Release
110 | if: ${{ needs.macos.result == 'success' }}
111 | uses: nexus-actions/release-nexus-staging-repo@main
112 | with:
113 | username: ${{ secrets.SONATYPE_NEXUS_USERNAME }}
114 | password: ${{ secrets.SONATYPE_NEXUS_PASSWORD }}
115 | staging_repository_id: ${{ needs.create-staging-repository.outputs.repository_id }}
--------------------------------------------------------------------------------
/.github/workflows/released.yaml:
--------------------------------------------------------------------------------
1 | name: Create release
2 |
3 | on:
4 | push:
5 | tags:
6 | - v*
7 |
8 | permissions:
9 | contents: write
10 |
11 | jobs:
12 | release:
13 | name: Release pushed tag
14 | runs-on: ubuntu-latest
15 | steps:
16 | - name: Create release
17 | env:
18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
19 | tag: ${{ github.ref_name }}
20 | run: |
21 | gh release create "$tag" \
22 | --repo="$GITHUB_REPOSITORY" \
23 | --title="${GITHUB_REPOSITORY#*/} ${tag#v}" \
24 | --generate-notes
--------------------------------------------------------------------------------
/.github/workflows/review.yaml:
--------------------------------------------------------------------------------
1 | name: "Build and Test"
2 | on:
3 | pull_request:
4 | paths-ignore:
5 | - '*.md'
6 | types:
7 | - synchronize
8 | - opened
9 | jobs:
10 | review:
11 | runs-on: ${{ matrix.os }}
12 | strategy:
13 | matrix:
14 | os: [ macos-latest, ubuntu-latest ]
15 | steps:
16 | - uses: actions/checkout@v4
17 | - name: Set up JDK 19
18 | uses: actions/setup-java@v4
19 | with:
20 | distribution: 'zulu'
21 | java-version: '19'
22 | cache: gradle
23 | - name: Setup Chrome
24 | uses: browser-actions/setup-chrome@v1
25 | - name: Gradle cache
26 | uses: gradle/actions/setup-gradle@v3
27 | - name: Tests with Gradle Major
28 | if: ${{ contains(github.event.pull_request.labels.*.name, 'major') }}
29 | run: ./gradlew ktlintCheck assemble build check allTests publishToMavenLocal -PincrementMajor=true
30 | - name: Tests with Gradle Minor
31 | if: ${{ contains(github.event.pull_request.labels.*.name, 'minor') }}
32 | run: ./gradlew ktlintCheck assemble build check allTests publishToMavenLocal -PincrementMinor=true
33 | - name: Tests with Gradle Patch
34 | if: ${{ !contains(github.event.pull_request.labels.*.name, 'major') && !contains(github.event.pull_request.labels.*.name, 'minor') }}
35 | run: ./gradlew ktlintCheck assemble build check allTests publishToMavenLocal
36 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build
2 | bin
3 | .idea
4 | *.iml
5 | .gradle
6 | *.swp
7 | .project
8 |
9 | kotlinx-io-native/repo/
10 |
11 | !.idea/vcs.xml
12 | !.idea/runConfigurations
13 | !.idea/codeStyles
14 |
15 | local.properties
--------------------------------------------------------------------------------
/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.kotlin-kotlin-stdlib-2.0.0-commonMain-2bbUHA.klib:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DitchOoM/buffer/56bae8919a09ab89ba831bc8862200735c0f4f61/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.kotlin-kotlin-stdlib-2.0.0-commonMain-2bbUHA.klib
--------------------------------------------------------------------------------
/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.kotlin-kotlin-test-2.0.0-annotationsCommonMain-24eTFQ.klib:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DitchOoM/buffer/56bae8919a09ab89ba831bc8862200735c0f4f61/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.kotlin-kotlin-test-2.0.0-annotationsCommonMain-24eTFQ.klib
--------------------------------------------------------------------------------
/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.kotlin-kotlin-test-2.0.0-assertionsCommonMain-24eTFQ.klib:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DitchOoM/buffer/56bae8919a09ab89ba831bc8862200735c0f4f61/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.kotlin-kotlin-test-2.0.0-assertionsCommonMain-24eTFQ.klib
--------------------------------------------------------------------------------
/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.kotlinx-atomicfu-0.23.1-commonMain-wFq7cg.klib:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DitchOoM/buffer/56bae8919a09ab89ba831bc8862200735c0f4f61/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.kotlinx-atomicfu-0.23.1-commonMain-wFq7cg.klib
--------------------------------------------------------------------------------
/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.kotlinx-atomicfu-0.23.1-nativeMain-wFq7cg.klib:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DitchOoM/buffer/56bae8919a09ab89ba831bc8862200735c0f4f61/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.kotlinx-atomicfu-0.23.1-nativeMain-wFq7cg.klib
--------------------------------------------------------------------------------
/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.kotlinx-kotlinx-coroutines-core-1.8.1-commonMain-XanZ2w.klib:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DitchOoM/buffer/56bae8919a09ab89ba831bc8862200735c0f4f61/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.kotlinx-kotlinx-coroutines-core-1.8.1-commonMain-XanZ2w.klib
--------------------------------------------------------------------------------
/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.kotlinx-kotlinx-coroutines-core-1.8.1-concurrentMain-XanZ2w.klib:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DitchOoM/buffer/56bae8919a09ab89ba831bc8862200735c0f4f61/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.kotlinx-kotlinx-coroutines-core-1.8.1-concurrentMain-XanZ2w.klib
--------------------------------------------------------------------------------
/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.kotlinx-kotlinx-coroutines-core-1.8.1-nativeDarwinMain-sy5nKg.klib:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DitchOoM/buffer/56bae8919a09ab89ba831bc8862200735c0f4f61/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.kotlinx-kotlinx-coroutines-core-1.8.1-nativeDarwinMain-sy5nKg.klib
--------------------------------------------------------------------------------
/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.kotlinx-kotlinx-coroutines-core-1.8.1-nativeMain-XanZ2w.klib:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DitchOoM/buffer/56bae8919a09ab89ba831bc8862200735c0f4f61/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.kotlinx-kotlinx-coroutines-core-1.8.1-nativeMain-XanZ2w.klib
--------------------------------------------------------------------------------
/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.kotlinx-kotlinx-coroutines-core-1.8.1-nativeOtherMain-XanZ2w.klib:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DitchOoM/buffer/56bae8919a09ab89ba831bc8862200735c0f4f61/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.kotlinx-kotlinx-coroutines-core-1.8.1-nativeOtherMain-XanZ2w.klib
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction, and distribution as defined
10 | by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is
13 | granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are
16 | controlled by, or are under common control with that entity. For the purposes of this definition,
17 | "control" means (i) the power, direct or indirect, to cause the direction or management of such
18 | entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
19 | outstanding shares, or (iii)
20 | beneficial ownership of such entity.
21 |
22 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this
23 | License.
24 |
25 | "Source" form shall mean the preferred form for making modifications, including but not limited
26 | to software source code, documentation source, and configuration files.
27 |
28 | "Object" form shall mean any form resulting from mechanical transformation or translation of a
29 | Source form, including but not limited to compiled object code, generated documentation, and
30 | conversions to other media types.
31 |
32 | "Work" shall mean the work of authorship, whether in Source or Object form, made available under
33 | the License, as indicated by a copyright notice that is included in or attached to the work
34 | (an example is provided in the Appendix below).
35 |
36 | "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or
37 | derived from) the Work and for which the editorial revisions, annotations, elaborations, or other
38 | modifications represent, as a whole, an original work of authorship. For the purposes of this
39 | License, Derivative Works shall not include works that remain separable from, or merely link (or
40 | bind by name) to the interfaces of, the Work and Derivative Works thereof.
41 |
42 | "Contribution" shall mean any work of authorship, including the original version of the Work and
43 | any modifications or additions to that Work or Derivative Works thereof, that is intentionally
44 | submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or
45 | Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this
46 | definition, "submitted"
47 | means any form of electronic, verbal, or written communication sent to the Licensor or its
48 | representatives, including but not limited to communication on electronic mailing lists, source
49 | code control systems, and issue tracking systems that are managed by, or on behalf of, the
50 | Licensor for the purpose of discussing and improving the Work, but excluding communication that
51 | is conspicuously marked or otherwise designated in writing by the copyright owner as "
52 | Not a Contribution."
53 |
54 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a
55 | Contribution has been received by Licensor and subsequently incorporated within the Work.
56 |
57 | 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor
58 | hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable
59 | copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform,
60 | sublicense, and distribute the Work and such Derivative Works in Source or Object form.
61 |
62 | 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor
63 | hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable
64 | (except as stated in this section) patent license to make, have made, use, offer to sell, sell,
65 | import, and otherwise transfer the Work, where such license applies only to those patent claims
66 | licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or
67 | by combination of their Contribution(s)
68 | with the Work to which such Contribution(s) was submitted. If You institute patent litigation
69 | against any entity (
70 | including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution
71 | incorporated within the Work constitutes direct or contributory patent infringement, then any
72 | patent licenses granted to You under this License for that Work shall terminate as of the date
73 | such litigation is filed.
74 |
75 | 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof
76 | in any medium, with or without modifications, and in Source or Object form, provided that You
77 | meet the following conditions:
78 |
79 | (a) You must give any other recipients of the Work or Derivative Works a copy of this License;
80 | and
81 |
82 | (b) You must cause any modified files to carry prominent notices stating that You changed the
83 | files; and
84 |
85 | (c) You must retain, in the Source form of any Derivative Works that You distribute, all
86 | copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding
87 | those notices that do not pertain to any part of the Derivative Works; and
88 |
89 | (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative
90 | Works that You distribute must include a readable copy of the attribution notices contained
91 | within such NOTICE file, excluding those notices that do not pertain to any part of the
92 | Derivative Works, in at least one of the following places: within a NOTICE text file distributed
93 | as part of the Derivative Works; within the Source form or documentation, if provided along with
94 | the Derivative Works; or, within a display generated by the Derivative Works, if and wherever
95 | such third-party notices normally appear. The contents of the NOTICE file are for informational
96 | purposes only and do not modify the License. You may add Your own attribution notices within
97 | Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the
98 | Work, provided that such additional attribution notices cannot be construed as modifying the
99 | License.
100 |
101 | You may add Your own copyright statement to Your modifications and may provide additional or
102 | different license terms and conditions for use, reproduction, or distribution of Your
103 | modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and
104 | distribution of the Work otherwise complies with the conditions stated in this License.
105 |
106 | 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution
107 | intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms
108 | and conditions of this License, without any additional terms or conditions. Notwithstanding the
109 | above, nothing herein shall supersede or modify the terms of any separate license agreement you
110 | may have executed with Licensor regarding such Contributions.
111 |
112 | 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service
113 | marks, or product names of the Licensor, except as required for reasonable and customary use in
114 | describing the origin of the Work and reproducing the content of the NOTICE file.
115 |
116 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor
117 | provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT
118 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation,
119 | any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
120 | PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or
121 | redistributing the Work and assume any risks associated with Your exercise of permissions under
122 | this License.
123 |
124 | 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including
125 | negligence), contract, or otherwise, unless required by applicable law (such as deliberate and
126 | grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for
127 | damages, including any direct, indirect, special, incidental, or consequential damages of any
128 | character arising as a result of this License or out of the use or inability to use the Work (
129 | including but not limited to damages for loss of goodwill, work stoppage, computer failure or
130 | malfunction, or any and all other commercial damages or losses), even if such Contributor has
131 | been advised of the possibility of such damages.
132 |
133 | 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works
134 | thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty,
135 | indemnity, or other liability obligations and/or rights consistent with this License. However, in
136 | accepting such obligations, You may act only on Your own behalf and on Your sole responsibility,
137 | not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each
138 | Contributor harmless for any liability incurred by, or claims asserted against, such Contributor
139 | by reason of your accepting any such warranty or additional liability.
140 |
141 | END OF TERMS AND CONDITIONS
142 |
143 | Copyright 2021 Rahul Behera
144 |
145 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
146 | compliance with the License. You may obtain a copy of the License at
147 |
148 | http://www.apache.org/licenses/LICENSE-2.0
149 |
150 | Unless required by applicable law or agreed to in writing, software distributed under the License is
151 | distributed on an "
152 | AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
153 | License for the specific language governing permissions and limitations under the License.
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | [![Contributors][contributors-shield]][contributors-url]
2 | [![Forks][forks-shield]][forks-url]
3 | [![Stargazers][stars-shield]][stars-url]
4 | [![Issues][issues-shield]][issues-url]
5 | [![MIT License][license-shield]][license-url]
6 | [![LinkedIn][linkedin-shield]][linkedin-url]
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
ByteBuffer
15 |
16 |
17 | A kotlin multiplatform library that allows you to allocate and modify byte[] natively using an API similar to Java's ByteBuffer API.
18 |
19 |
20 |
21 |
22 |
24 | Report Bug
25 | ·
26 | Request Feature
27 |
28 |
29 |
30 |
31 | Table of Contents
32 |
33 | -
34 | About The Project
35 |
38 |
41 |
42 | - Installation
43 | -
44 | Usage
45 |
54 |
55 | -
56 | Building Locally
57 |
58 | - Getting Started
59 | - Roadmap
60 | - Contributing
61 | - License
62 |
63 |
64 |
65 | ## About The Project
66 |
67 | Allocating and managing a chunk of memory can be slightly different based on each platform. This
68 | project aims to make it **easier to manage buffers in a cross platform way using kotlin
69 | multiplatform**. This was originally created as a side project for a kotlin multiplatform mqtt data
70 | sync solution.
71 |
72 | Implementation notes:
73 |
74 | * `JVM` + `Android` delegate to direct [ByteBuffers][byte-buffer-api] to avoid memory copies when
75 | possible.
76 | * Apple targets use NSData or NSMutableData
77 | * `JS` targets use Uint8Array.
78 | * `Native` platforms use standard byte arrays to manage memory.
79 |
80 | ### Runtime Dependencies
81 |
82 | * None
83 |
84 | ### [Supported Platforms](https://kotlinlang.org/docs/reference/mpp-supported-platforms.html)
85 |
86 | * All Kotlin Multiplatform supported OS's.
87 |
88 | | Platform | Wrapped Type |
89 | |:------------------:|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------:|
90 | | `JVM` 1.8 | [ByteBuffer](https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/nio/ByteBuffer.html) |
91 | | `Node.js` | [Uint8Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) including SharedArrayBuffer |
92 | | `Browser` (Chrome) | [Uint8Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) including SharedArrayBuffer |
93 | | `Android` | [ByteBuffer](https://developer.android.com/reference/java/nio/ByteBuffer) including [SharedMemory](https://developer.android.com/reference/android/os/SharedMemory) |
94 | | `iOS` | [NSData](https://developer.apple.com/documentation/foundation/nsdata?language=objc) |
95 | | `WatchOS` | [NSData](https://developer.apple.com/documentation/foundation/nsdata?language=objc) |
96 | | `TvOS` | [NSData](https://developer.apple.com/documentation/foundation/nsdata?language=objc) |
97 | | `MacOS` | [NSData](https://developer.apple.com/documentation/foundation/nsdata?language=objc) |
98 | | `Linux X64` | kotlin ByteArray |
99 | | `Windows X64` | TODO |
100 |
101 | ## Installation
102 |
103 | ### Gradle
104 |
105 | - [Add `implementation("com.ditchoom:buffer:$version")` to your `build.gradle` dependencies](https://search.maven.org/artifact/com.ditchoom/buffer)
106 |
107 | ## Usage
108 |
109 | ### Allocate a new platform agnostic buffer
110 |
111 | ```kotlin
112 | val buffer = PlatformBuffer.allocate(
113 | byteSize,
114 | zone = AllocationZone.Direct,
115 | byteOrder = ByteOrder.BIG_ENDIAN
116 | )
117 | ```
118 |
119 | ### Wrap an existing byte array into a platform agnostic buffer
120 |
121 | ```kotlin
122 | val byteArray = byteArrayOf(1, 2, 3, 4, 5)
123 | val buffer = PlatformBuffer.wrap(byteArray, byteOrder = ByteOrder.BIG_ENDIAN)
124 | ```
125 |
126 | ### Allocation Zones
127 |
128 | Allocation zones allow you to change where the buffer is allocated.
129 |
130 | - `AllocationZone.Custom` -> Allows you to override the underlying buffer. This can be helpful for
131 | memory mapped structures.
132 | - `AllocationZone.Heap` -> On JVM platforms, allocates a HeapByteBuffer, otherwise a native byte
133 | array
134 | - `AllocationZone.Direct` -> On JVM platforms, allocates a DirectByteBuffer, otherwise a native byte
135 | array
136 | - `AllocationZone.SharedMemory` -> On JS Platforms this will populate the `sharedArrayBuffer` parameter in `JsBuffer`.
137 | On API 27+ it allocates
138 | a [Shared Memory](https://developer.android.com/reference/android/os/SharedMemory) instance,
139 | otherwise will pipe the data during parcel using ParcelFileDescriptor and java.nio.Channel api. For `JS` platforms it
140 | will allocate
141 | a [`SharedArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer).
142 | If the
143 | proper [security requirements](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer#security_requirements)
144 | are not set, it will fallback to a standard `ArrayBuffer`.
145 |
146 | > **Android**: All `JvmBuffer`s are `Parcelable`. To avoid extra memory copies when using IPC, always choose `AllocationZone.SharedMemory`.
147 |
148 | > **Browser JS**: To enable SharedArrayBuffer, add the appropriate headers for the JS server in a gradle project by adding any file to a directory `webpack.config.d` next to the `src` directory containing:
149 | >```
150 | >if (config.devServer != null) {
151 | > config.devServer.headers = {
152 | > "Cross-Origin-Opener-Policy": "same-origin",
153 | > "Cross-Origin-Embedder-Policy": "require-corp"
154 | > }
155 | >}
156 | >```
157 |
158 | ### Byte order
159 |
160 | Byte order defaults to big endian but can be specified when creating the buffer
161 | with `ByteOrder.BIG_ENDIAN`
162 | or `ByteOrder.LITTLE_ENDIAN`
163 |
164 | The byte order of a buffer can be checked with `buffer.byteOrder`
165 |
166 | ### Relative write data into platform agnostic buffer
167 |
168 | ```kotlin
169 | val buffer: WriteBuffer
170 | // write signed byte
171 | buffer.writeByte(5.toByte())
172 | // write unsigned byte
173 | buffer.writeUByte(5.toUByte())
174 | // write short
175 | buffer.writeShort(5.toShort())
176 | // write unsigned short
177 | buffer.writeUShort(5.toUShort())
178 | // write int
179 | buffer.writeInt(5)
180 | // write unsigned int
181 | buffer.writeUInt(5.toUInt())
182 | // write long
183 | buffer.writeLong(5L)
184 | // write unsigned long
185 | buffer.writeULong(5uL)
186 | // write float
187 | buffer.writeFloat(123.456f)
188 | // write double
189 | buffer.writeDouble(123.456)
190 | // write text
191 | buffer.writeString("5", Charset.UTF8)
192 | // copy buffer into this one
193 | buffer.write(otherBuffer)
194 | // write byte array
195 | buffer.writeBytes(byteArrayOf(1, 2, 3, 4))
196 | // write partial byte array
197 | buffer.writeBytes(byteArrayOf(1, 2, 3, 4, 5), offset, length)
198 | ```
199 |
200 | ### Absolute write data into platform agnostic buffer
201 |
202 | ```kotlin
203 | val buffer: WriteBuffer
204 | // set signed byte
205 | buffer[index] = 5.toByte()
206 | // set unsigned byte
207 | buffer[index] = 5.toUByte()
208 | // set short
209 | buffer[index] = 5.toByte()
210 | // set unsigned short
211 | buffer[index] = 5.toUShort()
212 | // set int
213 | buffer[index] = 5
214 | // set unsigned int
215 | buffer[index] = 5.toUInt()
216 | // set long
217 | buffer[index] = 5L
218 | // set unsigned long
219 | buffer[index] = 5uL
220 | // set float
221 | buffer[index] = 123.456f
222 | // set double
223 | buffer[index] = 123.456
224 | ```
225 |
226 | ### Relative read data into platform agnostic buffer
227 |
228 | ```kotlin
229 | val buffer: ReadBuffer
230 | // read signed byte
231 | val b = buffer.readByte()
232 | // read unsigned byte
233 | val uByte = buffer.readUnsignedByte()
234 | // read short
235 | val short = buffer.readShort()
236 | // read unsigned short
237 | val uShort = buffer.readUnsignedShort()
238 | // read int
239 | val intValue = buffer.readInt()
240 | // read unsigned int
241 | val uIntValue = buffer.readUnsignedInt()
242 | // read long
243 | val longValue = buffer.readLong()
244 | // read unsigned long
245 | val uLongValue = buffer.readUnsignedLong()
246 | // read float
247 | val float = buffer.readFloat()
248 | // read double
249 | val double = buffer.readDouble()
250 | // read text
251 | val string = buffer.readUtf8(numOfBytesToRead)
252 | // read byte array
253 | val byteArray = buffer.readByteArray(numOfBytesToRead)
254 | // read a shared subsequence read buffer (changes to the original reflect here)
255 | val readBuffer = buffer.readBytes(numOfBytesForBuffer)
256 | ```
257 |
258 | ### Absolute read data into platform agnostic buffer
259 |
260 | ```kotlin
261 | val buffer: ReadBuffer
262 | // get signed byte
263 | val b = buffer.get(index) // or buffer[index]
264 | // get unsigned byte
265 | val uByte = buffer.getUnsignedByte(index)
266 | // get short
267 | val short = buffer.getShort(index)
268 | // get unsigned short
269 | val uShort = buffer.getUnsignedShort(index)
270 | // get int
271 | val intValue = buffer.getInt(index)
272 | // get unsigned int
273 | val uIntValue = buffer.getUnsignedInt(index)
274 | // get long
275 | val longValue = buffer.getLong(index)
276 | // get unsigned long
277 | val uLongValue = buffer.getUnsignedLong(index)
278 | // get float
279 | val float = buffer.getFloat(index)
280 | // get double
281 | val double = buffer.getDouble(index)
282 | // slice the buffer without adjusting the position or limit (changes to the original reflect here)
283 | val slicedBuffer = buffer.slice()
284 | ```
285 |
286 | ## Building Locally
287 |
288 | - `git clone git@github.com:DitchOoM/buffer.git`
289 | - Open cloned directory with [Intellij IDEA](https://www.jetbrains.com/idea/download).
290 | - Be sure
291 | to [open with gradle](https://www.jetbrains.com/help/idea/gradle.html#gradle_import_project_start)
292 |
293 | ## Roadmap
294 |
295 | See the [open issues](https://github.com/DitchOoM/buffer/issues) for a list of proposed features (
296 | and known issues).
297 |
298 | ## Contributing
299 |
300 | Contributions are what make the open source community such an amazing place to be learn, inspire,
301 | and create. Any contributions you make are **greatly appreciated**.
302 |
303 | 1. Fork the Project
304 | 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
305 | 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
306 | 4. Push to the Branch (`git push origin feature/AmazingFeature`)
307 | 5. Open a Pull Request
308 |
309 | ## License
310 |
311 | Distributed under the Apache 2.0 License. See `LICENSE` for more information.
312 |
313 | [contributors-shield]: https://img.shields.io/github/contributors/DitchOoM/buffer.svg?style=for-the-badge
314 |
315 | [contributors-url]: https://github.com/DitchOoM/buffer/graphs/contributors
316 |
317 | [forks-shield]: https://img.shields.io/github/forks/DitchOoM/buffer.svg?style=for-the-badge
318 |
319 | [forks-url]: https://github.com/DitchOoM/buffer/network/members
320 |
321 | [stars-shield]: https://img.shields.io/github/stars/DitchOoM/buffer.svg?style=for-the-badge
322 |
323 | [stars-url]: https://github.com/DitchOoM/buffer/stargazers
324 |
325 | [issues-shield]: https://img.shields.io/github/issues/DitchOoM/buffer.svg?style=for-the-badge
326 |
327 | [issues-url]: https://github.com/DitchOoM/buffer/issues
328 |
329 | [license-shield]: https://img.shields.io/github/license/DitchOoM/buffer.svg?style=for-the-badge
330 |
331 | [license-url]: https://github.com/DitchOoM/buffer/blob/master/LICENSE.md
332 |
333 | [linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555
334 |
335 | [linkedin-url]: https://www.linkedin.com/in/thebehera
336 |
337 | [byte-buffer-api]: https://docs.oracle.com/javase/8/docs/api/java/nio/ByteBuffer.html
338 |
339 | [maven-central]: https://search.maven.org/search?q=com.ditchoom
340 |
341 | [npm]: https://www.npmjs.com/search?q=ditchoom-buffer
342 |
343 | [cocoapods]: https://cocoapods.org/pods/DitchOoM-buffer
344 |
345 | [apt]: https://packages.ubuntu.com/search?keywords=ditchoom&searchon=names&suite=groovy§ion=all
346 |
347 | [yum]: https://pkgs.org/search/?q=DitchOoM-buffer
348 |
349 | [chocolately]: https://chocolatey.org/packages?q=DitchOoM-buffer
350 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import groovy.util.Node
2 | import groovy.xml.XmlParser
3 | import org.apache.tools.ant.taskdefs.condition.Os
4 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget
5 | import java.net.URL
6 |
7 | plugins {
8 | kotlin("multiplatform") version "2.0.0"
9 | id("com.android.library") version "8.4.0"
10 | id("io.codearte.nexus-staging") version "0.30.0"
11 | `maven-publish`
12 | signing
13 | id("org.jlleitschuh.gradle.ktlint") version "12.1.1"
14 | }
15 | val isRunningOnGithub = System.getenv("GITHUB_REPOSITORY")?.isNotBlank() == true
16 | val isMainBranchGithub = System.getenv("GITHUB_REF") == "refs/heads/main"
17 | val isMacOS = Os.isFamily(Os.FAMILY_MAC)
18 | val loadAllPlatforms = !isRunningOnGithub || (isMacOS && isMainBranchGithub) || !isMacOS
19 | val libraryVersionPrefix: String by project
20 | group = "com.ditchoom"
21 | val libraryVersion = getNextVersion().toString()
22 | println(
23 | "Version: ${libraryVersion}\nisRunningOnGithub: $isRunningOnGithub\nisMainBranchGithub: $isMainBranchGithub\n" +
24 | "OS:$isMacOS\nLoad All Platforms: $loadAllPlatforms",
25 | )
26 |
27 | repositories {
28 | google()
29 | mavenCentral()
30 | maven { setUrl("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/kotlin-js-wrappers/") }
31 | }
32 |
33 | kotlin {
34 | androidTarget {
35 | publishLibraryVariants("release")
36 | compilerOptions.jvmTarget.set(JvmTarget.JVM_1_8)
37 | }
38 | jvm {
39 | compilerOptions.jvmTarget.set(JvmTarget.JVM_1_8)
40 | }
41 | js {
42 | moduleName = "buffer-kt"
43 | browser()
44 | nodejs()
45 | }
46 | macosX64()
47 | macosArm64()
48 | linuxX64()
49 | linuxArm64()
50 | iosArm64()
51 | iosSimulatorArm64()
52 | iosX64()
53 | watchosArm64()
54 | watchosSimulatorArm64()
55 | watchosX64()
56 | tvosArm64()
57 | tvosSimulatorArm64()
58 | tvosX64()
59 | applyDefaultHierarchyTemplate()
60 | sourceSets {
61 | commonTest.dependencies {
62 | implementation(kotlin("test"))
63 | }
64 | androidMain.dependencies {
65 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1")
66 | }
67 |
68 | val androidInstrumentedTest by getting {
69 | dependencies {
70 | implementation(kotlin("test"))
71 | implementation("androidx.test:runner:1.5.2")
72 | implementation("androidx.test:rules:1.5.0")
73 | implementation("androidx.test:core-ktx:1.5.0")
74 | implementation("androidx.test.ext:junit:1.1.5")
75 | }
76 | }
77 |
78 | jsMain.dependencies {
79 | implementation("org.jetbrains.kotlin-wrappers:kotlin-web:1.0.0-pre.746")
80 | implementation("org.jetbrains.kotlin-wrappers:kotlin-js:1.0.0-pre.746")
81 | }
82 | }
83 | }
84 |
85 | android {
86 | buildFeatures {
87 | aidl = true
88 | }
89 | compileSdk = 34
90 | defaultConfig {
91 | minSdk = 16
92 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
93 | }
94 | namespace = "$group.${rootProject.name}"
95 | publishing {
96 | singleVariant("release") {
97 | withSourcesJar()
98 | withJavadocJar()
99 | }
100 | }
101 | }
102 |
103 | val javadocJar: TaskProvider by tasks.registering(Jar::class) {
104 | archiveClassifier.set("javadoc")
105 | }
106 |
107 | if (isRunningOnGithub) {
108 | if (isMainBranchGithub) {
109 | signing {
110 | useInMemoryPgpKeys(
111 | "56F1A973",
112 | System.getenv("GPG_SECRET"),
113 | System.getenv("GPG_SIGNING_PASSWORD"),
114 | )
115 | sign(publishing.publications)
116 | }
117 | }
118 |
119 | val ossUser = System.getenv("SONATYPE_NEXUS_USERNAME")
120 | val ossPassword = System.getenv("SONATYPE_NEXUS_PASSWORD")
121 |
122 | val publishedGroupId: String by project
123 | val libraryName: String by project
124 | val libraryDescription: String by project
125 | val siteUrl: String by project
126 | val gitUrl: String by project
127 | val licenseName: String by project
128 | val licenseUrl: String by project
129 | val developerOrg: String by project
130 | val developerName: String by project
131 | val developerEmail: String by project
132 | val developerId: String by project
133 |
134 | project.group = publishedGroupId
135 | project.version = libraryVersion
136 |
137 | publishing {
138 | publications.withType(MavenPublication::class) {
139 | groupId = publishedGroupId
140 | version = libraryVersion
141 |
142 | artifact(tasks["javadocJar"])
143 |
144 | pom {
145 | name.set(libraryName)
146 | description.set(libraryDescription)
147 | url.set(siteUrl)
148 |
149 | licenses {
150 | license {
151 | name.set(licenseName)
152 | url.set(licenseUrl)
153 | }
154 | }
155 | developers {
156 | developer {
157 | id.set(developerId)
158 | name.set(developerName)
159 | email.set(developerEmail)
160 | }
161 | }
162 | organization {
163 | name.set(developerOrg)
164 | }
165 | scm {
166 | connection.set(gitUrl)
167 | developerConnection.set(gitUrl)
168 | url.set(siteUrl)
169 | }
170 | }
171 | }
172 |
173 | repositories {
174 | val repositoryId = System.getenv("SONATYPE_REPOSITORY_ID")
175 | maven("https://oss.sonatype.org/service/local/staging/deployByRepositoryId/$repositoryId/") {
176 | name = "sonatype"
177 | credentials {
178 | username = ossUser
179 | password = ossPassword
180 | }
181 | }
182 | }
183 | }
184 |
185 | nexusStaging {
186 | username = ossUser
187 | password = ossPassword
188 | packageGroup = publishedGroupId
189 | }
190 | }
191 |
192 | ktlint {
193 | verbose.set(true)
194 | outputToConsole.set(true)
195 | }
196 |
197 | class Version(val major: UInt, val minor: UInt, val patch: UInt, val snapshot: Boolean) {
198 | constructor(string: String, snapshot: Boolean) :
199 | this(
200 | string.split('.')[0].toUInt(),
201 | string.split('.')[1].toUInt(),
202 | string.split('.')[2].toUInt(),
203 | snapshot,
204 | )
205 |
206 | fun incrementMajor() = Version(major + 1u, 0u, 0u, snapshot)
207 |
208 | fun incrementMinor() = Version(major, minor + 1u, 0u, snapshot)
209 |
210 | fun incrementPatch() = Version(major, minor, patch + 1u, snapshot)
211 |
212 | fun snapshot() = Version(major, minor, patch, true)
213 |
214 | fun isVersionZero() = major == 0u && minor == 0u && patch == 0u
215 |
216 | override fun toString(): String =
217 | if (snapshot) {
218 | "$major.$minor.$patch-SNAPSHOT"
219 | } else {
220 | "$major.$minor.$patch"
221 | }
222 | }
223 | private var latestVersion: Version? = Version(0u, 0u, 0u, true)
224 |
225 | @Suppress("UNCHECKED_CAST")
226 | fun getLatestVersion(): Version {
227 | val latestVersion = latestVersion
228 | if (latestVersion != null && !latestVersion.isVersionZero()) {
229 | return latestVersion
230 | }
231 | val xml = URL("https://repo1.maven.org/maven2/com/ditchoom/${rootProject.name}/maven-metadata.xml").readText()
232 | val versioning = XmlParser().parseText(xml)["versioning"] as List
233 | val latestStringList = versioning.first()["latest"] as List
234 | val result = Version((latestStringList.first().value() as List<*>).first().toString(), false)
235 | this.latestVersion = result
236 | return result
237 | }
238 |
239 | fun getNextVersion(snapshot: Boolean = !isRunningOnGithub): Version {
240 | var v = getLatestVersion()
241 | if (snapshot) {
242 | v = v.snapshot()
243 | }
244 | if (project.hasProperty("incrementMajor") && project.property("incrementMajor") == "true") {
245 | return v.incrementMajor()
246 | } else if (project.hasProperty("incrementMinor") && project.property("incrementMinor") == "true") {
247 | return v.incrementMinor()
248 | }
249 | return v.incrementPatch()
250 | }
251 |
252 | tasks.create("nextVersion") {
253 | println(getNextVersion())
254 | }
255 |
256 | val signingTasks = tasks.withType()
257 | tasks.withType().configureEach {
258 | dependsOn(signingTasks)
259 | }
260 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | kotlin.code.style=official
2 | android.useAndroidX=true
3 | org.gradle.jvmargs=-XX:MaxMetaspaceSize=4g
4 | kotlin.mpp.stability.nowarn=true
5 | libraryVersionPrefix=1.3.
6 | libraryName=Buffer
7 | libraryDescription=Multiplatform bytebuffer that delegates to native byte[] or ByteBuffer
8 | publishedGroupId=com.ditchoom
9 | artifactName=buffer
10 | siteUrl=https://github.com/DitchOoM/buffer
11 | gitUrl=git://github.com/DitchOOM/buffer.git
12 | developerId=thebehera
13 | developerOrg=Ditchoom
14 | developerName=Rahul Behera
15 | developerEmail=rbehera@gmail.com
16 | licenseName=The Apache Software License, Version 2.0
17 | licenseUrl=https://www.apache.org/licenses/LICENSE-2.0.txt
18 | allLicenses=["Apache-2.0"]
19 |
20 | kotlin.mpp.androidGradlePluginCompatibility.nowarn=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DitchOoM/buffer/56bae8919a09ab89ba831bc8862200735c0f4f61/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | #
21 | # Gradle start up script for POSIX generated by Gradle.
22 | #
23 | # Important for running:
24 | #
25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
26 | # noncompliant, but you have some other compliant shell such as ksh or
27 | # bash, then to run this script, type that shell name before the whole
28 | # command line, like:
29 | #
30 | # ksh Gradle
31 | #
32 | # Busybox and similar reduced shells will NOT work, because this script
33 | # requires all of these POSIX shell features:
34 | # * functions;
35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
37 | # * compound commands having a testable exit status, especially «case»;
38 | # * various built-in commands including «command», «set», and «ulimit».
39 | #
40 | # Important for patching:
41 | #
42 | # (2) This script targets any POSIX shell, so it avoids extensions provided
43 | # by Bash, Ksh, etc; in particular arrays are avoided.
44 | #
45 | # The "traditional" practice of packing multiple parameters into a
46 | # space-separated string is a well documented source of bugs and security
47 | # problems, so this is (mostly) avoided, by progressively accumulating
48 | # options in "$@", and eventually passing that to Java.
49 | #
50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
52 | # see the in-line comments for details.
53 | #
54 | # There are tweaks for specific operating systems such as AIX, CygWin,
55 | # Darwin, MinGW, and NonStop.
56 | #
57 | # (3) This script is generated from the Groovy template
58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
59 | # within the Gradle project.
60 | #
61 | # You can find Gradle at https://github.com/gradle/gradle/.
62 | #
63 | ##############################################################################
64 |
65 | # Attempt to set APP_HOME
66 |
67 | # Resolve links: $0 may be a link
68 | app_path=$0
69 |
70 | # Need this for daisy-chained symlinks.
71 | while
72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
73 | [ -h "$app_path" ]
74 | do
75 | ls=$( ls -ld "$app_path" )
76 | link=${ls#*' -> '}
77 | case $link in #(
78 | /*) app_path=$link ;; #(
79 | *) app_path=$APP_HOME$link ;;
80 | esac
81 | done
82 |
83 | # This is normally unused
84 | # shellcheck disable=SC2034
85 | APP_BASE_NAME=${0##*/}
86 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
87 | APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
88 |
89 | # Use the maximum available, or set MAX_FD != -1 to use that value.
90 | MAX_FD=maximum
91 |
92 | warn () {
93 | echo "$*"
94 | } >&2
95 |
96 | die () {
97 | echo
98 | echo "$*"
99 | echo
100 | exit 1
101 | } >&2
102 |
103 | # OS specific support (must be 'true' or 'false').
104 | cygwin=false
105 | msys=false
106 | darwin=false
107 | nonstop=false
108 | case "$( uname )" in #(
109 | CYGWIN* ) cygwin=true ;; #(
110 | Darwin* ) darwin=true ;; #(
111 | MSYS* | MINGW* ) msys=true ;; #(
112 | NONSTOP* ) nonstop=true ;;
113 | esac
114 |
115 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
116 |
117 |
118 | # Determine the Java command to use to start the JVM.
119 | if [ -n "$JAVA_HOME" ] ; then
120 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
121 | # IBM's JDK on AIX uses strange locations for the executables
122 | JAVACMD=$JAVA_HOME/jre/sh/java
123 | else
124 | JAVACMD=$JAVA_HOME/bin/java
125 | fi
126 | if [ ! -x "$JAVACMD" ] ; then
127 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
128 |
129 | Please set the JAVA_HOME variable in your environment to match the
130 | location of your Java installation."
131 | fi
132 | else
133 | JAVACMD=java
134 | if ! command -v java >/dev/null 2>&1
135 | then
136 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
137 |
138 | Please set the JAVA_HOME variable in your environment to match the
139 | location of your Java installation."
140 | fi
141 | fi
142 |
143 | # Increase the maximum file descriptors if we can.
144 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
145 | case $MAX_FD in #(
146 | max*)
147 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
148 | # shellcheck disable=SC2039,SC3045
149 | MAX_FD=$( ulimit -H -n ) ||
150 | warn "Could not query maximum file descriptor limit"
151 | esac
152 | case $MAX_FD in #(
153 | '' | soft) :;; #(
154 | *)
155 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
156 | # shellcheck disable=SC2039,SC3045
157 | ulimit -n "$MAX_FD" ||
158 | warn "Could not set maximum file descriptor limit to $MAX_FD"
159 | esac
160 | fi
161 |
162 | # Collect all arguments for the java command, stacking in reverse order:
163 | # * args from the command line
164 | # * the main class name
165 | # * -classpath
166 | # * -D...appname settings
167 | # * --module-path (only if needed)
168 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
169 |
170 | # For Cygwin or MSYS, switch paths to Windows format before running java
171 | if "$cygwin" || "$msys" ; then
172 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
173 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
174 |
175 | JAVACMD=$( cygpath --unix "$JAVACMD" )
176 |
177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
178 | for arg do
179 | if
180 | case $arg in #(
181 | -*) false ;; # don't mess with options #(
182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
183 | [ -e "$t" ] ;; #(
184 | *) false ;;
185 | esac
186 | then
187 | arg=$( cygpath --path --ignore --mixed "$arg" )
188 | fi
189 | # Roll the args list around exactly as many times as the number of
190 | # args, so each arg winds up back in the position where it started, but
191 | # possibly modified.
192 | #
193 | # NB: a `for` loop captures its iteration list before it begins, so
194 | # changing the positional parameters here affects neither the number of
195 | # iterations, nor the values presented in `arg`.
196 | shift # remove old arg
197 | set -- "$@" "$arg" # push replacement arg
198 | done
199 | fi
200 |
201 |
202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
204 |
205 | # Collect all arguments for the java command:
206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
207 | # and any embedded shellness will be escaped.
208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
209 | # treated as '${Hostname}' itself on the command line.
210 |
211 | set -- \
212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
213 | -classpath "$CLASSPATH" \
214 | org.gradle.wrapper.GradleWrapperMain \
215 | "$@"
216 |
217 | # Stop when "xargs" is not available.
218 | if ! command -v xargs >/dev/null 2>&1
219 | then
220 | die "xargs is not available"
221 | fi
222 |
223 | # Use "xargs" to parse quoted args.
224 | #
225 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
226 | #
227 | # In Bash we could simply go:
228 | #
229 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
230 | # set -- "${ARGS[@]}" "$@"
231 | #
232 | # but POSIX shell has neither arrays nor command substitution, so instead we
233 | # post-process each arg (as a line of input to sed) to backslash-escape any
234 | # character that might be a shell metacharacter, then use eval to reverse
235 | # that process (while maintaining the separation between arguments), and wrap
236 | # the whole thing up as a single "set" statement.
237 | #
238 | # This will of course break if any of these variables contains a newline or
239 | # an unmatched quote.
240 | #
241 |
242 | eval "set -- $(
243 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
244 | xargs -n1 |
245 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
246 | tr '\n' ' '
247 | )" '"$@"'
248 |
249 | exec "$JAVACMD" "$@"
250 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%"=="" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%"=="" set DIRNAME=.
29 | @rem This is normally unused
30 | set APP_BASE_NAME=%~n0
31 | set APP_HOME=%DIRNAME%
32 |
33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
35 |
36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
38 |
39 | @rem Find java.exe
40 | if defined JAVA_HOME goto findJavaFromJavaHome
41 |
42 | set JAVA_EXE=java.exe
43 | %JAVA_EXE% -version >NUL 2>&1
44 | if %ERRORLEVEL% equ 0 goto execute
45 |
46 | echo. 1>&2
47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
48 | echo. 1>&2
49 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
50 | echo location of your Java installation. 1>&2
51 |
52 | goto fail
53 |
54 | :findJavaFromJavaHome
55 | set JAVA_HOME=%JAVA_HOME:"=%
56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
57 |
58 | if exist "%JAVA_EXE%" goto execute
59 |
60 | echo. 1>&2
61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
62 | echo. 1>&2
63 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
64 | echo location of your Java installation. 1>&2
65 |
66 | goto fail
67 |
68 | :execute
69 | @rem Setup the command line
70 |
71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
72 |
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if %ERRORLEVEL% equ 0 goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | set EXIT_CODE=%ERRORLEVEL%
85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
87 | exit /b %EXIT_CODE%
88 |
89 | :mainEnd
90 | if "%OS%"=="Windows_NT" endlocal
91 |
92 | :omega
93 |
--------------------------------------------------------------------------------
/karma.config.d/karma.conf.js:
--------------------------------------------------------------------------------
1 | config.client = config.client || {}
2 | config.client.mocha = config.client.mocha || {}
3 | config.client.mocha.timeout = 10000
4 | config.browserNoActivityTimeout = 10000
5 | config.browserDisconnectTimeout = 10000
6 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google()
4 | gradlePluginPortal()
5 | mavenCentral()
6 | }
7 | }
8 | rootProject.name = "buffer"
9 | plugins {
10 | id("com.gradle.develocity") version ("3.17.3")
11 | }
12 | develocity {
13 | buildScan {
14 | uploadInBackground.set(System.getenv("CI") != null)
15 | termsOfUseUrl.set("https://gradle.com/help/legal-terms-of-use")
16 | termsOfUseAgree.set("yes")
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/androidInstrumentedTest/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
--------------------------------------------------------------------------------
/src/androidInstrumentedTest/aidl/com/ditchoom/buffer/BufferIpcTestAidl.aidl:
--------------------------------------------------------------------------------
1 | // BufferIpcTestAidl.aidl
2 | package com.ditchoom.buffer;
3 |
4 | import com.ditchoom.buffer.JvmBuffer;
5 |
6 | interface BufferIpcTestAidl {
7 | JvmBuffer aidl(int num, int type);
8 | }
--------------------------------------------------------------------------------
/src/androidInstrumentedTest/kotlin/com/ditchoom/buffer/IPCSimpleService.kt:
--------------------------------------------------------------------------------
1 | package com.ditchoom.buffer
2 |
3 | import android.app.Service
4 | import android.content.Intent
5 | import android.os.IBinder
6 |
7 | class IPCSimpleService : Service() {
8 | override fun onBind(intent: Intent?): IBinder =
9 | object : BufferIpcTestAidl.Stub() {
10 | private val byteSize = 4 * 1024 * 1024
11 |
12 | override fun aidl(
13 | num: Int,
14 | type: Int,
15 | ): JvmBuffer {
16 | val zone =
17 | when (type) {
18 | 0 -> AllocationZone.Heap
19 | 1 -> AllocationZone.Direct
20 | else -> AllocationZone.SharedMemory
21 | }
22 | val buffer = PlatformBuffer.allocate(byteSize, zone)
23 | buffer.writeInt(num)
24 | return buffer as JvmBuffer
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/androidInstrumentedTest/kotlin/com/ditchoom/buffer/IPCTest.kt:
--------------------------------------------------------------------------------
1 | package com.ditchoom.buffer
2 |
3 | import android.content.ComponentName
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.content.ServiceConnection
7 | import android.os.IBinder
8 | import androidx.test.ext.junit.runners.AndroidJUnit4
9 | import androidx.test.filters.MediumTest
10 | import androidx.test.platform.app.InstrumentationRegistry
11 | import androidx.test.rule.ServiceTestRule
12 | import kotlinx.coroutines.runBlocking
13 | import kotlinx.coroutines.sync.Mutex
14 | import org.junit.Rule
15 | import org.junit.Test
16 | import org.junit.runner.RunWith
17 | import java.util.concurrent.TimeUnit
18 | import kotlin.random.Random
19 | import kotlin.test.assertEquals
20 | import kotlin.test.assertFalse
21 | import kotlin.test.assertTrue
22 |
23 | @RunWith(AndroidJUnit4::class)
24 | @MediumTest
25 | class IPCTest {
26 | @get:Rule
27 | val serviceRule: ServiceTestRule = ServiceTestRule.withTimeout(3, TimeUnit.SECONDS)
28 |
29 | @Test
30 | fun sharedMemoryBuffer() =
31 | runBlocking {
32 | val mutex = Mutex(true)
33 | lateinit var stub: BufferIpcTestAidl
34 | serviceRule.bindService(
35 | Intent(InstrumentationRegistry.getInstrumentation().context, IPCSimpleService::class.java),
36 | object : ServiceConnection {
37 | override fun onServiceConnected(
38 | name: ComponentName,
39 | service: IBinder,
40 | ) {
41 | stub = BufferIpcTestAidl.Stub.asInterface(service)
42 | mutex.unlock()
43 | }
44 |
45 | override fun onServiceDisconnected(name: ComponentName) {
46 | throw IllegalStateException("Service Disconnected")
47 | }
48 | },
49 | Context.BIND_AUTO_CREATE,
50 | )
51 | mutex.lock()
52 | val num = Random.nextInt()
53 | val buffer2 = stub.aidl(num, 2)
54 | assertTrue { buffer2.byteBuffer.isDirect }
55 | buffer2.resetForRead()
56 | assertEquals(num, buffer2.readInt())
57 | }
58 |
59 | @Test
60 | fun copyJvmDirect() =
61 | runBlocking {
62 | val mutex = Mutex(true)
63 | lateinit var stub: BufferIpcTestAidl
64 | serviceRule.bindService(
65 | Intent(InstrumentationRegistry.getInstrumentation().context, IPCSimpleService::class.java),
66 | object : ServiceConnection {
67 | override fun onServiceConnected(
68 | name: ComponentName,
69 | service: IBinder,
70 | ) {
71 | stub = BufferIpcTestAidl.Stub.asInterface(service)
72 | mutex.unlock()
73 | }
74 |
75 | override fun onServiceDisconnected(name: ComponentName) {
76 | throw IllegalStateException("Service Disconnected")
77 | }
78 | },
79 | Context.BIND_AUTO_CREATE,
80 | )
81 | mutex.lock()
82 | val num = Random.nextInt()
83 | val buffer1 = stub.aidl(num, 1)
84 | assertTrue { buffer1.byteBuffer.isDirect }
85 | buffer1.resetForRead()
86 | assertEquals(num, buffer1.readInt())
87 | }
88 |
89 | @Test
90 | fun copyJvmHeap() =
91 | runBlocking {
92 | val mutex = Mutex(true)
93 | lateinit var stub: BufferIpcTestAidl
94 | serviceRule.bindService(
95 | Intent(InstrumentationRegistry.getInstrumentation().context, IPCSimpleService::class.java),
96 | object : ServiceConnection {
97 | override fun onServiceConnected(
98 | name: ComponentName,
99 | service: IBinder,
100 | ) {
101 | stub = BufferIpcTestAidl.Stub.asInterface(service)
102 | mutex.unlock()
103 | }
104 |
105 | override fun onServiceDisconnected(name: ComponentName) {
106 | throw IllegalStateException("Service Disconnected")
107 | }
108 | },
109 | Context.BIND_AUTO_CREATE,
110 | )
111 | mutex.lock()
112 | val num = Random.nextInt()
113 | val buffer0 = stub.aidl(num, 0)
114 | assertFalse { buffer0.byteBuffer.isDirect }
115 | buffer0.resetForRead()
116 | assertEquals(num, buffer0.readInt())
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/androidInstrumentedTest/kotlin/com/ditchoom/buffer/ParcelableTests.kt:
--------------------------------------------------------------------------------
1 | package com.ditchoom.buffer
2 |
3 | import android.os.Build
4 | import android.os.Parcel
5 | import android.os.SharedMemory
6 | import androidx.test.ext.junit.runners.AndroidJUnit4
7 | import androidx.test.filters.SmallTest
8 | import org.junit.Test
9 | import org.junit.runner.RunWith
10 | import java.nio.ByteBuffer
11 | import kotlin.random.Random
12 | import kotlin.test.assertEquals
13 |
14 | @RunWith(AndroidJUnit4::class)
15 | @SmallTest
16 | class ParcelableTests {
17 | @Test
18 | fun parcelSharedMemory() {
19 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O_MR1) return
20 | val sharedMemoryBuffer = SharedMemory.create("test", 2_000_000)
21 | val s = ParcelableSharedMemoryBuffer(sharedMemoryBuffer.mapReadWrite(), sharedMemoryBuffer)
22 | val i = Random.nextInt()
23 | s.writeInt(i)
24 |
25 | val parcel = Parcel.obtain()
26 | s.writeToParcel(parcel, Parcelable.PARCELABLE_WRITE_RETURN_VALUE)
27 | parcel.setDataPosition(0)
28 |
29 | val createdFromParcel = ParcelableSharedMemoryBuffer.CREATOR.createFromParcel(parcel)
30 | createdFromParcel.resetForRead()
31 | assertEquals(i, createdFromParcel.readInt())
32 | }
33 |
34 | @Test
35 | fun parcelFileDescriptor() {
36 | val buffer = ByteBuffer.allocateDirect(2_000_000)
37 | val s = JvmBuffer(buffer)
38 | val i = Random.nextInt()
39 | s.writeInt(i)
40 |
41 | val parcel = Parcel.obtain()
42 | s.writeToParcel(parcel, Parcelable.PARCELABLE_WRITE_RETURN_VALUE)
43 | parcel.setDataPosition(0)
44 |
45 | val createdFromParcel = JvmBuffer.CREATOR.createFromParcel(parcel)
46 | createdFromParcel.resetForRead()
47 | assertEquals(i, createdFromParcel.readInt())
48 | }
49 |
50 | @Test
51 | fun copyByteArray() {
52 | val s = JvmBuffer(ByteBuffer.allocate(4_000))
53 | val i = Random.nextInt()
54 | s.writeInt(i)
55 |
56 | val parcel = Parcel.obtain()
57 | s.writeToParcel(parcel, 0)
58 | parcel.setDataPosition(0)
59 |
60 | val createdFromParcel = JvmBuffer.CREATOR.createFromParcel(parcel)
61 | createdFromParcel.resetForRead()
62 | assertEquals(i, createdFromParcel.readInt())
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/androidMain/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/androidMain/aidl/com/ditchoom/buffer/JvmBuffer.aidl:
--------------------------------------------------------------------------------
1 | package com.ditchoom.buffer;
2 |
3 | parcelable JvmBuffer;
--------------------------------------------------------------------------------
/src/androidMain/kotlin/com/ditchoom/buffer/BaseJvmBuffer.kt:
--------------------------------------------------------------------------------
1 | package com.ditchoom.buffer
2 |
3 | import java.io.RandomAccessFile
4 | import java.nio.Buffer
5 | import java.nio.ByteBuffer
6 | import java.nio.CharBuffer
7 | import java.nio.charset.CodingErrorAction
8 | import kotlin.coroutines.resume
9 | import kotlin.coroutines.resumeWithException
10 | import kotlin.coroutines.suspendCoroutine
11 |
12 | abstract class BaseJvmBuffer(open val byteBuffer: ByteBuffer, val fileRef: RandomAccessFile? = null) :
13 | PlatformBuffer {
14 | override val byteOrder =
15 | when (byteBuffer.order()) {
16 | java.nio.ByteOrder.BIG_ENDIAN -> ByteOrder.BIG_ENDIAN
17 | java.nio.ByteOrder.LITTLE_ENDIAN -> ByteOrder.LITTLE_ENDIAN
18 | else -> ByteOrder.BIG_ENDIAN
19 | }
20 |
21 | // Use Buffer reference to avoid NoSuchMethodException between JVM. see https://stackoverflow.com/q/61267495
22 | private val buffer = byteBuffer as Buffer
23 |
24 | override fun resetForRead() {
25 | buffer.flip()
26 | }
27 |
28 | override fun resetForWrite() {
29 | buffer.clear()
30 | }
31 |
32 | override fun setLimit(limit: Int) {
33 | buffer.limit(limit)
34 | }
35 |
36 | override val capacity = buffer.capacity()
37 |
38 | override fun readByte() = byteBuffer.get()
39 |
40 | override fun get(index: Int): Byte = byteBuffer.get(index)
41 |
42 | override fun readByteArray(size: Int) = byteBuffer.toArray(size)
43 |
44 | override fun slice() = JvmBuffer(byteBuffer.slice())
45 |
46 | override fun readShort(): Short = byteBuffer.short
47 |
48 | override fun getShort(index: Int): Short = byteBuffer.getShort(index)
49 |
50 | override fun readInt() = byteBuffer.int
51 |
52 | override fun getInt(index: Int): Int = byteBuffer.getInt(index)
53 |
54 | override fun readLong() = byteBuffer.long
55 |
56 | override fun getLong(index: Int): Long = byteBuffer.getLong(index)
57 |
58 | override fun readString(
59 | length: Int,
60 | charset: Charset,
61 | ): String {
62 | val finalPosition = buffer.position() + length
63 | val readBuffer = byteBuffer.asReadOnlyBuffer()
64 | (readBuffer as Buffer).limit(finalPosition)
65 | val charsetConverted =
66 | when (charset) {
67 | Charset.UTF8 -> Charsets.UTF_8
68 | Charset.UTF16 -> Charsets.UTF_16
69 | Charset.UTF16BigEndian -> Charsets.UTF_16BE
70 | Charset.UTF16LittleEndian -> Charsets.UTF_16LE
71 | Charset.ASCII -> Charsets.US_ASCII
72 | Charset.ISOLatin1 -> Charsets.ISO_8859_1
73 | Charset.UTF32 -> Charsets.UTF_32
74 | Charset.UTF32LittleEndian -> Charsets.UTF_32LE
75 | Charset.UTF32BigEndian -> Charsets.UTF_32BE
76 | }
77 | val decoded =
78 | charsetConverted.newDecoder()
79 | .onMalformedInput(CodingErrorAction.REPORT)
80 | .onUnmappableCharacter(CodingErrorAction.REPORT)
81 | .decode(readBuffer)
82 | buffer.position(finalPosition)
83 | return decoded.toString()
84 | }
85 |
86 | override fun writeByte(byte: Byte): WriteBuffer {
87 | byteBuffer.put(byte)
88 | return this
89 | }
90 |
91 | override fun set(
92 | index: Int,
93 | byte: Byte,
94 | ): WriteBuffer {
95 | byteBuffer.put(index, byte)
96 | return this
97 | }
98 |
99 | override fun writeBytes(
100 | bytes: ByteArray,
101 | offset: Int,
102 | length: Int,
103 | ): WriteBuffer {
104 | byteBuffer.put(bytes, offset, length)
105 | return this
106 | }
107 |
108 | override fun writeShort(short: Short): WriteBuffer {
109 | byteBuffer.putShort(short)
110 | return this
111 | }
112 |
113 | override fun set(
114 | index: Int,
115 | short: Short,
116 | ): WriteBuffer {
117 | byteBuffer.putShort(index, short)
118 | return this
119 | }
120 |
121 | override fun writeInt(int: Int): WriteBuffer {
122 | byteBuffer.putInt(int)
123 | return this
124 | }
125 |
126 | override fun set(
127 | index: Int,
128 | int: Int,
129 | ): WriteBuffer {
130 | byteBuffer.putInt(index, int)
131 | return this
132 | }
133 |
134 | override fun writeLong(long: Long): WriteBuffer {
135 | byteBuffer.putLong(long)
136 | return this
137 | }
138 |
139 | override fun set(
140 | index: Int,
141 | long: Long,
142 | ): WriteBuffer {
143 | byteBuffer.putLong(index, long)
144 | return this
145 | }
146 |
147 | override fun writeString(
148 | text: CharSequence,
149 | charset: Charset,
150 | ): WriteBuffer {
151 | val encoder = charset.toEncoder()
152 | encoder.reset()
153 | encoder.encode(CharBuffer.wrap(text), byteBuffer, true)
154 | return this
155 | }
156 |
157 | override fun write(buffer: ReadBuffer) {
158 | if (buffer is JvmBuffer) {
159 | byteBuffer.put(buffer.byteBuffer)
160 | } else {
161 | byteBuffer.put(buffer.readByteArray(buffer.remaining()))
162 | }
163 | }
164 |
165 | override fun position(newPosition: Int) {
166 | buffer.position(newPosition)
167 | }
168 |
169 | override fun equals(other: Any?): Boolean {
170 | if (other !is PlatformBuffer) return false
171 | if (position() != other.position()) return false
172 | if (limit() != other.limit()) return false
173 | if (capacity != other.capacity) return false
174 | return true
175 | }
176 |
177 | override fun toString() = "Buffer[pos=${position()} lim=${limit()} cap=$capacity]"
178 |
179 | override suspend fun close() {
180 | fileRef?.aClose()
181 | }
182 |
183 | override fun limit() = buffer.limit()
184 |
185 | override fun position() = buffer.position()
186 | }
187 |
188 | suspend fun RandomAccessFile.aClose() =
189 | suspendCoroutine {
190 | try {
191 | // TODO: fix the blocking call
192 | @Suppress("BlockingMethodInNonBlockingContext")
193 | close()
194 | it.resume(Unit)
195 | } catch (e: Throwable) {
196 | it.resumeWithException(e)
197 | }
198 | }
199 |
200 | fun ByteBuffer.toArray(size: Int = remaining()): ByteArray {
201 | return if (hasArray()) {
202 | val result = ByteArray(size)
203 | val buffer = this as Buffer
204 | System.arraycopy(this.array(), buffer.arrayOffset() + buffer.position(), result, 0, size)
205 | buffer.position(buffer.position() + size)
206 | result
207 | } else {
208 | val byteArray = ByteArray(size)
209 | get(byteArray)
210 | byteArray
211 | }
212 | }
213 |
--------------------------------------------------------------------------------
/src/androidMain/kotlin/com/ditchoom/buffer/BufferFactory.kt:
--------------------------------------------------------------------------------
1 | @file:JvmName("BufferFactoryAndroid")
2 |
3 | package com.ditchoom.buffer
4 |
5 | import android.os.Build
6 | import android.os.SharedMemory
7 | import java.nio.ByteBuffer
8 |
9 | actual fun PlatformBuffer.Companion.allocate(
10 | size: Int,
11 | zone: AllocationZone,
12 | byteOrder: ByteOrder,
13 | ): PlatformBuffer {
14 | val byteOrderNative =
15 | when (byteOrder) {
16 | ByteOrder.BIG_ENDIAN -> java.nio.ByteOrder.BIG_ENDIAN
17 | ByteOrder.LITTLE_ENDIAN -> java.nio.ByteOrder.LITTLE_ENDIAN
18 | }
19 | return when (zone) {
20 | AllocationZone.Heap -> JvmBuffer(ByteBuffer.allocate(size).order(byteOrderNative))
21 | AllocationZone.Direct -> JvmBuffer(ByteBuffer.allocateDirect(size).order(byteOrderNative))
22 | AllocationZone.SharedMemory ->
23 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1 && size > 0) {
24 | val sharedMemory = SharedMemory.create(null, size)
25 | val buffer = sharedMemory.mapReadWrite().order(byteOrderNative)
26 | ParcelableSharedMemoryBuffer(buffer, sharedMemory)
27 | } else {
28 | JvmBuffer(ByteBuffer.allocateDirect(size).order(byteOrderNative))
29 | }
30 |
31 | is AllocationZone.Custom -> zone.allocator(size)
32 | }
33 | }
34 |
35 | actual fun PlatformBuffer.Companion.wrap(
36 | array: ByteArray,
37 | byteOrder: ByteOrder,
38 | ): PlatformBuffer {
39 | val byteOrderNative =
40 | when (byteOrder) {
41 | ByteOrder.BIG_ENDIAN -> java.nio.ByteOrder.BIG_ENDIAN
42 | ByteOrder.LITTLE_ENDIAN -> java.nio.ByteOrder.LITTLE_ENDIAN
43 | }
44 | return JvmBuffer(ByteBuffer.wrap(array).order(byteOrderNative))
45 | }
46 |
--------------------------------------------------------------------------------
/src/androidMain/kotlin/com/ditchoom/buffer/CharsetEncoderHelper.kt:
--------------------------------------------------------------------------------
1 | package com.ditchoom.buffer
2 |
3 | import java.nio.charset.CharsetEncoder
4 |
5 | fun Charset.toEncoder(): CharsetEncoder =
6 | when (this) {
7 | Charset.UTF8 -> utf8Encoder
8 | Charset.UTF16 -> utf16Encoder
9 | Charset.UTF16BigEndian -> utf16BEEncoder
10 | Charset.UTF16LittleEndian -> utf16LEEncoder
11 | Charset.ASCII -> utfAsciiEncoder
12 | Charset.ISOLatin1 -> utfISOLatin1Encoder
13 | Charset.UTF32 -> utf32Encoder
14 | Charset.UTF32LittleEndian -> utf32LEEncoder
15 | Charset.UTF32BigEndian -> utf32BEEncoder
16 | }.get()
17 |
18 | internal class DefaultEncoder(private val charset: java.nio.charset.Charset) :
19 | ThreadLocal() {
20 | override fun initialValue(): CharsetEncoder? = charset.newEncoder()
21 |
22 | override fun get(): CharsetEncoder = super.get()!!
23 | }
24 |
25 | internal val utf8Encoder = DefaultEncoder(Charsets.UTF_8)
26 | internal val utf16Encoder = DefaultEncoder(Charsets.UTF_16)
27 | internal val utf16BEEncoder = DefaultEncoder(Charsets.UTF_16BE)
28 | internal val utf16LEEncoder = DefaultEncoder(Charsets.UTF_16LE)
29 | internal val utfAsciiEncoder = DefaultEncoder(Charsets.US_ASCII)
30 | internal val utfISOLatin1Encoder = DefaultEncoder(Charsets.ISO_8859_1)
31 | internal val utf32Encoder = DefaultEncoder(Charsets.UTF_32)
32 | internal val utf32LEEncoder = DefaultEncoder(Charsets.UTF_32LE)
33 | internal val utf32BEEncoder = DefaultEncoder(Charsets.UTF_32BE)
34 |
--------------------------------------------------------------------------------
/src/androidMain/kotlin/com/ditchoom/buffer/JvmBuffer.kt:
--------------------------------------------------------------------------------
1 | package com.ditchoom.buffer
2 |
3 | import android.os.Build
4 | import android.os.Parcel
5 | import android.os.ParcelFileDescriptor
6 | import android.os.Parcelable.Creator
7 | import kotlinx.coroutines.CoroutineName
8 | import kotlinx.coroutines.CoroutineScope
9 | import kotlinx.coroutines.Dispatchers
10 | import kotlinx.coroutines.cancel
11 | import kotlinx.coroutines.launch
12 | import java.io.FileInputStream
13 | import java.io.FileOutputStream
14 | import java.nio.ByteBuffer
15 |
16 | open class JvmBuffer(val buffer: ByteBuffer) : BaseJvmBuffer(buffer) {
17 | override fun describeContents(): Int = 0
18 |
19 | override fun writeToParcel(
20 | dest: Parcel,
21 | flags: Int,
22 | ) {
23 | if (this is ParcelableSharedMemoryBuffer) {
24 | dest.writeByte(1)
25 | return
26 | } else {
27 | dest.writeByte(0)
28 | }
29 | dest.writeInt(byteBuffer.position())
30 | dest.writeInt(byteBuffer.limit())
31 | if (byteBuffer.isDirect) {
32 | dest.writeByte(1)
33 | } else {
34 | dest.writeByte(0)
35 | }
36 | byteBuffer.position(0)
37 | byteBuffer.limit(byteBuffer.capacity())
38 |
39 | val (readFileDescriptor, writeFileDescriptor) =
40 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
41 | ParcelFileDescriptor.createReliablePipe()
42 | } else {
43 | ParcelFileDescriptor.createPipe()
44 | }
45 | readFileDescriptor.writeToParcel(dest, 0)
46 | val scope = CoroutineScope(Dispatchers.IO + CoroutineName("IPC Write Channel Jvm Buffer"))
47 | scope.launch {
48 | FileOutputStream(writeFileDescriptor.fileDescriptor).channel.use { writeChannel ->
49 | writeChannel.write(buffer)
50 | }
51 | writeFileDescriptor.close()
52 | readFileDescriptor.close()
53 | scope.cancel()
54 | }
55 | }
56 |
57 | companion object CREATOR : Creator {
58 | override fun createFromParcel(parcel: Parcel): JvmBuffer {
59 | val p = parcel.dataPosition()
60 | if (parcel.readByte().toInt() == 1) {
61 | parcel.setDataPosition(p)
62 | return ParcelableSharedMemoryBuffer.createFromParcel(parcel)
63 | }
64 | val position = parcel.readInt()
65 | val limit = parcel.readInt()
66 | val isDirect = parcel.readByte() == 1.toByte()
67 | val buffer =
68 | if (isDirect) {
69 | ByteBuffer.allocateDirect(limit)
70 | } else {
71 | ByteBuffer.allocate(limit)
72 | }
73 | ParcelFileDescriptor.CREATOR.createFromParcel(parcel).use { pfd ->
74 | FileInputStream(pfd.fileDescriptor).channel.use { readChannel -> readChannel.read(buffer) }
75 | }
76 | buffer.position(position)
77 | buffer.limit(limit)
78 | return JvmBuffer(buffer)
79 | }
80 |
81 | override fun newArray(size: Int): Array {
82 | return arrayOfNulls(size)
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/androidMain/kotlin/com/ditchoom/buffer/Parcelable.kt:
--------------------------------------------------------------------------------
1 | package com.ditchoom.buffer
2 |
3 | import android.os.Parcelable
4 |
5 | @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
6 | actual typealias Parcelable = Parcelable
7 |
--------------------------------------------------------------------------------
/src/androidMain/kotlin/com/ditchoom/buffer/ParcelableSharedMemoryBuffer.kt:
--------------------------------------------------------------------------------
1 | package com.ditchoom.buffer
2 |
3 | import android.annotation.TargetApi
4 | import android.os.Build
5 | import android.os.Parcel
6 | import android.os.Parcelable
7 | import android.os.SharedMemory
8 | import java.nio.ByteBuffer
9 |
10 | @TargetApi(Build.VERSION_CODES.O_MR1)
11 | class ParcelableSharedMemoryBuffer(buffer: ByteBuffer, private val sharedMemory: SharedMemory) :
12 | JvmBuffer(buffer), Parcelable {
13 | override fun describeContents(): Int = Parcelable.CONTENTS_FILE_DESCRIPTOR
14 |
15 | override fun writeToParcel(
16 | dest: Parcel,
17 | flags: Int,
18 | ) {
19 | super.writeToParcel(dest, flags)
20 | dest.writeParcelable(sharedMemory, flags)
21 | dest.writeInt(buffer.position())
22 | dest.writeInt(buffer.limit())
23 | }
24 |
25 | override suspend fun close() {
26 | super.close()
27 | sharedMemory.close()
28 | }
29 |
30 | companion object CREATOR : Parcelable.Creator {
31 | override fun createFromParcel(parcel: Parcel): ParcelableSharedMemoryBuffer {
32 | parcel.readByte() // ignore this first byte
33 | val sharedMemory =
34 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
35 | parcel.readParcelable(this::class.java.classLoader, SharedMemory::class.java)
36 | } else {
37 | @Suppress("DEPRECATION")
38 | parcel.readParcelable(this::class.java.classLoader)
39 | }!!
40 | val buffer =
41 | ParcelableSharedMemoryBuffer(sharedMemory.mapReadWrite(), sharedMemory)
42 | buffer.position(parcel.readInt())
43 | buffer.setLimit(parcel.readInt())
44 | return buffer
45 | }
46 |
47 | override fun newArray(size: Int): Array = arrayOfNulls(size)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/appleMain/kotlin/com/ditchoom/buffer/BufferFactory.kt:
--------------------------------------------------------------------------------
1 | @file:OptIn(kotlinx.cinterop.ExperimentalForeignApi::class, kotlinx.cinterop.BetaInteropApi::class)
2 |
3 | package com.ditchoom.buffer
4 |
5 | import kotlinx.cinterop.UnsafeNumber
6 | import kotlinx.cinterop.convert
7 | import platform.Foundation.NSMutableData
8 | import platform.Foundation.create
9 |
10 | actual fun PlatformBuffer.Companion.allocate(
11 | size: Int,
12 | zone: AllocationZone,
13 | byteOrder: ByteOrder,
14 | ): PlatformBuffer {
15 | if (zone is AllocationZone.Custom) {
16 | return zone.allocator(size)
17 | }
18 |
19 | @OptIn(UnsafeNumber::class)
20 | return MutableDataBuffer(NSMutableData.create(length = size.convert())!!, byteOrder = byteOrder)
21 | }
22 |
23 | actual fun PlatformBuffer.Companion.wrap(
24 | array: ByteArray,
25 | byteOrder: ByteOrder,
26 | ): PlatformBuffer = MutableDataBuffer.wrap(array, byteOrder)
27 |
--------------------------------------------------------------------------------
/src/appleMain/kotlin/com/ditchoom/buffer/CharsetEncoderHelper.kt:
--------------------------------------------------------------------------------
1 | package com.ditchoom.buffer
2 |
3 | import kotlinx.cinterop.UnsafeNumber
4 | import platform.Foundation.NSASCIIStringEncoding
5 | import platform.Foundation.NSISOLatin1StringEncoding
6 | import platform.Foundation.NSStringEncoding
7 | import platform.Foundation.NSUTF16BigEndianStringEncoding
8 | import platform.Foundation.NSUTF16LittleEndianStringEncoding
9 | import platform.Foundation.NSUTF16StringEncoding
10 | import platform.Foundation.NSUTF32BigEndianStringEncoding
11 | import platform.Foundation.NSUTF32LittleEndianStringEncoding
12 | import platform.Foundation.NSUTF32StringEncoding
13 | import platform.Foundation.NSUTF8StringEncoding
14 |
15 | @OptIn(UnsafeNumber::class)
16 | fun Charset.toEncoding(): NSStringEncoding =
17 | when (this) {
18 | Charset.UTF8 -> NSUTF8StringEncoding
19 | Charset.UTF16 -> NSUTF16StringEncoding
20 | Charset.UTF16BigEndian -> NSUTF16BigEndianStringEncoding
21 | Charset.UTF16LittleEndian -> NSUTF16LittleEndianStringEncoding
22 | Charset.ASCII -> NSASCIIStringEncoding
23 | Charset.ISOLatin1 -> NSISOLatin1StringEncoding
24 | Charset.UTF32 -> NSUTF32StringEncoding
25 | Charset.UTF32LittleEndian -> NSUTF32LittleEndianStringEncoding
26 | Charset.UTF32BigEndian -> NSUTF32BigEndianStringEncoding
27 | }
28 |
--------------------------------------------------------------------------------
/src/appleMain/kotlin/com/ditchoom/buffer/DataBuffer.kt:
--------------------------------------------------------------------------------
1 | package com.ditchoom.buffer
2 |
3 | import kotlinx.cinterop.ByteVar
4 | import kotlinx.cinterop.CPointer
5 | import kotlinx.cinterop.UnsafeNumber
6 | import kotlinx.cinterop.convert
7 | import kotlinx.cinterop.get
8 | import kotlinx.cinterop.readBytes
9 | import platform.Foundation.NSData
10 | import platform.Foundation.NSMakeRange
11 | import platform.Foundation.NSString
12 | import platform.Foundation.create
13 | import platform.Foundation.isEqualToData
14 | import platform.Foundation.subdataWithRange
15 |
16 | @OptIn(kotlinx.cinterop.ExperimentalForeignApi::class, UnsafeNumber::class)
17 | open class DataBuffer(
18 | val data: NSData,
19 | override val byteOrder: ByteOrder,
20 | ) : ReadBuffer, SuspendCloseable, Parcelable {
21 | protected var position: Int = 0
22 | protected var limit: Int = data.length.toInt()
23 | open val capacity: Int = data.length.toInt()
24 |
25 | @Suppress("UNCHECKED_CAST")
26 | private val bytePointer = this.data.bytes as CPointer
27 |
28 | override fun resetForRead() {
29 | limit = position
30 | position = 0
31 | }
32 |
33 | override fun setLimit(limit: Int) {
34 | this.limit = limit
35 | }
36 |
37 | override fun readByte() = bytePointer[position++]
38 |
39 | override fun get(index: Int): Byte = bytePointer[index]
40 |
41 | override fun slice(): ReadBuffer {
42 | val range = NSMakeRange(position.convert(), (limit - position).convert())
43 | return DataBuffer(data.subdataWithRange(range), byteOrder = byteOrder)
44 | }
45 |
46 | override fun readByteArray(size: Int): ByteArray {
47 | if (size < 1) {
48 | return ByteArray(0)
49 | }
50 | val subDataRange = NSMakeRange(position.convert(), size.convert())
51 |
52 | @Suppress("UNCHECKED_CAST")
53 | val subDataBytePointer = data.subdataWithRange(subDataRange).bytes as CPointer
54 | val result = subDataBytePointer.readBytes(size)
55 | position += size
56 | return result
57 | }
58 |
59 | override fun readString(
60 | length: Int,
61 | charset: Charset,
62 | ): String {
63 | if (length == 0) return ""
64 | val subdata = data.subdataWithRange(NSMakeRange(position.convert(), length.convert()))
65 | val stringEncoding = charset.toEncoding()
66 |
67 | @Suppress("CAST_NEVER_SUCCEEDS")
68 | @OptIn(kotlinx.cinterop.BetaInteropApi::class)
69 | val string = NSString.create(subdata, stringEncoding) as String
70 | position += length
71 | return string
72 | }
73 |
74 | override fun limit() = limit
75 |
76 | override fun position() = position
77 |
78 | override fun position(newPosition: Int) {
79 | position = newPosition
80 | }
81 |
82 | override suspend fun close() = Unit
83 |
84 | override fun equals(other: Any?): Boolean {
85 | if (this === other) return true
86 | if (other !is DataBuffer) return false
87 | if (position != other.position) return false
88 | if (limit != other.limit) return false
89 | if (capacity != other.capacity) return false
90 | if (!data.isEqualToData(other.data)) return false
91 | return true
92 | }
93 |
94 | override fun hashCode(): Int {
95 | var result = data.hashCode()
96 | result = 31 * result + position.hashCode()
97 | result = 31 * result + limit.hashCode()
98 | result = 31 * result + capacity.hashCode()
99 | result = 31 * result + byteOrder.hashCode()
100 | result = 31 * result + bytePointer.hashCode()
101 | return result
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/appleMain/kotlin/com/ditchoom/buffer/MutableDataBuffer.kt:
--------------------------------------------------------------------------------
1 | package com.ditchoom.buffer
2 |
3 | import kotlinx.cinterop.ByteVar
4 | import kotlinx.cinterop.CPointer
5 | import kotlinx.cinterop.UnsafeNumber
6 | import kotlinx.cinterop.addressOf
7 | import kotlinx.cinterop.convert
8 | import kotlinx.cinterop.set
9 | import kotlinx.cinterop.usePinned
10 | import platform.CoreFoundation.CFDataRef
11 | import platform.Foundation.CFBridgingRelease
12 | import platform.Foundation.CFBridgingRetain
13 | import platform.Foundation.NSData
14 | import platform.Foundation.NSMakeRange
15 | import platform.Foundation.NSMutableData
16 | import platform.Foundation.NSString
17 | import platform.Foundation.dataUsingEncoding
18 | import platform.Foundation.dataWithBytesNoCopy
19 | import platform.Foundation.replaceBytesInRange
20 | import platform.Foundation.subdataWithRange
21 |
22 | @OptIn(kotlinx.cinterop.ExperimentalForeignApi::class, UnsafeNumber::class)
23 | class MutableDataBuffer(
24 | dataRef: NSData,
25 | override val byteOrder: ByteOrder,
26 | private val backingArray: ByteArray? = null,
27 | ) : DataBuffer(dataRef, byteOrder), PlatformBuffer {
28 | val mutableData = dataRef as? NSMutableData
29 |
30 | @Suppress("UNCHECKED_CAST")
31 | private val bytePointer = mutableData?.mutableBytes as? CPointer
32 |
33 | init {
34 | check(
35 | (bytePointer != null && backingArray == null) ||
36 | (bytePointer == null && backingArray != null),
37 | )
38 | }
39 |
40 | private fun writeByteInternal(
41 | index: Int,
42 | byte: Byte,
43 | ) {
44 | backingArray?.set(index, byte)
45 | bytePointer?.set(index, byte)
46 | }
47 |
48 | override fun resetForWrite() {
49 | position = 0
50 | limit = data.length.toInt()
51 | }
52 |
53 | override fun writeByte(byte: Byte): WriteBuffer {
54 | writeByteInternal(position++, byte)
55 | return this
56 | }
57 |
58 | override fun set(
59 | index: Int,
60 | byte: Byte,
61 | ): WriteBuffer {
62 | writeByteInternal(index, byte)
63 | return this
64 | }
65 |
66 | override fun writeBytes(
67 | bytes: ByteArray,
68 | offset: Int,
69 | length: Int,
70 | ): WriteBuffer {
71 | if (length < 1) {
72 | return this
73 | }
74 | if (mutableData != null) {
75 | val range = NSMakeRange(position.convert(), length.convert())
76 | bytes.usePinned { pin ->
77 | mutableData.replaceBytesInRange(range, pin.addressOf(offset))
78 | }
79 | position += length
80 | } else if (backingArray != null) {
81 | bytes.copyInto(backingArray, position, offset, offset + length)
82 | position += length
83 | }
84 | return this
85 | }
86 |
87 | override fun write(buffer: ReadBuffer) {
88 | if (buffer is DataBuffer && mutableData != null) {
89 | val bytesToCopySize = buffer.remaining()
90 | val otherSubdata =
91 | buffer.data.subdataWithRange(
92 | NSMakeRange(
93 | buffer.position().convert(),
94 | bytesToCopySize.convert(),
95 | ),
96 | )
97 | mutableData.replaceBytesInRange(
98 | NSMakeRange(
99 | position.convert(),
100 | bytesToCopySize.convert(),
101 | ),
102 | otherSubdata.bytes,
103 | )
104 | position += bytesToCopySize
105 | buffer.position(buffer.position() + bytesToCopySize)
106 | } else {
107 | val remainingByteArray = buffer.readByteArray(buffer.remaining())
108 | writeBytes(remainingByteArray)
109 | }
110 | }
111 |
112 | override fun writeString(
113 | text: CharSequence,
114 | charset: Charset,
115 | ): WriteBuffer {
116 | val string =
117 | if (text is String) {
118 | text as NSString
119 | } else {
120 | @Suppress("CAST_NEVER_SUCCEEDS")
121 | text.toString() as NSString
122 | }
123 | val charsetEncoding = charset.toEncoding()
124 | write(DataBuffer(string.dataUsingEncoding(charsetEncoding)!!, byteOrder))
125 | return this
126 | }
127 |
128 | companion object {
129 | fun wrap(
130 | byteArray: ByteArray,
131 | byteOrder: ByteOrder,
132 | ) = byteArray.useNSDataRef {
133 | MutableDataBuffer(it, byteOrder, byteArray)
134 | }
135 | }
136 | }
137 |
138 | private fun ByteArray.useNSDataRef(block: (NSData) -> T): T {
139 | @OptIn(kotlinx.cinterop.ExperimentalForeignApi::class, UnsafeNumber::class)
140 | return usePinned { pin ->
141 | val bytesPointer =
142 | when {
143 | isNotEmpty() -> pin.addressOf(0)
144 | else -> null
145 | }
146 | val nsData =
147 | NSData.dataWithBytesNoCopy(
148 | bytes = bytesPointer,
149 | length = size.convert(),
150 | freeWhenDone = false,
151 | )
152 |
153 | @Suppress("UNCHECKED_CAST")
154 | val typeRef = CFBridgingRetain(nsData) as CFDataRef
155 |
156 | try {
157 | block(nsData)
158 | } finally {
159 | CFBridgingRelease(typeRef)
160 | }
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/com/ditchoom/buffer/AllocationZone.kt:
--------------------------------------------------------------------------------
1 | package com.ditchoom.buffer
2 |
3 | sealed class AllocationZone {
4 | /** Allocates a buffer in the java heap.*/
5 | object Heap : AllocationZone()
6 |
7 | /** Allocates a direct buffer in native memory.*/
8 | object Direct : AllocationZone()
9 |
10 | /**
11 | * Allocates a direct buffer in native shared memory.
12 | * In Javascript it will allocate a SharedArrayBuffer
13 | * Requires Android API 27, Oreo MR1. Otherwise defaults to [Direct].
14 | ***/
15 | object SharedMemory : AllocationZone()
16 |
17 | class Custom(val allocator: (Int) -> PlatformBuffer) : AllocationZone()
18 | }
19 |
20 | @Deprecated("Use SharedMemory")
21 | typealias AndroidSharedMemory = AllocationZone.SharedMemory
22 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/com/ditchoom/buffer/BufferFactory.kt:
--------------------------------------------------------------------------------
1 | package com.ditchoom.buffer
2 |
3 | import kotlin.math.roundToInt
4 |
5 | expect fun PlatformBuffer.Companion.allocate(
6 | size: Int,
7 | zone: AllocationZone = AllocationZone.Heap,
8 | byteOrder: ByteOrder = ByteOrder.BIG_ENDIAN,
9 | ): PlatformBuffer
10 |
11 | expect fun PlatformBuffer.Companion.wrap(
12 | array: ByteArray,
13 | byteOrder: ByteOrder = ByteOrder.BIG_ENDIAN,
14 | ): PlatformBuffer
15 |
16 | @Deprecated(
17 | "Use toReadBuffer instead",
18 | ReplaceWith("toReadBuffer(Charset.UTF8, zone)", "com.ditchoom.buffer.Charset"),
19 | )
20 | fun String.toBuffer(zone: AllocationZone = AllocationZone.Heap): ReadBuffer = toReadBuffer(Charset.UTF8, zone)
21 |
22 | fun CharSequence.maxBufferSize(charset: Charset): Int {
23 | return (charset.maxBytesPerChar * this.length).roundToInt()
24 | }
25 |
26 | fun String.toReadBuffer(
27 | charset: Charset = Charset.UTF8,
28 | zone: AllocationZone = AllocationZone.Heap,
29 | ): ReadBuffer {
30 | if (this == "") {
31 | return ReadBuffer.EMPTY_BUFFER
32 | }
33 | val maxBytes = maxBufferSize(charset)
34 | val buffer = PlatformBuffer.allocate(maxBytes, zone)
35 | buffer.writeString(this, charset)
36 | buffer.resetForRead()
37 | return buffer.slice()
38 | }
39 |
40 | fun CharSequence.utf8Length(): Int {
41 | var count = 0
42 | var i = 0
43 | val len = length
44 | while (i < len) {
45 | val ch = get(i)
46 | if (ch.code <= 0x7F) {
47 | count++
48 | } else if (ch.code <= 0x7FF) {
49 | count += 2
50 | } else if (ch >= '\uD800' && ch.code < '\uDBFF'.code + 1) {
51 | count += 4
52 | ++i
53 | } else {
54 | count += 3
55 | }
56 | i++
57 | }
58 | return count
59 | }
60 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/com/ditchoom/buffer/ByteOrder.kt:
--------------------------------------------------------------------------------
1 | package com.ditchoom.buffer
2 |
3 | enum class ByteOrder {
4 | BIG_ENDIAN,
5 | LITTLE_ENDIAN,
6 | }
7 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/com/ditchoom/buffer/Charset.kt:
--------------------------------------------------------------------------------
1 | package com.ditchoom.buffer
2 |
3 | enum class Charset(val averageBytesPerChar: Float, val maxBytesPerChar: Float) {
4 | UTF8(1.1f, 4f),
5 | UTF16(2f, 4f),
6 | UTF16BigEndian(2f, 4f),
7 | UTF16LittleEndian(2f, 4f),
8 | ASCII(1f, 1f),
9 | ISOLatin1(1f, 1f), // aka ISO/IEC 8859-1
10 | UTF32(4f, 4f),
11 | UTF32LittleEndian(4f, 4f),
12 | UTF32BigEndian(4f, 4f),
13 | }
14 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/com/ditchoom/buffer/FragmentedReadBuffer.kt:
--------------------------------------------------------------------------------
1 | package com.ditchoom.buffer
2 |
3 | /**
4 | * Provides a single ReadBuffer interface that delegates to multiple buffers.
5 | * While reading from a buffer sometimes you might need more data to complete the decoding operation. This class will
6 | * handle reading from multiple fragmented buffers in memory and provide a simple read api.
7 | */
8 | class FragmentedReadBuffer(
9 | private val first: ReadBuffer,
10 | private val second: ReadBuffer,
11 | ) : ReadBuffer {
12 | private val firstInitialLimit = first.limit()
13 | private val secondInitialLimit = second.limit()
14 | private var currentPosition = 0
15 | private var currentLimit = firstInitialLimit + secondInitialLimit
16 |
17 | override val byteOrder: ByteOrder
18 | get() {
19 | throw IllegalStateException("Byte order is undefined for FragmentedReadBuffer")
20 | }
21 |
22 | override fun setLimit(limit: Int) {
23 | if (limit <= firstInitialLimit + secondInitialLimit) {
24 | currentLimit = limit
25 | }
26 | }
27 |
28 | override fun limit() = currentLimit
29 |
30 | override fun position() = currentPosition
31 |
32 | override fun position(newPosition: Int) {
33 | currentPosition = newPosition
34 | }
35 |
36 | override fun resetForRead() {
37 | currentPosition = 0
38 | first.resetForRead()
39 | second.resetForRead()
40 | }
41 |
42 | override fun readByte(): Byte {
43 | return if (currentPosition++ < firstInitialLimit) {
44 | first.readByte()
45 | } else {
46 | second.readByte()
47 | }
48 | }
49 |
50 | override fun get(index: Int): Byte {
51 | return if (currentPosition < firstInitialLimit) {
52 | first.readByte()
53 | } else {
54 | second.readByte()
55 | }
56 | }
57 |
58 | private fun readSizeIntoBuffer(
59 | size: Int,
60 | block: (ReadBuffer) -> T,
61 | ): T {
62 | val buffer =
63 | if (currentPosition < firstInitialLimit && currentPosition + size <= firstInitialLimit) {
64 | block(first)
65 | } else if (currentPosition < firstInitialLimit && currentPosition + size > firstInitialLimit) {
66 | val firstChunkSize = firstInitialLimit - currentPosition
67 | val secondChunkSize = size - firstChunkSize
68 | val secondBufferLimit = second.limit()
69 | second.setLimit(second.position() + secondChunkSize)
70 | val buffer = PlatformBuffer.allocate(size)
71 | buffer.write(first)
72 | buffer.write(second)
73 | second.setLimit(secondBufferLimit)
74 | buffer.resetForRead()
75 | block(buffer)
76 | } else {
77 | block(second)
78 | }
79 | currentPosition += size
80 | return buffer
81 | }
82 |
83 | override fun slice(): ReadBuffer {
84 | if (first.position() == 0 && first.limit() == 0) {
85 | return second
86 | } else if (second.position() == 0 && second.limit() == 0) {
87 | return first
88 | }
89 | val first = first.slice()
90 | val second = second.slice()
91 | val buffer = PlatformBuffer.allocate(first.limit() + second.limit())
92 | buffer.write(first)
93 | buffer.write(second)
94 | buffer.resetForRead()
95 | return buffer
96 | }
97 |
98 | override fun readByteArray(size: Int): ByteArray {
99 | return readSizeIntoBuffer(size) { it.readByteArray(size) }
100 | }
101 |
102 | override fun readShort() = readSizeIntoBuffer(Short.SIZE_BYTES) { it.readShort() }
103 |
104 | override fun readInt() = readSizeIntoBuffer(Int.SIZE_BYTES) { it.readInt() }
105 |
106 | override fun readLong() = readSizeIntoBuffer(ULong.SIZE_BYTES) { it.readLong() }
107 |
108 | override fun readString(
109 | length: Int,
110 | charset: Charset,
111 | ): String {
112 | return readSizeIntoBuffer(length) { it.readString(length, charset) }
113 | }
114 |
115 | override fun readUtf8Line(): CharSequence {
116 | if (currentPosition < firstInitialLimit) {
117 | val initialFirstPosition = first.position()
118 | val firstUtf8 = first.readUtf8Line()
119 | val bytesRead = first.position() - initialFirstPosition
120 | return if (firstUtf8.toString().utf8Length().toLong() == bytesRead.toLong()) {
121 | // read the entire string, check the second one
122 | currentPosition = firstInitialLimit
123 | val secondInitialPosition = second.position()
124 | val secondLine = second.readUtf8Line()
125 | currentPosition += second.position() - secondInitialPosition
126 | StringBuilder(firstUtf8).append(secondLine)
127 | } else {
128 | firstUtf8
129 | }
130 | } else {
131 | val secondInitialPosition = second.position()
132 | val line = second.readUtf8Line()
133 | currentPosition += second.position() - secondInitialPosition
134 | return line
135 | }
136 | }
137 |
138 | fun getBuffers(out: MutableList) {
139 | if (first is FragmentedReadBuffer) {
140 | first.getBuffers(out)
141 | } else if (first is PlatformBuffer) {
142 | out += first
143 | }
144 | if (second is FragmentedReadBuffer) {
145 | second.getBuffers(out)
146 | } else if (second is PlatformBuffer) {
147 | out += second
148 | }
149 | }
150 |
151 | fun toSingleBuffer(zone: AllocationZone = AllocationZone.Heap): PlatformBuffer {
152 | val firstLimit = firstInitialLimit
153 | val secondLimit = secondInitialLimit
154 | val initialPosition = position()
155 | val buffer = PlatformBuffer.allocate(firstLimit + secondLimit - initialPosition, zone)
156 | walk {
157 | buffer.write(it)
158 | }
159 | buffer.position(initialPosition)
160 | return buffer
161 | }
162 |
163 | fun walk(visitor: (ReadBuffer) -> Unit) {
164 | if (first is FragmentedReadBuffer) {
165 | first.walk(visitor)
166 | } else {
167 | visitor(first)
168 | }
169 | if (second is FragmentedReadBuffer) {
170 | second.walk(visitor)
171 | } else {
172 | visitor(second)
173 | }
174 | }
175 | }
176 |
177 | fun List.toComposableBuffer(): ReadBuffer {
178 | if (isEmpty()) {
179 | return ReadBuffer.EMPTY_BUFFER
180 | }
181 | if (size == 1) {
182 | return first()
183 | }
184 | if (size == 2) {
185 | return FragmentedReadBuffer(first(), get(1))
186 | }
187 | val half = size / 2
188 | return FragmentedReadBuffer(
189 | subList(0, half).toComposableBuffer(),
190 | subList(half, size).toComposableBuffer(),
191 | )
192 | }
193 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/com/ditchoom/buffer/Parcelable.kt:
--------------------------------------------------------------------------------
1 | package com.ditchoom.buffer
2 |
3 | @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
4 | expect interface Parcelable
5 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/com/ditchoom/buffer/PlatformBuffer.kt:
--------------------------------------------------------------------------------
1 | package com.ditchoom.buffer
2 |
3 | interface PlatformBuffer : ReadBuffer, WriteBuffer, SuspendCloseable, Parcelable {
4 | val capacity: Int
5 |
6 | companion object
7 | }
8 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/com/ditchoom/buffer/PositionBuffer.kt:
--------------------------------------------------------------------------------
1 | package com.ditchoom.buffer
2 |
3 | interface PositionBuffer {
4 | val byteOrder: ByteOrder
5 |
6 | fun setLimit(limit: Int)
7 |
8 | fun limit(): Int
9 |
10 | fun position(): Int
11 |
12 | fun position(newPosition: Int)
13 |
14 | fun remaining() = limit() - position()
15 |
16 | fun hasRemaining() = position() < limit()
17 | }
18 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/com/ditchoom/buffer/ReadBuffer.kt:
--------------------------------------------------------------------------------
1 | package com.ditchoom.buffer
2 |
3 | interface ReadBuffer : PositionBuffer {
4 | fun resetForRead()
5 |
6 | fun readByte(): Byte
7 |
8 | operator fun get(index: Int): Byte
9 |
10 | // slice does not change the position
11 | fun slice(): ReadBuffer
12 |
13 | fun readBytes(size: Int): ReadBuffer {
14 | if (size < 1) {
15 | return EMPTY_BUFFER
16 | }
17 | val oldLimit = limit()
18 | val oldPosition = position()
19 | setLimit(position() + size)
20 | val bytes = slice()
21 | position(oldPosition + size)
22 | setLimit(oldLimit)
23 | return bytes
24 | }
25 |
26 | fun readByteArray(size: Int): ByteArray
27 |
28 | fun readUnsignedByte(): UByte = readByte().toUByte()
29 |
30 | fun getUnsignedByte(index: Int): UByte = get(index).toUByte()
31 |
32 | fun readShort(): Short = readNumberWithByteSize(Short.SIZE_BYTES).toShort()
33 |
34 | fun getShort(index: Int): Short = getNumberWithStartIndexAndByteSize(index, Short.SIZE_BYTES).toShort()
35 |
36 | fun readUnsignedShort(): UShort = readShort().toUShort()
37 |
38 | fun getUnsignedShort(index: Int): UShort = getShort(index).toUShort()
39 |
40 | fun readInt(): Int = readNumberWithByteSize(Int.SIZE_BYTES).toInt()
41 |
42 | fun getInt(index: Int): Int = getNumberWithStartIndexAndByteSize(index, Int.SIZE_BYTES).toInt()
43 |
44 | fun readUnsignedInt(): UInt = readInt().toUInt()
45 |
46 | fun getUnsignedInt(index: Int): UInt = getInt(index).toUInt()
47 |
48 | fun readFloat(): Float = Float.fromBits(readInt())
49 |
50 | fun getFloat(index: Int): Float = Float.fromBits(getInt(index))
51 |
52 | fun readLong(): Long = readNumberWithByteSize(Long.SIZE_BYTES)
53 |
54 | fun getLong(index: Int): Long = getNumberWithStartIndexAndByteSize(index, Long.SIZE_BYTES)
55 |
56 | fun readUnsignedLong(): ULong = readLong().toULong()
57 |
58 | fun getUnsignedLong(index: Int): ULong = getLong(index).toULong()
59 |
60 | fun readDouble(): Double = Double.fromBits(readLong())
61 |
62 | fun getDouble(index: Int): Double = Double.fromBits(getLong(index))
63 |
64 | fun readString(
65 | length: Int,
66 | charset: Charset = Charset.UTF8,
67 | ): String
68 |
69 | @Deprecated(
70 | "Use readString instead",
71 | ReplaceWith("readString(bytes, Charset.UTF8)", "com.ditchoom.buffer.Charset"),
72 | )
73 | fun readUtf8(bytes: UInt): CharSequence = readString(bytes.toInt(), Charset.UTF8)
74 |
75 | @Deprecated(
76 | "Use readString instead",
77 | ReplaceWith("readString(bytes, Charset.UTF8)", "com.ditchoom.buffer.Charset"),
78 | )
79 | fun readUtf8(bytes: Int): CharSequence = readString(bytes, Charset.UTF8)
80 |
81 | fun readUtf8Line(): CharSequence {
82 | val initialPosition = position()
83 | var lastByte: Byte = 0
84 | var currentByte: Byte = 0
85 | var bytesRead = 0
86 | while (remaining() > 0) {
87 | lastByte = currentByte
88 | currentByte = readByte()
89 | bytesRead++
90 | if (currentByte == newLine[1]) {
91 | break
92 | }
93 | }
94 | val carriageFeedPositionIncrement =
95 | if (lastByte == newLine[0] && currentByte == newLine[1]) {
96 | 2
97 | } else if (currentByte == newLine[1]) {
98 | 1
99 | } else {
100 | 0
101 | }
102 |
103 | val bytesToRead = bytesRead - carriageFeedPositionIncrement
104 | position(initialPosition)
105 | val result = readString(bytesToRead, Charset.UTF8)
106 | position(position() + carriageFeedPositionIncrement)
107 | return result
108 | }
109 |
110 | fun readNumberWithByteSize(numberOfBytes: Int): Long {
111 | check(numberOfBytes in 1..8) { "byte size out of range" }
112 | val byteSizeRange =
113 | when (byteOrder) {
114 | ByteOrder.LITTLE_ENDIAN -> 0 until numberOfBytes
115 | ByteOrder.BIG_ENDIAN -> numberOfBytes - 1 downTo 0
116 | }
117 | var number = 0L
118 | for (i in byteSizeRange) {
119 | val bitIndex = i * 8
120 | number = readByte().toLong() and 0xff shl bitIndex or number
121 | }
122 | return number
123 | }
124 |
125 | fun getNumberWithStartIndexAndByteSize(
126 | startIndex: Int,
127 | numberOfBytes: Int,
128 | ): Long {
129 | check(numberOfBytes in 1..8) { "byte size out of range" }
130 | val byteSizeRange =
131 | when (byteOrder) {
132 | ByteOrder.LITTLE_ENDIAN -> 0 until numberOfBytes
133 | ByteOrder.BIG_ENDIAN -> numberOfBytes - 1 downTo 0
134 | }
135 | var number = 0L
136 | var index = startIndex
137 | for (i in byteSizeRange) {
138 | val bitIndex = i * 8
139 | number = get(index++).toLong() and 0xff shl bitIndex or number
140 | }
141 | return number
142 | }
143 |
144 | companion object {
145 | val newLine = "\r\n".encodeToByteArray()
146 | val EMPTY_BUFFER = PlatformBuffer.allocate(0)
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/com/ditchoom/buffer/SuspendCloseable.kt:
--------------------------------------------------------------------------------
1 | package com.ditchoom.buffer
2 |
3 | interface SuspendCloseable {
4 | suspend fun close()
5 | }
6 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/com/ditchoom/buffer/TransformedReadBuffer.kt:
--------------------------------------------------------------------------------
1 | package com.ditchoom.buffer
2 |
3 | class TransformedReadBuffer(val origin: ReadBuffer, val transformer: ((Int, Byte) -> Byte)) :
4 | ReadBuffer {
5 | override val byteOrder: ByteOrder = origin.byteOrder
6 |
7 | override fun limit() = origin.limit()
8 |
9 | override fun setLimit(limit: Int) {
10 | origin.setLimit(limit)
11 | }
12 |
13 | override fun position() = origin.position()
14 |
15 | override fun position(newPosition: Int) = origin.position(newPosition)
16 |
17 | override fun resetForRead() = origin.resetForRead()
18 |
19 | override fun readByte() = transformer(position(), origin.readByte())
20 |
21 | override fun get(index: Int): Byte = transformer(position(), origin[index])
22 |
23 | override fun slice(): ReadBuffer = origin.slice()
24 |
25 | override fun readByteArray(size: Int): ByteArray {
26 | val byteArray = origin.readByteArray(size)
27 | for (position in byteArray.indices) {
28 | byteArray[position] = transformer(position, byteArray[position])
29 | }
30 | return byteArray
31 | }
32 |
33 | override fun readShort(): Short {
34 | val buffer = PlatformBuffer.allocate(Short.SIZE_BYTES)
35 | buffer.writeShort(origin.readShort())
36 | buffer.resetForRead()
37 | val byte1 = transformer(position(), buffer.readByte())
38 | val byte2 = transformer(position(), buffer.readByte())
39 | buffer.resetForWrite()
40 | buffer.writeByte(byte1)
41 | buffer.writeByte(byte2)
42 | buffer.resetForRead()
43 | return buffer.readShort()
44 | }
45 |
46 | override fun getShort(index: Int): Short {
47 | val buffer = PlatformBuffer.allocate(Short.SIZE_BYTES)
48 | buffer.writeShort(origin.getShort(index))
49 | buffer.resetForRead()
50 | val byte1 = transformer(position(), buffer.readByte())
51 | val byte2 = transformer(position(), buffer.readByte())
52 | buffer.resetForWrite()
53 | buffer.writeByte(byte1)
54 | buffer.writeByte(byte2)
55 | buffer.resetForRead()
56 | return buffer.readShort()
57 | }
58 |
59 | override fun readInt(): Int {
60 | val buffer = PlatformBuffer.allocate(Int.SIZE_BYTES)
61 | buffer.writeInt(origin.readInt())
62 | buffer.resetForRead()
63 | val byte1 = transformer(position(), buffer.readByte())
64 | val byte2 = transformer(position(), buffer.readByte())
65 | val byte3 = transformer(position(), buffer.readByte())
66 | val byte4 = transformer(position(), buffer.readByte())
67 | buffer.resetForWrite()
68 | buffer.writeByte(byte1)
69 | buffer.writeByte(byte2)
70 | buffer.writeByte(byte3)
71 | buffer.writeByte(byte4)
72 | buffer.resetForRead()
73 | return buffer.readInt()
74 | }
75 |
76 | override fun getInt(index: Int): Int {
77 | val buffer = PlatformBuffer.allocate(Int.SIZE_BYTES)
78 | buffer.writeInt(origin.getInt(index))
79 | buffer.resetForRead()
80 | val byte1 = transformer(position(), buffer.readByte())
81 | val byte2 = transformer(position(), buffer.readByte())
82 | val byte3 = transformer(position(), buffer.readByte())
83 | val byte4 = transformer(position(), buffer.readByte())
84 | buffer.resetForWrite()
85 | buffer.writeByte(byte1)
86 | buffer.writeByte(byte2)
87 | buffer.writeByte(byte3)
88 | buffer.writeByte(byte4)
89 | buffer.resetForRead()
90 | return buffer.readInt()
91 | }
92 |
93 | override fun readLong(): Long {
94 | val buffer = PlatformBuffer.allocate(Long.SIZE_BYTES)
95 | buffer.writeLong(origin.readLong())
96 | buffer.resetForRead()
97 | val byte1 = transformer(position(), buffer.readByte())
98 | val byte2 = transformer(position(), buffer.readByte())
99 | val byte3 = transformer(position(), buffer.readByte())
100 | val byte4 = transformer(position(), buffer.readByte())
101 | val byte5 = transformer(position(), buffer.readByte())
102 | val byte6 = transformer(position(), buffer.readByte())
103 | val byte7 = transformer(position(), buffer.readByte())
104 | val byte8 = transformer(position(), buffer.readByte())
105 | buffer.resetForWrite()
106 | buffer.writeByte(byte1)
107 | buffer.writeByte(byte2)
108 | buffer.writeByte(byte3)
109 | buffer.writeByte(byte4)
110 | buffer.writeByte(byte5)
111 | buffer.writeByte(byte6)
112 | buffer.writeByte(byte7)
113 | buffer.writeByte(byte8)
114 | buffer.resetForRead()
115 | return buffer.readLong()
116 | }
117 |
118 | override fun getLong(index: Int): Long {
119 | val buffer = PlatformBuffer.allocate(Long.SIZE_BYTES)
120 | buffer.writeLong(origin.getLong(index))
121 | buffer.resetForRead()
122 | val byte1 = transformer(position(), buffer.readByte())
123 | val byte2 = transformer(position(), buffer.readByte())
124 | val byte3 = transformer(position(), buffer.readByte())
125 | val byte4 = transformer(position(), buffer.readByte())
126 | val byte5 = transformer(position(), buffer.readByte())
127 | val byte6 = transformer(position(), buffer.readByte())
128 | val byte7 = transformer(position(), buffer.readByte())
129 | val byte8 = transformer(position(), buffer.readByte())
130 | buffer.resetForWrite()
131 | buffer.writeByte(byte1)
132 | buffer.writeByte(byte2)
133 | buffer.writeByte(byte3)
134 | buffer.writeByte(byte4)
135 | buffer.writeByte(byte5)
136 | buffer.writeByte(byte6)
137 | buffer.writeByte(byte7)
138 | buffer.writeByte(byte8)
139 | buffer.resetForRead()
140 | return buffer.readLong()
141 | }
142 |
143 | override fun readString(
144 | length: Int,
145 | charset: Charset,
146 | ): String {
147 | return when (charset) {
148 | Charset.UTF8 -> readByteArray(length).decodeToString()
149 | else -> throw UnsupportedOperationException("Unsupported charset $charset")
150 | }
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/com/ditchoom/buffer/WriteBuffer.kt:
--------------------------------------------------------------------------------
1 | package com.ditchoom.buffer
2 |
3 | interface WriteBuffer : PositionBuffer {
4 | fun resetForWrite()
5 |
6 | fun writeByte(byte: Byte): WriteBuffer
7 |
8 | operator fun set(
9 | index: Int,
10 | byte: Byte,
11 | ): WriteBuffer
12 |
13 | @Deprecated(
14 | "Use writeByte for explicitness. This will be removed in the next release",
15 | ReplaceWith("writeByte(byte)"),
16 | )
17 | fun write(byte: Byte): WriteBuffer = writeByte(byte)
18 |
19 | fun writeBytes(bytes: ByteArray): WriteBuffer = writeBytes(bytes, 0, bytes.size)
20 |
21 | @Deprecated(
22 | "Use writeBytes for explicitness. This will be removed in the next release",
23 | ReplaceWith("writeBytes(bytes)"),
24 | )
25 | fun write(bytes: ByteArray): WriteBuffer = writeBytes(bytes)
26 |
27 | fun writeBytes(
28 | bytes: ByteArray,
29 | offset: Int,
30 | length: Int,
31 | ): WriteBuffer
32 |
33 | @Deprecated(
34 | "Use writeBytes for explicitness. This will be removed in the next release",
35 | ReplaceWith("writeBytes(bytes, offset, length)"),
36 | )
37 | fun write(
38 | bytes: ByteArray,
39 | offset: Int,
40 | length: Int,
41 | ): WriteBuffer = writeBytes(bytes, offset, length)
42 |
43 | fun writeUByte(uByte: UByte): WriteBuffer = writeByte(uByte.toByte())
44 |
45 | operator fun set(
46 | index: Int,
47 | uByte: UByte,
48 | ) = set(index, uByte.toByte())
49 |
50 | @Deprecated(
51 | "Use writeUByte for explicitness. This will be removed in the next release",
52 | ReplaceWith("writeUByte(uByte)"),
53 | )
54 | fun write(uByte: UByte): WriteBuffer = writeUByte(uByte)
55 |
56 | fun writeShort(short: Short): WriteBuffer = writeNumberOfByteSize(short.toLong(), Short.SIZE_BYTES)
57 |
58 | operator fun set(
59 | index: Int,
60 | short: Short,
61 | ) = setIndexNumberAndByteSize(index, short.toLong(), Short.SIZE_BYTES)
62 |
63 | @Deprecated(
64 | "Use writeShort for explicitness. This will be removed in the next release",
65 | ReplaceWith("writeShort(short)"),
66 | )
67 | fun write(short: Short): WriteBuffer = writeUShort(short.toUShort())
68 |
69 | fun writeUShort(uShort: UShort): WriteBuffer = writeShort(uShort.toShort())
70 |
71 | operator fun set(
72 | index: Int,
73 | uShort: UShort,
74 | ) = set(index, uShort.toShort())
75 |
76 | @Deprecated(
77 | "Use writeUShort for explicitness. This will be removed in the next release",
78 | ReplaceWith("writeUShort(uShort)"),
79 | )
80 | fun write(uShort: UShort): WriteBuffer = writeUShort(uShort)
81 |
82 | fun writeInt(int: Int): WriteBuffer = writeNumberOfByteSize(int.toLong(), Int.SIZE_BYTES)
83 |
84 | operator fun set(
85 | index: Int,
86 | int: Int,
87 | ) = setIndexNumberAndByteSize(index, int.toLong(), Int.SIZE_BYTES)
88 |
89 | @Deprecated(
90 | "Use writeInt for explicitness. This will be removed in the next release",
91 | ReplaceWith("writeInt(int)"),
92 | )
93 | fun write(int: Int): WriteBuffer = writeInt(int)
94 |
95 | fun writeUInt(uInt: UInt): WriteBuffer = writeInt(uInt.toInt())
96 |
97 | operator fun set(
98 | index: Int,
99 | uInt: UInt,
100 | ) = set(index, uInt.toInt())
101 |
102 | @Deprecated(
103 | "Use writeUInt for explicitness. This will be removed in the next release",
104 | ReplaceWith("writeUInt(uInt)"),
105 | )
106 | fun write(uInt: UInt): WriteBuffer = writeUInt(uInt)
107 |
108 | fun writeLong(long: Long): WriteBuffer = writeNumberOfByteSize(long, Long.SIZE_BYTES)
109 |
110 | operator fun set(
111 | index: Int,
112 | long: Long,
113 | ) = setIndexNumberAndByteSize(index, long, Long.SIZE_BYTES)
114 |
115 | fun writeNumberOfByteSize(
116 | number: Long,
117 | byteSize: Int,
118 | ): WriteBuffer {
119 | check(byteSize in 1..8) { "byte size out of range" }
120 | val byteSizeRange =
121 | when (byteOrder) {
122 | ByteOrder.LITTLE_ENDIAN -> 0 until byteSize
123 | ByteOrder.BIG_ENDIAN -> byteSize - 1 downTo 0
124 | }
125 | for (i in byteSizeRange) {
126 | val bitIndex = i * 8
127 | writeByte((number shr bitIndex and 0xff).toByte())
128 | }
129 | return this
130 | }
131 |
132 | fun setIndexNumberAndByteSize(
133 | index: Int,
134 | number: Long,
135 | byteSize: Int,
136 | ): WriteBuffer {
137 | check(byteSize in 1..8) { "byte size out of range" }
138 | val p = position()
139 | position(index)
140 | writeNumberOfByteSize(number, byteSize)
141 | position(p)
142 | return this
143 | }
144 |
145 | @Deprecated(
146 | "Use writeLong for explicitness. This will be removed in the next release",
147 | ReplaceWith("writeLong(long)"),
148 | )
149 | fun write(long: Long): WriteBuffer = writeLong(long)
150 |
151 | fun writeULong(uLong: ULong): WriteBuffer = writeLong(uLong.toLong())
152 |
153 | operator fun set(
154 | index: Int,
155 | uLong: ULong,
156 | ) = set(index, uLong.toLong())
157 |
158 | @Deprecated(
159 | "Use writeULong for explicitness. This will be removed in the next release",
160 | ReplaceWith("writeULong(uLong)"),
161 | )
162 | fun write(uLong: ULong): WriteBuffer = writeULong(uLong)
163 |
164 | fun writeFloat(float: Float): WriteBuffer = writeInt(float.toRawBits())
165 |
166 | operator fun set(
167 | index: Int,
168 | float: Float,
169 | ) = set(index, float.toRawBits())
170 |
171 | @Deprecated(
172 | "Use writeFloat for explicitness. This will be removed in the next release",
173 | ReplaceWith("writeFloat(float)"),
174 | )
175 | fun write(float: Float): WriteBuffer = writeFloat(float)
176 |
177 | fun writeDouble(double: Double): WriteBuffer = writeLong(double.toRawBits())
178 |
179 | operator fun set(
180 | index: Int,
181 | double: Double,
182 | ) = set(index, double.toRawBits())
183 |
184 | @Deprecated(
185 | "Use writeDouble for explicitness. This will be removed in the next release",
186 | ReplaceWith("writeDouble(double)"),
187 | )
188 | fun write(double: Double): WriteBuffer = writeDouble(double)
189 |
190 | @Deprecated(
191 | "Use writeString(txt, Charset.UTF8) instead",
192 | ReplaceWith("writeString(text, Charset.UTF8)", "com.ditchoom.buffer.Charset"),
193 | )
194 | fun writeUtf8(text: CharSequence): WriteBuffer = writeString(text, Charset.UTF8)
195 |
196 | fun writeString(
197 | text: CharSequence,
198 | charset: Charset = Charset.UTF8,
199 | ): WriteBuffer
200 |
201 | fun write(buffer: ReadBuffer)
202 | }
203 |
--------------------------------------------------------------------------------
/src/commonTest/kotlin/com/ditchoom/buffer/BufferTests.kt:
--------------------------------------------------------------------------------
1 | package com.ditchoom.buffer
2 |
3 | import kotlin.math.absoluteValue
4 | import kotlin.math.ceil
5 | import kotlin.test.Test
6 | import kotlin.test.assertContentEquals
7 | import kotlin.test.assertEquals
8 | import kotlin.test.assertFails
9 | import kotlin.test.assertFalse
10 | import kotlin.test.assertTrue
11 |
12 | class BufferTests {
13 | @Test
14 | fun slice() {
15 | val platformBuffer = PlatformBuffer.allocate(3)
16 | platformBuffer.writeByte((-1).toByte())
17 | platformBuffer.resetForRead()
18 | val slicedBuffer = platformBuffer.slice()
19 | assertEquals(0, platformBuffer.position())
20 | assertEquals(1, platformBuffer.limit())
21 | assertEquals(0, slicedBuffer.position())
22 | assertEquals(1, slicedBuffer.limit())
23 | assertEquals(-1, slicedBuffer.readByte())
24 | assertEquals(1, slicedBuffer.position())
25 | assertEquals(1, slicedBuffer.limit())
26 | }
27 |
28 | @Test
29 | fun sharedMemoryAllocates() {
30 | val platformBuffer =
31 | PlatformBuffer.allocate(
32 | Byte.SIZE_BYTES + UByte.SIZE_BYTES +
33 | Short.SIZE_BYTES + UShort.SIZE_BYTES +
34 | Int.SIZE_BYTES + UInt.SIZE_BYTES +
35 | Long.SIZE_BYTES + ULong.SIZE_BYTES,
36 | AllocationZone.SharedMemory,
37 | )
38 | testAllPrimitives(platformBuffer)
39 | }
40 |
41 | @Test
42 | fun absolute() {
43 | val platformBuffer =
44 | PlatformBuffer.allocate(
45 | Byte.SIZE_BYTES + UByte.SIZE_BYTES +
46 | Short.SIZE_BYTES + UShort.SIZE_BYTES +
47 | Int.SIZE_BYTES + UInt.SIZE_BYTES +
48 | Long.SIZE_BYTES + ULong.SIZE_BYTES,
49 | )
50 | testAllPrimitives(platformBuffer)
51 | }
52 |
53 | private fun testAllPrimitives(platformBuffer: PlatformBuffer) {
54 | var index = 0
55 | platformBuffer[index] = Byte.MIN_VALUE
56 | index += Byte.SIZE_BYTES
57 | platformBuffer[index] = UByte.MAX_VALUE
58 | index += UByte.SIZE_BYTES
59 |
60 | platformBuffer[index] = Short.MIN_VALUE
61 | index += Short.SIZE_BYTES
62 | platformBuffer[index] = UShort.MAX_VALUE
63 | index += UShort.SIZE_BYTES
64 |
65 | platformBuffer[index] = Int.MIN_VALUE
66 | index += Int.SIZE_BYTES
67 | platformBuffer[index] = UInt.MAX_VALUE
68 | index += UInt.SIZE_BYTES
69 |
70 | platformBuffer[index] = Long.MIN_VALUE
71 | index += Long.SIZE_BYTES
72 | platformBuffer[index] = ULong.MAX_VALUE
73 |
74 | index = 0
75 | assertEquals(platformBuffer[index], Byte.MIN_VALUE, "absolute byte read")
76 | index += Byte.SIZE_BYTES
77 | assertEquals(platformBuffer.getUnsignedByte(index), UByte.MAX_VALUE, "absolute ubyte read")
78 | index += UByte.SIZE_BYTES
79 |
80 | assertEquals(platformBuffer.getShort(index), Short.MIN_VALUE, "absolute short read")
81 | index += Short.SIZE_BYTES
82 | assertEquals(platformBuffer.getUnsignedShort(index), UShort.MAX_VALUE, "absolute ushort read")
83 | index += UShort.SIZE_BYTES
84 |
85 | assertEquals(platformBuffer.getInt(index), Int.MIN_VALUE, "absolute int read")
86 | index += Int.SIZE_BYTES
87 | assertEquals(platformBuffer.getUnsignedInt(index), UInt.MAX_VALUE, "absolute uint read")
88 | index += UInt.SIZE_BYTES
89 |
90 | assertEquals(platformBuffer.getLong(index), Long.MIN_VALUE, "absolute long read")
91 | index += Long.SIZE_BYTES
92 | assertEquals(platformBuffer.getUnsignedLong(index), ULong.MAX_VALUE, "absolute ulong read")
93 |
94 | // double validate with relative reading
95 | assertEquals(0, platformBuffer.position(), "relative initial position")
96 | assertEquals(platformBuffer.readByte(), Byte.MIN_VALUE, "relative byte read")
97 | assertEquals(1, platformBuffer.position(), "relative after byte read position")
98 | assertEquals(platformBuffer.readUnsignedByte(), UByte.MAX_VALUE, "relative ubyte read")
99 | assertEquals(2, platformBuffer.position(), "relative after ubyte read position")
100 |
101 | assertEquals(platformBuffer.readShort(), Short.MIN_VALUE, "relative short read")
102 | assertEquals(4, platformBuffer.position(), "relative after short read position")
103 | assertEquals(platformBuffer.readUnsignedShort(), UShort.MAX_VALUE, "relative ushort read")
104 | assertEquals(6, platformBuffer.position(), "relative after ushort read position")
105 |
106 | assertEquals(platformBuffer.readInt(), Int.MIN_VALUE, "relative int read")
107 | assertEquals(10, platformBuffer.position(), "relative after int read position")
108 | assertEquals(platformBuffer.readUnsignedInt(), UInt.MAX_VALUE, "relative uint read")
109 | assertEquals(14, platformBuffer.position(), "relative after uint read position")
110 |
111 | assertEquals(platformBuffer.readLong(), Long.MIN_VALUE, "relative long read")
112 | assertEquals(22, platformBuffer.position(), "relative after long read position")
113 | assertEquals(platformBuffer.readUnsignedLong(), ULong.MAX_VALUE, "relative ulong read")
114 | assertEquals(30, platformBuffer.position(), "relative after ulong read position")
115 | }
116 |
117 | @Test
118 | fun readByte() {
119 | val platformBuffer = PlatformBuffer.allocate(3)
120 | platformBuffer.writeByte((-1).toByte())
121 | platformBuffer.resetForRead()
122 | val slicedBuffer = platformBuffer.readBytes(1)
123 | assertEquals(1, platformBuffer.position())
124 | assertEquals(1, platformBuffer.limit())
125 | assertEquals(0, slicedBuffer.position())
126 | assertEquals(1, slicedBuffer.limit())
127 | assertEquals(-1, slicedBuffer.readByte())
128 | assertEquals(1, slicedBuffer.position())
129 | assertEquals(1, slicedBuffer.limit())
130 | }
131 |
132 | @Test
133 | fun getByte() {
134 | val platformBuffer = PlatformBuffer.allocate(3)
135 | val expectedValue = (-1).toByte()
136 | platformBuffer[0] = expectedValue
137 | val valueRead = platformBuffer[0]
138 | assertEquals(0, platformBuffer.position())
139 | assertEquals(3, platformBuffer.limit())
140 | assertEquals(-1, valueRead)
141 | }
142 |
143 | @Test
144 | fun sliceAndReadUtf8() {
145 | val expected = "test"
146 | // the first two bytes are not visible characters
147 | val bytes =
148 | byteArrayOf(
149 | -126,
150 | 4,
151 | expected[0].code.toByte(),
152 | expected[1].code.toByte(),
153 | expected[2].code.toByte(),
154 | expected[3].code.toByte(),
155 | )
156 | val platformBuffer = PlatformBuffer.allocate(bytes.size)
157 | platformBuffer.writeBytes(bytes)
158 | platformBuffer.position(2)
159 | assertEquals(expected, platformBuffer.readString(4, Charset.UTF8))
160 | }
161 |
162 | @Test
163 | fun sliceFragmented() {
164 | val platformBuffer1 = PlatformBuffer.allocate(3)
165 | platformBuffer1.writeByte(1.toByte())
166 | platformBuffer1.resetForRead()
167 |
168 | val platformBuffer2 = PlatformBuffer.allocate(3)
169 | platformBuffer2.writeByte((-1).toByte())
170 | platformBuffer2.resetForRead()
171 |
172 | val fragmentedBuffer = FragmentedReadBuffer(platformBuffer1, platformBuffer2)
173 | assertEquals(0, fragmentedBuffer.position())
174 | assertEquals(2, fragmentedBuffer.limit())
175 | assertEquals(1, fragmentedBuffer.readByte())
176 | assertEquals(1, fragmentedBuffer.position())
177 | assertEquals(2, fragmentedBuffer.limit())
178 | assertEquals(-1, fragmentedBuffer.readByte())
179 | assertEquals(2, fragmentedBuffer.position())
180 | assertEquals(2, fragmentedBuffer.limit())
181 | fragmentedBuffer.resetForRead()
182 | assertEquals(0, fragmentedBuffer.position())
183 | assertEquals(2, fragmentedBuffer.limit())
184 | val slicedBuffer = fragmentedBuffer.slice()
185 | assertEquals(0, slicedBuffer.position())
186 | assertEquals(2, slicedBuffer.limit())
187 | assertEquals(1, slicedBuffer.readByte())
188 | assertEquals(1, slicedBuffer.position())
189 | assertEquals(2, slicedBuffer.limit())
190 | assertEquals(-1, slicedBuffer.readByte())
191 | assertEquals(2, slicedBuffer.position())
192 | assertEquals(2, slicedBuffer.limit())
193 | }
194 |
195 | @Test
196 | fun byte() {
197 | val platformBuffer = PlatformBuffer.allocate(1)
198 | val byte = (-1).toByte()
199 | platformBuffer.writeByte(byte)
200 | platformBuffer.resetForRead()
201 | assertEquals(byte.toInt(), platformBuffer.readByte().toInt())
202 | }
203 |
204 | @Test
205 | fun byteArray() {
206 | val size = 200
207 | val platformBuffer = PlatformBuffer.allocate(size)
208 | val bytes = ByteArray(size) { -1 }
209 | platformBuffer.writeBytes(bytes)
210 | platformBuffer.resetForRead()
211 | val byteArray = platformBuffer.readByteArray(size)
212 | assertEquals(bytes.count(), byteArray.count())
213 | bytes.forEachIndexed { index, byte ->
214 | assertEquals(byte, byteArray[index], "Index $index")
215 | }
216 | }
217 |
218 | @Test
219 | fun relativeUnsignedByte() {
220 | val platformBuffer = PlatformBuffer.allocate(1)
221 | val byte = (-1).toUByte()
222 | platformBuffer.writeUByte(byte)
223 | platformBuffer.resetForRead()
224 | assertEquals(byte.toInt(), platformBuffer.readUnsignedByte().toInt())
225 | assertFalse(platformBuffer.hasRemaining())
226 | }
227 |
228 | @Test
229 | fun absoluteUnsignedByte() {
230 | val platformBuffer = PlatformBuffer.allocate(1)
231 | val byte = (-1).toUByte()
232 | platformBuffer[0] = byte
233 | assertEquals(byte, platformBuffer[0].toUByte())
234 | assertEquals(1, platformBuffer.remaining())
235 | }
236 |
237 | @Test
238 | fun relativeUnsignedShort() {
239 | val platformBuffer = PlatformBuffer.allocate(2)
240 | val uShort = UShort.MAX_VALUE.toInt() / 2
241 | platformBuffer.writeUShort(uShort.toUShort())
242 | platformBuffer.resetForRead()
243 | assertEquals(uShort, platformBuffer.readUnsignedShort().toInt())
244 | platformBuffer.resetForRead()
245 | val msb = platformBuffer.readByte()
246 | val lsb = platformBuffer.readByte()
247 | val value =
248 | (
249 | (0xff and msb.toInt() shl 8)
250 | or (0xff and lsb.toInt() shl 0)
251 | ).toUShort()
252 | assertEquals(value.toInt(), uShort)
253 | assertEquals(0, platformBuffer.remaining())
254 | }
255 |
256 | @Test
257 | fun absoluteUnsignedShort() {
258 | val platformBuffer = PlatformBuffer.allocate(2)
259 | val uShort = UShort.MAX_VALUE
260 | platformBuffer[0] = uShort
261 | assertEquals(uShort, platformBuffer.getUnsignedShort(0))
262 | val msb = platformBuffer[0]
263 | val lsb = platformBuffer[1]
264 | val value =
265 | (
266 | (0xff and msb.toInt() shl 8)
267 | or (0xff and lsb.toInt() shl 0)
268 | ).toUShort()
269 | assertEquals(value, uShort)
270 | assertEquals(2, platformBuffer.remaining())
271 | }
272 |
273 | @Test
274 | fun allUShortValues() {
275 | val buffer = PlatformBuffer.allocate(UShort.MAX_VALUE.toInt() * UShort.SIZE_BYTES)
276 | (0 until UShort.MAX_VALUE.toInt()).forEach {
277 | buffer.writeUShort(it.toUShort())
278 | }
279 | buffer.resetForRead()
280 | (0 until UShort.MAX_VALUE.toInt()).forEach {
281 | assertEquals(it, buffer.readUnsignedShort().toInt())
282 | }
283 | }
284 |
285 | @Test
286 | fun relativeUnsignedShortHalf() {
287 | val platformBuffer = PlatformBuffer.allocate(2)
288 | val uShort = (UShort.MAX_VALUE / 2u).toUShort()
289 | platformBuffer.writeUShort(uShort)
290 | platformBuffer.resetForRead()
291 | val actual = platformBuffer.readUnsignedShort().toInt()
292 | assertEquals(uShort.toInt(), actual)
293 | assertEquals(uShort.toString(), actual.toString())
294 | assertEquals(0, platformBuffer.remaining())
295 | }
296 |
297 | @Test
298 | fun absoluteUnsignedShortHalf() {
299 | val platformBuffer = PlatformBuffer.allocate(UShort.SIZE_BYTES)
300 | val uShort = (UShort.MAX_VALUE / 2u).toUShort()
301 | platformBuffer[0] = uShort
302 | val actual = platformBuffer.getUnsignedShort(0)
303 | assertEquals(uShort, actual)
304 | assertEquals(uShort.toString(), actual.toString())
305 | assertEquals(0, platformBuffer.position())
306 | }
307 |
308 | @Test
309 | fun relativeUnsignedInt() {
310 | val platformBuffer = PlatformBuffer.allocate(4)
311 | val uInt = (-1).toUInt()
312 | platformBuffer.writeUInt(uInt)
313 | platformBuffer.resetForRead()
314 | assertEquals(uInt.toLong(), platformBuffer.readUnsignedInt().toLong())
315 | assertEquals(0, platformBuffer.remaining())
316 | }
317 |
318 | @Test
319 | fun absoluteUnsignedInt() {
320 | val platformBuffer = PlatformBuffer.allocate(4)
321 | val uInt = (-1).toUInt()
322 | platformBuffer[0] = uInt
323 | assertEquals(uInt.toLong(), platformBuffer.getUnsignedInt(0).toLong())
324 | assertEquals(4, platformBuffer.remaining())
325 | }
326 |
327 | @Test
328 | fun unsignedIntHalf() {
329 | val platformBuffer = PlatformBuffer.allocate(4)
330 | val uInt = Int.MAX_VALUE.toUInt() / 2u
331 | platformBuffer.writeUInt(uInt)
332 | platformBuffer.resetForRead()
333 | assertEquals(uInt.toLong(), platformBuffer.readUnsignedInt().toLong())
334 | assertEquals(0, platformBuffer.remaining())
335 | }
336 |
337 | @Test
338 | fun relativeLong() {
339 | val platformBuffer = PlatformBuffer.allocate(Long.SIZE_BYTES)
340 | val long = (1234).toLong()
341 | assertEquals(0, platformBuffer.position())
342 | platformBuffer.writeLong(long)
343 | assertEquals(Long.SIZE_BYTES, platformBuffer.position())
344 | platformBuffer.resetForRead()
345 | assertEquals(0, platformBuffer.position())
346 | assertEquals(long, platformBuffer.readLong())
347 | assertEquals(Long.SIZE_BYTES, platformBuffer.position())
348 | platformBuffer.resetForRead()
349 | assertEquals(0, platformBuffer.position())
350 | assertEquals(long, platformBuffer.readNumberWithByteSize(Long.SIZE_BYTES))
351 | assertEquals(Long.SIZE_BYTES, platformBuffer.position())
352 |
353 | val platformBufferLittleEndian =
354 | PlatformBuffer.allocate(Long.SIZE_BYTES, byteOrder = ByteOrder.LITTLE_ENDIAN)
355 | platformBufferLittleEndian.writeLong(long)
356 | platformBufferLittleEndian.resetForRead()
357 | assertEquals(long, platformBufferLittleEndian.readLong())
358 | platformBufferLittleEndian.resetForRead()
359 | assertEquals(long, platformBufferLittleEndian.readNumberWithByteSize(Long.SIZE_BYTES))
360 | }
361 |
362 | @Test
363 | fun absoluteLong() {
364 | val platformBuffer = PlatformBuffer.allocate(Long.SIZE_BYTES)
365 | val long = 12345L
366 | assertEquals(0, platformBuffer.position())
367 | platformBuffer[0] = long
368 | assertEquals(0, platformBuffer.position())
369 | assertEquals(
370 | long,
371 | platformBuffer.getLong(0),
372 | "getLong BIG_ENDIAN buffer[" +
373 | "${platformBuffer[0]}, ${platformBuffer[1]}, ${platformBuffer[2]}, ${platformBuffer[3]}, " +
374 | "${platformBuffer[4]}, ${platformBuffer[5]}, ${platformBuffer[6]}, ${platformBuffer[7]}]",
375 | )
376 | assertEquals(0, platformBuffer.position())
377 | assertEquals(
378 | long,
379 | platformBuffer.getNumberWithStartIndexAndByteSize(0, Long.SIZE_BYTES),
380 | "getNumberWithStartIndexAndByteSize",
381 | )
382 | assertEquals(0, platformBuffer.position())
383 |
384 | val platformBufferLE =
385 | PlatformBuffer.allocate(Long.SIZE_BYTES, byteOrder = ByteOrder.LITTLE_ENDIAN)
386 | platformBufferLE[0] = long
387 | assertEquals(0, platformBufferLE.position())
388 | assertEquals(
389 | long,
390 | platformBufferLE.getLong(0),
391 | "getLong LITTLE_ENDIAN buffer[" +
392 | "${platformBufferLE[0]}, ${platformBufferLE[1]}, ${platformBufferLE[2]}, ${platformBufferLE[3]}, " +
393 | "${platformBufferLE[4]}, ${platformBufferLE[5]}, ${platformBufferLE[6]}, ${platformBufferLE[7]}]",
394 | )
395 | assertEquals(0, platformBufferLE.position())
396 | assertEquals(
397 | long,
398 | platformBufferLE.getNumberWithStartIndexAndByteSize(0, Long.SIZE_BYTES),
399 | "getNumberWithStartIndexAndByteSizeLittleEndian",
400 | )
401 | assertEquals(0, platformBufferLE.position())
402 | }
403 |
404 | @Test
405 | fun relativeLongBits() {
406 | val platformBuffer = PlatformBuffer.allocate(Long.SIZE_BYTES)
407 | val long = (1234).toLong()
408 | platformBuffer.writeNumberOfByteSize(long, Long.SIZE_BYTES)
409 | platformBuffer.resetForRead()
410 | assertEquals(long, platformBuffer.readLong())
411 | platformBuffer.resetForRead()
412 | assertEquals(long, platformBuffer.readNumberWithByteSize(Long.SIZE_BYTES))
413 |
414 | val platformBufferLittleEndian =
415 | PlatformBuffer.allocate(Long.SIZE_BYTES, byteOrder = ByteOrder.LITTLE_ENDIAN)
416 | platformBufferLittleEndian.writeNumberOfByteSize(long, Long.SIZE_BYTES)
417 | platformBufferLittleEndian.resetForRead()
418 | assertEquals(long, platformBufferLittleEndian.readLong())
419 | platformBufferLittleEndian.resetForRead()
420 | assertEquals(long, platformBufferLittleEndian.readNumberWithByteSize(Long.SIZE_BYTES))
421 | }
422 |
423 | @Test
424 | fun absoluteLongBits() {
425 | val platformBuffer = PlatformBuffer.allocate(Long.SIZE_BYTES)
426 | val long = (1234).toLong()
427 | platformBuffer.setIndexNumberAndByteSize(0, long, Long.SIZE_BYTES)
428 | assertEquals(0, platformBuffer.position())
429 | assertEquals(long, platformBuffer.getLong(0))
430 | assertEquals(0, platformBuffer.position())
431 | assertEquals(long, platformBuffer.getNumberWithStartIndexAndByteSize(0, Long.SIZE_BYTES))
432 | assertEquals(0, platformBuffer.position())
433 |
434 | val platformBufferLittleEndian =
435 | PlatformBuffer.allocate(Long.SIZE_BYTES, byteOrder = ByteOrder.LITTLE_ENDIAN)
436 | platformBufferLittleEndian.setIndexNumberAndByteSize(0, long, Long.SIZE_BYTES)
437 | assertEquals(0, platformBufferLittleEndian.position())
438 | assertEquals(long, platformBufferLittleEndian.getLong(0))
439 | assertEquals(0, platformBufferLittleEndian.position())
440 | assertEquals(long, platformBufferLittleEndian.getNumberWithStartIndexAndByteSize(0, Long.SIZE_BYTES))
441 | assertEquals(0, platformBufferLittleEndian.position())
442 | }
443 |
444 | @Test
445 | fun realtiveFloat() {
446 | val platformBuffer = PlatformBuffer.allocate(Float.SIZE_BYTES)
447 | val float = 123.456f
448 | platformBuffer.writeFloat(float)
449 | platformBuffer.resetForRead()
450 | // Note that in Kotlin/JS Float range is wider than "single format" bit layout can represent,
451 | // so some Float values may overflow, underflow or lose their accuracy after conversion to bits and back.
452 | assertTrue { (float - platformBuffer.readFloat()).absoluteValue < 0.00001f }
453 | }
454 |
455 | @Test
456 | fun absoluteFloat() {
457 | val platformBuffer = PlatformBuffer.allocate(Float.SIZE_BYTES)
458 | val float = 123.456f
459 | platformBuffer[0] = float
460 | assertEquals(0, platformBuffer.position())
461 | // Note that in Kotlin/JS Float range is wider than "single format" bit layout can represent,
462 | // so some Float values may overflow, underflow or lose their accuracy after conversion to bits and back.
463 | assertTrue { (float - platformBuffer.getFloat(0)).absoluteValue < 0.00001f }
464 | assertEquals(0, platformBuffer.position())
465 | }
466 |
467 | @Test
468 | fun relativeDouble() {
469 | val platformBuffer = PlatformBuffer.allocate(Double.SIZE_BYTES)
470 | val double = 123.456
471 | platformBuffer.writeDouble(double)
472 | platformBuffer.resetForRead()
473 | assertEquals(double, platformBuffer.readDouble())
474 | }
475 |
476 | @Test
477 | fun absoluteDouble() {
478 | val platformBuffer = PlatformBuffer.allocate(Double.SIZE_BYTES)
479 | val double = 123.456
480 | assertEquals(0, platformBuffer.position())
481 | platformBuffer[0] = double
482 | assertEquals(0, platformBuffer.position())
483 | assertEquals(
484 | double,
485 | platformBuffer.getDouble(0),
486 | "getDouble BIG_ENDIAN buffer[" +
487 | "${platformBuffer[0]}, ${platformBuffer[1]}, ${platformBuffer[2]}, ${platformBuffer[3]}, " +
488 | "${platformBuffer[4]}, ${platformBuffer[5]}, ${platformBuffer[6]}, ${platformBuffer[7]}]",
489 | )
490 | assertEquals(0, platformBuffer.position())
491 |
492 | val platformBufferLE =
493 | PlatformBuffer.allocate(Long.SIZE_BYTES, byteOrder = ByteOrder.LITTLE_ENDIAN)
494 | platformBufferLE[0] = double
495 | assertEquals(0, platformBufferLE.position())
496 | assertEquals(
497 | double,
498 | platformBufferLE.getDouble(0),
499 | "getDouble LITTLE_ENDIAN buffer[" +
500 | "${platformBufferLE[0]}, ${platformBufferLE[1]}, ${platformBufferLE[2]}, ${platformBufferLE[3]}, " +
501 | "${platformBufferLE[4]}, ${platformBufferLE[5]}, ${platformBufferLE[6]}, ${platformBufferLE[7]}]",
502 | )
503 | assertEquals(0, platformBufferLE.position())
504 | }
505 |
506 | @Test
507 | fun positionWriteBytes() {
508 | val text = "Hello world!"
509 | val input = PlatformBuffer.wrap(text.encodeToByteArray())
510 | val output = PlatformBuffer.allocate(text.length)
511 | output.write(input)
512 | assertEquals(input.position(), text.length)
513 | assertEquals(output.position(), text.length)
514 | output.position(0)
515 | assertEquals(output.readString(text.length), text)
516 | }
517 |
518 | @Test
519 | fun utf8String() {
520 | val string = "yolo swag lyfestyle"
521 | assertEquals(19, string.utf8Length())
522 | val platformBuffer = PlatformBuffer.allocate(19)
523 | platformBuffer.writeString(string, Charset.UTF8)
524 | platformBuffer.resetForRead()
525 | val actual = platformBuffer.readString(19, Charset.UTF8)
526 | assertEquals(string.length, actual.length)
527 | assertEquals(string, actual)
528 | }
529 |
530 | @Test
531 | fun readUtf8LineSingle() {
532 | val text = "hello"
533 | val buffer = text.toReadBuffer(Charset.UTF8)
534 | assertEquals("hello", buffer.readUtf8Line().toString())
535 | assertEquals(buffer.remaining(), 0)
536 | }
537 |
538 | @Test
539 | fun readUtf8LineDouble() {
540 | val text = "hello\r\n"
541 | val buffer = text.toReadBuffer(Charset.UTF8)
542 | assertEquals("hello", buffer.readUtf8Line().toString())
543 | assertEquals("", buffer.readUtf8Line().toString())
544 | assertEquals(buffer.remaining(), 0)
545 | }
546 |
547 | @Test
548 | fun readUtf8LineStart() {
549 | val text = "\r\nhello"
550 | val buffer = text.toReadBuffer(Charset.UTF8)
551 | assertEquals("", buffer.readUtf8Line().toString())
552 | assertEquals("hello", buffer.readUtf8Line().toString())
553 | assertEquals(buffer.remaining(), 0)
554 | }
555 |
556 | @Test
557 | fun readUtf8LineStartN() {
558 | val text = "\nhello"
559 | val buffer = text.toReadBuffer(Charset.UTF8)
560 | assertEquals("", buffer.readUtf8Line().toString())
561 | assertEquals("hello", buffer.readUtf8Line().toString())
562 | assertEquals(buffer.remaining(), 0)
563 | }
564 |
565 | @Test
566 | fun readUtf8LineMix() {
567 | val text = "\nhello\r\nhello\nhello\r\n"
568 | val buffer = text.toReadBuffer(Charset.UTF8)
569 | assertEquals("", buffer.readUtf8Line().toString())
570 | assertEquals("hello", buffer.readUtf8Line().toString())
571 | assertEquals("hello", buffer.readUtf8Line().toString())
572 | assertEquals("hello", buffer.readUtf8Line().toString())
573 | assertEquals("", buffer.readUtf8Line().toString())
574 | assertEquals(buffer.remaining(), 0)
575 | }
576 |
577 | @Test
578 | fun readUtf8LineMixMulti() {
579 | val text = "\nhello\r\n\nhello\n\nhello\r\n"
580 | val buffer = text.toReadBuffer(Charset.UTF8)
581 | assertEquals("", buffer.readUtf8Line().toString())
582 | assertEquals("hello", buffer.readUtf8Line().toString())
583 | assertEquals("", buffer.readUtf8Line().toString())
584 | assertEquals("hello", buffer.readUtf8Line().toString())
585 | assertEquals("", buffer.readUtf8Line().toString())
586 | assertEquals("hello", buffer.readUtf8Line().toString())
587 | assertEquals("", buffer.readUtf8Line().toString())
588 | assertEquals(buffer.remaining(), 0)
589 | }
590 |
591 | @Test
592 | fun readUtf8Line() {
593 | val stringArray = "yolo swag lyfestyle".split(' ')
594 | assertEquals(3, stringArray.size)
595 | val newLineString = stringArray.joinToString("\r\n")
596 | val stringBuffer = newLineString.toReadBuffer(Charset.UTF8)
597 | stringArray.forEach {
598 | val line = stringBuffer.readUtf8Line()
599 | assertEquals(it, line.toString())
600 | }
601 | }
602 |
603 | @Test
604 | fun readByteArray() {
605 | val string = "yolo swag lyfestyle"
606 | val stringBuffer = string.toReadBuffer(Charset.UTF8)
607 | assertEquals(string[0], Char(stringBuffer.readByte().toInt()))
608 | val s = stringBuffer.readByteArray(stringBuffer.remaining())
609 | assertEquals(string.substring(1), s.decodeToString())
610 | }
611 |
612 | @Test
613 | fun readByteArraySizeZeroDoesNotCrash() {
614 | val string = "yolo swag lyfestyle"
615 | val stringBuffer = string.toReadBuffer(Charset.UTF8)
616 | val emptyByteArray = stringBuffer.readByteArray(0)
617 | assertContentEquals(emptyByteArray, ByteArray(0))
618 | }
619 |
620 | @Test
621 | fun endianWrite() {
622 | val littleEndian2 = PlatformBuffer.allocate(2, byteOrder = ByteOrder.LITTLE_ENDIAN)
623 | littleEndian2.writeShort(0x0102.toShort())
624 | littleEndian2.resetForRead()
625 | assertEquals(0x02u, littleEndian2.readUnsignedByte())
626 | assertEquals(0x01u, littleEndian2.readUnsignedByte())
627 |
628 | val bigEndian2 = PlatformBuffer.allocate(2, byteOrder = ByteOrder.BIG_ENDIAN)
629 | bigEndian2.writeShort(0x0102.toShort())
630 | bigEndian2.resetForRead()
631 | assertEquals(0x01u, bigEndian2.readUnsignedByte())
632 | assertEquals(0x02u, bigEndian2.readUnsignedByte())
633 |
634 | val littleEndian4 = PlatformBuffer.allocate(4, byteOrder = ByteOrder.LITTLE_ENDIAN)
635 | littleEndian4.writeInt(0x01020304)
636 | littleEndian4.resetForRead()
637 | assertEquals(0x04u, littleEndian4.readUnsignedByte())
638 | assertEquals(0x03u, littleEndian4.readUnsignedByte())
639 | assertEquals(0x02u, littleEndian4.readUnsignedByte())
640 | assertEquals(0x01u, littleEndian4.readUnsignedByte())
641 |
642 | val bigEndian4 = PlatformBuffer.allocate(4, byteOrder = ByteOrder.BIG_ENDIAN)
643 | bigEndian4.writeInt(0x01020304)
644 | bigEndian4.resetForRead()
645 | assertEquals(0x01u, bigEndian4.readUnsignedByte())
646 | assertEquals(0x02u, bigEndian4.readUnsignedByte())
647 | assertEquals(0x03u, bigEndian4.readUnsignedByte())
648 | assertEquals(0x04u, bigEndian4.readUnsignedByte())
649 |
650 | val littleEndian8 = PlatformBuffer.allocate(8, byteOrder = ByteOrder.LITTLE_ENDIAN)
651 | littleEndian8.writeLong(0x0102030405060708)
652 | littleEndian8.resetForRead()
653 | assertEquals(0x08u, littleEndian8.readUnsignedByte())
654 | assertEquals(0x07u, littleEndian8.readUnsignedByte())
655 | assertEquals(0x06u, littleEndian8.readUnsignedByte())
656 | assertEquals(0x05u, littleEndian8.readUnsignedByte())
657 | assertEquals(0x04u, littleEndian8.readUnsignedByte())
658 | assertEquals(0x03u, littleEndian8.readUnsignedByte())
659 | assertEquals(0x02u, littleEndian8.readUnsignedByte())
660 | assertEquals(0x01u, littleEndian8.readUnsignedByte())
661 |
662 | val bigEndian8 = PlatformBuffer.allocate(8, byteOrder = ByteOrder.BIG_ENDIAN)
663 | bigEndian8.writeLong(0x0102030405060708)
664 | bigEndian8.resetForRead()
665 | assertEquals(0x01u, bigEndian8.readUnsignedByte())
666 | assertEquals(0x02u, bigEndian8.readUnsignedByte())
667 | assertEquals(0x03u, bigEndian8.readUnsignedByte())
668 | assertEquals(0x04u, bigEndian8.readUnsignedByte())
669 | assertEquals(0x05u, bigEndian8.readUnsignedByte())
670 | assertEquals(0x06u, bigEndian8.readUnsignedByte())
671 | assertEquals(0x07u, bigEndian8.readUnsignedByte())
672 | assertEquals(0x08u, bigEndian8.readUnsignedByte())
673 | }
674 |
675 | @Test
676 | fun endianRead() {
677 | val littleEndian2 = PlatformBuffer.allocate(2, byteOrder = ByteOrder.LITTLE_ENDIAN)
678 | littleEndian2.writeByte(0x01.toByte())
679 | littleEndian2.writeByte(0x02.toByte())
680 | littleEndian2.resetForRead()
681 | assertEquals(0x0201.toShort(), littleEndian2.readShort())
682 |
683 | val bigEndian2 = PlatformBuffer.allocate(2, byteOrder = ByteOrder.BIG_ENDIAN)
684 | bigEndian2.writeByte(0x01.toByte())
685 | bigEndian2.writeByte(0x02.toByte())
686 | bigEndian2.resetForRead()
687 | assertEquals(0x0102.toShort(), bigEndian2.readShort())
688 |
689 | val littleEndian4 = PlatformBuffer.allocate(4, byteOrder = ByteOrder.LITTLE_ENDIAN)
690 | littleEndian4.writeByte(0x01.toByte())
691 | littleEndian4.writeByte(0x02.toByte())
692 | littleEndian4.writeByte(0x03.toByte())
693 | littleEndian4.writeByte(0x04.toByte())
694 | littleEndian4.resetForRead()
695 | assertEquals(0x04030201, littleEndian4.readInt())
696 |
697 | val bigEndian4 = PlatformBuffer.allocate(4, byteOrder = ByteOrder.BIG_ENDIAN)
698 | bigEndian4.writeByte(0x01.toByte())
699 | bigEndian4.writeByte(0x02.toByte())
700 | bigEndian4.writeByte(0x03.toByte())
701 | bigEndian4.writeByte(0x04.toByte())
702 | bigEndian4.resetForRead()
703 | assertEquals(0x01020304, bigEndian4.readInt())
704 |
705 | val littleEndian8 = PlatformBuffer.allocate(8, byteOrder = ByteOrder.LITTLE_ENDIAN)
706 | littleEndian8.writeByte(0x01.toByte())
707 | littleEndian8.writeByte(0x02.toByte())
708 | littleEndian8.writeByte(0x03.toByte())
709 | littleEndian8.writeByte(0x04.toByte())
710 | littleEndian8.writeByte(0x05.toByte())
711 | littleEndian8.writeByte(0x06.toByte())
712 | littleEndian8.writeByte(0x07.toByte())
713 | littleEndian8.writeByte(0x08.toByte())
714 | littleEndian8.resetForRead()
715 | assertEquals(0x0807060504030201, littleEndian8.readLong())
716 |
717 | val bigEndian8 = PlatformBuffer.allocate(8, byteOrder = ByteOrder.BIG_ENDIAN)
718 | bigEndian8.writeByte(0x01.toByte())
719 | bigEndian8.writeByte(0x02.toByte())
720 | bigEndian8.writeByte(0x03.toByte())
721 | bigEndian8.writeByte(0x04.toByte())
722 | bigEndian8.writeByte(0x05.toByte())
723 | bigEndian8.writeByte(0x06.toByte())
724 | bigEndian8.writeByte(0x07.toByte())
725 | bigEndian8.writeByte(0x08.toByte())
726 | bigEndian8.resetForRead()
727 | assertEquals(0x0102030405060708, bigEndian8.readLong())
728 | }
729 |
730 | @Test
731 | fun partialByteArray() {
732 | val byteArray = byteArrayOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
733 | val partialArray = byteArray.sliceArray(2..6)
734 | val buffer = PlatformBuffer.allocate(byteArray.size)
735 | val position = buffer.position()
736 | buffer.writeBytes(byteArray, 2, 5)
737 | val deltaPosition = buffer.position() - position
738 | assertEquals(5, deltaPosition)
739 | buffer.resetForRead()
740 | assertContentEquals(partialArray, buffer.readByteArray(5))
741 | }
742 |
743 | @Test
744 | fun wrap() {
745 | val byteArray = byteArrayOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
746 | val buffer = PlatformBuffer.wrap(byteArray)
747 | assertEquals(byteArray.size, buffer.remaining(), "remaining")
748 | assertContentEquals(byteArray, buffer.readByteArray(buffer.remaining()), "equals")
749 | buffer.resetForRead()
750 | assertEquals(byteArray.size, buffer.remaining(), "remaining")
751 | byteArray.fill(-1)
752 | val modified = byteArray.copyOf()
753 | assertContentEquals(buffer.readByteArray(buffer.remaining()), modified, "modify original")
754 | }
755 |
756 | @Test
757 | fun encoding() {
758 | val string = "yolo swag lyfestyle"
759 | var successfulCount = 0
760 | Charset.values().forEach {
761 | val stringBuffer = PlatformBuffer.allocate(80)
762 | try {
763 | stringBuffer.writeString(string, it)
764 | stringBuffer.resetForRead()
765 | assertEquals(
766 | string,
767 | stringBuffer.readString(stringBuffer.remaining(), it),
768 | it.toString(),
769 | )
770 | successfulCount++
771 | } catch (e: UnsupportedOperationException) {
772 | // unallowed type.
773 | }
774 | }
775 | assertTrue { successfulCount > 0 }
776 | }
777 |
778 | @Test
779 | fun invalidCharacterInBufferThrows() {
780 | assertFails {
781 | val buffer = PlatformBuffer.wrap(byteArrayOf(2, 126, 33, -66, -100, 4, -39, 108))
782 | buffer.readString(buffer.remaining())
783 | println("should have failed by now")
784 | }
785 | }
786 |
787 | @Test
788 | fun emptyString() {
789 | val s = "".toReadBuffer()
790 | assertEquals(0, s.position())
791 | assertEquals(0, s.limit())
792 | }
793 |
794 | @OptIn(ExperimentalStdlibApi::class)
795 | @Test
796 | fun complexReadWrapReadAgain1() {
797 | val str = "0001A960DBD8A500006500006400010A003132333435363738"
798 |
799 | @OptIn(ExperimentalStdlibApi::class)
800 | fun String.toByteArrayFromHex(): ByteArray {
801 | var value = this
802 | if (value.isEmpty()) return byteArrayOf()
803 | val length = ceil(value.length / 2.0).toInt()
804 | value = value.padStart(length * 2, '0')
805 | return value.hexToByteArray()
806 | }
807 | val hex = str.toByteArrayFromHex()
808 | assertContentEquals(
809 | hex,
810 | byteArrayOf(0, 1, -87, 96, -37, -40, -91, 0, 0, 101, 0, 0, 100, 0, 1, 10, 0, 49, 50, 51, 52, 53, 54, 55, 56),
811 | )
812 | val buf = PlatformBuffer.wrap(hex)
813 | assertBufferEquals(
814 | buf,
815 | byteArrayOf(0, 1, -87, 96, -37, -40, -91, 0, 0, 101, 0, 0, 100, 0, 1, 10, 0, 49, 50, 51, 52, 53, 54, 55, 56),
816 | )
817 | assertEquals(0, buf.position())
818 | assertEquals(25, buf.limit())
819 | val messageId = buf.readUnsignedShort()
820 | assertBufferEquals(buf, byteArrayOf(-87, 96, -37, -40, -91, 0, 0, 101, 0, 0, 100, 0, 1, 10, 0, 49, 50, 51, 52, 53, 54, 55, 56))
821 | assertEquals(messageId.toInt(), 1)
822 | assertEquals(2, buf.position())
823 | assertEquals(25, buf.limit())
824 | val cmd = buf.readByte()
825 | assertBufferEquals(buf, byteArrayOf(96, -37, -40, -91, 0, 0, 101, 0, 0, 100, 0, 1, 10, 0, 49, 50, 51, 52, 53, 54, 55, 56))
826 | assertEquals(-87, cmd)
827 | assertEquals(3, buf.position())
828 | assertEquals(25, buf.limit())
829 | val rem = buf.remaining()
830 | assertEquals(22, rem)
831 | val data = buf.readByteArray(rem)
832 | assertContentEquals(data, byteArrayOf(96, -37, -40, -91, 0, 0, 101, 0, 0, 100, 0, 1, 10, 0, 49, 50, 51, 52, 53, 54, 55, 56))
833 | val newBuffer = PlatformBuffer.wrap(data)
834 | assertEquals(0, newBuffer.position())
835 | assertEquals(22, newBuffer.limit())
836 | assertBufferEquals(newBuffer, byteArrayOf(96, -37, -40, -91, 0, 0, 101, 0, 0, 100, 0, 1, 10, 0, 49, 50, 51, 52, 53, 54, 55, 56))
837 | val b = newBuffer.readByteArray(4)
838 | assertEquals(b.toHexString().uppercase(), "60DBD8A5")
839 | }
840 |
841 | fun assertBufferEquals(
842 | b: ReadBuffer,
843 | byteArray: ByteArray,
844 | ) {
845 | val p = b.position()
846 | val l = b.limit()
847 | assertContentEquals(b.readByteArray(b.remaining()), byteArray)
848 | b.position(p)
849 | b.setLimit(l)
850 | }
851 |
852 | @Test
853 | fun wrapByteArraySharesBuffer() {
854 | val array = byteArrayOf(0, 1, 2, 3, 4, 5)
855 | val buf = PlatformBuffer.wrap(array)
856 | assertEquals(1, buf[1])
857 | array[1] = -1
858 | assertEquals(-1, buf[1])
859 | }
860 |
861 | @Test
862 | fun simpleReadWrapReadAgain() {
863 | val array = byteArrayOf(0, 1, 2, 3, 4, 5)
864 | val buf = PlatformBuffer.wrap(array)
865 | buf.readBytes(3)
866 | val bytesRead = buf.readByteArray(buf.remaining())
867 | assertEquals(buf.position(), 6)
868 | assertEquals(buf.limit(), 6)
869 | assertEquals(bytesRead.size, 3)
870 | val buffer2 = PlatformBuffer.wrap(bytesRead)
871 | assertBufferEquals(buffer2, byteArrayOf(3, 4, 5))
872 | }
873 | }
874 |
--------------------------------------------------------------------------------
/src/commonTest/kotlin/com/ditchoom/buffer/FragmentedReadBufferTests.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS")
2 |
3 | package com.ditchoom.buffer
4 |
5 | import kotlin.test.Test
6 | import kotlin.test.assertEquals
7 | import kotlin.test.assertTrue
8 |
9 | class FragmentedReadBufferTests {
10 | @Test
11 | fun readByteFromFirstBuffer() {
12 | val expectedFirstByte = Byte.MAX_VALUE
13 | val first = PlatformBuffer.allocate(1)
14 | first.writeByte(expectedFirstByte)
15 | first.resetForRead()
16 | val second = PlatformBuffer.allocate(1)
17 | val expectedSecondByte = Byte.MIN_VALUE
18 | second.writeByte(expectedSecondByte)
19 | second.resetForRead()
20 |
21 | val composableBuffer = FragmentedReadBuffer(first, second)
22 | assertEquals(expectedFirstByte, composableBuffer.readByte())
23 | }
24 |
25 | @Test
26 | fun readByteFromSecondBuffer() {
27 | val expectedFirstByte = Byte.MAX_VALUE
28 | val first = PlatformBuffer.allocate(1)
29 | first.writeByte(expectedFirstByte)
30 | first.resetForRead()
31 | val second = PlatformBuffer.allocate(1)
32 | val expectedSecondByte = Byte.MIN_VALUE
33 | second.writeByte(expectedSecondByte)
34 | second.resetForRead()
35 |
36 | val composableBuffer = FragmentedReadBuffer(first, second)
37 | composableBuffer.position(1)
38 | assertEquals(expectedSecondByte, composableBuffer.readByte())
39 | }
40 |
41 | @Test
42 | fun readShort() {
43 | val first = PlatformBuffer.allocate(Short.SIZE_BYTES)
44 | first.writeShort(1.toShort())
45 | first.resetForRead()
46 | val second = PlatformBuffer.allocate(Short.SIZE_BYTES)
47 | second.writeShort(2.toShort())
48 | second.resetForRead()
49 | val composableBuffer = FragmentedReadBuffer(first, second)
50 | assertEquals(1, composableBuffer.readShort())
51 | assertEquals(2, composableBuffer.readShort())
52 | }
53 |
54 | @Test
55 | fun readInt() {
56 | val first = PlatformBuffer.allocate(Int.SIZE_BYTES)
57 | first.writeInt(1)
58 | first.resetForRead()
59 | val second = PlatformBuffer.allocate(Int.SIZE_BYTES)
60 | second.writeInt(2)
61 | second.resetForRead()
62 | val composableBuffer = FragmentedReadBuffer(first, second)
63 | assertEquals(1, composableBuffer.readInt())
64 | assertEquals(2, composableBuffer.readInt())
65 | }
66 |
67 | @Test
68 | fun readLong() {
69 | val first = PlatformBuffer.allocate(Long.SIZE_BYTES)
70 | first.writeLong(1L)
71 | first.resetForRead()
72 | val second = PlatformBuffer.allocate(Long.SIZE_BYTES)
73 | second.writeLong(2L)
74 | second.resetForRead()
75 | val composableBuffer = FragmentedReadBuffer(first, second)
76 | assertEquals(1L, composableBuffer.readLong())
77 | assertEquals(2L, composableBuffer.readLong())
78 | }
79 |
80 | @Test
81 | fun readBytesFromThreeBuffers() {
82 | val expectedFirstByte = Byte.MAX_VALUE
83 | val first = PlatformBuffer.allocate(1)
84 | first.writeByte(expectedFirstByte)
85 | first.resetForRead()
86 | val second = PlatformBuffer.allocate(1)
87 | val expectedSecondByte = 6.toByte()
88 | second.writeByte(expectedSecondByte)
89 | second.resetForRead()
90 | val third = PlatformBuffer.allocate(1)
91 | val expectedThirdByte = Byte.MIN_VALUE
92 | third.writeByte(expectedThirdByte)
93 | third.resetForRead()
94 |
95 | val composableBuffer = arrayListOf(first, second, third).toComposableBuffer()
96 | assertEquals(expectedFirstByte, composableBuffer.readByte())
97 | assertEquals(expectedSecondByte, composableBuffer.readByte())
98 | assertEquals(expectedThirdByte, composableBuffer.readByte())
99 | }
100 |
101 | @Test
102 | fun readBytesFromFourBuffers() {
103 | val expectedFirstByte = Byte.MAX_VALUE
104 | val first = PlatformBuffer.allocate(1)
105 | first.writeByte(expectedFirstByte)
106 | first.resetForRead()
107 | val second = PlatformBuffer.allocate(1)
108 | val expectedSecondByte = 6.toByte()
109 | second.writeByte(expectedSecondByte)
110 | second.resetForRead()
111 | val third = PlatformBuffer.allocate(1)
112 | val expectedThirdByte = 12.toByte()
113 | third.writeByte(expectedThirdByte)
114 | third.resetForRead()
115 |
116 | val fourth = PlatformBuffer.allocate(1)
117 | val expectedFourthByte = Byte.MIN_VALUE
118 | fourth.writeByte(expectedFourthByte)
119 | fourth.resetForRead()
120 |
121 | val composableBuffer = arrayListOf(first, second, third, fourth).toComposableBuffer()
122 | assertEquals(expectedFirstByte, composableBuffer.readByte())
123 | assertEquals(expectedSecondByte, composableBuffer.readByte())
124 | assertEquals(expectedThirdByte, composableBuffer.readByte())
125 | assertEquals(expectedFourthByte, composableBuffer.readByte())
126 | }
127 |
128 | @Test
129 | fun readBytesFromFiveBuffers() {
130 | val expectedFirstByte = Byte.MAX_VALUE
131 | val first = PlatformBuffer.allocate(1)
132 | first.writeByte(expectedFirstByte)
133 | first.resetForRead()
134 | val second = PlatformBuffer.allocate(1)
135 | val expectedSecondByte = 6.toByte()
136 | second.writeByte(expectedSecondByte)
137 | second.resetForRead()
138 | val third = PlatformBuffer.allocate(1)
139 | val expectedThirdByte = (-1).toByte()
140 | third.writeByte(expectedThirdByte)
141 | third.resetForRead()
142 |
143 | val fourth = PlatformBuffer.allocate(1)
144 | val expectedFourthByte = 0.toByte()
145 | fourth.writeByte(expectedFourthByte)
146 | fourth.resetForRead()
147 |
148 | val fifth = PlatformBuffer.allocate(1)
149 | val expectedFifthByte = Byte.MIN_VALUE
150 | fifth.writeByte(expectedFifthByte)
151 | fifth.resetForRead()
152 |
153 | val composableBuffer = arrayListOf(first, second, third, fourth, fifth).toComposableBuffer()
154 | assertEquals(expectedFirstByte, composableBuffer.readByte())
155 | assertEquals(expectedSecondByte, composableBuffer.readByte())
156 | assertEquals(expectedThirdByte, composableBuffer.readByte())
157 | assertEquals(expectedFourthByte, composableBuffer.readByte())
158 | assertEquals(expectedFifthByte, composableBuffer.readByte())
159 | }
160 |
161 | @Test
162 | fun readUByteFromFirstBuffer() {
163 | val expectedFirstUByte = UByte.MAX_VALUE
164 | val first = PlatformBuffer.allocate(1)
165 | first.writeUByte(expectedFirstUByte)
166 | first.resetForRead()
167 | val second = PlatformBuffer.allocate(1)
168 | val expectedSecondUByte = UByte.MIN_VALUE
169 | second.writeUByte(expectedSecondUByte)
170 | second.resetForRead()
171 |
172 | val composableBuffer = FragmentedReadBuffer(first, second)
173 | assertEquals(expectedFirstUByte, composableBuffer.readUnsignedByte())
174 | }
175 |
176 | @Test
177 | fun readUByteeFromSecondBuffer() {
178 | val expectedFirstUByte = UByte.MAX_VALUE
179 | val first = PlatformBuffer.allocate(1)
180 | first.writeUByte(expectedFirstUByte)
181 | first.resetForRead()
182 | val second = PlatformBuffer.allocate(1)
183 | val expectedSecondUByte = UByte.MIN_VALUE
184 | second.writeUByte(expectedSecondUByte)
185 | second.resetForRead()
186 |
187 | val composableBuffer = FragmentedReadBuffer(first, second)
188 | composableBuffer.position(1)
189 | assertEquals(expectedSecondUByte, composableBuffer.readUnsignedByte())
190 | }
191 |
192 | @Test
193 | fun readUByteFromThreeBuffers() {
194 | val expectedFirstUByte = UByte.MAX_VALUE
195 | val first = PlatformBuffer.allocate(1)
196 | first.writeUByte(expectedFirstUByte)
197 | first.resetForRead()
198 | val second = PlatformBuffer.allocate(1)
199 | val expectedSecondUByte = 6.toUByte()
200 | second.writeUByte(expectedSecondUByte)
201 | second.resetForRead()
202 | val third = PlatformBuffer.allocate(1)
203 | val expectedThirdUByte = UByte.MIN_VALUE
204 | third.writeUByte(expectedThirdUByte)
205 | third.resetForRead()
206 |
207 | val composableBuffer = arrayListOf(first, second, third).toComposableBuffer()
208 | assertEquals(expectedFirstUByte, composableBuffer.readUnsignedByte())
209 | assertEquals(expectedSecondUByte, composableBuffer.readUnsignedByte())
210 | assertEquals(expectedThirdUByte, composableBuffer.readUnsignedByte())
211 | }
212 |
213 | @Test
214 | fun readUByteFromFourBuffers() {
215 | val expectedFirstUByte = UByte.MAX_VALUE
216 | val first = PlatformBuffer.allocate(1)
217 | first.writeUByte(expectedFirstUByte)
218 | first.resetForRead()
219 | val second = PlatformBuffer.allocate(1)
220 | val expectedSecondUByte = 6.toUByte()
221 | second.writeUByte(expectedSecondUByte)
222 | second.resetForRead()
223 | val third = PlatformBuffer.allocate(1)
224 | val expectedThirdUByte = 12.toUByte()
225 | third.writeUByte(expectedThirdUByte)
226 | third.resetForRead()
227 |
228 | val fourth = PlatformBuffer.allocate(1)
229 | val expectedFourthUByte = UByte.MIN_VALUE
230 | fourth.writeUByte(expectedFourthUByte)
231 | fourth.resetForRead()
232 |
233 | val composableBuffer = arrayListOf(first, second, third, fourth).toComposableBuffer()
234 | assertEquals(expectedFirstUByte, composableBuffer.readUnsignedByte())
235 | assertEquals(expectedSecondUByte, composableBuffer.readUnsignedByte())
236 | assertEquals(expectedThirdUByte, composableBuffer.readUnsignedByte())
237 | assertEquals(expectedFourthUByte, composableBuffer.readUnsignedByte())
238 | }
239 |
240 | @Test
241 | fun readUByteFromFiveBuffers() {
242 | val expectedFirstUByte = UByte.MAX_VALUE
243 | val first = PlatformBuffer.allocate(1)
244 | first.writeUByte(expectedFirstUByte)
245 | first.resetForRead()
246 | val second = PlatformBuffer.allocate(1)
247 | val expectedSecondUByte = 6.toUByte()
248 | second.writeUByte(expectedSecondUByte)
249 | second.resetForRead()
250 | val third = PlatformBuffer.allocate(1)
251 | val expectedThirdUByte = (-1).toUByte()
252 | third.writeUByte(expectedThirdUByte)
253 | third.resetForRead()
254 |
255 | val fourth = PlatformBuffer.allocate(1)
256 | val expectedFourthUByte = 0.toUByte()
257 | fourth.writeUByte(expectedFourthUByte)
258 | fourth.resetForRead()
259 |
260 | val fifth = PlatformBuffer.allocate(1)
261 | val expectedFifthUByte = UByte.MIN_VALUE
262 | fifth.writeUByte(expectedFifthUByte)
263 | fifth.resetForRead()
264 |
265 | val composableBuffer = arrayListOf(first, second, third, fourth, fifth).toComposableBuffer()
266 | assertEquals(expectedFirstUByte, composableBuffer.readUnsignedByte())
267 | assertEquals(expectedSecondUByte, composableBuffer.readUnsignedByte())
268 | assertEquals(expectedThirdUByte, composableBuffer.readUnsignedByte())
269 | assertEquals(expectedFourthUByte, composableBuffer.readUnsignedByte())
270 | assertEquals(expectedFifthUByte, composableBuffer.readUnsignedByte())
271 | }
272 |
273 | @Test
274 | fun readUnsignedShortFromFirstBuffer() {
275 | val expectedFirstUShort = UShort.MAX_VALUE
276 | val first = PlatformBuffer.allocate(UShort.SIZE_BYTES)
277 | first.writeUShort(expectedFirstUShort)
278 | first.resetForRead()
279 |
280 | val second = PlatformBuffer.allocate(UShort.SIZE_BYTES)
281 | val expectedSecondUShort = UShort.MIN_VALUE
282 | second.writeUShort(expectedSecondUShort)
283 | second.resetForRead()
284 |
285 | val composableBuffer = FragmentedReadBuffer(first, second)
286 | assertEquals(expectedFirstUShort, composableBuffer.readUnsignedShort())
287 | assertEquals(expectedSecondUShort, composableBuffer.readUnsignedShort())
288 | }
289 |
290 | @Test
291 | fun readUnsignedShortFromSecondBuffer() {
292 | val expectedFirstUShort = UShort.MAX_VALUE
293 | val first = PlatformBuffer.allocate(UShort.SIZE_BYTES)
294 | first.writeUShort(expectedFirstUShort)
295 | first.resetForRead()
296 | val second = PlatformBuffer.allocate(UShort.SIZE_BYTES)
297 | val expectedSecondUShort = UShort.MIN_VALUE
298 | second.writeUShort(expectedSecondUShort)
299 | second.resetForRead()
300 |
301 | val composableBuffer = FragmentedReadBuffer(first, second)
302 | composableBuffer.position(UShort.SIZE_BYTES)
303 | assertEquals(expectedSecondUShort, composableBuffer.readUnsignedShort())
304 | }
305 |
306 | @Test
307 | fun readUnsignedShortsFromThreeBuffers() {
308 | val expectedFirstUShort = UShort.MAX_VALUE
309 | val first = PlatformBuffer.allocate(UShort.SIZE_BYTES)
310 | first.writeUShort(expectedFirstUShort)
311 | first.resetForRead()
312 | val second = PlatformBuffer.allocate(UShort.SIZE_BYTES)
313 | val expectedSecondUShort = 6.toUShort()
314 | second.writeUShort(expectedSecondUShort)
315 | second.resetForRead()
316 | val third = PlatformBuffer.allocate(UShort.SIZE_BYTES)
317 | val expectedThirdUShort = UShort.MIN_VALUE
318 | third.writeUShort(expectedThirdUShort)
319 | third.resetForRead()
320 |
321 | val composableBuffer = arrayListOf(first, second, third).toComposableBuffer()
322 | assertEquals(expectedFirstUShort, composableBuffer.readUnsignedShort())
323 | assertEquals(expectedSecondUShort, composableBuffer.readUnsignedShort())
324 | assertEquals(expectedThirdUShort, composableBuffer.readUnsignedShort())
325 | }
326 |
327 | @Test
328 | fun readUnsignedShortsFromFourBuffers() {
329 | val expectedFirstUShort = UShort.MAX_VALUE
330 | val first = PlatformBuffer.allocate(UShort.SIZE_BYTES)
331 | first.writeUShort(expectedFirstUShort)
332 | first.resetForRead()
333 | val second = PlatformBuffer.allocate(UShort.SIZE_BYTES)
334 | val expectedSecondUShort = 6.toUShort()
335 | second.writeUShort(expectedSecondUShort)
336 | second.resetForRead()
337 | val third = PlatformBuffer.allocate(UShort.SIZE_BYTES)
338 | val expectedThirdUShort = 12.toUShort()
339 | third.writeUShort(expectedThirdUShort)
340 | third.resetForRead()
341 |
342 | val fourth = PlatformBuffer.allocate(UShort.SIZE_BYTES)
343 | val expectedFourthUShort = UShort.MIN_VALUE
344 | fourth.writeUShort(expectedFourthUShort)
345 | fourth.resetForRead()
346 |
347 | val composableBuffer = arrayListOf(first, second, third, fourth).toComposableBuffer()
348 | assertEquals(expectedFirstUShort, composableBuffer.readUnsignedShort())
349 | assertEquals(expectedSecondUShort, composableBuffer.readUnsignedShort())
350 | assertEquals(expectedThirdUShort, composableBuffer.readUnsignedShort())
351 | assertEquals(expectedFourthUShort, composableBuffer.readUnsignedShort())
352 | }
353 |
354 | @Test
355 | fun readUnsignedShortsFromFiveBuffers() {
356 | val expectedFirstUShort = UShort.MAX_VALUE
357 | val first = PlatformBuffer.allocate(UShort.SIZE_BYTES)
358 | first.writeUShort(expectedFirstUShort)
359 | first.resetForRead()
360 | val second = PlatformBuffer.allocate(UShort.SIZE_BYTES)
361 | val expectedSecondUShort = 6.toUShort()
362 | second.writeUShort(expectedSecondUShort)
363 | second.resetForRead()
364 | val third = PlatformBuffer.allocate(UShort.SIZE_BYTES)
365 | val expectedThirdUShort = (-1).toUShort()
366 | third.writeUShort(expectedThirdUShort)
367 | third.resetForRead()
368 |
369 | val fourth = PlatformBuffer.allocate(UShort.SIZE_BYTES)
370 | val expectedFourthUShort = 0.toUShort()
371 | fourth.writeUShort(expectedFourthUShort)
372 | fourth.resetForRead()
373 |
374 | val fifth = PlatformBuffer.allocate(UShort.SIZE_BYTES)
375 | val expectedFifthUShort = UShort.MIN_VALUE
376 | fifth.writeUShort(expectedFifthUShort)
377 | fifth.resetForRead()
378 |
379 | val composableBuffer = arrayListOf(first, second, third, fourth, fifth).toComposableBuffer()
380 | assertEquals(expectedFirstUShort, composableBuffer.readUnsignedShort())
381 | assertEquals(expectedSecondUShort, composableBuffer.readUnsignedShort())
382 | assertEquals(expectedThirdUShort, composableBuffer.readUnsignedShort())
383 | assertEquals(expectedFourthUShort, composableBuffer.readUnsignedShort())
384 | assertEquals(expectedFifthUShort, composableBuffer.readUnsignedShort())
385 | }
386 |
387 | @Test
388 | fun readFragmentedStringFromThreeBuffers() {
389 | val expectedString = "yolo-swag-lyfestyle"
390 | val utf8length = expectedString.toReadBuffer(Charset.UTF8).limit()
391 | val composableBuffer =
392 | expectedString
393 | .split(Regex("(?=-)"))
394 | .map { it.toReadBuffer(Charset.UTF8) }
395 | .toComposableBuffer()
396 | val actual = composableBuffer.readString(utf8length, Charset.UTF8)
397 | assertEquals(expectedString, actual)
398 | }
399 |
400 | @Test
401 | fun utf8Line() {
402 | val buffers = arrayOf("yolo\r\n", "\nsw\n\r\nag", "\r\nli\n\r\nfe\r\nstyle\r\n")
403 | val composableBuffer = buffers.map { it.toReadBuffer(Charset.UTF8) }.toComposableBuffer()
404 | assertEquals("yolo", composableBuffer.readUtf8Line().toString())
405 | assertEquals("", composableBuffer.readUtf8Line().toString())
406 | assertEquals("sw", composableBuffer.readUtf8Line().toString())
407 | assertEquals("", composableBuffer.readUtf8Line().toString())
408 | assertEquals("ag", composableBuffer.readUtf8Line().toString())
409 | assertEquals("li", composableBuffer.readUtf8Line().toString())
410 | assertEquals("", composableBuffer.readUtf8Line().toString())
411 | assertEquals("fe", composableBuffer.readUtf8Line().toString())
412 | assertEquals("style", composableBuffer.readUtf8Line().toString())
413 | assertEquals("", composableBuffer.readUtf8Line().toString())
414 | assertTrue { composableBuffer.remaining() == 0 }
415 | }
416 |
417 | @Test
418 | fun largeFragmentedBuffer() {
419 | val buffers = mutableListOf()
420 | var indexCount = 0
421 | do { // 64 byte chunks
422 | val buffer = PlatformBuffer.allocate(64)
423 | repeat(64 / 4) {
424 | buffer.writeInt(indexCount++)
425 | }
426 | buffers += buffer
427 | } while (indexCount < 1024 * 1024)
428 | val fragmentedBuffer = buffers.toComposableBuffer() as FragmentedReadBuffer
429 | fragmentedBuffer.resetForRead()
430 | var intCount = 0
431 | fragmentedBuffer.walk {
432 | while (it.hasRemaining()) {
433 | assertEquals(intCount++, it.readInt())
434 | }
435 | }
436 | }
437 | }
438 |
--------------------------------------------------------------------------------
/src/commonTest/kotlin/com/ditchoom/buffer/TransformedReadBufferTest.kt:
--------------------------------------------------------------------------------
1 | package com.ditchoom.buffer
2 |
3 | import kotlin.test.Test
4 | import kotlin.test.assertEquals
5 |
6 | class TransformedReadBufferTest {
7 | @Test
8 | fun byte() {
9 | val buffer = PlatformBuffer.allocate(Byte.SIZE_BYTES)
10 | buffer.writeByte(10.toByte())
11 | buffer.resetForRead()
12 | val add1TransformedReadBuffer =
13 | TransformedReadBuffer(buffer) { _, byte ->
14 | (byte + 1).toByte()
15 | }
16 | assertEquals(11.toByte(), add1TransformedReadBuffer.readByte())
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/jsMain/kotlin/com/ditchoom/buffer/BufferFactory.kt:
--------------------------------------------------------------------------------
1 | package com.ditchoom.buffer
2 |
3 | import js.buffer.SharedArrayBuffer
4 | import org.khronos.webgl.ArrayBuffer
5 | import org.khronos.webgl.Int8Array
6 |
7 | fun PlatformBuffer.Companion.allocate(
8 | size: Int,
9 | byteOrder: ByteOrder,
10 | ) = allocate(size, AllocationZone.SharedMemory, byteOrder)
11 |
12 | actual fun PlatformBuffer.Companion.allocate(
13 | size: Int,
14 | zone: AllocationZone,
15 | byteOrder: ByteOrder,
16 | ): PlatformBuffer {
17 | if (zone is AllocationZone.Custom) {
18 | return zone.allocator(size)
19 | }
20 | val sharedArrayBuffer =
21 | try {
22 | if (zone is AllocationZone.SharedMemory) {
23 | SharedArrayBuffer(size)
24 | } else {
25 | null
26 | }
27 | } catch (t: Throwable) {
28 | null
29 | }
30 | if (sharedArrayBuffer == null && zone is AllocationZone.SharedMemory) {
31 | console.warn(
32 | "Failed to allocate shared buffer in BufferFactory.kt. Please check and validate the " +
33 | "appropriate headers are set on the http request as defined in the SharedArrayBuffer MDN docs." +
34 | "see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects" +
35 | "/SharedArrayBuffer#security_requirements",
36 | )
37 | }
38 | return if (sharedArrayBuffer != null) {
39 | val arrayBuffer = sharedArrayBuffer.unsafeCast().slice(0, size)
40 | JsBuffer(
41 | Int8Array(arrayBuffer),
42 | littleEndian = byteOrder == ByteOrder.LITTLE_ENDIAN,
43 | sharedArrayBuffer = sharedArrayBuffer,
44 | )
45 | } else {
46 | JsBuffer(Int8Array(size), littleEndian = byteOrder == ByteOrder.LITTLE_ENDIAN)
47 | }
48 | }
49 |
50 | actual fun PlatformBuffer.Companion.wrap(
51 | array: ByteArray,
52 | byteOrder: ByteOrder,
53 | ): PlatformBuffer =
54 | JsBuffer(
55 | array.unsafeCast(),
56 | littleEndian = byteOrder == ByteOrder.LITTLE_ENDIAN,
57 | )
58 |
--------------------------------------------------------------------------------
/src/jsMain/kotlin/com/ditchoom/buffer/JsBuffer.kt:
--------------------------------------------------------------------------------
1 | package com.ditchoom.buffer
2 |
3 | import js.buffer.BufferSource
4 | import js.buffer.SharedArrayBuffer
5 | import org.khronos.webgl.DataView
6 | import org.khronos.webgl.Int8Array
7 | import web.encoding.TextDecoder
8 | import web.encoding.TextDecoderOptions
9 |
10 | data class JsBuffer(
11 | val buffer: Int8Array,
12 | // network endian is big endian
13 | private val littleEndian: Boolean = false,
14 | private var position: Int = 0,
15 | private var limit: Int = 0,
16 | override val capacity: Int = buffer.byteLength,
17 | val sharedArrayBuffer: SharedArrayBuffer? = null,
18 | ) : PlatformBuffer {
19 | override val byteOrder = if (littleEndian) ByteOrder.LITTLE_ENDIAN else ByteOrder.BIG_ENDIAN
20 |
21 | init {
22 | limit = buffer.length
23 | }
24 |
25 | override fun resetForRead() {
26 | limit = position
27 | position = 0
28 | }
29 |
30 | override fun resetForWrite() {
31 | position = 0
32 | limit = capacity
33 | }
34 |
35 | override fun setLimit(limit: Int) {
36 | this.limit = limit
37 | }
38 |
39 | fun setPosition(position: Int) {
40 | this.position = position
41 | }
42 |
43 | override fun readByte(): Byte {
44 | val dataView = DataView(buffer.buffer, position++, 1)
45 | return dataView.getInt8(0)
46 | }
47 |
48 | override fun get(index: Int): Byte {
49 | val dataView = DataView(buffer.buffer, index, 1)
50 | return dataView.getInt8(0)
51 | }
52 |
53 | override fun slice(): ReadBuffer {
54 | return JsBuffer(
55 | Int8Array(buffer.buffer.slice(position, limit)),
56 | littleEndian,
57 | sharedArrayBuffer = sharedArrayBuffer,
58 | )
59 | }
60 |
61 | override fun readByteArray(size: Int): ByteArray {
62 | val subArray = buffer.subarray(position, position + size)
63 | val byteArray = Int8Array(subArray.buffer, subArray.byteOffset, size)
64 | position += size
65 | return byteArray.unsafeCast()
66 | }
67 |
68 | override fun readShort(): Short {
69 | val dataView = DataView(buffer.buffer, position, Short.SIZE_BYTES)
70 | position += Short.SIZE_BYTES
71 | return dataView.getInt16(0, littleEndian).toInt().toShort()
72 | }
73 |
74 | override fun getShort(index: Int): Short {
75 | val dataView = DataView(buffer.buffer, index, Short.SIZE_BYTES)
76 | return dataView.getInt16(0, littleEndian).toInt().toShort()
77 | }
78 |
79 | override fun readInt(): Int {
80 | val dataView = DataView(buffer.buffer, position, Int.SIZE_BYTES)
81 | position += Int.SIZE_BYTES
82 | return dataView.getInt32(0, littleEndian)
83 | }
84 |
85 | override fun getInt(index: Int): Int {
86 | val dataView = DataView(buffer.buffer, index, Int.SIZE_BYTES)
87 | return dataView.getInt32(0, littleEndian)
88 | }
89 |
90 | override fun readLong(): Long {
91 | val bytes = readByteArray(Long.SIZE_BYTES)
92 | return if (littleEndian) bytes.reversedArray().toLong() else bytes.toLong()
93 | }
94 |
95 | override fun getLong(index: Int): Long {
96 | val bytes = Int8Array(buffer.buffer, index, Long.SIZE_BYTES).unsafeCast()
97 | return if (littleEndian) bytes.reversedArray().toLong() else bytes.toLong()
98 | }
99 |
100 | private fun ByteArray.toLong(): Long {
101 | var result: Long = 0
102 | (0 until Long.SIZE_BYTES).forEach {
103 | result = result shl Long.SIZE_BYTES
104 | result = result or (this[it].toLong() and 0xFFL)
105 | }
106 | return result
107 | }
108 |
109 | override fun readString(
110 | length: Int,
111 | charset: Charset,
112 | ): String {
113 | val encoding =
114 | when (charset) {
115 | Charset.UTF8 -> "utf-8"
116 | Charset.UTF16 -> throw UnsupportedOperationException("Not sure how to implement")
117 | Charset.UTF16BigEndian -> "utf-16be"
118 | Charset.UTF16LittleEndian -> "utf-16le"
119 | Charset.ASCII -> "ascii"
120 | Charset.ISOLatin1 -> "iso-8859-1"
121 | Charset.UTF32 -> throw UnsupportedOperationException("Not sure how to implement")
122 | Charset.UTF32LittleEndian -> throw UnsupportedOperationException("Not sure how to implement")
123 | Charset.UTF32BigEndian -> throw UnsupportedOperationException("Not sure how to implement")
124 | }
125 |
126 | @Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE")
127 | val textDecoder = TextDecoder(encoding, js("{fatal: true}") as TextDecoderOptions)
128 | val result =
129 | textDecoder.decode(
130 | buffer.subarray(position, position + length).unsafeCast(),
131 | )
132 | position(position + length)
133 | return result
134 | }
135 |
136 | override fun write(buffer: ReadBuffer) {
137 | val size = buffer.remaining()
138 | if (buffer is JsBuffer) {
139 | this.buffer.set(buffer.buffer, position)
140 | } else {
141 | this.buffer.set(buffer.readByteArray(size).toTypedArray(), position)
142 | }
143 | position += size
144 | buffer.position(buffer.position() + size)
145 | }
146 |
147 | override fun writeByte(byte: Byte): WriteBuffer {
148 | val dataView = DataView(buffer.buffer, position++, Byte.SIZE_BYTES)
149 | dataView.setInt8(0, byte)
150 | return this
151 | }
152 |
153 | override fun set(
154 | index: Int,
155 | byte: Byte,
156 | ): WriteBuffer {
157 | val dataView = DataView(buffer.buffer, index, Byte.SIZE_BYTES)
158 | dataView.setInt8(0, byte)
159 | return this
160 | }
161 |
162 | override fun writeBytes(
163 | bytes: ByteArray,
164 | offset: Int,
165 | length: Int,
166 | ): WriteBuffer {
167 | val int8Array = bytes.unsafeCast().subarray(offset, offset + length)
168 | this.buffer.set(int8Array, position)
169 | position += int8Array.length
170 | return this
171 | }
172 |
173 | override fun writeShort(short: Short): WriteBuffer {
174 | val dataView = DataView(buffer.buffer, position, Short.SIZE_BYTES)
175 | position += UShort.SIZE_BYTES
176 | dataView.setUint16(0, short, littleEndian)
177 | return this
178 | }
179 |
180 | override fun set(
181 | index: Int,
182 | short: Short,
183 | ): WriteBuffer {
184 | val dataView = DataView(buffer.buffer, index, Short.SIZE_BYTES)
185 | dataView.setUint16(0, short, littleEndian)
186 | return this
187 | }
188 |
189 | override fun writeInt(int: Int): WriteBuffer {
190 | val dataView = DataView(buffer.buffer, position, UInt.SIZE_BYTES)
191 | position += UInt.SIZE_BYTES
192 | dataView.setInt32(0, int, littleEndian)
193 | return this
194 | }
195 |
196 | override fun set(
197 | index: Int,
198 | int: Int,
199 | ): WriteBuffer {
200 | val dataView = DataView(buffer.buffer, index, UInt.SIZE_BYTES)
201 | dataView.setUint32(0, int, littleEndian)
202 | return this
203 | }
204 |
205 | override fun writeLong(long: Long): WriteBuffer {
206 | val bytes = if (littleEndian) long.toByteArray().reversedArray() else long.toByteArray()
207 | writeBytes(bytes)
208 | return this
209 | }
210 |
211 | override fun set(
212 | index: Int,
213 | long: Long,
214 | ): WriteBuffer {
215 | val bytes = if (littleEndian) long.toByteArray().reversedArray() else long.toByteArray()
216 | val int8Array = bytes.unsafeCast().subarray(0, Long.SIZE_BYTES)
217 | this.buffer.set(int8Array, index)
218 | return this
219 | }
220 |
221 | private fun Long.toByteArray(): ByteArray {
222 | var l = this
223 | val result = ByteArray(8)
224 | for (i in 7 downTo 0) {
225 | result[i] = l.toByte()
226 | l = l shr 8
227 | }
228 | return result
229 | }
230 |
231 | override fun writeString(
232 | text: CharSequence,
233 | charset: Charset,
234 | ): WriteBuffer {
235 | when (charset) {
236 | Charset.UTF8 -> writeBytes(text.toString().encodeToByteArray())
237 | else -> throw UnsupportedOperationException("Unable to encode in $charset. Must use Charset.UTF8")
238 | }
239 | return this
240 | }
241 |
242 | override fun limit() = limit
243 |
244 | override fun position() = position
245 |
246 | override fun position(newPosition: Int) {
247 | position = newPosition
248 | }
249 |
250 | override suspend fun close() {}
251 |
252 | override fun equals(other: Any?): Boolean {
253 | if (this === other) return true
254 | if (other == null || this::class.js != other::class.js) return false
255 |
256 | other as JsBuffer
257 |
258 | if (littleEndian != other.littleEndian) return false
259 | if (position != other.position) return false
260 | if (limit != other.limit) return false
261 | if (capacity != other.capacity) return false
262 | val size = remaining()
263 | try {
264 | if (!readByteArray(size).contentEquals(other.readByteArray(size))) return false
265 | } finally {
266 | position -= size
267 | other.position -= size
268 | }
269 | return true
270 | }
271 |
272 | override fun hashCode(): Int {
273 | var result = littleEndian.hashCode()
274 | result = 31 * result + position
275 | result = 31 * result + limit
276 | result = 31 * result + capacity.hashCode()
277 | val size = remaining()
278 | try {
279 | result = 31 * result + readByteArray(size).hashCode()
280 | } finally {
281 | position -= size
282 | }
283 | return result
284 | }
285 | }
286 |
--------------------------------------------------------------------------------
/src/jsMain/kotlin/com/ditchoom/buffer/Parcelable.kt:
--------------------------------------------------------------------------------
1 | package com.ditchoom.buffer
2 |
3 | @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
4 | actual interface Parcelable
5 |
--------------------------------------------------------------------------------
/src/jvmMain/kotlin/com/ditchoom/buffer/BaseJvmBuffer.kt:
--------------------------------------------------------------------------------
1 | package com.ditchoom.buffer
2 |
3 | import java.io.RandomAccessFile
4 | import java.nio.Buffer
5 | import java.nio.ByteBuffer
6 | import java.nio.CharBuffer
7 | import java.nio.charset.CodingErrorAction
8 | import kotlin.coroutines.resume
9 | import kotlin.coroutines.resumeWithException
10 | import kotlin.coroutines.suspendCoroutine
11 |
12 | abstract class BaseJvmBuffer(val byteBuffer: ByteBuffer, val fileRef: RandomAccessFile? = null) :
13 | PlatformBuffer {
14 | override val byteOrder =
15 | when (byteBuffer.order()) {
16 | java.nio.ByteOrder.BIG_ENDIAN -> ByteOrder.BIG_ENDIAN
17 | java.nio.ByteOrder.LITTLE_ENDIAN -> ByteOrder.LITTLE_ENDIAN
18 | else -> ByteOrder.BIG_ENDIAN
19 | }
20 |
21 | // Use Buffer reference to avoid NoSuchMethodException between JVM. see https://stackoverflow.com/q/61267495
22 | private val buffer = byteBuffer as Buffer
23 |
24 | override fun resetForRead() {
25 | buffer.flip()
26 | }
27 |
28 | override fun resetForWrite() {
29 | buffer.clear()
30 | }
31 |
32 | override fun setLimit(limit: Int) {
33 | buffer.limit(limit)
34 | }
35 |
36 | override val capacity = buffer.capacity()
37 |
38 | override fun readByte() = byteBuffer.get()
39 |
40 | override fun get(index: Int): Byte = byteBuffer.get(index)
41 |
42 | override fun readByteArray(size: Int) = byteBuffer.toArray(size)
43 |
44 | override fun slice() = JvmBuffer(byteBuffer.slice())
45 |
46 | override fun readShort(): Short = byteBuffer.short
47 |
48 | override fun getShort(index: Int): Short = byteBuffer.getShort(index)
49 |
50 | override fun readInt() = byteBuffer.int
51 |
52 | override fun getInt(index: Int): Int = byteBuffer.getInt(index)
53 |
54 | override fun readLong() = byteBuffer.long
55 |
56 | override fun getLong(index: Int): Long = byteBuffer.getLong(index)
57 |
58 | override fun readString(
59 | length: Int,
60 | charset: Charset,
61 | ): String {
62 | val finalPosition = buffer.position() + length
63 | val readBuffer = byteBuffer.asReadOnlyBuffer()
64 | (readBuffer as Buffer).limit(finalPosition)
65 | val charsetConverted =
66 | when (charset) {
67 | Charset.UTF8 -> Charsets.UTF_8
68 | Charset.UTF16 -> Charsets.UTF_16
69 | Charset.UTF16BigEndian -> Charsets.UTF_16BE
70 | Charset.UTF16LittleEndian -> Charsets.UTF_16LE
71 | Charset.ASCII -> Charsets.US_ASCII
72 | Charset.ISOLatin1 -> Charsets.ISO_8859_1
73 | Charset.UTF32 -> Charsets.UTF_32
74 | Charset.UTF32LittleEndian -> Charsets.UTF_32LE
75 | Charset.UTF32BigEndian -> Charsets.UTF_32BE
76 | }
77 | val decoded =
78 | charsetConverted.newDecoder()
79 | .onMalformedInput(CodingErrorAction.REPORT)
80 | .onUnmappableCharacter(CodingErrorAction.REPORT)
81 | .decode(readBuffer)
82 | buffer.position(finalPosition)
83 | return decoded.toString()
84 | }
85 |
86 | override fun writeByte(byte: Byte): WriteBuffer {
87 | byteBuffer.put(byte)
88 | return this
89 | }
90 |
91 | override fun set(
92 | index: Int,
93 | byte: Byte,
94 | ): WriteBuffer {
95 | byteBuffer.put(index, byte)
96 | return this
97 | }
98 |
99 | override fun writeBytes(
100 | bytes: ByteArray,
101 | offset: Int,
102 | length: Int,
103 | ): WriteBuffer {
104 | byteBuffer.put(bytes, offset, length)
105 | return this
106 | }
107 |
108 | override fun writeShort(short: Short): WriteBuffer {
109 | byteBuffer.putShort(short)
110 | return this
111 | }
112 |
113 | override fun set(
114 | index: Int,
115 | short: Short,
116 | ): WriteBuffer {
117 | byteBuffer.putShort(index, short)
118 | return this
119 | }
120 |
121 | override fun writeInt(int: Int): WriteBuffer {
122 | byteBuffer.putInt(int)
123 | return this
124 | }
125 |
126 | override fun set(
127 | index: Int,
128 | int: Int,
129 | ): WriteBuffer {
130 | byteBuffer.putInt(index, int)
131 | return this
132 | }
133 |
134 | override fun writeLong(long: Long): WriteBuffer {
135 | byteBuffer.putLong(long)
136 | return this
137 | }
138 |
139 | override fun set(
140 | index: Int,
141 | long: Long,
142 | ): WriteBuffer {
143 | byteBuffer.putLong(index, long)
144 | return this
145 | }
146 |
147 | override fun writeString(
148 | text: CharSequence,
149 | charset: Charset,
150 | ): WriteBuffer {
151 | val encoder = charset.toEncoder()
152 | encoder.reset()
153 | encoder.encode(CharBuffer.wrap(text), byteBuffer, true)
154 | return this
155 | }
156 |
157 | override fun write(buffer: ReadBuffer) {
158 | if (buffer is JvmBuffer) {
159 | byteBuffer.put(buffer.byteBuffer)
160 | } else {
161 | byteBuffer.put(buffer.readByteArray(buffer.remaining()))
162 | }
163 | }
164 |
165 | override fun position(newPosition: Int) {
166 | buffer.position(newPosition)
167 | }
168 |
169 | override fun equals(other: Any?): Boolean {
170 | if (other !is PlatformBuffer) return false
171 | if (position() != other.position()) return false
172 | if (limit() != other.limit()) return false
173 | if (capacity != other.capacity) return false
174 | return true
175 | }
176 |
177 | override fun toString() = "Buffer[pos=${position()} lim=${limit()} cap=$capacity]"
178 |
179 | override suspend fun close() {
180 | fileRef?.aClose()
181 | }
182 |
183 | override fun limit() = buffer.limit()
184 |
185 | override fun position() = buffer.position()
186 | }
187 |
188 | suspend fun RandomAccessFile.aClose() =
189 | suspendCoroutine {
190 | try {
191 | // TODO: fix the blocking call
192 | @Suppress("BlockingMethodInNonBlockingContext")
193 | close()
194 | it.resume(Unit)
195 | } catch (e: Throwable) {
196 | it.resumeWithException(e)
197 | }
198 | }
199 |
200 | fun ByteBuffer.toArray(size: Int = remaining()): ByteArray {
201 | return if (hasArray()) {
202 | val result = ByteArray(size)
203 | val buffer = this as Buffer
204 | System.arraycopy(this.array(), buffer.arrayOffset() + buffer.position(), result, 0, size)
205 | buffer.position(buffer.position() + size)
206 | result
207 | } else {
208 | val byteArray = ByteArray(size)
209 | get(byteArray)
210 | byteArray
211 | }
212 | }
213 |
--------------------------------------------------------------------------------
/src/jvmMain/kotlin/com/ditchoom/buffer/BufferFactory.kt:
--------------------------------------------------------------------------------
1 | @file:JvmName("BufferFactoryJvm")
2 |
3 | package com.ditchoom.buffer
4 |
5 | import java.nio.ByteBuffer
6 |
7 | actual fun PlatformBuffer.Companion.allocate(
8 | size: Int,
9 | zone: AllocationZone,
10 | byteOrder: ByteOrder,
11 | ): PlatformBuffer {
12 | val byteOrderNative =
13 | when (byteOrder) {
14 | ByteOrder.BIG_ENDIAN -> java.nio.ByteOrder.BIG_ENDIAN
15 | ByteOrder.LITTLE_ENDIAN -> java.nio.ByteOrder.LITTLE_ENDIAN
16 | }
17 | return when (zone) {
18 | AllocationZone.Heap -> JvmBuffer(ByteBuffer.allocate(size).order(byteOrderNative))
19 | AllocationZone.SharedMemory,
20 | AllocationZone.Direct,
21 | -> JvmBuffer(ByteBuffer.allocateDirect(size).order(byteOrderNative))
22 |
23 | is AllocationZone.Custom -> zone.allocator(size)
24 | }
25 | }
26 |
27 | actual fun PlatformBuffer.Companion.wrap(
28 | array: ByteArray,
29 | byteOrder: ByteOrder,
30 | ): PlatformBuffer {
31 | val byteOrderNative =
32 | when (byteOrder) {
33 | ByteOrder.BIG_ENDIAN -> java.nio.ByteOrder.BIG_ENDIAN
34 | ByteOrder.LITTLE_ENDIAN -> java.nio.ByteOrder.LITTLE_ENDIAN
35 | }
36 | return JvmBuffer(ByteBuffer.wrap(array).order(byteOrderNative))
37 | }
38 |
--------------------------------------------------------------------------------
/src/jvmMain/kotlin/com/ditchoom/buffer/CharsetEncoderHelper.kt:
--------------------------------------------------------------------------------
1 | package com.ditchoom.buffer
2 |
3 | import java.nio.charset.CharsetEncoder
4 |
5 | fun Charset.toEncoder(): CharsetEncoder =
6 | when (this) {
7 | Charset.UTF8 -> utf8Encoder
8 | Charset.UTF16 -> utf16Encoder
9 | Charset.UTF16BigEndian -> utf16BEEncoder
10 | Charset.UTF16LittleEndian -> utf16LEEncoder
11 | Charset.ASCII -> utfAsciiEncoder
12 | Charset.ISOLatin1 -> utfISOLatin1Encoder
13 | Charset.UTF32 -> utf32Encoder
14 | Charset.UTF32LittleEndian -> utf32LEEncoder
15 | Charset.UTF32BigEndian -> utf32BEEncoder
16 | }.get()
17 |
18 | internal class DefaultEncoder(private val charset: java.nio.charset.Charset) :
19 | ThreadLocal() {
20 | override fun initialValue(): CharsetEncoder? = charset.newEncoder()
21 |
22 | override fun get(): CharsetEncoder = super.get()!!
23 | }
24 |
25 | internal val utf8Encoder = DefaultEncoder(Charsets.UTF_8)
26 | internal val utf16Encoder = DefaultEncoder(Charsets.UTF_16)
27 | internal val utf16BEEncoder = DefaultEncoder(Charsets.UTF_16BE)
28 | internal val utf16LEEncoder = DefaultEncoder(Charsets.UTF_16LE)
29 | internal val utfAsciiEncoder = DefaultEncoder(Charsets.US_ASCII)
30 | internal val utfISOLatin1Encoder = DefaultEncoder(Charsets.ISO_8859_1)
31 | internal val utf32Encoder = DefaultEncoder(Charsets.UTF_32)
32 | internal val utf32LEEncoder = DefaultEncoder(Charsets.UTF_32LE)
33 | internal val utf32BEEncoder = DefaultEncoder(Charsets.UTF_32BE)
34 |
--------------------------------------------------------------------------------
/src/jvmMain/kotlin/com/ditchoom/buffer/JvmBuffer.kt:
--------------------------------------------------------------------------------
1 | package com.ditchoom.buffer
2 |
3 | import java.io.RandomAccessFile
4 | import java.nio.ByteBuffer
5 |
6 | class JvmBuffer(byteBuffer: ByteBuffer, fileRef: RandomAccessFile? = null) :
7 | BaseJvmBuffer(byteBuffer, fileRef)
8 |
--------------------------------------------------------------------------------
/src/jvmMain/kotlin/com/ditchoom/buffer/Parcelable.kt:
--------------------------------------------------------------------------------
1 | package com.ditchoom.buffer
2 |
3 | @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
4 | actual interface Parcelable
5 |
--------------------------------------------------------------------------------
/src/linuxMain/kotlin/com/ditchoom/buffer/BufferFactory.kt:
--------------------------------------------------------------------------------
1 | package com.ditchoom.buffer
2 |
3 | actual fun PlatformBuffer.Companion.allocate(
4 | size: Int,
5 | zone: AllocationZone,
6 | byteOrder: ByteOrder,
7 | ): PlatformBuffer {
8 | if (zone is AllocationZone.Custom) {
9 | return zone.allocator(size)
10 | }
11 | return NativeBuffer(ByteArray(size), byteOrder = byteOrder)
12 | }
13 |
14 | actual fun PlatformBuffer.Companion.wrap(
15 | array: ByteArray,
16 | byteOrder: ByteOrder,
17 | ): PlatformBuffer = NativeBuffer(array, byteOrder = byteOrder)
18 |
--------------------------------------------------------------------------------
/src/nativeMain/kotlin/buffer/NativeBuffer.kt:
--------------------------------------------------------------------------------
1 | package com.ditchoom.buffer
2 |
3 | data class NativeBuffer(
4 | val data: ByteArray,
5 | private var position: Int = 0,
6 | private var limit: Int = data.size,
7 | override val capacity: Int = data.size,
8 | override val byteOrder: ByteOrder,
9 | ) : PlatformBuffer {
10 | override fun resetForRead() {
11 | limit = position
12 | position = 0
13 | }
14 |
15 | override fun resetForWrite() {
16 | position = 0
17 | limit = data.size
18 | }
19 |
20 | override fun setLimit(limit: Int) {
21 | this.limit = limit
22 | }
23 |
24 | override fun readByte() = data[position++]
25 |
26 | override fun get(index: Int): Byte = data[index]
27 |
28 | override fun slice(): ReadBuffer {
29 | return NativeBuffer(data.sliceArray(position until limit), byteOrder = byteOrder)
30 | }
31 |
32 | override fun readByteArray(size: Int): ByteArray {
33 | val result = data.copyOfRange(position, position + size)
34 | position += size
35 | return result
36 | }
37 |
38 | override fun readString(
39 | length: Int,
40 | charset: Charset,
41 | ): String {
42 | val value =
43 | when (charset) {
44 | Charset.UTF8 -> data.decodeToString(position, position + length, throwOnInvalidSequence = true)
45 | Charset.UTF16 -> throw UnsupportedOperationException("Not sure how to implement.")
46 | Charset.UTF16BigEndian -> throw UnsupportedOperationException("Not sure how to implement.")
47 | Charset.UTF16LittleEndian -> throw UnsupportedOperationException("Not sure how to implement.")
48 | Charset.ASCII -> throw UnsupportedOperationException("Not sure how to implement.")
49 | Charset.ISOLatin1 -> throw UnsupportedOperationException("Not sure how to implement.")
50 | Charset.UTF32 -> throw UnsupportedOperationException("Not sure how to implement.")
51 | Charset.UTF32LittleEndian -> throw UnsupportedOperationException("Not sure how to implement.")
52 | Charset.UTF32BigEndian -> throw UnsupportedOperationException("Not sure how to implement.")
53 | }
54 | position += length
55 | return value
56 | }
57 |
58 | override fun writeByte(byte: Byte): WriteBuffer {
59 | data[position++] = byte
60 | return this
61 | }
62 |
63 | override fun set(
64 | index: Int,
65 | byte: Byte,
66 | ): WriteBuffer {
67 | data[index] = byte
68 | return this
69 | }
70 |
71 | override fun writeBytes(
72 | bytes: ByteArray,
73 | offset: Int,
74 | length: Int,
75 | ): WriteBuffer {
76 | bytes.copyInto(data, position, offset, offset + length)
77 | position += length
78 | return this
79 | }
80 |
81 | override fun write(buffer: ReadBuffer) {
82 | val start = position()
83 | val byteSize =
84 | if (buffer is NativeBuffer) {
85 | writeBytes(buffer.data)
86 | buffer.data.size
87 | } else {
88 | val numBytes = buffer.remaining()
89 | writeBytes(buffer.readByteArray(numBytes))
90 | numBytes
91 | }
92 | buffer.position(start + byteSize)
93 | }
94 |
95 | override fun writeString(
96 | text: CharSequence,
97 | charset: Charset,
98 | ): WriteBuffer {
99 | when (charset) {
100 | Charset.UTF8 -> writeBytes(text.toString().encodeToByteArray())
101 | else -> throw UnsupportedOperationException("Unable to encode in $charset. Must use Charset.UTF8")
102 | }
103 | return this
104 | }
105 |
106 | override suspend fun close() = Unit
107 |
108 | override fun limit() = limit
109 |
110 | override fun position() = position
111 |
112 | override fun position(newPosition: Int) {
113 | position = newPosition
114 | }
115 |
116 | override fun equals(other: Any?): Boolean {
117 | if (this === other) return true
118 | other as NativeBuffer
119 | if (position != other.position) return false
120 | if (limit != other.limit) return false
121 | if (capacity != other.capacity) return false
122 | if (!data.contentEquals(other.data)) return false
123 | return true
124 | }
125 |
126 | override fun hashCode(): Int {
127 | var result = position.hashCode()
128 | result = 31 * result + limit.hashCode()
129 | result = 31 * result + capacity.hashCode()
130 | result = 31 * result + data.contentHashCode()
131 | return result
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/src/nativeMain/kotlin/buffer/Parcelable.kt:
--------------------------------------------------------------------------------
1 | package com.ditchoom.buffer
2 |
3 | @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
4 | actual interface Parcelable
5 |
--------------------------------------------------------------------------------
/webpack.config.d/config.js:
--------------------------------------------------------------------------------
1 | if (config.devServer != null) {
2 | config.devServer.headers = {
3 | "Cross-Origin-Opener-Policy": "same-origin",
4 | "Cross-Origin-Embedder-Policy": "require-corp"
5 | }
6 | }
--------------------------------------------------------------------------------