Closed jsha closed 3 years ago
I have an idea for how to do this in a more satisfactory way that works at the API level, not just internally. There are actually very few methods that are specific to client or server. So we should define the API mainly in terms of the commonality: have one type returned from both the client and server constructors, and define both the shared and the distinct functionality on that type. If a client-specific function is called on something that's really a server connection, return an error.
I'll describe these in terms of the unreleased connection
naming in the rustls API:
struct Conn {
conn: Box<mut dyn Any> // Holds a ClientConnection or ServerConnection
userdata: *mut c_void
}
impl CastPtr for rustls_connection {
RustType = Conn
}
struct rustls_connection *rustls_client_connection_new();
struct rustls_connection *rustls_server_connection_new();
// Each of these does a `downcast_mut` to ServerConnection or ClientConnection
rustls_connection_read_tls(struct rustls_connection *conn, ...);
rustls_connection_write_tls(struct rustls_connection *conn, ...);
rustls_connection_read(struct rustls_connection *conn, ...);
rustls_connection_write(struct rustls_connection *conn, ...);
// Returns false if the conn is actually a server connection
bool rustls_client_connection_is_early_data_accepted(struct rustls_connection *conn);
// Returns error if conn is actually a server connection
rustls_result rustls_client_connection_early_data_write(struct rustls_connection *conn, buf, len);
// Stores an empty string if conn is actually a client connection
rustls_server_connection_get_sni_hostname(struct rustls_connection *conn, buf, len);
Nice!
Just learned that dyn Trait
holds 2 pointers internally, one of the instance and one trait function table ref. And that there is more special sauce in dyn Any
. Hmm, and then I read something like:
Note that &dyn Any is limited to testing whether a value is of a specified concrete type, and cannot be used to test whether a type implements a trait.
Which means that the implementations of dyn Trait
and dyn Any
are internally radically different and that the one has really no relation to the other. And Any
is a trait, but dyn Any
is not about a Trait - it just looks like it.
I guess this is an example of Rust evolution. But it is tricky for a newcomer to learn that dyn Trait
Any is a trait
and dyn Any
are in a tangled relationship and limited by their internal implementations.
Why not use dyn Connection
anyway here?
dyn Connection
was my original plan but AFAICT I can't downcast a dyn Connection
to ServerConnection
or ClientConnection
, which is needed to call the methods that are specific to one or the other. If I'm wrong about that, that'd be great!
In rustls,
ClientSession
andServerSession
both implement theSession
trait, which contains a number of shared methods. The C bindings for those methods are duplicated between client.rs and server.rs. We explored methods of de-duplicating them in #30, and concluded that:Using a macro to generate code for each type of peer won't work because of token pasting issues.
We should define a new shared
session.rs
. Each method, e.g.rustls_client_session_read
, will call a correspondingrustls_session_read(&mut impl Session, ...)
. For some methods, likerustls_session_wants_read
, the body will be a single line. For these cases it's probably still worthwhile defining a shared version, just for consistency across methods ofSession
.