WIP initial commit with working greeting and login calls
This commit is contained in:
commit
f503132d85
|
@ -0,0 +1,3 @@
|
||||||
|
/target
|
||||||
|
/misc
|
||||||
|
Cargo.lock
|
|
@ -0,0 +1,20 @@
|
||||||
|
[package]
|
||||||
|
name = "epp-client"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
confy = "0.4"
|
||||||
|
futures = "0.3"
|
||||||
|
lazy_static = "1.4"
|
||||||
|
quick-xml = { version = "0.22", features = [ "serialize" ] }
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
tokio = { version = "1.0", features = [ "full" ] }
|
||||||
|
tokio-rustls = "0.22"
|
||||||
|
webpki = "0.22"
|
||||||
|
webpki-roots = "0.21"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tokio-test = "*"
|
|
@ -0,0 +1,47 @@
|
||||||
|
use confy;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::default;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref CONFIG: EppClientConfig = match confy::load("epp-client") {
|
||||||
|
Ok(cfg) => cfg,
|
||||||
|
Err(e) => panic!("Config read error: {}", e),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct EppClientConnection {
|
||||||
|
host: String,
|
||||||
|
port: u16,
|
||||||
|
username: String,
|
||||||
|
password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct EppClientConfig {
|
||||||
|
pub servers: HashMap<String, EppClientConnection>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl default::Default for EppClientConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
let servers: HashMap<String, EppClientConnection> = HashMap::new();
|
||||||
|
Self { servers: servers }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EppClientConnection {
|
||||||
|
pub fn connection_details(&self) -> (String, u16) {
|
||||||
|
(self.host.to_string(), self.port)
|
||||||
|
}
|
||||||
|
pub fn credentials(&self) -> (String, String) {
|
||||||
|
(self.username.to_string(), self.password.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EppClientConfig {
|
||||||
|
pub fn registry(&self, registry: &str) -> Option<&EppClientConnection> {
|
||||||
|
self.servers.get(registry)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,185 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::mpsc;
|
||||||
|
use std::{str, u32};
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use futures::executor::block_on;
|
||||||
|
use std::{error::Error, net::ToSocketAddrs, io as stdio};
|
||||||
|
use tokio_rustls::{TlsConnector, rustls::ClientConfig, webpki::DNSNameRef, client::TlsStream};
|
||||||
|
use tokio::{net::TcpStream, io::AsyncWriteExt, io::AsyncReadExt};
|
||||||
|
|
||||||
|
use crate::config::{CONFIG, EppClientConnection};
|
||||||
|
use crate::error;
|
||||||
|
use crate::epp::request::EppRequest;
|
||||||
|
|
||||||
|
struct EppConnection {
|
||||||
|
registry: String,
|
||||||
|
credentials: (String, String),
|
||||||
|
stream: TlsStream<TcpStream>,
|
||||||
|
pub greeting: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct EppClient {
|
||||||
|
connection: EppConnection,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EppConnection {
|
||||||
|
pub async fn new(
|
||||||
|
registry: String,
|
||||||
|
credentials: (String, String),
|
||||||
|
mut stream: TlsStream<TcpStream>) -> Result<EppConnection, Box<dyn Error>> {
|
||||||
|
let mut buf = vec![0u8; 4096];
|
||||||
|
stream.read(&mut buf).await?;
|
||||||
|
let greeting = str::from_utf8(&buf)?.to_string();
|
||||||
|
|
||||||
|
Ok(EppConnection {
|
||||||
|
registry: registry,
|
||||||
|
credentials: credentials,
|
||||||
|
stream: stream,
|
||||||
|
greeting: greeting
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn write(&mut self, buf: &Vec<u8>) -> Result<(), Box<dyn Error>> {
|
||||||
|
self.stream.write_all(buf).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send_epp_request(&mut self, content: &str) -> Result<(), Box<dyn Error>> {
|
||||||
|
let len = content.len();
|
||||||
|
|
||||||
|
let buf_size = len + 4;
|
||||||
|
let mut buf: Vec<u8> = vec![0u8; buf_size];
|
||||||
|
|
||||||
|
let len_u32: [u8; 4] = u32::to_be_bytes(len.try_into()?);
|
||||||
|
|
||||||
|
buf[..4].clone_from_slice(&len_u32);
|
||||||
|
buf[4..].clone_from_slice(&content.as_bytes());
|
||||||
|
|
||||||
|
self.write(&buf).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read(&mut self) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||||
|
let mut buf = vec![0u8; 4096];
|
||||||
|
self.stream.read(&mut buf).await?;
|
||||||
|
Ok(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_epp_response(&mut self) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||||
|
let mut buf = [0u8; 4];
|
||||||
|
self.stream.read_exact(&mut buf).await?;
|
||||||
|
|
||||||
|
let buf_size :usize = u32::from_be_bytes(buf).try_into()?;
|
||||||
|
|
||||||
|
println!("Response buffer size: {}", buf_size);
|
||||||
|
|
||||||
|
let mut buf = vec![0u8; buf_size - 4];
|
||||||
|
|
||||||
|
self.stream.read(&mut buf).await?;
|
||||||
|
|
||||||
|
Ok(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_epp_response(&mut self) -> Result<String, Box<dyn Error>> {
|
||||||
|
let contents = self.read().await?;
|
||||||
|
|
||||||
|
let response = str::from_utf8(&contents)?.to_string();
|
||||||
|
|
||||||
|
Ok(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn transact(&mut self, content: &str) -> Result<String, Box<dyn Error>> {
|
||||||
|
let content = format!("{}\r\n\r\n", content);
|
||||||
|
|
||||||
|
self.send_epp_request(&content).await?;
|
||||||
|
self.get_epp_response().await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn close(&mut self) -> Result<(), Box<dyn Error>> {
|
||||||
|
println!("Closing ...");
|
||||||
|
|
||||||
|
self.stream.shutdown().await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for EppConnection {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
block_on(self.close());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EppClient {
|
||||||
|
pub async fn transact(&mut self, request: &EppRequest) -> Result<String, Box<dyn Error>> {
|
||||||
|
let epp_xml = request.to_epp_xml()?;
|
||||||
|
|
||||||
|
println!("Request:\r\n{}", epp_xml);
|
||||||
|
|
||||||
|
let response = self.connection.transact(&epp_xml).await?;
|
||||||
|
println!("Response:\r\n{}", response);
|
||||||
|
|
||||||
|
Ok(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn transact_xml(&mut self, xml: &str) -> Result<String, Box<dyn Error>> {
|
||||||
|
self.connection.transact(&xml).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn greeting(&self) -> String {
|
||||||
|
return String::from(&self.connection.greeting)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn epp_connect(registry_creds: &EppClientConnection) -> Result<TlsStream<TcpStream>, error::Error> {
|
||||||
|
let (host, port) = registry_creds.connection_details();
|
||||||
|
|
||||||
|
println!("{}: {}", host, port);
|
||||||
|
|
||||||
|
let addr = (host.as_str(), port)
|
||||||
|
.to_socket_addrs()?
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| stdio::ErrorKind::NotFound)?;
|
||||||
|
|
||||||
|
let mut config = ClientConfig::new();
|
||||||
|
|
||||||
|
config
|
||||||
|
.root_store
|
||||||
|
.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
|
||||||
|
|
||||||
|
let connector = TlsConnector::from(Arc::new(config));
|
||||||
|
let stream = TcpStream::connect(&addr).await?;
|
||||||
|
|
||||||
|
let domain = DNSNameRef::try_from_ascii_str(&host)
|
||||||
|
.map_err(|_| stdio::Error::new(stdio::ErrorKind::InvalidInput, format!("Invalid domain: {}", host)))?;
|
||||||
|
|
||||||
|
let stream = connector.connect(domain, stream).await?;
|
||||||
|
|
||||||
|
Ok(stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn connect(registry: &'static str) -> Result<EppClient, Box<dyn Error>> {
|
||||||
|
let registry_creds = match CONFIG.registry(registry) {
|
||||||
|
Some(creds) => creds,
|
||||||
|
None => return Err(format!("missing credentials for {}", registry).into())
|
||||||
|
};
|
||||||
|
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let stream = epp_connect(®istry_creds).await.unwrap();
|
||||||
|
let credentials = registry_creds.credentials();
|
||||||
|
|
||||||
|
let connection = EppConnection::new(
|
||||||
|
registry.to_string(),
|
||||||
|
credentials,
|
||||||
|
stream
|
||||||
|
).await.unwrap();
|
||||||
|
|
||||||
|
let client = EppClient { connection: connection };
|
||||||
|
|
||||||
|
tx.send(client).unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
let client = rx.recv()?;
|
||||||
|
|
||||||
|
Ok(client)
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod objects;
|
||||||
|
pub mod request;
|
|
@ -0,0 +1,169 @@
|
||||||
|
use quick_xml::se;
|
||||||
|
use serde::{Deserialize, Serialize, Serializer};
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
const EPP_XML_HEADER: &str = r#"<?xml version="1.0" encoding="UTF-8" standalone="no"?>"#;
|
||||||
|
const EPP_XMLNS: &str = "urn:ietf:params:xml:ns:epp-1.0";
|
||||||
|
const EPP_XMLNS_XSI: &str = "http://www.w3.org/2001/XMLSchema-instance";
|
||||||
|
const EPP_XSI_SCHEMA_LOCATION: &str = "urn:ietf:params:xml:ns:epp-1.0 epp-1.0.xsd";
|
||||||
|
|
||||||
|
const EPP_VERSION: &str = "1.0";
|
||||||
|
const EPP_LANG: &str = "en";
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||||
|
pub struct StringValue(String);
|
||||||
|
|
||||||
|
impl Default for StringValue {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(String::from(""))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait StringValueTrait {
|
||||||
|
fn to_string_value(&self) -> StringValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StringValueTrait for &str {
|
||||||
|
fn to_string_value(&self) -> StringValue {
|
||||||
|
StringValue(self.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub enum RequestType {
|
||||||
|
Hello,
|
||||||
|
Command {
|
||||||
|
login: Login,
|
||||||
|
#[serde(rename = "clTRIDv")]
|
||||||
|
client_tr_id: StringValue,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||||
|
#[serde(rename = "epp")]
|
||||||
|
pub struct EppObject {
|
||||||
|
xmlns: String,
|
||||||
|
#[serde(rename = "xmlns:xsi")]
|
||||||
|
xmlns_xsi: String,
|
||||||
|
#[serde(rename = "xsi:schemaLocation")]
|
||||||
|
xsi_schema_location: String,
|
||||||
|
data: RequestType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EppObject {
|
||||||
|
pub fn new(data: RequestType) -> EppObject {
|
||||||
|
EppObject {
|
||||||
|
data: data,
|
||||||
|
xmlns: EPP_XMLNS.to_string(),
|
||||||
|
xmlns_xsi: EPP_XMLNS_XSI.to_string(),
|
||||||
|
xsi_schema_location: EPP_XSI_SCHEMA_LOCATION.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_epp_xml(&self) -> Result<String, Box<dyn Error>> {
|
||||||
|
let epp_xml = format!("{}\r\n{}", EPP_XML_HEADER, se::to_string(self)?);
|
||||||
|
|
||||||
|
Ok(epp_xml)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type EppRequest = EppObject;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub struct Hello;
|
||||||
|
|
||||||
|
impl Hello {
|
||||||
|
pub fn new() -> EppRequest {
|
||||||
|
EppRequest::new(RequestType::Hello)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||||
|
#[serde(rename = "options")]
|
||||||
|
pub struct LoginOptions {
|
||||||
|
version: StringValue,
|
||||||
|
lang: StringValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LoginOptions {
|
||||||
|
pub fn build(version: &str, lang: &str) -> LoginOptions {
|
||||||
|
LoginOptions {
|
||||||
|
version: version.to_string_value(),
|
||||||
|
lang: lang.to_string_value(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||||
|
#[serde(rename = "svcExtension")]
|
||||||
|
pub struct ServiceExtension {
|
||||||
|
#[serde(rename = "extURI")]
|
||||||
|
ext_uris: Option<Vec<StringValue>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||||
|
pub struct Services {
|
||||||
|
#[serde(rename = "objURI")]
|
||||||
|
obj_uris: Vec<StringValue>,
|
||||||
|
#[serde(rename = "svcExtension")]
|
||||||
|
svc_ext: Option<ServiceExtension>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||||
|
pub struct Command {
|
||||||
|
login: Login,
|
||||||
|
#[serde(rename = "clTRID")]
|
||||||
|
client_tr_id: StringValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||||
|
#[serde(rename = "login")]
|
||||||
|
pub struct Login {
|
||||||
|
#[serde(rename(serialize = "clID", deserialize = "clID"))]
|
||||||
|
username: StringValue,
|
||||||
|
#[serde(rename = "pw", default)]
|
||||||
|
password: StringValue,
|
||||||
|
options: LoginOptions,
|
||||||
|
#[serde(rename = "svcs")]
|
||||||
|
services: Services,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Login {
|
||||||
|
pub fn new(username: &str, password: &str, client_tr_id: &str) -> EppRequest {
|
||||||
|
let login = Login {
|
||||||
|
username: username.to_string_value(),
|
||||||
|
password: password.to_string_value(),
|
||||||
|
options: LoginOptions {
|
||||||
|
version: EPP_VERSION.to_string_value(),
|
||||||
|
lang: EPP_LANG.to_string_value(),
|
||||||
|
},
|
||||||
|
services: Services {
|
||||||
|
obj_uris: vec![
|
||||||
|
"urn:ietf:params:xml:ns:host-1.0".to_string_value(),
|
||||||
|
"urn:ietf:params:xml:ns:contact-1.0".to_string_value(),
|
||||||
|
"urn:ietf:params:xml:ns:domain-1.0".to_string_value(),
|
||||||
|
],
|
||||||
|
svc_ext: Some(ServiceExtension {
|
||||||
|
ext_uris: Some(vec![
|
||||||
|
"http://schema.ispapi.net/epp/xml/keyvalue-1.0".to_string_value()
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
EppRequest::new(RequestType::Command {
|
||||||
|
login: login,
|
||||||
|
client_tr_id: client_tr_id.to_string_value(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_options(&mut self, options: LoginOptions) {
|
||||||
|
self.options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_services(&mut self, services: Services) {
|
||||||
|
self.services = services;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
pub trait EppXml {
|
||||||
|
fn serialize(&self) -> Result<String, Box<dyn Error>>;
|
||||||
|
fn deserialize(&self) -> Result<Self, Box<dyn Error>>;
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
EppConnectionError(std::io::Error),
|
||||||
|
Other(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for Error {}
|
||||||
|
|
||||||
|
impl Display for Error {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "epp-client Exception: {:?}", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::io::Error> for Error {
|
||||||
|
fn from(e: std::io::Error) -> Self {
|
||||||
|
Self::EppConnectionError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::io::ErrorKind> for Error {
|
||||||
|
fn from(e: std::io::ErrorKind) -> Self {
|
||||||
|
Self::EppConnectionError(std::io::Error::from(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for Error {
|
||||||
|
fn from(e: String) -> Self {
|
||||||
|
Self::Other(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// impl From<std::io::Error> for Box<EppClientError> {
|
||||||
|
// fn from(e: std::io::Error) -> Self {
|
||||||
|
|
||||||
|
// }
|
||||||
|
// }
|
|
@ -0,0 +1,35 @@
|
||||||
|
// pub mod config;
|
||||||
|
// pub mod connection;
|
||||||
|
|
||||||
|
// #[cfg(test)]
|
||||||
|
// mod tests {
|
||||||
|
// use super::config;
|
||||||
|
// use super::connection;
|
||||||
|
// use std::str;
|
||||||
|
|
||||||
|
// #[test]
|
||||||
|
// fn config() {
|
||||||
|
// let servers = &config::CONFIG.servers;
|
||||||
|
|
||||||
|
// ()
|
||||||
|
// }
|
||||||
|
|
||||||
|
// macro_rules! aw {
|
||||||
|
// ($e:expr) => {
|
||||||
|
// tokio_test::block_on($e)
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[test]
|
||||||
|
// fn connect() {
|
||||||
|
// let mut cn = aw!(connection::connect("hexonet")).unwrap();
|
||||||
|
// println!("lol");
|
||||||
|
// let contents = aw!(cn.read()).unwrap();
|
||||||
|
|
||||||
|
// match str::from_utf8(&contents) {
|
||||||
|
// Ok(v) => println!("{}", v),
|
||||||
|
// Err(e) => panic!("Error: {}", e)
|
||||||
|
// }
|
||||||
|
// aw!(cn.close());
|
||||||
|
// }
|
||||||
|
// }
|
|
@ -0,0 +1,34 @@
|
||||||
|
pub mod config;
|
||||||
|
pub mod connection;
|
||||||
|
pub mod epp;
|
||||||
|
pub mod error;
|
||||||
|
|
||||||
|
use std::time::SystemTime;
|
||||||
|
use crate::{epp::request};
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
let mut client = match connection::connect("hexonet").await {
|
||||||
|
Ok(client) => {
|
||||||
|
println!("{}", client.greeting());
|
||||||
|
client
|
||||||
|
},
|
||||||
|
Err(e) => panic!("Error: {}", e)
|
||||||
|
};
|
||||||
|
|
||||||
|
let epp_hello = request::Hello::new();
|
||||||
|
|
||||||
|
client.transact(&epp_hello).await.unwrap();
|
||||||
|
|
||||||
|
let timestamp = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap();
|
||||||
|
let cl_trid = format!("eppdev:{}", timestamp.as_secs());
|
||||||
|
let epp_login = request::Login::new("eppdev", "sh48sja#27*A", &cl_trid);
|
||||||
|
|
||||||
|
// let response = epp_login.to_epp_xml().unwrap();
|
||||||
|
|
||||||
|
client.transact(&epp_login).await.unwrap();
|
||||||
|
|
||||||
|
//let response = client.transact(&epp_hello).await.unwrap();
|
||||||
|
|
||||||
|
//println!("{}", response);
|
||||||
|
}
|
Loading…
Reference in New Issue