Răsfoiți Sursa

Add missing files for v5.7.0

Threema 10 luni în urmă
părinte
comite
b28d3ad71b
64 a modificat fișierele cu 5663 adăugiri și 1 ștergeri
  1. 0 1
      .gitignore
  2. 155 0
      domain/libthreema/patches/blake2/CHANGELOG.md
  3. 28 0
      domain/libthreema/patches/blake2/Cargo.toml
  4. 201 0
      domain/libthreema/patches/blake2/LICENSE-APACHE
  5. 26 0
      domain/libthreema/patches/blake2/LICENSE-MIT
  6. 65 0
      domain/libthreema/patches/blake2/README.md
  7. 22 0
      domain/libthreema/patches/blake2/benches/mod.rs
  8. 34 0
      domain/libthreema/patches/blake2/src/as_bytes.rs
  9. 47 0
      domain/libthreema/patches/blake2/src/consts.rs
  10. 172 0
      domain/libthreema/patches/blake2/src/lib.rs
  11. 446 0
      domain/libthreema/patches/blake2/src/macros.rs
  12. 142 0
      domain/libthreema/patches/blake2/src/simd.rs
  13. 52 0
      domain/libthreema/patches/blake2/src/simd/simd_opt.rs
  14. 69 0
      domain/libthreema/patches/blake2/src/simd/simd_opt/u32x4.rs
  15. 143 0
      domain/libthreema/patches/blake2/src/simd/simd_opt/u64x4.rs
  16. 22 0
      domain/libthreema/patches/blake2/src/simd/simdint.rs
  17. 103 0
      domain/libthreema/patches/blake2/src/simd/simdop.rs
  18. 77 0
      domain/libthreema/patches/blake2/src/simd/simdty.rs
  19. BIN
      domain/libthreema/patches/blake2/tests/data/blake2b/fixed.blb
  20. BIN
      domain/libthreema/patches/blake2/tests/data/blake2b/mac.blb
  21. BIN
      domain/libthreema/patches/blake2/tests/data/blake2b/variable.blb
  22. BIN
      domain/libthreema/patches/blake2/tests/data/blake2s/mac.blb
  23. BIN
      domain/libthreema/patches/blake2/tests/data/blake2s/variable.blb
  24. 29 0
      domain/libthreema/patches/blake2/tests/mac.rs
  25. 19 0
      domain/libthreema/patches/blake2/tests/mod.rs
  26. 40 0
      domain/libthreema/patches/blake2/tests/persona.rs
  27. 28 0
      domain/libthreema/patches/blake2/tests/unkeyed.rs
  28. 2 0
      domain/libthreema/patches/tsify/.gitignore
  29. 42 0
      domain/libthreema/patches/tsify/Cargo.toml
  30. 176 0
      domain/libthreema/patches/tsify/LICENSE_APACHE
  31. 25 0
      domain/libthreema/patches/tsify/LICENSE_MIT
  32. 245 0
      domain/libthreema/patches/tsify/README.md
  33. 50 0
      domain/libthreema/patches/tsify/src/lib.rs
  34. 8 0
      domain/libthreema/patches/tsify/test.sh
  35. 321 0
      domain/libthreema/patches/tsify/tests/enum.rs
  36. 75 0
      domain/libthreema/patches/tsify/tests/expand/borrow.expanded.rs
  37. 9 0
      domain/libthreema/patches/tsify/tests/expand/borrow.rs
  38. 76 0
      domain/libthreema/patches/tsify/tests/expand/generic_enum.expanded.rs
  39. 10 0
      domain/libthreema/patches/tsify/tests/expand/generic_enum.rs
  40. 143 0
      domain/libthreema/patches/tsify/tests/expand/generic_struct.expanded.rs
  41. 11 0
      domain/libthreema/patches/tsify/tests/expand/generic_struct.rs
  42. 7 0
      domain/libthreema/patches/tsify/tests/expand/type_alias.expanded.rs
  43. 2 0
      domain/libthreema/patches/tsify/tests/expand/type_alias.rs
  44. 4 0
      domain/libthreema/patches/tsify/tests/expandtest.rs
  45. 53 0
      domain/libthreema/patches/tsify/tests/flatten.rs
  46. 86 0
      domain/libthreema/patches/tsify/tests/generics.rs
  47. 74 0
      domain/libthreema/patches/tsify/tests/optional.rs
  48. 121 0
      domain/libthreema/patches/tsify/tests/rename.rs
  49. 55 0
      domain/libthreema/patches/tsify/tests/skip.rs
  50. 129 0
      domain/libthreema/patches/tsify/tests/struct.rs
  51. 22 0
      domain/libthreema/patches/tsify/tests/transparent.rs
  52. 76 0
      domain/libthreema/patches/tsify/tests/type_override.rs
  53. 20 0
      domain/libthreema/patches/tsify/tests/wasm.rs
  54. 25 0
      domain/libthreema/patches/tsify/tsify-macros/Cargo.toml
  55. 113 0
      domain/libthreema/patches/tsify/tsify-macros/src/attrs.rs
  56. 78 0
      domain/libthreema/patches/tsify/tsify-macros/src/container.rs
  57. 40 0
      domain/libthreema/patches/tsify/tsify-macros/src/ctxt.rs
  58. 268 0
      domain/libthreema/patches/tsify/tsify-macros/src/decl.rs
  59. 50 0
      domain/libthreema/patches/tsify/tsify-macros/src/derive.rs
  60. 48 0
      domain/libthreema/patches/tsify/tsify-macros/src/lib.rs
  61. 291 0
      domain/libthreema/patches/tsify/tsify-macros/src/parser.rs
  62. 41 0
      domain/libthreema/patches/tsify/tsify-macros/src/type_alias.rs
  63. 806 0
      domain/libthreema/patches/tsify/tsify-macros/src/typescript.rs
  64. 141 0
      domain/libthreema/patches/tsify/tsify-macros/src/wasm_bindgen.rs

+ 0 - 1
.gitignore

@@ -18,5 +18,4 @@ export/
 threema-android-*
 release/
 .cxx/
-patches/
 **/.DS_Store

+ 155 - 0
domain/libthreema/patches/blake2/CHANGELOG.md

