# `nupp.io.tls`
`nupp.io.tls` takes ownership of a connection `nupp.io.net` already opened
and turns it into an encrypted connection.
```nupp
local function fetch(): string
local stream = assert(nupp.io.net.connect({host = "example.com", port = 443}))
local session = assert(nupp.io.tls.client(stream, {
hostname = "example.com",
}))
assert(session:handshake())
assert(session:write("GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n"))
const body = assert(session:read(1024))
session:close()
return body
end
```
Passing `authority` selects exactly those PEM roots. Omitting it uses the
platform's root certificates. `SSL_CERT_FILE` and `SSL_CERT_DIR` override the
platform on every system; without them, macOS reads Security.framework anchors,
Windows reads its root stores, and other Unix systems search the ordinary
distribution locations. The roots are loaded once per process. An explicitly
configured file which cannot be read is a failure rather than permission to
silently trust something else.
A session reads and writes through the same `Reader` and `Writer` contracts a
plain connection does, so a parser written against byte I/O reads an encrypted
connection without knowing it has one.
The session owns the connection. There is no plaintext handle alongside it and
no second reactor polling the same socket. Closing the session sends
`close_notify`, drains it, and closes the transport.
Compatible client and server configurations are cached in bounded native
process state so Rustls can reuse its resumption state across sessions and
worker lanes. It is not persistent across processes. Resumption never sends
application data before the handshake: TLS 1.3 early data is deliberately not
enabled because accepting it requires an application-level replay policy.
## Types
### `ClientOptions` _type_
```nupp
type ClientOptions = {
--- The name to expect on the peer's certificate, sent as SNI. Given
--- whenever `verify` is on, because a certificate verified against no name
--- is a certificate belonging to anybody.
hostname: string?,
--- Certificates to trust, as PEM. Nil uses the platform's root
--- certificates; a supplied string replaces them.
authority: string?,
--- Whether the peer's certificate must satisfy the trust store. On by
--- default: a client that does not verify has encryption without
--- authentication, which is a different and much weaker thing than TLS is
--- usually taken to mean.
verify: boolean?,
--- Application protocols to offer, in decreasing preference order, such as
--- `{"h2", "http/1.1"}`. The server chooses from these, so the order is a
--- preference and not a demand.
protocols: {string}?
}
```
How a client session is opened.
### `ServerOptions` _type_
```nupp
type ServerOptions = {
--- The certificate chain to present, as PEM.
certificate: string,
--- The private key for it, as PEM.
privateKey: string,
--- Application protocols this server speaks, in decreasing preference
--- order. The server picks, so this order is the one that decides.
---
--- A server that names protocols and shares none with a client refuses the
--- handshake rather than continuing without one, which is what makes naming
--- them a commitment rather than a hint.
protocols: {string}?
}
```
How a server session is opened.
### `Session` _record_
```nupp
record tls.Session is Reader, Writer
function isConnected(self): boolean end
function isReady(self): boolean end
function isReleased(self): boolean end
function isEnded(self): boolean end
function protocol(self): string? end
function isVerified(self): boolean end
function isResumed(self): boolean end
function step(self): (boolean?, string?) end
function handshake(self): (boolean, string?) end
function read(self, count: integer): (string?, string?) end
function readSpan(self, exclusive destination: span.Writable): (integer?, string?) end
function readInto(self, exclusive destination: Buffer, offset: integer?, count: integer?): (integer?, string?) end
function transferTo(self, exclusive destination: Writer): (integer?, string?) end
function write(exclusive self, bytes: string): (boolean, string?) end
function writeSpan(exclusive self, borrows source: span.ByteSpan): (integer?, string?) end
function flush(self): (boolean, string?) end
function close(takes self): nil end
end
```
An encrypted connection.
Owns the connection it encrypts over. Closing the session sends
`close_notify`, drains it, and closes the socket.
#### Methods
##### `isConnected`
```nupp
isConnected: function isConnected(self): boolean
```
Whether the connection underneath is still open.
False once this session is released: closing frees the native session,
so the question has to be answered from this record's own state rather
than asked of memory that has been given back.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
##### `isReady`
```nupp
isReady: function isReady(self): boolean
```
Whether the handshake has finished.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
##### `isReleased`
```nupp
isReleased: function isReleased(self): boolean
```
Whether this session has been released.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
##### `isEnded`
```nupp
isEnded: function isEnded(self): boolean
```
Whether the peer closed its sending half.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
##### `protocol`
```nupp
protocol: function protocol(self): string?
```
The application protocol both sides agreed on, or nil when none was
negotiated.
Nil is not a failure: it is what neither side offering a list looks like.
A server that offered a list sharing nothing with the client's refuses
the handshake instead, so a session that exists and answers nil was never
asked to pick one.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | |
###### Returns
| Type | Description |
| --- | --- |
| `string?` | |
##### `isVerified`
```nupp
isVerified: function isVerified(self): boolean
```
Whether the peer's certificate satisfied what was asked of it.
Answered after the handshake and not before, because before it there is
no certificate to have an opinion about.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
##### `isResumed`
```nupp
isResumed: function isResumed(self): boolean
```
Whether this connection skipped the full asymmetric handshake by
resuming a cached session.
Servers answer false. The server-side store is deliberately opaque: a
client can use this answer to observe its cache, while accepting a
session does not change what a server application may do with it.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
##### `step`
```nupp
step: function step(self): boolean?, string?
```
Drives the handshake one pass, without suspending.
The form a host with its own loop wants, and the form two sessions in one
thread need: a handshake is two peers taking turns, so something has to
be able to give one side a turn without waiting for it to finish.
`handshake` is this in a loop with the waiting done for you.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | this session |
###### Returns
| Type | Description |
| --- | --- |
| `boolean?` | whether the handshake has finished, or nil when it failed |
| `string?` | why it failed, when it did |
##### `handshake`
```nupp
handshake: function handshake(self): boolean, string?
```
Drives the handshake to completion, suspending while it waits on the
connection.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | this session |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether the handshake finished |
| `string?` | why it did not, when unsuccessful |
##### `read`
```nupp
read: function read(self, count: integer): string?, string?
```
Reads up to `count` decrypted bytes, suspending while none have arrived.
An empty answer is the end, exactly as on a plain connection, and it is
only ever the end: here that end is the peer's `close_notify` and
nothing else. A connection that simply stopped is a truncated stream and
is reported as a failure, because what arrived is the front of a message
rather than the whole of one and no authenticated party said otherwise.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | this session |
| `count` | `integer` | the most bytes to read |
###### Returns
| Type | Description |
| --- | --- |
| `string?` | the bytes, or nil when the session is closed |
| `string?` | why it could not read, when unsuccessful |
###### Raises
- when count is not a positive integer
##### `readSpan`
```nupp
readSpan: function readSpan(self, exclusive destination: span.Writable): integer?, string?
```
Reads directly into a checked writable span. A zero answer is the end.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | |
| `exclusive destination` | `span.Writable\` | |
###### Returns
| Type | Description |
| --- | --- |
| `integer?` | |
| `string?` | |
###### Raises
- when the destination is empty
##### `readInto`
```nupp
readInto: function readInto(self, exclusive destination: Buffer, offset: integer?, count: integer?): integer?, string?
```
Reads into a buffer. A zero answer is the end.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | |
| `exclusive destination` | `Buffer` | |
| `offset` | `integer?` | |
| `count` | `integer?` | |
###### Returns
| Type | Description |
| --- | --- |
| `integer?` | |
| `string?` | |
###### Raises
- when count is not a positive integer
##### `transferTo`
```nupp
transferTo: function transferTo(self, exclusive destination: Writer): integer?, string?
```
Writes everything left to a writer.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | |
| `exclusive destination` | `Writer` | |
###### Returns
| Type | Description |
| --- | --- |
| `integer?` | |
| `string?` | |
##### `write`
```nupp
write: function write(exclusive self, bytes: string): boolean, string?
```
Writes plaintext, in as many records as it takes.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `exclusive self` | `any` | this session |
| `bytes` | `string` | the bytes to append |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether they were written |
| `string?` | why they were not, when unsuccessful |
##### `writeSpan`
```nupp
writeSpan: function writeSpan(exclusive self, borrows source: span.ByteSpan): integer?, string?
```
Appends bytes from a checked shared span.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `exclusive self` | `any` | |
| `borrows source` | `span.ByteSpan` | |
###### Returns
| Type | Description |
| --- | --- |
| `integer?` | |
| `string?` | |
##### `flush`
```nupp
flush: function flush(self): boolean, string?
```
Waits until the records this session wrote have left the process.
A TLS write puts encrypted records on the connection underneath, and
that connection has a queue. Reporting success while they sit in it
would let a caller write, flush, close the connection, and have the
records cancelled with it.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
| `string?` | |
##### `close`
```nupp
close: function close(takes self): nil
```
Sends `close_notify`, drains it, and closes the owned connection.
Terminal and idempotent.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `takes self` | `any` | |
###### Returns
| Type | Description |
| --- | --- |
| `nil` | |
## Functions
### `tls.client` _function_
```nupp
function tls.client(takes stream: net.Stream, options: tls.ClientOptions): affine(tls.Session)?, string?
```
Encrypts a connection as the client.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `takes stream` | `net.Stream` | the connection to adopt; it is consumed even when setup fails |
| `options` | `tls.ClientOptions` | what to expect of the peer |
#### Returns
| Type | Description |
| --- | --- |
| `affine(tls.Session)?` | the session, which the caller owns and must close |
| `string?` | why it could not be made, when unsuccessful |
#### Raises
- when verification is asked for without a name to verify against
### `tls.server` _function_
```nupp
function tls.server(takes stream: net.Stream, options: tls.ServerOptions): affine(tls.Session)?, string?
```
Encrypts a connection as the server.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `takes stream` | `net.Stream` | the connection to adopt; it is consumed even when setup fails |
| `options` | `tls.ServerOptions` | the certificate to present and what to ask of the client |
#### Returns
| Type | Description |
| --- | --- |
| `affine(tls.Session)?` | the session, which the caller owns and must close |
| `string?` | why it could not be made, when unsuccessful |