34
34
35
35
use std:: {
36
36
collections:: { BTreeMap , BTreeSet } ,
37
- fmt:: Display ,
37
+ fmt:: { self , Display } ,
38
38
hash:: Hash ,
39
39
net:: SocketAddr ,
40
40
str:: FromStr ,
@@ -80,7 +80,9 @@ impl NodeIdExt for NodeId {
80
80
81
81
/// Data about a node that may be published to and resolved from discovery services.
82
82
///
83
- /// This includes an optional [`RelayUrl`] and a set of direct addresses.
83
+ /// This includes an optional [`RelayUrl`], a set of direct addresses, and the optional
84
+ /// [`UserData`], a string that can be set by applications and is not parsed or used by iroh
85
+ /// itself.
84
86
///
85
87
/// This struct does not include the node's [`NodeId`], only the data *about* a certain
86
88
/// node. See [`NodeInfo`] for a struct that contains a [`NodeId`] with associated [`NodeData`].
@@ -90,6 +92,8 @@ pub struct NodeData {
90
92
relay_url : Option < RelayUrl > ,
91
93
/// Direct addresses where this node can be reached.
92
94
direct_addresses : BTreeSet < SocketAddr > ,
95
+ /// Optional user-defined [`UserData`] for this node.
96
+ user_data : Option < UserData > ,
93
97
}
94
98
95
99
impl NodeData {
@@ -98,6 +102,7 @@ impl NodeData {
98
102
Self {
99
103
relay_url,
100
104
direct_addresses,
105
+ user_data : None ,
101
106
}
102
107
}
103
108
@@ -113,11 +118,22 @@ impl NodeData {
113
118
self
114
119
}
115
120
121
+ /// Sets the user-defined data and returns the updated node data.
122
+ pub fn with_user_data ( mut self , user_data : Option < UserData > ) -> Self {
123
+ self . user_data = user_data;
124
+ self
125
+ }
126
+
116
127
/// Returns the relay URL of the node.
117
128
pub fn relay_url ( & self ) -> Option < & RelayUrl > {
118
129
self . relay_url . as_ref ( )
119
130
}
120
131
132
+ /// Returns the optional user-defined data of the node.
133
+ pub fn user_data ( & self ) -> Option < & UserData > {
134
+ self . user_data . as_ref ( )
135
+ }
136
+
121
137
/// Returns the direct addresses of the node.
122
138
pub fn direct_addresses ( & self ) -> & BTreeSet < SocketAddr > {
123
139
& self . direct_addresses
@@ -137,17 +153,84 @@ impl NodeData {
137
153
pub fn set_relay_url ( & mut self , relay_url : Option < RelayUrl > ) {
138
154
self . relay_url = relay_url
139
155
}
156
+
157
+ /// Sets the user-defined data of the node data.
158
+ pub fn set_user_data ( & mut self , user_data : Option < UserData > ) {
159
+ self . user_data = user_data;
160
+ }
140
161
}
141
162
142
163
impl From < NodeAddr > for NodeData {
143
164
fn from ( node_addr : NodeAddr ) -> Self {
144
165
Self {
145
166
relay_url : node_addr. relay_url ,
146
167
direct_addresses : node_addr. direct_addresses ,
168
+ user_data : None ,
169
+ }
170
+ }
171
+ }
172
+
173
+ // User-defined data that can be published and resolved through node discovery.
174
+ ///
175
+ /// Under the hood this is a UTF-8 String is no longer than [`UserData::MAX_LENGTH`] bytes.
176
+ ///
177
+ /// Iroh does not keep track of or examine the user-defined data.
178
+ ///
179
+ /// `UserData` implements [`FromStr`] and [`TryFrom<String>`], so you can
180
+ /// convert `&str` and `String` into `UserData` easily.
181
+ #[ derive( Debug , Clone , Eq , PartialEq , Ord , PartialOrd , Hash ) ]
182
+ pub struct UserData ( String ) ;
183
+
184
+ impl UserData {
185
+ /// The max byte length allowed for user-defined data.
186
+ ///
187
+ /// In DNS discovery services, the user-defined data is stored in a TXT record character string,
188
+ /// which has a max length of 255 bytes. We need to subtract the `user-data=` prefix,
189
+ /// which leaves 245 bytes for the actual user-defined data.
190
+ pub const MAX_LENGTH : usize = 245 ;
191
+ }
192
+
193
+ /// Error returned when an input value is too long for [`UserData`].
194
+ #[ derive( Debug , thiserror:: Error ) ]
195
+ #[ error( "User-defined data exceeds max length" ) ]
196
+ pub struct MaxLengthExceededError ;
197
+
198
+ impl TryFrom < String > for UserData {
199
+ type Error = MaxLengthExceededError ;
200
+
201
+ fn try_from ( value : String ) -> Result < Self , Self :: Error > {
202
+ if value. len ( ) > Self :: MAX_LENGTH {
203
+ Err ( MaxLengthExceededError )
204
+ } else {
205
+ Ok ( Self ( value) )
206
+ }
207
+ }
208
+ }
209
+
210
+ impl FromStr for UserData {
211
+ type Err = MaxLengthExceededError ;
212
+
213
+ fn from_str ( s : & str ) -> std:: result:: Result < Self , Self :: Err > {
214
+ if s. len ( ) > Self :: MAX_LENGTH {
215
+ Err ( MaxLengthExceededError )
216
+ } else {
217
+ Ok ( Self ( s. to_string ( ) ) )
147
218
}
148
219
}
149
220
}
150
221
222
+ impl fmt:: Display for UserData {
223
+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
224
+ write ! ( f, "{}" , self . 0 )
225
+ }
226
+ }
227
+
228
+ impl AsRef < str > for UserData {
229
+ fn as_ref ( & self ) -> & str {
230
+ & self . 0
231
+ }
232
+ }
233
+
151
234
/// Information about a node that may be published to and resolved from discovery services.
152
235
///
153
236
/// This struct couples a [`NodeId`] with its associated [`NodeData`].
@@ -181,9 +264,16 @@ impl From<&TxtAttrs<IrohAttr>> for NodeInfo {
181
264
. flatten ( )
182
265
. filter_map ( |s| SocketAddr :: from_str ( s) . ok ( ) )
183
266
. collect ( ) ;
267
+ let user_data = attrs
268
+ . get ( & IrohAttr :: UserData )
269
+ . into_iter ( )
270
+ . flatten ( )
271
+ . next ( )
272
+ . and_then ( |s| UserData :: from_str ( s) . ok ( ) ) ;
184
273
let data = NodeData {
185
274
relay_url : relay_url. map ( Into :: into) ,
186
275
direct_addresses,
276
+ user_data,
187
277
} ;
188
278
Self { node_id, data }
189
279
}
@@ -226,6 +316,12 @@ impl NodeInfo {
226
316
self
227
317
}
228
318
319
+ /// Sets the user-defined data and returns the updated node info.
320
+ pub fn with_user_data ( mut self , user_data : Option < UserData > ) -> Self {
321
+ self . data = self . data . with_user_data ( user_data) ;
322
+ self
323
+ }
324
+
229
325
/// Converts into a [`NodeAddr`] by cloning the needed fields.
230
326
pub fn to_node_addr ( & self ) -> NodeAddr {
231
327
NodeAddr {
@@ -321,6 +417,8 @@ pub(super) enum IrohAttr {
321
417
Relay ,
322
418
/// Direct address.
323
419
Addr ,
420
+ /// User-defined data
421
+ UserData ,
324
422
}
325
423
326
424
/// Attributes parsed from [`IROH_TXT_NAME`] TXT records.
@@ -343,6 +441,9 @@ impl From<&NodeInfo> for TxtAttrs<IrohAttr> {
343
441
for addr in & info. data . direct_addresses {
344
442
attrs. push ( ( IrohAttr :: Addr , addr. to_string ( ) ) ) ;
345
443
}
444
+ if let Some ( user_data) = & info. data . user_data {
445
+ attrs. push ( ( IrohAttr :: UserData , user_data. to_string ( ) ) ) ;
446
+ }
346
447
Self :: from_parts ( info. node_id , attrs. into_iter ( ) )
347
448
}
348
449
}
@@ -552,7 +653,8 @@ mod tests {
552
653
let node_data = NodeData :: new (
553
654
Some ( "https://example.com" . parse ( ) . unwrap ( ) ) ,
554
655
[ "127.0.0.1:1234" . parse ( ) . unwrap ( ) ] . into_iter ( ) . collect ( ) ,
555
- ) ;
656
+ )
657
+ . with_user_data ( Some ( "foobar" . parse ( ) . unwrap ( ) ) ) ;
556
658
let node_id = "vpnk377obfvzlipnsfbqba7ywkkenc4xlpmovt5tsfujoa75zqia"
557
659
. parse ( )
558
660
. unwrap ( ) ;
@@ -569,7 +671,8 @@ mod tests {
569
671
let node_data = NodeData :: new (
570
672
Some ( "https://example.com" . parse ( ) . unwrap ( ) ) ,
571
673
[ "127.0.0.1:1234" . parse ( ) . unwrap ( ) ] . into_iter ( ) . collect ( ) ,
572
- ) ;
674
+ )
675
+ . with_user_data ( Some ( "foobar" . parse ( ) . unwrap ( ) ) ) ;
573
676
let expected = NodeInfo :: from_parts ( secret_key. public ( ) , node_data) ;
574
677
let packet = expected. to_pkarr_signed_packet ( & secret_key, 30 ) . unwrap ( ) ;
575
678
let actual = NodeInfo :: from_pkarr_signed_packet ( & packet) . unwrap ( ) ;
0 commit comments