@@ -0,0 +1,155 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to
+[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## 0.10.6 (2022-12-16)
+
+### Added
+
+- `size_opt` Cargo feature ([#440])
+
+### Changed
+
+- Implement `KeyInit::new` in terms of `KeyInit::new_from_slice` ([#435])
+
+[#435]: https://github.com/RustCrypto/hashes/pull/435
+[#440]: https://github.com/RustCrypto/hashes/pull/440
+
+## 0.10.5 (2022-11-11)
+
+### Fixed
+
+- Implementation of the `KeyInit::new` method for MAC types ([#432])
+
+[#432]: https://github.com/RustCrypto/hashes/pull/432
+
+## 0.10.4 (2022-02-17) [YANKED]
+
+### Fixed
+
+- Bug on big-endian targets ([#366])
+
+[#366]: https://github.com/RustCrypto/hashes/pull/366
+
+## 0.10.3 (2022-02-17) [YANKED]
+
+### Fixed
+
+- Minimal versions build ([#363])
+
+[#363]: https://github.com/RustCrypto/hashes/pull/363
+
+## 0.10.2 (2022-01-09) [YANKED]
+
+## Fixed
+
+- Rare compilation error by adding `'static` bound on `OutSize`. ([#347])
+- Values of `KeySize` associated type. ([#349])
+
+[#347]: https://github.com/RustCrypto/hashes/pull/347
+[#349]: https://github.com/RustCrypto/hashes/pull/349
+
+## 0.10.1 (2022-01-05) [YANKED]
+
+## Fixed
+
+- Compilation error with enabled `reset` feature. ([#342])
+
+[#342]: https://github.com/RustCrypto/hashes/pull/342
+
+## 0.10.0 (2021-12-07) [YANKED]
+
+### Changed
+
+- Update to `digest` v0.10 and remove dependency on `crypto-mac` ([#217])
+- `Blake2b` and `Blake2s` renamed into `Blake2b512` and `Blake2s256`
+  respectively. New `Blake2b` and `Blake2s` are generic over output size.
+  `VarBlake2b` and `VarBlake2s` renamed into `Blake2bVar` and `Blake2sVar`
+  respectively. ([#217])
+- Hasher reset functionality moved behind a new non-default feature, `reset`.
+  This must be enabled to use the methods `reset`, `finalize_reset` and
+  `finalize_into_reset`.
+
+### Removed
+
+- `Blake2b` and `Blake2s` no longer support MAC functionality. ([#217])
+
+### Added
+
+- Separate `Blake2bMac` and `Blake2sMac` types generic over output size and
+  `Blake2bMac512` and `Blake2sMac256` type aliases around them. ([#217])
+
+[#217]: https://github.com/RustCrypto/hashes/pull/217
+
+## 0.9.2 (2021-08-25)
+
+### Fixed
+
+- Building with `simd_opt` on recent nightlies ([#301])
+
+[#301]: https://github.com/RustCrypto/hashes/pull/301
+
+## 0.9.1 (2020-10-26)
+
+### Changed
+
+- Bump `opaque-debug` to v0.3 ([#168])
+- Bump `block-buffer` to v0.9 ([#164])
+
+[#168]: https://github.com/RustCrypto/hashes/pull/168
+[#164]: https://github.com/RustCrypto/hashes/pull/164
+
+## 0.9.0 (2020-06-10)
+
+### Added
+
+- Support for Persona and Salt ([#78])
+
+### Changed
+
+- Update to `digest` v0.9 release; MSRV 1.41+ ([#155])
+- Use new `*Dirty` traits from the `digest` crate ([#153])
+- Bump `crypto-mac` to v0.8 release ([#152])
+- Bump `block-buffer` to v0.8 release ([#151])
+- Rename `*result*` to `finalize` ([#148])
+- Upgrade to Rust 2018 edition ([#119])
+
+[#155]: https://github.com/RustCrypto/hashes/pull/155
+[#153]: https://github.com/RustCrypto/hashes/pull/153
+[#152]: https://github.com/RustCrypto/hashes/pull/152
+[#151]: https://github.com/RustCrypto/hashes/pull/151
+[#148]: https://github.com/RustCrypto/hashes/pull/148
+[#119]: https://github.com/RustCrypto/hashes/pull/133
+[#78]: https://github.com/RustCrypto/hashes/pull/78
+
+## 0.8.1 (2019-08-25)
+
+## 0.8.0 (2018-10-11)
+
+## 0.7.1 (2018-04-30)
+
+## 0.7.0 (2017-11-15)
+
+## 0.6.1 (2017-07-24)
+
+## 0.6.0 (2017-06-12)
+
+## 0.5.2 (2017-05-17)
+
+## 0.5.1 (2017-05-02)
+
+## 0.5.0 (2017-04-06)
+
+## 0.4.0 (2017-03-06)
+
+## 0.3.0 (2016-11-17)
+
+## 0.2.0 (2016-10-14)
+
+## 0.1.1 (2016-10-11)
+
+## 0.1.0 (2016-10-09)

+ 28 - 0
domain/libthreema/patches/blake2/Cargo.toml

@@ -0,0 +1,28 @@
+[package]
+name = "blake2"
+version = "0.10.6"
+description = "BLAKE2 hash functions"
+authors = ["RustCrypto Developers"]
+license = "MIT OR Apache-2.0"
+readme = "README.md"
+edition = "2018"
+documentation = "https://docs.rs/blake2"
+repository = "https://github.com/RustCrypto/hashes"
+keywords = ["crypto", "blake2", "hash", "digest"]
+categories = ["cryptography", "no-std"]
+
+[dependencies]
+digest = { version = "0.10.3", features = ["mac"] }
+
+[dev-dependencies]
+digest = { version = "0.10.3", features = ["dev"] }
+hex-literal = "0.2.2"
+
+[features]
+default = ["std"]
+std = ["digest/std"]
+reset = [] # Enable reset functionality
+simd = []
+simd_opt = ["simd"]
+simd_asm = ["simd_opt"]
+size_opt = [] # Optimize for code size. Removes some `inline(always)`

+ 201 - 0
domain/libthreema/patches/blake2/LICENSE-APACHE

@@ -0,0 +1,201 @@
+                              Apache License
+                        Version 2.0, January 2004
+                     http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+   "License" shall mean the terms and conditions for use, reproduction,
+   and distribution as defined by Sections 1 through 9 of this document.
+
+   "Licensor" shall mean the copyright owner or entity authorized by
+   the copyright owner that is granting the License.
+
+   "Legal Entity" shall mean the union of the acting entity and all
+   other entities that control, are controlled by, or are under common
+   control with that entity. For the purposes of this definition,
+   "control" means (i) the power, direct or indirect, to cause the
+   direction or management of such entity, whether by contract or
+   otherwise, or (ii) ownership of fifty percent (50%) or more of the
+   outstanding shares, or (iii) beneficial ownership of such entity.
+
+   "You" (or "Your") shall mean an individual or Legal Entity
+   exercising permissions granted by this License.
+
+   "Source" form shall mean the preferred form for making modifications,
+   including but not limited to software source code, documentation
+   source, and configuration files.
+
+   "Object" form shall mean any form resulting from mechanical
+   transformation or translation of a Source form, including but
+   not limited to compiled object code, generated documentation,
+   and conversions to other media types.
+
+   "Work" shall mean the work of authorship, whether in Source or
+   Object form, made available under the License, as indicated by a
+   copyright notice that is included in or attached to the work
+   (an example is provided in the Appendix below).
+
+   "Derivative Works" shall mean any work, whether in Source or Object
+   form, that is based on (or derived from) the Work and for which the
+   editorial revisions, annotations, elaborations, or other modifications
+   represent, as a whole, an original work of authorship. For the purposes
+   of this License, Derivative Works shall not include works that remain
+   separable from, or merely link (or bind by name) to the interfaces of,
+   the Work and Derivative Works thereof.
+
+   "Contribution" shall mean any work of authorship, including
+   the original version of the Work and any modifications or additions
+   to that Work or Derivative Works thereof, that is intentionally
+   submitted to Licensor for inclusion in the Work by the copyright owner
+   or by an individual or Legal Entity authorized to submit on behalf of
+   the copyright owner. For the purposes of this definition, "submitted"
+   means any form of electronic, verbal, or written communication sent
+   to the Licensor or its representatives, including but not limited to
+   communication on electronic mailing lists, source code control systems,
+   and issue tracking systems that are managed by, or on behalf of, the
+   Licensor for the purpose of discussing and improving the Work, but
+   excluding communication that is conspicuously marked or otherwise
+   designated in writing by the copyright owner as "Not a Contribution."
+
+   "Contributor" shall mean Licensor and any individual or Legal Entity
+   on behalf of whom a Contribution has been received by Licensor and
+   subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   copyright license to reproduce, prepare Derivative Works of,
+   publicly display, publicly perform, sublicense, and distribute the
+   Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   (except as stated in this section) patent license to make, have made,
+   use, offer to sell, sell, import, and otherwise transfer the Work,
+   where such license applies only to those patent claims licensable
+   by such Contributor that are necessarily infringed by their
+   Contribution(s) alone or by combination of their Contribution(s)
+   with the Work to which such Contribution(s) was submitted. If You
+   institute patent litigation against any entity (including a
+   cross-claim or counterclaim in a lawsuit) alleging that the Work
+   or a Contribution incorporated within the Work constitutes direct
+   or contributory patent infringement, then any patent licenses
+   granted to You under this License for that Work shall terminate
+   as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+   Work or Derivative Works thereof in any medium, with or without
+   modifications, and in Source or Object form, provided that You
+   meet the following conditions:
+
+   (a) You must give any other recipients of the Work or
+       Derivative Works a copy of this License; and
+
+   (b) You must cause any modified files to carry prominent notices
+       stating that You changed the files; and
+
+   (c) You must retain, in the Source form of any Derivative Works
+       that You distribute, all copyright, patent, trademark, and
+       attribution notices from the Source form of the Work,
+       excluding those notices that do not pertain to any part of
+       the Derivative Works; and
+
+   (d) If the Work includes a "NOTICE" text file as part of its
+       distribution, then any Derivative Works that You distribute must
+       include a readable copy of the attribution notices contained
+       within such NOTICE file, excluding those notices that do not
+       pertain to any part of the Derivative Works, in at least one
+       of the following places: within a NOTICE text file distributed
+       as part of the Derivative Works; within the Source form or
+       documentation, if provided along with the Derivative Works; or,
+       within a display generated by the Derivative Works, if and
+       wherever such third-party notices normally appear. The contents
+       of the NOTICE file are for informational purposes only and
+       do not modify the License. You may add Your own attribution
+       notices within Derivative Works that You distribute, alongside
+       or as an addendum to the NOTICE text from the Work, provided
+       that such additional attribution notices cannot be construed
+       as modifying the License.
+
+   You may add Your own copyright statement to Your modifications and
+   may provide additional or different license terms and conditions
+   for use, reproduction, or distribution of Your modifications, or
+   for any such Derivative Works as a whole, provided Your use,
+   reproduction, and distribution of the Work otherwise complies with
+   the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+   any Contribution intentionally submitted for inclusion in the Work
+   by You to the Licensor shall be under the terms and conditions of
+   this License, without any additional terms or conditions.
+   Notwithstanding the above, nothing herein shall supersede or modify
+   the terms of any separate license agreement you may have executed
+   with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+   names, trademarks, service marks, or product names of the Licensor,
+   except as required for reasonable and customary use in describing the
+   origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+   agreed to in writing, Licensor provides the Work (and each
+   Contributor provides its Contributions) on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+   implied, including, without limitation, any warranties or conditions
+   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+   PARTICULAR PURPOSE. You are solely responsible for determining the
+   appropriateness of using or redistributing the Work and assume any
+   risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+   whether in tort (including negligence), contract, or otherwise,
+   unless required by applicable law (such as deliberate and grossly
+   negligent acts) or agreed to in writing, shall any Contributor be
+   liable to You for damages, including any direct, indirect, special,
+   incidental, or consequential damages of any character arising as a
+   result of this License or out of the use or inability to use the
+   Work (including but not limited to damages for loss of goodwill,
+   work stoppage, computer failure or malfunction, or any and all
+   other commercial damages or losses), even if such Contributor
+   has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+   the Work or Derivative Works thereof, You may choose to offer,
+   and charge a fee for, acceptance of support, warranty, indemnity,
+   or other liability obligations and/or rights consistent with this
+   License. However, in accepting such obligations, You may act only
+   on Your own behalf and on Your sole responsibility, not on behalf
+   of any other Contributor, and only if You agree to indemnify,
+   defend, and hold each Contributor harmless for any liability
+   incurred by, or claims asserted against, such Contributor by reason
+   of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+   To apply the Apache License to your work, attach the following
+   boilerplate notice, with the fields enclosed by brackets "[]"
+   replaced with your own identifying information. (Don't include
+   the brackets!)  The text should be enclosed in the appropriate
+   comment syntax for the file format. We also recommend that a
+   file or class name and description of purpose be included on the
+   same "printed page" as the copyright notice for easier
+   identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.

+ 26 - 0
domain/libthreema/patches/blake2/LICENSE-MIT

@@ -0,0 +1,26 @@
+Copyright (c) 2015-2016 The blake2-rfc Developers, Cesar Barros
+Copyright (c) 2017 Artyom Pavlov
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without
+limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.

+ 65 - 0
domain/libthreema/patches/blake2/README.md

@@ -0,0 +1,65 @@
+This is a copy of https://github.com/RustCrypto/hashes/tree/blake2-v0.10.6 with
+a patch applied to generate a non-keyed hash with parameters, see:
+https://github.com/RustCrypto/hashes/issues/482
+
+The patch resides here:
+https://github.com/threema-theo/hashes/tree/blake2b-keyless
+
+(Note that the patch targets `master` and this is a backport to the tag
+`blake2-v0.10.6`.)
+
+TODO(LIB-7): We shall switch to the upstream crate once this is fixed.
+
+# RustCrypto: BLAKE2
+
+[![crate][crate-image]][crate-link] [![Docs][docs-image]][docs-link]
+![Apache2/MIT licensed][license-image] ![Rust Version][rustc-image]
+[![Project Chat][chat-image]][chat-link]
+[![Build Status][build-image]][build-link]
+
+Pure Rust implementation of the [BLAKE2 hash function][1] family.
+
+[Documentation][docs-link]
+
+## Minimum Supported Rust Version
+
+Rust **1.41** or higher.
+
+Minimum supported Rust version can be changed in the future, but it will be done
+with a minor version bump.
+
+## SemVer Policy
+
+- All on-by-default features of this library are covered by SemVer
+- MSRV is considered exempt from SemVer as noted above
+
+## License
+
+Licensed under either of:
+
+- [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)
+- [MIT license](http://opensource.org/licenses/MIT)
+
+at your option.
+
+### Contribution
+
+Unless you explicitly state otherwise, any contribution intentionally submitted
+for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
+dual licensed as above, without any additional terms or conditions.
+
+[//]: # 'badges'
+[crate-image]: https://img.shields.io/crates/v/blake2.svg
+[crate-link]: https://crates.io/crates/blake2
+[docs-image]: https://docs.rs/blake2/badge.svg
+[docs-link]: https://docs.rs/blake2/
+[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg
+[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg
+[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260041-hashes
+[rustc-image]: https://img.shields.io/badge/rustc-1.41+-blue.svg
+[build-image]:
+  https://github.com/RustCrypto/hashes/workflows/blake2/badge.svg?branch=master
+[build-link]:
+  https://github.com/RustCrypto/hashes/actions?query=workflow%3Ablake2
+[//]: # 'general links'
+[1]: https://blake2.net/

+ 22 - 0
domain/libthreema/patches/blake2/benches/mod.rs

@@ -0,0 +1,22 @@
+#![feature(test)]
+extern crate test;
+
+use blake2::{Blake2b512, Blake2s256};
+use digest::bench_update;
+use test::Bencher;
+
+bench_update!(
+    Blake2b512::default();
+    blake2b512_10 10;
+    blake2b512_100 100;
+    blake2b512_1000 1000;
+    blake2b512_10000 10000;
+);
+
+bench_update!(
+    Blake2s256::default();
+    blake2s256_10 10;
+    blake2s256_100 100;
+    blake2s256_1000 1000;
+    blake2s256_10000 10000;
+);

+ 34 - 0
domain/libthreema/patches/blake2/src/as_bytes.rs

@@ -0,0 +1,34 @@
+// Copyright 2016 blake2-rfc Developers
+//
+// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
+// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
+// http://opensource.org/licenses/MIT>, at your option. This file may not be
+// copied, modified, or distributed except according to those terms.
+
+use core::mem;
+use core::slice;
+
+#[allow(clippy::missing_safety_doc)]
+pub unsafe trait Safe {}
+
+pub trait AsBytes {
+    fn as_bytes(&self) -> &[u8];
+}
+
+impl<T: Safe> AsBytes for [T] {
+    #[inline]
+    fn as_bytes(&self) -> &[u8] {
+        unsafe {
+            slice::from_raw_parts(self.as_ptr() as *const u8, self.len() * mem::size_of::<T>())
+        }
+    }
+}
+
+unsafe impl Safe for u8 {}
+unsafe impl Safe for u16 {}
+unsafe impl Safe for u32 {}
+unsafe impl Safe for u64 {}
+unsafe impl Safe for i8 {}
+unsafe impl Safe for i16 {}
+unsafe impl Safe for i32 {}
+unsafe impl Safe for i64 {}

+ 47 - 0
domain/libthreema/patches/blake2/src/consts.rs

@@ -0,0 +1,47 @@
+#![allow(clippy::unreadable_literal)]
+
+pub static SIGMA: [[usize; 16]; 12] = [
+    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
+    [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3],
+    [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4],
+    [7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8],
+    [9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13],
+    [2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9],
+    [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11],
+    [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10],
+    [6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5],
+    [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0],
+    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
+    [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3],
+];
+
+pub static BLAKE2B_IV: [u64; 8] = [
+    0x6a09e667f3bcc908,
+    0xbb67ae8584caa73b,
+    0x3c6ef372fe94f82b,
+    0xa54ff53a5f1d36f1,
+    0x510e527fade682d1,
+    0x9b05688c2b3e6c1f,
+    0x1f83d9abfb41bd6b,
+    0x5be0cd19137e2179,
+];
+
+/*
+pub const BLAKE2B_BLOCKBYTES : usize = 128;
+pub const BLAKE2B_OUTBYTES : usize = 64;
+pub const BLAKE2B_KEYBYTES : usize = 64;
+pub const BLAKE2B_SALTBYTES : usize = 16;
+pub const BLAKE2B_PERSONALBYTES : usize = 16;
+*/
+
+pub static BLAKE2S_IV: [u32; 8] = [
+    0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19,
+];
+
+/*
+pub const BLAKE2S_BLOCKBYTES : usize = 64;
+pub const BLAKE2S_OUTBYTES : usize = 32;
+pub const BLAKE2S_KEYBYTES : usize = 32;
+pub const BLAKE2S_SALTBYTES : usize = 8;
+pub const BLAKE2S_PERSONALBYTES : usize = 8;
+*/

+ 172 - 0
domain/libthreema/patches/blake2/src/lib.rs

@@ -0,0 +1,172 @@
+//! An implementation of the [BLAKE2][1] hash functions.
+//!
+//! # Usage
+//!
+//! [`Blake2b512`] and [`Blake2s256`] can be used in the following way:
+//!
+//! ```rust
+//! use blake2::{Blake2b512, Blake2s256, Digest};
+//! use hex_literal::hex;
+//!
+//! // create a Blake2b512 object
+//! let mut hasher = Blake2b512::new();
+//!
+//! // write input message
+//! hasher.update(b"hello world");
+//!
+//! // read hash digest and consume hasher
+//! let res = hasher.finalize();
+//! assert_eq!(res[..], hex!("
+//!     021ced8799296ceca557832ab941a50b4a11f83478cf141f51f933f653ab9fbc
+//!     c05a037cddbed06e309bf334942c4e58cdf1a46e237911ccd7fcf9787cbc7fd0
+//! ")[..]);
+//!
+//! // same example for Blake2s256:
+//! let mut hasher = Blake2s256::new();
+//! hasher.update(b"hello world");
+//! let res = hasher.finalize();
+//! assert_eq!(res[..], hex!("
+//!     9aec6806794561107e594b1f6a8a6b0c92a0cba9acf5e5e93cca06f781813b0b
+//! ")[..]);
+//! ```
+//!
+//! Also see [RustCrypto/hashes](https://github.com/RustCrypto/hashes) readme.
+//!
+//! ## Variable output size
+//!
+//! This implementation supports run and compile time variable sizes.
+//!
+//! Run time variable output example:
+//! ```rust
+//! use blake2::Blake2bVar;
+//! use blake2::digest::{Update, VariableOutput};
+//! use hex_literal::hex;
+//!
+//! let mut hasher = Blake2bVar::new(10).unwrap();
+//! hasher.update(b"my_input");
+//! let mut buf = [0u8; 10];
+//! hasher.finalize_variable(&mut buf).unwrap();
+//! assert_eq!(buf, hex!("2cc55c84e416924e6400"));
+//! ```
+//!
+//! Compile time variable output example:
+//! ```rust
+//! use blake2::{Blake2b, Digest, digest::consts::U10};
+//! use hex_literal::hex;
+//!
+//! type Blake2b80 = Blake2b<U10>;
+//!
+//! let mut hasher = Blake2b80::new();
+//! hasher.update(b"my_input");
+//! let res = hasher.finalize();
+//! assert_eq!(res[..], hex!("2cc55c84e416924e6400")[..]);
+//! ```
+//!
+//! # Acknowledgment
+//! Based on the [blake2-rfc][2] crate.
+//!
+//! [1]: https://en.wikipedia.org/wiki/BLAKE_(hash_function)#BLAKE2
+//! [2]: https://github.com/cesarb/blake2-rfc
+
+#![no_std]
+#![doc(
+    html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg",
+    html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg"
+)]
+#![warn(missing_docs, rust_2018_idioms)]
+#![cfg_attr(feature = "simd", feature(platform_intrinsics, repr_simd))]
+#![cfg_attr(feature = "simd", allow(incomplete_features))]
+
+#[cfg(feature = "std")]
+extern crate std;
+
+pub use digest::{self, Digest};
+
+use core::{convert::TryInto, fmt, marker::PhantomData, ops::Div};
+use digest::{
+    block_buffer::{Lazy, LazyBuffer},
+    consts::{U128, U32, U4, U64},
+    core_api::{
+        AlgorithmName, Block, BlockSizeUser, Buffer, BufferKindUser, CoreWrapper,
+        CtVariableCoreWrapper, OutputSizeUser, RtVariableCoreWrapper, TruncSide, UpdateCore,
+        VariableOutputCore,
+    },
+    crypto_common::{InvalidLength, Key, KeyInit, KeySizeUser},
+    generic_array::{ArrayLength, GenericArray},
+    typenum::{IsLessOrEqual, LeEq, NonZero, Unsigned},
+    FixedOutput, HashMarker, InvalidOutputSize, MacMarker, Output, Update,
+};
+#[cfg(feature = "reset")]
+use digest::{FixedOutputReset, Reset};
+
+mod as_bytes;
+mod consts;
+
+mod simd;
+
+#[macro_use]
+mod macros;
+
+use as_bytes::AsBytes;
+use consts::{BLAKE2B_IV, BLAKE2S_IV};
+use simd::{u32x4, u64x4, Vector4};
+
+blake2_impl!(
+    Blake2bVarCore,
+    "Blake2b",
+    u64,
+    u64x4,
+    U64,
+    U128,
+    32,
+    24,
+    16,
+    63,
+    BLAKE2B_IV,
+    "Blake2b instance with a variable output.",
+    "Blake2b instance with a fixed output.",
+);
+
+/// BLAKE2b which allows to choose output size at runtime.
+pub type Blake2bVar = RtVariableCoreWrapper<Blake2bVarCore>;
+/// Core hasher state of BLAKE2b generic over output size.
+pub type Blake2bCore<OutSize> = CtVariableCoreWrapper<Blake2bVarCore, OutSize>;
+/// BLAKE2b generic over output size.
+pub type Blake2b<OutSize> = CoreWrapper<Blake2bCore<OutSize>>;
+/// BLAKE2b-512 hasher state.
+pub type Blake2b512 = Blake2b<U64>;
+
+blake2_mac_impl!(Blake2bMac, Blake2bVarCore, U64, "Blake2b MAC function");
+
+/// BLAKE2b-512 MAC state.
+pub type Blake2bMac512 = Blake2bMac<U64>;
+
+blake2_impl!(
+    Blake2sVarCore,
+    "Blake2s",
+    u32,
+    u32x4,
+    U32,
+    U64,
+    16,
+    12,
+    8,
+    7,
+    BLAKE2S_IV,
+    "Blake2s instance with a variable output.",
+    "Blake2s instance with a fixed output.",
+);
+
+/// BLAKE2s which allows to choose output size at runtime.
+pub type Blake2sVar = RtVariableCoreWrapper<Blake2sVarCore>;
+/// Core hasher state of BLAKE2s generic over output size.
+pub type Blake2sCore<OutSize> = CtVariableCoreWrapper<Blake2sVarCore, OutSize>;
+/// BLAKE2s generic over output size.
+pub type Blake2s<OutSize> = CoreWrapper<Blake2sCore<OutSize>>;
+/// BLAKE2s-256 hasher state.
+pub type Blake2s256 = Blake2s<U32>;
+
+blake2_mac_impl!(Blake2sMac, Blake2sVarCore, U32, "Blake2s MAC function");
+
+/// BLAKE2s-256 MAC state.
+pub type Blake2sMac256 = Blake2sMac<U32>;

+ 446 - 0
domain/libthreema/patches/blake2/src/macros.rs

@@ -0,0 +1,446 @@
+macro_rules! blake2_impl {
+    (
+        $name:ident, $alg_name:expr, $word:ident, $vec:ident, $bytes:ident,
+        $block_size:ident, $R1:expr, $R2:expr, $R3:expr, $R4:expr, $IV:expr,
+        $vardoc:expr, $doc:expr,
+    ) => {
+        #[derive(Clone)]
+        #[doc=$vardoc]
+        pub struct $name {
+            h: [$vec; 2],
+            t: u64,
+            #[cfg(feature = "reset")]
+            h0: [$vec; 2],
+        }
+
+        impl $name {
+            #[inline(always)]
+            fn iv0() -> $vec {
+                $vec::new($IV[0], $IV[1], $IV[2], $IV[3])
+            }
+            #[inline(always)]
+            fn iv1() -> $vec {
+                $vec::new($IV[4], $IV[5], $IV[6], $IV[7])
+            }
+
+            /// Creates a new context with the full set of sequential-mode parameters.
+            pub fn new_with_params(
+                salt: &[u8],
+                persona: &[u8],
+                key_size: usize,
+                output_size: usize,
+            ) -> Self {
+                assert!(key_size <= $bytes::to_usize());
+                assert!(output_size <= $bytes::to_usize());
+
+                // The number of bytes needed to express two words.
+                let length = $bytes::to_usize() / 4;
+                assert!(salt.len() <= length);
+                assert!(persona.len() <= length);
+
+                // Build a parameter block
+                let mut p = [0 as $word; 8];
+                p[0] = 0x0101_0000 ^ ((key_size as $word) << 8) ^ (output_size as $word);
+
+                // salt is two words long
+                if salt.len() < length {
+                    let mut padded_salt =
+                        GenericArray::<u8, <$bytes as Div<U4>>::Output>::default();
+                    for i in 0..salt.len() {
+                        padded_salt[i] = salt[i];
+                    }
+                    p[4] = $word::from_le_bytes(padded_salt[0..length / 2].try_into().unwrap());
+                    p[5] = $word::from_le_bytes(
+                        padded_salt[length / 2..padded_salt.len()]
+                            .try_into()
+                            .unwrap(),
+                    );
+                } else {
+                    p[4] = $word::from_le_bytes(salt[0..salt.len() / 2].try_into().unwrap());
+                    p[5] =
+                        $word::from_le_bytes(salt[salt.len() / 2..salt.len()].try_into().unwrap());
+                }
+
+                // persona is also two words long
+                if persona.len() < length {
+                    let mut padded_persona =
+                        GenericArray::<u8, <$bytes as Div<U4>>::Output>::default();
+                    for i in 0..persona.len() {
+                        padded_persona[i] = persona[i];
+                    }
+                    p[6] = $word::from_le_bytes(padded_persona[0..length / 2].try_into().unwrap());
+                    p[7] = $word::from_le_bytes(
+                        padded_persona[length / 2..padded_persona.len()]
+                            .try_into()
+                            .unwrap(),
+                    );
+                } else {
+                    p[6] = $word::from_le_bytes(persona[0..length / 2].try_into().unwrap());
+                    p[7] = $word::from_le_bytes(
+                        persona[length / 2..persona.len()].try_into().unwrap(),
+                    );
+                }
+
+                let h = [
+                    Self::iv0() ^ $vec::new(p[0], p[1], p[2], p[3]),
+                    Self::iv1() ^ $vec::new(p[4], p[5], p[6], p[7]),
+                ];
+                $name {
+                    #[cfg(feature = "reset")]
+                    h0: h.clone(),
+                    h,
+                    t: 0,
+                }
+            }
+
+            fn finalize_with_flag(
+                &mut self,
+                final_block: &GenericArray<u8, $block_size>,
+                flag: $word,
+                out: &mut Output<Self>,
+            ) {
+                self.compress(final_block, !0, flag);
+                let buf = [self.h[0].to_le(), self.h[1].to_le()];
+                out.copy_from_slice(buf.as_bytes())
+            }
+
+            fn compress(&mut self, block: &Block<Self>, f0: $word, f1: $word) {
+                use $crate::consts::SIGMA;
+
+                #[cfg_attr(not(feature = "size_opt"), inline(always))]
+                fn quarter_round(v: &mut [$vec; 4], rd: u32, rb: u32, m: $vec) {
+                    v[0] = v[0].wrapping_add(v[1]).wrapping_add(m.from_le());
+                    v[3] = (v[3] ^ v[0]).rotate_right_const(rd);
+                    v[2] = v[2].wrapping_add(v[3]);
+                    v[1] = (v[1] ^ v[2]).rotate_right_const(rb);
+                }
+
+                #[cfg_attr(not(feature = "size_opt"), inline(always))]
+                fn shuffle(v: &mut [$vec; 4]) {
+                    v[1] = v[1].shuffle_left_1();
+                    v[2] = v[2].shuffle_left_2();
+                    v[3] = v[3].shuffle_left_3();
+                }
+
+                #[cfg_attr(not(feature = "size_opt"), inline(always))]
+                fn unshuffle(v: &mut [$vec; 4]) {
+                    v[1] = v[1].shuffle_right_1();
+                    v[2] = v[2].shuffle_right_2();
+                    v[3] = v[3].shuffle_right_3();
+                }
+
+                #[cfg_attr(not(feature = "size_opt"), inline(always))]
+                fn round(v: &mut [$vec; 4], m: &[$word; 16], s: &[usize; 16]) {
+                    quarter_round(v, $R1, $R2, $vec::gather(m, s[0], s[2], s[4], s[6]));
+                    quarter_round(v, $R3, $R4, $vec::gather(m, s[1], s[3], s[5], s[7]));
+
+                    shuffle(v);
+                    quarter_round(v, $R1, $R2, $vec::gather(m, s[8], s[10], s[12], s[14]));
+                    quarter_round(v, $R3, $R4, $vec::gather(m, s[9], s[11], s[13], s[15]));
+                    unshuffle(v);
+                }
+
+                let mut m: [$word; 16] = Default::default();
+                let n = core::mem::size_of::<$word>();
+                for (v, chunk) in m.iter_mut().zip(block.chunks_exact(n)) {
+                    *v = $word::from_ne_bytes(chunk.try_into().unwrap());
+                }
+                let h = &mut self.h;
+
+                let t0 = self.t as $word;
+                let t1 = match $bytes::to_u8() {
+                    64 => 0,
+                    32 => (self.t >> 32) as $word,
+                    _ => unreachable!(),
+                };
+
+                let mut v = [
+                    h[0],
+                    h[1],
+                    Self::iv0(),
+                    Self::iv1() ^ $vec::new(t0, t1, f0, f1),
+                ];
+
+                round(&mut v, &m, &SIGMA[0]);
+                round(&mut v, &m, &SIGMA[1]);
+                round(&mut v, &m, &SIGMA[2]);
+                round(&mut v, &m, &SIGMA[3]);
+                round(&mut v, &m, &SIGMA[4]);
+                round(&mut v, &m, &SIGMA[5]);
+                round(&mut v, &m, &SIGMA[6]);
+                round(&mut v, &m, &SIGMA[7]);
+                round(&mut v, &m, &SIGMA[8]);
+                round(&mut v, &m, &SIGMA[9]);
+                if $bytes::to_u8() == 64 {
+                    round(&mut v, &m, &SIGMA[0]);
+                    round(&mut v, &m, &SIGMA[1]);
+                }
+
+                h[0] = h[0] ^ (v[0] ^ v[2]);
+                h[1] = h[1] ^ (v[1] ^ v[3]);
+            }
+        }
+
+        impl HashMarker for $name {}
+
+        impl BlockSizeUser for $name {
+            type BlockSize = $block_size;
+        }
+
+        impl BufferKindUser for $name {
+            type BufferKind = Lazy;
+        }
+
+        impl UpdateCore for $name {
+            #[inline]
+            fn update_blocks(&mut self, blocks: &[Block<Self>]) {
+                for block in blocks {
+                    self.t += block.len() as u64;
+                    self.compress(block, 0, 0);
+                }
+            }
+        }
+
+        impl OutputSizeUser for $name {
+            type OutputSize = $bytes;
+        }
+
+        impl VariableOutputCore for $name {
+            const TRUNC_SIDE: TruncSide = TruncSide::Left;
+
+            #[inline]
+            fn new(output_size: usize) -> Result<Self, InvalidOutputSize> {
+                if output_size > Self::OutputSize::USIZE {
+                    return Err(InvalidOutputSize);
+                }
+                Ok(Self::new_with_params(&[], &[], 0, output_size))
+            }
+
+            #[inline]
+            fn finalize_variable_core(
+                &mut self,
+                buffer: &mut Buffer<Self>,
+                out: &mut Output<Self>,
+            ) {
+                self.t += buffer.get_pos() as u64;
+                let block = buffer.pad_with_zeros();
+                self.finalize_with_flag(block, 0, out);
+            }
+        }
+
+        #[cfg(feature = "reset")]
+        impl Reset for $name {
+            fn reset(&mut self) {
+                self.h = self.h0;
+                self.t = 0;
+            }
+        }
+
+        impl AlgorithmName for $name {
+            #[inline]
+            fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result {
+                f.write_str($alg_name)
+            }
+        }
+
+        impl fmt::Debug for $name {
+            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+                f.write_str(concat!(stringify!($name), " { ... }"))
+            }
+        }
+    };
+}
+
+macro_rules! blake2_mac_impl {
+    (
+        $name:ident, $hash:ty, $max_size:ty, $doc:expr
+    ) => {
+        #[derive(Clone)]
+        #[doc=$doc]
+        pub struct $name<OutSize>
+        where
+            OutSize: ArrayLength<u8> + IsLessOrEqual<$max_size>,
+            LeEq<OutSize, $max_size>: NonZero,
+        {
+            core: $hash,
+            buffer: LazyBuffer<<$hash as BlockSizeUser>::BlockSize>,
+            #[cfg(feature = "reset")]
+            key_block: Option<Key<Self>>,
+            _out: PhantomData<OutSize>,
+        }
+
+        impl<OutSize> $name<OutSize>
+        where
+            OutSize: ArrayLength<u8> + IsLessOrEqual<$max_size>,
+            LeEq<OutSize, $max_size>: NonZero,
+        {
+            /// Create new instance using provided key, salt, and persona.
+            ///
+            /// Setting key to `None` indicates unkeyed usage.
+            ///
+            /// # Errors
+            ///
+            /// If key is `Some`, then its length should not be zero or bigger
+            /// than the block size. The salt and persona length should not be
+            /// bigger than quarter of block size. If any of those conditions is
+            /// false the method will return an error.
+            #[inline]
+            pub fn new_with_salt_and_personal(
+                key: Option<&[u8]>,
+                salt: &[u8],
+                persona: &[u8],
+            ) -> Result<Self, InvalidLength> {
+                let kl = key.map_or(0, |k| k.len());
+                let bs = <$hash as BlockSizeUser>::BlockSize::USIZE;
+                let qbs = bs / 4;
+                if key.is_some() && kl == 0 || kl > bs || salt.len() > qbs || persona.len() > qbs {
+                    return Err(InvalidLength);
+                }
+                let buffer = if let Some(k) = key {
+                    let mut padded_key = Block::<$hash>::default();
+                    padded_key[..kl].copy_from_slice(k);
+                    LazyBuffer::new(&padded_key)
+                } else {
+                    LazyBuffer::default()
+                };
+                Ok(Self {
+                    core: <$hash>::new_with_params(salt, persona, kl, OutSize::USIZE),
+                    buffer,
+                    #[cfg(feature = "reset")]
+                    key_block: key.map(|k| {
+                        let mut t = Key::<Self>::default();
+                        t[..kl].copy_from_slice(k);
+                        t
+                    }),
+                    _out: PhantomData,
+                })
+            }
+        }
+
+        impl<OutSize> KeySizeUser for $name<OutSize>
+        where
+            OutSize: ArrayLength<u8> + IsLessOrEqual<$max_size>,
+            LeEq<OutSize, $max_size>: NonZero,
+        {
+            type KeySize = $max_size;
+        }
+
+        impl<OutSize> KeyInit for $name<OutSize>
+        where
+            OutSize: ArrayLength<u8> + IsLessOrEqual<$max_size>,
+            LeEq<OutSize, $max_size>: NonZero,
+        {
+            #[inline]
+            fn new(key: &Key<Self>) -> Self {
+                Self::new_from_slice(key).expect("Key has correct length")
+            }
+
+            #[inline]
+            fn new_from_slice(key: &[u8]) -> Result<Self, InvalidLength> {
+                let kl = key.len();
+                if kl > <Self as KeySizeUser>::KeySize::USIZE {
+                    return Err(InvalidLength);
+                }
+                let mut padded_key = Block::<$hash>::default();
+                padded_key[..kl].copy_from_slice(key);
+                Ok(Self {
+                    core: <$hash>::new_with_params(&[], &[], key.len(), OutSize::USIZE),
+                    buffer: LazyBuffer::new(&padded_key),
+                    #[cfg(feature = "reset")]
+                    key_block: {
+                        let mut t = Key::<Self>::default();
+                        t[..kl].copy_from_slice(key);
+                        Some(t)
+                    },
+                    _out: PhantomData,
+                })
+            }
+        }
+
+        impl<OutSize> Update for $name<OutSize>
+        where
+            OutSize: ArrayLength<u8> + IsLessOrEqual<$max_size>,
+            LeEq<OutSize, $max_size>: NonZero,
+        {
+            #[inline]
+            fn update(&mut self, input: &[u8]) {
+                let Self { core, buffer, .. } = self;
+                buffer.digest_blocks(input, |blocks| core.update_blocks(blocks));
+            }
+        }
+
+        impl<OutSize> OutputSizeUser for $name<OutSize>
+        where
+            OutSize: ArrayLength<u8> + IsLessOrEqual<$max_size> + 'static,
+            LeEq<OutSize, $max_size>: NonZero,
+        {
+            type OutputSize = OutSize;
+        }
+
+        impl<OutSize> FixedOutput for $name<OutSize>
+        where
+            OutSize: ArrayLength<u8> + IsLessOrEqual<$max_size> + 'static,
+            LeEq<OutSize, $max_size>: NonZero,
+        {
+            #[inline]
+            fn finalize_into(mut self, out: &mut Output<Self>) {
+                let Self { core, buffer, .. } = &mut self;
+                let mut full_res = Default::default();
+                core.finalize_variable_core(buffer, &mut full_res);
+                out.copy_from_slice(&full_res[..OutSize::USIZE]);
+            }
+        }
+
+        #[cfg(feature = "reset")]
+        impl<OutSize> Reset for $name<OutSize>
+        where
+            OutSize: ArrayLength<u8> + IsLessOrEqual<$max_size>,
+            LeEq<OutSize, $max_size>: NonZero,
+        {
+            fn reset(&mut self) {
+                self.core.reset();
+                self.buffer = if let Some(k) = self.key_block {
+                    let kl = k.len();
+                    let mut padded_key = Block::<$hash>::default();
+                    padded_key[..kl].copy_from_slice(&k);
+                    LazyBuffer::new(&padded_key)
+                } else {
+                    LazyBuffer::default()
+                }
+            }
+        }
+
+        #[cfg(feature = "reset")]
+        impl<OutSize> FixedOutputReset for $name<OutSize>
+        where
+            OutSize: ArrayLength<u8> + IsLessOrEqual<$max_size>,
+            LeEq<OutSize, $max_size>: NonZero,
+        {
+            #[inline]
+            fn finalize_into_reset(&mut self, out: &mut Output<Self>) {
+                let Self { core, buffer, .. } = self;
+                let mut full_res = Default::default();
+                core.finalize_variable_core(buffer, &mut full_res);
+                out.copy_from_slice(&full_res[..OutSize::USIZE]);
+                self.reset();
+            }
+        }
+
+        impl<OutSize> MacMarker for $name<OutSize>
+        where
+            OutSize: ArrayLength<u8> + IsLessOrEqual<$max_size>,
+            LeEq<OutSize, $max_size>: NonZero,
+        {
+        }
+
+        impl<OutSize> fmt::Debug for $name<OutSize>
+        where
+            OutSize: ArrayLength<u8> + IsLessOrEqual<$max_size>,
+            LeEq<OutSize, $max_size>: NonZero,
+        {
+            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+                write!(f, "{}{} {{ ... }}", stringify!($name), OutSize::USIZE)
+            }
+        }
+    };
+}

+ 142 - 0
domain/libthreema/patches/blake2/src/simd.rs

@@ -0,0 +1,142 @@
+// Copyright 2015 blake2-rfc Developers
+//
+// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
+// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
+// http://opensource.org/licenses/MIT>, at your option. This file may not be
+// copied, modified, or distributed except according to those terms.
+
+mod simd_opt;
+mod simdint;
+mod simdop;
+mod simdty;
+
+pub use self::simdty::{u32x4, u64x4};
+
+pub trait Vector4<T>: Copy {
+    fn gather(src: &[T], i0: usize, i1: usize, i2: usize, i3: usize) -> Self;
+
+    #[allow(clippy::wrong_self_convention)]
+    fn from_le(self) -> Self;
+    fn to_le(self) -> Self;
+
+    fn wrapping_add(self, rhs: Self) -> Self;
+
+    fn rotate_right_const(self, n: u32) -> Self;
+
+    fn shuffle_left_1(self) -> Self;
+    fn shuffle_left_2(self) -> Self;
+    fn shuffle_left_3(self) -> Self;
+
+    #[inline(always)]
+    fn shuffle_right_1(self) -> Self {
+        self.shuffle_left_3()
+    }
+    #[inline(always)]
+    fn shuffle_right_2(self) -> Self {
+        self.shuffle_left_2()
+    }
+    #[inline(always)]
+    fn shuffle_right_3(self) -> Self {
+        self.shuffle_left_1()
+    }
+}
+
+macro_rules! impl_vector4 {
+    ($vec:ident, $word:ident) => {
+        impl Vector4<$word> for $vec {
+            #[inline(always)]
+            fn gather(src: &[$word], i0: usize, i1: usize, i2: usize, i3: usize) -> Self {
+                $vec::new(src[i0], src[i1], src[i2], src[i3])
+            }
+
+            #[cfg(target_endian = "little")]
+            #[inline(always)]
+            fn from_le(self) -> Self {
+                self
+            }
+
+            #[cfg(not(target_endian = "little"))]
+            #[inline(always)]
+            fn from_le(self) -> Self {
+                $vec::new(
+                    $word::from_le(self.0),
+                    $word::from_le(self.1),
+                    $word::from_le(self.2),
+                    $word::from_le(self.3),
+                )
+            }
+
+            #[cfg(target_endian = "little")]
+            #[inline(always)]
+            fn to_le(self) -> Self {
+                self
+            }
+
+            #[cfg(not(target_endian = "little"))]
+            #[inline(always)]
+            fn to_le(self) -> Self {
+                $vec::new(
+                    self.0.to_le(),
+                    self.1.to_le(),
+                    self.2.to_le(),
+                    self.3.to_le(),
+                )
+            }
+
+            #[inline(always)]
+            fn wrapping_add(self, rhs: Self) -> Self {
+                self + rhs
+            }
+
+            #[inline(always)]
+            fn rotate_right_const(self, n: u32) -> Self {
+                simd_opt::$vec::rotate_right_const(self, n)
+            }
+
+            #[cfg(feature = "simd")]
+            #[inline(always)]
+            fn shuffle_left_1(self) -> Self {
+                use crate::simd::simdint::simd_shuffle4;
+                const IDX: [u32; 4] = [1, 2, 3, 0];
+                unsafe { simd_shuffle4(self, self, IDX) }
+            }
+
+            #[cfg(not(feature = "simd"))]
+            #[inline(always)]
+            fn shuffle_left_1(self) -> Self {
+                $vec::new(self.1, self.2, self.3, self.0)
+            }
+
+            #[cfg(feature = "simd")]
+            #[inline(always)]
+            fn shuffle_left_2(self) -> Self {
+                use crate::simd::simdint::simd_shuffle4;
+                const IDX: [u32; 4] = [2, 3, 0, 1];
+                unsafe { simd_shuffle4(self, self, IDX) }
+            }
+
+            #[cfg(not(feature = "simd"))]
+            #[inline(always)]
+            fn shuffle_left_2(self) -> Self {
+                $vec::new(self.2, self.3, self.0, self.1)
+            }
+
+            #[cfg(feature = "simd")]
+            #[inline(always)]
+            fn shuffle_left_3(self) -> Self {
+                use crate::simd::simdint::simd_shuffle4;
+                const IDX: [u32; 4] = [3, 0, 1, 2];
+                unsafe { simd_shuffle4(self, self, IDX) }
+            }
+
+            #[cfg(not(feature = "simd"))]
+            #[inline(always)]
+            fn shuffle_left_3(self) -> Self {
+                $vec::new(self.3, self.0, self.1, self.2)
+            }
+        }
+    };
+}
+
+impl_vector4!(u32x4, u32);
+impl_vector4!(u64x4, u64);

+ 52 - 0
domain/libthreema/patches/blake2/src/simd/simd_opt.rs

@@ -0,0 +1,52 @@
+// Copyright 2015 blake2-rfc Developers
+//
+// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
+// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
+// http://opensource.org/licenses/MIT>, at your option. This file may not be
+// copied, modified, or distributed except according to those terms.
+
+#[allow(unused_macros)]
+#[cfg(feature = "simd")]
+macro_rules! transmute_shuffle {
+    ($tmp:ident, $shuffle:ident, $vec:expr, $idx_n:expr, $idx:expr) => {
+        unsafe {
+            use crate::simd::simdint::$shuffle;
+            use crate::simd::simdty::$tmp;
+            use core::mem::transmute;
+
+            const IDX: [u32; $idx_n] = $idx;
+            let tmp_i: $tmp = transmute($vec);
+            let tmp_o: $tmp = $shuffle(tmp_i, tmp_i, IDX);
+            transmute(tmp_o)
+        }
+    };
+}
+
+#[cfg(feature = "simd")]
+pub mod u32x4;
+#[cfg(feature = "simd")]
+pub mod u64x4;
+
+#[cfg(not(feature = "simd"))]
+macro_rules! simd_opt {
+    ($vec:ident) => {
+        pub mod $vec {
+            use crate::simd::simdty::$vec;
+
+            #[inline(always)]
+            pub fn rotate_right_const(vec: $vec, n: u32) -> $vec {
+                $vec::new(
+                    vec.0.rotate_right(n),
+                    vec.1.rotate_right(n),
+                    vec.2.rotate_right(n),
+                    vec.3.rotate_right(n),
+                )
+            }
+        }
+    };
+}
+
+#[cfg(not(feature = "simd"))]
+simd_opt!(u32x4);
+#[cfg(not(feature = "simd"))]
+simd_opt!(u64x4);

+ 69 - 0
domain/libthreema/patches/blake2/src/simd/simd_opt/u32x4.rs

@@ -0,0 +1,69 @@
+// Copyright 2015 blake2-rfc Developers
+//
+// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
+// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
+// http://opensource.org/licenses/MIT>, at your option. This file may not be
+// copied, modified, or distributed except according to those terms.
+
+use crate::simd::simdty::u32x4;
+
+#[cfg(feature = "simd_opt")]
+#[inline(always)]
+pub fn rotate_right_const(vec: u32x4, n: u32) -> u32x4 {
+    match n {
+        16 => rotate_right_16(vec),
+        8 => rotate_right_8(vec),
+        _ => rotate_right_any(vec, n),
+    }
+}
+
+#[cfg(not(feature = "simd_opt"))]
+#[inline(always)]
+pub fn rotate_right_const(vec: u32x4, n: u32) -> u32x4 {
+    rotate_right_any(vec, n)
+}
+
+#[inline(always)]
+fn rotate_right_any(vec: u32x4, n: u32) -> u32x4 {
+    let r = n as u32;
+    let l = 32 - r;
+
+    (vec >> u32x4::new(r, r, r, r)) ^ (vec << u32x4::new(l, l, l, l))
+}
+
+#[cfg(feature = "simd_opt")]
+#[inline(always)]
+fn rotate_right_16(vec: u32x4) -> u32x4 {
+    if cfg!(target_feature = "ssse3") {
+        // pshufb (SSSE3) / vpshufb (AVX2)
+        transmute_shuffle!(
+            u8x16,
+            simd_shuffle16,
+            vec,
+            16,
+            [2, 3, 0, 1, 6, 7, 4, 5, 10, 11, 8, 9, 14, 15, 12, 13]
+        )
+    } else if cfg!(any(target_feature = "sse2", target_feature = "neon")) {
+        // pshuflw+pshufhw (SSE2) / vrev (NEON)
+        transmute_shuffle!(u16x8, simd_shuffle8, vec, 8, [1, 0, 3, 2, 5, 4, 7, 6])
+    } else {
+        rotate_right_any(vec, 16)
+    }
+}
+
+#[cfg(feature = "simd_opt")]
+#[inline(always)]
+fn rotate_right_8(vec: u32x4) -> u32x4 {
+    if cfg!(target_feature = "ssse3") {
+        // pshufb (SSSE3) / vpshufb (AVX2)
+        transmute_shuffle!(
+            u8x16,
+            simd_shuffle16,
+            vec,
+            16,
+            [1, 2, 3, 0, 5, 6, 7, 4, 9, 10, 11, 8, 13, 14, 15, 12]
+        )
+    } else {
+        rotate_right_any(vec, 8)
+    }
+}

+ 143 - 0
domain/libthreema/patches/blake2/src/simd/simd_opt/u64x4.rs

@@ -0,0 +1,143 @@
+// Copyright 2015 blake2-rfc Developers
+//
+// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
+// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
+// http://opensource.org/licenses/MIT>, at your option. This file may not be
+// copied, modified, or distributed except according to those terms.
+
+use crate::simd::simdty::u64x4;
+
+#[cfg(feature = "simd_opt")]
+#[inline(always)]
+pub fn rotate_right_const(vec: u64x4, n: u32) -> u64x4 {
+    match n {
+        32 => rotate_right_32(vec),
+        24 => rotate_right_24(vec),
+        16 => rotate_right_16(vec),
+        _ => rotate_right_any(vec, n),
+    }
+}
+
+#[cfg(not(feature = "simd_opt"))]
+#[inline(always)]
+pub fn rotate_right_const(vec: u64x4, n: u32) -> u64x4 {
+    rotate_right_any(vec, n)
+}
+
+#[inline(always)]
+fn rotate_right_any(vec: u64x4, n: u32) -> u64x4 {
+    let r = n as u64;
+    let l = 64 - r;
+
+    (vec >> u64x4::new(r, r, r, r)) ^ (vec << u64x4::new(l, l, l, l))
+}
+
+#[cfg(feature = "simd_opt")]
+#[inline(always)]
+fn rotate_right_32(vec: u64x4) -> u64x4 {
+    if cfg!(any(target_feature = "sse2", target_feature = "neon")) {
+        // 2 x pshufd (SSE2) / vpshufd (AVX2) / 2 x vrev (NEON)
+        transmute_shuffle!(u32x8, simd_shuffle8, vec, 8, [1, 0, 3, 2, 5, 4, 7, 6])
+    } else {
+        rotate_right_any(vec, 32)
+    }
+}
+
+#[cfg(feature = "simd_opt")]
+#[inline(always)]
+fn rotate_right_24(vec: u64x4) -> u64x4 {
+    if cfg!(all(
+        feature = "simd_asm",
+        target_feature = "neon",
+        target_arch = "arm"
+    )) {
+        // 4 x vext (NEON)
+        rotate_right_vext(vec, 3)
+    } else if cfg!(target_feature = "ssse3") {
+        // 2 x pshufb (SSSE3) / vpshufb (AVX2)
+        transmute_shuffle!(
+            u8x32,
+            simd_shuffle32,
+            vec,
+            32,
+            [
+                3, 4, 5, 6, 7, 0, 1, 2, 11, 12, 13, 14, 15, 8, 9, 10, 19, 20, 21, 22, 23, 16, 17,
+                18, 27, 28, 29, 30, 31, 24, 25, 26
+            ]
+        )
+    } else {
+        rotate_right_any(vec, 24)
+    }
+}
+
+#[cfg(feature = "simd_opt")]
+#[inline(always)]
+fn rotate_right_16(vec: u64x4) -> u64x4 {
+    if cfg!(all(
+        feature = "simd_asm",
+        target_feature = "neon",
+        target_arch = "arm"
+    )) {
+        // 4 x vext (NEON)
+        rotate_right_vext(vec, 2)
+    } else if cfg!(target_feature = "ssse3") {
+        // 2 x pshufb (SSSE3) / vpshufb (AVX2)
+        transmute_shuffle!(
+            u8x32,
+            simd_shuffle32,
+            vec,
+            32,
+            [
+                2, 3, 4, 5, 6, 7, 0, 1, 10, 11, 12, 13, 14, 15, 8, 9, 18, 19, 20, 21, 22, 23, 16,
+                17, 26, 27, 28, 29, 30, 31, 24, 25
+            ]
+        )
+    } else if cfg!(target_feature = "sse2") {
+        // 2 x pshuflw+pshufhw (SSE2)
+        transmute_shuffle!(
+            u16x16,
+            simd_shuffle16,
+            vec,
+            16,
+            [1, 2, 3, 0, 5, 6, 7, 4, 9, 10, 11, 8, 13, 14, 15, 12]
+        )
+    } else {
+        rotate_right_any(vec, 16)
+    }
+}
+
+#[cfg(all(feature = "simd_asm", target_feature = "neon", target_arch = "arm"))]
+mod simd_asm_neon_arm {
+    use crate::simd::simdty::{u64x2, u64x4};
+
+    #[inline(always)]
+    fn vext_u64(vec: u64x2, b: u8) -> u64x2 {
+        unsafe {
+            let result: u64x2;
+            asm!("vext.8 ${0:e}, ${1:e}, ${1:e}, $2\nvext.8 ${0:f}, ${1:f}, ${1:f}, $2"
+               : "=w" (result)
+               : "w" (vec), "n" (b));
+            result
+        }
+    }
+
+    #[inline(always)]
+    pub fn rotate_right_vext(vec: u64x4, b: u8) -> u64x4 {
+        use crate::simd::simdint::{simd_shuffle2, simd_shuffle4};
+
+        unsafe {
+            let tmp0 = vext_u64(simd_shuffle2(vec, vec, [0, 1]), b);
+            let tmp1 = vext_u64(simd_shuffle2(vec, vec, [2, 3]), b);
+            simd_shuffle4(tmp0, tmp1, [0, 1, 2, 3])
+        }
+    }
+}
+
+#[cfg(all(feature = "simd_asm", target_feature = "neon", target_arch = "arm"))]
+use self::simd_asm_neon_arm::rotate_right_vext;
+
+#[cfg(feature = "simd_opt")]
+#[cfg(not(all(feature = "simd_asm", target_feature = "neon", target_arch = "arm")))]
+fn rotate_right_vext(_vec: u64x4, _n: u8) -> u64x4 {
+    unreachable!()
+}

+ 22 - 0
domain/libthreema/patches/blake2/src/simd/simdint.rs

@@ -0,0 +1,22 @@
+// Copyright 2015 blake2-rfc Developers
+//
+// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
+// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
+// http://opensource.org/licenses/MIT>, at your option. This file may not be
+// copied, modified, or distributed except according to those terms.
+
+#![allow(dead_code)]
+
+#[cfg(feature = "simd")]
+extern "platform-intrinsic" {
+    pub fn simd_add<T>(x: T, y: T) -> T;
+    pub fn simd_shl<T>(x: T, y: T) -> T;
+    pub fn simd_shr<T>(x: T, y: T) -> T;
+    pub fn simd_xor<T>(x: T, y: T) -> T;
+
+    pub fn simd_shuffle2<T, U>(v: T, w: T, idx: [u32; 2]) -> U;
+    pub fn simd_shuffle4<T, U>(v: T, w: T, idx: [u32; 4]) -> U;
+    pub fn simd_shuffle8<T, U>(v: T, w: T, idx: [u32; 8]) -> U;
+    pub fn simd_shuffle16<T, U>(v: T, w: T, idx: [u32; 16]) -> U;
+    pub fn simd_shuffle32<T, U>(v: T, w: T, idx: [u32; 32]) -> U;
+}

+ 103 - 0
domain/libthreema/patches/blake2/src/simd/simdop.rs

@@ -0,0 +1,103 @@
+// Copyright 2015 blake2-rfc Developers
+//
+// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
+// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
+// http://opensource.org/licenses/MIT>, at your option. This file may not be
+// copied, modified, or distributed except according to those terms.
+
+#[cfg(feature = "simd")]
+use crate::simd::simdint;
+use crate::simd::simdty::{u32x4, u64x4};
+
+use core::ops::{Add, BitXor, Shl, Shr};
+
+macro_rules! impl_ops {
+    ($vec:ident) => {
+        impl Add for $vec {
+            type Output = Self;
+
+            #[cfg(feature = "simd")]
+            #[inline(always)]
+            fn add(self, rhs: Self) -> Self::Output {
+                unsafe { simdint::simd_add(self, rhs) }
+            }
+
+            #[cfg(not(feature = "simd"))]
+            #[inline(always)]
+            fn add(self, rhs: Self) -> Self::Output {
+                $vec::new(
+                    self.0.wrapping_add(rhs.0),
+                    self.1.wrapping_add(rhs.1),
+                    self.2.wrapping_add(rhs.2),
+                    self.3.wrapping_add(rhs.3),
+                )
+            }
+        }
+
+        impl BitXor for $vec {
+            type Output = Self;
+
+            #[cfg(feature = "simd")]
+            #[inline(always)]
+            fn bitxor(self, rhs: Self) -> Self::Output {
+                unsafe { simdint::simd_xor(self, rhs) }
+            }
+
+            #[cfg(not(feature = "simd"))]
+            #[inline(always)]
+            fn bitxor(self, rhs: Self) -> Self::Output {
+                $vec::new(
+                    self.0 ^ rhs.0,
+                    self.1 ^ rhs.1,
+                    self.2 ^ rhs.2,
+                    self.3 ^ rhs.3,
+                )
+            }
+        }
+
+        impl Shl<$vec> for $vec {
+            type Output = Self;
+
+            #[cfg(feature = "simd")]
+            #[inline(always)]
+            fn shl(self, rhs: Self) -> Self::Output {
+                unsafe { simdint::simd_shl(self, rhs) }
+            }
+
+            #[cfg(not(feature = "simd"))]
+            #[inline(always)]
+            fn shl(self, rhs: Self) -> Self::Output {
+                $vec::new(
+                    self.0 << rhs.0,
+                    self.1 << rhs.1,
+                    self.2 << rhs.2,
+                    self.3 << rhs.3,
+                )
+            }
+        }
+
+        impl Shr<$vec> for $vec {
+            type Output = Self;
+
+            #[cfg(feature = "simd")]
+            #[inline(always)]
+            fn shr(self, rhs: Self) -> Self::Output {
+                unsafe { simdint::simd_shr(self, rhs) }
+            }
+
+            #[cfg(not(feature = "simd"))]
+            #[inline(always)]
+            fn shr(self, rhs: Self) -> Self::Output {
+                $vec::new(
+                    self.0 >> rhs.0,
+                    self.1 >> rhs.1,
+                    self.2 >> rhs.2,
+                    self.3 >> rhs.3,
+                )
+            }
+        }
+    };
+}
+
+impl_ops!(u32x4);
+impl_ops!(u64x4);

+ 77 - 0
domain/libthreema/patches/blake2/src/simd/simdty.rs

@@ -0,0 +1,77 @@
+// Copyright 2016 blake2-rfc Developers
+//
+// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
+// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
+// http://opensource.org/licenses/MIT>, at your option. This file may not be
+// copied, modified, or distributed except according to those terms.
+
+#![allow(dead_code, non_camel_case_types)]
+
+use crate::as_bytes::Safe;
+
+#[cfg(feature = "simd")]
+macro_rules! decl_simd {
+    ($($decl:item)*) => {
+        $(
+            #[derive(Clone, Copy, Debug)]
+            #[repr(simd)]
+            $decl
+        )*
+    }
+}
+
+#[cfg(not(feature = "simd"))]
+macro_rules! decl_simd {
+    ($($decl:item)*) => {
+        $(
+            #[derive(Clone, Copy, Debug)]
+            #[repr(C)]
+            $decl
+        )*
+    }
+}
+
+decl_simd! {
+    pub struct Simd2<T>(pub T, pub T);
+    pub struct Simd4<T>(pub T, pub T, pub T, pub T);
+    pub struct Simd8<T>(pub T, pub T, pub T, pub T,
+                        pub T, pub T, pub T, pub T);
+    pub struct Simd16<T>(pub T, pub T, pub T, pub T,
+                         pub T, pub T, pub T, pub T,
+                         pub T, pub T, pub T, pub T,
+                         pub T, pub T, pub T, pub T);
+    pub struct Simd32<T>(pub T, pub T, pub T, pub T,
+                         pub T, pub T, pub T, pub T,
+                         pub T, pub T, pub T, pub T,
+                         pub T, pub T, pub T, pub T,
+                         pub T, pub T, pub T, pub T,
+                         pub T, pub T, pub T, pub T,
+                         pub T, pub T, pub T, pub T,
+                         pub T, pub T, pub T, pub T);
+}
+
+pub type u64x2 = Simd2<u64>;
+
+pub type u32x4 = Simd4<u32>;
+pub type u64x4 = Simd4<u64>;
+
+pub type u16x8 = Simd8<u16>;
+pub type u32x8 = Simd8<u32>;
+
+pub type u8x16 = Simd16<u8>;
+pub type u16x16 = Simd16<u16>;
+
+pub type u8x32 = Simd32<u8>;
+
+impl<T> Simd4<T> {
+    #[inline(always)]
+    pub fn new(e0: T, e1: T, e2: T, e3: T) -> Simd4<T> {
+        Simd4(e0, e1, e2, e3)
+    }
+}
+
+unsafe impl<T: Safe> Safe for Simd2<T> {}
+unsafe impl<T: Safe> Safe for Simd4<T> {}
+unsafe impl<T: Safe> Safe for Simd8<T> {}
+unsafe impl<T: Safe> Safe for Simd16<T> {}
+unsafe impl<T: Safe> Safe for Simd32<T> {}

BIN
domain/libthreema/patches/blake2/tests/data/blake2b/fixed.blb


BIN
domain/libthreema/patches/blake2/tests/data/blake2b/mac.blb


BIN
domain/libthreema/patches/blake2/tests/data/blake2b/variable.blb


BIN
domain/libthreema/patches/blake2/tests/data/blake2s/mac.blb


BIN
domain/libthreema/patches/blake2/tests/data/blake2s/variable.blb


+ 29 - 0
domain/libthreema/patches/blake2/tests/mac.rs

@@ -0,0 +1,29 @@
+#[cfg(not(feature = "reset"))]
+use digest::new_mac_test as new_test;
+#[cfg(feature = "reset")]
+use digest::new_resettable_mac_test as new_test;
+
+new_test!(blake2b_mac, "blake2b/mac", blake2::Blake2bMac512);
+new_test!(blake2s_mac, "blake2s/mac", blake2::Blake2sMac256);
+
+#[test]
+fn blake2b_new_test() {
+    use blake2::digest::{generic_array::GenericArray, KeyInit, Mac};
+
+    fn run<T: Mac + KeyInit>(key: &[u8]) {
+        const DATA: &[u8] = &[42; 300];
+        let res1 = <T as Mac>::new(GenericArray::from_slice(key))
+            .chain_update(DATA)
+            .finalize()
+            .into_bytes();
+        let res2 = <T as Mac>::new_from_slice(&key)
+            .unwrap()
+            .chain_update(DATA)
+            .finalize()
+            .into_bytes();
+        assert_eq!(res1, res2);
+    }
+
+    run::<blake2::Blake2sMac256>(&[0x42; 32]);
+    run::<blake2::Blake2bMac512>(&[0x42; 64]);
+}

+ 19 - 0
domain/libthreema/patches/blake2/tests/mod.rs

@@ -0,0 +1,19 @@
+#[cfg(feature = "reset")]
+use digest::dev::{fixed_reset_test as fixed_fn, variable_reset_test as varaible_fn};
+#[cfg(not(feature = "reset"))]
+use digest::dev::{fixed_test as fixed_fn, variable_test as varaible_fn};
+use digest::new_test;
+
+new_test!(blake2b_fixed, "blake2b/fixed", blake2::Blake2b512, fixed_fn,);
+new_test!(
+    blake2b_variable,
+    "blake2b/variable",
+    blake2::Blake2bVar,
+    varaible_fn,
+);
+new_test!(
+    blake2s_variable,
+    "blake2s/variable",
+    blake2::Blake2sVar,
+    varaible_fn,
+);

+ 40 - 0
domain/libthreema/patches/blake2/tests/persona.rs

@@ -0,0 +1,40 @@
+use blake2::{digest::FixedOutput, Blake2bMac512, Blake2sMac256};
+use hex_literal::hex;
+
+#[test]
+#[rustfmt::skip]
+fn blake2s_persona() {
+    let key= hex!("
+        000102030405060708090a0b0c0d0e0f
+        101112131415161718191a1b1c1d1e1f
+    ");
+    let persona = b"personal";
+    let ctx = Blake2sMac256::new_with_salt_and_personal(Some(&key), &[], persona).unwrap();
+    assert_eq!(
+        ctx.finalize_fixed()[..],
+        hex!("
+            25a4ee63b594aed3f88a971e1877ef70
+            99534f9097291f88fb86c79b5e70d022
+        ")[..],
+    );
+}
+
+#[test]
+#[rustfmt::skip]
+fn blake2b_persona() {
+    let key = hex!("
+        000102030405060708090a0b0c0d0e0f
+        101112131415161718191a1b1c1d1e1f
+    ");
+    let persona = b"personal";
+    let ctx = Blake2bMac512::new_with_salt_and_personal(Some(&key), &[], persona).unwrap();
+    assert_eq!(
+        ctx.finalize_fixed()[..],
+        hex!("
+            03de3b295dcfc3b25b05abb09bc95fe3
+            e9ff3073638badc68101d1e42019d077
+            1dd07525a3aae8318e92c5e5d967ba92
+            e4810d0021d7bf3b49da0b4b4a8a4e1f
+        ")[..],
+    );
+}

+ 28 - 0
domain/libthreema/patches/blake2/tests/unkeyed.rs

@@ -0,0 +1,28 @@
+use blake2::{digest::FixedOutput, Blake2bMac512, Blake2sMac256};
+use hex_literal::hex;
+
+#[test]
+fn blake2s_unkeyed() {
+    let ctx = Blake2sMac256::new_with_salt_and_personal(None, b"salt", b"persona").unwrap();
+    assert_eq!(
+        ctx.finalize_fixed(),
+        hex!(
+            "d7de83e2b1fedd9755db747235b7ba4b"
+            "f9773a16b91c6b241e4b1d926160d9eb"
+        ),
+    );
+}
+
+#[test]
+fn blake2b_unkeyed() {
+    let ctx = Blake2bMac512::new_with_salt_and_personal(None, b"salt", b"persona").unwrap();
+    assert_eq!(
+        ctx.finalize_fixed(),
+        hex!(
+            "fa3cd38902ae0602d8f0066f18c579fa"
+            "e8068074fbe91f9f5774f841f5ab51fe"
+            "39140ad78d6576f8a0b9f8f4c2642211"
+            "11c9911d8ba1dbefcd034acdbedb8cde"
+        ),
+    );
+}

+ 2 - 0
domain/libthreema/patches/tsify/.gitignore

@@ -0,0 +1,2 @@
+/target
+Cargo.lock

+ 42 - 0
domain/libthreema/patches/tsify/Cargo.toml

@@ -0,0 +1,42 @@
+[package]
+name = "tsify"
+version = "0.4.5"
+edition = "2021"
+authors = ["Madono Haru <madonoharu@gmail.com>"]
+license = "MIT OR Apache-2.0"
+description = "Tsify is a library for generating TypeScript definitions from rust code."
+repository = "https://github.com/madonoharu/tsify"
+homepage = "https://github.com/madonoharu/tsify"
+keywords = ["wasm", "wasm-bindgen", "typescript"]
+categories = ["wasm"]
+
+[dependencies]
+tsify-macros = { path = "tsify-macros", version = "0.4.3" }
+wasm-bindgen = { version = "0.2.86", optional = true }
+serde = { version = "1.0", optional = true }
+serde_json = { version = "1.0", optional = true }
+serde-wasm-bindgen = { version = "0.5.0", optional = true }
+gloo-utils = { version = "0.1.6", optional = true }
+
+[dev-dependencies]
+indoc = "2.0.1"
+js-sys = "0.3.63"
+macrotest = "1.0"
+pretty_assertions = "1.3.0"
+serde = { version = "1.0", features = ["derive"] }
+serde-wasm-bindgen = "0.5.0"
+serde_json = "1.0"
+wasm-bindgen = "0.2.86"
+wasm-bindgen-test = "0.3.36"
+
+[features]
+default = ["json"]
+wasm-bindgen = ["tsify-macros/wasm-bindgen", "dep:wasm-bindgen"]
+js = ["wasm-bindgen", "tsify-macros/js", "dep:serde", "dep:serde-wasm-bindgen"]
+json = [
+  "wasm-bindgen",
+  "tsify-macros/json",
+  "dep:serde",
+  "dep:gloo-utils",
+  "dep:serde_json",
+]

+ 176 - 0
domain/libthreema/patches/tsify/LICENSE_APACHE

@@ -0,0 +1,176 @@
+                              Apache License
+                        Version 2.0, January 2004
+                     http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+   "License" shall mean the terms and conditions for use, reproduction,
+   and distribution as defined by Sections 1 through 9 of this document.
+
+   "Licensor" shall mean the copyright owner or entity authorized by
+   the copyright owner that is granting the License.
+
+   "Legal Entity" shall mean the union of the acting entity and all
+   other entities that control, are controlled by, or are under common
+   control with that entity. For the purposes of this definition,
+   "control" means (i) the power, direct or indirect, to cause the
+   direction or management of such entity, whether by contract or
+   otherwise, or (ii) ownership of fifty percent (50%) or more of the
+   outstanding shares, or (iii) beneficial ownership of such entity.
+
+   "You" (or "Your") shall mean an individual or Legal Entity
+   exercising permissions granted by this License.
+
+   "Source" form shall mean the preferred form for making modifications,
+   including but not limited to software source code, documentation
+   source, and configuration files.
+
+   "Object" form shall mean any form resulting from mechanical
+   transformation or translation of a Source form, including but
+   not limited to compiled object code, generated documentation,
+   and conversions to other media types.
+
+   "Work" shall mean the work of authorship, whether in Source or
+   Object form, made available under the License, as indicated by a
+   copyright notice that is included in or attached to the work
+   (an example is provided in the Appendix below).
+
+   "Derivative Works" shall mean any work, whether in Source or Object
+   form, that is based on (or derived from) the Work and for which the
+   editorial revisions, annotations, elaborations, or other modifications
+   represent, as a whole, an original work of authorship. For the purposes
+   of this License, Derivative Works shall not include works that remain
+   separable from, or merely link (or bind by name) to the interfaces of,
+   the Work and Derivative Works thereof.
+
+   "Contribution" shall mean any work of authorship, including
+   the original version of the Work and any modifications or additions
+   to that Work or Derivative Works thereof, that is intentionally
+   submitted to Licensor for inclusion in the Work by the copyright owner
+   or by an individual or Legal Entity authorized to submit on behalf of
+   the copyright owner. For the purposes of this definition, "submitted"
+   means any form of electronic, verbal, or written communication sent
+   to the Licensor or its representatives, including but not limited to
+   communication on electronic mailing lists, source code control systems,
+   and issue tracking systems that are managed by, or on behalf of, the
+   Licensor for the purpose of discussing and improving the Work, but
+   excluding communication that is conspicuously marked or otherwise
+   designated in writing by the copyright owner as "Not a Contribution."
+
+   "Contributor" shall mean Licensor and any individual or Legal Entity
+   on behalf of whom a Contribution has been received by Licensor and
+   subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   copyright license to reproduce, prepare Derivative Works of,
+   publicly display, publicly perform, sublicense, and distribute the
+   Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   (except as stated in this section) patent license to make, have made,
+   use, offer to sell, sell, import, and otherwise transfer the Work,
+   where such license applies only to those patent claims licensable
+   by such Contributor that are necessarily infringed by their
+   Contribution(s) alone or by combination of their Contribution(s)
+   with the Work to which such Contribution(s) was submitted. If You
+   institute patent litigation against any entity (including a
+   cross-claim or counterclaim in a lawsuit) alleging that the Work
+   or a Contribution incorporated within the Work constitutes direct
+   or contributory patent infringement, then any patent licenses
+   granted to You under this License for that Work shall terminate
+   as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+   Work or Derivative Works thereof in any medium, with or without
+   modifications, and in Source or Object form, provided that You
+   meet the following conditions:
+
+   (a) You must give any other recipients of the Work or
+       Derivative Works a copy of this License; and
+
+   (b) You must cause any modified files to carry prominent notices
+       stating that You changed the files; and
+
+   (c) You must retain, in the Source form of any Derivative Works
+       that You distribute, all copyright, patent, trademark, and
+       attribution notices from the Source form of the Work,
+       excluding those notices that do not pertain to any part of
+       the Derivative Works; and
+
+   (d) If the Work includes a "NOTICE" text file as part of its
+       distribution, then any Derivative Works that You distribute must
+       include a readable copy of the attribution notices contained
+       within such NOTICE file, excluding those notices that do not
+       pertain to any part of the Derivative Works, in at least one
+       of the following places: within a NOTICE text file distributed
+       as part of the Derivative Works; within the Source form or
+       documentation, if provided along with the Derivative Works; or,
+       within a display generated by the Derivative Works, if and
+       wherever such third-party notices normally appear. The contents
+       of the NOTICE file are for informational purposes only and
+       do not modify the License. You may add Your own attribution
+       notices within Derivative Works that You distribute, alongside
+       or as an addendum to the NOTICE text from the Work, provided
+       that such additional attribution notices cannot be construed
+       as modifying the License.
+
+   You may add Your own copyright statement to Your modifications and
+   may provide additional or different license terms and conditions
+   for use, reproduction, or distribution of Your modifications, or
+   for any such Derivative Works as a whole, provided Your use,
+   reproduction, and distribution of the Work otherwise complies with
+   the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+   any Contribution intentionally submitted for inclusion in the Work
+   by You to the Licensor shall be under the terms and conditions of
+   this License, without any additional terms or conditions.
+   Notwithstanding the above, nothing herein shall supersede or modify
+   the terms of any separate license agreement you may have executed
+   with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+   names, trademarks, service marks, or product names of the Licensor,
+   except as required for reasonable and customary use in describing the
+   origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+   agreed to in writing, Licensor provides the Work (and each
+   Contributor provides its Contributions) on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+   implied, including, without limitation, any warranties or conditions
+   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+   PARTICULAR PURPOSE. You are solely responsible for determining the
+   appropriateness of using or redistributing the Work and assume any
+   risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+   whether in tort (including negligence), contract, or otherwise,
+   unless required by applicable law (such as deliberate and grossly
+   negligent acts) or agreed to in writing, shall any Contributor be
+   liable to You for damages, including any direct, indirect, special,
+   incidental, or consequential damages of any character arising as a
+   result of this License or out of the use or inability to use the
+   Work (including but not limited to damages for loss of goodwill,
+   work stoppage, computer failure or malfunction, or any and all
+   other commercial damages or losses), even if such Contributor
+   has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+   the Work or Derivative Works thereof, You may choose to offer,
+   and charge a fee for, acceptance of support, warranty, indemnity,
+   or other liability obligations and/or rights consistent with this
+   License. However, in accepting such obligations, You may act only
+   on Your own behalf and on Your sole responsibility, not on behalf
+   of any other Contributor, and only if You agree to indemnify,
+   defend, and hold each Contributor harmless for any liability
+   incurred by, or claims asserted against, such Contributor by reason
+   of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS

+ 25 - 0
domain/libthreema/patches/tsify/LICENSE_MIT

@@ -0,0 +1,25 @@
+Copyright (c) 2018 Madono Haru <madonoharu@gmail.com>
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without
+limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.

+ 245 - 0
domain/libthreema/patches/tsify/README.md

@@ -0,0 +1,245 @@
+This is a fork of https://github.com/madonoharu/tsify with a patch applied to
+map bytes-like types to `Uint8Array` correctly as we use them.
+
+The patch resides in lgr/tsify/-/tree/map-bytes-like-to-uint8array
+
+TODO(LIB3-8): We shall get this upstream or remove the integration of tsify.
+
+# Tsify
+
+Tsify is a library for generating TypeScript definitions from Rust code.
+
+Using this with [`wasm-bindgen`](https://github.com/rustwasm/wasm-bindgen) will
+automatically output the types to `.d.ts`.
+
+Inspired by
+[`typescript-definitions`](https://github.com/arabidopsis/typescript-definitions)
+and [`ts-rs`](https://github.com/Aleph-Alpha/ts-rs).
+
+## Example
+
+<details>
+<summary>
+Click to show Cargo.toml.
+</summary>
+
+```toml
+[dependencies]
+tsify = "0.4.5"
+serde = { version = "1.0", features = ["derive"] }
+wasm-bindgen = { version = "0.2" }
+```
+
+</details>
+
+```rust
+use serde::{Deserialize, Serialize};
+use tsify::Tsify;
+use wasm_bindgen::prelude::*;
+
+#[derive(Tsify, Serialize, Deserialize)]
+#[tsify(into_wasm_abi, from_wasm_abi)]
+pub struct Point {
+    x: i32,
+    y: i32,
+}
+
+#[wasm_bindgen]
+pub fn into_js() -> Point {
+    Point { x: 0, y: 0 }
+}
+
+#[wasm_bindgen]
+pub fn from_js(point: Point) {}
+```
+
+Will generate the following `.d.ts` file:
+
+```ts
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * @returns {Point}
+ */
+export function into_js(): Point;
+/**
+ * @param {Point} point
+ */
+export function from_js(point: Point): void;
+export interface Point {
+  x: number;
+  y: number;
+}
+```
+
+This is the behavior due to
+[`typescript_custom_section`](https://rustwasm.github.io/docs/wasm-bindgen/reference/attributes/on-rust-exports/typescript_custom_section.html)
+and
+[`Rust Type conversions`](https://rustwasm.github.io/docs/wasm-bindgen/contributing/design/rust-type-conversions.html).
+
+## Crate Features
+
+- `json` (default) enables serialization through
+  [`serde_json`](https://github.com/serde-rs/json).
+- `js` enables serialization through
+  [`serde-wasm-bindgen`](https://github.com/cloudflare/serde-wasm-bindgen) and
+  generates the appropriate types for it. This will be the default in future
+  versions.
+
+## Attributes
+
+Tsify container attributes
+
+- `into_wasm_abi` implements `IntoWasmAbi` and `OptionIntoWasmAbi`. This can be
+  converted directly from Rust to JS via `serde_json` or `serde-wasm-bindgen`.
+- `from_wasm_abi` implements `FromWasmAbi` and `OptionFromWasmAbi`. This is the
+  opposite operation of the above.
+- `namespace` generates a namespace for the enum variants.
+
+Tsify field attributes
+
+- `type`
+- `optional`
+
+Serde attributes
+
+- `rename`
+- `rename-all`
+- `tag`
+- `content`
+- `untagged`
+- `skip`
+- `skip_serializing`
+- `skip_deserializing`
+- `skip_serializing_if = "Option::is_none"`
+- `flatten`
+- `default`
+- `transparent`
+
+## Type Override
+
+```rust
+use tsify::Tsify;
+
+#[derive(Tsify)]
+pub struct Foo {
+    #[tsify(type = "0 | 1 | 2")]
+    x: i32,
+}
+```
+
+Generated type:
+
+```ts
+export interface Foo {
+  x: 0 | 1 | 2;
+}
+```
+
+## Optional Properties
+
+```rust
+#[derive(Tsify)]
+struct Optional {
+    #[tsify(optional)]
+    a: Option<i32>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    b: Option<String>,
+    #[serde(default)]
+    c: i32,
+}
+```
+
+Generated type:
+
+```ts
+export interface Optional {
+  a?: number;
+  b?: string;
+  c?: number;
+}
+```
+
+## Enum
+
+```rust
+#[derive(Tsify)]
+enum Color {
+    Red,
+    Blue,
+    Green,
+    Rgb(u8, u8, u8),
+    Hsv {
+        hue: f64,
+        saturation: f64,
+        value: f64,
+    },
+}
+```
+
+Generated type:
+
+```ts
+export type Color =
+  | 'Red'
+  | 'Blue'
+  | 'Green'
+  | {Rgb: [number, number, number]}
+  | {Hsv: {hue: number; saturation: number; value: number}};
+```
+
+## Enum with namespace
+
+```rust
+#[derive(Tsify)]
+#[tsify(namespace)]
+enum Color {
+    Red,
+    Blue,
+    Green,
+    Rgb(u8, u8, u8),
+    Hsv {
+        hue: f64,
+        saturation: f64,
+        value: f64,
+    },
+}
+```
+
+Generated type:
+
+```ts
+declare namespace Color {
+  export type Red = 'Red';
+  export type Blue = 'Blue';
+  export type Green = 'Green';
+  export type Rgb = {Rgb: [number, number, number]};
+  export type Hsv = {Hsv: {hue: number; saturation: number; value: number}};
+}
+
+export type Color =
+  | 'Red'
+  | 'Blue'
+  | 'Green'
+  | {Rgb: [number, number, number]}
+  | {Hsv: {hue: number; saturation: number; value: number}};
+```
+
+## Type Aliases
+
+```rust
+use tsify::{declare, Tsify};
+
+#[derive(Tsify)]
+struct Foo<T>(T);
+
+#[declare]
+type Bar = Foo<i32>;
+```
+
+Generated type:
+
+```ts
+export type Foo<T> = T;
+export type Bar = Foo<number>;
+```

+ 50 - 0
domain/libthreema/patches/tsify/src/lib.rs

@@ -0,0 +1,50 @@
+#![allow(clippy::wrong_self_convention)]
+
+#[cfg(all(feature = "json", not(feature = "js")))]
+pub use gloo_utils::format::JsValueSerdeExt;
+pub use tsify_macros::*;
+#[cfg(feature = "wasm-bindgen")]
+use wasm_bindgen::{JsCast, JsValue};
+
+pub trait Tsify {
+    #[cfg(feature = "wasm-bindgen")]
+    type JsType: JsCast;
+
+    const DECL: &'static str;
+
+    #[cfg(all(feature = "json", not(feature = "js")))]
+    #[inline]
+    fn into_js(&self) -> serde_json::Result<Self::JsType>
+    where
+        Self: serde::Serialize,
+    {
+        JsValue::from_serde(self).map(JsCast::unchecked_from_js)
+    }
+
+    #[cfg(all(feature = "json", not(feature = "js")))]
+    #[inline]
+    fn from_js<T: Into<JsValue>>(js: T) -> serde_json::Result<Self>
+    where
+        Self: serde::de::DeserializeOwned,
+    {
+        js.into().into_serde()
+    }
+
+    #[cfg(feature = "js")]
+    #[inline]
+    fn into_js(&self) -> Result<Self::JsType, serde_wasm_bindgen::Error>
+    where
+        Self: serde::Serialize,
+    {
+        serde_wasm_bindgen::to_value(self).map(JsCast::unchecked_from_js)
+    }
+
+    #[cfg(feature = "js")]
+    #[inline]
+    fn from_js<T: Into<JsValue>>(js: T) -> Result<Self, serde_wasm_bindgen::Error>
+    where
+        Self: serde::de::DeserializeOwned,
+    {
+        serde_wasm_bindgen::from_value(js.into())
+    }
+}

+ 8 - 0
domain/libthreema/patches/tsify/test.sh

@@ -0,0 +1,8 @@
+#!/bin/bash
+
+set -ex
+
+cargo test --all
+cargo test --all -F js
+wasm-pack test --node
+wasm-pack test --node -F js

+ 321 - 0
domain/libthreema/patches/tsify/tests/enum.rs

@@ -0,0 +1,321 @@
+#![allow(dead_code)]
+
+use indoc::indoc;
+use pretty_assertions::assert_eq;
+use tsify::Tsify;
+
+struct Foo {
+    a: i32,
+    b: String,
+}
+
+#[test]
+fn test_externally_tagged_enum() {
+    #[derive(Tsify)]
+    enum External {
+        Struct { x: String, y: i32 },
+        EmptyStruct {},
+        Tuple(i32, String),
+        EmptyTuple(),
+        Newtype(Foo),
+        Unit,
+    }
+
+    let expected = indoc! {r#"
+        export type External = { Struct: { x: string; y: number } } | { EmptyStruct: {} } | { Tuple: [number, string] } | { EmptyTuple: [] } | { Newtype: Foo } | "Unit";"#
+    };
+
+    assert_eq!(External::DECL, expected);
+}
+
+#[test]
+fn test_externally_tagged_enum_with_namespace() {
+    #[derive(Tsify)]
+    #[tsify(namespace)]
+    enum External {
+        Struct { x: String, y: i32 },
+        EmptyStruct {},
+        Tuple(i32, String),
+        EmptyTuple(),
+        Newtype(Foo),
+        Unit,
+    }
+
+    let expected = indoc! {r#"
+        type __ExternalFoo = Foo;
+        declare namespace External {
+            export type Struct = { Struct: { x: string; y: number } };
+            export type EmptyStruct = { EmptyStruct: {} };
+            export type Tuple = { Tuple: [number, string] };
+            export type EmptyTuple = { EmptyTuple: [] };
+            export type Newtype = { Newtype: __ExternalFoo };
+            export type Unit = "Unit";
+        }
+        
+        export type External = { Struct: { x: string; y: number } } | { EmptyStruct: {} } | { Tuple: [number, string] } | { EmptyTuple: [] } | { Newtype: Foo } | "Unit";"#
+    };
+
+    assert_eq!(External::DECL, expected);
+}
+
+#[test]
+fn test_internally_tagged_enum() {
+    #[derive(Tsify)]
+    #[serde(tag = "t")]
+    enum Internal {
+        Struct { x: String, y: i32 },
+        EmptyStruct {},
+        Newtype(Foo),
+        Unit,
+    }
+
+    let expected = indoc! {r#"
+        export type Internal = { t: "Struct"; x: string; y: number } | { t: "EmptyStruct" } | ({ t: "Newtype" } & Foo) | { t: "Unit" };"#
+    };
+
+    assert_eq!(Internal::DECL, expected);
+}
+
+#[test]
+fn test_internally_tagged_enum_with_namespace() {
+    #[derive(Tsify)]
+    #[serde(tag = "t")]
+    #[tsify(namespace)]
+    enum Internal {
+        Struct { x: String, y: i32 },
+        EmptyStruct {},
+        Newtype(Foo),
+        Unit,
+    }
+
+    let expected = indoc! {r#"
+        type __InternalFoo = Foo;
+        declare namespace Internal {
+            export type Struct = { t: "Struct"; x: string; y: number };
+            export type EmptyStruct = { t: "EmptyStruct" };
+            export type Newtype = { t: "Newtype" } & __InternalFoo;
+            export type Unit = { t: "Unit" };
+        }
+        
+        export type Internal = { t: "Struct"; x: string; y: number } | { t: "EmptyStruct" } | ({ t: "Newtype" } & Foo) | { t: "Unit" };"#
+    };
+
+    assert_eq!(Internal::DECL, expected);
+}
+
+#[test]
+fn test_adjacently_tagged_enum() {
+    #[derive(Tsify)]
+    #[serde(tag = "t", content = "c")]
+    enum Adjacent {
+        Struct { x: String, y: i32 },
+        EmptyStruct {},
+        Tuple(i32, String),
+        EmptyTuple(),
+        Newtype(Foo),
+        Unit,
+    }
+
+    let expected = indoc! {r#"
+    export type Adjacent = { t: "Struct"; c: { x: string; y: number } } | { t: "EmptyStruct"; c: {} } | { t: "Tuple"; c: [number, string] } | { t: "EmptyTuple"; c: [] } | { t: "Newtype"; c: Foo } | { t: "Unit" };"#
+    };
+
+    assert_eq!(Adjacent::DECL, expected);
+}
+
+#[test]
+fn test_adjacently_tagged_enum_with_namespace() {
+    #[derive(Tsify)]
+    #[serde(tag = "t", content = "c")]
+    #[tsify(namespace)]
+    enum Adjacent {
+        Struct { x: String, y: i32 },
+        EmptyStruct {},
+        Tuple(i32, String),
+        EmptyTuple(),
+        Newtype(Foo),
+        Unit,
+    }
+
+    let expected = indoc! {r#"
+        type __AdjacentFoo = Foo;
+        declare namespace Adjacent {
+            export type Struct = { t: "Struct"; c: { x: string; y: number } };
+            export type EmptyStruct = { t: "EmptyStruct"; c: {} };
+            export type Tuple = { t: "Tuple"; c: [number, string] };
+            export type EmptyTuple = { t: "EmptyTuple"; c: [] };
+            export type Newtype = { t: "Newtype"; c: __AdjacentFoo };
+            export type Unit = { t: "Unit" };
+        }
+    
+        export type Adjacent = { t: "Struct"; c: { x: string; y: number } } | { t: "EmptyStruct"; c: {} } | { t: "Tuple"; c: [number, string] } | { t: "EmptyTuple"; c: [] } | { t: "Newtype"; c: Foo } | { t: "Unit" };"#
+    };
+
+    assert_eq!(Adjacent::DECL, expected);
+}
+
+#[test]
+fn test_untagged_enum() {
+    #[derive(Tsify)]
+    #[serde(untagged)]
+    enum Untagged {
+        Struct { x: String, y: i32 },
+        EmptyStruct {},
+        Tuple(i32, String),
+        EmptyTuple(),
+        Newtype(Foo),
+        Unit,
+    }
+
+    let expected = if cfg!(feature = "js") {
+        indoc! {r#"
+            export type Untagged = { x: string; y: number } | {} | [number, string] | [] | Foo | undefined;"#
+        }
+    } else {
+        indoc! {r#"
+            export type Untagged = { x: string; y: number } | {} | [number, string] | [] | Foo | null;"#
+        }
+    };
+
+    assert_eq!(Untagged::DECL, expected);
+}
+
+#[test]
+fn test_untagged_enum_with_namespace() {
+    #[derive(Tsify)]
+    #[serde(untagged)]
+    #[tsify(namespace)]
+    enum Untagged {
+        Struct { x: String, y: i32 },
+        EmptyStruct {},
+        Tuple(i32, String),
+        EmptyTuple(),
+        Newtype(Foo),
+        Unit,
+    }
+
+    let expected = if cfg!(feature = "js") {
+        indoc! {r#"
+            type __UntaggedFoo = Foo;
+            declare namespace Untagged {
+                export type Struct = { x: string; y: number };
+                export type EmptyStruct = {};
+                export type Tuple = [number, string];
+                export type EmptyTuple = [];
+                export type Newtype = __UntaggedFoo;
+                export type Unit = undefined;
+            }
+        
+            export type Untagged = { x: string; y: number } | {} | [number, string] | [] | Foo | undefined;"#
+        }
+    } else {
+        indoc! {r#"
+            type __UntaggedFoo = Foo;
+            declare namespace Untagged {
+                export type Struct = { x: string; y: number };
+                export type EmptyStruct = {};
+                export type Tuple = [number, string];
+                export type EmptyTuple = [];
+                export type Newtype = __UntaggedFoo;
+                export type Unit = null;
+            }
+        
+            export type Untagged = { x: string; y: number } | {} | [number, string] | [] | Foo | null;"#
+        }
+    };
+
+    assert_eq!(Untagged::DECL, expected);
+}
+
+#[test]
+fn test_module_reimport_enum() {
+    #[derive(Tsify)]
+    #[tsify(namespace)]
+    enum Internal {
+        Struct { x: String, y: i32 },
+        EmptyStruct {},
+        Tuple(i32, String),
+        EmptyTuple(),
+        Newtype(Foo),
+        Newtype2(Foo),
+        Unit,
+    }
+
+    let expected = indoc! {r#"
+        type __InternalFoo = Foo;
+        declare namespace Internal {
+            export type Struct = { Struct: { x: string; y: number } };
+            export type EmptyStruct = { EmptyStruct: {} };
+            export type Tuple = { Tuple: [number, string] };
+            export type EmptyTuple = { EmptyTuple: [] };
+            export type Newtype = { Newtype: __InternalFoo };
+            export type Newtype2 = { Newtype2: __InternalFoo };
+            export type Unit = "Unit";
+        }
+
+        export type Internal = { Struct: { x: string; y: number } } | { EmptyStruct: {} } | { Tuple: [number, string] } | { EmptyTuple: [] } | { Newtype: Foo } | { Newtype2: Foo } | "Unit";"#
+    };
+
+    assert_eq!(Internal::DECL, expected);
+}
+
+#[test]
+fn test_module_template_enum() {
+    struct Test<T> {
+        inner: T,
+    }
+
+    #[derive(Tsify)]
+    #[tsify(namespace)]
+    enum Internal<T> {
+        Newtype(Test<T>),
+        NewtypeF(Test<Foo>),
+        NewtypeL(Test<Foo>),
+        Unit,
+    }
+    let expected = indoc! {r#"
+        type __InternalFoo = Foo;
+        type __InternalTest<A> = Test<A>;
+        declare namespace Internal {
+            export type Newtype<T> = { Newtype: __InternalTest<T> };
+            export type NewtypeF = { NewtypeF: __InternalTest<__InternalFoo> };
+            export type NewtypeL = { NewtypeL: __InternalTest<__InternalFoo> };
+            export type Unit = "Unit";
+        }
+
+        export type Internal<T> = { Newtype: Test<T> } | { NewtypeF: Test<Foo> } | { NewtypeL: Test<Foo> } | "Unit";"#
+    };
+
+    assert_eq!(expected, Internal::<Foo>::DECL);
+}
+
+struct Test<T> {
+    inner: T,
+}
+
+#[test]
+fn test_module_template_enum_inner() {
+    struct Test<T> {
+        inner: T,
+    }
+
+    #[derive(Tsify)]
+    #[tsify(namespace)]
+    enum Internal {
+        Newtype(Test<Foo>),
+        Unit,
+    }
+
+    let expected = indoc! {r#"
+        type __InternalFoo = Foo;
+        type __InternalTest<A> = Test<A>;
+        declare namespace Internal {
+            export type Newtype = { Newtype: __InternalTest<__InternalFoo> };
+            export type Unit = "Unit";
+        }
+    
+        export type Internal = { Newtype: Test<Foo> } | "Unit";"#
+    };
+
+    assert_eq!(Internal::DECL, expected);
+}

+ 75 - 0
domain/libthreema/patches/tsify/tests/expand/borrow.expanded.rs

@@ -0,0 +1,75 @@
+use std::borrow::Cow;
+use tsify::Tsify;
+#[tsify(into_wasm_abi, from_wasm_abi)]
+struct Borrow<'a> {
+    raw: &'a str,
+    cow: Cow<'a, str>,
+}
+#[automatically_derived]
+const _: () = {
+    extern crate serde as _serde;
+    use tsify::Tsify;
+    use wasm_bindgen::{
+        convert::{FromWasmAbi, IntoWasmAbi, OptionFromWasmAbi, OptionIntoWasmAbi},
+        describe::WasmDescribe, prelude::*,
+    };
+    #[wasm_bindgen]
+    extern "C" {
+        #[wasm_bindgen(typescript_type = "Borrow")]
+        pub type JsType;
+    }
+    impl<'a> Tsify for Borrow<'a> {
+        type JsType = JsType;
+        const DECL: &'static str = "export interface Borrow {\n    raw: string;\n    cow: string;\n}";
+    }
+    #[wasm_bindgen(typescript_custom_section)]
+    const TS_APPEND_CONTENT: &'static str = "export interface Borrow {\n    raw: string;\n    cow: string;\n}";
+    impl<'a> WasmDescribe for Borrow<'a> {
+        #[inline]
+        fn describe() {
+            <Self as Tsify>::JsType::describe()
+        }
+    }
+    impl<'a> IntoWasmAbi for Borrow<'a>
+    where
+        Self: _serde::Serialize,
+    {
+        type Abi = <JsType as IntoWasmAbi>::Abi;
+        #[inline]
+        fn into_abi(self) -> Self::Abi {
+            self.into_js().unwrap_throw().into_abi()
+        }
+    }
+    impl<'a> OptionIntoWasmAbi for Borrow<'a>
+    where
+        Self: _serde::Serialize,
+    {
+        #[inline]
+        fn none() -> Self::Abi {
+            <JsType as OptionIntoWasmAbi>::none()
+        }
+    }
+    impl<'a> FromWasmAbi for Borrow<'a>
+    where
+        Self: _serde::de::DeserializeOwned,
+    {
+        type Abi = <JsType as FromWasmAbi>::Abi;
+        #[inline]
+        unsafe fn from_abi(js: Self::Abi) -> Self {
+            let result = Self::from_js(&JsType::from_abi(js));
+            if let Err(err) = result {
+                wasm_bindgen::throw_str(err.to_string().as_ref());
+            }
+            result.unwrap_throw()
+        }
+    }
+    impl<'a> OptionFromWasmAbi for Borrow<'a>
+    where
+        Self: _serde::de::DeserializeOwned,
+    {
+        #[inline]
+        fn is_none(js: &Self::Abi) -> bool {
+            <JsType as OptionFromWasmAbi>::is_none(js)
+        }
+    }
+};

+ 9 - 0
domain/libthreema/patches/tsify/tests/expand/borrow.rs

@@ -0,0 +1,9 @@
+use std::borrow::Cow;
+use tsify::Tsify;
+
+#[derive(Tsify)]
+#[tsify(into_wasm_abi, from_wasm_abi)]
+struct Borrow<'a> {
+    raw: &'a str,
+    cow: Cow<'a, str>,
+}

+ 76 - 0
domain/libthreema/patches/tsify/tests/expand/generic_enum.expanded.rs

@@ -0,0 +1,76 @@
+use tsify::Tsify;
+#[tsify(into_wasm_abi, from_wasm_abi)]
+pub enum GenericEnum<T, U> {
+    Unit,
+    NewType(T),
+    Seq(T, U),
+    Map { x: T, y: U },
+}
+#[automatically_derived]
+const _: () = {
+    extern crate serde as _serde;
+    use tsify::Tsify;
+    use wasm_bindgen::{
+        convert::{FromWasmAbi, IntoWasmAbi, OptionFromWasmAbi, OptionIntoWasmAbi},
+        describe::WasmDescribe, prelude::*,
+    };
+    #[wasm_bindgen]
+    extern "C" {
+        #[wasm_bindgen(typescript_type = "GenericEnum")]
+        pub type JsType;
+    }
+    impl<T, U> Tsify for GenericEnum<T, U> {
+        type JsType = JsType;
+        const DECL: &'static str = "export type GenericEnum<T, U> = \"Unit\" | { NewType: T } | { Seq: [T, U] } | { Map: { x: T; y: U } };";
+    }
+    #[wasm_bindgen(typescript_custom_section)]
+    const TS_APPEND_CONTENT: &'static str = "export type GenericEnum<T, U> = \"Unit\" | { NewType: T } | { Seq: [T, U] } | { Map: { x: T; y: U } };";
+    impl<T, U> WasmDescribe for GenericEnum<T, U> {
+        #[inline]
+        fn describe() {
+            <Self as Tsify>::JsType::describe()
+        }
+    }
+    impl<T, U> IntoWasmAbi for GenericEnum<T, U>
+    where
+        Self: _serde::Serialize,
+    {
+        type Abi = <JsType as IntoWasmAbi>::Abi;
+        #[inline]
+        fn into_abi(self) -> Self::Abi {
+            self.into_js().unwrap_throw().into_abi()
+        }
+    }
+    impl<T, U> OptionIntoWasmAbi for GenericEnum<T, U>
+    where
+        Self: _serde::Serialize,
+    {
+        #[inline]
+        fn none() -> Self::Abi {
+            <JsType as OptionIntoWasmAbi>::none()
+        }
+    }
+    impl<T, U> FromWasmAbi for GenericEnum<T, U>
+    where
+        Self: _serde::de::DeserializeOwned,
+    {
+        type Abi = <JsType as FromWasmAbi>::Abi;
+        #[inline]
+        unsafe fn from_abi(js: Self::Abi) -> Self {
+            let result = Self::from_js(&JsType::from_abi(js));
+            if let Err(err) = result {
+                wasm_bindgen::throw_str(err.to_string().as_ref());
+            }
+            result.unwrap_throw()
+        }
+    }
+    impl<T, U> OptionFromWasmAbi for GenericEnum<T, U>
+    where
+        Self: _serde::de::DeserializeOwned,
+    {
+        #[inline]
+        fn is_none(js: &Self::Abi) -> bool {
+            <JsType as OptionFromWasmAbi>::is_none(js)
+        }
+    }
+};

+ 10 - 0
domain/libthreema/patches/tsify/tests/expand/generic_enum.rs

@@ -0,0 +1,10 @@
+use tsify::Tsify;
+
+#[derive(Tsify)]
+#[tsify(into_wasm_abi, from_wasm_abi)]
+pub enum GenericEnum<T, U> {
+    Unit,
+    NewType(T),
+    Seq(T, U),
+    Map { x: T, y: U },
+}

+ 143 - 0
domain/libthreema/patches/tsify/tests/expand/generic_struct.expanded.rs

@@ -0,0 +1,143 @@
+use tsify::Tsify;
+#[tsify(into_wasm_abi, from_wasm_abi)]
+pub struct GenericStruct<T> {
+    x: T,
+}
+#[automatically_derived]
+const _: () = {
+    extern crate serde as _serde;
+    use tsify::Tsify;
+    use wasm_bindgen::{
+        convert::{FromWasmAbi, IntoWasmAbi, OptionFromWasmAbi, OptionIntoWasmAbi},
+        describe::WasmDescribe, prelude::*,
+    };
+    #[wasm_bindgen]
+    extern "C" {
+        #[wasm_bindgen(typescript_type = "GenericStruct")]
+        pub type JsType;
+    }
+    impl<T> Tsify for GenericStruct<T> {
+        type JsType = JsType;
+        const DECL: &'static str = "export interface GenericStruct<T> {\n    x: T;\n}";
+    }
+    #[wasm_bindgen(typescript_custom_section)]
+    const TS_APPEND_CONTENT: &'static str = "export interface GenericStruct<T> {\n    x: T;\n}";
+    impl<T> WasmDescribe for GenericStruct<T> {
+        #[inline]
+        fn describe() {
+            <Self as Tsify>::JsType::describe()
+        }
+    }
+    impl<T> IntoWasmAbi for GenericStruct<T>
+    where
+        Self: _serde::Serialize,
+    {
+        type Abi = <JsType as IntoWasmAbi>::Abi;
+        #[inline]
+        fn into_abi(self) -> Self::Abi {
+            self.into_js().unwrap_throw().into_abi()
+        }
+    }
+    impl<T> OptionIntoWasmAbi for GenericStruct<T>
+    where
+        Self: _serde::Serialize,
+    {
+        #[inline]
+        fn none() -> Self::Abi {
+            <JsType as OptionIntoWasmAbi>::none()
+        }
+    }
+    impl<T> FromWasmAbi for GenericStruct<T>
+    where
+        Self: _serde::de::DeserializeOwned,
+    {
+        type Abi = <JsType as FromWasmAbi>::Abi;
+        #[inline]
+        unsafe fn from_abi(js: Self::Abi) -> Self {
+            let result = Self::from_js(&JsType::from_abi(js));
+            if let Err(err) = result {
+                wasm_bindgen::throw_str(err.to_string().as_ref());
+            }
+            result.unwrap_throw()
+        }
+    }
+    impl<T> OptionFromWasmAbi for GenericStruct<T>
+    where
+        Self: _serde::de::DeserializeOwned,
+    {
+        #[inline]
+        fn is_none(js: &Self::Abi) -> bool {
+            <JsType as OptionFromWasmAbi>::is_none(js)
+        }
+    }
+};
+#[tsify(into_wasm_abi, from_wasm_abi)]
+pub struct GenericNewtype<T>(T);
+#[automatically_derived]
+const _: () = {
+    extern crate serde as _serde;
+    use tsify::Tsify;
+    use wasm_bindgen::{
+        convert::{FromWasmAbi, IntoWasmAbi, OptionFromWasmAbi, OptionIntoWasmAbi},
+        describe::WasmDescribe, prelude::*,
+    };
+    #[wasm_bindgen]
+    extern "C" {
+        #[wasm_bindgen(typescript_type = "GenericNewtype")]
+        pub type JsType;
+    }
+    impl<T> Tsify for GenericNewtype<T> {
+        type JsType = JsType;
+        const DECL: &'static str = "export type GenericNewtype<T> = T;";
+    }
+    #[wasm_bindgen(typescript_custom_section)]
+    const TS_APPEND_CONTENT: &'static str = "export type GenericNewtype<T> = T;";
+    impl<T> WasmDescribe for GenericNewtype<T> {
+        #[inline]
+        fn describe() {
+            <Self as Tsify>::JsType::describe()
+        }
+    }
+    impl<T> IntoWasmAbi for GenericNewtype<T>
+    where
+        Self: _serde::Serialize,
+    {
+        type Abi = <JsType as IntoWasmAbi>::Abi;
+        #[inline]
+        fn into_abi(self) -> Self::Abi {
+            self.into_js().unwrap_throw().into_abi()
+        }
+    }
+    impl<T> OptionIntoWasmAbi for GenericNewtype<T>
+    where
+        Self: _serde::Serialize,
+    {
+        #[inline]
+        fn none() -> Self::Abi {
+            <JsType as OptionIntoWasmAbi>::none()
+        }
+    }
+    impl<T> FromWasmAbi for GenericNewtype<T>
+    where
+        Self: _serde::de::DeserializeOwned,
+    {
+        type Abi = <JsType as FromWasmAbi>::Abi;
+        #[inline]
+        unsafe fn from_abi(js: Self::Abi) -> Self {
+            let result = Self::from_js(&JsType::from_abi(js));
+            if let Err(err) = result {
+                wasm_bindgen::throw_str(err.to_string().as_ref());
+            }
+            result.unwrap_throw()
+        }
+    }
+    impl<T> OptionFromWasmAbi for GenericNewtype<T>
+    where
+        Self: _serde::de::DeserializeOwned,
+    {
+        #[inline]
+        fn is_none(js: &Self::Abi) -> bool {
+            <JsType as OptionFromWasmAbi>::is_none(js)
+        }
+    }
+};

+ 11 - 0
domain/libthreema/patches/tsify/tests/expand/generic_struct.rs

@@ -0,0 +1,11 @@
+use tsify::Tsify;
+
+#[derive(Tsify)]
+#[tsify(into_wasm_abi, from_wasm_abi)]
+pub struct GenericStruct<T> {
+    x: T,
+}
+
+#[derive(Tsify)]
+#[tsify(into_wasm_abi, from_wasm_abi)]
+pub struct GenericNewtype<T>(T);

+ 7 - 0
domain/libthreema/patches/tsify/tests/expand/type_alias.expanded.rs

@@ -0,0 +1,7 @@
+type TypeAlias<T, U> = Foo<T, i32, U>;
+#[automatically_derived]
+const _: () = {
+    use wasm_bindgen::prelude::*;
+    #[wasm_bindgen(typescript_custom_section)]
+    const TS_APPEND_CONTENT: &'static str = "export type TypeAlias<T, U> = Foo<T, number, U>;";
+};

+ 2 - 0
domain/libthreema/patches/tsify/tests/expand/type_alias.rs

@@ -0,0 +1,2 @@
+#[tsify::declare]
+type TypeAlias<T, U> = Foo<T, i32, U>;

+ 4 - 0
domain/libthreema/patches/tsify/tests/expandtest.rs

@@ -0,0 +1,4 @@
+#[test]
+fn expandtest() {
+    macrotest::expand_args("tests/expand/*.rs", ["--features", "tsify/json"]);
+}

+ 53 - 0
domain/libthreema/patches/tsify/tests/flatten.rs

@@ -0,0 +1,53 @@
+#![allow(dead_code)]
+
+use indoc::indoc;
+use pretty_assertions::assert_eq;
+use tsify::Tsify;
+
+#[test]
+fn test_flatten() {
+    #[derive(Tsify)]
+    struct A {
+        a: i32,
+        b: String,
+    }
+
+    #[derive(Tsify)]
+    struct B {
+        #[serde(flatten)]
+        extra: A,
+        c: i32,
+    }
+
+    assert_eq!(
+        B::DECL,
+        indoc! {"
+            export interface B extends A {
+                c: number;
+            }"
+        }
+    );
+}
+
+#[test]
+fn test_flatten_option() {
+    #[derive(Tsify)]
+    struct A {
+        a: i32,
+        b: String,
+    }
+
+    #[derive(Tsify)]
+    struct B {
+        #[serde(flatten)]
+        extra: Option<A>,
+        c: i32,
+    }
+
+    assert_eq!(
+        B::DECL,
+        indoc! {"
+            export type B = { c: number } & (A | {});"
+        }
+    );
+}

+ 86 - 0
domain/libthreema/patches/tsify/tests/generics.rs

@@ -0,0 +1,86 @@
+#![allow(dead_code)]
+
+use indoc::indoc;
+use pretty_assertions::assert_eq;
+use tsify::Tsify;
+
+#[test]
+fn test_generic_struct() {
+    #[derive(Tsify)]
+    pub struct GenericStruct<'a, A, B, C, D> {
+        a: A,
+        b: B,
+        #[serde(skip)]
+        c: &'a C,
+        d: D,
+    }
+
+    assert_eq!(
+        GenericStruct::<(), (), (), ()>::DECL,
+        indoc! {"
+            export interface GenericStruct<A, B, D> {
+                a: A;
+                b: B;
+                d: D;
+            }"
+        }
+    );
+
+    #[derive(Tsify)]
+    pub struct GenericNewtype<T>(T);
+
+    assert_eq!(
+        GenericNewtype::<()>::DECL,
+        "export type GenericNewtype<T> = T;"
+    );
+
+    #[derive(Tsify)]
+    pub struct GenericTuple<'a, A, B, C, D>(A, #[serde(skip)] &'a B, C, D);
+
+    assert_eq!(
+        GenericTuple::<(), (), (), ()>::DECL,
+        "export type GenericTuple<A, C, D> = [A, C, D];"
+    );
+}
+
+#[test]
+fn test_generic_enum() {
+    #[derive(Tsify)]
+    pub enum GenericEnum<T, U> {
+        Unit,
+        NewType(T),
+        Seq(T, U),
+        Map { x: T, y: U },
+    }
+
+    let expected = indoc! {r#"
+        export type GenericEnum<T, U> = "Unit" | { NewType: T } | { Seq: [T, U] } | { Map: { x: T; y: U } };"#
+    };
+
+    assert_eq!(GenericEnum::<(), ()>::DECL, expected);
+}
+
+#[test]
+fn test_generic_enum_with_namespace() {
+    #[derive(Tsify)]
+    #[tsify(namespace)]
+    pub enum GenericEnum<T, U> {
+        Unit,
+        NewType(T),
+        Seq(T, U),
+        Map { x: T, y: U },
+    }
+
+    let expected = indoc! {r#"
+        declare namespace GenericEnum {
+            export type Unit = "Unit";
+            export type NewType<T> = { NewType: T };
+            export type Seq<T, U> = { Seq: [T, U] };
+            export type Map<T, U> = { Map: { x: T; y: U } };
+        }
+        
+        export type GenericEnum<T, U> = "Unit" | { NewType: T } | { Seq: [T, U] } | { Map: { x: T; y: U } };"#
+    };
+
+    assert_eq!(GenericEnum::<(), ()>::DECL, expected);
+}

+ 74 - 0
domain/libthreema/patches/tsify/tests/optional.rs

@@ -0,0 +1,74 @@
+#![allow(dead_code)]
+
+use indoc::indoc;
+use pretty_assertions::assert_eq;
+use tsify::Tsify;
+
+#[test]
+fn test_optional() {
+    #[derive(Tsify)]
+    struct Optional {
+        #[tsify(optional)]
+        a: Option<i32>,
+        #[serde(skip_serializing_if = "Option::is_none")]
+        b: Option<String>,
+        #[serde(default)]
+        c: i32,
+        #[serde(default)]
+        d: Option<String>,
+    }
+
+    #[derive(Tsify)]
+    #[serde(default)]
+    struct OptionalAll {
+        a: i32,
+        b: i32,
+        c: Option<i32>,
+    }
+
+    if cfg!(feature = "js") {
+        assert_eq!(
+            Optional::DECL,
+            indoc! {"
+            export interface Optional {
+                a?: number;
+                b?: string;
+                c?: number;
+                d?: string | undefined;
+            }"
+            }
+        );
+        assert_eq!(
+            OptionalAll::DECL,
+            indoc! {"
+                export interface OptionalAll {
+                    a?: number;
+                    b?: number;
+                    c?: number | undefined;
+                }"
+            }
+        );
+    } else {
+        assert_eq!(
+            Optional::DECL,
+            indoc! {"
+                export interface Optional {
+                    a?: number;
+                    b?: string;
+                    c?: number;
+                    d?: string | null;
+                }"
+            }
+        );
+        assert_eq!(
+            OptionalAll::DECL,
+            indoc! {"
+                export interface OptionalAll {
+                    a?: number;
+                    b?: number;
+                    c?: number | null;
+                }"
+            }
+        );
+    }
+}

+ 121 - 0
domain/libthreema/patches/tsify/tests/rename.rs

@@ -0,0 +1,121 @@
+#![allow(dead_code)]
+
+use indoc::indoc;
+use pretty_assertions::assert_eq;
+use tsify::Tsify;
+
+#[test]
+fn test_rename() {
+    #[derive(Tsify)]
+    struct RenamedStruct {
+        #[serde(rename = "X")]
+        x: i32,
+        #[serde(rename = "Y")]
+        y: i32,
+    }
+
+    assert_eq!(
+        RenamedStruct::DECL,
+        indoc! {"
+            export interface RenamedStruct {
+                X: number;
+                Y: number;
+            }"
+        }
+    );
+
+    #[derive(Tsify)]
+    enum RenamedEnum {
+        #[serde(rename = "X")]
+        A(bool),
+        #[serde(rename = "Y")]
+        B(i64),
+        #[serde(rename = "Z")]
+        C(String),
+        #[serde(skip)]
+        D(i32),
+    }
+
+    let expected = indoc! {r#"
+        export type RenamedEnum = { X: boolean } | { Y: number } | { Z: string };"#
+
+    };
+
+    assert_eq!(RenamedEnum::DECL, expected);
+}
+
+#[test]
+fn test_rename_all() {
+    #[allow(clippy::enum_variant_names)]
+    #[derive(Tsify)]
+    #[serde(rename_all = "snake_case")]
+    #[tsify(namespace)]
+    enum Enum {
+        SnakeCase {
+            foo: bool,
+            foo_bar: bool,
+        },
+        #[serde(rename_all = "camelCase")]
+        CamelCase {
+            foo: bool,
+            foo_bar: bool,
+        },
+        #[serde(rename_all = "kebab-case")]
+        KebabCase {
+            foo: bool,
+            foo_bar: bool,
+        },
+        #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
+        ScreamingSnakeCase {
+            foo: bool,
+            foo_bar: bool,
+        },
+    }
+
+    #[derive(Tsify)]
+    #[serde(rename_all = "PascalCase")]
+    struct PascalCase {
+        foo: bool,
+        foo_bar: bool,
+    }
+
+    #[derive(Tsify)]
+    #[serde(rename_all = "SCREAMING-KEBAB-CASE")]
+    struct ScreamingKebab {
+        foo: bool,
+        foo_bar: bool,
+    }
+
+    let expected = indoc! {r#"
+        declare namespace Enum {
+            export type snake_case = { snake_case: { foo: boolean; foo_bar: boolean } };
+            export type camel_case = { camel_case: { foo: boolean; fooBar: boolean } };
+            export type kebab_case = { kebab_case: { foo: boolean; "foo-bar": boolean } };
+            export type screaming_snake_case = { screaming_snake_case: { FOO: boolean; FOO_BAR: boolean } };
+        }
+        
+        export type Enum = { snake_case: { foo: boolean; foo_bar: boolean } } | { camel_case: { foo: boolean; fooBar: boolean } } | { kebab_case: { foo: boolean; "foo-bar": boolean } } | { screaming_snake_case: { FOO: boolean; FOO_BAR: boolean } };"#
+    };
+
+    assert_eq!(Enum::DECL, expected);
+
+    assert_eq!(
+        PascalCase::DECL,
+        indoc! {"
+            export interface PascalCase {
+                Foo: boolean;
+                FooBar: boolean;
+            }"
+        }
+    );
+
+    assert_eq!(
+        ScreamingKebab::DECL,
+        indoc! {r#"
+            export interface ScreamingKebab {
+                FOO: boolean;
+                "FOO-BAR": boolean;
+            }"#
+        }
+    );
+}

+ 55 - 0
domain/libthreema/patches/tsify/tests/skip.rs

@@ -0,0 +1,55 @@
+#![allow(dead_code)]
+
+use indoc::indoc;
+use pretty_assertions::assert_eq;
+use tsify::Tsify;
+
+#[test]
+fn test_skip() {
+    #[derive(Tsify)]
+    struct Struct {
+        a: i32,
+        #[serde(skip)]
+        b: i32,
+        #[serde(skip_serializing)]
+        c: i32,
+        #[serde(skip_deserializing)]
+        d: i32,
+    }
+
+    assert_eq!(
+        Struct::DECL,
+        indoc! {"
+            export interface Struct {
+                a: number;
+            }"
+        }
+    );
+
+    #[derive(Tsify)]
+    struct Tuple(#[serde(skip)] String, i32);
+
+    assert_eq!(Tuple::DECL, "export type Tuple = [number];");
+
+    #[derive(Tsify)]
+    #[tsify(namespace)]
+    enum Enum {
+        #[serde(skip)]
+        A,
+        #[serde(skip_serializing)]
+        B,
+        #[serde(skip_deserializing)]
+        C,
+        D,
+    }
+
+    let expected = indoc! {r#"
+        declare namespace Enum {
+            export type D = "D";
+        }
+        
+        export type Enum = "D";"#
+    };
+
+    assert_eq!(Enum::DECL, expected);
+}

+ 129 - 0
domain/libthreema/patches/tsify/tests/struct.rs

@@ -0,0 +1,129 @@
+#![allow(dead_code)]
+
+use std::collections::HashMap;
+
+use indoc::indoc;
+use pretty_assertions::assert_eq;
+use tsify::Tsify;
+
+#[test]
+fn test_unit() {
+    #[derive(Tsify)]
+    struct Unit;
+
+    if cfg!(feature = "js") {
+        assert_eq!(Unit::DECL, "export type Unit = undefined;");
+    } else {
+        assert_eq!(Unit::DECL, "export type Unit = null;");
+    };
+}
+
+#[test]
+fn test_named_fields() {
+    #[derive(Tsify)]
+    struct A {
+        a: (usize, u64),
+        b: HashMap<String, i128>,
+    }
+
+    let expected = if cfg!(feature = "js") {
+        indoc! {"
+            export interface A {
+                a: [number, number];
+                b: Map<string, bigint>;
+            }"
+        }
+    } else {
+        indoc! {"
+            export interface A {
+                a: [number, number];
+                b: Record<string, number>;
+            }"
+        }
+    };
+
+    assert_eq!(A::DECL, expected);
+}
+
+#[test]
+fn test_newtype_struct() {
+    #[derive(Tsify)]
+    struct Newtype(i32);
+
+    assert_eq!(Newtype::DECL, "export type Newtype = number;");
+}
+
+#[test]
+fn test_tuple_struct() {
+    #[derive(Tsify)]
+    struct Tuple(i32, String);
+    #[derive(Tsify)]
+    struct EmptyTuple();
+
+    assert_eq!(Tuple::DECL, "export type Tuple = [number, string];");
+    assert_eq!(EmptyTuple::DECL, "export type EmptyTuple = [];");
+}
+
+#[test]
+fn test_nested_struct() {
+    #[derive(Tsify)]
+    struct A {
+        x: f64,
+    }
+
+    #[derive(Tsify)]
+    struct B {
+        a: A,
+    }
+
+    assert_eq!(
+        B::DECL,
+        indoc! {"
+            export interface B {
+                a: A;
+            }"
+        }
+    );
+}
+
+#[test]
+fn test_struct_with_borrowed_fields() {
+    use std::borrow::Cow;
+
+    #[derive(Tsify)]
+    struct Borrow<'a> {
+        raw: &'a str,
+        cow: Cow<'a, str>,
+    }
+
+    assert_eq!(
+        Borrow::DECL,
+        indoc! {"
+            export interface Borrow {
+                raw: string;
+                cow: string;
+            }"
+        }
+    );
+}
+
+#[test]
+fn test_tagged_struct() {
+    #[derive(Tsify)]
+    #[serde(tag = "type")]
+    struct TaggedStruct {
+        x: i32,
+        y: i32,
+    }
+
+    assert_eq!(
+        TaggedStruct::DECL,
+        indoc! {r#"
+            export interface TaggedStruct {
+                type: "TaggedStruct";
+                x: number;
+                y: number;
+            }"#
+        }
+    );
+}

+ 22 - 0
domain/libthreema/patches/tsify/tests/transparent.rs

@@ -0,0 +1,22 @@
+#![allow(dead_code)]
+
+use pretty_assertions::assert_eq;
+use tsify::Tsify;
+
+#[test]
+fn test_transparent() {
+    #[derive(Tsify)]
+    #[serde(transparent)]
+    struct A(String, #[serde(skip)] f64);
+
+    #[derive(Tsify)]
+    #[serde(transparent)]
+    struct B {
+        #[serde(skip)]
+        x: String,
+        y: f64,
+    }
+
+    assert_eq!("export type A = string;", A::DECL);
+    assert_eq!("export type B = number;", B::DECL);
+}

+ 76 - 0
domain/libthreema/patches/tsify/tests/type_override.rs

@@ -0,0 +1,76 @@
+#![allow(dead_code)]
+
+use indoc::indoc;
+use pretty_assertions::assert_eq;
+use tsify::Tsify;
+
+struct Unsupported;
+
+#[test]
+fn test_struct_with_type_override() {
+    #[derive(Tsify)]
+    struct Struct {
+        a: i32,
+        #[tsify(type = "0 | 1 | 2")]
+        b: i32,
+        #[tsify(type = "string | null")]
+        c: Unsupported,
+    }
+
+    #[derive(Tsify)]
+    struct Newtype(#[tsify(type = "string | null")] Unsupported);
+
+    assert_eq!(
+        Struct::DECL,
+        indoc! {r#"
+            export interface Struct {
+                a: number;
+                b: 0 | 1 | 2;
+                c: string | null;
+            }"#
+        }
+    );
+
+    assert_eq!(Newtype::DECL, "export type Newtype = string | null;");
+}
+
+#[test]
+fn test_enum_with_type_override() {
+    #[derive(Tsify)]
+    enum Enum {
+        Struct {
+            #[tsify(type = "`tpl_lit_${string}`")]
+            x: String,
+            #[tsify(type = "0 | 1 | 2")]
+            y: i32,
+        },
+        Tuple(
+            #[tsify(type = "`tpl_lit_${string}`")] String,
+            #[tsify(type = "0 | 1 | 2")] i32,
+        ),
+        Newtype(#[tsify(type = "number")] Unsupported),
+    }
+
+    let expected = indoc! {r#"
+        export type Enum = { Struct: { x: `tpl_lit_${string}`; y: 0 | 1 | 2 } } | { Tuple: [`tpl_lit_${string}`, 0 | 1 | 2] } | { Newtype: number };"#
+    };
+
+    assert_eq!(Enum::DECL, expected);
+}
+
+#[test]
+fn test_generic_struct_with_type_override() {
+    #[derive(Tsify)]
+    pub struct Foo<T> {
+        #[tsify(type = "[T, ...T[]]")]
+        bar: Vec<T>,
+    }
+
+    let expected = indoc! {r#"
+        export interface Foo<T> {
+            bar: [T, ...T[]];
+        }"#
+    };
+
+    assert_eq!(Foo::<()>::DECL, expected);
+}

+ 20 - 0
domain/libthreema/patches/tsify/tests/wasm.rs

@@ -0,0 +1,20 @@
+use serde::{Deserialize, Serialize};
+use tsify::Tsify;
+use wasm_bindgen_test::wasm_bindgen_test;
+
+#[wasm_bindgen_test]
+fn test_convert() {
+    #[derive(Debug, PartialEq, Serialize, Deserialize, Tsify)]
+    #[tsify(into_wasm_abi, from_wasm_abi)]
+    struct Unit;
+
+    let js = Unit.into_js().unwrap();
+
+    if cfg!(feature = "js") {
+        assert!(js.is_undefined());
+    } else {
+        assert!(js.is_null());
+    }
+
+    assert_eq!(Unit::from_js(js).unwrap(), Unit);
+}

+ 25 - 0
domain/libthreema/patches/tsify/tsify-macros/Cargo.toml

@@ -0,0 +1,25 @@
+[package]
+name = "tsify-macros"
+version = "0.4.5"
+edition = "2021"
+authors = ["Madono Haru <madonoharu@gmail.com>"]
+license = "MIT OR Apache-2.0"
+description = "Macros for tsify"
+repository = "https://github.com/madonoharu/tsify"
+homepage = "https://github.com/madonoharu/tsify"
+keywords = ["wasm", "wasm-bindgen", "typescript"]
+categories = ["wasm"]
+
+[lib]
+proc-macro = true
+
+[dependencies]
+proc-macro2 = "1.0"
+quote = "1.0"
+syn = { version = "2.0", default-features = false, features = ["full", "parsing", "printing", "proc-macro"] }
+serde_derive_internals = "0.28"
+
+[features]
+wasm-bindgen = []
+js = []
+json = []

+ 113 - 0
domain/libthreema/patches/tsify/tsify-macros/src/attrs.rs

@@ -0,0 +1,113 @@
+use serde_derive_internals::ast::Field;
+
+#[derive(Debug, Default)]
+pub struct TsifyContainerAttars {
+    pub into_wasm_abi: bool,
+    pub from_wasm_abi: bool,
+    pub namespace: bool,
+}
+
+impl TsifyContainerAttars {
+    pub fn from_derive_input(input: &syn::DeriveInput) -> syn::Result<Self> {
+        let mut attrs = Self {
+            into_wasm_abi: false,
+            from_wasm_abi: false,
+            namespace: false,
+        };
+
+        for attr in &input.attrs {
+            if !attr.path().is_ident("tsify") {
+                continue;
+            }
+
+            attr.parse_nested_meta(|meta| {
+                if meta.path.is_ident("into_wasm_abi") {
+                    if attrs.into_wasm_abi {
+                        return Err(meta.error("duplicate attribute"));
+                    }
+                    attrs.into_wasm_abi = true;
+                    return Ok(());
+                }
+
+                if meta.path.is_ident("from_wasm_abi") {
+                    if attrs.from_wasm_abi {
+                        return Err(meta.error("duplicate attribute"));
+                    }
+                    attrs.from_wasm_abi = true;
+                    return Ok(());
+                }
+
+                if meta.path.is_ident("namespace") {
+                    if !matches!(input.data, syn::Data::Enum(_)) {
+                        return Err(meta.error("#[tsify(namespace)] can only be used on enums"));
+                    }
+                    if attrs.namespace {
+                        return Err(meta.error("duplicate attribute"));
+                    }
+                    attrs.namespace = true;
+                    return Ok(());
+                }
+
+                Err(meta.error("unsupported tsify attribute, expected one of `into_wasm_abi`, `from_wasm_abi`, `namespace`"))
+            })?;
+        }
+
+        Ok(attrs)
+    }
+}
+
+#[derive(Debug, Default)]
+pub struct TsifyFieldAttrs {
+    pub type_override: Option<String>,
+    pub optional: bool,
+}
+
+impl TsifyFieldAttrs {
+    pub fn from_serde_field(field: &Field) -> syn::Result<Self> {
+        let mut attrs = Self {
+            type_override: None,
+            optional: false,
+        };
+
+        for attr in &field.original.attrs {
+            if !attr.path().is_ident("tsify") {
+                continue;
+            }
+
+            attr.parse_nested_meta(|meta| {
+                if meta.path.is_ident("type") {
+                    if attrs.type_override.is_some() {
+                        return Err(meta.error("duplicate attribute"));
+                    }
+                    let lit = meta.value()?.parse::<syn::LitStr>()?;
+                    attrs.type_override = Some(lit.value());
+                    return Ok(());
+                }
+
+                if meta.path.is_ident("optional") {
+                    if attrs.optional {
+                        return Err(meta.error("duplicate attribute"));
+                    }
+                    attrs.optional = true;
+                    return Ok(());
+                }
+
+                Err(meta.error("unsupported tsify attribute, expected one of `type` or `optional`"))
+            })?;
+        }
+
+        if let Some(expr) = field.attrs.skip_serializing_if() {
+            let path = expr
+                .path
+                .segments
+                .iter()
+                .map(|segment| segment.ident.to_string())
+                .collect::<Vec<_>>()
+                .join("::");
+
+            attrs.optional |= &path == "Option::is_none";
+        }
+
+        Ok(attrs)
+    }
+}

+ 78 - 0
domain/libthreema/patches/tsify/tsify-macros/src/container.rs

@@ -0,0 +1,78 @@
+use serde_derive_internals::{ast, ast::Container as SerdeContainer, attr};
+
+use crate::{attrs::TsifyContainerAttars, ctxt::Ctxt};
+
+pub struct Container<'a> {
+    pub ctxt: Ctxt,
+    pub attrs: TsifyContainerAttars,
+    pub serde_container: SerdeContainer<'a>,
+}
+
+impl<'a> Container<'a> {
+    pub fn new(serde_container: SerdeContainer<'a>) -> Self {
+        let input = &serde_container.original;
+        let attrs = TsifyContainerAttars::from_derive_input(input);
+        let ctxt = Ctxt::new();
+
+        let attrs = match attrs {
+            Ok(attrs) => attrs,
+            Err(err) => {
+                ctxt.syn_error(err);
+                Default::default()
+            }
+        };
+
+        Self {
+            ctxt,
+            attrs,
+            serde_container,
+        }
+    }
+
+    pub fn from_derive_input(input: &'a syn::DeriveInput) -> syn::Result<Self> {
+        let cx = serde_derive_internals::Ctxt::new();
+        let serde_cont =
+            SerdeContainer::from_ast(&cx, input, serde_derive_internals::Derive::Serialize);
+
+        match serde_cont {
+            Some(serde_container) => {
+                cx.check()?;
+                Ok(Self::new(serde_container))
+            }
+            None => Err(cx.check().expect_err("serde_cont is None")),
+        }
+    }
+
+    pub fn ident(&self) -> &syn::Ident {
+        &self.serde_container.ident
+    }
+
+    #[inline]
+    pub fn serde_attrs(&self) -> &attr::Container {
+        &self.serde_container.attrs
+    }
+
+    pub fn transparent(&self) -> bool {
+        self.serde_attrs().transparent()
+    }
+
+    pub fn name(&self) -> String {
+        self.serde_attrs().name().serialize_name()
+    }
+
+    pub fn generics(&self) -> &syn::Generics {
+        self.serde_container.generics
+    }
+
+    pub fn serde_data(&self) -> &ast::Data {
+        &self.serde_container.data
+    }
+
+    pub fn syn_error(&self, err: syn::Error) {
+        self.ctxt.syn_error(err);
+    }
+
+    pub fn check(self) -> syn::Result<()> {
+        self.ctxt.check()
+    }
+}

+ 40 - 0
domain/libthreema/patches/tsify/tsify-macros/src/ctxt.rs

@@ -0,0 +1,40 @@
+use std::cell::RefCell;
+
+pub struct Ctxt {
+    errors: RefCell<Option<Vec<syn::Error>>>,
+}
+
+impl Ctxt {
+    pub fn new() -> Self {
+        Self {
+            errors: RefCell::new(Some(Vec::new())),
+        }
+    }
+
+    pub fn syn_error(&self, err: syn::Error) {
+        self.errors.borrow_mut().as_mut().unwrap().push(err)
+    }
+
+    pub fn check(self) -> syn::Result<()> {
+        let mut errors = self.errors.take().unwrap().into_iter();
+
+        let mut combined = match errors.next() {
+            Some(first) => first,
+            None => return Ok(()),
+        };
+
+        for rest in errors {
+            combined.combine(rest);
+        }
+
+        Err(combined)
+    }
+}
+
+impl Drop for Ctxt {
+    fn drop(&mut self) {
+        if !std::thread::panicking() && self.errors.borrow().is_some() {
+            panic!("forgot to check for errors");
+        }
+    }
+}

+ 268 - 0
domain/libthreema/patches/tsify/tsify-macros/src/decl.rs

@@ -0,0 +1,268 @@
+use std::fmt::Display;
+use std::ops::Deref;
+
+use crate::typescript::{TsType, TsTypeElement, TsTypeLit};
+
+#[derive(Clone)]
+pub struct TsTypeAliasDecl {
+    pub id: String,
+    pub export: bool,
+    pub type_params: Vec<String>,
+    pub type_ann: TsType,
+}
+
+impl Display for TsTypeAliasDecl {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        let right = if self.type_params.is_empty() {
+            self.id.clone()
+        } else {
+            let type_params = self.type_params.join(", ");
+            format!("{}<{}>", self.id, type_params)
+        };
+
+        if self.export {
+            write!(f, "export ")?;
+        }
+        write!(f, "type {} = {};", right, self.type_ann)
+    }
+}
+
+pub struct TsInterfaceDecl {
+    pub id: String,
+    pub type_params: Vec<String>,
+    pub extends: Vec<TsType>,
+    pub body: Vec<TsTypeElement>,
+}
+
+impl Display for TsInterfaceDecl {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "export interface {}", self.id)?;
+
+        if !self.type_params.is_empty() {
+            let type_params = self.type_params.join(", ");
+            write!(f, "<{type_params}>")?;
+        }
+
+        if !self.extends.is_empty() {
+            let extends = self
+                .extends
+                .iter()
+                .map(|ty| ty.to_string())
+                .collect::<Vec<_>>()
+                .join(", ");
+
+            write!(f, " extends {extends}")?;
+        }
+
+        if self.body.is_empty() {
+            write!(f, " {{}}")
+        } else {
+            let members = self
+                .body
+                .iter()
+                .map(|elem| format!("\n    {elem};"))
+                .collect::<Vec<_>>()
+                .join("");
+
+            write!(f, " {{{members}\n}}")
+        }
+    }
+}
+
+pub struct TsEnumDecl {
+    pub id: String,
+    pub type_params: Vec<String>,
+    pub members: Vec<TsTypeAliasDecl>,
+    pub namespace: bool,
+}
+
+const ALPHABET_UPPER: [char; 26] = [
+    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
+    'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+];
+
+fn tparam(i: usize) -> String {
+    let mut s = String::new();
+    let mut i = i;
+    loop {
+        s.push(ALPHABET_UPPER[i % ALPHABET_UPPER.len()]);
+        if i < ALPHABET_UPPER.len() {
+            return s;
+        }
+        i /= ALPHABET_UPPER.len();
+    }
+}
+
+impl TsEnumDecl {
+    fn replace_type_params(ts_type: TsType, type_args: &mut Vec<String>) -> TsType {
+        match ts_type {
+            TsType::Ref { name, type_params } => TsType::Ref {
+                name,
+                type_params: type_params
+                    .iter()
+                    .map(|_| {
+                        let name = tparam(type_args.len());
+                        type_args.push(name.clone());
+                        TsType::Ref {
+                            name,
+                            type_params: Vec::new(),
+                        }
+                    })
+                    .collect(),
+            },
+            TsType::Array(t) => TsType::Array(Box::new(TsEnumDecl::replace_type_params(
+                t.deref().clone(),
+                type_args,
+            ))),
+            TsType::Tuple(tv) => TsType::Tuple(
+                tv.iter()
+                    .map(|t| TsEnumDecl::replace_type_params(t.clone(), type_args))
+                    .collect(),
+            ),
+            TsType::Option(t) => TsType::Option(Box::new(TsEnumDecl::replace_type_params(
+                t.deref().clone(),
+                type_args,
+            ))),
+            TsType::Fn { params, type_ann } => TsType::Fn {
+                params: params
+                    .iter()
+                    .map(|t| TsEnumDecl::replace_type_params(t.clone(), type_args))
+                    .collect(),
+                type_ann: Box::new(TsEnumDecl::replace_type_params(
+                    type_ann.deref().clone(),
+                    type_args,
+                )),
+            },
+            TsType::TypeLit(lit) => TsType::TypeLit(TsTypeLit {
+                members: lit
+                    .members
+                    .iter()
+                    .map(|t| TsTypeElement {
+                        key: t.key.clone(),
+                        optional: t.optional,
+                        type_ann: TsEnumDecl::replace_type_params(t.type_ann.clone(), type_args),
+                    })
+                    .collect(),
+            }),
+            TsType::Intersection(tv) => TsType::Intersection(
+                tv.iter()
+                    .map(|t| TsEnumDecl::replace_type_params(t.clone(), type_args))
+                    .collect(),
+            ),
+            TsType::Union(tv) => TsType::Union(
+                tv.iter()
+                    .map(|t| TsEnumDecl::replace_type_params(t.clone(), type_args))
+                    .collect(),
+            ),
+            _ => ts_type,
+        }
+    }
+}
+
+impl Display for TsEnumDecl {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        if self.namespace {
+            let mut type_refs = self
+                .members
+                .iter()
+                .flat_map(|type_alias| {
+                    let mut type_refs = Vec::new();
+                    type_alias.type_ann.type_refs(&mut type_refs);
+
+                    type_refs
+                        .iter()
+                        .filter(|(name, _)| !self.type_params.contains(name))
+                        .map(|(name, type_args)| {
+                            let mut type_refs = Vec::new();
+                            let ts_type = TsEnumDecl::replace_type_params(
+                                TsType::Ref {
+                                    name: name.clone(),
+                                    type_params: type_args.clone(),
+                                },
+                                &mut type_refs,
+                            );
+
+                            TsTypeAliasDecl {
+                                id: format!("__{}{}", self.id, name),
+                                export: false,
+                                type_params: type_refs,
+                                type_ann: ts_type,
+                            }
+                        })
+                        .collect::<Vec<_>>()
+                })
+                .collect::<Vec<_>>();
+            type_refs.sort_by_key(|type_ref| type_ref.id.clone());
+            type_refs.dedup_by_key(|type_ref| type_ref.id.clone());
+            for type_ref in type_refs {
+                writeln!(f, "{}", type_ref)?;
+            }
+            write!(f, "declare namespace {}", self.id)?;
+
+            if self.members.is_empty() {
+                write!(f, " {{}}")?;
+            } else {
+                let prefix = format!("__{}", self.id);
+                let members = self
+                    .members
+                    .iter()
+                    .map(|elem| TsTypeAliasDecl {
+                        id: elem.id.clone(),
+                        export: true,
+                        type_params: elem.type_params.clone(),
+                        type_ann: elem
+                            .type_ann
+                            .clone()
+                            .prefix_type_refs(&prefix, &self.type_params),
+                    })
+                    .map(|elem| format!("\n    {elem}"))
+                    .collect::<Vec<_>>()
+                    .join("");
+
+                write!(f, " {{{members}\n}}")?;
+            }
+
+            write!(f, "\n\n")?;
+        }
+
+        TsTypeAliasDecl {
+            id: self.id.clone(),
+            export: true,
+            type_params: self.type_params.clone(),
+            type_ann: TsType::Union(
+                self.members
+                    .iter()
+                    .map(|member| member.type_ann.clone())
+                    .collect(),
+            ),
+        }
+        .fmt(f)
+    }
+}
+
+#[allow(clippy::enum_variant_names)]
+pub enum Decl {
+    TsTypeAlias(TsTypeAliasDecl),
+    TsInterface(TsInterfaceDecl),
+    TsEnum(TsEnumDecl),
+}
+
+impl Decl {
+    pub fn id(&self) -> &String {
+        match self {
+            Decl::TsTypeAlias(decl) => &decl.id,
+            Decl::TsInterface(decl) => &decl.id,
+            Decl::TsEnum(decl) => &decl.id,
+        }
+    }
+}
+
+impl Display for Decl {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Decl::TsTypeAlias(decl) => decl.fmt(f),
+            Decl::TsInterface(decl) => decl.fmt(f),
+            Decl::TsEnum(decl) => decl.fmt(f),
+        }
+    }
+}

+ 50 - 0
domain/libthreema/patches/tsify/tsify-macros/src/derive.rs

@@ -0,0 +1,50 @@
+use proc_macro2::TokenStream;
+use quote::quote;
+use syn::{parse_quote, DeriveInput};
+
+use crate::{container::Container, parser::Parser, wasm_bindgen};
+
+pub fn expand(input: DeriveInput) -> syn::Result<TokenStream> {
+    let cont = Container::from_derive_input(&input)?;
+
+    let parser = Parser::new(&cont);
+    let decl = parser.parse();
+
+    let (impl_generics, ty_generics, where_clause) = cont.generics().split_for_impl();
+
+    let ident = cont.ident();
+    let decl_str = decl.to_string();
+
+    let tokens = if cfg!(feature = "wasm-bindgen") {
+        wasm_bindgen::expand(&cont, decl)
+    } else {
+        quote! {
+            #[automatically_derived]
+            const _: () = {
+                use tsify::Tsify;
+                impl #impl_generics Tsify for #ident #ty_generics #where_clause {
+                    const DECL: &'static str = #decl_str;
+                }
+            };
+        }
+    };
+
+    cont.check()?;
+
+    Ok(tokens)
+}
+
+pub fn expand_by_attr(args: TokenStream, input: DeriveInput) -> syn::Result<TokenStream> {
+    let mut cloned_input = input.clone();
+    let attr: syn::Attribute = parse_quote!(#[tsify(#args)]);
+    cloned_input.attrs.push(attr);
+
+    let derived = expand(cloned_input)?;
+
+    let tokens = quote! {
+      #input
+      #derived
+    };
+
+    Ok(tokens)
+}

+ 48 - 0
domain/libthreema/patches/tsify/tsify-macros/src/lib.rs

@@ -0,0 +1,48 @@
+mod attrs;
+mod container;
+mod ctxt;
+mod decl;
+mod derive;
+mod parser;
+mod type_alias;
+mod typescript;
+mod wasm_bindgen;
+
+use syn::{parse_macro_input, DeriveInput};
+
+fn declare_impl(
+    args: proc_macro2::TokenStream,
+    item: syn::Item,
+) -> syn::Result<proc_macro2::TokenStream> {
+    match item {
+        syn::Item::Type(item) => type_alias::expend(item),
+        syn::Item::Enum(item) => derive::expand_by_attr(args, item.into()),
+        syn::Item::Struct(item) => derive::expand_by_attr(args, item.into()),
+        _ => Err(syn::Error::new_spanned(
+            args,
+            "#[declare] can only be applied to a struct, enum, or type alias.",
+        )),
+    }
+}
+
+#[proc_macro_attribute]
+pub fn declare(
+    args: proc_macro::TokenStream,
+    item: proc_macro::TokenStream,
+) -> proc_macro::TokenStream {
+    let item: syn::Item = parse_macro_input!(item);
+    let args = proc_macro2::TokenStream::from(args);
+
+    declare_impl(args, item)
+        .unwrap_or_else(syn::Error::into_compile_error)
+        .into()
+}
+
+#[proc_macro_derive(Tsify, attributes(tsify, serde))]
+pub fn derive_tsify(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+    let item: DeriveInput = parse_macro_input!(input);
+
+    derive::expand(item)
+        .unwrap_or_else(syn::Error::into_compile_error)
+        .into()
+}

+ 291 - 0
domain/libthreema/patches/tsify/tsify-macros/src/parser.rs

@@ -0,0 +1,291 @@
+use std::collections::HashSet;
+
+use serde_derive_internals::{
+    ast::{Data, Field, Style, Variant},
+    attr::TagType,
+};
+
+use crate::{
+    attrs::TsifyFieldAttrs,
+    container::Container,
+    decl::{Decl, TsEnumDecl, TsInterfaceDecl, TsTypeAliasDecl},
+    typescript::{TsType, TsTypeElement, TsTypeLit},
+};
+
+enum ParsedFields {
+    Named(Vec<TsTypeElement>, Vec<TsType>),
+    Unnamed(Vec<TsType>),
+    Transparent(TsType),
+}
+
+impl From<ParsedFields> for TsType {
+    fn from(fields: ParsedFields) -> Self {
+        match fields {
+            ParsedFields::Named(members, extends) => {
+                let type_lit = TsType::from(TsTypeLit { members });
+
+                if extends.is_empty() {
+                    type_lit
+                } else {
+                    type_lit.and(TsType::Intersection(extends))
+                }
+            }
+            ParsedFields::Unnamed(elems) => TsType::Tuple(elems),
+            ParsedFields::Transparent(ty) => ty,
+        }
+    }
+}
+
+enum FieldsStyle {
+    Named,
+    Unnamed,
+}
+
+#[derive(Clone)]
+pub struct Parser<'a> {
+    pub container: &'a Container<'a>,
+}
+
+impl<'a> Parser<'a> {
+    pub fn new(container: &'a Container<'a>) -> Self {
+        Self { container }
+    }
+
+    pub fn parse(&self) -> Decl {
+        match self.container.serde_data() {
+            Data::Struct(style, ref fields) => self.parse_struct(*style, fields),
+            Data::Enum(ref variants) => self.parse_enum(variants),
+        }
+    }
+
+    fn create_relevant_type_params(&self, type_ref_names: HashSet<&String>) -> Vec<String> {
+        self.container
+            .generics()
+            .type_params()
+            .into_iter()
+            .map(|p| p.ident.to_string())
+            .filter(|t| type_ref_names.contains(t))
+            .collect()
+    }
+
+    fn create_type_alias_decl(&self, type_ann: TsType) -> Decl {
+        Decl::TsTypeAlias(TsTypeAliasDecl {
+            id: self.container.name(),
+            export: true,
+            type_params: self.create_relevant_type_params(type_ann.type_ref_names()),
+            type_ann,
+        })
+    }
+
+    fn create_decl(&self, members: Vec<TsTypeElement>, extends: Vec<TsType>) -> Decl {
+        // An interface can only extend an identifier/qualified-name with optional type arguments.
+        if extends.iter().all(|ty| ty.is_ref()) {
+            let mut type_ref_names: HashSet<&String> = HashSet::new();
+            members.iter().for_each(|member| {
+                type_ref_names.extend(member.type_ann.type_ref_names());
+            });
+            extends.iter().for_each(|ty| {
+                type_ref_names.extend(ty.type_ref_names());
+            });
+
+            let type_params = self.create_relevant_type_params(type_ref_names);
+
+            Decl::TsInterface(TsInterfaceDecl {
+                id: self.container.name(),
+                type_params,
+                extends,
+                body: members,
+            })
+        } else {
+            let extra = TsType::Intersection(
+                extends
+                    .into_iter()
+                    .map(|ty| match ty {
+                        TsType::Option(ty) => TsType::Union(vec![*ty, TsType::empty_type_lit()]),
+                        _ => ty,
+                    })
+                    .collect(),
+            );
+            let type_ann = TsType::TypeLit(TsTypeLit { members }).and(extra);
+
+            self.create_type_alias_decl(type_ann)
+        }
+    }
+
+    fn parse_struct(&self, style: Style, fields: &[Field]) -> Decl {
+        let parsed_fields = self.parse_fields(style, fields);
+        let tag_type = self.container.serde_attrs().tag();
+
+        match (tag_type, parsed_fields) {
+            (TagType::Internal { tag }, ParsedFields::Named(members, extends)) => {
+                let name = self.container.name();
+
+                let tag_field = TsTypeElement {
+                    key: tag.clone(),
+                    type_ann: TsType::Lit(name),
+                    optional: false,
+                };
+
+                let mut vec = Vec::with_capacity(members.len() + 1);
+                vec.push(tag_field);
+                vec.extend(members);
+
+                self.create_decl(vec, extends)
+            }
+            (_, ParsedFields::Named(members, extends)) => self.create_decl(members, extends),
+            (_, parsed_fields) => self.create_type_alias_decl(parsed_fields.into()),
+        }
+    }
+
+    fn parse_fields(&self, style: Style, fields: &[Field]) -> ParsedFields {
+        let style = match style {
+            Style::Struct => FieldsStyle::Named,
+            Style::Newtype => return ParsedFields::Transparent(self.parse_field(&fields[0]).0),
+            Style::Tuple => FieldsStyle::Unnamed,
+            Style::Unit => return ParsedFields::Transparent(TsType::nullish()),
+        };
+
+        let fields = fields
+            .iter()
+            .filter(|field| {
+                !field.attrs.skip_serializing()
+                    && !field.attrs.skip_deserializing()
+                    && !is_phantom(field.ty)
+            })
+            .collect::<Vec<_>>();
+
+        if fields.len() == 1 && self.container.transparent() {
+            return ParsedFields::Transparent(self.parse_field(fields[0]).0);
+        }
+
+        match style {
+            FieldsStyle::Named => {
+                let (members, flatten_fields) = self.parse_named_fields(fields);
+
+                ParsedFields::Named(members, flatten_fields)
+            }
+            FieldsStyle::Unnamed => {
+                let elems = fields
+                    .into_iter()
+                    .map(|field| self.parse_field(field).0)
+                    .collect();
+
+                ParsedFields::Unnamed(elems)
+            }
+        }
+    }
+
+    fn parse_field(&self, field: &Field) -> (TsType, Option<TsifyFieldAttrs>) {
+        let ts_attrs = match TsifyFieldAttrs::from_serde_field(field) {
+            Ok(attrs) => attrs,
+            Err(err) => {
+                self.container.syn_error(err);
+                return (TsType::NEVER, None);
+            }
+        };
+
+        let type_ann = TsType::from(field.ty);
+
+        if let Some(t) = &ts_attrs.type_override {
+            let type_ref_names = type_ann.type_ref_names();
+            let type_params = self.create_relevant_type_params(type_ref_names);
+            (
+                TsType::Override {
+                    type_override: t.clone(),
+                    type_params,
+                },
+                Some(ts_attrs),
+            )
+        } else {
+            (type_ann, Some(ts_attrs))
+        }
+    }
+
+    fn parse_named_fields(&self, fields: Vec<&Field>) -> (Vec<TsTypeElement>, Vec<TsType>) {
+        let (flatten_fields, members): (Vec<_>, Vec<_>) =
+            fields.into_iter().partition(|field| field.attrs.flatten());
+
+        let members = members
+            .into_iter()
+            .map(|field| {
+                let key = field.attrs.name().serialize_name();
+                let (type_ann, field_attrs) = self.parse_field(field);
+
+                let optional = field_attrs.map_or(false, |attrs| attrs.optional);
+                let default_is_none = self.container.serde_attrs().default().is_none()
+                    && field.attrs.default().is_none();
+
+                let type_ann = if optional {
+                    match type_ann {
+                        TsType::Option(t) => *t,
+                        _ => type_ann,
+                    }
+                } else {
+                    type_ann
+                };
+
+                TsTypeElement {
+                    key,
+                    type_ann,
+                    optional: optional || !default_is_none,
+                }
+            })
+            .collect();
+
+        let flatten_fields = flatten_fields
+            .into_iter()
+            .map(|field| self.parse_field(field).0)
+            .collect();
+
+        (members, flatten_fields)
+    }
+
+    fn parse_enum(&self, variants: &[Variant]) -> Decl {
+        let members = variants
+            .iter()
+            .filter(|v| !v.attrs.skip_serializing() && !v.attrs.skip_deserializing())
+            .map(|variant| {
+                let decl = self.create_type_alias_decl(self.parse_variant(variant));
+                if let Decl::TsTypeAlias(mut type_alias) = decl {
+                    type_alias.id = variant.attrs.name().serialize_name();
+
+                    type_alias
+                } else {
+                    panic!();
+                }
+            })
+            .collect::<Vec<_>>();
+
+        let type_ref_names = members
+            .iter()
+            .flat_map(|type_alias| type_alias.type_ann.type_ref_names())
+            .collect::<HashSet<_>>();
+
+        let relevant_type_params = self.create_relevant_type_params(type_ref_names);
+
+        Decl::TsEnum(TsEnumDecl {
+            id: self.container.name(),
+            type_params: relevant_type_params,
+            members,
+            namespace: self.container.attrs.namespace,
+        })
+    }
+
+    fn parse_variant(&self, variant: &Variant) -> TsType {
+        let tag_type = self.container.serde_attrs().tag();
+        let name = variant.attrs.name().serialize_name();
+        let style = variant.style;
+        let type_ann: TsType = self.parse_fields(style, &variant.fields).into();
+        type_ann.with_tag_type(name, style, tag_type)
+    }
+}
+
+fn is_phantom(ty: &syn::Type) -> bool {
+    if let syn::Type::Path(syn::TypePath { path, .. }) = ty {
+        path.segments
+            .last()
+            .map_or(false, |path| path.ident == "PhantomData")
+    } else {
+        false
+    }
+}

+ 41 - 0
domain/libthreema/patches/tsify/tsify-macros/src/type_alias.rs

@@ -0,0 +1,41 @@
+use proc_macro2::TokenStream;
+use quote::quote;
+
+use crate::{ctxt::Ctxt, decl::TsTypeAliasDecl, typescript::TsType};
+
+pub fn expend(item: syn::ItemType) -> syn::Result<TokenStream> {
+    let ctxt = Ctxt::new();
+
+    let type_ann = TsType::from(item.ty.as_ref());
+
+    let decl = TsTypeAliasDecl {
+        id: item.ident.to_string(),
+        export: true,
+        type_params: item
+            .generics
+            .type_params()
+            .map(|ty| ty.ident.to_string())
+            .collect(),
+        type_ann,
+    };
+
+    let decl_str = decl.to_string();
+
+    let typescript_custom_section = quote! {
+        #[automatically_derived]
+        const _: () = {
+            use wasm_bindgen::prelude::*;
+            #[wasm_bindgen(typescript_custom_section)]
+            const TS_APPEND_CONTENT: &'static str = #decl_str;
+        };
+    };
+
+    ctxt.check()?;
+
+    let tokens = quote! {
+      #item
+      #typescript_custom_section
+    };
+
+    Ok(tokens)
+}

+ 806 - 0
domain/libthreema/patches/tsify/tsify-macros/src/typescript.rs

@@ -0,0 +1,806 @@
+use std::{collections::HashSet, fmt::Display};
+
+use serde_derive_internals::{ast::Style, attr::TagType};
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum TsKeywordTypeKind {
+    Number,
+    Bigint,
+    Boolean,
+    String,
+    Void,
+    Undefined,
+    Null,
+    Never,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct TsTypeElement {
+    pub key: String,
+    pub type_ann: TsType,
+    pub optional: bool,
+}
+
+impl From<TsTypeElement> for TsTypeLit {
+    fn from(m: TsTypeElement) -> Self {
+        TsTypeLit { members: vec![m] }
+    }
+}
+
+impl From<TsTypeElement> for TsType {
+    fn from(m: TsTypeElement) -> Self {
+        TsType::TypeLit(m.into())
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct TsTypeLit {
+    pub members: Vec<TsTypeElement>,
+}
+
+impl From<TsTypeLit> for TsType {
+    fn from(lit: TsTypeLit) -> Self {
+        TsType::TypeLit(lit)
+    }
+}
+
+impl TsTypeLit {
+    fn get_mut(&mut self, key: &String) -> Option<&mut TsTypeElement> {
+        self.members.iter_mut().find(|member| &member.key == key)
+    }
+
+    fn and(self, other: Self) -> Self {
+        let init = TsTypeLit { members: vec![] };
+
+        self.members
+            .into_iter()
+            .chain(other.members.into_iter())
+            .fold(init, |mut acc, m| {
+                if let Some(acc_m) = acc.get_mut(&m.key) {
+                    let mut tmp = TsType::NULL;
+                    std::mem::swap(&mut acc_m.type_ann, &mut tmp);
+                    acc_m.type_ann = tmp.and(m.type_ann);
+                } else {
+                    acc.members.push(m)
+                }
+
+                acc
+            })
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum TsType {
+    Keyword(TsKeywordTypeKind),
+    Lit(String),
+    Array(Box<Self>),
+    Tuple(Vec<Self>),
+    Option(Box<Self>),
+    Ref {
+        name: String,
+        type_params: Vec<Self>,
+    },
+    Fn {
+        params: Vec<Self>,
+        type_ann: Box<Self>,
+    },
+    TypeLit(TsTypeLit),
+    Intersection(Vec<Self>),
+    Union(Vec<Self>),
+    Override {
+        type_override: String,
+        type_params: Vec<String>,
+    },
+}
+
+macro_rules! type_lit {
+    ($($k: ident: $t: path);* $(;)?) => {
+        TsType::TypeLit(TsTypeLit {
+            members: vec![$(
+                TsTypeElement {
+                    key: stringify!($k).to_string(),
+                    type_ann: $t,
+                    optional: false,
+                }
+            ),*],
+        })
+    };
+}
+
+impl From<TsKeywordTypeKind> for TsType {
+    fn from(kind: TsKeywordTypeKind) -> Self {
+        Self::Keyword(kind)
+    }
+}
+
+impl From<&syn::Type> for TsType {
+    fn from(ty: &syn::Type) -> Self {
+        Self::from_syn_type(ty)
+    }
+}
+
+impl TsType {
+    pub const NUMBER: TsType = TsType::Keyword(TsKeywordTypeKind::Number);
+    pub const BIGINT: TsType = TsType::Keyword(TsKeywordTypeKind::Bigint);
+    pub const BOOLEAN: TsType = TsType::Keyword(TsKeywordTypeKind::Boolean);
+    pub const STRING: TsType = TsType::Keyword(TsKeywordTypeKind::String);
+    pub const VOID: TsType = TsType::Keyword(TsKeywordTypeKind::Void);
+    pub const UNDEFINED: TsType = TsType::Keyword(TsKeywordTypeKind::Undefined);
+    pub const NULL: TsType = TsType::Keyword(TsKeywordTypeKind::Null);
+    pub const NEVER: TsType = TsType::Keyword(TsKeywordTypeKind::Never);
+
+    pub const fn nullish() -> Self {
+        if cfg!(feature = "js") {
+            Self::UNDEFINED
+        } else {
+            Self::NULL
+        }
+    }
+
+    pub const fn empty_type_lit() -> Self {
+        Self::TypeLit(TsTypeLit { members: vec![] })
+    }
+
+    pub fn is_ref(&self) -> bool {
+        matches!(self, Self::Ref { .. })
+    }
+
+    pub fn and(self, other: Self) -> Self {
+        match (self, other) {
+            (TsType::TypeLit(x), TsType::TypeLit(y)) => x.and(y).into(),
+            (TsType::Intersection(x), TsType::Intersection(y)) => {
+                let mut vec = Vec::with_capacity(x.len() + y.len());
+                vec.extend(x);
+                vec.extend(y);
+                TsType::Intersection(vec)
+            }
+            (TsType::Intersection(x), y) => {
+                let mut vec = Vec::with_capacity(x.len() + 1);
+                vec.extend(x);
+                vec.push(y);
+                TsType::Intersection(vec)
+            }
+            (x, TsType::Intersection(y)) => {
+                let mut vec = Vec::with_capacity(y.len() + 1);
+                vec.push(x);
+                vec.extend(y);
+                TsType::Intersection(vec)
+            }
+            (x, y) => TsType::Intersection(vec![x, y]),
+        }
+    }
+
+    fn bytes_short_circuit(ty: &syn::Type) -> Option<Self> {
+        use syn::Type::*;
+        use syn::TypePath;
+
+        let Path(TypePath { path, .. }) = ty else {
+            return None
+        };
+        let Some(segment) = path.segments.last() else {
+            return None
+        };
+        let name = segment.ident.to_string();
+        match name.as_str() {
+            "u8" => Some(Self::Ref {
+                name: "Uint8Array".to_string(),
+                type_params: vec![],
+            }),
+            _ => None,
+        }
+    }
+
+    fn from_syn_type(ty: &syn::Type) -> Self {
+        use syn::Type::*;
+        use syn::{
+            TypeArray, TypeBareFn, TypeGroup, TypeImplTrait, TypeParamBound, TypeParen, TypePath,
+            TypeReference, TypeSlice, TypeTraitObject, TypeTuple,
+        };
+
+        match ty {
+            Array(TypeArray { elem, len, .. }) => {
+                Self::bytes_short_circuit(elem).unwrap_or_else(|| {
+                    let elem = Self::from_syn_type(elem);
+                    let len = parse_len(len);
+
+                    match len {
+                        Some(len) if len <= 16 => Self::Tuple(vec![elem; len]),
+                        _ => Self::Array(Box::new(elem)),
+                    }
+                })
+            }
+
+            Slice(TypeSlice { elem, .. }) => Self::bytes_short_circuit(elem)
+                .unwrap_or_else(|| Self::Array(Box::new(Self::from_syn_type(elem)))),
+
+            Reference(TypeReference { elem, .. })
+            | Paren(TypeParen { elem, .. })
+            | Group(TypeGroup { elem, .. }) => Self::from_syn_type(elem),
+
+            BareFn(TypeBareFn { inputs, output, .. }) => {
+                let params = inputs
+                    .iter()
+                    .map(|arg| Self::from_syn_type(&arg.ty))
+                    .collect();
+
+                let type_ann = if let syn::ReturnType::Type(_, ty) = output {
+                    Self::from_syn_type(ty)
+                } else {
+                    TsType::VOID
+                };
+
+                Self::Fn {
+                    params,
+                    type_ann: Box::new(type_ann),
+                }
+            }
+
+            Tuple(TypeTuple { elems, .. }) => {
+                if elems.is_empty() {
+                    TsType::nullish()
+                } else {
+                    let elems = elems.iter().map(Self::from_syn_type).collect();
+                    Self::Tuple(elems)
+                }
+            }
+
+            Path(TypePath { path, .. }) => Self::from_path(path).unwrap_or(TsType::NEVER),
+
+            TraitObject(TypeTraitObject { bounds, .. })
+            | ImplTrait(TypeImplTrait { bounds, .. }) => {
+                let elems = bounds
+                    .iter()
+                    .filter_map(|t| match t {
+                        TypeParamBound::Trait(t) => Self::from_path(&t.path),
+                        _ => None, // skip lifetime etc.
+                    })
+                    .collect();
+
+                Self::Intersection(elems)
+            }
+
+            Ptr(_) | Infer(_) | Macro(_) | Never(_) | Verbatim(_) => TsType::NEVER,
+
+            _ => TsType::NEVER,
+        }
+    }
+
+    fn from_path(path: &syn::Path) -> Option<Self> {
+        path.segments.last().map(Self::from_path_segment)
+    }
+
+    fn from_path_segment(segment: &syn::PathSegment) -> Self {
+        let name = segment.ident.to_string();
+
+        let (args, output) = match &segment.arguments {
+            syn::PathArguments::AngleBracketed(path) => {
+                let args = path
+                    .args
+                    .iter()
+                    .filter_map(|p| match p {
+                        syn::GenericArgument::Type(t) => Some(t),
+                        syn::GenericArgument::AssocType(t) => Some(&t.ty),
+                        _ => None,
+                    })
+                    .collect();
+
+                (args, None)
+            }
+
+            syn::PathArguments::Parenthesized(path) => {
+                let args = path.inputs.iter().collect();
+
+                let output = match &path.output {
+                    syn::ReturnType::Default => None,
+                    syn::ReturnType::Type(_, tp) => Some(tp.as_ref()),
+                };
+
+                (args, output)
+            }
+
+            syn::PathArguments::None => (vec![], None),
+        };
+
+        match name.as_str() {
+            "u8" | "u16" | "u32" | "u64" | "usize" | "i8" | "i16" | "i32" | "i64" | "isize"
+            | "f64" | "f32" => Self::NUMBER,
+
+            "u128" | "i128" => {
+                if cfg!(feature = "js") {
+                    Self::BIGINT
+                } else {
+                    Self::NUMBER
+                }
+            }
+
+            "String" | "str" | "char" | "Path" | "PathBuf" => Self::STRING,
+
+            "bool" => Self::BOOLEAN,
+
+            "Box" | "Cow" | "Rc" | "Arc" | "Cell" | "RefCell" if args.len() == 1 => {
+                Self::from_syn_type(args[0])
+            }
+
+            "Vec" if args.len() == 1 => Self::bytes_short_circuit(args[0]).unwrap_or_else(|| {
+                let elem = Self::from_syn_type(args[0]);
+                Self::Array(Box::new(elem))
+            }),
+
+            "VecDeque" | "LinkedList" if args.len() == 1 => {
+                let elem = Self::from_syn_type(args[0]);
+                Self::Array(Box::new(elem))
+            }
+
+            "HashMap" | "BTreeMap" if args.len() == 2 => {
+                let type_params = args.iter().map(|arg| Self::from_syn_type(arg)).collect();
+
+                let name = if cfg!(feature = "js") {
+                    "Map"
+                } else {
+                    "Record"
+                }
+                .to_string();
+
+                Self::Ref { name, type_params }
+            }
+
+            "HashSet" | "BTreeSet" if args.len() == 1 => {
+                let elem = Self::from_syn_type(args[0]);
+                Self::Array(Box::new(elem))
+            }
+
+            "Option" if args.len() == 1 => Self::Option(Box::new(Self::from_syn_type(args[0]))),
+
+            "Result" if args.len() == 2 => {
+                let arg0 = Self::from_syn_type(args[0]);
+                let arg1 = Self::from_syn_type(args[1]);
+
+                let ok = type_lit! { Ok: arg0 };
+                let err = type_lit! { Err: arg1 };
+
+                Self::Union(vec![ok, err])
+            }
+
+            "Duration" => type_lit! {
+                secs: Self::NUMBER;
+                nanos: Self::NUMBER;
+            },
+
+            "SystemTime" => type_lit! {
+                secs_since_epoch: Self::NUMBER;
+                nanos_since_epoch: Self::NUMBER;
+            },
+
+            "Range" | "RangeInclusive" => {
+                let start = Self::from_syn_type(args[0]);
+                let end = start.clone();
+
+                type_lit! {
+                    start: start;
+                    end: end;
+                }
+            }
+
+            "Fn" | "FnOnce" | "FnMut" => {
+                let params = args.into_iter().map(Self::from_syn_type).collect();
+                let type_ann = output
+                    .map(Self::from_syn_type)
+                    .unwrap_or_else(|| TsType::VOID);
+
+                Self::Fn {
+                    params,
+                    type_ann: Box::new(type_ann),
+                }
+            }
+            _ => {
+                let type_params = args.into_iter().map(Self::from_syn_type).collect();
+                Self::Ref { name, type_params }
+            }
+        }
+    }
+
+    pub fn with_tag_type(self, name: String, style: Style, tag_type: &TagType) -> Self {
+        let type_ann = self;
+
+        match tag_type {
+            TagType::External => {
+                if matches!(style, Style::Unit) {
+                    TsType::Lit(name)
+                } else {
+                    TsTypeElement {
+                        key: name,
+                        type_ann,
+                        optional: false,
+                    }
+                    .into()
+                }
+            }
+            TagType::Internal { tag } => {
+                if type_ann == TsType::nullish() {
+                    let tag_field: TsType = TsTypeElement {
+                        key: tag.clone(),
+                        type_ann: TsType::Lit(name),
+                        optional: false,
+                    }
+                    .into();
+
+                    tag_field
+                } else {
+                    let tag_field: TsType = TsTypeElement {
+                        key: tag.clone(),
+                        type_ann: TsType::Lit(name),
+                        optional: false,
+                    }
+                    .into();
+
+                    tag_field.and(type_ann)
+                }
+            }
+            TagType::Adjacent { tag, content } => {
+                let tag_field = TsTypeElement {
+                    key: tag.clone(),
+                    type_ann: TsType::Lit(name),
+                    optional: false,
+                };
+
+                if matches!(style, Style::Unit) {
+                    tag_field.into()
+                } else {
+                    let content_field = TsTypeElement {
+                        key: content.clone(),
+                        type_ann,
+                        optional: false,
+                    };
+
+                    TsTypeLit {
+                        members: vec![tag_field, content_field],
+                    }
+                    .into()
+                }
+            }
+            TagType::None => type_ann,
+        }
+    }
+
+    pub fn visit<'a, F: FnMut(&'a TsType)>(&'a self, f: &mut F) {
+        f(self);
+
+        match self {
+            TsType::Ref { type_params, .. } => {
+                type_params.iter().for_each(|t| t.visit(f));
+            }
+            TsType::Array(elem) => elem.visit(f),
+            TsType::Tuple(elems) => {
+                elems.iter().for_each(|t| t.visit(f));
+            }
+            TsType::Option(t) => t.visit(f),
+            TsType::Fn { params, type_ann } => {
+                params
+                    .iter()
+                    .chain(Some(type_ann.as_ref()))
+                    .for_each(|t| t.visit(f));
+            }
+            TsType::TypeLit(TsTypeLit { members }) => {
+                members.iter().for_each(|m| m.type_ann.visit(f));
+            }
+            TsType::Intersection(tys) | TsType::Union(tys) => {
+                tys.iter().for_each(|t| t.visit(f));
+            }
+            TsType::Keyword(_) | TsType::Lit(_) | TsType::Override { .. } => (),
+        }
+    }
+
+    pub fn type_ref_names(&self) -> HashSet<&String> {
+        let mut set: HashSet<&String> = HashSet::new();
+
+        self.visit(&mut |ty: &TsType| match ty {
+            TsType::Ref { name, .. } => {
+                set.insert(name);
+            }
+            TsType::Override { type_params, .. } => set.extend(type_params),
+            _ => (),
+        });
+
+        set
+    }
+
+    pub fn prefix_type_refs(self, prefix: &String, exceptions: &Vec<String>) -> Self {
+        match self {
+            TsType::Array(t) => TsType::Array(Box::new(t.prefix_type_refs(prefix, exceptions))),
+            TsType::Tuple(tv) => TsType::Tuple(
+                tv.iter()
+                    .map(|t| t.clone().prefix_type_refs(prefix, exceptions))
+                    .collect(),
+            ),
+            TsType::Option(t) => TsType::Option(Box::new(t.prefix_type_refs(prefix, exceptions))),
+            TsType::Ref { name, type_params } => {
+                if exceptions.contains(&name) {
+                    TsType::Ref {
+                        name,
+                        type_params: type_params
+                            .iter()
+                            .map(|t| t.clone().prefix_type_refs(prefix, exceptions))
+                            .collect(),
+                    }
+                } else {
+                    TsType::Ref {
+                        name: format!("{}{}", prefix, name),
+                        type_params: type_params
+                            .iter()
+                            .map(|t| t.clone().prefix_type_refs(prefix, exceptions))
+                            .collect(),
+                    }
+                }
+            }
+            TsType::Fn { params, type_ann } => TsType::Fn {
+                params: params
+                    .iter()
+                    .map(|t| t.clone().prefix_type_refs(prefix, exceptions))
+                    .collect(),
+                type_ann: Box::new(type_ann.prefix_type_refs(prefix, exceptions)),
+            },
+            TsType::TypeLit(lit) => TsType::TypeLit(TsTypeLit {
+                members: lit
+                    .members
+                    .iter()
+                    .map(|t| TsTypeElement {
+                        key: t.key.clone(),
+                        optional: t.optional,
+                        type_ann: t.type_ann.clone().prefix_type_refs(prefix, exceptions),
+                    })
+                    .collect(),
+            }),
+            TsType::Intersection(tv) => TsType::Intersection(
+                tv.iter()
+                    .map(|t| t.clone().prefix_type_refs(prefix, exceptions))
+                    .collect(),
+            ),
+            TsType::Union(tv) => TsType::Union(
+                tv.iter()
+                    .map(|t| t.clone().prefix_type_refs(prefix, exceptions))
+                    .collect(),
+            ),
+            _ => self,
+        }
+    }
+
+    pub fn type_refs(&self, type_refs: &mut Vec<(String, Vec<TsType>)>) {
+        match self {
+            TsType::Array(t) | TsType::Option(t) => t.type_refs(type_refs),
+            TsType::Tuple(tv) | TsType::Union(tv) | TsType::Intersection(tv) => {
+                tv.iter().for_each(|t| t.type_refs(type_refs))
+            }
+            TsType::Ref { name, type_params } => {
+                type_refs.push((name.clone(), type_params.clone()));
+                type_params
+                    .iter()
+                    .for_each(|t| t.clone().type_refs(type_refs));
+            }
+            TsType::Fn { params, type_ann } => {
+                params.iter().for_each(|t| t.clone().type_refs(type_refs));
+                type_ann.type_refs(type_refs);
+            }
+            TsType::TypeLit(lit) => {
+                lit.members.iter().for_each(|t| {
+                    t.type_ann.type_refs(type_refs);
+                });
+            }
+            _ => {}
+        }
+    }
+}
+
+fn parse_len(expr: &syn::Expr) -> Option<usize> {
+    if let syn::Expr::Lit(syn::ExprLit {
+        lit: syn::Lit::Int(lit_int),
+        ..
+    }) = expr
+    {
+        lit_int.base10_parse::<usize>().ok()
+    } else {
+        None
+    }
+}
+
+fn is_js_ident(string: &str) -> bool {
+    !string.contains('-')
+}
+
+impl Display for TsTypeElement {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        let key = &self.key;
+        let type_ann = &self.type_ann;
+
+        let optional_ann = if self.optional { "?" } else { "" };
+
+        if is_js_ident(key) {
+            write!(f, "{key}{optional_ann}: {type_ann}")
+        } else {
+            write!(f, "\"{key}\"{optional_ann}: {type_ann}")
+        }
+    }
+}
+
+impl Display for TsTypeLit {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        let members = self
+            .members
+            .iter()
+            .map(|elem| elem.to_string())
+            .collect::<Vec<_>>()
+            .join("; ");
+
+        if members.is_empty() {
+            write!(f, "{{}}")
+        } else {
+            write!(f, "{{ {members} }}")
+        }
+    }
+}
+
+impl Display for TsType {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            TsType::Keyword(kind) => {
+                let ty = format!("{:?}", kind).to_lowercase();
+                write!(f, "{ty}")
+            }
+
+            TsType::Lit(lit) => {
+                write!(f, "\"{lit}\"")
+            }
+
+            TsType::Array(elem) => match elem.as_ref() {
+                TsType::Union(_) | TsType::Intersection(_) | &TsType::Option(_) => {
+                    write!(f, "({elem})[]")
+                }
+                _ => write!(f, "{elem}[]"),
+            },
+
+            TsType::Tuple(elems) => {
+                let elems = elems
+                    .iter()
+                    .map(|elem| elem.to_string())
+                    .collect::<Vec<_>>()
+                    .join(", ");
+
+                write!(f, "[{elems}]")
+            }
+
+            TsType::Ref { name, type_params } => {
+                let params = type_params
+                    .iter()
+                    .map(|param| param.to_string())
+                    .collect::<Vec<_>>()
+                    .join(", ");
+
+                if params.is_empty() {
+                    write!(f, "{name}")
+                } else {
+                    write!(f, "{name}<{params}>")
+                }
+            }
+
+            TsType::Fn { params, type_ann } => {
+                let params = params
+                    .iter()
+                    .enumerate()
+                    .map(|(i, param)| format!("arg{i}: {param}"))
+                    .collect::<Vec<_>>()
+                    .join(", ");
+
+                write!(f, "({params}) => {type_ann}")
+            }
+
+            TsType::Option(elem) => {
+                write!(f, "{elem} | {}", TsType::nullish())
+            }
+
+            TsType::TypeLit(type_lit) => {
+                write!(f, "{type_lit}")
+            }
+
+            TsType::Intersection(types) => {
+                if types.len() == 1 {
+                    let ty = &types[0];
+                    return write!(f, "{ty}");
+                }
+
+                let types = types
+                    .iter()
+                    .map(|ty| match ty {
+                        TsType::Union(_) => format!("({ty})"),
+                        _ => ty.to_string(),
+                    })
+                    .collect::<Vec<_>>()
+                    .join(" & ");
+
+                write!(f, "{types}")
+            }
+
+            TsType::Union(types) => {
+                if types.len() == 1 {
+                    let ty = &types[0];
+                    return write!(f, "{ty}");
+                }
+
+                let types = types
+                    .iter()
+                    .map(|ty| match ty {
+                        TsType::Intersection(_) => format!("({ty})"),
+                        _ => ty.to_string(),
+                    })
+                    .collect::<Vec<_>>()
+                    .join(" | ");
+
+                write!(f, "{types}")
+            }
+
+            TsType::Override { type_override, .. } => f.write_str(type_override),
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::TsType;
+
+    macro_rules! assert_ts {
+        ( $( $t:ty )|* , $expected:expr) => {
+          $({
+            let ty: syn::Type = syn::parse_quote!($t);
+            let ts_type = TsType::from_syn_type(&ty);
+            assert_eq!(ts_type.to_string(), $expected);
+          })*
+        };
+      }
+
+    #[test]
+    fn test_basic_types() {
+        if cfg!(feature = "js") {
+            assert_ts!((), "undefined");
+            assert_ts!(u128 | i128, "bigint");
+            assert_ts!(HashMap<String, i32> | BTreeMap<String, i32>, "Map<string, number>");
+            assert_ts!(Option<i32>, "number | undefined");
+            assert_ts!(Vec<Option<T>> | VecDeque<Option<T>> | LinkedList<Option<T>> | &'a [Option<T>], "(T | undefined)[]");
+        } else {
+            assert_ts!((), "null");
+            assert_ts!(u128 | i128, "number");
+            assert_ts!(HashMap<String, i32> | BTreeMap<String, i32>, "Record<string, number>");
+            assert_ts!(Option<i32>, "number | null");
+            assert_ts!(Vec<Option<T>> | VecDeque<Option<T>> | LinkedList<Option<T>> | &'a [Option<T>], "(T | null)[]");
+        }
+
+        assert_ts!(
+            u8 | u16 | u32 | u64 | usize | i8 | i16 | i32 | i64 | isize | f32 | f64,
+            "number"
+        );
+        assert_ts!(String | str | char | Path | PathBuf, "string");
+        assert_ts!(bool, "boolean");
+        assert_ts!(Box<i32> | Rc<i32> | Arc<i32> | Cell<i32> | RefCell<i32> | Cow<'a, i32>, "number");
+        assert_ts!(Vec<i32> | VecDeque<i32> | LinkedList<i32> | &'a [i32], "number[]");
+        assert_ts!(HashSet<i32> | BTreeSet<i32>, "number[]");
+
+        assert_ts!(Result<i32, String>, "{ Ok: number } | { Err: string }");
+        assert_ts!(dyn Fn(String, f64) | dyn FnOnce(String, f64) | dyn FnMut(String, f64), "(arg0: string, arg1: number) => void");
+        assert_ts!(dyn Fn(String) -> i32 | dyn FnOnce(String) -> i32 | dyn FnMut(String) -> i32, "(arg0: string) => number");
+
+        assert_ts!((i32), "number");
+        assert_ts!((i32, String, bool), "[number, string, boolean]");
+
+        assert_ts!([i32; 4], "[number, number, number, number]");
+        assert_ts!([i32; 16], format!("[{}]", ["number"; 16].join(", ")));
+        assert_ts!([i32; 17], "number[]");
+        assert_ts!([i32; 1 + 1], "number[]");
+
+        assert_ts!(Duration, "{ secs: number; nanos: number }");
+        assert_ts!(
+            SystemTime,
+            "{ secs_since_epoch: number; nanos_since_epoch: number }"
+        );
+
+        assert_ts!(Range<i32>, "{ start: number; end: number }");
+        assert_ts!(Range<&'static str>, "{ start: string; end: string }");
+        assert_ts!(RangeInclusive<usize>, "{ start: number; end: number }");
+    }
+}

+ 141 - 0
domain/libthreema/patches/tsify/tsify-macros/src/wasm_bindgen.rs

@@ -0,0 +1,141 @@
+use proc_macro2::TokenStream;
+use quote::quote;
+use syn::parse_quote;
+
+use crate::{container::Container, decl::Decl};
+
+pub fn expand(cont: &Container, decl: Decl) -> TokenStream {
+    let attrs = &cont.attrs;
+    let ident = cont.ident();
+
+    let decl_str = decl.to_string();
+    let (impl_generics, ty_generics, where_clause) = cont.generics().split_for_impl();
+
+    let typescript_custom_section = quote! {
+        #[wasm_bindgen(typescript_custom_section)]
+        const TS_APPEND_CONTENT: &'static str = #decl_str;
+    };
+
+    let wasm_abi = attrs.into_wasm_abi || attrs.from_wasm_abi;
+
+    let wasm_describe = wasm_abi.then(|| {
+        quote! {
+            impl #impl_generics WasmDescribe for #ident #ty_generics #where_clause {
+                #[inline]
+                fn describe() {
+                    <Self as Tsify>::JsType::describe()
+                }
+            }
+        }
+    });
+
+    let use_serde = wasm_abi.then(|| match cont.serde_container.attrs.custom_serde_path() {
+        Some(path) => quote! {
+            use #path as _serde;
+        },
+        None => quote! {
+            extern crate serde as _serde;
+        },
+    });
+    let into_wasm_abi = attrs.into_wasm_abi.then(|| expand_into_wasm_abi(cont));
+    let from_wasm_abi = attrs.from_wasm_abi.then(|| expand_from_wasm_abi(cont));
+
+    let typescript_type = decl.id();
+
+    quote! {
+        #[automatically_derived]
+        const _: () = {
+            #use_serde
+            use tsify::Tsify;
+            use wasm_bindgen::{
+                convert::{FromWasmAbi, IntoWasmAbi, OptionFromWasmAbi, OptionIntoWasmAbi},
+                describe::WasmDescribe,
+                prelude::*,
+            };
+
+
+            #[wasm_bindgen]
+            extern "C" {
+                #[wasm_bindgen(typescript_type = #typescript_type)]
+                pub type JsType;
+            }
+
+            impl #impl_generics Tsify for #ident #ty_generics #where_clause {
+                type JsType = JsType;
+                const DECL: &'static str = #decl_str;
+            }
+
+            #typescript_custom_section
+            #wasm_describe
+            #into_wasm_abi
+            #from_wasm_abi
+        };
+    }
+}
+
+fn expand_into_wasm_abi(cont: &Container) -> TokenStream {
+    let ident = cont.ident();
+    let serde_path = cont.serde_container.attrs.serde_path();
+
+    let mut generics = cont.generics().clone();
+    generics
+        .make_where_clause()
+        .predicates
+        .push(parse_quote!(Self: #serde_path::Serialize));
+
+    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
+
+    quote! {
+        impl #impl_generics IntoWasmAbi for #ident #ty_generics #where_clause {
+            type Abi = <JsType as IntoWasmAbi>::Abi;
+
+            #[inline]
+            fn into_abi(self) -> Self::Abi {
+                self.into_js().unwrap_throw().into_abi()
+            }
+        }
+
+        impl #impl_generics OptionIntoWasmAbi for #ident #ty_generics #where_clause {
+            #[inline]
+            fn none() -> Self::Abi {
+                <JsType as OptionIntoWasmAbi>::none()
+            }
+        }
+    }
+}
+
+fn expand_from_wasm_abi(cont: &Container) -> TokenStream {
+    let ident = cont.ident();
+    let serde_path = cont.serde_container.attrs.serde_path();
+
+    let mut generics = cont.generics().clone();
+
+    generics
+        .make_where_clause()
+        .predicates
+        .push(parse_quote!(Self: #serde_path::de::DeserializeOwned));
+
+    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
+
+    quote! {
+        impl #impl_generics FromWasmAbi for #ident #ty_generics #where_clause {
+            type Abi = <JsType as FromWasmAbi>::Abi;
+
+            #[inline]
+            unsafe fn from_abi(js: Self::Abi) -> Self {
+                let result = Self::from_js(&JsType::from_abi(js));
+                if let Err(err) = result {
+                    wasm_bindgen::throw_str(err.to_string().as_ref());
+                }
+                result.unwrap_throw()
+            }
+        }
+
+        impl #impl_generics OptionFromWasmAbi for #ident #ty_generics #where_clause {
+            #[inline]
+            fn is_none(js: &Self::Abi) -> bool {
+                <JsType as OptionFromWasmAbi>::is_none(js)
+            }
+        }
+    }
+}