Compare commits

...

10 Commits

Author SHA1 Message Date
Evgeny Poberezkin fe0ff44f92 Merge branch 'master' into ep/add-splice 2023-11-17 11:00:44 +00:00
Evgeny Poberezkin 7a7f3dbd74 core: add simplemq version 2023-11-17 10:51:47 +00:00
Evgeny Poberezkin 0d7a048988 nix: patches for armv7a, fix segfault issues, etc (#3383)
* bump mobile-core-tools

* Update deps

* 🤦

* patch

* needs p

* 32bit patches

* bump haskell.nix

* bump again

* fix broken flake.lock

* bump haskell.nix

* bump haskell.nix (to fix darwin)

---------

Co-authored-by: Moritz Angermann <moritz.angermann@gmail.com>
2023-11-16 22:54:54 +00:00
spaced4ndy 975f6d488e android: fix group join via invitation chat item (#3372) 2023-11-15 11:46:45 +04:00
spaced4ndy 36509a6d79 ios, android: new message decryption error - ratchet synchronization (#3368) 2023-11-14 19:39:32 +04:00
spaced4ndy 5bbde22ffa core: new message decryption error - ratchet synchronization (#3367) 2023-11-14 18:23:05 +04:00
Evgeny Poberezkin 1e8ae6d861 docs: update windows app link 2023-11-14 09:37:24 +00:00
Evgeny Poberezkin a2fe5cfb66 core: fix incorrect JSON serialization (#3361) 2023-11-13 17:45:10 +00:00
Stanislav Dmitrenko 338417d963 desktop: catch unreadable crypto file (#3359) 2023-11-13 14:06:01 +00:00
spaced4ndy 5beeff5cb6 core: take chat lock when synchronizing ratchet (#3349) 2023-11-12 12:41:41 +00:00
20 changed files with 103 additions and 36 deletions
@@ -165,6 +165,8 @@ struct CIRcvDecryptionError: View {
message = Text("\(msgCount) messages failed to decrypt.") + Text("\n") + why
case .other:
message = Text("\(msgCount) messages failed to decrypt.") + Text("\n") + why
case .ratchetSync:
message = Text("Encryption re-negotiation failed.")
}
return message
}
+2
View File
@@ -2676,6 +2676,7 @@ public enum MsgDecryptError: String, Decodable {
case tooManySkipped
case ratchetEarlier
case other
case ratchetSync
var text: String {
switch self {
@@ -2683,6 +2684,7 @@ public enum MsgDecryptError: String, Decodable {
case .tooManySkipped: return NSLocalizedString("Permanent decryption error", comment: "message decrypt error item")
case .ratchetEarlier: return NSLocalizedString("Decryption error", comment: "message decrypt error item")
case .other: return NSLocalizedString("Decryption error", comment: "message decrypt error item")
case .ratchetSync: return NSLocalizedString("Encryption re-negotiation error", comment: "message decrypt error item")
}
}
}
@@ -35,7 +35,12 @@ actual fun shareFile(text: String, fileSource: CryptoFile) {
val tmpFile = File(tmpDir, fileSource.filePath)
tmpFile.deleteOnExit()
ChatModel.filesToDelete.add(tmpFile)
decryptCryptoFile(getAppFilePath(fileSource.filePath), fileSource.cryptoArgs, tmpFile.absolutePath)
try {
decryptCryptoFile(getAppFilePath(fileSource.filePath), fileSource.cryptoArgs, tmpFile.absolutePath)
} catch (e: Exception) {
Log.e(TAG, "Unable to decrypt crypto file: " + e.stackTraceToString())
return
}
getAppFileUri(tmpFile.absolutePath)
} else {
getAppFileUri(fileSource.filePath)
@@ -96,15 +101,21 @@ fun saveImage(ciFile: CIFile?) {
val outputStream = BufferedOutputStream(stream)
if (ciFile.fileSource?.cryptoArgs != null) {
createTmpFileAndDelete { tmpFile ->
decryptCryptoFile(filePath, ciFile.fileSource.cryptoArgs, tmpFile.absolutePath)
try {
decryptCryptoFile(filePath, ciFile.fileSource.cryptoArgs, tmpFile.absolutePath)
} catch (e: Exception) {
Log.e(TAG, "Unable to decrypt crypto file: " + e.stackTraceToString())
return@createTmpFileAndDelete
}
tmpFile.inputStream().use { it.copyTo(outputStream) }
showToast(generalGetString(MR.strings.image_saved))
}
outputStream.close()
} else {
File(filePath).inputStream().use { it.copyTo(outputStream) }
outputStream.close()
showToast(generalGetString(MR.strings.image_saved))
}
showToast(generalGetString(MR.strings.image_saved))
}
}
} else {
@@ -2106,13 +2106,15 @@ enum class MsgDecryptError {
@SerialName("ratchetHeader") RatchetHeader,
@SerialName("tooManySkipped") TooManySkipped,
@SerialName("ratchetEarlier") RatchetEarlier,
@SerialName("other") Other;
@SerialName("other") Other,
@SerialName("ratchetSync") RatchetSync;
val text: String get() = when (this) {
RatchetHeader -> generalGetString(MR.strings.decryption_error)
TooManySkipped -> generalGetString(MR.strings.decryption_error)
RatchetEarlier -> generalGetString(MR.strings.decryption_error)
Other -> generalGetString(MR.strings.decryption_error)
RatchetSync -> generalGetString(MR.strings.encryption_renegotiation_error)
}
}
@@ -894,7 +894,7 @@ fun BoxWithConstraintsScope.ChatItemsList(
@Composable
fun ChatItemViewShortHand(cItem: ChatItem, range: IntRange?) {
ChatItemView(chat.chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, range = range, deleteMessage = deleteMessage, deleteMessages = deleteMessages, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = { _, _ -> }, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, setReaction = setReaction, showItemDetails = showItemDetails, developerTools = developerTools)
ChatItemView(chat.chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, range = range, deleteMessage = deleteMessage, deleteMessages = deleteMessages, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, setReaction = setReaction, showItemDetails = showItemDetails, developerTools = developerTools)
}
@Composable
@@ -214,7 +214,13 @@ fun rememberSaveFileLauncher(ciFile: CIFile?): FileChooserLauncher =
if (filePath != null && to != null) {
if (ciFile?.fileSource?.cryptoArgs != null) {
createTmpFileAndDelete { tmpFile ->
decryptCryptoFile(filePath, ciFile.fileSource.cryptoArgs, tmpFile.absolutePath)
try {
decryptCryptoFile(filePath, ciFile.fileSource.cryptoArgs, tmpFile.absolutePath)
} catch (e: Exception) {
Log.e(TAG, "Unable to decrypt crypto file: " + e.stackTraceToString())
tmpFile.delete()
return@createTmpFileAndDelete
}
copyFileToFile(tmpFile, to) {}
tmpFile.delete()
}
@@ -218,5 +218,7 @@ private fun alertMessage(msgDecryptError: MsgDecryptError, msgCount: UInt): Stri
MsgDecryptError.Other -> String.format(generalGetString(MR.strings.alert_text_decryption_error_n_messages_failed_to_decrypt), msgCount.toLong()) + "\n" +
generalGetString(MR.strings.alert_text_fragment_encryption_out_of_sync_old_database)
MsgDecryptError.RatchetSync -> generalGetString(MR.strings.alert_text_encryption_renegotiation_failed)
}
}
@@ -45,6 +45,7 @@
<string name="invalid_chat">invalid chat</string>
<string name="invalid_data">invalid data</string>
<string name="decryption_error">Decryption error</string>
<string name="encryption_renegotiation_error">Encryption re-negotiation error</string>
<!-- PendingContactConnection - ChatModel.kt -->
<string name="connection_local_display_name">connection %1$d</string>
@@ -866,6 +867,7 @@
<string name="alert_text_decryption_error_n_messages_failed_to_decrypt">%1$d messages failed to decrypt.</string>
<string name="alert_text_decryption_error_too_many_skipped">%1$d messages skipped.</string>
<string name="alert_text_fragment_encryption_out_of_sync_old_database">It can happen when you or your connection used the old database backup.</string>
<string name="alert_text_encryption_renegotiation_failed">Encryption re-negotiation failed.</string>
<string name="alert_text_fragment_please_report_to_developers">Please report it to the developers.</string>
<!-- Privacy settings -->
@@ -59,6 +59,7 @@ actual object AudioPlayer: AudioPlayerInterface {
}
}.onFailure {
Log.e(TAG, it.stackTraceToString())
fileSource.deleteTmpFile()
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.unknown_error), it.message)
return null
}
@@ -27,7 +27,11 @@ actual fun shareFile(text: String, fileSource: CryptoFile) {
FileChooserLauncher(false) { to: URI? ->
if (to != null) {
if (fileSource.cryptoArgs != null) {
decryptCryptoFile(getAppFilePath(fileSource.filePath), fileSource.cryptoArgs, to.path)
try {
decryptCryptoFile(getAppFilePath(fileSource.filePath), fileSource.cryptoArgs, to.path)
} catch (e: Exception) {
Log.e(TAG, "Unable to decrypt crypto file: " + e.stackTraceToString())
}
} else {
copyFileToFile(File(fileSource.filePath), to) {}
}
@@ -48,7 +48,12 @@ actual fun copyItemToClipboard(cItem: ChatItem, clipboard: ClipboardManager) {
val filePath: String = if (fileSource.cryptoArgs != null) {
val tmpFile = File(tmpDir, fileSource.filePath)
tmpFile.deleteOnExit()
decryptCryptoFile(getAppFilePath(fileSource.filePath), fileSource.cryptoArgs, tmpFile.absolutePath)
try {
decryptCryptoFile(getAppFilePath(fileSource.filePath), fileSource.cryptoArgs, tmpFile.absolutePath)
} catch (e: Exception) {
Log.e(TAG, "Unable to decrypt crypto file: " + e.stackTraceToString())
return
}
tmpFile.absolutePath
} else {
getAppFilePath(fileSource.filePath)
@@ -94,9 +94,14 @@ actual fun getAppFileUri(fileName: String): URI =
actual fun getLoadedImage(file: CIFile?): Pair<ImageBitmap, ByteArray>? {
val filePath = getLoadedFilePath(file)
return if (filePath != null) {
val data = if (file?.fileSource?.cryptoArgs != null) readCryptoFile(filePath, file.fileSource.cryptoArgs) else File(filePath).readBytes()
val bitmap = getBitmapFromByteArray(data, false)
if (bitmap != null) bitmap to data else null
try {
val data = if (file?.fileSource?.cryptoArgs != null) readCryptoFile(filePath, file.fileSource.cryptoArgs) else File(filePath).readBytes()
val bitmap = getBitmapFromByteArray(data, false)
if (bitmap != null) bitmap to data else null
} catch (e: Exception) {
Log.e(TAG, "Unable to read crypto file: " + e.stackTraceToString())
null
}
} else {
null
}
+1 -1
View File
@@ -9,7 +9,7 @@ constraints: zip +disable-bzip2 +disable-zstd
source-repository-package
type: git
location: https://github.com/simplex-chat/simplexmq.git
tag: 9460551a042ce9dbd3f686576942fade823a6941
tag: 7aae6f3cbe4aa2942371f8dd968eb439ccec3b15
source-repository-package
type: git
+1 -1
View File
@@ -25,7 +25,7 @@ Using the same profile as on mobile device is not yet supported you need to
**Mac**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.2/simplex-desktop-macos-x86_64.dmg) (Intel), [aarch64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.1/simplex-desktop-macos-aarch64.dmg) (Apple Silicon).
**Windows**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.0-beta.0/simplex-desktop-windows-x86-64.msi) (BETA).
**Windows**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.0-beta.3/simplex-desktop-windows-x86-64.msi) (BETA).
## Mobile apps
Generated
+12 -12
View File
@@ -190,11 +190,11 @@
"hackage": {
"flake": false,
"locked": {
"lastModified": 1698020746,
"narHash": "sha256-/eFVtqLu4sGfjJiwmelfH2qK0ZGFOmyCxBTJ1ncOeSI=",
"lastModified": 1699834964,
"narHash": "sha256-733KT+G0c1euCeb60/u1qbX22Kvu9lNnIDfAmk6Jxq0=",
"owner": "input-output-hk",
"repo": "hackage.nix",
"rev": "dc3cf3bbc0b1e9780e008f9cd6af4a9aeb344ad7",
"rev": "2e891e530400187ea1083ffef15adf259061be41",
"type": "github"
},
"original": {
@@ -240,11 +240,11 @@
"stackage": "stackage"
},
"locked": {
"lastModified": 1698666250,
"narHash": "sha256-nt8n0N//L7A6T9tgPVAp0Z2JurVDh/DveN2pAIsvIMI=",
"lastModified": 1700119633,
"narHash": "sha256-nZY2eIo8TkRbXgJXEWMm9zor330GuUtcNzvUN9tN64U=",
"owner": "input-output-hk",
"repo": "haskell.nix",
"rev": "cc58b1a8e0faeb1a4968a91affe0bcc21632f138",
"rev": "1fe47a3d52e1ecd6247c8ab83811a21de2e2f074",
"type": "github"
},
"original": {
@@ -417,11 +417,11 @@
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1698662474,
"narHash": "sha256-VoMvl+N1K8LuWO8rjlm6ko//1/l35InxFTrtNSKRc20=",
"lastModified": 1699767871,
"narHash": "sha256-kxeCUfwC/Vgh2FvVMlBUq0eVx1JvfHyN+5MPKUik9mE=",
"owner": "zw3rk",
"repo": "mobile-core-tools",
"rev": "00d48cc5a929af4b047604805dd8aba9933b37e4",
"rev": "4dcb77d5ea896d749381806dfab5358851b08951",
"type": "github"
},
"original": {
@@ -661,11 +661,11 @@
"stackage": {
"flake": false,
"locked": {
"lastModified": 1698278970,
"narHash": "sha256-CogUKaZr3d4cJTeqk/u04Ct2fg1EHBCuN3SrWbNYsuM=",
"lastModified": 1699834215,
"narHash": "sha256-g/JKy0BCvJaxPuYDl3QVc4OY8cFEomgG+hW/eEV470M=",
"owner": "input-output-hk",
"repo": "stackage.nix",
"rev": "d0599a4c08237202eb560aa2d2625b5daa818cc6",
"rev": "47aacd04abcce6bad57f43cbbbd133538380248e",
"type": "github"
},
"original": {
+21 -4
View File
@@ -127,8 +127,22 @@
hardeningDisable = [ "fortify" ];
}
);in {
# STATIC x86_64-linux
"${pkgs.pkgsCross.musl64.hostPlatform.system}-static:exe:simplex-chat" = (drv pkgs.pkgsCross.musl64).simplex-chat.components.exes.simplex-chat;
"${pkgs.pkgsCross.musl32.hostPlatform.system}-static:exe:simplex-chat" = (drv pkgs.pkgsCross.musl32).simplex-chat.components.exes.simplex-chat;
# STATIC i686-linux
"${pkgs.pkgsCross.musl32.hostPlatform.system}-static:exe:simplex-chat" = (drv' {
pkgs' = pkgs.pkgsCross.musl32;
extra-modules = [{
# 32 bit patches
packages.basement.patches = [
./scripts/nix/basement-pr-573.patch
];
packages.memory.patches = [
./scripts/nix/memory-pr-99.patch
];
}];
}).simplex-chat.components.exes.simplex-chat;
# WINDOWS x86_64-mingwW64
"${pkgs.pkgsCross.mingwW64.hostPlatform.system}:exe:simplex-chat" = (drv' {
pkgs' = pkgs.pkgsCross.mingwW64;
extra-modules = [{
@@ -229,15 +243,17 @@
'';
});
# "${pkgs.pkgsCross.muslpi.hostPlatform.system}-static:exe:simplex-chat" = (drv pkgs.pkgsCross.muslpi).simplex-chat.components.exes.simplex-chat;
# STATIC aarch64-linux
"${pkgs.pkgsCross.aarch64-multiplatform-musl.hostPlatform.system}-static:exe:simplex-chat" = (drv pkgs.pkgsCross.aarch64-multiplatform-musl).simplex-chat.components.exes.simplex-chat;
"armv7a-android:lib:support" = (drv android32Pkgs).android-support.components.library.override {
"armv7a-android:lib:support" = (drv android32Pkgs).android-support.components.library.override (p: {
smallAddressSpace = true;
# we won't want -dyamic (see aarch64-android:lib:simplex-chat)
enableShared = false;
# we also do not want to have any dependencies listed (especially no rts!)
enableStatic = false;
setupBuildFlags = map (x: "--ghc-option=${x}") [ "-shared" "-o" "libsupport.so" ];
setupBuildFlags = p.component.setupBuildFlags ++ map (x: "--ghc-option=${x}") [ "-shared" "-o" "libsupport.so" ];
postInstall = ''
mkdir -p $out/_pkg
@@ -250,7 +266,7 @@
echo "file binary-dist \"$(echo $out/*.zip)\"" \
> $out/nix-support/hydra-build-products
'';
};
});
# The android-support package is at
# https://github.com/simplex-chat/android-support
"aarch64-android:lib:support" = (drv androidPkgs).android-support.components.library.override (p: {
@@ -293,6 +309,7 @@
packages.direct-sqlcipher.patches = [
./scripts/nix/direct-sqlcipher-android-log.patch
];
# 32 bit patches
packages.basement.patches = [
./scripts/nix/basement-pr-573.patch
];
+1 -1
View File
@@ -1,5 +1,5 @@
{
"https://github.com/simplex-chat/simplexmq.git"."9460551a042ce9dbd3f686576942fade823a6941" = "1j5s7h55j6dpmiajdh380mma1jkffbn88qyqfgjn5nx6il2svkmz";
"https://github.com/simplex-chat/simplexmq.git"."7aae6f3cbe4aa2942371f8dd968eb439ccec3b15" = "0qx7l1jq0ll1448s077v37s0rv7vlbrdbdgf46x734cl9g0gndk3";
"https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38";
"https://github.com/kazu-yamamoto/http2.git"."f5525b755ff2418e6e6ecc69e877363b0d0bcaeb" = "0fyx0047gvhm99ilp212mmz37j84cwrfnpmssib5dw363fyb88b6";
"https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d";
+5 -3
View File
@@ -1256,7 +1256,7 @@ processChatCommand = \case
connectionStats <- withAgent $ \a -> abortConnectionSwitch a connId
pure $ CRGroupMemberSwitchAborted user g m connectionStats
_ -> throwChatError CEGroupMemberNotActive
APISyncContactRatchet contactId force -> withUser $ \user -> do
APISyncContactRatchet contactId force -> withUser $ \user -> withChatLock "syncContactRatchet" $ do
ct <- withStore $ \db -> getContact db user contactId
case contactConnId ct of
Just connId -> do
@@ -1264,7 +1264,7 @@ processChatCommand = \case
createInternalChatItem user (CDDirectSnd ct) (CISndConnEvent $ SCERatchetSync rss Nothing) Nothing
pure $ CRContactRatchetSyncStarted user ct cStats
Nothing -> throwChatError $ CEContactNotActive ct
APISyncGroupMemberRatchet gId gMemberId force -> withUser $ \user -> do
APISyncGroupMemberRatchet gId gMemberId force -> withUser $ \user -> withChatLock "syncGroupMemberRatchet" $ do
(g, m) <- withStore $ \db -> (,) <$> getGroupInfo db user gId <*> getGroupMember db user gId gMemberId
case memberConnId m of
Just connId -> do
@@ -1921,7 +1921,7 @@ processChatCommand = \case
QuitChat -> liftIO exitSuccess
ShowVersion -> do
-- simplexmqCommitQ makes iOS builds crash m(
let versionInfo = coreVersionInfo "" -- $(simplexmqCommitQ)
let versionInfo = coreVersionInfo $(simplexmqCommitQ)
chatMigrations <- map upMigration <$> withStore' (Migrations.getCurrent . DB.conn)
agentMigrations <- withAgent getAgentMigrations
pure $ CRVersionInfo {versionInfo, chatMigrations, agentMigrations}
@@ -3635,6 +3635,7 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
RATCHET_HEADER -> (MDERatchetHeader, 1)
RATCHET_EARLIER _ -> (MDERatchetEarlier, 1)
RATCHET_SKIPPED n -> (MDETooManySkipped, n)
RATCHET_SYNC -> (MDERatchetSync, 0)
mdeUpdatedCI :: (MsgDecryptError, Word32) -> CChatItem c -> Maybe (ChatItem c 'MDRcv, CIContent 'MDRcv)
mdeUpdatedCI (mde', n') (CChatItem _ ci@ChatItem {content = CIRcvDecryptionError mde n})
@@ -3643,6 +3644,7 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
MDETooManySkipped -> r n' -- the numbers are not added as sequential MDETooManySkipped will have it incremented by 1
MDERatchetEarlier -> r (n + n')
MDEOther -> r (n + n')
MDERatchetSync -> r 0
| otherwise = Nothing
where
r n'' = Just (ci, CIRcvDecryptionError mde n'')
+8 -2
View File
@@ -150,7 +150,12 @@ ciMsgContent = \case
CIRcvMsgContent mc -> Just mc
_ -> Nothing
data MsgDecryptError = MDERatchetHeader | MDETooManySkipped | MDERatchetEarlier | MDEOther
data MsgDecryptError
= MDERatchetHeader
| MDETooManySkipped
| MDERatchetEarlier
| MDEOther
| MDERatchetSync
deriving (Eq, Show, Generic)
instance ToJSON MsgDecryptError where
@@ -460,6 +465,7 @@ msgDecryptErrorText err n =
MDETooManySkipped -> Just $ "too many skipped messages" <> counter
MDERatchetEarlier -> Just $ "earlier message" <> counter
MDEOther -> counter_
MDERatchetSync -> Just "synchronization error"
counter_ = if n == 1 then Nothing else Just $ tshow n <> " messages"
counter = maybe "" (", " <>) counter_
@@ -555,7 +561,7 @@ jsonCIContent = \case
CIRcvChatFeatureRejected feature -> JCIRcvChatFeatureRejected {feature}
CIRcvGroupFeatureRejected groupFeature -> JCIRcvGroupFeatureRejected {groupFeature}
CISndModerated -> JCISndModerated
CIRcvModerated -> JCISndModerated
CIRcvModerated -> JCIRcvModerated
CIInvalidJSON json -> JCIInvalidJSON (toMsgDirection $ msgDirection @d) json
aciContentJSON :: JSONCIContent -> ACIContent
+1 -1
View File
@@ -49,7 +49,7 @@ extra-deps:
# - simplexmq-1.0.0@sha256:34b2004728ae396e3ae449cd090ba7410781e2b3cefc59259915f4ca5daa9ea8,8561
# - ../simplexmq
- github: simplex-chat/simplexmq
commit: 9460551a042ce9dbd3f686576942fade823a6941
commit: 7aae6f3cbe4aa2942371f8dd968eb439ccec3b15
- github: kazu-yamamoto/http2
commit: f5525b755ff2418e6e6ecc69e877363b0d0bcaeb
# - ../direct-sqlcipher