Skip to main content

Session

Struct Session 

Source
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 an RpcFuture without waiting for the reply. Multiple RPCs can be in flight simultaneously.
  • Streaming: rpc_stream() writes an RPC and returns an RpcStream that yields raw bytes as they arrive. This avoids buffering the entire response for large payloads.

Implementations§

Source§

impl Session

Source

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?
examples/basic.rs (line 6)
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}
Source

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?
examples/disconnect_watcher.rs (line 17)
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}
Source

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).

Source

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.

Source

pub fn session_id(&self) -> u32

Examples found in repository?
examples/disconnect_watcher.rs (line 20)
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
Hide additional examples
examples/basic.rs (line 8)
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}
Source

pub fn server_capabilities(&self) -> &[String]

Examples found in repository?
examples/basic.rs (line 10)
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}
Source

pub fn framing_mode(&self) -> FramingMode

Examples found in repository?
examples/basic.rs (line 9)
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}
Source

pub fn state(&self) -> SessionState

Examples found in repository?
examples/basic.rs (line 126)
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}
Source

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?
examples/disconnect_watcher.rs (line 27)
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}
Source

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?
examples/basic.rs (line 83)
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}
Source

pub async fn rpc_raw(&self, inner_xml: &str) -> Result<RpcReply>

Send a raw RPC and wait for the reply.

Examples found in repository?
examples/basic.rs (line 110)
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}
Source

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?;
Source

pub async fn get_config( &self, source: Datastore, filter: Option<&str>, ) -> Result<String>

Retrieve configuration from a datastore.

Examples found in repository?
examples/disconnect_watcher.rs (line 35)
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
Hide additional examples
examples/basic.rs (line 13)
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}
Source

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?
examples/basic.rs (line 26)
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}
Source

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.

Source

pub async fn get(&self, filter: Option<&str>) -> Result<String>

Retrieve running configuration and state data.

Source

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.

Source

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.

Source

pub async fn edit_config(&self, target: Datastore, config: &str) -> Result<()>

Edit the configuration of a target datastore.

Examples found in repository?
examples/basic.rs (lines 48-64)
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}
Source

pub async fn lock(&self, target: Datastore) -> Result<()>

Lock a datastore

Examples found in repository?
examples/basic.rs (line 44)
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}
Source

pub async fn unlock(&self, target: Datastore) -> Result<()>

Unlock a datastore.

Examples found in repository?
examples/basic.rs (line 77)
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}
Source

pub async fn commit(&self) -> Result<()>

Commit the candidate configuration to running.

Examples found in repository?
examples/basic.rs (line 69)
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}
Source

pub async fn close_session(&self) -> Result<()>

Gracefully close the NETCONF session.

Examples found in repository?
examples/disconnect_watcher.rs (line 54)
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
Hide additional examples
examples/basic.rs (line 125)
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}
Source

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.

Source

pub async fn kill_session(&self, session_id: u32) -> Result<()>

Force-close another NETCONF session.

Source

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.

Source

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.

Source

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.

Source

pub fn connected_since(&self) -> Instant

The Instant when the session was established (hello exchange complete and reader task started).

Trait Implementations§

Source§

impl Drop for Session

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> Classify for T

Source§

type Classified = T

Source§

fn classify(self) -> T

Source§

impl<T> Declassify for T

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V