pub struct Session { /* private fields */ }Expand description
A NETCONF session with pipelining support.
Architecture:
- Writer: the session holds the write half of the stream and a codec for encoding outgoing RPCs.
- Reader task: a background tokio task reads framed messages from
the read half, classifies them (
<rpc-reply>vs<notification>), and routes them to the correct handler. - Pipelining:
rpc_send()writes an RPC and returns anRpcFuturewithout waiting for the reply. Multiple RPCs can be in flight simultaneously.
Implementations§
Source§impl Session
impl Session
Sourcepub async fn connect(
host: &str,
port: u16,
username: &str,
password: &str,
) -> Result<Self>
pub async fn connect( host: &str, port: u16, username: &str, password: &str, ) -> Result<Self>
Connect to a NETCONF server over SSH with password authentication.
Examples found in repository?
4async fn main() -> netconf_rust::Result<()> {
5 // --- Connect ---
6 let session = Session::connect("localhost", 830, "netconf", "netconf").await?;
7
8 println!("Session ID: {}", session.session_id());
9 println!("Framing: {:?}", session.framing_mode());
10 println!("Capabilities: {}", session.server_capabilities().join(", "));
11
12 // --- Get full running config ---
13 let config = session.get_config(Datastore::Running, None).await?;
14 println!("\n=== Running config ===\n{config}");
15
16 // --- Filtered get-config with subtree filter ---
17 let interfaces = session
18 .get_config(
19 Datastore::Running,
20 Some(r#"<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"/>"#),
21 )
22 .await?;
23 println!("\n=== Interfaces ===\n{interfaces}");
24
25 // --- Zero-copy DataPayload ---
26 let payload = session.get_config_payload(Datastore::Running, None).await?;
27 println!("\n=== Config payload size: {} bytes ===", payload.len());
28
29 // Stream XML events directly from the payload's bytes
30 let mut reader = payload.reader();
31 let mut element_count = 0;
32 loop {
33 match reader.read_event() {
34 Ok(quick_xml::events::Event::Start(_)) => element_count += 1,
35 Ok(quick_xml::events::Event::Eof) => break,
36 _ => {}
37 }
38 }
39 println!("Total XML elements: {element_count}");
40
41 // --- Edit config with candidate commit ---
42 // Note: NACM may deny access to certain data models depending on the user's
43 // permissions. We use the netconf-server model which the netconf user can write.
44 session.lock(Datastore::Candidate).await?;
45 println!("\n=== Locked candidate ===");
46
47 match session
48 .edit_config(
49 Datastore::Candidate,
50 r#"
51 <netconf-server xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-server">
52 <listen>
53 <endpoint>
54 <name>default-ssh</name>
55 <ssh>
56 <tcp-server-parameters>
57 <local-address>0.0.0.0</local-address>
58 </tcp-server-parameters>
59 </ssh>
60 </endpoint>
61 </listen>
62 </netconf-server>
63 "#,
64 )
65 .await
66 {
67 Ok(()) => {
68 println!("Edited candidate config");
69 session.commit().await?;
70 println!("Committed");
71 }
72 Err(e) => {
73 eprintln!("edit-config failed (NACM?): {e}");
74 }
75 }
76
77 session.unlock(Datastore::Candidate).await?;
78 println!("Unlocked candidate");
79
80 // --- Pipelining: send 3 RPCs before awaiting any replies ---
81 println!("\n=== Pipelining ===");
82 let fut1 = session
83 .rpc_send("<get-config><source><running/></source></get-config>")
84 .await?;
85 let fut2 = session
86 .rpc_send("<get-config><source><running/></source></get-config>")
87 .await?;
88 let fut3 = session
89 .rpc_send("<get-config><source><running/></source></get-config>")
90 .await?;
91
92 println!(
93 "Sent 3 RPCs (message-ids: {}, {}, {})",
94 fut1.message_id(),
95 fut2.message_id(),
96 fut3.message_id()
97 );
98
99 let reply1 = fut1.response().await?;
100 let reply2 = fut2.response().await?;
101 let reply3 = fut3.response().await?;
102 println!(
103 "Got 3 replies (message-ids: {}, {}, {})",
104 reply1.message_id, reply2.message_id, reply3.message_id
105 );
106
107 // --- Raw RPC for operations not covered by convenience methods ---
108 println!("\n=== Raw RPC ===");
109 let reply = session
110 .rpc_raw("<get-schema><identifier>ietf-interfaces</identifier></get-schema>")
111 .await?;
112 match reply.body {
113 netconf_rust::RpcReplyBody::Data(payload) => {
114 println!("Got schema ({} bytes)", payload.len())
115 }
116 netconf_rust::RpcReplyBody::Ok => println!("ok"),
117 netconf_rust::RpcReplyBody::Error(errors) => {
118 for e in &errors {
119 eprintln!("Error: {}: {}", e.error_tag, e.error_message);
120 }
121 }
122 }
123
124 // --- Graceful close ---
125 session.close_session().await?;
126 println!("\nSession closed (state: {})", session.state());
127
128 Ok(())
129}Sourcepub async fn connect_with_config(
host: &str,
port: u16,
username: &str,
password: &str,
config: Config,
) -> Result<Self>
pub async fn connect_with_config( host: &str, port: u16, username: &str, password: &str, config: Config, ) -> Result<Self>
Connect with custom configuration.
Examples found in repository?
7async fn main() -> netconf_rust::Result<()> {
8 let config = Config::builder()
9 .keepalive_interval(Duration::from_secs(10))
10 .keepalive_max(3)
11 .rpc_timeout(Duration::from_secs(30))
12 .connect_timeout(Duration::from_secs(10))
13 .nodelay(true)
14 .build();
15
16 let session = Arc::new(
17 Session::connect_with_config("localhost", 830, "netconf", "netconf", config).await?,
18 );
19
20 println!("Connected (session {})", session.session_id());
21
22 // Spawn a background task that fires when the session disconnects.
23 // This is the pattern you'd use in a long-running service to clean up
24 // sessions from a DashMap or similar structure.
25 let watcher_session = Arc::clone(&session);
26 let watcher = tokio::spawn(async move {
27 let reason = watcher_session.disconnected().await;
28 println!("Disconnect detected: {reason}");
29 // In a real service you'd remove the session from your map here:
30 // sessions.remove(&uuid);
31 });
32
33 // Normal operations — the watcher runs in the background
34 let config = session
35 .get_config(netconf_rust::Datastore::Running, None)
36 .await?;
37 println!("Got config ({} bytes)", config.len());
38
39 // You can also race an RPC against disconnect using select!
40 tokio::select! {
41 result = session.get_config(netconf_rust::Datastore::Running, None) => {
42 match result {
43 Ok(data) => println!("Got config again ({} bytes)", data.len()),
44 Err(e) => eprintln!("RPC failed: {e}"),
45 }
46 }
47 reason = session.disconnected() => {
48 eprintln!("Connection lost while waiting for RPC: {reason}");
49 }
50 }
51
52 // Graceful close — the watcher will fire with DisconnectReason::Eof
53 // after the server acknowledges the close and drops the connection.
54 session.close_session().await?;
55 println!("Session closed");
56
57 // Wait for the watcher to complete
58 let _ = watcher.await;
59
60 Ok(())
61}Sourcepub async fn from_stream<S: AsyncRead + AsyncWrite + Unpin + Send + 'static>(
stream: S,
) -> Result<Self>
pub async fn from_stream<S: AsyncRead + AsyncWrite + Unpin + Send + 'static>( stream: S, ) -> Result<Self>
Create a session from an existing stream (useful for testing).
Sourcepub async fn from_stream_with_config<S: AsyncRead + AsyncWrite + Unpin + Send + 'static>(
stream: S,
config: Config,
) -> Result<Self>
pub async fn from_stream_with_config<S: AsyncRead + AsyncWrite + Unpin + Send + 'static>( stream: S, config: Config, ) -> Result<Self>
Create a session from an existing stream with custom configuration.
Sourcepub fn session_id(&self) -> u32
pub fn session_id(&self) -> u32
Examples found in repository?
7async fn main() -> netconf_rust::Result<()> {
8 let config = Config::builder()
9 .keepalive_interval(Duration::from_secs(10))
10 .keepalive_max(3)
11 .rpc_timeout(Duration::from_secs(30))
12 .connect_timeout(Duration::from_secs(10))
13 .nodelay(true)
14 .build();
15
16 let session = Arc::new(
17 Session::connect_with_config("localhost", 830, "netconf", "netconf", config).await?,
18 );
19
20 println!("Connected (session {})", session.session_id());
21
22 // Spawn a background task that fires when the session disconnects.
23 // This is the pattern you'd use in a long-running service to clean up
24 // sessions from a DashMap or similar structure.
25 let watcher_session = Arc::clone(&session);
26 let watcher = tokio::spawn(async move {
27 let reason = watcher_session.disconnected().await;
28 println!("Disconnect detected: {reason}");
29 // In a real service you'd remove the session from your map here:
30 // sessions.remove(&uuid);
31 });
32
33 // Normal operations — the watcher runs in the background
34 let config = session
35 .get_config(netconf_rust::Datastore::Running, None)
36 .await?;
37 println!("Got config ({} bytes)", config.len());
38
39 // You can also race an RPC against disconnect using select!
40 tokio::select! {
41 result = session.get_config(netconf_rust::Datastore::Running, None) => {
42 match result {
43 Ok(data) => println!("Got config again ({} bytes)", data.len()),
44 Err(e) => eprintln!("RPC failed: {e}"),
45 }
46 }
47 reason = session.disconnected() => {
48 eprintln!("Connection lost while waiting for RPC: {reason}");
49 }
50 }
51
52 // Graceful close — the watcher will fire with DisconnectReason::Eof
53 // after the server acknowledges the close and drops the connection.
54 session.close_session().await?;
55 println!("Session closed");
56
57 // Wait for the watcher to complete
58 let _ = watcher.await;
59
60 Ok(())
61}More examples
4async fn main() -> netconf_rust::Result<()> {
5 // --- Connect ---
6 let session = Session::connect("localhost", 830, "netconf", "netconf").await?;
7
8 println!("Session ID: {}", session.session_id());
9 println!("Framing: {:?}", session.framing_mode());
10 println!("Capabilities: {}", session.server_capabilities().join(", "));
11
12 // --- Get full running config ---
13 let config = session.get_config(Datastore::Running, None).await?;
14 println!("\n=== Running config ===\n{config}");
15
16 // --- Filtered get-config with subtree filter ---
17 let interfaces = session
18 .get_config(
19 Datastore::Running,
20 Some(r#"<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"/>"#),
21 )
22 .await?;
23 println!("\n=== Interfaces ===\n{interfaces}");
24
25 // --- Zero-copy DataPayload ---
26 let payload = session.get_config_payload(Datastore::Running, None).await?;
27 println!("\n=== Config payload size: {} bytes ===", payload.len());
28
29 // Stream XML events directly from the payload's bytes
30 let mut reader = payload.reader();
31 let mut element_count = 0;
32 loop {
33 match reader.read_event() {
34 Ok(quick_xml::events::Event::Start(_)) => element_count += 1,
35 Ok(quick_xml::events::Event::Eof) => break,
36 _ => {}
37 }
38 }
39 println!("Total XML elements: {element_count}");
40
41 // --- Edit config with candidate commit ---
42 // Note: NACM may deny access to certain data models depending on the user's
43 // permissions. We use the netconf-server model which the netconf user can write.
44 session.lock(Datastore::Candidate).await?;
45 println!("\n=== Locked candidate ===");
46
47 match session
48 .edit_config(
49 Datastore::Candidate,
50 r#"
51 <netconf-server xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-server">
52 <listen>
53 <endpoint>
54 <name>default-ssh</name>
55 <ssh>
56 <tcp-server-parameters>
57 <local-address>0.0.0.0</local-address>
58 </tcp-server-parameters>
59 </ssh>
60 </endpoint>
61 </listen>
62 </netconf-server>
63 "#,
64 )
65 .await
66 {
67 Ok(()) => {
68 println!("Edited candidate config");
69 session.commit().await?;
70 println!("Committed");
71 }
72 Err(e) => {
73 eprintln!("edit-config failed (NACM?): {e}");
74 }
75 }
76
77 session.unlock(Datastore::Candidate).await?;
78 println!("Unlocked candidate");
79
80 // --- Pipelining: send 3 RPCs before awaiting any replies ---
81 println!("\n=== Pipelining ===");
82 let fut1 = session
83 .rpc_send("<get-config><source><running/></source></get-config>")
84 .await?;
85 let fut2 = session
86 .rpc_send("<get-config><source><running/></source></get-config>")
87 .await?;
88 let fut3 = session
89 .rpc_send("<get-config><source><running/></source></get-config>")
90 .await?;
91
92 println!(
93 "Sent 3 RPCs (message-ids: {}, {}, {})",
94 fut1.message_id(),
95 fut2.message_id(),
96 fut3.message_id()
97 );
98
99 let reply1 = fut1.response().await?;
100 let reply2 = fut2.response().await?;
101 let reply3 = fut3.response().await?;
102 println!(
103 "Got 3 replies (message-ids: {}, {}, {})",
104 reply1.message_id, reply2.message_id, reply3.message_id
105 );
106
107 // --- Raw RPC for operations not covered by convenience methods ---
108 println!("\n=== Raw RPC ===");
109 let reply = session
110 .rpc_raw("<get-schema><identifier>ietf-interfaces</identifier></get-schema>")
111 .await?;
112 match reply.body {
113 netconf_rust::RpcReplyBody::Data(payload) => {
114 println!("Got schema ({} bytes)", payload.len())
115 }
116 netconf_rust::RpcReplyBody::Ok => println!("ok"),
117 netconf_rust::RpcReplyBody::Error(errors) => {
118 for e in &errors {
119 eprintln!("Error: {}: {}", e.error_tag, e.error_message);
120 }
121 }
122 }
123
124 // --- Graceful close ---
125 session.close_session().await?;
126 println!("\nSession closed (state: {})", session.state());
127
128 Ok(())
129}Sourcepub fn server_capabilities(&self) -> &[String]
pub fn server_capabilities(&self) -> &[String]
Examples found in repository?
4async fn main() -> netconf_rust::Result<()> {
5 // --- Connect ---
6 let session = Session::connect("localhost", 830, "netconf", "netconf").await?;
7
8 println!("Session ID: {}", session.session_id());
9 println!("Framing: {:?}", session.framing_mode());
10 println!("Capabilities: {}", session.server_capabilities().join(", "));
11
12 // --- Get full running config ---
13 let config = session.get_config(Datastore::Running, None).await?;
14 println!("\n=== Running config ===\n{config}");
15
16 // --- Filtered get-config with subtree filter ---
17 let interfaces = session
18 .get_config(
19 Datastore::Running,
20 Some(r#"<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"/>"#),
21 )
22 .await?;
23 println!("\n=== Interfaces ===\n{interfaces}");
24
25 // --- Zero-copy DataPayload ---
26 let payload = session.get_config_payload(Datastore::Running, None).await?;
27 println!("\n=== Config payload size: {} bytes ===", payload.len());
28
29 // Stream XML events directly from the payload's bytes
30 let mut reader = payload.reader();
31 let mut element_count = 0;
32 loop {
33 match reader.read_event() {
34 Ok(quick_xml::events::Event::Start(_)) => element_count += 1,
35 Ok(quick_xml::events::Event::Eof) => break,
36 _ => {}
37 }
38 }
39 println!("Total XML elements: {element_count}");
40
41 // --- Edit config with candidate commit ---
42 // Note: NACM may deny access to certain data models depending on the user's
43 // permissions. We use the netconf-server model which the netconf user can write.
44 session.lock(Datastore::Candidate).await?;
45 println!("\n=== Locked candidate ===");
46
47 match session
48 .edit_config(
49 Datastore::Candidate,
50 r#"
51 <netconf-server xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-server">
52 <listen>
53 <endpoint>
54 <name>default-ssh</name>
55 <ssh>
56 <tcp-server-parameters>
57 <local-address>0.0.0.0</local-address>
58 </tcp-server-parameters>
59 </ssh>
60 </endpoint>
61 </listen>
62 </netconf-server>
63 "#,
64 )
65 .await
66 {
67 Ok(()) => {
68 println!("Edited candidate config");
69 session.commit().await?;
70 println!("Committed");
71 }
72 Err(e) => {
73 eprintln!("edit-config failed (NACM?): {e}");
74 }
75 }
76
77 session.unlock(Datastore::Candidate).await?;
78 println!("Unlocked candidate");
79
80 // --- Pipelining: send 3 RPCs before awaiting any replies ---
81 println!("\n=== Pipelining ===");
82 let fut1 = session
83 .rpc_send("<get-config><source><running/></source></get-config>")
84 .await?;
85 let fut2 = session
86 .rpc_send("<get-config><source><running/></source></get-config>")
87 .await?;
88 let fut3 = session
89 .rpc_send("<get-config><source><running/></source></get-config>")
90 .await?;
91
92 println!(
93 "Sent 3 RPCs (message-ids: {}, {}, {})",
94 fut1.message_id(),
95 fut2.message_id(),
96 fut3.message_id()
97 );
98
99 let reply1 = fut1.response().await?;
100 let reply2 = fut2.response().await?;
101 let reply3 = fut3.response().await?;
102 println!(
103 "Got 3 replies (message-ids: {}, {}, {})",
104 reply1.message_id, reply2.message_id, reply3.message_id
105 );
106
107 // --- Raw RPC for operations not covered by convenience methods ---
108 println!("\n=== Raw RPC ===");
109 let reply = session
110 .rpc_raw("<get-schema><identifier>ietf-interfaces</identifier></get-schema>")
111 .await?;
112 match reply.body {
113 netconf_rust::RpcReplyBody::Data(payload) => {
114 println!("Got schema ({} bytes)", payload.len())
115 }
116 netconf_rust::RpcReplyBody::Ok => println!("ok"),
117 netconf_rust::RpcReplyBody::Error(errors) => {
118 for e in &errors {
119 eprintln!("Error: {}: {}", e.error_tag, e.error_message);
120 }
121 }
122 }
123
124 // --- Graceful close ---
125 session.close_session().await?;
126 println!("\nSession closed (state: {})", session.state());
127
128 Ok(())
129}Sourcepub fn framing_mode(&self) -> FramingMode
pub fn framing_mode(&self) -> FramingMode
Examples found in repository?
4async fn main() -> netconf_rust::Result<()> {
5 // --- Connect ---
6 let session = Session::connect("localhost", 830, "netconf", "netconf").await?;
7
8 println!("Session ID: {}", session.session_id());
9 println!("Framing: {:?}", session.framing_mode());
10 println!("Capabilities: {}", session.server_capabilities().join(", "));
11
12 // --- Get full running config ---
13 let config = session.get_config(Datastore::Running, None).await?;
14 println!("\n=== Running config ===\n{config}");
15
16 // --- Filtered get-config with subtree filter ---
17 let interfaces = session
18 .get_config(
19 Datastore::Running,
20 Some(r#"<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"/>"#),
21 )
22 .await?;
23 println!("\n=== Interfaces ===\n{interfaces}");
24
25 // --- Zero-copy DataPayload ---
26 let payload = session.get_config_payload(Datastore::Running, None).await?;
27 println!("\n=== Config payload size: {} bytes ===", payload.len());
28
29 // Stream XML events directly from the payload's bytes
30 let mut reader = payload.reader();
31 let mut element_count = 0;
32 loop {
33 match reader.read_event() {
34 Ok(quick_xml::events::Event::Start(_)) => element_count += 1,
35 Ok(quick_xml::events::Event::Eof) => break,
36 _ => {}
37 }
38 }
39 println!("Total XML elements: {element_count}");
40
41 // --- Edit config with candidate commit ---
42 // Note: NACM may deny access to certain data models depending on the user's
43 // permissions. We use the netconf-server model which the netconf user can write.
44 session.lock(Datastore::Candidate).await?;
45 println!("\n=== Locked candidate ===");
46
47 match session
48 .edit_config(
49 Datastore::Candidate,
50 r#"
51 <netconf-server xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-server">
52 <listen>
53 <endpoint>
54 <name>default-ssh</name>
55 <ssh>
56 <tcp-server-parameters>
57 <local-address>0.0.0.0</local-address>
58 </tcp-server-parameters>
59 </ssh>
60 </endpoint>
61 </listen>
62 </netconf-server>
63 "#,
64 )
65 .await
66 {
67 Ok(()) => {
68 println!("Edited candidate config");
69 session.commit().await?;
70 println!("Committed");
71 }
72 Err(e) => {
73 eprintln!("edit-config failed (NACM?): {e}");
74 }
75 }
76
77 session.unlock(Datastore::Candidate).await?;
78 println!("Unlocked candidate");
79
80 // --- Pipelining: send 3 RPCs before awaiting any replies ---
81 println!("\n=== Pipelining ===");
82 let fut1 = session
83 .rpc_send("<get-config><source><running/></source></get-config>")
84 .await?;
85 let fut2 = session
86 .rpc_send("<get-config><source><running/></source></get-config>")
87 .await?;
88 let fut3 = session
89 .rpc_send("<get-config><source><running/></source></get-config>")
90 .await?;
91
92 println!(
93 "Sent 3 RPCs (message-ids: {}, {}, {})",
94 fut1.message_id(),
95 fut2.message_id(),
96 fut3.message_id()
97 );
98
99 let reply1 = fut1.response().await?;
100 let reply2 = fut2.response().await?;
101 let reply3 = fut3.response().await?;
102 println!(
103 "Got 3 replies (message-ids: {}, {}, {})",
104 reply1.message_id, reply2.message_id, reply3.message_id
105 );
106
107 // --- Raw RPC for operations not covered by convenience methods ---
108 println!("\n=== Raw RPC ===");
109 let reply = session
110 .rpc_raw("<get-schema><identifier>ietf-interfaces</identifier></get-schema>")
111 .await?;
112 match reply.body {
113 netconf_rust::RpcReplyBody::Data(payload) => {
114 println!("Got schema ({} bytes)", payload.len())
115 }
116 netconf_rust::RpcReplyBody::Ok => println!("ok"),
117 netconf_rust::RpcReplyBody::Error(errors) => {
118 for e in &errors {
119 eprintln!("Error: {}: {}", e.error_tag, e.error_message);
120 }
121 }
122 }
123
124 // --- Graceful close ---
125 session.close_session().await?;
126 println!("\nSession closed (state: {})", session.state());
127
128 Ok(())
129}Sourcepub fn state(&self) -> SessionState
pub fn state(&self) -> SessionState
Examples found in repository?
4async fn main() -> netconf_rust::Result<()> {
5 // --- Connect ---
6 let session = Session::connect("localhost", 830, "netconf", "netconf").await?;
7
8 println!("Session ID: {}", session.session_id());
9 println!("Framing: {:?}", session.framing_mode());
10 println!("Capabilities: {}", session.server_capabilities().join(", "));
11
12 // --- Get full running config ---
13 let config = session.get_config(Datastore::Running, None).await?;
14 println!("\n=== Running config ===\n{config}");
15
16 // --- Filtered get-config with subtree filter ---
17 let interfaces = session
18 .get_config(
19 Datastore::Running,
20 Some(r#"<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"/>"#),
21 )
22 .await?;
23 println!("\n=== Interfaces ===\n{interfaces}");
24
25 // --- Zero-copy DataPayload ---
26 let payload = session.get_config_payload(Datastore::Running, None).await?;
27 println!("\n=== Config payload size: {} bytes ===", payload.len());
28
29 // Stream XML events directly from the payload's bytes
30 let mut reader = payload.reader();
31 let mut element_count = 0;
32 loop {
33 match reader.read_event() {
34 Ok(quick_xml::events::Event::Start(_)) => element_count += 1,
35 Ok(quick_xml::events::Event::Eof) => break,
36 _ => {}
37 }
38 }
39 println!("Total XML elements: {element_count}");
40
41 // --- Edit config with candidate commit ---
42 // Note: NACM may deny access to certain data models depending on the user's
43 // permissions. We use the netconf-server model which the netconf user can write.
44 session.lock(Datastore::Candidate).await?;
45 println!("\n=== Locked candidate ===");
46
47 match session
48 .edit_config(
49 Datastore::Candidate,
50 r#"
51 <netconf-server xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-server">
52 <listen>
53 <endpoint>
54 <name>default-ssh</name>
55 <ssh>
56 <tcp-server-parameters>
57 <local-address>0.0.0.0</local-address>
58 </tcp-server-parameters>
59 </ssh>
60 </endpoint>
61 </listen>
62 </netconf-server>
63 "#,
64 )
65 .await
66 {
67 Ok(()) => {
68 println!("Edited candidate config");
69 session.commit().await?;
70 println!("Committed");
71 }
72 Err(e) => {
73 eprintln!("edit-config failed (NACM?): {e}");
74 }
75 }
76
77 session.unlock(Datastore::Candidate).await?;
78 println!("Unlocked candidate");
79
80 // --- Pipelining: send 3 RPCs before awaiting any replies ---
81 println!("\n=== Pipelining ===");
82 let fut1 = session
83 .rpc_send("<get-config><source><running/></source></get-config>")
84 .await?;
85 let fut2 = session
86 .rpc_send("<get-config><source><running/></source></get-config>")
87 .await?;
88 let fut3 = session
89 .rpc_send("<get-config><source><running/></source></get-config>")
90 .await?;
91
92 println!(
93 "Sent 3 RPCs (message-ids: {}, {}, {})",
94 fut1.message_id(),
95 fut2.message_id(),
96 fut3.message_id()
97 );
98
99 let reply1 = fut1.response().await?;
100 let reply2 = fut2.response().await?;
101 let reply3 = fut3.response().await?;
102 println!(
103 "Got 3 replies (message-ids: {}, {}, {})",
104 reply1.message_id, reply2.message_id, reply3.message_id
105 );
106
107 // --- Raw RPC for operations not covered by convenience methods ---
108 println!("\n=== Raw RPC ===");
109 let reply = session
110 .rpc_raw("<get-schema><identifier>ietf-interfaces</identifier></get-schema>")
111 .await?;
112 match reply.body {
113 netconf_rust::RpcReplyBody::Data(payload) => {
114 println!("Got schema ({} bytes)", payload.len())
115 }
116 netconf_rust::RpcReplyBody::Ok => println!("ok"),
117 netconf_rust::RpcReplyBody::Error(errors) => {
118 for e in &errors {
119 eprintln!("Error: {}: {}", e.error_tag, e.error_message);
120 }
121 }
122 }
123
124 // --- Graceful close ---
125 session.close_session().await?;
126 println!("\nSession closed (state: {})", session.state());
127
128 Ok(())
129}Sourcepub async fn disconnected(&self) -> DisconnectReason
pub async fn disconnected(&self) -> DisconnectReason
Returns a future that completes when the session disconnects.
Can be called multiple times — each call clones an internal
watch::Receiver, so multiple tasks can independently await
the same disconnect event. If the session is already disconnected
when called, returns immediately.
§Example
let reason = session.disconnected().await;
println!("session died: {reason}");Examples found in repository?
7async fn main() -> netconf_rust::Result<()> {
8 let config = Config::builder()
9 .keepalive_interval(Duration::from_secs(10))
10 .keepalive_max(3)
11 .rpc_timeout(Duration::from_secs(30))
12 .connect_timeout(Duration::from_secs(10))
13 .nodelay(true)
14 .build();
15
16 let session = Arc::new(
17 Session::connect_with_config("localhost", 830, "netconf", "netconf", config).await?,
18 );
19
20 println!("Connected (session {})", session.session_id());
21
22 // Spawn a background task that fires when the session disconnects.
23 // This is the pattern you'd use in a long-running service to clean up
24 // sessions from a DashMap or similar structure.
25 let watcher_session = Arc::clone(&session);
26 let watcher = tokio::spawn(async move {
27 let reason = watcher_session.disconnected().await;
28 println!("Disconnect detected: {reason}");
29 // In a real service you'd remove the session from your map here:
30 // sessions.remove(&uuid);
31 });
32
33 // Normal operations — the watcher runs in the background
34 let config = session
35 .get_config(netconf_rust::Datastore::Running, None)
36 .await?;
37 println!("Got config ({} bytes)", config.len());
38
39 // You can also race an RPC against disconnect using select!
40 tokio::select! {
41 result = session.get_config(netconf_rust::Datastore::Running, None) => {
42 match result {
43 Ok(data) => println!("Got config again ({} bytes)", data.len()),
44 Err(e) => eprintln!("RPC failed: {e}"),
45 }
46 }
47 reason = session.disconnected() => {
48 eprintln!("Connection lost while waiting for RPC: {reason}");
49 }
50 }
51
52 // Graceful close — the watcher will fire with DisconnectReason::Eof
53 // after the server acknowledges the close and drops the connection.
54 session.close_session().await?;
55 println!("Session closed");
56
57 // Wait for the watcher to complete
58 let _ = watcher.await;
59
60 Ok(())
61}Sourcepub async fn rpc_send(&self, inner_xml: &str) -> Result<RpcFuture>
pub async fn rpc_send(&self, inner_xml: &str) -> Result<RpcFuture>
Send a raw RPC and return a future for the reply (pipelining).
This writes the RPC to the server immediately but does not wait
for the reply. Call RpcFuture::response() to await the reply.
Multiple RPCs can be pipelined by calling this repeatedly before
awaiting any of them.
Examples found in repository?
4async fn main() -> netconf_rust::Result<()> {
5 // --- Connect ---
6 let session = Session::connect("localhost", 830, "netconf", "netconf").await?;
7
8 println!("Session ID: {}", session.session_id());
9 println!("Framing: {:?}", session.framing_mode());
10 println!("Capabilities: {}", session.server_capabilities().join(", "));
11
12 // --- Get full running config ---
13 let config = session.get_config(Datastore::Running, None).await?;
14 println!("\n=== Running config ===\n{config}");
15
16 // --- Filtered get-config with subtree filter ---
17 let interfaces = session
18 .get_config(
19 Datastore::Running,
20 Some(r#"<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"/>"#),
21 )
22 .await?;
23 println!("\n=== Interfaces ===\n{interfaces}");
24
25 // --- Zero-copy DataPayload ---
26 let payload = session.get_config_payload(Datastore::Running, None).await?;
27 println!("\n=== Config payload size: {} bytes ===", payload.len());
28
29 // Stream XML events directly from the payload's bytes
30 let mut reader = payload.reader();
31 let mut element_count = 0;
32 loop {
33 match reader.read_event() {
34 Ok(quick_xml::events::Event::Start(_)) => element_count += 1,
35 Ok(quick_xml::events::Event::Eof) => break,
36 _ => {}
37 }
38 }
39 println!("Total XML elements: {element_count}");
40
41 // --- Edit config with candidate commit ---
42 // Note: NACM may deny access to certain data models depending on the user's
43 // permissions. We use the netconf-server model which the netconf user can write.
44 session.lock(Datastore::Candidate).await?;
45 println!("\n=== Locked candidate ===");
46
47 match session
48 .edit_config(
49 Datastore::Candidate,
50 r#"
51 <netconf-server xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-server">
52 <listen>
53 <endpoint>
54 <name>default-ssh</name>
55 <ssh>
56 <tcp-server-parameters>
57 <local-address>0.0.0.0</local-address>
58 </tcp-server-parameters>
59 </ssh>
60 </endpoint>
61 </listen>
62 </netconf-server>
63 "#,
64 )
65 .await
66 {
67 Ok(()) => {
68 println!("Edited candidate config");
69 session.commit().await?;
70 println!("Committed");
71 }
72 Err(e) => {
73 eprintln!("edit-config failed (NACM?): {e}");
74 }
75 }
76
77 session.unlock(Datastore::Candidate).await?;
78 println!("Unlocked candidate");
79
80 // --- Pipelining: send 3 RPCs before awaiting any replies ---
81 println!("\n=== Pipelining ===");
82 let fut1 = session
83 .rpc_send("<get-config><source><running/></source></get-config>")
84 .await?;
85 let fut2 = session
86 .rpc_send("<get-config><source><running/></source></get-config>")
87 .await?;
88 let fut3 = session
89 .rpc_send("<get-config><source><running/></source></get-config>")
90 .await?;
91
92 println!(
93 "Sent 3 RPCs (message-ids: {}, {}, {})",
94 fut1.message_id(),
95 fut2.message_id(),
96 fut3.message_id()
97 );
98
99 let reply1 = fut1.response().await?;
100 let reply2 = fut2.response().await?;
101 let reply3 = fut3.response().await?;
102 println!(
103 "Got 3 replies (message-ids: {}, {}, {})",
104 reply1.message_id, reply2.message_id, reply3.message_id
105 );
106
107 // --- Raw RPC for operations not covered by convenience methods ---
108 println!("\n=== Raw RPC ===");
109 let reply = session
110 .rpc_raw("<get-schema><identifier>ietf-interfaces</identifier></get-schema>")
111 .await?;
112 match reply.body {
113 netconf_rust::RpcReplyBody::Data(payload) => {
114 println!("Got schema ({} bytes)", payload.len())
115 }
116 netconf_rust::RpcReplyBody::Ok => println!("ok"),
117 netconf_rust::RpcReplyBody::Error(errors) => {
118 for e in &errors {
119 eprintln!("Error: {}: {}", e.error_tag, e.error_message);
120 }
121 }
122 }
123
124 // --- Graceful close ---
125 session.close_session().await?;
126 println!("\nSession closed (state: {})", session.state());
127
128 Ok(())
129}Sourcepub async fn rpc_raw(&self, inner_xml: &str) -> Result<RpcReply>
pub async fn rpc_raw(&self, inner_xml: &str) -> Result<RpcReply>
Send a raw RPC and wait for the reply.
Examples found in repository?
4async fn main() -> netconf_rust::Result<()> {
5 // --- Connect ---
6 let session = Session::connect("localhost", 830, "netconf", "netconf").await?;
7
8 println!("Session ID: {}", session.session_id());
9 println!("Framing: {:?}", session.framing_mode());
10 println!("Capabilities: {}", session.server_capabilities().join(", "));
11
12 // --- Get full running config ---
13 let config = session.get_config(Datastore::Running, None).await?;
14 println!("\n=== Running config ===\n{config}");
15
16 // --- Filtered get-config with subtree filter ---
17 let interfaces = session
18 .get_config(
19 Datastore::Running,
20 Some(r#"<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"/>"#),
21 )
22 .await?;
23 println!("\n=== Interfaces ===\n{interfaces}");
24
25 // --- Zero-copy DataPayload ---
26 let payload = session.get_config_payload(Datastore::Running, None).await?;
27 println!("\n=== Config payload size: {} bytes ===", payload.len());
28
29 // Stream XML events directly from the payload's bytes
30 let mut reader = payload.reader();
31 let mut element_count = 0;
32 loop {
33 match reader.read_event() {
34 Ok(quick_xml::events::Event::Start(_)) => element_count += 1,
35 Ok(quick_xml::events::Event::Eof) => break,
36 _ => {}
37 }
38 }
39 println!("Total XML elements: {element_count}");
40
41 // --- Edit config with candidate commit ---
42 // Note: NACM may deny access to certain data models depending on the user's
43 // permissions. We use the netconf-server model which the netconf user can write.
44 session.lock(Datastore::Candidate).await?;
45 println!("\n=== Locked candidate ===");
46
47 match session
48 .edit_config(
49 Datastore::Candidate,
50 r#"
51 <netconf-server xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-server">
52 <listen>
53 <endpoint>
54 <name>default-ssh</name>
55 <ssh>
56 <tcp-server-parameters>
57 <local-address>0.0.0.0</local-address>
58 </tcp-server-parameters>
59 </ssh>
60 </endpoint>
61 </listen>
62 </netconf-server>
63 "#,
64 )
65 .await
66 {
67 Ok(()) => {
68 println!("Edited candidate config");
69 session.commit().await?;
70 println!("Committed");
71 }
72 Err(e) => {
73 eprintln!("edit-config failed (NACM?): {e}");
74 }
75 }
76
77 session.unlock(Datastore::Candidate).await?;
78 println!("Unlocked candidate");
79
80 // --- Pipelining: send 3 RPCs before awaiting any replies ---
81 println!("\n=== Pipelining ===");
82 let fut1 = session
83 .rpc_send("<get-config><source><running/></source></get-config>")
84 .await?;
85 let fut2 = session
86 .rpc_send("<get-config><source><running/></source></get-config>")
87 .await?;
88 let fut3 = session
89 .rpc_send("<get-config><source><running/></source></get-config>")
90 .await?;
91
92 println!(
93 "Sent 3 RPCs (message-ids: {}, {}, {})",
94 fut1.message_id(),
95 fut2.message_id(),
96 fut3.message_id()
97 );
98
99 let reply1 = fut1.response().await?;
100 let reply2 = fut2.response().await?;
101 let reply3 = fut3.response().await?;
102 println!(
103 "Got 3 replies (message-ids: {}, {}, {})",
104 reply1.message_id, reply2.message_id, reply3.message_id
105 );
106
107 // --- Raw RPC for operations not covered by convenience methods ---
108 println!("\n=== Raw RPC ===");
109 let reply = session
110 .rpc_raw("<get-schema><identifier>ietf-interfaces</identifier></get-schema>")
111 .await?;
112 match reply.body {
113 netconf_rust::RpcReplyBody::Data(payload) => {
114 println!("Got schema ({} bytes)", payload.len())
115 }
116 netconf_rust::RpcReplyBody::Ok => println!("ok"),
117 netconf_rust::RpcReplyBody::Error(errors) => {
118 for e in &errors {
119 eprintln!("Error: {}: {}", e.error_tag, e.error_message);
120 }
121 }
122 }
123
124 // --- Graceful close ---
125 session.close_session().await?;
126 println!("\nSession closed (state: {})", session.state());
127
128 Ok(())
129}Sourcepub async fn get_config(
&self,
source: Datastore,
filter: Option<&str>,
) -> Result<String>
pub async fn get_config( &self, source: Datastore, filter: Option<&str>, ) -> Result<String>
Retrieve configuration from a datastore.
Examples found in repository?
7async fn main() -> netconf_rust::Result<()> {
8 let config = Config::builder()
9 .keepalive_interval(Duration::from_secs(10))
10 .keepalive_max(3)
11 .rpc_timeout(Duration::from_secs(30))
12 .connect_timeout(Duration::from_secs(10))
13 .nodelay(true)
14 .build();
15
16 let session = Arc::new(
17 Session::connect_with_config("localhost", 830, "netconf", "netconf", config).await?,
18 );
19
20 println!("Connected (session {})", session.session_id());
21
22 // Spawn a background task that fires when the session disconnects.
23 // This is the pattern you'd use in a long-running service to clean up
24 // sessions from a DashMap or similar structure.
25 let watcher_session = Arc::clone(&session);
26 let watcher = tokio::spawn(async move {
27 let reason = watcher_session.disconnected().await;
28 println!("Disconnect detected: {reason}");
29 // In a real service you'd remove the session from your map here:
30 // sessions.remove(&uuid);
31 });
32
33 // Normal operations — the watcher runs in the background
34 let config = session
35 .get_config(netconf_rust::Datastore::Running, None)
36 .await?;
37 println!("Got config ({} bytes)", config.len());
38
39 // You can also race an RPC against disconnect using select!
40 tokio::select! {
41 result = session.get_config(netconf_rust::Datastore::Running, None) => {
42 match result {
43 Ok(data) => println!("Got config again ({} bytes)", data.len()),
44 Err(e) => eprintln!("RPC failed: {e}"),
45 }
46 }
47 reason = session.disconnected() => {
48 eprintln!("Connection lost while waiting for RPC: {reason}");
49 }
50 }
51
52 // Graceful close — the watcher will fire with DisconnectReason::Eof
53 // after the server acknowledges the close and drops the connection.
54 session.close_session().await?;
55 println!("Session closed");
56
57 // Wait for the watcher to complete
58 let _ = watcher.await;
59
60 Ok(())
61}More examples
4async fn main() -> netconf_rust::Result<()> {
5 // --- Connect ---
6 let session = Session::connect("localhost", 830, "netconf", "netconf").await?;
7
8 println!("Session ID: {}", session.session_id());
9 println!("Framing: {:?}", session.framing_mode());
10 println!("Capabilities: {}", session.server_capabilities().join(", "));
11
12 // --- Get full running config ---
13 let config = session.get_config(Datastore::Running, None).await?;
14 println!("\n=== Running config ===\n{config}");
15
16 // --- Filtered get-config with subtree filter ---
17 let interfaces = session
18 .get_config(
19 Datastore::Running,
20 Some(r#"<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"/>"#),
21 )
22 .await?;
23 println!("\n=== Interfaces ===\n{interfaces}");
24
25 // --- Zero-copy DataPayload ---
26 let payload = session.get_config_payload(Datastore::Running, None).await?;
27 println!("\n=== Config payload size: {} bytes ===", payload.len());
28
29 // Stream XML events directly from the payload's bytes
30 let mut reader = payload.reader();
31 let mut element_count = 0;
32 loop {
33 match reader.read_event() {
34 Ok(quick_xml::events::Event::Start(_)) => element_count += 1,
35 Ok(quick_xml::events::Event::Eof) => break,
36 _ => {}
37 }
38 }
39 println!("Total XML elements: {element_count}");
40
41 // --- Edit config with candidate commit ---
42 // Note: NACM may deny access to certain data models depending on the user's
43 // permissions. We use the netconf-server model which the netconf user can write.
44 session.lock(Datastore::Candidate).await?;
45 println!("\n=== Locked candidate ===");
46
47 match session
48 .edit_config(
49 Datastore::Candidate,
50 r#"
51 <netconf-server xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-server">
52 <listen>
53 <endpoint>
54 <name>default-ssh</name>
55 <ssh>
56 <tcp-server-parameters>
57 <local-address>0.0.0.0</local-address>
58 </tcp-server-parameters>
59 </ssh>
60 </endpoint>
61 </listen>
62 </netconf-server>
63 "#,
64 )
65 .await
66 {
67 Ok(()) => {
68 println!("Edited candidate config");
69 session.commit().await?;
70 println!("Committed");
71 }
72 Err(e) => {
73 eprintln!("edit-config failed (NACM?): {e}");
74 }
75 }
76
77 session.unlock(Datastore::Candidate).await?;
78 println!("Unlocked candidate");
79
80 // --- Pipelining: send 3 RPCs before awaiting any replies ---
81 println!("\n=== Pipelining ===");
82 let fut1 = session
83 .rpc_send("<get-config><source><running/></source></get-config>")
84 .await?;
85 let fut2 = session
86 .rpc_send("<get-config><source><running/></source></get-config>")
87 .await?;
88 let fut3 = session
89 .rpc_send("<get-config><source><running/></source></get-config>")
90 .await?;
91
92 println!(
93 "Sent 3 RPCs (message-ids: {}, {}, {})",
94 fut1.message_id(),
95 fut2.message_id(),
96 fut3.message_id()
97 );
98
99 let reply1 = fut1.response().await?;
100 let reply2 = fut2.response().await?;
101 let reply3 = fut3.response().await?;
102 println!(
103 "Got 3 replies (message-ids: {}, {}, {})",
104 reply1.message_id, reply2.message_id, reply3.message_id
105 );
106
107 // --- Raw RPC for operations not covered by convenience methods ---
108 println!("\n=== Raw RPC ===");
109 let reply = session
110 .rpc_raw("<get-schema><identifier>ietf-interfaces</identifier></get-schema>")
111 .await?;
112 match reply.body {
113 netconf_rust::RpcReplyBody::Data(payload) => {
114 println!("Got schema ({} bytes)", payload.len())
115 }
116 netconf_rust::RpcReplyBody::Ok => println!("ok"),
117 netconf_rust::RpcReplyBody::Error(errors) => {
118 for e in &errors {
119 eprintln!("Error: {}: {}", e.error_tag, e.error_message);
120 }
121 }
122 }
123
124 // --- Graceful close ---
125 session.close_session().await?;
126 println!("\nSession closed (state: {})", session.state());
127
128 Ok(())
129}Sourcepub async fn get_config_payload(
&self,
source: Datastore,
filter: Option<&str>,
) -> Result<DataPayload>
pub async fn get_config_payload( &self, source: Datastore, filter: Option<&str>, ) -> Result<DataPayload>
Retrieve configuration as a zero-copy DataPayload.
Same as get_config() but returns a DataPayload instead of String,
avoiding a copy of the response body. Use payload.as_str() for a
zero-copy &str view, or payload.reader() for streaming XML events.
Examples found in repository?
4async fn main() -> netconf_rust::Result<()> {
5 // --- Connect ---
6 let session = Session::connect("localhost", 830, "netconf", "netconf").await?;
7
8 println!("Session ID: {}", session.session_id());
9 println!("Framing: {:?}", session.framing_mode());
10 println!("Capabilities: {}", session.server_capabilities().join(", "));
11
12 // --- Get full running config ---
13 let config = session.get_config(Datastore::Running, None).await?;
14 println!("\n=== Running config ===\n{config}");
15
16 // --- Filtered get-config with subtree filter ---
17 let interfaces = session
18 .get_config(
19 Datastore::Running,
20 Some(r#"<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"/>"#),
21 )
22 .await?;
23 println!("\n=== Interfaces ===\n{interfaces}");
24
25 // --- Zero-copy DataPayload ---
26 let payload = session.get_config_payload(Datastore::Running, None).await?;
27 println!("\n=== Config payload size: {} bytes ===", payload.len());
28
29 // Stream XML events directly from the payload's bytes
30 let mut reader = payload.reader();
31 let mut element_count = 0;
32 loop {
33 match reader.read_event() {
34 Ok(quick_xml::events::Event::Start(_)) => element_count += 1,
35 Ok(quick_xml::events::Event::Eof) => break,
36 _ => {}
37 }
38 }
39 println!("Total XML elements: {element_count}");
40
41 // --- Edit config with candidate commit ---
42 // Note: NACM may deny access to certain data models depending on the user's
43 // permissions. We use the netconf-server model which the netconf user can write.
44 session.lock(Datastore::Candidate).await?;
45 println!("\n=== Locked candidate ===");
46
47 match session
48 .edit_config(
49 Datastore::Candidate,
50 r#"
51 <netconf-server xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-server">
52 <listen>
53 <endpoint>
54 <name>default-ssh</name>
55 <ssh>
56 <tcp-server-parameters>
57 <local-address>0.0.0.0</local-address>
58 </tcp-server-parameters>
59 </ssh>
60 </endpoint>
61 </listen>
62 </netconf-server>
63 "#,
64 )
65 .await
66 {
67 Ok(()) => {
68 println!("Edited candidate config");
69 session.commit().await?;
70 println!("Committed");
71 }
72 Err(e) => {
73 eprintln!("edit-config failed (NACM?): {e}");
74 }
75 }
76
77 session.unlock(Datastore::Candidate).await?;
78 println!("Unlocked candidate");
79
80 // --- Pipelining: send 3 RPCs before awaiting any replies ---
81 println!("\n=== Pipelining ===");
82 let fut1 = session
83 .rpc_send("<get-config><source><running/></source></get-config>")
84 .await?;
85 let fut2 = session
86 .rpc_send("<get-config><source><running/></source></get-config>")
87 .await?;
88 let fut3 = session
89 .rpc_send("<get-config><source><running/></source></get-config>")
90 .await?;
91
92 println!(
93 "Sent 3 RPCs (message-ids: {}, {}, {})",
94 fut1.message_id(),
95 fut2.message_id(),
96 fut3.message_id()
97 );
98
99 let reply1 = fut1.response().await?;
100 let reply2 = fut2.response().await?;
101 let reply3 = fut3.response().await?;
102 println!(
103 "Got 3 replies (message-ids: {}, {}, {})",
104 reply1.message_id, reply2.message_id, reply3.message_id
105 );
106
107 // --- Raw RPC for operations not covered by convenience methods ---
108 println!("\n=== Raw RPC ===");
109 let reply = session
110 .rpc_raw("<get-schema><identifier>ietf-interfaces</identifier></get-schema>")
111 .await?;
112 match reply.body {
113 netconf_rust::RpcReplyBody::Data(payload) => {
114 println!("Got schema ({} bytes)", payload.len())
115 }
116 netconf_rust::RpcReplyBody::Ok => println!("ok"),
117 netconf_rust::RpcReplyBody::Error(errors) => {
118 for e in &errors {
119 eprintln!("Error: {}: {}", e.error_tag, e.error_message);
120 }
121 }
122 }
123
124 // --- Graceful close ---
125 session.close_session().await?;
126 println!("\nSession closed (state: {})", session.state());
127
128 Ok(())
129}Sourcepub async fn get(&self, filter: Option<&str>) -> Result<String>
pub async fn get(&self, filter: Option<&str>) -> Result<String>
Retrieve running configuration and state data.
Sourcepub async fn get_payload(&self, filter: Option<&str>) -> Result<DataPayload>
pub async fn get_payload(&self, filter: Option<&str>) -> Result<DataPayload>
Retrieve running configuration and state data as a zero-copy DataPayload.
Same as get() but returns a DataPayload instead of String,
avoiding a copy of the response body.
Sourcepub async fn edit_config(&self, target: Datastore, config: &str) -> Result<()>
pub async fn edit_config(&self, target: Datastore, config: &str) -> Result<()>
Edit the configuration of a target datastore.
Examples found in repository?
4async fn main() -> netconf_rust::Result<()> {
5 // --- Connect ---
6 let session = Session::connect("localhost", 830, "netconf", "netconf").await?;
7
8 println!("Session ID: {}", session.session_id());
9 println!("Framing: {:?}", session.framing_mode());
10 println!("Capabilities: {}", session.server_capabilities().join(", "));
11
12 // --- Get full running config ---
13 let config = session.get_config(Datastore::Running, None).await?;
14 println!("\n=== Running config ===\n{config}");
15
16 // --- Filtered get-config with subtree filter ---
17 let interfaces = session
18 .get_config(
19 Datastore::Running,
20 Some(r#"<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"/>"#),
21 )
22 .await?;
23 println!("\n=== Interfaces ===\n{interfaces}");
24
25 // --- Zero-copy DataPayload ---
26 let payload = session.get_config_payload(Datastore::Running, None).await?;
27 println!("\n=== Config payload size: {} bytes ===", payload.len());
28
29 // Stream XML events directly from the payload's bytes
30 let mut reader = payload.reader();
31 let mut element_count = 0;
32 loop {
33 match reader.read_event() {
34 Ok(quick_xml::events::Event::Start(_)) => element_count += 1,
35 Ok(quick_xml::events::Event::Eof) => break,
36 _ => {}
37 }
38 }
39 println!("Total XML elements: {element_count}");
40
41 // --- Edit config with candidate commit ---
42 // Note: NACM may deny access to certain data models depending on the user's
43 // permissions. We use the netconf-server model which the netconf user can write.
44 session.lock(Datastore::Candidate).await?;
45 println!("\n=== Locked candidate ===");
46
47 match session
48 .edit_config(
49 Datastore::Candidate,
50 r#"
51 <netconf-server xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-server">
52 <listen>
53 <endpoint>
54 <name>default-ssh</name>
55 <ssh>
56 <tcp-server-parameters>
57 <local-address>0.0.0.0</local-address>
58 </tcp-server-parameters>
59 </ssh>
60 </endpoint>
61 </listen>
62 </netconf-server>
63 "#,
64 )
65 .await
66 {
67 Ok(()) => {
68 println!("Edited candidate config");
69 session.commit().await?;
70 println!("Committed");
71 }
72 Err(e) => {
73 eprintln!("edit-config failed (NACM?): {e}");
74 }
75 }
76
77 session.unlock(Datastore::Candidate).await?;
78 println!("Unlocked candidate");
79
80 // --- Pipelining: send 3 RPCs before awaiting any replies ---
81 println!("\n=== Pipelining ===");
82 let fut1 = session
83 .rpc_send("<get-config><source><running/></source></get-config>")
84 .await?;
85 let fut2 = session
86 .rpc_send("<get-config><source><running/></source></get-config>")
87 .await?;
88 let fut3 = session
89 .rpc_send("<get-config><source><running/></source></get-config>")
90 .await?;
91
92 println!(
93 "Sent 3 RPCs (message-ids: {}, {}, {})",
94 fut1.message_id(),
95 fut2.message_id(),
96 fut3.message_id()
97 );
98
99 let reply1 = fut1.response().await?;
100 let reply2 = fut2.response().await?;
101 let reply3 = fut3.response().await?;
102 println!(
103 "Got 3 replies (message-ids: {}, {}, {})",
104 reply1.message_id, reply2.message_id, reply3.message_id
105 );
106
107 // --- Raw RPC for operations not covered by convenience methods ---
108 println!("\n=== Raw RPC ===");
109 let reply = session
110 .rpc_raw("<get-schema><identifier>ietf-interfaces</identifier></get-schema>")
111 .await?;
112 match reply.body {
113 netconf_rust::RpcReplyBody::Data(payload) => {
114 println!("Got schema ({} bytes)", payload.len())
115 }
116 netconf_rust::RpcReplyBody::Ok => println!("ok"),
117 netconf_rust::RpcReplyBody::Error(errors) => {
118 for e in &errors {
119 eprintln!("Error: {}: {}", e.error_tag, e.error_message);
120 }
121 }
122 }
123
124 // --- Graceful close ---
125 session.close_session().await?;
126 println!("\nSession closed (state: {})", session.state());
127
128 Ok(())
129}Sourcepub async fn lock(&self, target: Datastore) -> Result<()>
pub async fn lock(&self, target: Datastore) -> Result<()>
Lock a datastore
Examples found in repository?
4async fn main() -> netconf_rust::Result<()> {
5 // --- Connect ---
6 let session = Session::connect("localhost", 830, "netconf", "netconf").await?;
7
8 println!("Session ID: {}", session.session_id());
9 println!("Framing: {:?}", session.framing_mode());
10 println!("Capabilities: {}", session.server_capabilities().join(", "));
11
12 // --- Get full running config ---
13 let config = session.get_config(Datastore::Running, None).await?;
14 println!("\n=== Running config ===\n{config}");
15
16 // --- Filtered get-config with subtree filter ---
17 let interfaces = session
18 .get_config(
19 Datastore::Running,
20 Some(r#"<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"/>"#),
21 )
22 .await?;
23 println!("\n=== Interfaces ===\n{interfaces}");
24
25 // --- Zero-copy DataPayload ---
26 let payload = session.get_config_payload(Datastore::Running, None).await?;
27 println!("\n=== Config payload size: {} bytes ===", payload.len());
28
29 // Stream XML events directly from the payload's bytes
30 let mut reader = payload.reader();
31 let mut element_count = 0;
32 loop {
33 match reader.read_event() {
34 Ok(quick_xml::events::Event::Start(_)) => element_count += 1,
35 Ok(quick_xml::events::Event::Eof) => break,
36 _ => {}
37 }
38 }
39 println!("Total XML elements: {element_count}");
40
41 // --- Edit config with candidate commit ---
42 // Note: NACM may deny access to certain data models depending on the user's
43 // permissions. We use the netconf-server model which the netconf user can write.
44 session.lock(Datastore::Candidate).await?;
45 println!("\n=== Locked candidate ===");
46
47 match session
48 .edit_config(
49 Datastore::Candidate,
50 r#"
51 <netconf-server xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-server">
52 <listen>
53 <endpoint>
54 <name>default-ssh</name>
55 <ssh>
56 <tcp-server-parameters>
57 <local-address>0.0.0.0</local-address>
58 </tcp-server-parameters>
59 </ssh>
60 </endpoint>
61 </listen>
62 </netconf-server>
63 "#,
64 )
65 .await
66 {
67 Ok(()) => {
68 println!("Edited candidate config");
69 session.commit().await?;
70 println!("Committed");
71 }
72 Err(e) => {
73 eprintln!("edit-config failed (NACM?): {e}");
74 }
75 }
76
77 session.unlock(Datastore::Candidate).await?;
78 println!("Unlocked candidate");
79
80 // --- Pipelining: send 3 RPCs before awaiting any replies ---
81 println!("\n=== Pipelining ===");
82 let fut1 = session
83 .rpc_send("<get-config><source><running/></source></get-config>")
84 .await?;
85 let fut2 = session
86 .rpc_send("<get-config><source><running/></source></get-config>")
87 .await?;
88 let fut3 = session
89 .rpc_send("<get-config><source><running/></source></get-config>")
90 .await?;
91
92 println!(
93 "Sent 3 RPCs (message-ids: {}, {}, {})",
94 fut1.message_id(),
95 fut2.message_id(),
96 fut3.message_id()
97 );
98
99 let reply1 = fut1.response().await?;
100 let reply2 = fut2.response().await?;
101 let reply3 = fut3.response().await?;
102 println!(
103 "Got 3 replies (message-ids: {}, {}, {})",
104 reply1.message_id, reply2.message_id, reply3.message_id
105 );
106
107 // --- Raw RPC for operations not covered by convenience methods ---
108 println!("\n=== Raw RPC ===");
109 let reply = session
110 .rpc_raw("<get-schema><identifier>ietf-interfaces</identifier></get-schema>")
111 .await?;
112 match reply.body {
113 netconf_rust::RpcReplyBody::Data(payload) => {
114 println!("Got schema ({} bytes)", payload.len())
115 }
116 netconf_rust::RpcReplyBody::Ok => println!("ok"),
117 netconf_rust::RpcReplyBody::Error(errors) => {
118 for e in &errors {
119 eprintln!("Error: {}: {}", e.error_tag, e.error_message);
120 }
121 }
122 }
123
124 // --- Graceful close ---
125 session.close_session().await?;
126 println!("\nSession closed (state: {})", session.state());
127
128 Ok(())
129}Sourcepub async fn unlock(&self, target: Datastore) -> Result<()>
pub async fn unlock(&self, target: Datastore) -> Result<()>
Unlock a datastore.
Examples found in repository?
4async fn main() -> netconf_rust::Result<()> {
5 // --- Connect ---
6 let session = Session::connect("localhost", 830, "netconf", "netconf").await?;
7
8 println!("Session ID: {}", session.session_id());
9 println!("Framing: {:?}", session.framing_mode());
10 println!("Capabilities: {}", session.server_capabilities().join(", "));
11
12 // --- Get full running config ---
13 let config = session.get_config(Datastore::Running, None).await?;
14 println!("\n=== Running config ===\n{config}");
15
16 // --- Filtered get-config with subtree filter ---
17 let interfaces = session
18 .get_config(
19 Datastore::Running,
20 Some(r#"<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"/>"#),
21 )
22 .await?;
23 println!("\n=== Interfaces ===\n{interfaces}");
24
25 // --- Zero-copy DataPayload ---
26 let payload = session.get_config_payload(Datastore::Running, None).await?;
27 println!("\n=== Config payload size: {} bytes ===", payload.len());
28
29 // Stream XML events directly from the payload's bytes
30 let mut reader = payload.reader();
31 let mut element_count = 0;
32 loop {
33 match reader.read_event() {
34 Ok(quick_xml::events::Event::Start(_)) => element_count += 1,
35 Ok(quick_xml::events::Event::Eof) => break,
36 _ => {}
37 }
38 }
39 println!("Total XML elements: {element_count}");
40
41 // --- Edit config with candidate commit ---
42 // Note: NACM may deny access to certain data models depending on the user's
43 // permissions. We use the netconf-server model which the netconf user can write.
44 session.lock(Datastore::Candidate).await?;
45 println!("\n=== Locked candidate ===");
46
47 match session
48 .edit_config(
49 Datastore::Candidate,
50 r#"
51 <netconf-server xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-server">
52 <listen>
53 <endpoint>
54 <name>default-ssh</name>
55 <ssh>
56 <tcp-server-parameters>
57 <local-address>0.0.0.0</local-address>
58 </tcp-server-parameters>
59 </ssh>
60 </endpoint>
61 </listen>
62 </netconf-server>
63 "#,
64 )
65 .await
66 {
67 Ok(()) => {
68 println!("Edited candidate config");
69 session.commit().await?;
70 println!("Committed");
71 }
72 Err(e) => {
73 eprintln!("edit-config failed (NACM?): {e}");
74 }
75 }
76
77 session.unlock(Datastore::Candidate).await?;
78 println!("Unlocked candidate");
79
80 // --- Pipelining: send 3 RPCs before awaiting any replies ---
81 println!("\n=== Pipelining ===");
82 let fut1 = session
83 .rpc_send("<get-config><source><running/></source></get-config>")
84 .await?;
85 let fut2 = session
86 .rpc_send("<get-config><source><running/></source></get-config>")
87 .await?;
88 let fut3 = session
89 .rpc_send("<get-config><source><running/></source></get-config>")
90 .await?;
91
92 println!(
93 "Sent 3 RPCs (message-ids: {}, {}, {})",
94 fut1.message_id(),
95 fut2.message_id(),
96 fut3.message_id()
97 );
98
99 let reply1 = fut1.response().await?;
100 let reply2 = fut2.response().await?;
101 let reply3 = fut3.response().await?;
102 println!(
103 "Got 3 replies (message-ids: {}, {}, {})",
104 reply1.message_id, reply2.message_id, reply3.message_id
105 );
106
107 // --- Raw RPC for operations not covered by convenience methods ---
108 println!("\n=== Raw RPC ===");
109 let reply = session
110 .rpc_raw("<get-schema><identifier>ietf-interfaces</identifier></get-schema>")
111 .await?;
112 match reply.body {
113 netconf_rust::RpcReplyBody::Data(payload) => {
114 println!("Got schema ({} bytes)", payload.len())
115 }
116 netconf_rust::RpcReplyBody::Ok => println!("ok"),
117 netconf_rust::RpcReplyBody::Error(errors) => {
118 for e in &errors {
119 eprintln!("Error: {}: {}", e.error_tag, e.error_message);
120 }
121 }
122 }
123
124 // --- Graceful close ---
125 session.close_session().await?;
126 println!("\nSession closed (state: {})", session.state());
127
128 Ok(())
129}Sourcepub async fn commit(&self) -> Result<()>
pub async fn commit(&self) -> Result<()>
Commit the candidate configuration to running.
Examples found in repository?
4async fn main() -> netconf_rust::Result<()> {
5 // --- Connect ---
6 let session = Session::connect("localhost", 830, "netconf", "netconf").await?;
7
8 println!("Session ID: {}", session.session_id());
9 println!("Framing: {:?}", session.framing_mode());
10 println!("Capabilities: {}", session.server_capabilities().join(", "));
11
12 // --- Get full running config ---
13 let config = session.get_config(Datastore::Running, None).await?;
14 println!("\n=== Running config ===\n{config}");
15
16 // --- Filtered get-config with subtree filter ---
17 let interfaces = session
18 .get_config(
19 Datastore::Running,
20 Some(r#"<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"/>"#),
21 )
22 .await?;
23 println!("\n=== Interfaces ===\n{interfaces}");
24
25 // --- Zero-copy DataPayload ---
26 let payload = session.get_config_payload(Datastore::Running, None).await?;
27 println!("\n=== Config payload size: {} bytes ===", payload.len());
28
29 // Stream XML events directly from the payload's bytes
30 let mut reader = payload.reader();
31 let mut element_count = 0;
32 loop {
33 match reader.read_event() {
34 Ok(quick_xml::events::Event::Start(_)) => element_count += 1,
35 Ok(quick_xml::events::Event::Eof) => break,
36 _ => {}
37 }
38 }
39 println!("Total XML elements: {element_count}");
40
41 // --- Edit config with candidate commit ---
42 // Note: NACM may deny access to certain data models depending on the user's
43 // permissions. We use the netconf-server model which the netconf user can write.
44 session.lock(Datastore::Candidate).await?;
45 println!("\n=== Locked candidate ===");
46
47 match session
48 .edit_config(
49 Datastore::Candidate,
50 r#"
51 <netconf-server xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-server">
52 <listen>
53 <endpoint>
54 <name>default-ssh</name>
55 <ssh>
56 <tcp-server-parameters>
57 <local-address>0.0.0.0</local-address>
58 </tcp-server-parameters>
59 </ssh>
60 </endpoint>
61 </listen>
62 </netconf-server>
63 "#,
64 )
65 .await
66 {
67 Ok(()) => {
68 println!("Edited candidate config");
69 session.commit().await?;
70 println!("Committed");
71 }
72 Err(e) => {
73 eprintln!("edit-config failed (NACM?): {e}");
74 }
75 }
76
77 session.unlock(Datastore::Candidate).await?;
78 println!("Unlocked candidate");
79
80 // --- Pipelining: send 3 RPCs before awaiting any replies ---
81 println!("\n=== Pipelining ===");
82 let fut1 = session
83 .rpc_send("<get-config><source><running/></source></get-config>")
84 .await?;
85 let fut2 = session
86 .rpc_send("<get-config><source><running/></source></get-config>")
87 .await?;
88 let fut3 = session
89 .rpc_send("<get-config><source><running/></source></get-config>")
90 .await?;
91
92 println!(
93 "Sent 3 RPCs (message-ids: {}, {}, {})",
94 fut1.message_id(),
95 fut2.message_id(),
96 fut3.message_id()
97 );
98
99 let reply1 = fut1.response().await?;
100 let reply2 = fut2.response().await?;
101 let reply3 = fut3.response().await?;
102 println!(
103 "Got 3 replies (message-ids: {}, {}, {})",
104 reply1.message_id, reply2.message_id, reply3.message_id
105 );
106
107 // --- Raw RPC for operations not covered by convenience methods ---
108 println!("\n=== Raw RPC ===");
109 let reply = session
110 .rpc_raw("<get-schema><identifier>ietf-interfaces</identifier></get-schema>")
111 .await?;
112 match reply.body {
113 netconf_rust::RpcReplyBody::Data(payload) => {
114 println!("Got schema ({} bytes)", payload.len())
115 }
116 netconf_rust::RpcReplyBody::Ok => println!("ok"),
117 netconf_rust::RpcReplyBody::Error(errors) => {
118 for e in &errors {
119 eprintln!("Error: {}: {}", e.error_tag, e.error_message);
120 }
121 }
122 }
123
124 // --- Graceful close ---
125 session.close_session().await?;
126 println!("\nSession closed (state: {})", session.state());
127
128 Ok(())
129}Sourcepub async fn close_session(&self) -> Result<()>
pub async fn close_session(&self) -> Result<()>
Gracefully close the NETCONF session.
Examples found in repository?
7async fn main() -> netconf_rust::Result<()> {
8 let config = Config::builder()
9 .keepalive_interval(Duration::from_secs(10))
10 .keepalive_max(3)
11 .rpc_timeout(Duration::from_secs(30))
12 .connect_timeout(Duration::from_secs(10))
13 .nodelay(true)
14 .build();
15
16 let session = Arc::new(
17 Session::connect_with_config("localhost", 830, "netconf", "netconf", config).await?,
18 );
19
20 println!("Connected (session {})", session.session_id());
21
22 // Spawn a background task that fires when the session disconnects.
23 // This is the pattern you'd use in a long-running service to clean up
24 // sessions from a DashMap or similar structure.
25 let watcher_session = Arc::clone(&session);
26 let watcher = tokio::spawn(async move {
27 let reason = watcher_session.disconnected().await;
28 println!("Disconnect detected: {reason}");
29 // In a real service you'd remove the session from your map here:
30 // sessions.remove(&uuid);
31 });
32
33 // Normal operations — the watcher runs in the background
34 let config = session
35 .get_config(netconf_rust::Datastore::Running, None)
36 .await?;
37 println!("Got config ({} bytes)", config.len());
38
39 // You can also race an RPC against disconnect using select!
40 tokio::select! {
41 result = session.get_config(netconf_rust::Datastore::Running, None) => {
42 match result {
43 Ok(data) => println!("Got config again ({} bytes)", data.len()),
44 Err(e) => eprintln!("RPC failed: {e}"),
45 }
46 }
47 reason = session.disconnected() => {
48 eprintln!("Connection lost while waiting for RPC: {reason}");
49 }
50 }
51
52 // Graceful close — the watcher will fire with DisconnectReason::Eof
53 // after the server acknowledges the close and drops the connection.
54 session.close_session().await?;
55 println!("Session closed");
56
57 // Wait for the watcher to complete
58 let _ = watcher.await;
59
60 Ok(())
61}More examples
4async fn main() -> netconf_rust::Result<()> {
5 // --- Connect ---
6 let session = Session::connect("localhost", 830, "netconf", "netconf").await?;
7
8 println!("Session ID: {}", session.session_id());
9 println!("Framing: {:?}", session.framing_mode());
10 println!("Capabilities: {}", session.server_capabilities().join(", "));
11
12 // --- Get full running config ---
13 let config = session.get_config(Datastore::Running, None).await?;
14 println!("\n=== Running config ===\n{config}");
15
16 // --- Filtered get-config with subtree filter ---
17 let interfaces = session
18 .get_config(
19 Datastore::Running,
20 Some(r#"<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"/>"#),
21 )
22 .await?;
23 println!("\n=== Interfaces ===\n{interfaces}");
24
25 // --- Zero-copy DataPayload ---
26 let payload = session.get_config_payload(Datastore::Running, None).await?;
27 println!("\n=== Config payload size: {} bytes ===", payload.len());
28
29 // Stream XML events directly from the payload's bytes
30 let mut reader = payload.reader();
31 let mut element_count = 0;
32 loop {
33 match reader.read_event() {
34 Ok(quick_xml::events::Event::Start(_)) => element_count += 1,
35 Ok(quick_xml::events::Event::Eof) => break,
36 _ => {}
37 }
38 }
39 println!("Total XML elements: {element_count}");
40
41 // --- Edit config with candidate commit ---
42 // Note: NACM may deny access to certain data models depending on the user's
43 // permissions. We use the netconf-server model which the netconf user can write.
44 session.lock(Datastore::Candidate).await?;
45 println!("\n=== Locked candidate ===");
46
47 match session
48 .edit_config(
49 Datastore::Candidate,
50 r#"
51 <netconf-server xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-server">
52 <listen>
53 <endpoint>
54 <name>default-ssh</name>
55 <ssh>
56 <tcp-server-parameters>
57 <local-address>0.0.0.0</local-address>
58 </tcp-server-parameters>
59 </ssh>
60 </endpoint>
61 </listen>
62 </netconf-server>
63 "#,
64 )
65 .await
66 {
67 Ok(()) => {
68 println!("Edited candidate config");
69 session.commit().await?;
70 println!("Committed");
71 }
72 Err(e) => {
73 eprintln!("edit-config failed (NACM?): {e}");
74 }
75 }
76
77 session.unlock(Datastore::Candidate).await?;
78 println!("Unlocked candidate");
79
80 // --- Pipelining: send 3 RPCs before awaiting any replies ---
81 println!("\n=== Pipelining ===");
82 let fut1 = session
83 .rpc_send("<get-config><source><running/></source></get-config>")
84 .await?;
85 let fut2 = session
86 .rpc_send("<get-config><source><running/></source></get-config>")
87 .await?;
88 let fut3 = session
89 .rpc_send("<get-config><source><running/></source></get-config>")
90 .await?;
91
92 println!(
93 "Sent 3 RPCs (message-ids: {}, {}, {})",
94 fut1.message_id(),
95 fut2.message_id(),
96 fut3.message_id()
97 );
98
99 let reply1 = fut1.response().await?;
100 let reply2 = fut2.response().await?;
101 let reply3 = fut3.response().await?;
102 println!(
103 "Got 3 replies (message-ids: {}, {}, {})",
104 reply1.message_id, reply2.message_id, reply3.message_id
105 );
106
107 // --- Raw RPC for operations not covered by convenience methods ---
108 println!("\n=== Raw RPC ===");
109 let reply = session
110 .rpc_raw("<get-schema><identifier>ietf-interfaces</identifier></get-schema>")
111 .await?;
112 match reply.body {
113 netconf_rust::RpcReplyBody::Data(payload) => {
114 println!("Got schema ({} bytes)", payload.len())
115 }
116 netconf_rust::RpcReplyBody::Ok => println!("ok"),
117 netconf_rust::RpcReplyBody::Error(errors) => {
118 for e in &errors {
119 eprintln!("Error: {}: {}", e.error_tag, e.error_message);
120 }
121 }
122 }
123
124 // --- Graceful close ---
125 session.close_session().await?;
126 println!("\nSession closed (state: {})", session.state());
127
128 Ok(())
129}Sourcepub async fn kill_session(&self, session_id: u32) -> Result<()>
pub async fn kill_session(&self, session_id: u32) -> Result<()>
Force-close another NETCONF session.
Sourcepub fn with_timeout(&self, timeout: Duration) -> SessionWithTimeout<'_>
pub fn with_timeout(&self, timeout: Duration) -> SessionWithTimeout<'_>
Return a wrapper that applies timeout to every RPC sent through it,
overriding the session-level rpc_timeout.
Sourcepub fn pending_rpc_count(&self) -> usize
pub fn pending_rpc_count(&self) -> usize
Number of RPCs that have been sent but not yet replied to.
Sourcepub fn last_rpc_at(&self) -> Option<Instant>
pub fn last_rpc_at(&self) -> Option<Instant>
The Instant when the most recent RPC reply was received, or
None if no reply has been received yet.
Sourcepub fn connected_since(&self) -> Instant
pub fn connected_since(&self) -> Instant
The Instant when the session was established (hello exchange
complete and reader task started).