1 module tagion.gossip.AddressBook; 2 3 import core.thread : Thread; 4 import std.file : exists; 5 import std.format; 6 import std.path : setExtension; 7 import std.random; 8 import tagion.basic.tagionexceptions; 9 import tagion.crypto.Types : Pubkey; 10 import tagion.dart.DART : DART; 11 import tagion.dart.DARTRim; 12 import tagion.hibon.HiBONFile; 13 import tagion.hibon.HiBONRecord; 14 import tagion.logger.Logger : log; 15 import tagion.utils.Miscellaneous : cutHex; 16 17 @safe class AddressBookException : TagionException { 18 this(string msg, string file = __FILE__, size_t line = __LINE__) pure { 19 super(msg, file, line); 20 } 21 } 22 23 /** check function used in the HiBON package */ 24 alias check = Check!(AddressBookException); 25 26 enum lockext = "lock"; 27 28 /** 29 * Lock file 30 * @param filename - file to lock 31 */ 32 void lock(string filename) { 33 import std.file : fwrite = write; 34 35 immutable file_lock = filename.setExtension(lockext); 36 file_lock.fwrite(null); 37 } 38 39 /** 40 * Unlock file 41 * @param filename - file to unlock 42 */ 43 void unlock(string filename) nothrow { 44 import std.file : remove; 45 46 immutable file_lock = filename.setExtension(lockext); 47 try { 48 file_lock.remove; 49 } 50 catch (Exception e) { 51 // ignore 52 } 53 } 54 55 /** 56 * Check file is locked or unlocked 57 * @param filename - file to check 58 * @return true if locked 59 */ 60 bool locked(string filename) { 61 immutable file_lock = filename.setExtension(lockext); 62 return file_lock.exists; 63 } 64 65 /** Address book for libp2p */ 66 @safe 67 synchronized class AddressBook { 68 import core.time; 69 70 alias NodeAddresses = NodeAddress[Pubkey]; 71 alias NodePair = typeof((() @trusted => (cast(NodeAddresses) addresses).byKeyValue.front)()); 72 73 /** \struct AddressDirectory 74 * Storage for node addresses 75 */ 76 static struct AddressDirectory { 77 /* associative array with node addresses 78 * node address - value, public key - key 79 */ 80 NodeAddresses addresses; 81 mixin HiBONRecord; 82 } 83 84 /** used for lock, unlock file */ 85 enum max_count = 3; 86 /** used for lock, unlock file */ 87 protected int timeout = 300; 88 /** nodes amount */ 89 protected size_t nodes; 90 91 /** 92 * Set number of active nodes 93 * @param nodes - number of active nodes 94 */ 95 void number_of_active_nodes(const size_t nodes) pure nothrow 96 in { 97 debug log.trace("this.nodes %s set to %s", this.nodes, nodes); 98 assert(this.nodes is size_t.init); 99 } 100 do { 101 this.nodes = nodes; 102 } 103 104 protected { 105 Random rnd; 106 } 107 this() { 108 rnd = shared(Random)(unpredictableSeed); 109 } 110 111 /** Addresses for node */ 112 protected shared(NodeAddresses) addresses; 113 114 /** 115 * Create associative array with public keys and addresses of nodes 116 * @return addresses of nodes 117 */ 118 immutable(NodeAddress[Pubkey]) _data() @trusted { 119 pragma(msg, "fixme(cbr): AddressBook._data This function should be removed when the addressbook has been implemented"); 120 NodeAddress[Pubkey] result; 121 foreach (pkey, addr; addresses) { 122 result[pkey] = addr; 123 } 124 return cast(immutable) result; 125 } 126 127 /** 128 * Overwrite node addresses associative array 129 * @param addrs - array to overwrite 130 */ 131 private void overwrite(const(NodeAddress[Pubkey]) addrs) { 132 addresses = null; 133 foreach (pkey, addr; addrs) { 134 addresses[pkey] = addr; 135 } 136 } 137 138 /** 139 * Load file if it's exist 140 * @param filename - file to load 141 * @param do_unlock - flag for unlock file 142 */ 143 void load(string filename, bool do_unlock = true) @trusted { 144 void local_read() @safe { 145 auto dir = filename.fread!AddressDirectory; 146 overwrite(dir.addresses); 147 } 148 149 if (filename.exists) { 150 int count_down = max_count; 151 while (filename.locked) { 152 Thread.sleep(timeout.msecs); 153 count_down--; 154 check(count_down > 0, format("The bootstrap file is locked. Timeout can't load file %s", filename)); 155 } 156 filename.lock; 157 local_read; 158 if (do_unlock) { 159 filename.unlock; 160 } 161 } 162 } 163 164 /** 165 * Save addresses to file 166 * @param filename - file to save addresses 167 * @param nonelock - flag tolock file for save operation 168 */ 169 void save(string filename, bool nonelock = false) @trusted { 170 void local_write() { 171 AddressDirectory dir; 172 dir.addresses = cast(NodeAddress[Pubkey]) addresses; 173 filename.fwrite(dir); 174 } 175 176 int count_down = max_count; 177 while (!nonelock && filename.locked) { 178 Thread.sleep(timeout.msecs); 179 count_down--; 180 check(count_down > 0, format("The bootstrap file is locked. Timeout can't save file %s", filename)); 181 } 182 filename.lock; 183 local_write; 184 filename.unlock; 185 } 186 187 /** 188 * Init NodeAddress if public key exist 189 * @param pkey - public key for check 190 * @return initialized node address 191 */ 192 immutable(NodeAddress) opIndex(const Pubkey pkey) const pure nothrow { 193 auto addr = pkey in addresses; 194 if (addr) { 195 return cast(immutable)(*addr); 196 } 197 return NodeAddress.init; 198 } 199 200 /** 201 * Create associative array addresses 202 * @param addr - value 203 * @param pkey - key 204 */ 205 void opIndexAssign(const NodeAddress addr, const Pubkey pkey) 206 in ((pkey in addresses) is null, format("Address %s has already been set", pkey.cutHex)) 207 do { 208 import std.stdio; 209 import tagion.utils.Miscellaneous : cutHex; 210 211 addresses[pkey] = addr; 212 } 213 214 /** 215 * Remove addresses by public key 216 * @param pkey - public key fo remove addresses 217 */ 218 void erase(const Pubkey pkey) pure nothrow { 219 addresses.remove(pkey); 220 } 221 222 /** 223 * Check for a public key in network 224 * @param pkey - public key fo check 225 * @return true if public key exist 226 */ 227 bool exists(const Pubkey pkey) const nothrow { 228 return (pkey in addresses) !is null; 229 } 230 231 /** 232 * Check for an active public key in network 233 * @param pkey - public key fo check 234 * @return true if pkey active 235 */ 236 bool isActive(const Pubkey pkey) const pure nothrow { 237 return (pkey in addresses) !is null; 238 } 239 240 /** 241 * Return active node channels in network 242 * @return active node channels 243 */ 244 Pubkey[] activeNodeChannels() @trusted const pure nothrow { 245 auto channels = (cast(NodeAddresses) addresses).keys; 246 return channels; 247 } 248 249 const(string) getAddress(const Pubkey pkey) const pure nothrow { 250 return addresses[pkey].address; 251 } 252 253 /** 254 * Return amount of active nodes in network 255 * @return amount of active nodes 256 */ 257 size_t numOfActiveNodes() const pure nothrow { 258 return addresses.length; 259 } 260 261 /** 262 * Return amount of nodes in networt 263 * @return amount of nodes 264 */ 265 size_t numOfNodes() const pure nothrow { 266 return addresses.length; 267 } 268 269 /** 270 * Check that nodes >= 4 and addresses >= nodes 271 * @return true if network ready 272 */ 273 bool isReady() const pure nothrow { 274 return (nodes >= 4) && (addresses.length >= nodes); 275 } 276 277 /** 278 * For random generation node pair 279 * @return node pair 280 */ 281 immutable(NodePair) random() @trusted const pure { 282 if (addresses.length) { 283 import std.range : dropExactly; 284 285 auto _addresses = cast(NodeAddresses) addresses; 286 const random_key_index = uniform(0, addresses.length, cast(Random) rnd); 287 return _addresses.byKeyValue.dropExactly(random_key_index).front; 288 } 289 return NodePair.init; 290 } 291 292 /** 293 * Select active channel by index 294 * @param index - index to select active channel 295 * @return active channel 296 */ 297 const(Pubkey) selectActiveChannel(const size_t index) @trusted const pure { 298 import std.range : dropExactly; 299 300 auto _addresses = cast(NodeAddresses) addresses; 301 return _addresses.byKey.dropExactly(index).front; 302 } 303 } 304 305 static shared(AddressBook) addressbook; 306 307 shared static this() { 308 addressbook = new shared(AddressBook); 309 } 310 311 /** 312 * \struct NodeAddress 313 * Struct for node addresses 314 */ 315 @safe 316 @recordType("NNR") 317 struct NodeAddress { 318 enum tcp_token = "/tcp/"; 319 enum p2p_token = "/p2p/"; 320 enum intrn_token = "/node/"; 321 /** node address */ 322 string address; 323 /** If true, then struct with node addresses is used as an address 324 * If false, then the local address used 325 */ 326 bool is_marshal; 327 /** node id */ 328 string id; 329 /** node port */ 330 uint port; 331 /** DART sector */ 332 SectorRange sector; 333 334 mixin HiBONRecord!( 335 q{ 336 this(string address) { 337 pragma(msg, "fixme(pr): addressbook for mode0 should be created instead"); 338 this.address = address; 339 } 340 this(string address, const ulong port_base, bool marshal = false) { 341 import std.string; 342 343 try { 344 this.address = address; 345 this.is_marshal = marshal; 346 if (!marshal) { 347 pragma(msg, "fixme(cbr): This code should be done with a regex"); 348 this.id = address[address.lastIndexOf(p2p_token) + 5 .. $]; 349 auto tcpIndex = address.indexOf(tcp_token) + tcp_token.length; 350 this.port = to!uint(address[tcpIndex .. tcpIndex + 4]); 351 352 const node_number = this.port - port_base; 353 354 sector = SectorRange(0, 0); 355 356 } 357 else if (address[0..intrn_token.length] != intrn_token) { 358 import std.json; 359 auto json = parseJSON(address); 360 this.id = json["ID"].str; 361 auto addr = (() @trusted => json["Addrs"].array()[0].str())(); 362 auto tcpIndex = addr.indexOf(tcp_token) + tcp_token.length; 363 this.port = to!uint(addr[tcpIndex .. tcpIndex + 4]); 364 } 365 } 366 catch (Exception e) { 367 log.fatal(e.msg); 368 } 369 } 370 }); 371 372 /** 373 * Parse node address 374 * @param addr - address to parse 375 * @return parsed address 376 */ 377 static string parseAddr(string addr) { 378 import std.string; 379 380 pragma(msg, "fixme(cbr): change this to a more bust parse (use regex)"); 381 string result; 382 const firstpartAddr = addr.indexOf('[') + 1; 383 const secondpartAddr = addr[firstpartAddr .. $].indexOf(' ') + firstpartAddr; 384 const firstpartId = addr.indexOf('{') + 1; 385 const secondpartId = addr.indexOf(':'); 386 result = addr[firstpartAddr .. secondpartAddr] ~ p2p_token ~ addr[firstpartId .. secondpartId]; 387 return result; 388 } 389 390 /** 391 * Parse node address to string 392 * @return string address 393 */ 394 public string toString() { 395 return address; 396 } 397 }