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 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,91 @@ 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 data of the node data.
158
+ pub fn set_user_data ( & mut self , user_data : Option < UserData > ) {
159
+ self . user_data = user_data;
160
+ }
161
+
162
+ /// Converts into a [`NodeAddr`] and [`UserData`].
163
+ pub fn into_node_addr_and_user_data ( self , node_id : NodeId ) -> ( NodeAddr , Option < UserData > ) {
164
+ (
165
+ NodeAddr {
166
+ node_id,
167
+ relay_url : self . relay_url ,
168
+ direct_addresses : self . direct_addresses ,
169
+ } ,
170
+ self . user_data ,
171
+ )
172
+ }
140
173
}
141
174
142
175
impl From < NodeAddr > for NodeData {
143
176
fn from ( node_addr : NodeAddr ) -> Self {
144
177
Self {
145
178
relay_url : node_addr. relay_url ,
146
179
direct_addresses : node_addr. direct_addresses ,
180
+ user_data : None ,
181
+ }
182
+ }
183
+ }
184
+
185
+ // User defined data that can be published and resolved through node discovery.
186
+ ///
187
+ /// Under the hood this is a UTF-8 String that is less than or equal to
188
+ /// [`USER_DATA_MAX_LENGTH`] bytes.
189
+ ///
190
+ /// Iroh does not keep track of or examine user defined data.
191
+ #[ derive( Debug , Clone , Eq , PartialEq , Ord , PartialOrd , Hash ) ]
192
+ pub struct UserData ( String ) ;
193
+
194
+ /// The max byte length allowed for user defined data.
195
+ ///
196
+ /// In DNS discovery services, the user data is stored in a TXT record character string,
197
+ /// which has a max length of 255 bytes. We need to subtract "user-data=", which leaves
198
+ /// 245 bytes for the actual user data.
199
+ pub const USER_DATA_MAX_LENGTH : usize = 245 ;
200
+
201
+ /// Error returned when an input value is too long for [`UserData`].
202
+ #[ derive( Debug , thiserror:: Error ) ]
203
+ #[ error( "User-defined data exceeds max length" ) ]
204
+ pub struct MaxLengthExceededError ;
205
+
206
+ impl TryFrom < String > for UserData {
207
+ type Error = MaxLengthExceededError ;
208
+ fn try_from ( value : String ) -> Result < Self , Self :: Error > {
209
+ if value. len ( ) > USER_DATA_MAX_LENGTH {
210
+ Err ( MaxLengthExceededError )
211
+ } else {
212
+ Ok ( Self ( value) )
213
+ }
214
+ }
215
+ }
216
+
217
+ impl FromStr for UserData {
218
+ type Err = MaxLengthExceededError ;
219
+
220
+ fn from_str ( s : & str ) -> std:: result:: Result < Self , Self :: Err > {
221
+ if s. len ( ) > USER_DATA_MAX_LENGTH {
222
+ Err ( MaxLengthExceededError )
223
+ } else {
224
+ Ok ( Self ( s. to_string ( ) ) )
147
225
}
148
226
}
149
227
}
150
228
229
+ impl fmt:: Display for UserData {
230
+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
231
+ write ! ( f, "{}" , self . 0 )
232
+ }
233
+ }
234
+
235
+ impl AsRef < str > for UserData {
236
+ fn as_ref ( & self ) -> & str {
237
+ & self . 0
238
+ }
239
+ }
240
+
151
241
/// Information about a node that may be published to and resolved from discovery services.
152
242
///
153
243
/// This struct couples a [`NodeId`] with its associated [`NodeData`].
@@ -181,9 +271,16 @@ impl From<&TxtAttrs<IrohAttr>> for NodeInfo {
181
271
. flatten ( )
182
272
. filter_map ( |s| SocketAddr :: from_str ( s) . ok ( ) )
183
273
. collect ( ) ;
274
+ let user_data = attrs
275
+ . get ( & IrohAttr :: UserData )
276
+ . into_iter ( )
277
+ . flatten ( )
278
+ . next ( )
279
+ . and_then ( |s| UserData :: from_str ( s) . ok ( ) ) ;
184
280
let data = NodeData {
185
281
relay_url : relay_url. map ( Into :: into) ,
186
282
direct_addresses,
283
+ user_data,
187
284
} ;
188
285
Self { node_id, data }
189
286
}
@@ -200,6 +297,7 @@ impl From<NodeAddr> for NodeInfo {
200
297
let data = NodeData {
201
298
direct_addresses : value. direct_addresses ,
202
299
relay_url : value. relay_url ,
300
+ user_data : None ,
203
301
} ;
204
302
Self :: new ( value. node_id , data)
205
303
}
@@ -260,6 +358,18 @@ impl NodeInfo {
260
358
pub fn to_txt_strings ( & self ) -> Vec < String > {
261
359
self . to_attrs ( ) . to_txt_strings ( ) . collect ( )
262
360
}
361
+
362
+ /// Converts into a [`NodeAddr`] and optional [`UserData`].
363
+ pub fn into_node_addr_and_user_data ( self ) -> ( NodeAddr , Option < UserData > ) {
364
+ (
365
+ NodeAddr {
366
+ node_id : self . node_id ,
367
+ relay_url : self . data . relay_url ,
368
+ direct_addresses : self . data . direct_addresses ,
369
+ } ,
370
+ self . data . user_data ,
371
+ )
372
+ }
263
373
}
264
374
265
375
impl std:: ops:: Deref for NodeInfo {
@@ -300,6 +410,8 @@ pub(super) enum IrohAttr {
300
410
Relay ,
301
411
/// Direct address.
302
412
Addr ,
413
+ /// User-defined data
414
+ UserData ,
303
415
}
304
416
305
417
/// Attributes parsed from [`IROH_TXT_NAME`] TXT records.
@@ -322,6 +434,9 @@ impl From<&NodeInfo> for TxtAttrs<IrohAttr> {
322
434
for addr in & info. data . direct_addresses {
323
435
attrs. push ( ( IrohAttr :: Addr , addr. to_string ( ) ) ) ;
324
436
}
437
+ if let Some ( user_data) = & info. data . user_data {
438
+ attrs. push ( ( IrohAttr :: UserData , user_data. to_string ( ) ) ) ;
439
+ }
325
440
Self :: from_parts ( info. node_id , attrs. into_iter ( ) )
326
441
}
327
442
}
@@ -531,7 +646,8 @@ mod tests {
531
646
let node_data = NodeData :: new (
532
647
Some ( "https://example.com" . parse ( ) . unwrap ( ) ) ,
533
648
[ "127.0.0.1:1234" . parse ( ) . unwrap ( ) ] . into_iter ( ) . collect ( ) ,
534
- ) ;
649
+ )
650
+ . with_user_data ( Some ( "foobar" . parse ( ) . unwrap ( ) ) ) ;
535
651
let node_id = "vpnk377obfvzlipnsfbqba7ywkkenc4xlpmovt5tsfujoa75zqia"
536
652
. parse ( )
537
653
. unwrap ( ) ;
@@ -548,7 +664,8 @@ mod tests {
548
664
let node_data = NodeData :: new (
549
665
Some ( "https://example.com" . parse ( ) . unwrap ( ) ) ,
550
666
[ "127.0.0.1:1234" . parse ( ) . unwrap ( ) ] . into_iter ( ) . collect ( ) ,
551
- ) ;
667
+ )
668
+ . with_user_data ( Some ( "foobar" . parse ( ) . unwrap ( ) ) ) ;
552
669
let expected = NodeInfo :: new ( secret_key. public ( ) , node_data) ;
553
670
let packet = expected. to_pkarr_signed_packet ( & secret_key, 30 ) . unwrap ( ) ;
554
671
let actual = NodeInfo :: from_pkarr_signed_packet ( & packet) . unwrap ( ) ;
0 commit comments