Skip to main content

netget/server/dc_peer/
actions.rs

1use crate::llm::actions::{
2    protocol_trait::{ActionResult, Protocol, Server},
3    ActionDefinition,
4};
5use crate::protocol::{EventType, SpawnContext};
6use crate::server::p2p_support::{action, parameter};
7use crate::state::AppState;
8use anyhow::{ensure, Result};
9use serde_json::{json, Value};
10use std::{
11    future::Future,
12    net::SocketAddr,
13    pin::Pin,
14    sync::{Arc, LazyLock},
15};
16
17#[derive(Default)]
18pub struct DcPeerProtocol;
19impl DcPeerProtocol {
20    pub fn new() -> Self {
21        Self
22    }
23}
24pub fn reply() -> ActionDefinition {
25    action(
26        "dc_peer_reply",
27        "Handler-supplied file and file-list downloads, ranges and refusals",
28        vec![
29            parameter(
30                "data_base64",
31                "string",
32                "Complete source bytes (1 MiB max); requested range is selected by NetGet",
33                false,
34            ),
35            parameter(
36                "file_list_xml",
37                "string",
38                "XML file list to transmit, optionally BZip2-compressed",
39                false,
40            ),
41            parameter("error", "string", "Refuse this request", false),
42        ],
43        json!({"type":"dc_peer_reply","data_base64":"SGVsbG8="}),
44    )
45}
46impl Protocol for DcPeerProtocol {
47    fn protocol_name(&self) -> &'static str {
48        "NMDC Peer"
49    }
50    fn stack_name(&self) -> &'static str {
51        "ETH>IP>TCP>NMDC Peer"
52    }
53    fn keywords(&self) -> Vec<&'static str> {
54        vec!["dc_peer", "nmdc peer"]
55    }
56    fn description(&self) -> &'static str {
57        "Selected NMDC Peer file transfers listening role"
58    }
59    fn example_prompt(&self) -> &'static str {
60        "Listen on NMDC Peer port 412; supply protocol decisions with handlers"
61    }
62    fn group_name(&self) -> &'static str {
63        "Peer-to-peer"
64    }
65    fn get_startup_parameters(&self) -> Vec<crate::llm::actions::ParameterDefinition> {
66        let mut fields = crate::server::p2p_support::tls_parameters(true);
67        fields.push(crate::server::dc_peer::codec::nickname_parameter(
68            crate::server::dc_peer::codec::DEFAULT_LISTENER_NICKNAME,
69        ));
70        fields
71    }
72    fn get_startup_examples(&self) -> crate::llm::actions::StartupExamples {
73        let base = json!({"type":"open_server","base_stack":"dc_peer","port":412,"instruction":"Serve selected NMDC Peer operations"});
74        let mut fixed = base.clone();
75        fixed["event_handlers"] = json!([{"event_pattern":"dc_peer_request","handler":{"type":"static","actions":[reply().example]}}]);
76        let mut script = base.clone();
77        script["event_handlers"] = json!([{"event_pattern":"dc_peer_request","handler":{"type":"script","language":"python","code":"import json,sys\ne=json.load(sys.stdin)['event']\nprint(json.dumps({'actions':[{'type':'dc_peer_reply','error':'denied'}]}))"}}]);
78        crate::llm::actions::StartupExamples::new(base, script, fixed)
79    }
80    fn get_async_actions(&self, _: &AppState) -> Vec<ActionDefinition> {
81        vec![action(
82            "disconnect",
83            "Close this peer",
84            vec![],
85            json!({"type":"disconnect"}),
86        )]
87    }
88    fn get_sync_actions(&self) -> Vec<ActionDefinition> {
89        vec![reply()]
90    }
91    fn get_event_types(&self) -> Vec<EventType> {
92        EVENTS.clone()
93    }
94    fn metadata(&self) -> crate::protocol::ProtocolMetadataV2 {
95        crate::protocol::ProtocolMetadataV2::builder().state(crate::protocol::metadata::DevelopmentState::Experimental).implementation("Native bounded NMDC Peer selected codec; TCP and optional implicit TLS").llm_control("Handler-supplied file and file-list downloads, ranges and refusals").e2e_testing("Independent peer exchanges, malformed input, owner shutdown and standalone feature builds").notes("NMDC uploader/listener and downloader/connector. ADCGET/ADCSND file requests, XML/BZip2 file lists and optional full-payload Tiger tree hash verification. Configurable local nickname must match hub rendezvous identity. 1 MiB per transfer; no filesystem access, multi-source scheduler, peer discovery or push uploads. 256 connections, 30 s first frame, 600 s idle, 10 s handshake/exchange. No persistent protocol data; handlers supply content and decisions.").max_inbound_bytes(super::codec::MAX_COMMAND).well_known_port(412).build()
96    }
97}
98impl Server for DcPeerProtocol {
99    fn spawn(&self, ctx: SpawnContext) -> Pin<Box<dyn Future<Output = Result<SocketAddr>> + Send>> {
100        Box::pin(async move {
101            let name = ctx
102                .startup_params
103                .as_ref()
104                .map(|p| p.get_optional_string("nickname"))
105                .transpose()?
106                .flatten()
107                .unwrap_or_else(|| super::codec::DEFAULT_LISTENER_NICKNAME.into());
108            super::codec::nickname(&name)?;
109            crate::server::p2p_support::spawn(
110                ctx,
111                Arc::new(Self),
112                move || super::codec::Device::with_nickname(name.clone()),
113                &EVENTS,
114            )
115            .await
116        })
117    }
118    fn execute_action(&self, v: Value) -> Result<ActionResult> {
119        if v["type"] == "disconnect" {
120            return Ok(ActionResult::CloseConnection);
121        }
122        ensure!(v["type"] == "dc_peer_reply", "unknown action");
123        Ok(ActionResult::Custom {
124            name: "dc_peer_reply".into(),
125            data: v,
126        })
127    }
128}
129pub static EVENTS: LazyLock<Vec<EventType>> = LazyLock::new(|| {
130    vec![EventType::new(
131        "dc_peer_request",
132        "Handler-supplied file and file-list downloads, ranges and refusals",
133        reply().example,
134    )
135    .with_actions(vec![reply()])
136    .with_parameters(vec![
137        parameter("operation", "string", "Selected protocol operation", true),
138        parameter(
139            "identifier",
140            "string",
141            "Requested object, message or file identifier",
142            false,
143        ),
144    ])]
145});