refactor(pubky): move pkarr logic to shared

This commit is contained in:
nazeh
2024-07-28 19:40:02 +03:00
parent 3cfd876808
commit e0b58451b5
5 changed files with 116 additions and 219 deletions

View File

@@ -20,5 +20,5 @@ pub use error::Error;
pub struct PubkyClient {
http: reqwest::Client,
#[cfg(not(target_arch = "wasm32"))]
pkarr: PkarrClientAsync,
pub(crate) pkarr: PkarrClientAsync,
}

View File

@@ -33,7 +33,7 @@ impl PubkyClient {
/// Check the current sesison for a given Pubky in its homeserver.
///
/// Returns an [Error::NotSignedIn] if so, or [ureq::Error] if
/// Returns an [Error::NotSignedIn] if so, or [reqwest::Error] if
/// the response has any other `>=400` status code.
pub async fn session(&self, pubky: &PublicKey) -> Result<Session> {
let (homeserver, mut url) = self.resolve_pubky_homeserver(pubky).await?;

View File

@@ -5,75 +5,21 @@ use pkarr::{
Keypair, PublicKey, SignedPacket,
};
use crate::shared::pkarr::{format_url, parse_pubky_svcb, prepare_packet_for_signup};
use crate::{
error::{Error, Result},
PubkyClient,
};
impl PubkyClient {
/// Publish the SVCB record for `_pubky.<public_key>`.
pub(crate) async fn publish_pubky_homeserver(
pub(crate) async fn pkarr_resolve(
&self,
keypair: &Keypair,
host: &str,
) -> Result<()> {
let existing = self.pkarr.resolve(&keypair.public_key()).await?;
let signed_packet = prepare_packet_for_signup(keypair, host, existing)?;
self.pkarr.publish(&signed_packet).await?;
Ok(())
public_key: &PublicKey,
) -> Result<Option<SignedPacket>> {
Ok(self.pkarr.resolve(public_key).await?)
}
/// Resolve the homeserver for a pubky.
pub(crate) async fn resolve_pubky_homeserver(
&self,
pubky: &PublicKey,
) -> Result<(PublicKey, Url)> {
let target = format!("_pubky.{}", pubky);
self.resolve_endpoint(&target)
.await
.map_err(|_| Error::Generic("Could not resolve homeserver".to_string()))
}
/// Resolve a service's public_key and clearnet url from a Pubky domain
pub(crate) async fn resolve_endpoint(&self, target: &str) -> Result<(PublicKey, Url)> {
let original_target = target;
// TODO: cache the result of this function?
let mut target = target.to_string();
let mut homeserver_public_key = None;
let mut host = target.clone();
let mut step = 0;
// PublicKey is very good at extracting the Pkarr TLD from a string.
while let Ok(public_key) = PublicKey::try_from(target.clone()) {
let response = self
.pkarr
.resolve(&public_key)
.await
.map_err(|e| Error::ResolveEndpoint(original_target.into()))?;
let done = parse_pubky_svcb(
response,
&public_key,
&mut target,
&mut homeserver_public_key,
&mut host,
&mut step,
);
if done {
break;
}
}
format_url(original_target, homeserver_public_key, host)
pub(crate) async fn pkarr_publish(&self, signed_packet: &SignedPacket) -> Result<()> {
Ok(self.pkarr.publish(signed_packet).await?)
}
}
@@ -88,15 +34,6 @@ mod tests {
};
use pubky_homeserver::Homeserver;
#[tokio::test]
async fn resolve_endpoint() {
let target = "oc9tdmh8c4pmy3tk946oqqfkhic18xdiytadspiy55qhbnja5w9o";
let client = PubkyClient::new();
client.resolve_endpoint(target).await.unwrap();
}
#[tokio::test]
async fn resolve_homeserver() {
let testnet = Testnet::new(3);

View File

@@ -5,101 +5,130 @@ use pkarr::{
Keypair, PublicKey, SignedPacket,
};
use crate::error::{Error, Result};
use crate::{
error::{Error, Result},
PubkyClient,
};
const MAX_RECURSIVE_PUBKY_HOMESERVER_RESOLUTION: u8 = 3;
pub fn prepare_packet_for_signup(
keypair: &Keypair,
host: &str,
existing: Option<SignedPacket>,
) -> Result<SignedPacket> {
let mut packet = Packet::new_reply(0);
impl PubkyClient {
/// Publish the SVCB record for `_pubky.<public_key>`.
pub(crate) async fn publish_pubky_homeserver(
&self,
keypair: &Keypair,
host: &str,
) -> Result<()> {
let existing = self.pkarr_resolve(&keypair.public_key()).await?;
if let Some(existing) = existing {
for answer in existing.packet().answers.iter().cloned() {
if !answer.name.to_string().starts_with("_pubky") {
packet.answers.push(answer.into_owned())
let mut packet = Packet::new_reply(0);
if let Some(existing) = existing {
for answer in existing.packet().answers.iter().cloned() {
if !answer.name.to_string().starts_with("_pubky") {
packet.answers.push(answer.into_owned())
}
}
}
let svcb = SVCB::new(0, host.try_into()?);
packet.answers.push(pkarr::dns::ResourceRecord::new(
"_pubky".try_into().unwrap(),
pkarr::dns::CLASS::IN,
60 * 60,
pkarr::dns::rdata::RData::SVCB(svcb),
));
let signed_packet = SignedPacket::from_packet(keypair, &packet)?;
self.pkarr_publish(&signed_packet).await?;
Ok(())
}
let svcb = SVCB::new(0, host.try_into()?);
/// Resolve the homeserver for a pubky.
pub(crate) async fn resolve_pubky_homeserver(
&self,
pubky: &PublicKey,
) -> Result<(PublicKey, Url)> {
let target = format!("_pubky.{}", pubky);
packet.answers.push(pkarr::dns::ResourceRecord::new(
"_pubky".try_into().unwrap(),
pkarr::dns::CLASS::IN,
60 * 60,
pkarr::dns::rdata::RData::SVCB(svcb),
));
self.resolve_endpoint(&target)
.await
.map_err(|_| Error::Generic("Could not resolve homeserver".to_string()))
}
Ok(SignedPacket::from_packet(keypair, &packet)?)
}
/// Resolve a service's public_key and clearnet url from a Pubky domain
pub(crate) async fn resolve_endpoint(&self, target: &str) -> Result<(PublicKey, Url)> {
let original_target = target;
// TODO: cache the result of this function?
pub fn parse_pubky_svcb(
signed_packet: Option<SignedPacket>,
public_key: &PublicKey,
target: &mut String,
homeserver_public_key: &mut Option<PublicKey>,
host: &mut String,
step: &mut u8,
) -> bool {
*step += 1;
let mut target = target.to_string();
let mut homeserver_public_key = None;
let mut host = target.clone();
let mut prior = None;
let mut step = 0;
if let Some(signed_packet) = signed_packet {
for answer in signed_packet.resource_records(target) {
if let pkarr::dns::rdata::RData::SVCB(svcb) = &answer.rdata {
if svcb.priority == 0 {
prior = Some(svcb)
} else if let Some(sofar) = prior {
if svcb.priority >= sofar.priority {
prior = Some(svcb)
// PublicKey is very good at extracting the Pkarr TLD from a string.
while let Ok(public_key) = PublicKey::try_from(target.clone()) {
step += 1;
let response = self
.pkarr_resolve(&public_key)
.await
.map_err(|e| Error::ResolveEndpoint(original_target.into()))?;
let mut prior = None;
if let Some(signed_packet) = response {
for answer in signed_packet.resource_records(&target) {
if let pkarr::dns::rdata::RData::SVCB(svcb) = &answer.rdata {
if svcb.priority == 0 {
prior = Some(svcb)
} else if let Some(sofar) = prior {
if svcb.priority >= sofar.priority {
prior = Some(svcb)
}
// TODO return random if priority is the same
} else {
prior = Some(svcb)
}
}
// TODO return random if priority is the same
} else {
prior = Some(svcb)
}
if let Some(svcb) = prior {
homeserver_public_key = Some(public_key.clone());
target = svcb.target.to_string();
if let Some(port) = svcb.get_param(pkarr::dns::rdata::SVCB::PORT) {
if port.len() < 2 {
// TODO: debug! Error encoding port!
}
let port = u16::from_be_bytes([port[0], port[1]]);
host = format!("{target}:{port}");
} else {
host.clone_from(&target);
};
if step >= MAX_RECURSIVE_PUBKY_HOMESERVER_RESOLUTION {
continue;
};
}
}
}
if let Some(svcb) = prior {
*homeserver_public_key = Some(public_key.clone());
*target = svcb.target.to_string();
if let Some(port) = svcb.get_param(pkarr::dns::rdata::SVCB::PORT) {
if port.len() < 2 {
// TODO: debug! Error encoding port!
}
let port = u16::from_be_bytes([port[0], port[1]]);
*host = format!("{target}:{port}");
if let Some(homeserver) = homeserver_public_key {
let url = if host.starts_with("localhost") {
format!("http://{host}")
} else {
host.clone_from(target);
format!("https://{host}")
};
return *step >= MAX_RECURSIVE_PUBKY_HOMESERVER_RESOLUTION;
return Ok((homeserver, Url::parse(&url)?));
}
Err(Error::ResolveEndpoint(original_target.into()))
}
true
}
pub fn format_url(
original_target: &str,
homeserver_public_key: Option<PublicKey>,
host: String,
) -> Result<(PublicKey, Url)> {
if let Some(homeserver) = homeserver_public_key {
let url = if host.starts_with("localhost") {
format!("http://{host}")
} else {
format!("https://{host}")
};
return Ok((homeserver, Url::parse(&url)?));
}
Err(Error::ResolveEndpoint(original_target.into()))
}

View File

@@ -8,86 +8,17 @@ pub use pkarr::{
};
use crate::error::{Error, Result};
use crate::shared::pkarr::{format_url, parse_pubky_svcb, prepare_packet_for_signup};
use crate::PubkyClient;
const TEST_RELAY: &str = "http://localhost:15411/pkarr";
#[macro_export]
macro_rules! log {
($($arg:expr),*) => {
web_sys::console::debug_1(&format!($($arg),*).into());
};
}
impl PubkyClient {
/// Publish the SVCB record for `_pubky.<public_key>`.
pub(crate) async fn publish_pubky_homeserver(
&self,
keypair: &Keypair,
host: &str,
) -> Result<()> {
// let existing = self.pkarr.resolve(&keypair.public_key()).await?;
let existing = self.pkarr_resolve(&keypair.public_key()).await?;
let signed_packet = prepare_packet_for_signup(keypair, host, existing)?;
// self.pkarr.publish(&signed_packet).await?;
self.pkarr_publish(&signed_packet).await?;
Ok(())
}
/// Resolve the homeserver for a pubky.
pub(crate) async fn resolve_pubky_homeserver(
&self,
pubky: &PublicKey,
) -> Result<(PublicKey, Url)> {
let target = format!("_pubky.{}", pubky);
self.resolve_endpoint(&target)
.await
.map_err(|_| Error::Generic("Could not resolve homeserver".to_string()))
}
/// Resolve a service's public_key and clearnet url from a Pubky domain
pub(crate) async fn resolve_endpoint(&self, target: &str) -> Result<(PublicKey, Url)> {
let original_target = target;
// TODO: cache the result of this function?
let mut target = target.to_string();
let mut homeserver_public_key = None;
let mut host = target.clone();
let mut step = 0;
// PublicKey is very good at extracting the Pkarr TLD from a string.
while let Ok(public_key) = PublicKey::try_from(target.clone()) {
let response = self
.pkarr_resolve(&public_key)
.await
.map_err(|e| Error::ResolveEndpoint(original_target.into()))?;
let done = parse_pubky_svcb(
response,
&public_key,
&mut target,
&mut homeserver_public_key,
&mut host,
&mut step,
);
if done {
break;
}
}
format_url(original_target, homeserver_public_key, host)
}
//TODO: Allow multiple relays in parallel
//TODO: migrate to pkarr::PkarrRelayClient
async fn pkarr_resolve(&self, public_key: &PublicKey) -> Result<Option<SignedPacket>> {
pub(crate) async fn pkarr_resolve(
&self,
public_key: &PublicKey,
) -> Result<Option<SignedPacket>> {
let res = self
.http
.get(format!("{TEST_RELAY}/{}", public_key))
@@ -106,7 +37,7 @@ impl PubkyClient {
Ok(Some(existing))
}
async fn pkarr_publish(&self, signed_packet: &SignedPacket) -> Result<()> {
pub(crate) async fn pkarr_publish(&self, signed_packet: &SignedPacket) -> Result<()> {
self.http
.put(format!("{TEST_RELAY}/{}", signed_packet.public_key()))
.body(signed_packet.to_relay_payload())