pub trait SyncProtocol<'a, T>{
// Required methods
fn name(&self) -> &'static str;
fn initiate<'async_trait>(
self: Arc<Self>,
topic_query: T,
tx: Box<&'a mut (dyn AsyncWrite + Send + Unpin)>,
rx: Box<&'a mut (dyn AsyncRead + Send + Unpin)>,
app_tx: Box<&'a mut (dyn Sink<FromSync<T>, Error = SyncError> + Send + Unpin)>,
) -> Pin<Box<dyn Future<Output = Result<(), SyncError>> + Send + 'async_trait>>
where Self: 'async_trait,
'a: 'async_trait;
fn accept<'async_trait>(
self: Arc<Self>,
tx: Box<&'a mut (dyn AsyncWrite + Send + Unpin)>,
rx: Box<&'a mut (dyn AsyncRead + Send + Unpin)>,
app_tx: Box<&'a mut (dyn Sink<FromSync<T>, Error = SyncError> + Send + Unpin)>,
) -> Pin<Box<dyn Future<Output = Result<(), SyncError>> + Send + 'async_trait>>
where Self: 'async_trait,
'a: 'async_trait;
}Expand description
Traits to implement a custom sync protocol.
Implementing a SyncProtocol trait needs extra care and is only required when designing custom
low-level peer-to-peer protocols and data types. p2panda already comes with solutions which can
be used “out of the box”, providing implementations for most applications and usecases.
§Design
Sync sessions take place when two peers connect to each other and follow the sync protocol. They are designed as a two-party protocol featuring an “initiator” and an “acceptor” role.
Each protocol usually follows two phases: 1) The “Handshake” phase, during which the “initiator” sends the “topic query” and any access control data to the “acceptor”, and 2) The “Sync” phase, where the requested application data is finally exchanged and validated.
§Privacy and Security
The SyncProtocol trait has been designed to allow privacy-respecting implementations where
application data (via access control) and the topic query itself (for example via Diffie
Hellmann) is securely exchanged without revealing any information to unknown peers
unnecessarily. This usually takes place during the “Handshake” phase of the protocol.
The underlying transport layer should provide automatic authentication of the remote peer, a
reliable connection and transport encryption. p2panda-net, for example, uses self-certified
TLS 1.3 over QUIC.
§Streams
Three distinct data channels are provided by the underlying transport layer to each
SyncProtocol implementation: tx for sending data to the remote peer, rx to receive data
from the remote peer and app_tx to send received data to the higher-level application-,
validation- and persistance-layers.
§Topic queries
Topics queries are generic data types which can be used to subjectively express interest in a particular subset of the data we want to sync over, like chat group identifiers or very specific “search queries”, for example “give me all documents containing the word ‘billy’.”
With the help of the TopicMap trait we can keep sync implementations agnostic to specific
topic query implementations. The sync protocol only needs to feed the “topic query” into the
“map” which will answer with the actual to-be-synced data entities (for example coming from a
store). This allows application developers to re-use your SyncProtocol implementation for
their custom TopicQuery requirements.
§Validation
Basic data-format and -encoding validation usually takes place during the “Sync” phase of the protocol.
Further validation which might require more knowledge of the application state or can only be applied after decrypting the payload should be handled outside the sync protocol, by sending it upstream to higher application layers.
§Errors
Protocol implementations operate on multiple layers at the same time, expressed in distinct error categories:
- Unexpected behaviour of the remote peer not following the implemented protocol
- Handling (rare) critical system failures
Required Methods§
Sourcefn name(&self) -> &'static str
fn name(&self) -> &'static str
Custom identifier for this sync protocol implementation.
This is currently only used for debugging or logging purposes.
Sourcefn initiate<'async_trait>(
self: Arc<Self>,
topic_query: T,
tx: Box<&'a mut (dyn AsyncWrite + Send + Unpin)>,
rx: Box<&'a mut (dyn AsyncRead + Send + Unpin)>,
app_tx: Box<&'a mut (dyn Sink<FromSync<T>, Error = SyncError> + Send + Unpin)>,
) -> Pin<Box<dyn Future<Output = Result<(), SyncError>> + Send + 'async_trait>>where
Self: 'async_trait,
'a: 'async_trait,
fn initiate<'async_trait>(
self: Arc<Self>,
topic_query: T,
tx: Box<&'a mut (dyn AsyncWrite + Send + Unpin)>,
rx: Box<&'a mut (dyn AsyncRead + Send + Unpin)>,
app_tx: Box<&'a mut (dyn Sink<FromSync<T>, Error = SyncError> + Send + Unpin)>,
) -> Pin<Box<dyn Future<Output = Result<(), SyncError>> + Send + 'async_trait>>where
Self: 'async_trait,
'a: 'async_trait,
Initiate a sync protocol session over the provided bi-directional stream for the given topic query.
During the “Handshake” phase the “initiator” usually requests access and informs the remote
peer about the “topic query” they are interested in. Implementations for p2panda-net
are required to send a SyncFrom::HandshakeSuccess message to the application layer (via
app_tx) during this phase to inform the backend that we’ve successfully requested access,
exchanged the topic query with the remote peer and are about to begin sync.
After the “Handshake” is complete the protocol enters the “Sync” phase, during which
the actual application data is exchanged with the remote peer. It’s left up to each
protocol implementation to decide whether data is exchanged in one or both directions.
Synced data is forwarded to the application layers via the SyncFrom::Data message
(via app_tx).
In case of a detected failure (either through a critical error on our end or an unexpected
behaviour from the remote peer) a SyncError is returned.
Sourcefn accept<'async_trait>(
self: Arc<Self>,
tx: Box<&'a mut (dyn AsyncWrite + Send + Unpin)>,
rx: Box<&'a mut (dyn AsyncRead + Send + Unpin)>,
app_tx: Box<&'a mut (dyn Sink<FromSync<T>, Error = SyncError> + Send + Unpin)>,
) -> Pin<Box<dyn Future<Output = Result<(), SyncError>> + Send + 'async_trait>>where
Self: 'async_trait,
'a: 'async_trait,
fn accept<'async_trait>(
self: Arc<Self>,
tx: Box<&'a mut (dyn AsyncWrite + Send + Unpin)>,
rx: Box<&'a mut (dyn AsyncRead + Send + Unpin)>,
app_tx: Box<&'a mut (dyn Sink<FromSync<T>, Error = SyncError> + Send + Unpin)>,
) -> Pin<Box<dyn Future<Output = Result<(), SyncError>> + Send + 'async_trait>>where
Self: 'async_trait,
'a: 'async_trait,
Accept a sync protocol session over the provided bi-directional stream.
During the “Handshake” phase the “acceptor” usually responds to the access request and
learns about the “topic query” from the remote peer. Implementations for p2panda-net are
required to send a SyncFrom::HandshakeSuccess message to the application layer (via
app_tx) during this phase to inform the backend that the topic query has been
successfully received from the remote peer and that data exchange is about to begin.
After the “Handshake” is complete the protocol enters the “Sync” phase, during which
the actual application data is exchanged with the remote peer. It’s left up to each
protocol implementation to decide whether data is exchanged in one or both directions.
Synced data is forwarded to the application layers via the SyncFrom::Data message
(via app_tx).
In case of a detected failure (either through a critical error on our end or an unexpected
behaviour from the remote peer) a SyncError is returned.