ocpp_client/
connect.rs

1use base64::Engine;
2use base64::prelude::BASE64_STANDARD;
3use tokio::net::{TcpStream};
4use tokio_tungstenite::{client_async_tls, MaybeTlsStream, WebSocketStream};
5use tokio_tungstenite::tungstenite::client::IntoClientRequest;
6use tokio_tungstenite::tungstenite::http::{Request};
7use tokio_tungstenite::tungstenite::http::header::{AUTHORIZATION, SEC_WEBSOCKET_PROTOCOL};
8use url::Url;
9use crate::client::Client;
10#[cfg(feature = "ocpp_1_6")]
11use crate::ocpp_1_6::OCPP1_6Client;
12
13#[cfg(feature = "ocpp_2_0_1")]
14use crate::ocpp_2_0_1::OCPP2_0_1Client;
15
16/// Connect to an OCPP server using the best OCPP version available.
17pub async fn connect(address: &str, options: Option<ConnectOptions<'_>>) -> Result<Client, Box<dyn std::error::Error + Send + Sync>> {
18    let (stream, protocol) = setup_socket(address, "ocpp1.6, ocpp2.0.1", options).await?;
19
20    match protocol.as_str() {
21        #[cfg(feature = "ocpp_1_6")]
22        "ocpp1.6" => {
23            Ok(Client::OCPP1_6(OCPP1_6Client::new(stream)))
24        }
25        #[cfg(feature = "ocpp_2_0_1")]
26        "ocpp2.0.1" => {
27            Ok(Client::OCPP2_0_1(OCPP2_0_1Client::new(stream)))
28        }
29        _ => {
30            Err("The CSMS server has selected a protocol that we don't support".into())
31        }
32    }
33}
34
35/// Connect to an OCPP 1.6 server.
36#[cfg(feature = "ocpp_1_6")]
37pub async fn connect_1_6(address: &str, options: Option<ConnectOptions<'_>>) -> Result<OCPP1_6Client, Box<dyn std::error::Error + Send + Sync>> {
38    let (stream, _) = setup_socket(address, "ocpp1.6", options).await?;
39    Ok(OCPP1_6Client::new(stream))
40}
41
42/// Connect to an OCPP 2.0.1 server.
43pub async fn connect_2_0_1(address: &str, options: Option<ConnectOptions<'_>>) -> Result<OCPP2_0_1Client, Box<dyn std::error::Error + Send + Sync>> {
44    let (stream, _) = setup_socket(address, "ocpp2.0.1", options).await?;
45    Ok(OCPP2_0_1Client::new(stream))
46}
47
48async fn setup_socket(address: &str, protocols: &str, options: Option<ConnectOptions<'_>>) -> Result<(WebSocketStream<MaybeTlsStream<TcpStream>>, String), Box<dyn std::error::Error + Send + Sync>>{
49    let address = Url::parse(address)?;
50
51    let socket_addrs = address.socket_addrs(|| None)?;
52    let stream = TcpStream::connect(&*socket_addrs).await?;
53
54    let mut request: Request<()> = address.to_string().into_client_request()?;
55    request.headers_mut().insert(SEC_WEBSOCKET_PROTOCOL, protocols.parse()?);
56    if let Some(options) = options {
57        if let Some(username) = options.username {
58            let data = format!("{}:{}", username, options.password.unwrap_or(""));
59            let encoded = BASE64_STANDARD.encode(data);
60            request.headers_mut().insert(AUTHORIZATION, format!("Basic {}", encoded).parse()?);
61        }
62    }
63
64    let (stream, response) = client_async_tls(request, stream).await?;
65
66    let protocol = response.headers().get(SEC_WEBSOCKET_PROTOCOL).ok_or("No OCPP protocol negotiated")?;
67
68    Ok((stream, protocol.to_str()?.to_string()))
69}
70
71#[derive(Debug, Clone)]
72pub struct ConnectOptions<'a> {
73    pub username: Option<&'a str>,
74    pub password: Option<&'a str>
75}