netget/server/dc_peer/
actions.rs1use 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});