Skip to main content

ProxyProtocolClient

Struct ProxyProtocolClient 

Source
pub struct ProxyProtocolClient { /* private fields */ }
Expand description

Client for connecting to and communicating through a ap-proxy server.

This is the main client API for connecting to a proxy server, authenticating, discovering peers via rendezvous codes, and sending messages.

§Lifecycle

  1. Create client with new()
  2. Connect and authenticate with connect()
  3. Perform operations (send messages, request rendezvous codes, etc.)
  4. Disconnect with disconnect()

§Examples

Basic usage:

use ap_proxy_client::{ProxyClientConfig, ProxyProtocolClient, IncomingMessage};

// Create and connect
let config = ProxyClientConfig {
    proxy_url: "ws://localhost:8080".to_string(),
    identity_keypair: None,
};
let mut client = ProxyProtocolClient::new(config);
let mut incoming = client.connect().await?;

// Handle messages
tokio::spawn(async move {
    while let Some(msg) = incoming.recv().await {
        match msg {
            IncomingMessage::Send { source, payload, .. } => {
                println!("Got message from {:?}", source);
            }
            _ => {}
        }
    }
});

// Send a message
// client.send_to(target_fingerprint, b"Hello".to_vec()).await?;

Implementations§

Source§

impl ProxyProtocolClient

Source

pub fn new(config: ProxyClientConfig) -> Self

Create a new proxy client with the given configuration.

This does not establish a connection - call connect() to connect and authenticate.

If config.identity_keypair is None, a new random identity will be generated. Otherwise, the provided identity will be used for authentication.

§Examples

Create client with new identity:

use ap_proxy_client::{ProxyClientConfig, ProxyProtocolClient};

let config = ProxyClientConfig {
    proxy_url: "ws://localhost:8080".to_string(),
    identity_keypair: None, // Will generate new identity
};
let client = ProxyProtocolClient::new(config);
println!("Client fingerprint: {:?}", client.fingerprint());

Create client with existing identity:

use ap_proxy_client::{ProxyClientConfig, ProxyProtocolClient, IdentityKeyPair};

let keypair = IdentityKeyPair::generate();
let config = ProxyClientConfig {
    proxy_url: "ws://localhost:8080".to_string(),
    identity_keypair: Some(keypair),
};
let client = ProxyProtocolClient::new(config);
Source

pub async fn connect( &mut self, ) -> Result<UnboundedReceiver<IncomingMessage>, ProxyError>

Connect to the proxy server and perform authentication.

Establishes a WebSocket connection, completes the challenge-response authentication, and returns a channel for receiving incoming messages.

§Authentication Flow
  1. Connect to WebSocket at the configured URL
  2. Receive authentication challenge from server
  3. Sign challenge with client’s private key
  4. Send signed response to server
  5. Server verifies signature and accepts connection
§Timeout

Authentication must complete within 5 seconds or this method returns ProxyError::AuthenticationTimeout.

§Errors
§Examples
use ap_proxy_client::{ProxyClientConfig, ProxyProtocolClient, IncomingMessage};

let config = ProxyClientConfig {
    proxy_url: "ws://localhost:8080".to_string(),
    identity_keypair: None,
};
let mut client = ProxyProtocolClient::new(config);

// Connect and get incoming message channel
let mut incoming = client.connect().await?;

// Handle messages
while let Some(msg) = incoming.recv().await {
    println!("Received: {:?}", msg);
}
Source

pub async fn send_to( &self, destination: IdentityFingerprint, payload: Vec<u8>, ) -> Result<(), ProxyError>

Send a message to another authenticated client.

The message is routed through the proxy server to the destination client. The proxy validates the source identity but cannot inspect the payload.

§Authentication Required

This method requires an active authenticated connection. Call connect() first.

§Payload Encryption

The proxy does not encrypt message payloads. Clients should implement end-to-end encryption (e.g., using the Noise protocol) before calling this method.

§Errors
§Examples
use ap_proxy_client::{ProxyClientConfig, ProxyProtocolClient};

let mut client = ProxyProtocolClient::new(config);
client.connect().await?;

// Get destination fingerprint from rendezvous lookup
// let destination = ...; // from IncomingMessage::IdentityInfo

// Send encrypted message
// let payload = encrypt_message(b"Hello!")?;
// client.send_to(destination, payload).await?;
Source

pub async fn request_rendezvous(&self) -> Result<(), ProxyError>

