diff --git a/Cargo.lock b/Cargo.lock
index e37a3dc..93eac0b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -45,6 +45,21 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
+[[package]]
+name = "android-tzdata"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "anstream"
version = "0.6.13"
@@ -216,6 +231,20 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+[[package]]
+name = "chrono"
+version = "0.4.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e"
+dependencies = [
+ "android-tzdata",
+ "iana-time-zone",
+ "js-sys",
+ "num-traits",
+ "wasm-bindgen",
+ "windows-targets 0.52.4",
+]
+
[[package]]
name = "clap"
version = "4.5.3"
@@ -274,6 +303,12 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
+
[[package]]
name = "cpufeatures"
version = "0.2.12"
@@ -729,6 +764,29 @@ dependencies = [
"tracing",
]
+[[package]]
+name = "iana-time-zone"
+version = "0.1.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
[[package]]
name = "idna"
version = "0.5.0"
@@ -818,6 +876,7 @@ name = "lavina-core"
version = "0.0.2-dev"
dependencies = [
"anyhow",
+ "chrono",
"prometheus",
"serde",
"sqlx",
@@ -2383,6 +2442,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+[[package]]
+name = "windows-core"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
+dependencies = [
+ "windows-targets 0.52.4",
+]
+
[[package]]
name = "windows-sys"
version = "0.48.0"
diff --git a/crates/lavina-core/Cargo.toml b/crates/lavina-core/Cargo.toml
index 727835c..941b753 100644
--- a/crates/lavina-core/Cargo.toml
+++ b/crates/lavina-core/Cargo.toml
@@ -10,3 +10,4 @@ serde.workspace = true
tokio.workspace = true
tracing.workspace = true
prometheus.workspace = true
+chrono = "0.4.37"
diff --git a/crates/lavina-core/src/repo/mod.rs b/crates/lavina-core/src/repo/mod.rs
index d81ec0c..e9eee6c 100644
--- a/crates/lavina-core/src/repo/mod.rs
+++ b/crates/lavina-core/src/repo/mod.rs
@@ -87,14 +87,15 @@ impl Storage {
return Err(anyhow!("No such user"));
};
sqlx::query(
- "insert into messages(room_id, id, content, author_id)
- values (?, ?, ?, ?);
+ "insert into messages(room_id, id, content, author_id, created_at)
+ values (?, ?, ?, ?, ?);
update rooms set message_count = message_count + 1 where id = ?;",
)
.bind(room_id)
.bind(id)
.bind(content)
.bind(author_id)
+ .bind(chrono::Utc::now().to_string())
.bind(room_id)
.execute(&mut *executor)
.await?;
diff --git a/crates/projection-xmpp/tests/lib.rs b/crates/projection-xmpp/tests/lib.rs
index 9dfae4c..cc51ea5 100644
--- a/crates/projection-xmpp/tests/lib.rs
+++ b/crates/projection-xmpp/tests/lib.rs
@@ -108,6 +108,7 @@ impl<'a> TestScopeTls<'a> {
}
struct IgnoreCertVerification;
+
impl ServerCertVerifier for IgnoreCertVerification {
fn verify_server_cert(
&self,
@@ -122,6 +123,79 @@ impl ServerCertVerifier for IgnoreCertVerification {
}
}
+/// Some clients prefer to close their tags, i.e. Gajim.
+#[tokio::test]
+async fn scenario_basic_closed_tag() -> Result<()> {
+ tracing_subscriber::fmt::try_init();
+ let config = ServerConfig {
+ listen_on: "127.0.0.1:0".parse().unwrap(),
+ cert: "tests/certs/xmpp.pem".parse().unwrap(),
+ key: "tests/certs/xmpp.key".parse().unwrap(),
+ };
+ let mut metrics = MetricsRegistry::new();
+ let mut storage = Storage::open(StorageConfig {
+ db_path: ":memory:".into(),
+ })
+ .await?;
+ let rooms = RoomRegistry::new(&mut metrics, storage.clone()).unwrap();
+ let players = PlayerRegistry::empty(rooms.clone(), &mut metrics).unwrap();
+ let server = launch(config, players, rooms, metrics, storage.clone()).await.unwrap();
+
+ // test scenario
+
+ storage.create_user("tester").await?;
+ storage.set_password("tester", "password").await?;
+
+ let mut stream = TcpStream::connect(server.addr).await?;
+ let mut s = TestScope::new(&mut stream);
+ tracing::info!("TCP connection established");
+
+ s.send(r#""#).await?;
+ s.send(r#"
+
+ "#).await?;
+
+ assert_matches!(s.next_xml_event().await?, Event::Decl(_) => {});
+ assert_matches!(s.next_xml_event().await?, Event::Start(b) => assert_eq!(b.local_name().into_inner(), b"stream"));
+ assert_matches!(s.next_xml_event().await?, Event::Start(b) => assert_eq!(b.local_name().into_inner(), b"features"));
+ assert_matches!(s.next_xml_event().await?, Event::Start(b) => assert_eq!(b.local_name().into_inner(), b"starttls"));
+ assert_matches!(s.next_xml_event().await?, Event::Empty(b) => assert_eq!(b.local_name().into_inner(), b"required"));
+ assert_matches!(s.next_xml_event().await?, Event::End(b) => assert_eq!(b.local_name().into_inner(), b"starttls"));
+ assert_matches!(s.next_xml_event().await?, Event::End(b) => assert_eq!(b.local_name().into_inner(), b"features"));
+ s.send(r#""#).await?;
+ assert_matches!(s.next_xml_event().await?, Event::Empty(b) => assert_eq!(b.local_name().into_inner(), b"proceed"));
+ let buffer = s.buffer;
+ tracing::info!("TLS feature negotiation complete");
+
+ let connector = TlsConnector::from(Arc::new(
+ ClientConfig::builder()
+ .with_safe_defaults()
+ .with_custom_certificate_verifier(Arc::new(IgnoreCertVerification))
+ .with_no_client_auth(),
+ ));
+ tracing::info!("Initiating TLS connection...");
+ let mut stream = connector.connect(ServerName::IpAddress(server.addr.ip()), stream).await?;
+ tracing::info!("TLS connection established");
+
+ let mut s = TestScopeTls::new(&mut stream, buffer);
+
+ s.send(r#""#).await?;
+ s.send(r#"
+
+
+
+ "#).await?;
+ assert_matches!(s.next_xml_event().await?, Event::Decl(_) => {});
+ assert_matches!(s.next_xml_event().await?, Event::Start(b) => assert_eq!(b.local_name().into_inner(), b"stream"));
+
+ stream.shutdown().await?;
+
+ // wrap up
+
+ server.terminate().await?;
+ Ok(())
+}
+
#[tokio::test]
async fn scenario_basic() -> Result<()> {
tracing_subscriber::fmt::try_init();