silk.tls.connection.
An authenticated TLS 1.3 connection over a ByteDuplex transport, with timeouts and close
handling.
When to use#
Use withClient to authenticate a server, use the connection inside a callback, and close it
when the callback returns. Use authenticateOwned when the connection must live longer than
one scope, for example in a connection pool. Then you must call OwnedConnection.close. Load
the trust snapshot before you call either one.
Details#
Authentication reads the wall clock one time for certificate dates, makes a Client, and runs
the handshake over the transport. ConnectionOptions sets the handshake timeout, and an
optional external deadline. You get the connection only after authentication succeeds. Then use
OwnedConnection.readSome, OwnedConnection.writeSome, and OwnedConnection.flush.
Errors#
ConnectionErrorfor a TLS failure, a transport failure, or a timeout.OutOfMemoryErrorif theAllocatorfails.
Gotchas#
After an I/O error or a cancellation, the connection closes its transport, so that no partial
buffer is sent again. OwnedConnection.shutdownWrite sends close_notify and closes the
write direction. OwnedConnection.close closes at once, without close_notify.
withClient ignores errors from its own close, so that they do not hide the result of the
callback.
Examples#
Connect every dependency#
This example gives the client no server bytes and no trust, so the handshake fails. A real
program provides a network transport, the system clocks, a secure Random, and loaded trust in
the same places.
import silk.mem.allocator { Allocator, OutOfMemoryError }
import silk.core.effect { Effect }
import silk.tls.https_identity { HttpsIdentity, OriginHost, ReferenceIdentity }
import silk.io.memory_byte_duplex { MemoryByteDuplex, MemoryReadEvent, MemoryWriteEvent }
import silk.system.monotonic_clock { MonotonicClock }
import silk.core.option { Option }
import silk.system.random { Random }
import silk.core.result { Result }
import silk.system.system_clock { Instant, SystemClock }
import silk.tls.client { AlpnConfig, ClientConfig, ClientLimits }
import silk.tls.connection { OwnedConnection, ConnectionOptions, withClient }
import silk.tls.trust_anchor { TrustAnchor }
import silk.tls.trust_snapshot { SnapshotLimits, TrustSnapshot }
import silk.num.u64
import silk.num.usize
import silk.collections.list { List }
struct FixedWall {}
impl SystemClock for FixedWall {
effect fn now(self: &mut Self) -> Instant {
return SystemClock.make(0, 0)
}
effect fn getResolution(self: &mut Self) -> u64 {
return u64.toU64(1)
}
}
struct FixedMonotonic {}
impl MonotonicClock for FixedMonotonic {
effect fn now(self: &mut Self) -> Instant {
return SystemClock.make(0, 0)
}
effect fn getResolution(self: &mut Self) -> u64 {
return u64.toU64(1)
}
effect fn waitUntil(self: &mut Self, when: Instant) -> () {
drop when
return ()
}
effect fn waitFor(self: &mut Self, howLong: u64) -> () {
drop howLong
return ()
}
}
struct ExampleRandom {}
impl Random for ExampleRandom {
effect fn fillBytes(self: &mut Self, output: &mut [u8]) -> () {
let mut index = usize.ZERO
while index < output.length {
output[index] = 1
index = index + usize.ONE
}
return ()
}
}
fn reference<'a>(host: &'a [u8]) -> ReferenceIdentity<'a> {
return match move HttpsIdentity.reference(OriginHost<'a>.Dns { bytes: host }) {
Result.Success { value } => value
Result.Failure { error } => {
Intrinsic.trap("unexpected failure")
return reference(host)
}
}
}
fn emptyTrust() -> TrustSnapshot {
return match move TrustSnapshot.fromAnchors(
List<TrustAnchor>.make(),
SnapshotLimits { anchors: 0, encodedBytes: 0 },
) {
Result.Success { value } => move value
Result.Failure { error } => {
Intrinsic.trap("unexpected failure")
return emptyTrust()
}
}
}
effect fn inspect(connection: &mut OwnedConnection<MemoryByteDuplex>) -> i32 {
// The handshake fails in this example, so reaching this point is unexpected.
drop connection
return 1
}
effect<'env> fn expected<E: 'env, 'env>(error: E) -> i32 {
drop error
return 0
}
effect<'env> fn failed<E: 'env, 'env>(error: E) -> i32 {
drop error
return 1
}
effect fn program() -> i32
! OutOfMemoryError {
let mut allocator = Allocator.systemAllocatorProvider()
let reads = List<MemoryReadEvent>.make()
let writes = List<MemoryWriteEvent>.make()
let mut transport = run MemoryByteDuplex.make(move reads, move writes, 256, 4, Option<i32>.none())
|> Effect.provideMut<Allocator>(&mut allocator)
let trust = emptyTrust()
let mut wall = FixedWall {}
let mut monotonic = FixedMonotonic {}
let mut random = ExampleRandom {}
let host = b"example.com"
let config = ClientConfig {
reference: reference(&host),
alpn: AlpnConfig.defaults(),
limits: ClientLimits.defaults(),
}
let attempt = withClient<i32, never>(
move transport,
&config,
move trust,
ConnectionOptions.defaults(),
inspect,
)
|> Effect.provideMut<SystemClock>(&mut wall)
|> Effect.provideMut<MonotonicClock>(&mut monotonic)
|> Effect.provideMut<Random>(&mut random)
|> Effect.provideMut<Allocator>(&mut allocator)
// Without server bytes, authentication fails as expected.
return run Effect.catchAll(move attempt, expected)
}
pub fn main() -> i32 {
return run Effect.catchAll(program(), failed)
}Import as OwnedConnection with import silk.tls.connection { OwnedConnection }.
Public declarations: 6.
ConnectionPhase#
pub enum ConnectionPhaseThe state of an OwnedConnection. Read it with OwnedConnection.phase.
Open#
OpenYou can read and write.
PeerEnded#
PeerEndedThe server sent close_notify. You can still read plaintext that is buffered.
WriteShutdown#
WriteShutdownYou called OwnedConnection.shutdownWrite. You can still read, but you cannot write.
Invalid#
InvalidA TLS or transport error occurred. Later I/O fails. Call OwnedConnection.close.
Closed#
ClosedYou called OwnedConnection.close. All later I/O fails.
ConnectionError#
pub union ConnectionErrorThe error from authentication and from connection I/O: a TLS failure, a transport failure, or a timeout.
Tls#
TlsThe TLS client rejected the server or the operation. After this error, the connection stays failed.
Field error#
pub error: TlsErrorThe TLS error.
Io#
IoThe transport failed. During the handshake, a transport timeout becomes HandshakeTimeout.
Field error#
pub error: ByteIoErrorThe transport error.
HandshakeTimeout#
HandshakeTimeoutThe handshake did not complete before its deadline.
Implementation Debug for ConnectionError#
impl Debug for ConnectionErrorOperation debug#
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut WriterWrites the name of the variant and its fields, such as Tls { ... }.
ConnectionOptions#
pub struct ConnectionOptionsTimeouts for authentication: the handshake timeout and an optional external deadline. Start from
ConnectionOptions.defaults.
Field handshakeTimeoutNanoseconds#
pub handshakeTimeoutNanoseconds: u64The maximum duration of the handshake, in nanoseconds, measured with the MonotonicClock.
Field externalDeadline#
pub externalDeadline: Option<Instant>An optional absolute deadline on the MonotonicClock. If it is earlier than the handshake
timeout, the handshake stops at this deadline.
Implementation ConnectionOptions#
impl ConnectionOptionsAssociated function defaults#
pub fn defaults() -> ConnectionOptionsReturns a 30-second handshake timeout and no external deadline.
OwnedConnection#
pub struct OwnedConnection<P>An authenticated TLS client connection that owns its transport P.
Get one from authenticateOwned, or borrow one in the withClient callback. All I/O
functions take an optional deadline on the MonotonicClock. Use Option<Instant>.none() to
wait without a limit. Use OwnedConnection.readSome, OwnedConnection.writeSome, and
OwnedConnection.flush for application data.
Gotchas#
After an I/O error or a cancellation, the connection closes its transport, so that no partial
buffer is sent again. OwnedConnection.shutdownWrite sends close_notify and closes the
write direction. OwnedConnection.close closes at once, without close_notify. You must call
OwnedConnection.close on a connection from authenticateOwned.
Implementation OwnedConnection<P>#
impl<P> OwnedConnection<P>Method phase#
pub fn phase(self: &Self) -> ConnectionPhaseReturns the current ConnectionPhase.
Method authentication#
pub fn authentication<'a>(self: &'a Self) -> Option<Authentication<'a>>Returns the result of authentication: the cipher suite, the ALPN protocol, and the certificate details.
Method readSome#
pub effect fn readSome<'connection>(
self: &'connection mut Self,
output: &mut [u8],
deadline: Option<Instant>,
) -> ReadTransfer
! ConnectionError | OutOfMemoryError
? &mut MonotonicClock | &mut Allocator | &mut Random
where
&mut P provides &ByteDuplex from &mut ByteDuplex,
&mut P provides &ByteDuplex from &mut ByteDuplex | &mut MonotonicClock | &mut Allocator | &mut RandomReads decrypted application data into output, and returns how many bytes it read.
Details#
The function first sends TLS output that is waiting. It returns at least one byte, or the end
of input when the server sent close_notify.
Errors#
TlswithTruncatedAfterAuthenticationif the transport ends withoutclose_notify.TlswithEmptyBufferifoutputis empty.Ioif the transport fails or thedeadlinepasses.
Method writeSome#
pub effect fn writeSome<'connection>(
self: &'connection mut Self,
input: &[u8],
deadline: Option<Instant>,
) -> usize
! ConnectionError | OutOfMemoryError
? &mut MonotonicClock | &mut Allocator | &mut Random
where
&mut P provides &ByteDuplex from &mut ByteDuplex,
&mut P provides &ByteDuplex from &mut ByteDuplex | &mut MonotonicClock | &mut Allocator | &mut RandomEncrypts and sends a prefix of input, and returns how many bytes it sent.
Details#
One call encrypts at most one TLS record of 16384 bytes, and sends all of that record before
it returns. Call it again with the rest of input.
Errors#
Tlsif the connection cannot write, for example afterOwnedConnection.shutdownWrite.Ioif the transport fails, the server closed the connection, or thedeadlinepasses.
Method flush#
pub effect fn flush<'connection>(self: &'connection mut Self, deadline: Option<Instant>) -> ()
! ConnectionError | OutOfMemoryError
? &mut MonotonicClock | &mut Allocator | &mut Random
where
&mut P provides &ByteDuplex from &mut ByteDuplex,
&mut P provides &ByteDuplex from &mut ByteDuplex | &mut MonotonicClock | &mut Allocator | &mut RandomSends all TLS output that is waiting, then flushes the transport.
Method shutdownWrite#
pub effect fn shutdownWrite<'connection>(self: &'connection mut Self, deadline: Option<Instant>) -> ()
! ConnectionError | OutOfMemoryError
? &mut MonotonicClock | &mut Allocator | &mut Random
where
&mut P provides &ByteDuplex from &mut ByteDuplex,
&mut P provides &ByteDuplex from &mut ByteDuplex | &mut MonotonicClock | &mut Allocator | &mut RandomSends close_notify, flushes, and closes the write direction of the transport.
You can still read the response of the server after this call. Call OwnedConnection.close
when you are done.
Method close#
pub effect fn close(self: &mut Self) -> ()
! ConnectionError
? Without<&mut ByteDuplex, ByteDuplex>
where &mut P provides &ByteDuplex from &mut ByteDuplexCloses the transport at once, without close_notify.
Details#
The phase becomes Closed before the transport is closed. A second call does nothing and
succeeds.
Errors#
Ioif the transport fails to close. The connection stays closed.
authenticateOwned#
pub effect<'env> fn authenticateOwned<P: 'env, 'env>(
provider: P,
config: &ClientConfig,
trust: TrustSnapshot,
options: ConnectionOptions,
) -> OwnedConnection<P>
! ConnectionError | OutOfMemoryError
? &mut SystemClock | &mut MonotonicClock | &mut Allocator | &mut Random
where
&mut P provides &ByteDuplex from &mut ByteDuplex,
&mut P provides &ByteDuplex from &mut ByteDuplex | &mut MonotonicClock | &mut Allocator | &mut RandomRuns the TLS handshake over provider, and returns the authenticated connection.
Details#
The function reads the SystemClock one time to check certificate dates, and uses the
MonotonicClock for the timeouts in options. You own the result. Call
OwnedConnection.close when you are done.
Errors#
ConnectionErrorif the handshake fails or times out. The function closesproviderbefore it returns the error.OutOfMemoryErrorif theAllocatorfails.
withClient#
pub effect<'env> fn withClient<'env, A, E, ?CallbackRequirements, P: 'env>(
provider: P,
config: &'env ClientConfig,
trust: TrustSnapshot,
options: ConnectionOptions,
callback: for<'call> once fn<'env>(&'call mut OwnedConnection<P>) -> once Effect<'call & 'env; A ! E ? CallbackRequirements>,
) -> A
! E | ConnectionError | OutOfMemoryError
? CallbackRequirements | &mut SystemClock | &mut MonotonicClock | &mut Allocator | &mut Random
where
&mut P provides &ByteDuplex from &mut ByteDuplex,
&mut P provides &ByteDuplex from &mut ByteDuplex | &mut MonotonicClock | &mut Allocator | &mut Random,
CallbackRequirements in Without<CallbackRequirements, ByteDuplex>Runs the TLS handshake over provider, calls callback with the authenticated connection, and
then closes it.
When to use#
Use it to authenticate a server and use the connection inside one scope. Use
authenticateOwned when the connection must live longer, for example in a connection pool.
Load the trust snapshot before you call it.
Details#
The function reads the SystemClock one time to check certificate dates, and uses the
MonotonicClock for the timeouts in options. /// The connection closes when the callback
returns, fails, or is cancelled. Errors from that close are ignored. Call
OwnedConnection.shutdownWrite in the callback to send close_notify.
Errors#
Efrom the callback.ConnectionErrorif the handshake fails or times out.OutOfMemoryErrorif theAllocatorfails.
Category: TLS and certificates.
Profiles: aarch64-apple-darwin, aarch64-unknown-linux-gnu, aarch64-unknown-linux-gnu-no-libc, x86_64-unknown-linux-gnu, x86_64-unknown-linux-gnu-no-libc, wasm32-unknown-unknown, wasm32-wasip1, wasm32-wasip2.