model.py
1 # This file is part of DarkFi (https://dark.fi) 2 # 3 # Copyright (C) 2020-2025 Dyne.org foundation 4 # 5 # This program is free software: you can redistribute it and/or modify 6 # it under the terms of the GNU Affero General Public License as 7 # published by the Free Software Foundation, either version 3 of the 8 # License, or (at your option) any later version. 9 # 10 # This program is distributed in the hope that it will be useful, 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 # GNU Affero General Public License for more details. 14 # 15 # You should have received a copy of the GNU Affero General Public License 16 # along with this program. If not, see <https://www.gnu.org/licenses/>. 17 18 import logging, time 19 import datetime as dt 20 from collections import defaultdict as dd 21 22 23 class Model: 24 25 def __init__(self): 26 self.nodes = {} 27 self.liliths = {} 28 29 def add_node(self, node): 30 channel_lookup = {} 31 name = list(node.keys())[0] 32 values = list(node.values())[0] 33 info = values['result'] 34 channels = info['channels'] 35 36 self.nodes[name] = {} 37 self.nodes[name]['outbound'] = {} 38 self.nodes[name]['inbound'] = {} 39 self.nodes[name]['manual'] = {} 40 self.nodes[name]['direct'] = {} 41 self.nodes[name]['direct_peer_discovery'] = None 42 self.nodes[name]['event'] = {} 43 self.nodes[name]['seed'] = {} 44 self.nodes[name]['msgs'] = dd(list) 45 46 for channel in channels: 47 id = channel['id'] 48 channel_lookup[id] = channel 49 50 for channel in channels: 51 if channel['session'] != 'inbound': 52 continue 53 id = channel['id'] 54 url = channel_lookup[id]['url'] 55 self.nodes[name]['inbound'][f'{id}'] = url 56 57 for i, id in enumerate(info['outbound_slots']): 58 if id == 0: 59 outbounds = self.nodes[name]['outbound'][f'{i}'] = ['none', 0] 60 continue 61 assert id in channel_lookup 62 url = channel_lookup[id]['url'] 63 outbounds = self.nodes[name]['outbound'][f'{i}'] = [url, id] 64 65 for channel in channels: 66 if channel['session'] != 'seed': 67 continue 68 id = channel['id'] 69 url = channel['url'] 70 self.nodes[name]['seed'][f'{id}'] = url 71 72 for channel in channels: 73 if channel['session'] != 'manual': 74 continue 75 id = channel['id'] 76 url = channel['url'] 77 self.nodes[name]['manual'][f'{id}'] = url 78 79 for channel in channels: 80 if channel['session'] != 'direct': 81 continue 82 id = channel['id'] 83 url = channel['url'] 84 self.nodes[name]['direct'][f'{url}'] = [url, id] 85 86 def add_offline(self, node, is_lilith: bool): 87 name = list(node.keys())[0] 88 values = list(node.values())[0] 89 if is_lilith: 90 self.liliths[name] = values 91 else: 92 self.nodes[name] = values 93 94 def add_event(self, event): 95 name = list(event.keys())[0] 96 values = list(event.values())[0] 97 params = values.get('params') 98 event = params[0].get('event') 99 info = params[0].get('info') 100 101 t = time.localtime() 102 current_time = time.strftime('%H:%M:%S', t) 103 104 match event: 105 case 'send': 106 nano = info.get('time') 107 cmd = info.get('cmd') 108 chan = info.get('chan') 109 addr = chan.get('addr') 110 t = (dt.datetime 111 .fromtimestamp(int(nano)/1000000000) 112 .strftime('%H:%M:%S')) 113 msgs = self.nodes[name]['msgs'] 114 msgs[addr].append((t, event, cmd)) 115 case 'recv': 116 nano = info.get('time') 117 cmd = info.get('cmd') 118 chan = info.get('chan') 119 addr = chan.get('addr') 120 t = (dt.datetime 121 .fromtimestamp(int(nano)/1000000000) 122 .strftime('%H:%M:%S')) 123 msgs = self.nodes[name]['msgs'] 124 msgs[addr].append((t, event, cmd)) 125 case 'inbound_connected': 126 addr = info['addr'] 127 id = info.get('channel_id') 128 self.nodes[name]['inbound'][f'{id}'] = addr 129 logging.debug(f'{current_time} inbound (connect): {addr}') 130 case 'inbound_disconnected': 131 addr = info['addr'] 132 id = info.get('channel_id') 133 self.nodes[name]['inbound'][f'{id}'] = {} 134 logging.debug(f'{current_time} inbound (disconnect): {addr}') 135 case 'outbound_slot_sleeping': 136 slot = info['slot'] 137 event = self.nodes[name]['event'] 138 event[(f'{name}', f'{slot}')] = ['sleeping', 0] 139 logging.debug(f'{current_time} slot {slot}: sleeping') 140 case 'outbound_slot_connecting': 141 slot = info['slot'] 142 addr = info['addr'] 143 event = self.nodes[name]['event'] 144 event[(f'{name}', f'{slot}')] = [f'connecting: addr={addr}', 0] 145 logging.debug(f'{current_time} slot {slot}: connecting addr={addr}') 146 case 'outbound_slot_connected': 147 slot = info['slot'] 148 addr = info['addr'] 149 event = self.nodes[name]['event'] 150 event[(f'{name}', f'{slot}')] = [f'connected: addr={addr}', 0] 151 id = info['channel_id'] 152 self.nodes[name]['outbound'][f'{slot}'] = [addr, id] 153 logging.debug(f'{current_time} slot {slot}: connected addr={addr}') 154 case 'outbound_slot_disconnected': 155 slot = info['slot'] 156 err = info['err'] 157 event = self.nodes[name]['event'] 158 event[(f'{name}', f'{slot}')] = [f'disconnected: {err}', 0] 159 logging.debug(f'{current_time} slot {slot}: disconnected err={err}') 160 case 'outbound_peer_discovery': 161 attempt = info['attempt'] 162 state = info['state'] 163 event = self.nodes[name]['event'] 164 key = (f'{name}', 'outbound') 165 event[key] = f'peer discovery: {state} (attempt {attempt})' 166 logging.debug(f'{current_time} peer_discovery: {state} (attempt {attempt})') 167 case 'direct_connecting': 168 connect_addr = info['connect_addr'] 169 event = self.nodes[name]['event'] 170 event[(f'{name}', f'{connect_addr}')] = [f'connecting: addr={connect_addr}', 0] 171 self.nodes[name]['direct'][f'{connect_addr}'] = ['', 0] 172 logging.debug(f'{current_time} direct (connecting): addr={connect_addr}') 173 case 'direct_connected': 174 connect_addr = info['connect_addr'] 175 addr = info['addr'] 176 id = info['channel_id'] 177 event = self.nodes[name]['event'] 178 event[(f'{name}', f'{connect_addr}')] = [addr, id] 179 self.nodes[name]['direct'][f'{connect_addr}'] = [addr, id] 180 logging.debug(f'{current_time} direct (connected): addr={addr}') 181 case 'direct_disconnected': 182 connect_addr = info['connect_addr'] 183 err = info['err'] 184 self.nodes[name]['direct'][f'{connect_addr}'] = {} 185 logging.debug(f'{current_time} direct (disconnected): addr={connect_addr} err={err}') 186 case 'direct_peer_discovery': 187 if self.nodes[name]['direct_peer_discovery'] is None: 188 self.nodes[name]['direct_peer_discovery'] = 0 189 attempt = info['attempt'] 190 state = info['state'] 191 event = self.nodes[name]['event'] 192 key = (f'{name}', 'direct') 193 event[key] = f'peer discovery: {state} (attempt {attempt})' 194 logging.debug(f'{current_time} peer_discovery: {state} (attempt {attempt})') 195 196 197 def add_lilith(self, lilith): 198 key = list(lilith.keys())[0] 199 values = list(lilith.values())[0] 200 info = values['result'] 201 spawns = info['spawns'] 202 203 self.liliths[key] = {} 204 self.liliths[key]['spawns'] = {} 205 206 for (i, spawn) in enumerate(spawns): 207 name = spawn['name'] 208 urls = spawn['urls'] 209 whitelist = spawn['whitelist'] 210 greylist = spawn['greylist'] 211 goldlist = spawn['goldlist'] 212 213 spawn = self.liliths[key]['spawns'][name] = {} 214 spawn['urls'] = urls 215 spawn['whitelist'] = whitelist 216 spawn['greylist'] = greylist 217 spawn['goldlist'] = goldlist 218 219 #logging.debug(f'added lilith {self.liliths}') 220 221 def __repr__(self): 222 return f'{self.nodes}' 223 return f'{self.liliths}'