-
Notifications
You must be signed in to change notification settings - Fork 86
Adding transports
This document aims to describe the basics of implementing a new transport driver for Pat. Although the architecture is rather simple, the differences in various modes tends to require different approches in the implementation details. Therefore, this document focuses on providing an overview of the required API exposed by a package/type to be used as a transport in Pat.
The project's codebase relies heavily on the concept of Interfaces in Go, and especially so when it comes to supporting various transport ("modes").
By taking advantage of the net.Conn
and net.Listener
interfaces provided by the Go standard library, we're able to abstract away almost all the details of the underlying transports' differences. As a bonus, the transport drivers can be re-used for other purposes by other applications.
The architecture is quite simple:
- Any type implementing net.Conn can be used by Pat's FBB/B2F implementation to exchange Winlink messages.
- Any type implementing net.Listener can be used by Pat to listen for incoming P2P connections.
In addition to these, the transport.Dialer interface was introduced as a mechanism for pluggable transport schemes in Pat. This is how Pat knows which driver to use when the user tries to connect using an URI like telnet://
or ardop://
.
A transport is most often supplied as a Go package, either maintained as part of the la5nta/wl2k-go framework, or as an independently maintained external package. The latter is the case for the Pactor driver.
The minimum set of interfaces required to make an outgoing connection is net.Conn and transport.Dialer.
The Dialer interface takes a URL holding information like the target address and (optionally) other per-connection parameters, returning a net.Conn
or an error if the dial attempt failed.
For most modems, some initialization is required prior to dialing the connection. For this reason, it is common to encapsulate the modem in a Modem-struct implementing the transport.Dialer
interface, like seen in the ardop package, like this:
type Modem struct {
// some private fields to keep track of modem state and to send/receive data
}
func (*m Modem) DialURL(url *transport.URL) (net.Conn, error) {
// Dial the connection and return
}
Both Modem
and the returned net.Conn
must be safe for concurrent access. This means that the implementation must prevent successive DialURL calls while the connection is active, unless the Modem supports multiple simultaneous connections.
The Dialer must be registered by Pat on startup. This is typically done after initialization of the modem, like with the ARDOP transport. If no initialization is required, registration can be done by the init() function of the implementing package. This will cause it to be registered as a side effect of importing the package, like with the telnet transport.
The transport.ContextDialer has been introduced as an alternative to transport.Dialer. This extends the Dialer concept with the capability of dial cancellation, allowing Pat to cancel a pending connect by cancelling the provided context. The ardop package's TNC.DialURLContext has an example on how to implement this. Another one in package agwpe.
The net.Listener interface encapsulate net.Conn
in an Accept()
in order to support incoming connections.
A typical example (omitting the Close and Addr methods) could be:
func (m *Modem) Listen() (ln net.Listener, err error) {
// Configure the modem to accept incoming connections before returning a new listener
return listener{modem: m}, nil
}
type listener struct {
modem *Modem
}
func (l listener) Accept() (c net.Conn, err error) {
// Block here until a new connection is received (or listener.Close is called).
// Prepare and return the connection.
}
func (l listener) Close() error {
// Cancel any blocking Accept() call and configure the modem
// to stop listening for incoming connections.
}
See the ardop package for a complete example.
The transport package defines some additional optional interfaces that can be implemented to enhance Pat's ability to handle the connection.
By implementing the TxBuffer
and Flusher
interfaces, Pat will be able to provide a more accurate download/upload progress indication to the user.
Some modems also support switching to more robust modes while the connection is in progress. Those modes can benefit from implementing the Robust
interface.