1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-08-07 17:03:01 +03:00

Upgrade most HTTP/Hyper crates and make mas-listener work

This commit is contained in:
Quentin Gliech
2024-07-01 18:21:05 +02:00
parent ba58195414
commit a7a9369469
10 changed files with 232 additions and 130 deletions

220
Cargo.lock generated
View File

@@ -571,19 +571,19 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]]
name = "axum"
version = "0.6.20"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf"
checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf"
dependencies = [
"async-trait",
"axum-core",
"bitflags 1.3.2",
"bytes",
"futures-util",
"headers",
"http 0.2.12",
"http-body 0.4.6",
"hyper 0.14.29",
"http 1.1.0",
"http-body 1.0.0",
"http-body-util",
"hyper 1.4.0",
"hyper-util",
"itoa",
"matchit",
"memchr",
@@ -595,57 +595,64 @@ dependencies = [
"serde_json",
"serde_path_to_error",
"serde_urlencoded",
"sync_wrapper",
"sync_wrapper 1.0.1",
"tokio",
"tower",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "axum-core"
version = "0.3.4"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c"
checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3"
dependencies = [
"async-trait",
"bytes",
"futures-util",
"http 0.2.12",
"http-body 0.4.6",
"http 1.1.0",
"http-body 1.0.0",
"http-body-util",
"mime",
"pin-project-lite",
"rustversion",
"sync_wrapper 0.1.2",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "axum-extra"
version = "0.8.0"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ab90e7b70bea63a153137162affb6a0bce26b584c24a4c7885509783e2cf30b"
checksum = "0be6ea09c9b96cb5076af0de2e383bd2bc0c18f827cf1967bdd353e0b910d733"
dependencies = [
"axum",
"axum-core",
"bytes",
"cookie 0.17.0",
"cookie",
"futures-util",
"http 0.2.12",
"http-body 0.4.6",
"headers 0.4.0",
"http 1.1.0",
"http-body 1.0.0",
"http-body-util",
"mime",
"pin-project-lite",
"serde",
"tokio",
"tower",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "axum-macros"
version = "0.3.8"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdca6a10ecad987bda04e95606ef85a5417dcaac1a78455242d72e031e2b6b62"
checksum = "00c055ee2d014ae5981ce1016374e8213682aa14d9bf40e48ab48b5f3ef20eaa"
dependencies = [
"heck 0.4.1",
"proc-macro2",
@@ -1039,12 +1046,12 @@ dependencies = [
[[package]]
name = "cookie"
version = "0.17.0"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24"
checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
dependencies = [
"aes-gcm",
"base64 0.21.7",
"base64 0.22.1",
"hkdf",
"percent-encoding",
"rand",
@@ -1054,24 +1061,13 @@ dependencies = [
"version_check",
]
[[package]]
name = "cookie"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
dependencies = [
"percent-encoding",
"time",
"version_check",
]
[[package]]
name = "cookie_store"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4934e6b7e8419148b6ef56950d277af8561060b56afd59e2aadf98b59fce6baa"
dependencies = [
"cookie 0.18.1",
"cookie",
"idna 0.5.0",
"log",
"publicsuffix",
@@ -2112,13 +2108,28 @@ checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270"
dependencies = [
"base64 0.21.7",
"bytes",
"headers-core",
"headers-core 0.2.0",
"http 0.2.12",
"httpdate",
"mime",
"sha1",
]
[[package]]
name = "headers"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9"
dependencies = [
"base64 0.21.7",
"bytes",
"headers-core 0.3.0",
"http 1.1.0",
"httpdate",
"mime",
"sha1",
]
[[package]]
name = "headers-core"
version = "0.2.0"
@@ -2128,6 +2139,15 @@ dependencies = [
"http 0.2.12",
]
[[package]]
name = "headers-core"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4"
dependencies = [
"http 1.1.0",
]
[[package]]
name = "heck"
version = "0.4.1"
@@ -2320,17 +2340,19 @@ dependencies = [
[[package]]
name = "hyper-rustls"
version = "0.25.0"
version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "399c78f9338483cb7e630c8474b07268983c6bd5acee012e4211f9f7bb21b070"
checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155"
dependencies = [
"futures-util",
"http 0.2.12",
"hyper 0.14.29",
"rustls 0.22.4",
"http 1.1.0",
"hyper 1.4.0",
"hyper-util",
"rustls 0.23.10",
"rustls-pki-types",
"tokio",
"tokio-rustls",
"tokio-rustls 0.26.0",
"tower-service",
]
[[package]]
@@ -2340,12 +2362,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ab92f4f49ee4fb4f997c784b7a2e0fa70050211e0b6a287f898c3c9785ca956"
dependencies = [
"bytes",
"futures-channel",
"futures-util",
"http 1.1.0",
"http-body 1.0.0",
"hyper 1.4.0",
"pin-project-lite",
"socket2 0.5.7",
"tokio",
"tower",
"tower-service",
"tracing",
]
[[package]]
@@ -2899,7 +2926,7 @@ dependencies = [
"rustls-pemfile 2.1.2",
"socket2 0.5.7",
"tokio",
"tokio-rustls",
"tokio-rustls 0.25.0",
"tracing",
"url",
"webpki-roots 0.26.3",
@@ -3001,9 +3028,9 @@ dependencies = [
"chrono",
"data-encoding",
"futures-util",
"headers",
"http 0.2.12",
"http-body 0.4.6",
"headers 0.3.9",
"http 1.1.0",
"http-body 1.0.0",
"icu_locid",
"mas-data-model",
"mas-http",
@@ -3041,7 +3068,7 @@ dependencies = [
"dotenvy",
"figment",
"httpdate",
"hyper 0.14.29",
"hyper 1.4.0",
"ipnetwork",
"itertools 0.13.0",
"listenfd",
@@ -3086,7 +3113,7 @@ dependencies = [
"sqlx",
"tokio",
"tower",
"tower-http",
"tower-http 0.4.4",
"tracing",
"tracing-appender",
"tracing-opentelemetry",
@@ -3148,7 +3175,7 @@ name = "mas-email"
version = "0.9.0"
dependencies = [
"async-trait",
"headers",
"headers 0.3.9",
"lettre",
"mas-templates",
"thiserror",
@@ -3172,8 +3199,8 @@ dependencies = [
"chrono",
"cookie_store",
"futures-util",
"headers",
"hyper 0.14.29",
"headers 0.3.9",
"hyper 1.4.0",
"insta",
"lettre",
"mas-axum-utils",
@@ -3210,7 +3237,7 @@ dependencies = [
"time",
"tokio",
"tower",
"tower-http",
"tower-http 0.4.4",
"tracing",
"tracing-subscriber",
"ulid",
@@ -3225,10 +3252,10 @@ dependencies = [
"anyhow",
"bytes",
"futures-util",
"headers",
"http 0.2.12",
"http-body 0.4.6",
"hyper 0.14.29",
"headers 0.3.9",
"http 1.1.0",
"http-body 1.0.0",
"hyper 1.4.0",
"hyper-rustls",
"mas-tower",
"opentelemetry",
@@ -3241,7 +3268,7 @@ dependencies = [
"thiserror",
"tokio",
"tower",
"tower-http",
"tower-http 0.4.4",
"tracing",
"tracing-opentelemetry",
]
@@ -3301,7 +3328,7 @@ dependencies = [
"convert_case",
"csv",
"futures-util",
"hyper 0.14.29",
"hyper 1.4.0",
"serde",
"tokio",
"tracing",
@@ -3376,18 +3403,19 @@ dependencies = [
"bytes",
"event-listener 5.3.1",
"futures-util",
"http-body 0.4.6",
"hyper 0.14.29",
"http-body 1.0.0",
"hyper 1.4.0",
"hyper-util",
"libc",
"pin-project-lite",
"rustls-pemfile 2.1.2",
"socket2 0.5.7",
"thiserror",
"tokio",
"tokio-rustls",
"tokio-rustls 0.25.0",
"tokio-test",
"tower-http",
"tower-service",
"tower",
"tower-http 0.5.2",
"tracing",
"tracing-subscriber",
]
@@ -3398,7 +3426,7 @@ version = "0.9.0"
dependencies = [
"anyhow",
"async-trait",
"http 0.2.12",
"http 1.1.0",
"serde",
"tokio",
"url",
@@ -3410,7 +3438,7 @@ version = "0.9.0"
dependencies = [
"anyhow",
"async-trait",
"http 0.2.12",
"http 1.1.0",
"mas-axum-utils",
"mas-http",
"mas-matrix",
@@ -3433,10 +3461,10 @@ dependencies = [
"chrono",
"form_urlencoded",
"futures-util",
"headers",
"http 0.2.12",
"http-body 0.4.6",
"hyper 0.14.29",
"headers 0.3.9",
"http 1.1.0",
"http-body 1.0.0",
"hyper 1.4.0",
"hyper-rustls",
"language-tags",
"mas-http",
@@ -3456,7 +3484,7 @@ dependencies = [
"thiserror",
"tokio",
"tower",
"tower-http",
"tower-http 0.4.4",
"tracing",
"url",
"wiremock",
@@ -3592,7 +3620,7 @@ dependencies = [
"arc-swap",
"camino",
"chrono",
"http 0.2.12",
"http 1.1.0",
"mas-data-model",
"mas-i18n",
"mas-router",
@@ -3616,7 +3644,7 @@ dependencies = [
name = "mas-tower"
version = "0.9.0"
dependencies = [
"http 0.2.12",
"http 1.1.0",
"opentelemetry",
"opentelemetry-http",
"opentelemetry-semantic-conventions",
@@ -3860,7 +3888,7 @@ dependencies = [
"assert_matches",
"chrono",
"data-encoding",
"http 0.2.12",
"http 1.1.0",
"language-tags",
"mas-iana",
"mas-jose",
@@ -4887,6 +4915,19 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rustls"
version = "0.23.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402"
dependencies = [
"once_cell",
"rustls-pki-types",
"rustls-webpki 0.102.4",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-native-certs"
version = "0.7.0"
@@ -5841,6 +5882,12 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
[[package]]
name = "sync_wrapper"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394"
[[package]]
name = "synstructure"
version = "0.13.1"
@@ -5997,6 +6044,17 @@ dependencies = [
"tokio",
]
[[package]]
name = "tokio-rustls"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4"
dependencies = [
"rustls 0.23.10",
"rustls-pki-types",
"tokio",
]
[[package]]
name = "tokio-stream"
version = "0.1.15"
@@ -6116,6 +6174,22 @@ dependencies = [
"tracing",
]
[[package]]
name = "tower-http"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5"
dependencies = [
"bitflags 2.6.0",
"bytes",
"http 1.1.0",
"http-body 1.0.0",
"http-body-util",
"pin-project-lite",
"tower-layer",
"tower-service",
]
[[package]]
name = "tower-layer"
version = "0.3.2"

View File

@@ -68,6 +68,15 @@ version = "0.1.80"
[workspace.dependencies.anyhow]
version = "1.0.86"
# HTTP router
[workspace.dependencies.axum]
version = "0.7.5"
# Extra utilities for Axum
[workspace.dependencies.axum-extra]
version = "0.9.3"
features = ["cookie-private", "cookie-key-expansion", "typed-header"]
# UTF-8 paths
[workspace.dependencies.camino]
version = "1.1.7"
@@ -94,20 +103,25 @@ version = "0.3.9"
# HTTP request/response
[workspace.dependencies.http]
version = "0.2.12"
version = "1.1.0"
# HTTP body trait
[workspace.dependencies.http-body]
version = "0.4.6"
version = "1.0.0"
# HTTP client
# HTTP client and server
[workspace.dependencies.hyper]
version = "0.14.29"
features = ["client", "http1", "http2", "stream", "runtime"]
version = "1.4.0"
features = ["client", "http1", "http2"]
# Additional Hyper utilties
[workspace.dependencies.hyper-util]
version = "0.1.6"
features = ["server", "server-auto", "service", "http1", "http2", "tokio"]
# Hyper Rustls support
[workspace.dependencies.hyper-rustls]
version = "0.25.0"
version = "0.27.2"
features = ["http1", "http2"]
default-features = false

View File

@@ -13,8 +13,8 @@ workspace = true
[dependencies]
async-trait.workspace = true
axum = { version = "0.6.20", features = ["headers"] }
axum-extra = { version = "0.8.0", features = ["cookie-private", "cookie-key-expansion"] }
axum.workspace = true
axum-extra.workspace = true
chrono.workspace = true
data-encoding = "2.6.0"
futures-util = "0.3.30"

View File

@@ -13,7 +13,7 @@ workspace = true
[dependencies]
anyhow.workspace = true
axum = "0.6.20"
axum.workspace = true
camino.workspace = true
clap.workspace = true
console = "0.15.8"

View File

@@ -31,9 +31,9 @@ sentry = { version = "0.31.8", default-features = false }
hyper.workspace = true
tower.workspace = true
tower-http = { version = "0.4.4", features = ["cors"] }
axum = "0.6.20"
axum-macros = "0.3.8"
axum-extra = { version = "0.8.0", features = ["cookie-private"] }
axum.workspace = true
axum-macros = "0.4.1"
axum-extra.workspace = true
async-graphql.workspace = true
@@ -52,7 +52,12 @@ serde_urlencoded = "0.7.1"
# Password hashing
argon2 = { version = "0.5.3", features = ["password-hash", "std"] }
bcrypt = "0.15.1"
pbkdf2 = { version = "0.12.2", features = ["password-hash", "std", "simple", "parallel"] }
pbkdf2 = { version = "0.12.2", features = [
"password-hash",
"std",
"simple",
"parallel",
] }
zeroize = "1.8.1"
# Various data types and utilities

View File

@@ -40,13 +40,12 @@ tokio.workspace = true
[features]
client = [
"dep:mas-tower",
"dep:rustls",
"hyper/tcp",
"dep:hyper-rustls",
"dep:rustls-platform-verifier",
"tower/limit",
"tower-http/timeout",
"tower-http/follow-redirect",
"tower-http/set-header",
"dep:mas-tower",
"dep:rustls",
"dep:hyper-rustls",
"dep:rustls-platform-verifier",
"tower/limit",
"tower-http/timeout",
"tower-http/follow-redirect",
"tower-http/set-header",
]

View File

@@ -17,14 +17,15 @@ event-listener = "5.3.1"
futures-util = "0.3.30"
http-body.workspace = true
hyper = { workspace = true, features = ["server"] }
hyper-util.workspace = true
libc = "0.2.155"
pin-project-lite = "0.2.14"
socket2 = "0.5.7"
thiserror.workspace = true
tokio.workspace = true
tokio-rustls = "0.25.0"
tower-http = { version = "0.4.4", features = ["add-extension"] }
tower-service = "0.3.2"
tower.workspace = true
tower-http = { version = "0.5.2", features = ["add-extension"] }
tracing.workspace = true
[dev-dependencies]

View File

@@ -21,16 +21,17 @@ use std::{
};
use anyhow::Context;
use hyper::{service::service_fn, Request, Response};
use hyper::{Request, Response};
use mas_listener::{server::Server, shutdown::ShutdownStream, ConnectionInfo};
use tokio::signal::unix::SignalKind;
use tokio_rustls::rustls::{server::WebPkiClientVerifier, RootCertStore, ServerConfig};
use tower::service_fn;
static CA_CERT_PEM: &[u8] = include_bytes!("./certs/ca.pem");
static SERVER_CERT_PEM: &[u8] = include_bytes!("./certs/server.pem");
static SERVER_KEY_PEM: &[u8] = include_bytes!("./certs/server-key.pem");
async fn handler(req: Request<hyper::Body>) -> Result<Response<String>, Infallible> {
async fn handler(req: Request<hyper::body::Incoming>) -> Result<Response<String>, Infallible> {
tracing::info!("Handling request");
tokio::time::sleep(Duration::from_secs(3)).await;
let info = req.extensions().get::<ConnectionInfo>().unwrap();

View File

@@ -22,14 +22,17 @@ use std::{
use event_listener::{Event, EventListener};
use futures_util::{stream::SelectAll, Stream, StreamExt};
use http_body::Body;
use hyper::{body::HttpBody, server::conn::Connection, Request, Response};
use hyper::{Request, Response};
use hyper_util::{
rt::{TokioExecutor, TokioIo},
server::conn::auto::Connection,
service::TowerToHyperService,
};
use pin_project_lite::pin_project;
use thiserror::Error;
use tokio::io::{AsyncRead, AsyncWrite};
use tokio_rustls::rustls::ServerConfig;
use tower::Service;
use tower_http::add_extension::AddExtension;
use tower_service::Service;
use tracing::Instrument;
use crate::{
@@ -91,10 +94,10 @@ impl<S> Server<S> {
/// Run a single server
pub async fn run<B, SD>(self, shutdown: SD)
where
S: Service<Request<hyper::Body>, Response = Response<B>> + Clone + Send + 'static,
S: Service<Request<hyper::body::Incoming>, Response = Response<B>> + Clone + Send + 'static,
S::Future: Send + 'static,
S::Error: std::error::Error + Send + Sync + 'static,
B: Body + Send + 'static,
B: http_body::Body + Send + 'static,
B::Data: Send,
B::Error: std::error::Error + Send + Sync + 'static,
SD: Stream + Unpin,
@@ -173,15 +176,20 @@ async fn accept<S, B>(
stream: UnixOrTcpConnection,
service: S,
) -> Result<
Connection<MaybeTlsStream<Rewind<UnixOrTcpConnection>>, AddExtension<S, ConnectionInfo>>,
Connection<
'static,
TokioIo<MaybeTlsStream<Rewind<UnixOrTcpConnection>>>,
TowerToHyperService<AddExtension<S, ConnectionInfo>>,
TokioExecutor,
>,
AcceptError,
>
where
S: Service<Request<hyper::Body>, Response = Response<B>>,
S: Service<Request<hyper::body::Incoming>, Response = Response<B>> + Send + Clone + 'static,
S::Error: std::error::Error + Send + Sync + 'static,
S::Future: Send + 'static,
B: HttpBody + Send + 'static,
B::Data: Send + 'static,
B: http_body::Body + Send + 'static,
B::Data: Send,
B::Error: std::error::Error + Send + Sync + 'static,
{
let span = tracing::Span::current();
@@ -219,18 +227,17 @@ where
net_peer_addr: peer_addr.into_net(),
};
let service = AddExtension::new(service, info);
let mut builder = hyper_util::server::conn::auto::Builder::new(TokioExecutor::new());
if is_h2 {
builder = builder.http2_only();
}
builder.http1().keep_alive(true);
let conn = if is_h2 {
hyper::server::conn::Http::new()
.http2_only(true)
.serve_connection(stream, service)
} else {
hyper::server::conn::Http::new()
.http1_only(true)
.http1_keep_alive(true)
.serve_connection(stream, service)
};
let service = TowerToHyperService::new(AddExtension::new(service, info));
let conn = builder
.serve_connection(TokioIo::new(stream), service)
.into_owned();
Ok(conn)
})
@@ -270,18 +277,19 @@ impl<C> AbortableConnection<C> {
}
}
impl<T, S, B> Future for AbortableConnection<Connection<T, S>>
impl<T, S, B> Future
for AbortableConnection<Connection<'static, T, TowerToHyperService<S>, TokioExecutor>>
where
Connection<T, S>: Future,
S: Service<Request<hyper::Body>, Response = Response<B>> + Send + 'static,
Connection<'static, T, TowerToHyperService<S>, TokioExecutor>: Future,
S: Service<Request<hyper::body::Incoming>, Response = Response<B>> + Send + Clone + 'static,
S::Future: Send + 'static,
S::Error: std::error::Error + Send + Sync,
B: HttpBody + Send + 'static,
T: hyper::rt::Read + hyper::rt::Write + Unpin,
B: http_body::Body + Send + 'static,
B::Data: Send,
B::Error: std::error::Error + Send + Sync,
T: AsyncRead + AsyncWrite + Unpin,
B::Error: std::error::Error + Send + Sync + 'static,
{
type Output = <Connection<T, S> as Future>::Output;
type Output = <Connection<'static, T, TowerToHyperService<S>, TokioExecutor> as Future>::Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.project();
@@ -308,10 +316,10 @@ where
#[allow(clippy::too_many_lines)]
pub async fn run_servers<S, B, SD>(listeners: impl IntoIterator<Item = Server<S>>, mut shutdown: SD)
where
S: Service<Request<hyper::Body>, Response = Response<B>> + Clone + Send + 'static,
S: Service<Request<hyper::body::Incoming>, Response = Response<B>> + Clone + Send + 'static,
S::Future: Send + 'static,
S::Error: std::error::Error + Send + Sync + 'static,
B: Body + Send + 'static,
B: http_body::Body + Send + 'static,
B::Data: Send,
B::Error: std::error::Error + Send + Sync + 'static,
SD: Stream + Unpin,

View File

@@ -12,7 +12,7 @@ publish = false
workspace = true
[dependencies]
axum = { version = "0.6.20", default-features = false }
axum.workspace = true
serde.workspace = true
serde_urlencoded = "0.7.1"
url.workspace = true