Request a rendezvous code from the server.

The server will generate a temporary code (format: “ABC-DEF-GHI”) that maps to your identity. The code will be delivered via IncomingMessage::RendezvousInfo on the channel returned by connect().

§Rendezvous Code Properties
  • Format: 9 alphanumeric characters (e.g., “ABC-DEF-GHI”)
  • Lifetime: 5 minutes
  • Single-use: deleted after lookup
  • Enables peer discovery without sharing long-lived identifiers
§Usage Pattern
  1. Call this method to request a code
  2. Receive the code via IncomingMessage::RendezvousInfo
  3. Share the code with a peer (e.g., display as QR code)
  4. Peer uses request_identity() to look up your identity
§Authentication Required

This method requires an active authenticated connection.

§Errors
§Examples
use ap_proxy_client::{ProxyClientConfig, ProxyProtocolClient, IncomingMessage};

let mut client = ProxyProtocolClient::new(config);
let mut incoming = client.connect().await?;

// Request a code
client.request_rendezvous().await?;

// Wait for response
if let Some(IncomingMessage::RendezvousInfo(code)) = incoming.recv().await {
    println!("Share this code: {}", code.as_str());
    // Display as QR code, send via messaging, etc.
}
Source

pub async fn request_identity( &self, rendezvous_code: RendezvousCode, ) -> Result<(), ProxyError>

Look up a peer’s identity using a rendezvous code.

Queries the server for the identity associated with a rendezvous code. If the code is valid and hasn’t expired, the server responds with IncomingMessage::IdentityInfo containing the peer’s identity and fingerprint.

§Code Consumption

Rendezvous codes are single-use. After successful lookup, the server deletes the code and it cannot be used again.

§Authentication Required

This method requires an active authenticated connection.

§Errors

The server may not respond if the code is invalid, expired, or already used. Implement a timeout when waiting for the response.

§Examples
use ap_proxy_client::{ProxyClientConfig, ProxyProtocolClient, IncomingMessage, RendezvousCode};

let mut client = ProxyProtocolClient::new(config);
let mut incoming = client.connect().await?;

// Get code from user (e.g., QR scan, text input)
let code = RendezvousCode::from_string("ABC-DEF-GHI".to_string());

// Look up the identity
client.request_identity(code).await?;

// Wait for response with timeout
match tokio::time::timeout(
    tokio::time::Duration::from_secs(5),
    incoming.recv()
).await {
    Ok(Some(IncomingMessage::IdentityInfo { fingerprint, identity })) => {
        println!("Found peer: {:?}", fingerprint);
        // Now you can send messages to this peer
        // client.send_to(fingerprint, payload).await?;
    }
    _ => {
        println!("Code not found or expired");
    }
}
Source

pub fn fingerprint(&self) -> IdentityFingerprint

Get this client’s identity fingerprint.

Returns the SHA256 fingerprint of the client’s public key. This is the identifier that other clients use to send messages to this client.

The fingerprint is available immediately after creating the client, before connecting.

§Examples
use ap_proxy_client::{ProxyClientConfig, ProxyProtocolClient};

let config = ProxyClientConfig {
    proxy_url: "ws://localhost:8080".to_string(),
    identity_keypair: None,
};
let client = ProxyProtocolClient::new(config);
println!("My fingerprint: {:?}", client.fingerprint());
Source

pub async fn is_authenticated(&self) -> bool

Check if the client is currently authenticated.

Returns true if the client has completed authentication and can send/receive messages. Returns false if disconnected or still connecting.

§Examples
use ap_proxy_client::{ProxyClientConfig, ProxyProtocolClient};

let mut client = ProxyProtocolClient::new(config);

assert!(!client.is_authenticated().await);

client.connect().await?;
assert!(client.is_authenticated().await);

client.disconnect().await?;
assert!(!client.is_authenticated().await);
Source

pub async fn disconnect(&mut self) -> Result<(), ProxyError>

Disconnect from the proxy server and clean up resources.

Aborts background tasks, closes the WebSocket connection, and resets state. After disconnecting, you can call connect() again to reconnect.

This method is automatically called when the client is dropped, but calling it explicitly allows you to handle errors.

§Examples
use ap_proxy_client::{ProxyClientConfig, ProxyProtocolClient};

let mut client = ProxyProtocolClient::new(config);
client.connect().await?;

// Do work...

// Clean disconnect
client.disconnect().await?;

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more