irontide_session_core/
persistence.rs1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
5pub struct DhtNodeEntry {
6 pub host: String,
8 pub port: i64,
10}
11
12#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14pub struct PeerStrikeEntry {
15 pub ip: String,
17 pub count: i64,
19}
20
21#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
28pub struct SessionState {
29 #[serde(rename = "dht-nodes", default)]
31 pub dht_nodes: Vec<DhtNodeEntry>,
32 #[serde(
35 rename = "dht-node-id",
36 default,
37 skip_serializing_if = "Option::is_none"
38 )]
39 pub dht_node_id: Option<String>,
40 #[serde(rename = "torrents", default)]
42 pub torrents: Vec<irontide_core::FastResumeData>,
43 #[serde(rename = "banned-peers", default)]
45 pub banned_peers: Vec<String>,
46 #[serde(rename = "peer-strikes", default)]
48 pub peer_strikes: Vec<PeerStrikeEntry>,
49}
50
51impl SessionState {
52 #[must_use]
54 pub fn new() -> Self {
55 Self {
56 dht_nodes: Vec::new(),
57 dht_node_id: None,
58 torrents: Vec::new(),
59 banned_peers: Vec::new(),
60 peer_strikes: Vec::new(),
61 }
62 }
63}
64
65impl Default for SessionState {
66 fn default() -> Self {
67 Self::new()
68 }
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74 use pretty_assertions::assert_eq;
75
76 #[test]
77 fn session_state_bencode_round_trip() {
78 let state = SessionState {
79 dht_nodes: vec![
80 DhtNodeEntry {
81 host: "router.bittorrent.com".into(),
82 port: 6881,
83 },
84 DhtNodeEntry {
85 host: "dht.transmissionbt.com".into(),
86 port: 6881,
87 },
88 ],
89 dht_node_id: None,
90 torrents: vec![irontide_core::FastResumeData::new(
91 vec![0xAA; 20],
92 "test-torrent".into(),
93 "/downloads".into(),
94 )],
95 banned_peers: Vec::new(),
96 peer_strikes: Vec::new(),
97 };
98
99 let encoded = irontide_bencode::to_bytes(&state).unwrap();
100 let decoded: SessionState = irontide_bencode::from_bytes(&encoded).unwrap();
101 assert_eq!(state, decoded);
102 }
103
104 #[test]
105 fn session_state_with_node_id_round_trip() {
106 let state = SessionState {
107 dht_nodes: vec![DhtNodeEntry {
108 host: "1.2.3.4".into(),
109 port: 6881,
110 }],
111 dht_node_id: Some("26d8457c04424098fd9e615b297745c772f49706".into()),
112 torrents: vec![],
113 banned_peers: vec![],
114 peer_strikes: vec![],
115 };
116
117 let encoded = irontide_bencode::to_bytes(&state).unwrap();
118 let encoded_str = String::from_utf8_lossy(&encoded);
119 assert!(
120 encoded_str.contains("dht-node-id"),
121 "encoded bencode should contain dht-node-id key: {encoded_str}"
122 );
123
124 let decoded: SessionState = irontide_bencode::from_bytes(&encoded).unwrap();
125 assert_eq!(state.dht_node_id, decoded.dht_node_id);
126 }
127
128 #[test]
129 fn empty_session_state_round_trip() {
130 let state = SessionState::new();
131
132 let encoded = irontide_bencode::to_bytes(&state).unwrap();
133 let decoded: SessionState = irontide_bencode::from_bytes(&encoded).unwrap();
134 assert_eq!(state, decoded);
135 }
136
137 #[test]
138 fn session_state_with_bans_round_trip() {
139 let state = SessionState {
140 dht_nodes: vec![],
141 dht_node_id: None,
142 torrents: vec![],
143 banned_peers: vec!["10.0.0.1".into(), "192.168.1.5".into()],
144 peer_strikes: vec![
145 PeerStrikeEntry {
146 ip: "10.0.0.1".into(),
147 count: 3,
148 },
149 PeerStrikeEntry {
150 ip: "10.0.0.2".into(),
151 count: 1,
152 },
153 ],
154 };
155
156 let encoded = irontide_bencode::to_bytes(&state).unwrap();
157 let decoded: SessionState = irontide_bencode::from_bytes(&encoded).unwrap();
158 assert_eq!(state, decoded);
159 assert_eq!(decoded.banned_peers.len(), 2);
160 assert_eq!(decoded.peer_strikes.len(), 2);
161 }
162
163 #[test]
164 fn session_state_backward_compatible() {
165 let old_state = SessionState {
167 dht_nodes: vec![DhtNodeEntry {
168 host: "example.com".into(),
169 port: 6881,
170 }],
171 dht_node_id: None,
172 torrents: vec![],
173 banned_peers: vec![],
174 peer_strikes: vec![],
175 };
176 let encoded = irontide_bencode::to_bytes(&old_state).unwrap();
177
178 let decoded: SessionState = irontide_bencode::from_bytes(&encoded).unwrap();
181 assert!(decoded.banned_peers.is_empty());
182 assert!(decoded.peer_strikes.is_empty());
183 assert_eq!(decoded.dht_nodes.len(), 1);
184 }
185}