From b4dd72e7eb30b7da782acfda0662e41f464f47ee Mon Sep 17 00:00:00 2001 From: l5y <220195275+l5yth@users.noreply.github.com> Date: Thu, 8 Jan 2026 15:51:31 +0100 Subject: [PATCH] matrix: listen for synapse on port 41448 (#607) * matrix: listen for synapse on port 41448 * matrix: address review comments * matrix: address review comments * matrix: cover missing unit test vectors * matrix: cover missing unit test vectors --- docker-compose.yml | 2 + matrix/Cargo.lock | 356 ++++++++++++++++++++++-------------- matrix/Cargo.toml | 2 + matrix/Config.toml | 3 +- matrix/Dockerfile | 2 + matrix/README.md | 8 +- matrix/src/config.rs | 5 + matrix/src/main.rs | 100 +++++++++- matrix/src/matrix.rs | 1 + matrix/src/matrix_server.rs | 195 ++++++++++++++++++++ 10 files changed, 520 insertions(+), 154 deletions(-) create mode 100644 matrix/src/matrix_server.rs diff --git a/docker-compose.yml b/docker-compose.yml index e3da6a6..3eecf2a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -140,6 +140,8 @@ services: - potatomesh-network depends_on: - web-bridge + ports: + - "41448:41448" profiles: - bridge diff --git a/matrix/Cargo.lock b/matrix/Cargo.lock index d7bdda6..1e73ba7 100644 --- a/matrix/Cargo.lock +++ b/matrix/Cargo.lock @@ -27,24 +27,84 @@ dependencies = [ "serde_json", ] +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +dependencies = [ + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.10.0" @@ -53,9 +113,9 @@ checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "bytes" @@ -65,9 +125,9 @@ checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] name = "cc" -version = "1.2.47" +version = "1.2.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd405d82c84ff7f35739f175f67d8b9fb7687a0e84ccdc78bd3568839827cf07" +checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" dependencies = [ "find-msvc-tools", "shlex", @@ -91,7 +151,7 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -154,9 +214,9 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "find-msvc-tools" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" [[package]] name = "fnv" @@ -188,21 +248,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "futures" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - [[package]] name = "futures-channel" version = "0.3.31" @@ -210,7 +255,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", - "futures-sink", ] [[package]] @@ -230,12 +274,6 @@ dependencies = [ "futures-util", ] -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - [[package]] name = "futures-sink" version = "0.3.31" @@ -254,12 +292,8 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ - "futures-channel", "futures-core", - "futures-io", - "futures-sink", "futures-task", - "memchr", "pin-project-lite", "pin-utils", "slab", @@ -294,9 +328,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" dependencies = [ "atomic-waker", "bytes", @@ -420,9 +454,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56" +checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" dependencies = [ "base64", "bytes", @@ -492,9 +526,9 @@ checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" dependencies = [ "icu_collections", "icu_locale_core", @@ -506,9 +540,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" [[package]] name = "icu_provider" @@ -548,9 +582,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", "hashbrown", @@ -564,9 +598,9 @@ checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "iri-string" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" dependencies = [ "memchr", "serde", @@ -574,15 +608,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "js-sys" -version = "0.3.82" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" dependencies = [ "once_cell", "wasm-bindgen", @@ -596,9 +630,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.177" +version = "0.2.179" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f" [[package]] name = "linux-raw-sys" @@ -623,9 +657,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "lru-slab" @@ -642,6 +676,12 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "memchr" version = "2.7.6" @@ -656,9 +696,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mio" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", "wasi", @@ -667,20 +707,21 @@ dependencies = [ [[package]] name = "mockito" -version = "1.7.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7760e0e418d9b7e5777c0374009ca4c93861b9066f18cb334a20ce50ab63aa48" +checksum = "7e0603425789b4a70fcc4ac4f5a46a566c116ee3e2a6b768dc623f7719c611de" dependencies = [ "assert-json-diff", "bytes", "colored", - "futures-util", + "futures-core", "http", "http-body", "http-body-util", "hyper", "hyper-util", "log", + "pin-project-lite", "rand", "regex", "serde_json", @@ -727,7 +768,7 @@ version = "0.10.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" dependencies = [ - "bitflags 2.10.0", + "bitflags", "cfg-if", "foreign-types", "libc", @@ -817,6 +858,7 @@ name = "potatomesh-matrix-bridge" version = "0.5.9" dependencies = [ "anyhow", + "axum", "mockito", "reqwest", "serde", @@ -825,6 +867,7 @@ dependencies = [ "tempfile", "tokio", "toml", + "tower", "tracing", "tracing-subscriber", "urlencoding", @@ -850,9 +893,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" dependencies = [ "unicode-ident", ] @@ -914,9 +957,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.42" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" dependencies = [ "proc-macro2", ] @@ -962,7 +1005,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.10.0", + "bitflags", ] [[package]] @@ -996,9 +1039,9 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "reqwest" -version = "0.12.24" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64", "bytes", @@ -1060,11 +1103,11 @@ checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustix" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ - "bitflags 2.10.0", + "bitflags", "errno", "libc", "linux-raw-sys", @@ -1073,9 +1116,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.35" +version = "0.23.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" dependencies = [ "once_cell", "ring", @@ -1087,9 +1130,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" +checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" dependencies = [ "web-time", "zeroize", @@ -1114,9 +1157,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" [[package]] name = "scc" @@ -1154,7 +1197,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.10.0", + "bitflags", "core-foundation", "core-foundation-sys", "libc", @@ -1203,22 +1246,33 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", "serde", "serde_core", ] [[package]] name = "serde_spanned" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" dependencies = [ "serde_core", ] @@ -1237,11 +1291,12 @@ dependencies = [ [[package]] name = "serial_test" -version = "3.2.0" +version = "3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b258109f244e1d6891bf1053a55d63a5cd4f8f4c30cf9a1280989f80e7a1fa9" +checksum = "0d0b343e184fc3b7bb44dff0705fffcf4b3756ba6aff420dddd8b24ca145e555" dependencies = [ - "futures", + "futures-executor", + "futures-util", "log", "once_cell", "parking_lot", @@ -1251,9 +1306,9 @@ dependencies = [ [[package]] name = "serial_test_derive" -version = "3.2.0" +version = "3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" +checksum = "6f50427f258fb77356e4cd4aa0e87e2bd2c66dbcee41dc405282cae2bfc26c83" dependencies = [ "proc-macro2", "quote", @@ -1317,9 +1372,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.111" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -1348,20 +1403,20 @@ dependencies = [ [[package]] name = "system-configuration" -version = "0.5.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 1.3.2", + "bitflags", "core-foundation", "system-configuration-sys", ] [[package]] name = "system-configuration-sys" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" dependencies = [ "core-foundation-sys", "libc", @@ -1369,9 +1424,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.23.0" +version = "3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" dependencies = [ "fastrand", "getrandom 0.3.4", @@ -1436,9 +1491,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.48.0" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ "bytes", "libc", @@ -1483,9 +1538,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.17" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", @@ -1496,9 +1551,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.8" +version = "0.9.10+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" +checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48" dependencies = [ "indexmap", "serde_core", @@ -1511,27 +1566,27 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.3" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ "serde_core", ] [[package]] name = "toml_parser" -version = "1.0.4" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" dependencies = [ "winnow", ] [[package]] name = "toml_writer" -version = "1.0.4" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" [[package]] name = "tower" @@ -1546,15 +1601,16 @@ dependencies = [ "tokio", "tower-layer", "tower-service", + "tracing", ] [[package]] name = "tower-http" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf146f99d442e8e68e585f5d798ccd3cad9a7835b917e09728880a862706456" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags 2.10.0", + "bitflags", "bytes", "futures-util", "http", @@ -1580,10 +1636,11 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -1602,9 +1659,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.35" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -1623,9 +1680,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.20" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" dependencies = [ "matchers", "nu-ansi-term", @@ -1659,9 +1716,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.7" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", @@ -1719,9 +1776,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" dependencies = [ "cfg-if", "once_cell", @@ -1732,9 +1789,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.55" +version = "0.4.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" +checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" dependencies = [ "cfg-if", "js-sys", @@ -1745,9 +1802,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1755,9 +1812,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" dependencies = [ "bumpalo", "proc-macro2", @@ -1768,18 +1825,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.82" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" dependencies = [ "js-sys", "wasm-bindgen", @@ -1797,9 +1854,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" +checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" dependencies = [ "rustls-pki-types", ] @@ -1848,6 +1905,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.60.2" @@ -2038,18 +2104,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.30" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea879c944afe8a2b25fef16bb4ba234f47c694565e97383b36f3a878219065c" +checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.30" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf955aa904d6040f70dc8e9384444cb1030aed272ba3cb09bbc4ab9e7c1f34f5" +checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" dependencies = [ "proc-macro2", "quote", @@ -2115,3 +2181,9 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zmij" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8" diff --git a/matrix/Cargo.toml b/matrix/Cargo.toml index 185e0a3..d3f4ef3 100644 --- a/matrix/Cargo.toml +++ b/matrix/Cargo.toml @@ -27,8 +27,10 @@ anyhow = "1" tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["fmt", "env-filter"] } urlencoding = "2" +axum = { version = "0.7", features = ["json"] } [dev-dependencies] tempfile = "3" mockito = "1" serial_test = "3" +tower = "0.5" diff --git a/matrix/Config.toml b/matrix/Config.toml index 0d34e40..c0bb0f6 100644 --- a/matrix/Config.toml +++ b/matrix/Config.toml @@ -9,6 +9,8 @@ poll_interval_secs = 60 homeserver = "https://matrix.dod.ngo" # Appservice access token (from your registration.yaml) as_token = "INVALID_TOKEN_NOT_WORKING" +# Homeserver token used to authenticate Synapse callbacks +hs_token = "INVALID_TOKEN_NOT_WORKING" # Server name (domain) part of Matrix user IDs server_name = "dod.ngo" # Room ID to send into (must be joined by the appservice / puppets) @@ -17,4 +19,3 @@ room_id = "!sXabOBXbVObAlZQEUs:c-base.org" # "#potato-bridge:c-base.org" [state] # Where to persist last seen message id (optional but recommended) state_file = "bridge_state.json" - diff --git a/matrix/Dockerfile b/matrix/Dockerfile index 5c9c869..83355a6 100644 --- a/matrix/Dockerfile +++ b/matrix/Dockerfile @@ -37,6 +37,8 @@ COPY --from=builder /app/target/release/potatomesh-matrix-bridge /usr/local/bin/ COPY matrix/Config.toml /app/Config.example.toml COPY matrix/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh +EXPOSE 41448 + RUN chmod +x /usr/local/bin/docker-entrypoint.sh ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"] diff --git a/matrix/README.md b/matrix/README.md index 1ba7aae..db4f428 100644 --- a/matrix/README.md +++ b/matrix/README.md @@ -134,7 +134,7 @@ A minimal example sketch (you **must** adjust URLs, secrets, namespaces): ```yaml id: potatomesh-bridge -url: "http://your-bridge-host:8080" # not used by this bridge if it only calls out +url: "http://your-bridge-host:41448" as_token: "YOUR_APPSERVICE_AS_TOKEN" hs_token: "SECRET_HS_TOKEN" sender_localpart: "potatomesh-bridge" @@ -145,10 +145,12 @@ namespaces: regex: "@potato_[0-9a-f]{8}:example.org" ``` -For this bridge, only the `as_token` and `namespaces.users` actually matter. The bridge does not accept inbound events; it only uses the `as_token` to call the homeserver. +This bridge listens for Synapse appservice callbacks on port `41448` so it can log inbound transaction payloads. It still only forwards messages one way (PotatoMesh → Matrix), so inbound Matrix events are acknowledged but not bridged. The `as_token` and `namespaces.users` entries remain required for outbound calls, and the `url` should point at the listener. In Synapse’s `homeserver.yaml`, add the registration file under `app_service_config_files`, restart, and invite a puppet user to your target room (or use room ID directly). +The bridge validates inbound appservice callbacks by comparing the `access_token` query param to `hs_token` in `Config.toml`, so keep those values in sync. + --- ## Build @@ -182,6 +184,7 @@ Provide your config at `/app/Config.toml` and persist the bridge state file by m ```bash docker run --rm \ + -p 41448:41448 \ -v bridge_state:/app \ -v "$(pwd)/matrix/Config.toml:/app/Config.toml:ro" \ potatomesh-matrix-bridge @@ -191,6 +194,7 @@ If you prefer to isolate the state file from the config, mount it directly inste ```bash docker run --rm \ + -p 41448:41448 \ -v bridge_state:/app \ -v "$(pwd)/matrix/Config.toml:/app/Config.toml:ro" \ potatomesh-matrix-bridge diff --git a/matrix/src/config.rs b/matrix/src/config.rs index 21c2118..47bf5b7 100644 --- a/matrix/src/config.rs +++ b/matrix/src/config.rs @@ -25,6 +25,7 @@ pub struct PotatomeshConfig { pub struct MatrixConfig { pub homeserver: String, pub as_token: String, + pub hs_token: String, pub server_name: String, pub room_id: String, } @@ -73,6 +74,7 @@ mod tests { [matrix] homeserver = "https://matrix.example.org" as_token = "AS_TOKEN" + hs_token = "HS_TOKEN" server_name = "example.org" room_id = "!roomid:example.org" @@ -86,6 +88,7 @@ mod tests { assert_eq!(cfg.matrix.homeserver, "https://matrix.example.org"); assert_eq!(cfg.matrix.as_token, "AS_TOKEN"); + assert_eq!(cfg.matrix.hs_token, "HS_TOKEN"); assert_eq!(cfg.matrix.server_name, "example.org"); assert_eq!(cfg.matrix.room_id, "!roomid:example.org"); @@ -108,6 +111,7 @@ mod tests { [matrix] homeserver = "https://matrix.example.org" as_token = "AS_TOKEN" + hs_token = "HS_TOKEN" server_name = "example.org" room_id = "!roomid:example.org" @@ -140,6 +144,7 @@ mod tests { [matrix] homeserver = "https://matrix.example.org" as_token = "AS_TOKEN" + hs_token = "HS_TOKEN" server_name = "example.org" room_id = "!roomid:example.org" diff --git a/matrix/src/main.rs b/matrix/src/main.rs index b45423f..6eaf7ae 100644 --- a/matrix/src/main.rs +++ b/matrix/src/main.rs @@ -14,17 +14,22 @@ mod config; mod matrix; +mod matrix_server; mod potatomesh; -use std::{fs, path::Path}; +use std::{fs, net::SocketAddr, path::Path}; use anyhow::Result; -use tokio::time::{sleep, Duration}; +use tokio::time::Duration; use tracing::{error, info}; +#[cfg(not(test))] use crate::config::Config; use crate::matrix::MatrixAppserviceClient; +use crate::matrix_server::run_synapse_listener; use crate::potatomesh::{FetchParams, PotatoClient, PotatoMessage, PotatoNode}; +#[cfg(not(test))] +use tokio::time::sleep; #[derive(Debug, serde::Serialize, serde::Deserialize, Default)] pub struct BridgeState { @@ -114,6 +119,18 @@ fn build_fetch_params(state: &BridgeState) -> FetchParams { } } +/// Persist the bridge state and log any write errors. +fn persist_state(state: &BridgeState, state_path: &str) { + if let Err(e) = state.save(state_path) { + error!("Error saving state: {:?}", e); + } +} + +/// Emit an info log for the latest bridge state snapshot. +fn log_state_update(state: &BridgeState) { + info!("Updated state: {:?}", state); +} + async fn poll_once( potato: &PotatoClient, matrix: &MatrixAppserviceClient, @@ -136,9 +153,8 @@ async fn poll_once( if let Some(port) = &msg.portnum { if port != "TEXT_MESSAGE_APP" { state.update_with(msg); - if let Err(e) = state.save(state_path) { - error!("Error saving state: {:?}", e); - } + log_state_update(state); + persist_state(state, state_path); continue; } } @@ -148,11 +164,8 @@ async fn poll_once( continue; } - state.update_with(msg); // persist after each processed message - if let Err(e) = state.save(state_path) { - error!("Error saving state: {:?}", e); - } + persist_state(state, state_path); } } Err(e) => { @@ -161,6 +174,15 @@ async fn poll_once( } } +fn spawn_synapse_listener(addr: SocketAddr, token: String) -> tokio::task::JoinHandle<()> { + tokio::spawn(async move { + if let Err(e) = run_synapse_listener(addr, token).await { + error!("Synapse listener failed: {:?}", e); + } + }) +} + +#[cfg(not(test))] #[tokio::main] async fn main() -> Result<()> { // Logging: RUST_LOG=info,bridge=debug,reqwest=warn ... @@ -181,6 +203,10 @@ async fn main() -> Result<()> { let matrix = MatrixAppserviceClient::new(http.clone(), cfg.matrix.clone()); matrix.health_check().await?; + let synapse_addr = SocketAddr::from(([0, 0, 0, 0], 41448)); + let synapse_token = cfg.matrix.hs_token.clone(); + let _synapse_handle = spawn_synapse_listener(synapse_addr, synapse_token); + let state_path = &cfg.state.state_file; let mut state = BridgeState::load(state_path)?; info!("Loaded state: {:?}", state); @@ -224,7 +250,9 @@ async fn handle_message( .send_formatted_message_as(&user_id, &body, &formatted_body) .await?; + info!("Bridged message: {:?}", msg); state.update_with(msg); + log_state_update(state); Ok(()) } @@ -538,6 +566,57 @@ mod tests { assert_eq!(params.since, None); } + #[test] + fn log_state_update_emits_info() { + let state = BridgeState::default(); + log_state_update(&state); + } + + #[test] + fn persist_state_writes_file() { + let tmp_dir = tempfile::tempdir().unwrap(); + let file_path = tmp_dir.path().join("state.json"); + let path_str = file_path.to_str().unwrap(); + + let state = BridgeState { + last_message_id: Some(42), + last_rx_time: Some(123), + last_rx_time_ids: vec![42], + last_checked_at: None, + }; + + persist_state(&state, path_str); + + let loaded = BridgeState::load(path_str).unwrap(); + assert_eq!(loaded.last_message_id, Some(42)); + } + + #[test] + fn persist_state_logs_on_error() { + let tmp_dir = tempfile::tempdir().unwrap(); + let dir_path = tmp_dir.path().to_str().unwrap(); + let state = BridgeState::default(); + + // Writing to a directory path should trigger the error branch. + persist_state(&state, dir_path); + } + + #[tokio::test] + async fn spawn_synapse_listener_starts_task() { + let addr = SocketAddr::from(([127, 0, 0, 1], 0)); + let handle = spawn_synapse_listener(addr, "HS_TOKEN".to_string()); + tokio::time::sleep(Duration::from_millis(10)).await; + handle.abort(); + } + + #[tokio::test] + async fn spawn_synapse_listener_logs_error_on_bind_failure() { + let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); + let addr = listener.local_addr().unwrap(); + let handle = spawn_synapse_listener(addr, "HS_TOKEN".to_string()); + let _ = handle.await; + } + #[tokio::test] async fn poll_once_leaves_state_unchanged_without_messages() { let tmp_dir = tempfile::tempdir().unwrap(); @@ -561,6 +640,7 @@ mod tests { let matrix_cfg = MatrixConfig { homeserver: server.url(), as_token: "AS_TOKEN".to_string(), + hs_token: "HS_TOKEN".to_string(), server_name: "example.org".to_string(), room_id: "!roomid:example.org".to_string(), }; @@ -610,6 +690,7 @@ mod tests { let matrix_cfg = MatrixConfig { homeserver: server.url(), as_token: "AS_TOKEN".to_string(), + hs_token: "HS_TOKEN".to_string(), server_name: "example.org".to_string(), room_id: "!roomid:example.org".to_string(), }; @@ -639,6 +720,7 @@ mod tests { let matrix_cfg = MatrixConfig { homeserver: server.url(), as_token: "AS_TOKEN".to_string(), + hs_token: "HS_TOKEN".to_string(), server_name: "example.org".to_string(), room_id: "!roomid:example.org".to_string(), }; diff --git a/matrix/src/matrix.rs b/matrix/src/matrix.rs index f0ece04..b5bccb9 100644 --- a/matrix/src/matrix.rs +++ b/matrix/src/matrix.rs @@ -232,6 +232,7 @@ mod tests { MatrixConfig { homeserver: "https://matrix.example.org".to_string(), as_token: "AS_TOKEN".to_string(), + hs_token: "HS_TOKEN".to_string(), server_name: "example.org".to_string(), room_id: "!roomid:example.org".to_string(), } diff --git a/matrix/src/matrix_server.rs b/matrix/src/matrix_server.rs new file mode 100644 index 0000000..034c818 --- /dev/null +++ b/matrix/src/matrix_server.rs @@ -0,0 +1,195 @@ +// Copyright © 2025-26 l5yth & contributors +// +// 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. + +use axum::{ + extract::{Path, Query, State}, + http::StatusCode, + response::IntoResponse, + routing::put, + Json, Router, +}; +use serde_json::Value; +use std::net::SocketAddr; +use tracing::info; + +#[derive(Clone)] +struct SynapseState { + hs_token: String, +} + +#[derive(serde::Deserialize)] +struct AuthQuery { + access_token: Option, +} + +/// Captures inbound Synapse transaction payloads for logging. +#[derive(Debug)] +struct SynapseResponse { + txn_id: String, + payload: Value, +} + +/// Build the router that handles Synapse appservice transactions. +fn build_router(state: SynapseState) -> Router { + Router::new() + .route( + "/_matrix/appservice/v1/transactions/:txn_id", + put(handle_transaction), + ) + .with_state(state) +} + +/// Handle inbound transaction callbacks from Synapse. +async fn handle_transaction( + Path(txn_id): Path, + State(state): State, + Query(auth): Query, + Json(payload): Json, +) -> impl IntoResponse { + let token_matches = auth + .access_token + .as_ref() + .is_some_and(|token| token == &state.hs_token); + if !token_matches { + return (StatusCode::UNAUTHORIZED, Json(serde_json::json!({}))); + } + let response = SynapseResponse { txn_id, payload }; + info!( + "Status response: SynapseResponse {{ txn_id: {}, payload: {:?} }}", + response.txn_id, response.payload + ); + (StatusCode::OK, Json(serde_json::json!({}))) +} + +/// Listen for Synapse callbacks on the configured address. +pub async fn run_synapse_listener(addr: SocketAddr, hs_token: String) -> anyhow::Result<()> { + let app = build_router(SynapseState { hs_token }); + let listener = tokio::net::TcpListener::bind(addr).await?; + info!("Synapse listener bound on {}", addr); + axum::serve(listener, app).await?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use axum::body::Body; + use axum::http::Request; + use tokio::time::{sleep, Duration}; + use tower::ServiceExt; + + #[tokio::test] + async fn transactions_endpoint_accepts_payloads() { + let app = build_router(SynapseState { + hs_token: "HS_TOKEN".to_string(), + }); + let payload = serde_json::json!({ + "events": [], + "txn_id": "123" + }); + + let response = app + .oneshot( + Request::builder() + .method("PUT") + .uri("/_matrix/appservice/v1/transactions/123?access_token=HS_TOKEN") + .header("content-type", "application/json") + .body(Body::from(payload.to_string())) + .unwrap(), + ) + .await + .unwrap(); + + assert_eq!(response.status(), StatusCode::OK); + let body = axum::body::to_bytes(response.into_body(), usize::MAX) + .await + .unwrap(); + assert_eq!(body.as_ref(), b"{}"); + } + + #[tokio::test] + async fn transactions_endpoint_rejects_missing_token() { + let app = build_router(SynapseState { + hs_token: "HS_TOKEN".to_string(), + }); + let payload = serde_json::json!({ + "events": [], + "txn_id": "123" + }); + + let response = app + .oneshot( + Request::builder() + .method("PUT") + .uri("/_matrix/appservice/v1/transactions/123") + .header("content-type", "application/json") + .body(Body::from(payload.to_string())) + .unwrap(), + ) + .await + .unwrap(); + + assert_eq!(response.status(), StatusCode::UNAUTHORIZED); + let body = axum::body::to_bytes(response.into_body(), usize::MAX) + .await + .unwrap(); + assert_eq!(body.as_ref(), b"{}"); + } + + #[tokio::test] + async fn transactions_endpoint_rejects_wrong_token() { + let app = build_router(SynapseState { + hs_token: "HS_TOKEN".to_string(), + }); + let payload = serde_json::json!({ + "events": [], + "txn_id": "123" + }); + + let response = app + .oneshot( + Request::builder() + .method("PUT") + .uri("/_matrix/appservice/v1/transactions/123?access_token=NOPE") + .header("content-type", "application/json") + .body(Body::from(payload.to_string())) + .unwrap(), + ) + .await + .unwrap(); + + assert_eq!(response.status(), StatusCode::UNAUTHORIZED); + let body = axum::body::to_bytes(response.into_body(), usize::MAX) + .await + .unwrap(); + assert_eq!(body.as_ref(), b"{}"); + } + + #[tokio::test] + async fn run_synapse_listener_starts_and_can_abort() { + let addr = SocketAddr::from(([127, 0, 0, 1], 0)); + let handle = + tokio::spawn(async move { run_synapse_listener(addr, "HS_TOKEN".to_string()).await }); + sleep(Duration::from_millis(10)).await; + handle.abort(); + } + + #[tokio::test] + async fn run_synapse_listener_returns_error_on_bind_failure() { + let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); + let addr = listener.local_addr().unwrap(); + let result = run_synapse_listener(addr, "HS_TOKEN".to_string()).await; + assert!(result.is_err()); + } +}