pub struct Session { /* private fields */ }Expand description
A NETCONF session with pipelining and streaming 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 chunks 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. - Streaming:
rpc_stream()writes an RPC and returns anRpcStreamthat yields raw bytes as they arrive. This avoids buffering the entire response for large payloads.
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 fn disconnected(
&self,
) -> impl Future<Output = DisconnectReason> + Send + 'static
pub fn disconnected( &self, ) -> impl Future<Output = DisconnectReason> + Send + 'static
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 rpc_stream(&self, inner_xml: &str) -> Result<RpcStream>
pub async fn rpc_stream(&self, inner_xml: &str) -> Result<RpcStream>
Send a raw RPC and return a streaming response.
Unlike rpc_send() which buffers the entire
response, this returns an RpcStream that yields raw XML bytes
as individual chunks arrive from the server. The stream implements
AsyncRead, so it can be piped directly into compression encoders,
file writers, or any byte-oriented consumer.
Normal (buffered) and streaming RPCs can be freely interleaved on
the same session — the reader task routes each message independently
based on its message-id.
§Example
// Pipeline a normal RPC alongside a streaming one
let edit_future = session.rpc_send("<edit-config>...</edit-config>").await?;
let mut config_stream = session.rpc_stream("<get-config><source><running/></source></get-config>").await?;
// Stream the large response
let mut buf = [0u8; 8192];
loop {
let n = config_stream.read(&mut buf).await?;
if n == 0 { break; }
// process buf[..n]
}
// Collect the normal RPC reply
let edit_reply = edit_future.response().await?;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_config_stream(
&self,
source: Datastore,
filter: Option<&str>,
) -> Result<RpcStream>
pub async fn get_config_stream( &self, source: Datastore, filter: Option<&str>, ) -> Result<RpcStream>
Retrieve configuration from a datastore as a streaming response.
Same as get_config() but returns an RpcStream instead of
buffering the entire response.
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 get_stream(&self, filter: Option<&str>) -> Result<RpcStream>
pub async fn get_stream(&self, filter: Option<&str>) -> Result<RpcStream>
Retrieve running configuration and state data as a streaming response.
Same as get() but returns an RpcStream instead of buffering
the entire response.
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 close(self) -> Result<()>
pub async fn close(self) -> Result<()>
Gracefully close the NETCONF session and shut down the transport.
Sends a <close-session/> RPC, shuts down the write half of the
stream, and drops the session (aborting the reader task and releasing
the SSH handle). Prefer this over close_session()
when you are done with the session entirely.
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 fully replied to.
This includes both RPCs awaiting their first reply byte (in the pending map) and streaming RPCs whose response is in flight but not yet complete.
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).