├── LICENSE ├── README.md ├── analysis └── subnet_finder.py ├── scripts ├── ether_ipv4.zeek ├── identify_paths.zeek ├── main.zeek └── tracedroute.zeek ├── static ├── aws-vps.png ├── home.png ├── mhack-defcon.png └── white-boarded-c.jpg ├── viz ├── aaalm.js ├── d3.v5.js ├── index.html ├── js-pcb │ ├── dsn2pcb.js │ ├── inter-map.js │ ├── layer.js │ ├── main.js │ ├── mymath.js │ ├── router.js │ ├── view.js │ └── worker.js ├── merri.ttf └── packer.js └── zkg.meta /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Secure Network S.R.L., Nicholas Skelsey 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aaalm 2 | 3 | aaalm is a zeek package that passively infers the structure of an IPv4 network over Ethernet from communication among hosts. 4 | 5 | It will discover gateways, routers, and associate devices to subnets and gateways based on hueristics from analysis of raw packets and connections. It can even infer routing paths if the analyzed traffic contains icmp responses to a traceroute. 6 | 7 | The tool inside of `/viz` can then interpret this information to generate a diagram suitable for printing on A4 paper or even bigger on A3, hence the name, the A3 Lan Mapper. 8 | 9 | Here are some examples. 10 | 11 | What a VPS on Amazon can see by running traceroute. 12 | 13 | ![aws vps diagram](https://raw.githubusercontent.com/nskelsey/aaalm/master/static/aws-vps.png) 14 | 15 | The same diagram in A4 16 | 17 | ![printed a4 diagram](https://raw.githubusercontent.com/nskelsey/aaalm/master/static/white-boarded-c.jpg) 18 | 19 | The network at my apartment 20 | 21 | ![My home network](https://raw.githubusercontent.com/nskelsey/aaalm/master/static/home.png) 22 | 23 | During CTFs teams must attack and defend network services. This is what the network at [DEFCON 27](https://www.defcon.org/html/defcon-27/dc-27-ctf.html) looked like to [mHACKeroni](https://mhackeroni.it/): 24 | 25 | ![mhack defcon](https://raw.githubusercontent.com/nskelsey/aaalm/master/static/mhack-defcon.png) 26 | 27 | ## Installation 28 | 29 | Install [Zeek](https://docs.zeek.org/en/stable/quickstart/) and its package manager [zkg](https://docs.zeek.org/projects/package-manager/en/stable/quickstart.html). 30 | 31 | Use the `zkg` to download and install the package. 32 | 33 | ```zsh 34 | > zkg install aaalm 35 | ``` 36 | 37 | Otherwise clone this repository. 38 | 39 | 40 | ## Usage 41 | 42 | Run zeek with the following command with a packet capture file with lots of inter device communication. 43 | 44 | ```zsh 45 | > zeek -r path/to/file.pcap -b main.zeek 46 | ``` 47 | 48 | 49 | Editing `Verbose` flag will output a set of subnets that can be used as a starting point to define the `Sites::local_nets` set. 50 | 51 | ```zsh 52 | > zeek -r path/to/file.pcap -b main.zeek -e "redef EtherIPv4::Verbose = T;" 53 | ``` 54 | 55 | The `devices.log` file will contain the inferred network structure, while `subnet.log` will contain the identifed local networks. 56 | 57 | 58 | ### Generating the graphics 59 | 60 | Visit [my site](https://nskelsey.com/aaalm) to try it out. 61 | 62 | The DIY version requires that you can run a webserver locally. With python3 it's easy: 63 | 64 | ``` 65 | > python -m http.server -b localhost 66 | ``` 67 | 68 | Now navigate to [the index](https://localhost:8000/) with your browser and follow the instructions to generate a map. 69 | 70 | ## Notes 71 | 72 | ### Performance 73 | 74 | Note that this package uses the `raw_packet` event to analyze __every packet__ contained in a pcap or observed on the monitored interface. 75 | If you are using a cluster to monitor gigabit loads __do not use__ this package __in realtime__. 76 | Execution against __hundreds of megabytes__ of traffic produces meaningful output in __less than thirty seconds__. 77 | 78 | If you are monitoring traffic in tens or hundreds of gigabits per second but do not already know your network's layout, you may have __other problems__. 79 | 80 | #### Visualizing routing paths 81 | 82 | If your packet capture file contains traffic from programs like traceroute, it's possible to visualize these paths. 83 | 84 | Note that only traceroutes performed with low TTL UDP packets which solicit ICMP responses from servers are tracked. 85 | Further there is some bug with the signature `detect-low-ttls.sig` that breaks detection even with some UDP traffic. 86 | 87 | In any case the reconstructed route will be logged to the file `tracedroute.log`. 88 | 89 | ### Techniques Used 90 | 91 | #### Placing devices in subnets 92 | By default the script uses a greedly algorithm to place addresses into `\24`. 93 | 94 | For more precise subnet grouping, the script in `analysis/find_subnet.py` will compute a series of statistical tests to determine probable subnet groupings. The script will modify `*.log` the files to improve the quality of subnet groupings if the default behavoir is insufficient. 95 | 96 | ```zsh 97 | > python2 subnet_finder.py in-device.log 98 | # Will output device.log and subnet.log 99 | ``` 100 | 101 | Afterwards the `net_routes.log` must also be updated by hand to reflect these new changes. 102 | 103 | #### Identifying routers 104 | 105 | Router identification works by tracking unique MACs inside of the `l2_header` and storing the set of `ip_src` addresses, then simply checking if these MACs are originating traffic with multiple source IP addresses. 106 | 107 | #### Identifying gateways 108 | 109 | Gateway identification works similiarly but captures the special case where emitted traffic seems to originate from a public IP address. 110 | -------------------------------------------------------------------------------- /analysis/subnet_finder.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import csv 3 | import json 4 | import sys 5 | 6 | from collections import OrderedDict 7 | 8 | def ip_string_to_number(ip): 9 | return reduce(lambda x,y: x|y, map(lambda (x,y): x << y, zip(map(int,ip.split('.')),reversed(range(0,32,8))))) 10 | 11 | 12 | def ip_number_to_string(ip): 13 | return '.'.join(str((ip & (0xff << x))>>x) for x in reversed(range(0,32,8))) 14 | 15 | 16 | def get_ip_nums_from_file(fp): 17 | return sorted(map(lambda x:ip_string_to_number(x.strip()),fp)) 18 | 19 | 20 | def group_sorted_ips_by_subnet(ips): 21 | # assume ips are sorted numbers 22 | found_subnets = OrderedDict() 23 | netmask_size = 2 24 | for ip in ips: 25 | if ip == 0 or ip == 4294967295: 26 | continue 27 | my_netmask_size = netmask_size 28 | my_netmask = (ip >> (my_netmask_size)) << (my_netmask_size) 29 | # the all-zeros and the all-ones host values are reserved for the network address of the subnet and its broadcast address 30 | # if we get all-zeros or all-ones, the subnet mask must be larger 31 | # while (ip ^ my_netmask) in (0, (1<> (my_netmask_size)) << (my_netmask_size) 35 | my_netmask = (my_netmask, 32 - my_netmask_size) 36 | ip_list = found_subnets.get(my_netmask,[]) 37 | ip_list.append(ip) 38 | found_subnets[my_netmask] = ip_list 39 | return found_subnets 40 | 41 | 42 | def join_subnets(subnet1, subnet2): 43 | if type(subnet1) == type(subnet2): 44 | if type(subnet1) == list: 45 | return list(set(subnet1+subnet2)) 46 | else: 47 | out = OrderedDict() 48 | keys = set(subnet1.keys() + subnet2.keys()) 49 | for k in keys: 50 | if k in subnet1: 51 | if k in subnet2: 52 | out[k] = join_subnets(subnet1[k],subnet2[k]) 53 | else: 54 | out[k] = subnet1[k] 55 | else: 56 | out[k] = subnet2[k] 57 | return out 58 | else: 59 | # a list means hosts directly in this subnet, that cannot be in any other subnet (i.e. all-zeros and all-ones) 60 | # if we have both hosts directly here and hosts structured in subnets, it means our hypotesis of a smaller subnet is false 61 | # thus we flatten to a single list and consider all hosts as part of the same subnet 62 | if type(subnet1) == list: 63 | (ip_list, tree) = (subnet1, subnet2) 64 | else: 65 | (ip_list, tree) = (subnet2, subnet1) 66 | out = set(ip_list) 67 | for v in tree.values(): 68 | out |= set(join_subnets(v,[])) 69 | return sorted(list(out)) 70 | 71 | 72 | def merge_subnets(found_subnets, netmask_size=2, stop_netmask_size=16): 73 | upper_level = OrderedDict() 74 | current_netmask = None 75 | for key, value in found_subnets.items(): 76 | (subnet, prev_netmask_size) = key 77 | if netmask_size < 32 - prev_netmask_size: 78 | upper_level[key] = value 79 | continue 80 | elif netmask_size == 32 - prev_netmask_size: 81 | upper_level[key] = join_subnets(upper_level.get(key,OrderedDict()), value) 82 | else: 83 | my_netmask = (subnet >> (netmask_size)) << (netmask_size) 84 | my_netmask = (my_netmask, 32 - netmask_size) 85 | this_level = upper_level.get(my_netmask,OrderedDict()) 86 | this_level = join_subnets(this_level,OrderedDict({key:value})) 87 | upper_level[my_netmask] = this_level 88 | if netmask_size >= stop_netmask_size: 89 | return upper_level 90 | else: 91 | return merge_subnets(upper_level, netmask_size+1, stop_netmask_size) 92 | 93 | 94 | # this keeps the smallest subnet 95 | def clean_merged(merged): 96 | if type(merged) == list: 97 | return merged 98 | out = OrderedDict() 99 | for key, value in merged.items(): 100 | ret = clean_merged(value) 101 | if len(ret) > 1 or type(ret) == list: 102 | out[key] = ret 103 | else: 104 | (key, value) = ret.items()[0] 105 | out[key] = value 106 | return out 107 | 108 | 109 | def represent(clean,out=None): 110 | if type(clean) == list: 111 | return list(map(ip_number_to_string, clean)) 112 | # if len(clean) > 1: 113 | # return list(map(ip_number_to_string, clean)) 114 | # else: 115 | # return ip_number_to_string(clean[0]) 116 | elif len(clean) > 1: 117 | out = OrderedDict() 118 | for (subnet,netmask_size), value in clean.items(): 119 | out["%s/%d" % (ip_number_to_string(subnet),netmask_size)] = represent(value) 120 | return out 121 | else: 122 | return represent(clean.values()[0]) 123 | 124 | 125 | def find_minimum_netmask_size_between(start,end): 126 | val = start ^ end 127 | netmask_size = 0 128 | for (m,n) in zip((0xffff0000, 0xff00ff00, 0xf0f0f0f0, 0b110011001100110011001100110011, 0b1010101010101010101010101010101),(16,8,4,2,1)): 129 | if val & m: 130 | netmask_size += n 131 | return 32 - netmask_size 132 | 133 | 134 | def compute_cluster_likelihood(clean): 135 | if type(clean) == list: 136 | size = len(clean) 137 | avg = sum(map(lambda x:x/size,clean)) 138 | std = sum(map(lambda x:(x-avg)**2,clean))**0.5 139 | merge = 1 140 | return (merge,avg,std,size,clean) 141 | elif len(clean) > 1: 142 | avg_sum = [] 143 | size_sum = [] 144 | std_sum = [] 145 | out = OrderedDict() 146 | for (subnet,netmask_size), value in clean.items(): 147 | ret = compute_cluster_likelihood(value) 148 | (m,avg,std,size,x) = ret 149 | avg_sum.append(avg) 150 | std_sum.append(std) 151 | size_sum.append(size) 152 | out[(subnet,netmask_size)] = (m, avg, std, size, x) 153 | size = sum(size_sum) 154 | if size > 32: 155 | avg = sum(map(lambda (a,s): a/size*s,zip(avg_sum,size_sum))) 156 | std = (sum(map(lambda (d,s): (d**2)*s,zip(std_sum,size_sum)))/size)**0.5 157 | else: 158 | ip_list = join_subnets([],clean) 159 | (_,avg,std,size,_) = compute_cluster_likelihood(ip_list) 160 | 161 | (lowerbound,netmask_size) = clean.keys()[0] 162 | (upperbound,netmask_size) = clean.keys()[-1] 163 | upperbound = upperbound | (0xffffffff >> netmask_size) 164 | netmask_size = find_minimum_netmask_size_between(lowerbound,upperbound) 165 | 166 | lowerbound = (lowerbound >> (32 - netmask_size)) << (32 - netmask_size) 167 | upperbound = upperbound | (0xffffffff >> netmask_size) 168 | 169 | lowerbound = max(lowerbound, avg - std) 170 | upperbound = min(upperbound, avg + std) 171 | 172 | overlapping = 0 173 | for (subnet,netmask_size), a, s in zip(clean.keys(),avg_sum,std_sum): 174 | end = subnet | (0xffffffff >> netmask_size) 175 | start = max(lowerbound, a - s, subnet) 176 | end = min(upperbound, a + s, end) 177 | delta = end - start 178 | if delta > 0: 179 | overlapping += delta 180 | 181 | merge = float(overlapping) / (upperbound - lowerbound) 182 | return [merge,avg,std,size,out] 183 | else: 184 | return compute_cluster_likelihood(clean.values()[0]) 185 | 186 | 187 | # 0.682689492137086 is one one sigma confidence (68%) 188 | def join_cluster_probability_higher_than(clustered, threshold=0.682689492137086): 189 | (merge,avg,std,size, subnets) = clustered 190 | if type(subnets) == list: 191 | return subnets 192 | out = OrderedDict() 193 | for key, value in subnets.items(): 194 | (my_subnet, my_netmask_size) = key 195 | (merge,avg,std,size, subnets) = value 196 | if merge > threshold: 197 | if type(subnets) == list: 198 | hosts = subnets 199 | else: 200 | hosts = [] 201 | remaining = list(subnets.items()) 202 | while remaining: 203 | ((subnet,netmask_size),(m,a,d,s,subnets)) = remaining.pop(0) 204 | if type(subnets) == list: 205 | hosts.extend(subnets) 206 | else: 207 | remaining = list(subnets.items()) + remaining 208 | out[key] = hosts 209 | else: 210 | out[key] = join_cluster_probability_higher_than(value, threshold) 211 | return out 212 | 213 | 214 | def traverse(tree, path): 215 | sub_elem_dict = tree[-1] 216 | 217 | res = [(tree[0], path)] 218 | i_val = zip(range(len(sub_elem_dict)), sub_elem_dict.values()) 219 | for i, t in i_val: 220 | if type(t[-1]) == int: 221 | continue 222 | r = traverse(t, path+[i]) 223 | res = res + r 224 | 225 | return res; 226 | 227 | 228 | def flattener(t): 229 | res = [] 230 | for k, v in t.iteritems(): 231 | if type(v) == list: 232 | res += v 233 | else: 234 | res += flattener(v) 235 | 236 | return res 237 | 238 | 239 | def pick_best_merges(stat_tree, clean_tree, num): 240 | all_probs = [] # tuple of (value, [index_path_down_trees]) 241 | 242 | flattened = traverse(stat_tree, []) 243 | flattened = sorted(flattened, key=lambda x:x[0], reverse=True) 244 | 245 | to_merge = flattened[:num] 246 | to_merge_from_bottom = sorted(to_merge, cmp=lambda x,y:cmp(len(x[1]),len(y[1])), reverse=True) 247 | 248 | for v, lst in to_merge_from_bottom: 249 | tmp_tree = clean_tree 250 | for i in lst: 251 | tmp_tree = tmp_tree.values()[i] 252 | 253 | ref = clean_tree 254 | for i in lst[:-1]: 255 | ref = ref.values()[i] 256 | 257 | flat = flattener(tmp_tree) 258 | itm = ref.items() 259 | k, _ = itm[lst[-1]] 260 | ref[k] = flat 261 | 262 | return clean_tree 263 | 264 | 265 | def read_tsv(fstream): 266 | ips = [] 267 | 268 | devreader = csv.reader(fstream, delimiter='\t') 269 | length = sum(1 for _ in devreader) 270 | fstream.seek(0) 271 | to_skip = {0,1,2,3,4,5,6,7,length-1} 272 | for row, i in zip(devreader, range(length)): 273 | if i in to_skip: 274 | continue 275 | ip = ip_string_to_number(row[0].strip()) 276 | ips.append(ip) 277 | 278 | fstream.close() 279 | 280 | return ips 281 | 282 | def write_tsvs(ordered_subnet_dict): 283 | all_leaf_subnets = {} 284 | 285 | def unroll_tree(od_tree): 286 | for k,v in od_tree.iteritems(): 287 | if type(v) == list: 288 | all_leaf_subnets[k] = v 289 | else: 290 | unroll_tree(v) 291 | 292 | unroll_tree(ordered_subnet_dict) 293 | 294 | str_tree = represent(all_leaf_subnets) 295 | 296 | with open("subnet.log", "w") as sf: 297 | sf.write('''#separator \x09 298 | #set_separator , 299 | #empty_field (empty) 300 | #unset_field - 301 | #path subnet 302 | #open 2019-09-03-14-52-03 303 | #fields net num_devices link_local 304 | #types subnet count bool 305 | ''') 306 | 307 | tsvwriter = csv.writer(sf, delimiter='\t') 308 | for snet in str_tree.keys(): 309 | tsvwriter.writerow([snet, len(str_tree[snet]), "F"]) 310 | 311 | with open("device.log.out", "w") as df: 312 | df.write('''#separator \x09 313 | #set_separator , 314 | #empty_field (empty) 315 | #unset_field - 316 | #path device 317 | #open 2019-09-03-14-52-03 318 | #fields dev_src_ip first_seen possible_subnet 319 | #types addr time subnet 320 | ''') 321 | 322 | tsvwriter = csv.writer(df, delimiter='\t') 323 | 324 | for snet, d_lst in str_tree.iteritems(): 325 | for dev in d_lst: 326 | tsvwriter.writerow([dev, 1561700000, snet]) 327 | 328 | # TODO must read and then adjust net_routes.log 329 | return 330 | 331 | 332 | if __name__ == "__main__": 333 | parser = argparse.ArgumentParser() 334 | parser.add_argument('device_log', type=argparse.FileType('r'), 335 | default=sys.stdin) 336 | 337 | args = parser.parse_args() 338 | ips = read_tsv(args.device_log) 339 | 340 | groups = group_sorted_ips_by_subnet(ips) 341 | merged = merge_subnets(groups) 342 | clean = clean_merged(merged) 343 | 344 | clustered = compute_cluster_likelihood(clean) 345 | joined = join_cluster_probability_higher_than(clustered) 346 | 347 | write_tsvs(joined) 348 | -------------------------------------------------------------------------------- /scripts/ether_ipv4.zeek: -------------------------------------------------------------------------------- 1 | module EtherIPv4; 2 | 3 | export { 4 | 5 | type obj_type : enum { DEVICE, ROUTER, GATEWAY, }; 6 | 7 | # Similar to Site::private_address_space without the ipv6 addresses 8 | global local_nets = { 9 | 192.168.0.0/16, 10 | 127.0.0.0/8, 11 | 172.16.0.0/12, 12 | 10.0.0.0/8, 13 | 100.64.0.0/10 14 | }; 15 | 16 | redef enum Log::ID += { LOG_DEV, LOG_NET, LOG_ROUT, LOG_NET_ROUT }; 17 | 18 | 19 | global Verbose = F &redef; 20 | 21 | # Discard from the final output any tracked devices that are public IPs 22 | global UsePublic = T &redef; 23 | 24 | type TrackedIP: record { 25 | # The tracked source address 26 | dev_src_ip: addr &log; 27 | 28 | first_seen: time &log; 29 | obj_type: obj_type; 30 | 31 | # All of the observed macs and their frequency 32 | seen_macs: table[string] of count ; 33 | 34 | possible_subnet: subnet &log; 35 | 36 | #possible_vlan: count &log &optional; 37 | #possible_r_subnet: subnet &log &optional; 38 | 39 | # Note unused fields for now 40 | # seen_routes: table[addr] of string; 41 | # seen_ports: set[port]; 42 | 43 | }; 44 | 45 | # Table that maps every ip address to a TrackedIP object for quick lookup 46 | global all_src_ips: table[addr] of TrackedIP; 47 | 48 | type TrackedSubnet: record { 49 | net: subnet &log; 50 | #vlan: count &log &optional; 51 | num_devices: count &log; 52 | #num_strange: count &log &optional; 53 | #router_mac: string &log &optional; 54 | link_local: bool &log; 55 | }; 56 | 57 | type TrackedRouter: record { 58 | mac: string &log; 59 | num_ips: count &log; 60 | obj_type: obj_type &log; 61 | routed_subnets: vector of subnet; 62 | }; 63 | 64 | type TrackedNetRoute: record { 65 | router_mac: string &log; 66 | net: subnet &log; 67 | }; 68 | 69 | # Table that maps every src mac address to every src ip address found together in a packet. 70 | global mac_src_ip_emitted: table[string] of set[addr]; 71 | 72 | # Table that maps every src mac address to every vlan tag found in a packet. 73 | global mac_src_vlan_emitted: table[string] of set[count]; 74 | 75 | # Table that tracks every src ip address issued with a specific vlan tag. 76 | global vlan_ip_emitted: table[count] of set[addr]; 77 | 78 | # Table used to track communication with devices outside of a vlan. 79 | global vlan_ip_strange: table[count] of set[addr]; 80 | 81 | # Table of tables that models the arp table of each mac originating traffic 82 | global mac_src_routing_table: table[string] of table[string] of addr; 83 | 84 | # build_vlans constructs possible vlans based on the src ip addresses 85 | # and their corresponding vlan tag. It will produce results only as good as 86 | # the input. 87 | global build_vlans: function(vlan_ip_tbl_set: table[count] of set[addr], p: bool) : table[count] of TrackedSubnet; 88 | 89 | # find_routers lists all mac addrs with more than one source ip. This signifies 90 | # that the network interface is attached to a router or a gateway or that the 91 | # network inteface has multiple ip addresses or something funky is going on. 92 | global find_routers: function(): table[string] of TrackedRouter; 93 | 94 | # find all ip addresses that only have only one mac address associated 95 | global find_link_local: function(): set[addr]; 96 | 97 | # infer_subnets uses the devices in `ip_set` to build a set of `subnet`s. 98 | # These subnets are generated recursively splitting the address space into 99 | # contiguous blocks. 100 | global infer_subnets: function(ip_set: set[addr]): vector of subnet; 101 | 102 | } 103 | 104 | 105 | event raw_packet(p: raw_pkt_hdr) 106 | { 107 | # if the packet is ipv4 and has a mac src process it. 108 | if ((p?$ip) && (p$l2?$src)) { 109 | local dev_src_ip: addr = p$ip$src; 110 | 111 | local dev: TrackedIP; 112 | if (dev_src_ip !in all_src_ips) { 113 | dev$first_seen = network_time(); 114 | dev$dev_src_ip = dev_src_ip; 115 | 116 | all_src_ips[dev_src_ip] = dev; 117 | } else { 118 | dev = all_src_ips[dev_src_ip]; 119 | } 120 | local dev_src_mac = p$l2$src; 121 | 122 | if (dev_src_mac !in dev$seen_macs) { 123 | dev$seen_macs[dev_src_mac] = 1; 124 | } else { 125 | dev$seen_macs[dev_src_mac] = dev$seen_macs[dev_src_mac] + 1; 126 | } 127 | 128 | local s: set[addr]; 129 | if (dev_src_mac !in mac_src_ip_emitted) { 130 | mac_src_ip_emitted[dev_src_mac] = s; 131 | } else { 132 | s = mac_src_ip_emitted[dev_src_mac]; 133 | } 134 | add s[dev_src_ip]; 135 | } 136 | 137 | 138 | # Check if the packet is a vlan tagged ipv4 packet inside of an ethernet frame 139 | # If so add it to the data structures defined above 140 | if (p?$ip && p$l2?$src && p$l2?$vlan) { 141 | local mac_src= p$l2$src; 142 | local ip_src = p$ip$src; 143 | local vlan = p$l2$vlan; 144 | 145 | local g: set[count]; 146 | if (mac_src !in mac_src_vlan_emitted) { 147 | mac_src_vlan_emitted[mac_src] = g; 148 | } else { 149 | g = mac_src_vlan_emitted[mac_src]; 150 | } 151 | add g[vlan]; 152 | 153 | local h: set[addr]; 154 | if (vlan !in vlan_ip_emitted) { 155 | vlan_ip_emitted[vlan] = h; 156 | local t: set[addr]; 157 | vlan_ip_strange[vlan] = t; 158 | } else { 159 | h = vlan_ip_emitted[vlan]; 160 | } 161 | add h[ip_src]; 162 | } 163 | } 164 | 165 | 166 | function power(base: count, exponent: count): count 167 | { 168 | local v = 1; 169 | local i = 0; 170 | while (i < exponent) { 171 | v = v * base; 172 | i += 1; 173 | } 174 | return v; 175 | } 176 | 177 | 178 | function recurse_subnet(ip_c_set: set[count], level: count, output_set: set[subnet]) 179 | { 180 | local contiguous_blocks = vector(24, 16, 8); 181 | local e: count = contiguous_blocks[level]; 182 | 183 | local divisor = power(2, e); 184 | 185 | local block_sets: table[count] of set[count]; 186 | for (c in ip_c_set) { 187 | 188 | local t = c / divisor; 189 | local s: set[count]; 190 | if (t in block_sets) { 191 | s = block_sets[t]; 192 | } 193 | add s[c]; 194 | block_sets[t] = s; 195 | 196 | } 197 | 198 | local res: set[subnet]; 199 | for (cnt in block_sets) { 200 | local iv: index_vec = [divisor*cnt]; 201 | local sn = counts_to_addr(iv); 202 | add res[sn/(32 - e)]; 203 | add output_set[sn/(32 - e)]; 204 | 205 | # recurse here; if below has 1 or more subnet do not return 206 | if (level < (|contiguous_blocks| - 1)) { 207 | recurse_subnet(block_sets[cnt], level + 1, output_set); 208 | } 209 | } 210 | } 211 | 212 | function net_sort(a: subnet, b: subnet): int 213 | { 214 | local c: int = addr_to_counts(subnet_to_addr(a))[0]; 215 | local d: int = addr_to_counts(subnet_to_addr(b))[0]; 216 | return d - c; 217 | } 218 | 219 | 220 | function infer_subnets(ip_set: set[addr]): vector of subnet 221 | { 222 | local ip_c_set: set[count]; 223 | 224 | local seen_public = F; 225 | for (_ip in ip_set) { 226 | if (Site::is_private_addr(_ip)) { 227 | local j: index_vec = addr_to_counts(_ip); 228 | add ip_c_set[j[0]]; 229 | } else { 230 | seen_public = T; 231 | } 232 | } 233 | 234 | local res: set[subnet]; 235 | 236 | 237 | recurse_subnet(ip_c_set, 0, res); 238 | 239 | local v :vector of subnet = vector(); 240 | for (sn in res) { 241 | local w = subnet_width(sn); 242 | if (24 == w) { 243 | v += sn; 244 | } 245 | } 246 | 247 | if (seen_public) { 248 | v += 0.0.0.0/0; 249 | } 250 | 251 | sort(v, net_sort); 252 | return v; 253 | } 254 | 255 | 256 | function build_vlans(vlan_ip_tbl_set: table[count] of set[addr], p: bool) : table[count] of TrackedSubnet 257 | { 258 | local vlan_subnets: table[count] of TrackedSubnet; 259 | 260 | for (_vlan in vlan_ip_tbl_set) { 261 | local set_ip = vlan_ip_tbl_set[_vlan]; 262 | local strange = vlan_ip_strange[_vlan]; 263 | 264 | # filter and track all observed IPs that are not part of local_networks 265 | for (_ip in set_ip) { 266 | if (addr_to_subnet(_ip) !in local_nets) { 267 | add strange[_ip]; 268 | } 269 | } 270 | 271 | set_ip = set_ip - strange; 272 | 273 | local snet_tree = infer_subnets(set_ip); 274 | #local t_snet: TrackedSubnet = [$net_tree=snet_tree, $vlan=_vlan, $num_devices=|set_ip|]; 275 | 276 | #vlan_subnets[_vlan] = t_snet; 277 | 278 | if (p) { 279 | print _vlan, snet_tree, |set_ip|; 280 | if (|strange| > 0) { 281 | print "non local to vlan", _vlan, strange; 282 | } 283 | } 284 | }; 285 | 286 | return vlan_subnets; 287 | } 288 | 289 | 290 | function find_routers(): table[string] of TrackedRouter 291 | { 292 | local r_t: table[string] of TrackedRouter; 293 | 294 | for (mac_src in mac_src_ip_emitted) { 295 | local ip_set = mac_src_ip_emitted[mac_src]; 296 | 297 | if (|ip_set| > 1) { 298 | local subnets = infer_subnets(ip_set); 299 | 300 | local tr: TrackedRouter = [ 301 | $mac=mac_src, 302 | $num_ips=|ip_set|, 303 | $routed_subnets=subnets, 304 | $obj_type=ROUTER 305 | ]; 306 | 307 | local subnet_set: set[subnet]; 308 | for (i in subnets) { 309 | add subnet_set[subnets[i]]; 310 | } 311 | 312 | local g = matching_subnets(8.8.8.8/32, subnet_set); 313 | if (|g| > 0) { 314 | tr$obj_type = GATEWAY; 315 | } 316 | 317 | r_t[mac_src] = tr; 318 | } 319 | } 320 | 321 | return r_t; 322 | } 323 | 324 | 325 | function find_link_local(): set[addr] 326 | { 327 | local all_link_local: set[addr]; 328 | 329 | for (mac_src in mac_src_ip_emitted) { 330 | local ip_set = mac_src_ip_emitted[mac_src]; 331 | if (|ip_set| == 1) { 332 | for (ip in ip_set) { 333 | add all_link_local[ip]; 334 | } 335 | } 336 | } 337 | 338 | return all_link_local; 339 | } 340 | -------------------------------------------------------------------------------- /scripts/identify_paths.zeek: -------------------------------------------------------------------------------- 1 | @load policy/misc/detect-traceroute/main.bro 2 | -------------------------------------------------------------------------------- /scripts/main.zeek: -------------------------------------------------------------------------------- 1 | @load base/utils/site 2 | 3 | @load policy/protocols/conn/mac-logging 4 | @load policy/protocols/conn/vlan-logging 5 | 6 | @load ether_ipv4 7 | @load tracedroute 8 | 9 | module EtherIPv4; 10 | 11 | 12 | event zeek_init() 13 | { 14 | Log::create_stream(EtherIPv4::LOG_DEV, [$columns=EtherIPv4::TrackedIP, $path="device"]); 15 | Log::create_stream(EtherIPv4::LOG_NET, [$columns=EtherIPv4::TrackedSubnet, $path="subnet"]); 16 | Log::create_stream(EtherIPv4::LOG_ROUT, [$columns=EtherIPv4::TrackedRouter, $path="router"]); 17 | Log::create_stream(EtherIPv4::LOG_NET_ROUT, [$columns=EtherIPv4::TrackedNetRoute, $path="net_route"]); 18 | } 19 | 20 | 21 | function log_nets(t: table[subnet] of TrackedSubnet) 22 | { 23 | for (s in t) { 24 | local tracked_net = t[s]; 25 | Log::write(LOG_NET, tracked_net); 26 | } 27 | } 28 | 29 | 30 | function log_devices(t: table[addr] of TrackedIP) 31 | { 32 | for (ip in t) { 33 | local tracked_ip = t[ip]; 34 | if (UsePublic || Site::is_private_addr(ip)) { 35 | Log::write(LOG_DEV, tracked_ip); 36 | } 37 | } 38 | } 39 | 40 | function log_routers(t: table[string] of TrackedRouter) 41 | { 42 | for (mac in t) { 43 | local tracked_router = t[mac]; 44 | Log::write(LOG_ROUT, tracked_router); 45 | } 46 | } 47 | 48 | 49 | event zeek_done() 50 | { 51 | local all_ips: set[addr]; 52 | local all_subnets_table: table[subnet] of TrackedSubnet; 53 | 54 | for (ip in all_src_ips) { 55 | add all_ips[ip]; 56 | } 57 | 58 | local all_subnets_vec = infer_subnets(all_ips); 59 | for (i in all_subnets_vec) { 60 | local net = all_subnets_vec[i]; 61 | local tracked_net: TrackedSubnet = [ 62 | $net=net, $link_local=F, $num_devices=0 63 | ]; 64 | all_subnets_table[net] = tracked_net; 65 | } 66 | 67 | local all_link_local_ips = find_link_local(); 68 | 69 | for (ip in all_src_ips) { 70 | local t_ip = all_src_ips[ip]; 71 | 72 | local vs: vector of subnet = matching_subnets(ip/32, all_subnets_table); 73 | 74 | local tr = all_subnets_table[vs[0]]; 75 | if (ip in all_link_local_ips) { 76 | tr$link_local = T; 77 | } 78 | 79 | t_ip$possible_subnet = tr$net; 80 | } 81 | 82 | local mac_to_router = find_routers(); 83 | local subnet_to_router : table[subnet] of TrackedRouter; 84 | 85 | for (mac in mac_to_router) { 86 | local router: TrackedRouter = mac_to_router[mac]; 87 | 88 | for (i in router$routed_subnets) { 89 | local s = router$routed_subnets[i]; 90 | 91 | subnet_to_router[s] = router; 92 | 93 | local net_route: TrackedNetRoute = [ 94 | $net=s, $router_mac=mac 95 | ]; 96 | 97 | Log::write(LOG_NET_ROUT, net_route); 98 | } 99 | } 100 | 101 | log_routers(mac_to_router); 102 | log_nets(all_subnets_table); 103 | log_devices(all_src_ips); 104 | Tracedroute::log_tracedroutes(Tracedroute::icmp_traceroute_tbl); 105 | } 106 | -------------------------------------------------------------------------------- /scripts/tracedroute.zeek: -------------------------------------------------------------------------------- 1 | ##! This script detects a large number of ICMP Time Exceeded messages heading 2 | ##! toward hosts that have sent low TTL packets. It generates a notice when the 3 | ##! number of ICMP Time Exceeded messages for a source-destination pair exceeds 4 | ##! a threshold. 5 | 6 | @load base/frameworks/signatures 7 | @load-sigs ./detect-low-ttls.sig 8 | 9 | redef Signatures::ignored_ids += /traceroute-detector.*/; 10 | 11 | module Tracedroute; 12 | 13 | export { 14 | redef enum Log::ID += { LOG }; 15 | 16 | redef enum Notice::Type += { 17 | ## Indicates that a host was seen running traceroutes. For more 18 | ## detail about specific traceroutes that we run, refer to the 19 | ## traceroute.log. 20 | Detected 21 | }; 22 | 23 | ## By default this script requires that any host detected running 24 | ## traceroutes first send low TTL packets (TTL < 10) to the traceroute 25 | ## destination host. Changing this setting to F will relax the 26 | ## detection a bit by solely relying on ICMP time-exceeded messages to 27 | ## detect traceroute. 28 | const require_low_ttl_packets = T &redef; 29 | 30 | ## Defines the threshold for ICMP Time Exceeded messages for a src-dst 31 | ## pair. This threshold only comes into play after a host is found to 32 | ## be sending low TTL packets. 33 | const icmp_time_exceeded_threshold: double = 1 &redef; 34 | 35 | ## Interval at which to watch for the 36 | ## :zeek:id:`Traceroute::icmp_time_exceeded_threshold` variable to be 37 | ## crossed. At the end of each interval the counter is reset. 38 | const icmp_time_exceeded_interval = 3min &redef; 39 | 40 | ## The log record for the traceroute log. 41 | type Info: record { 42 | ## Address initiating the traceroute. 43 | originator: addr &log; 44 | ## Destination address of the traceroute. 45 | dst: addr &log; 46 | ## Protocol used for the traceroute. 47 | proto: string &log; 48 | 49 | ## extra bonus! 50 | emitter: addr &log; 51 | ttl: count &log; 52 | }; 53 | 54 | global log_traceroute: event(rec: Tracedroute::Info); 55 | 56 | type TrackedResponse: record { 57 | resp_ip: addr; 58 | reached_dst: bool &optional; 59 | }; 60 | 61 | type TrackedRequest: record { 62 | ttl_out: count; 63 | }; 64 | 65 | type TrackedRoute: record { 66 | dst: addr &log; 67 | originator: addr &log; 68 | saw_unreached: bool &log &optional; 69 | 70 | proto: string &optional; # Just UDP for now 71 | t_resp_tbl: table[port] of TrackedResponse; 72 | t_req_tbl: table[port] of TrackedRequest; 73 | }; 74 | 75 | global icmp_traceroute_tbl: table[string] of TrackedRoute; 76 | global log_tracedroutes: function(icmp_tbl: table[string] of TrackedRoute); 77 | } 78 | 79 | event zeek_init() &priority=5 80 | { 81 | Log::create_stream(Tracedroute::LOG, [$columns=Info, $ev=log_traceroute, $path="tracedroute"]); 82 | } 83 | 84 | function log_tracedroutes(icmp_tbl: table[string] of TrackedRoute) { 85 | for (t in icmp_tbl) { 86 | local v : vector of set[addr] = vector(); 87 | local i = 0; 88 | while (i < 10) { 89 | v[i] = set(); 90 | i += 1; 91 | } 92 | 93 | local r = icmp_tbl[t]; 94 | if (|r$t_resp_tbl| > 2 && |r$t_req_tbl| > 2) { 95 | for (p in r$t_resp_tbl) { 96 | if (p !in r$t_req_tbl) { 97 | ; 98 | } else { 99 | local tresp = r$t_resp_tbl[p]; 100 | local treq = r$t_req_tbl[p]; 101 | local s = v[treq$ttl_out]; 102 | add s[tresp$resp_ip]; 103 | } 104 | } 105 | 106 | for (j in v) { 107 | for (emit_addr in v[j]) { 108 | local tr_info = [ 109 | $originator=r$originator, 110 | $dst=r$dst, 111 | $proto="udp", 112 | $emitter=emit_addr, 113 | $ttl=j 114 | ]; 115 | Log::write(Tracedroute::LOG, tr_info); 116 | } 117 | } 118 | } 119 | } 120 | } 121 | 122 | # event raw_packet(p: raw_pkt_hdr) 123 | # { 124 | # # TODO figure out why some packet captures do not fire signature-detect 125 | # # but still have low ttls da @nskelsey del passato, baci e abbracci! 126 | # # TODO 127 | # if (p$ip$ttl < 10) { 128 | # print "raw-pkt", p$ip$ttl; 129 | # } 130 | # 131 | # } 132 | 133 | 134 | # Low TTL packets are detected with a signature. 135 | event signature_match(state: signature_state, msg: string, data: string) 136 | { 137 | if ( state$sig_id == /traceroute-detector.*/ ) 138 | { 139 | # print msg; 140 | local s = cat(state$conn$id$orig_h,"-",state$conn$id$resp_h,"-",get_port_transport_proto(state$conn$id$resp_p)); 141 | 142 | local p_hdr = get_current_packet_header(); 143 | local p = get_current_packet(); 144 | 145 | #print "sig detect match", s, p_hdr$ip$ttl, state$conn$id$orig_p; 146 | 147 | if (!p_hdr?$ip) { 148 | return; 149 | } 150 | 151 | local tr : TrackedRoute; 152 | local tmp_req_tbl : table[port] of TrackedRequest; 153 | local tmp_resp_tbl : table[port] of TrackedResponse; 154 | if (s !in icmp_traceroute_tbl) { 155 | tr = [$originator=state$conn$id$orig_h, $dst=state$conn$id$resp_h, $t_resp_tbl=tmp_resp_tbl, $t_req_tbl=tmp_req_tbl]; 156 | icmp_traceroute_tbl[s] = tr; 157 | } else { 158 | tr = icmp_traceroute_tbl[s]; 159 | } 160 | 161 | local udp_port = state$conn$id$orig_p; 162 | 163 | local t_req : TrackedRequest; 164 | if (udp_port !in tr$t_req_tbl) { 165 | t_req = [$ttl_out=p_hdr$ip$ttl]; 166 | tr$t_req_tbl[udp_port] = t_req; 167 | } else { 168 | print "t_req already seen:", s, udp_port, t_req; 169 | } 170 | } 171 | } 172 | 173 | event icmp_time_exceeded(c: connection, icmp: icmp_conn, code: count, context: icmp_context) 174 | { 175 | local s = cat(context$id$orig_h,"-",context$id$resp_h,"-",get_port_transport_proto(context$id$resp_p)); 176 | 177 | local p_hdr = get_current_packet_header(); 178 | local p = get_current_packet(); 179 | 180 | if (!p_hdr?$ip) { 181 | return; 182 | } 183 | 184 | #print "icmp_time_exceeded", s, icmp$orig_h, p_hdr$ip$ttl, c$id$resp_p, context$id$orig_p; 185 | local tr : TrackedRoute; 186 | local tmp_req_tbl : table[port] of TrackedRequest; 187 | local tmp_resp_tbl : table[port] of TrackedResponse; 188 | if (s !in icmp_traceroute_tbl) { 189 | tr = [$originator=context$id$orig_h, $dst=context$id$resp_h, $t_resp_tbl=tmp_resp_tbl, $t_req_tbl=tmp_req_tbl]; 190 | icmp_traceroute_tbl[s] = tr; 191 | } else { 192 | tr = icmp_traceroute_tbl[s]; 193 | } 194 | 195 | local udp_port = context$id$orig_p; 196 | 197 | local t_resp : TrackedResponse; 198 | if (udp_port !in tr$t_resp_tbl) { 199 | t_resp = [$resp_ip=icmp$orig_h]; 200 | tr$t_resp_tbl[udp_port] = t_resp; 201 | } else { 202 | print "t_resp already seen:", s, udp_port, t_resp; 203 | } 204 | 205 | } 206 | 207 | event icmp_unreachable(c: connection, icmp: icmp_conn, code: count, context: icmp_context) 208 | { 209 | local s = cat(context$id$orig_h,"-",context$id$resp_h,"-",get_port_transport_proto(context$id$resp_p)); 210 | 211 | local tr : TrackedRoute; 212 | if (s in icmp_traceroute_tbl) { 213 | tr = icmp_traceroute_tbl[s]; 214 | } else { 215 | print "saw icmp_unreachable but no req found", s; 216 | } 217 | 218 | local udp_port = context$id$orig_p; 219 | 220 | local t_resp : TrackedResponse; 221 | if (udp_port !in tr$t_resp_tbl) { 222 | t_resp = [$resp_ip=icmp$orig_h, $reached_dst=T]; 223 | tr$t_resp_tbl[udp_port] = t_resp; 224 | } else { 225 | print "t_resp already seen:", s, udp_port, t_resp; 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /static/aws-vps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSkelsey/aaalm/d42dfd79ae8e26b59383804cc0dfa4badea599fb/static/aws-vps.png -------------------------------------------------------------------------------- /static/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSkelsey/aaalm/d42dfd79ae8e26b59383804cc0dfa4badea599fb/static/home.png -------------------------------------------------------------------------------- /static/mhack-defcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSkelsey/aaalm/d42dfd79ae8e26b59383804cc0dfa4badea599fb/static/mhack-defcon.png -------------------------------------------------------------------------------- /static/white-boarded-c.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSkelsey/aaalm/d42dfd79ae8e26b59383804cc0dfa4badea599fb/static/white-boarded-c.jpg -------------------------------------------------------------------------------- /viz/aaalm.js: -------------------------------------------------------------------------------- 1 | const chart_sizes = [{w: 1112, h: 645, x_c:940}, {w: 1400, h: 1112, x_c:1407}, {w: 4451, h: 3148, x_c:3443}]; 2 | const paper_sizes = [{w: "29.7cm", h: "21cm"}, {w: "42cm", h: "29.7cm"}, {w: "118.8cm", h: "84cm"}]; 3 | 4 | const colors = d3.schemeCategory10; 5 | //const colors = ["#F1AFB6", "#F4BEA1", "#F9E1A8", "#ADE3C8", "#BAE5E3", "#6390B9", "#C24F8E", "#E3B4C9"]; 6 | 7 | const grid = { 8 | width: -1, 9 | height: -1, 10 | x_correct: -1, 11 | // Per node spacing 12 | x_offset: 50, 13 | y_offset: 40, 14 | x_pad: 50, 15 | y_pad: 40, 16 | r: 3, 17 | group_pad: 300 18 | }; 19 | 20 | 21 | let size = 0; 22 | let earliestDate = new Date(); 23 | let title = ""; 24 | 25 | let files = []; 26 | let worker = null; 27 | 28 | 29 | function setGridSize(idx) { 30 | grid.width = chart_sizes[idx].w; 31 | grid.height = chart_sizes[idx].h; 32 | grid.x_correct = chart_sizes[idx].x_c; 33 | let e = document.getElementById("resize-page"); 34 | e.style.width = paper_sizes[idx].w; 35 | e.style.height = paper_sizes[idx].h; 36 | } 37 | 38 | 39 | function symmetricDifference(A, B) { 40 | var _difference = new Set(A); 41 | for (var elem of B) { 42 | if (_difference.has(elem)) { 43 | _difference.delete(elem); 44 | } else { 45 | _difference.add(elem); 46 | } 47 | } 48 | return _difference; 49 | } 50 | 51 | 52 | function handleFileSelect(evt) { 53 | files = evt.target.files; 54 | 55 | let id = "file-list"; 56 | 57 | var output = []; 58 | for (var i = 0, f; f = files[i]; i++) { 59 | output.push('
  • ', escape(f.name), ' (', f.type || 'n/a', ') - ', 60 | f.size, ' bytes, last modified: ', 61 | f.lastModifiedDate ? f.lastModifiedDate.toLocaleDateString() : 'n/a', 62 | '
  • '); 63 | if (f.lastModifiedDate < earliestDate) { 64 | earliestDate = f.lastModifiedDate; 65 | } 66 | } 67 | document.getElementById(id).innerHTML = output.join(''); 68 | } 69 | 70 | 71 | function errorMessage(msg) { 72 | document.getElementById("error-msg").innerHTML = `Error: ${msg}` 73 | } 74 | 75 | 76 | function dropZeekTSVHeaders(raw_text) { 77 | let all_lines = raw_text.split("\n"); 78 | all_lines = all_lines.slice(6); 79 | 80 | all_lines.splice(1,1); 81 | all_lines.pop(); 82 | all_lines.pop(); 83 | 84 | all_lines[0] = all_lines[0].replace("#fields\t", ""); 85 | 86 | return all_lines.join("\n"); 87 | } 88 | 89 | 90 | function processFiles(evt) { 91 | if (files.length != 5) { 92 | errorMessage("Not enough or no files selected"); 93 | return; 94 | } 95 | 96 | title = document.getElementById("net-title").value; 97 | 98 | var e = document.getElementById("size-pick"); 99 | setGridSize(e.value); 100 | 101 | let promises = []; 102 | for (var i = 0, f; f = files[i]; i++) { 103 | 104 | if (!f.type.match('text.*')) { 105 | continue; 106 | } 107 | 108 | var reader = new FileReader(); 109 | 110 | p = new Promise((resolve, reject) => { 111 | reader.onload = (function(file) { 112 | return function(e) { 113 | let data = e.target.result; 114 | let c = dropZeekTSVHeaders(data); 115 | let o = { 116 | name: file.name, 117 | tsv: d3.tsvParse(c) 118 | }; 119 | resolve(o); 120 | } 121 | })(f); 122 | 123 | reader.readAsText(f); 124 | }) 125 | promises.push(p); 126 | } 127 | Promise.all(promises).then(values => { 128 | const requiredFiles = new Set(["device", "subnet", "net_route", "router", "tracedroute"]); 129 | let B = new Set(); 130 | let map = new Map(); 131 | 132 | values.forEach(d => { 133 | let non_exten_name = d.name.split('.')[0] 134 | B.add(non_exten_name); 135 | map.set(non_exten_name, d.tsv); 136 | }); 137 | 138 | let C = symmetricDifference(requiredFiles, B); 139 | 140 | if (C.size != 0) { 141 | let e = `There are incorrectly named and/or extra files: ${C}`; 142 | errorMessage(e); 143 | throw e; 144 | } 145 | 146 | return map; 147 | }).then(buildMap); 148 | } 149 | 150 | 151 | function setupHTML(metadata) { 152 | d3.select("#title > h2") 153 | .text(`${metadata.title} LAN map`); 154 | 155 | d3.select("#title > i") 156 | .text(`on ${metadata.date} by ${metadata.host}`); 157 | 158 | let now = new Date().toLocaleDateString(); 159 | d3.select("#creationDate") 160 | .text(`on ${now}`); 161 | 162 | d3.select("#totDev") 163 | .text(metadata.num_dev); 164 | 165 | d3.select("#totSN") 166 | .text(metadata.num_subnets); 167 | 168 | d3.select("#totNets") 169 | .text(metadata.num_nets); 170 | 171 | d3.select("#credit").style("display", "inline-block"); 172 | d3.select("#legend").style("display", "flex"); 173 | } 174 | 175 | 176 | function ip2int(ip) { 177 | let s = ip.split('.').reduce(function(ipInt, octet) { return (ipInt<<8) + parseInt(octet, 10)}, 0) >>> 0; 178 | return s; 179 | } 180 | 181 | 182 | function orientToDegrees(j) { 183 | return j*-90; 184 | } 185 | 186 | 187 | function orientToRotation(j) { 188 | return "rotate("+orientToDegrees(j)+")"; 189 | } 190 | 191 | function intersection(setA, setB) { 192 | var _intersection = new Set(); 193 | for (var elem of setB) { 194 | if (setA.has(elem)) { 195 | _intersection.add(elem); 196 | } 197 | } 198 | return _intersection; 199 | } 200 | 201 | function merge_routes(net_routes, sn_map, routers) { 202 | let routerMap = new Map(); 203 | routers.forEach((d, i) => { 204 | d.name = "R"+(i+1); 205 | 206 | d.route_path_name = `RP${i+0}`; 207 | d.routes = []; 208 | 209 | routerMap.set(d.mac, d); 210 | if (i < 2) { 211 | d.x = 4; 212 | d.y = i; 213 | } else { 214 | d.x = 6 - i; 215 | d.y = 2; 216 | } 217 | }); 218 | 219 | let link_local = new Set(["LCN-1"]); 220 | routerMap.forEach(v=>link_local.add(`${v.name}-1`)); 221 | 222 | sn_map.forEach(s=> { 223 | if (s.link_local == "T") { 224 | link_local.add(`${s.name}-1`); 225 | } 226 | }); 227 | 228 | let netSets = [link_local]; 229 | 230 | net_routes.forEach(d => { 231 | r = routerMap.get(d.router_mac); 232 | s = sn_map.get(d.net); 233 | 234 | let set = new Set([`${r.name}-2`, `${s.name}-1`]); 235 | netSets.push(set); 236 | }) 237 | 238 | outputSets = []; 239 | 240 | for (let i = 0; i < netSets.length; i++) { 241 | workingSet = netSets[i]; 242 | 243 | let g_poses = [] 244 | for (let g = 0; g < outputSets.length; g++) { 245 | let s = outputSets[g]; 246 | let o = intersection(s, workingSet); 247 | if (o.size > 0) { 248 | workingSet.forEach(e=>s.add(e)); 249 | g_poses.push(g) 250 | } 251 | } 252 | if (g_poses.length == 0) { 253 | outputSets.push(workingSet); 254 | } else { 255 | let f = g_poses.reduce((accum, idx)=> { 256 | s = outputSets[idx]; 257 | s.forEach(d=>accum.add(d)); 258 | return accum; 259 | }, new Set()); 260 | 261 | outputSets = outputSets.filter((_, i) => { 262 | return g_poses.indexOf(i) == -1; 263 | }); 264 | 265 | outputSets.push(f); 266 | } 267 | g_poses = []; 268 | } 269 | 270 | return outputSets; 271 | } 272 | 273 | function processTracedroutes(traced_routes, deviceMap, grid) { 274 | 275 | function make_path_coords(dev_src_ip) { 276 | let dev = deviceMap.get(dev_src_ip); 277 | 278 | let x_pos = dev.x*grid.x_offset + dev.subnet.fit.x; 279 | let y_pos = dev.y*grid.y_offset + dev.subnet.fit.y; 280 | 281 | return [x_pos, y_pos]; 282 | } 283 | 284 | let tr_route_map = new Map(); 285 | 286 | traced_routes.forEach(d => { 287 | let s = `${d.originator}-${d.dst}-${d.proto}`; 288 | 289 | let o = { 290 | name: s, 291 | originator: d.originator, 292 | dst: d.dst, 293 | stops: [], 294 | positions: [], 295 | }; 296 | 297 | for (let y = 0; y < 10; y++) { 298 | o.stops.push(new Set()); 299 | o.positions.push([]); 300 | } 301 | 302 | if (tr_route_map.has(s)) { 303 | o = tr_route_map.get(s); 304 | } else { 305 | tr_route_map.set(s, o); 306 | } 307 | o.stops[d.ttl].add(d.emitter); 308 | 309 | let coords = make_path_coords(d.emitter); 310 | o.positions[d.ttl].push(coords); 311 | }); 312 | 313 | 314 | for (let v of tr_route_map.values()) { 315 | v.simple_path = v.positions.filter(d=>d.length > 0); 316 | v.simple_path = v.simple_path.map(d=>d[0]); 317 | 318 | // push the originating host to the front of the line 319 | v.simple_path.unshift(make_path_coords(v.originator)); 320 | 321 | if (v[v.simple_path.length-1] != v.dst && deviceMap.has(v.dst)) { 322 | v.simple_path.push(make_path_coords(v.dst)); 323 | } 324 | }; 325 | 326 | let tr_list = Array.from(tr_route_map.values()); 327 | return tr_list; 328 | } 329 | 330 | 331 | 332 | 333 | function processDevices(prefix, devices, j) { 334 | // Compute x and y position for each device inside of its subnet 335 | let subnet_base = ip2int(prefix); 336 | 337 | let sorted_devices = devices.map(d => {d.ip = ip2int(d.dev_src_ip); return d;}) 338 | .sort((a,b) => a.ip - b.ip); 339 | 340 | let ctr = 3; 341 | 342 | if (devices.length == 0) { 343 | return ctr; 344 | } 345 | 346 | let last_ip = sorted_devices.ip 347 | for(let i = 0; i < sorted_devices.length; i++) { 348 | let d = sorted_devices[i]; 349 | d.orientation = j; 350 | 351 | let ip = ip2int(d.dev_src_ip); 352 | //d.val = ip - subnet_base; 353 | d.val = d.dev_src_ip.split(".").pop(); 354 | 355 | let diff = ip - last_ip; 356 | if (diff > 1) { 357 | let s = Math.ceil(diff / 128.0); 358 | s = Math.min(3, s); 359 | ctr = ctr + s; 360 | } 361 | let pos = ctr; 362 | 363 | let base = 8 364 | d.pos = ctr; 365 | d.x = pos % base; 366 | d.y = Math.floor(pos / base); 367 | ctr++; 368 | last_ip = ip; 369 | } 370 | 371 | return ctr; 372 | } 373 | 374 | 375 | function deleteForm() { 376 | let element = document.getElementById("wizard"); 377 | element.parentNode.removeChild(element); 378 | } 379 | 380 | 381 | function drawSVG(subnets, routers, tr_list, grid) { 382 | const svg = d3.select("#map") 383 | .style("width", grid.width) 384 | .style("height", grid.height) 385 | .append("g") 386 | .attr("class", "margin") 387 | .attr("transform", `translate(${grid.x_pad}, ${grid.y_pad})`) 388 | 389 | const subnet_group = svg.selectAll("g.subnet-group") 390 | .data(subnets) 391 | .enter().append("g") 392 | .attr("class", "subnet-group") 393 | .attr("transform", d => { 394 | let r = ''; 395 | let t = `translate(${d.fit.x}, ${d.fit.y})`; 396 | if (d.rotate) { 397 | r = 'rotate(90)'; 398 | } 399 | return t + ' ' + r 400 | }); 401 | 402 | subnet_group.append("rect") 403 | .attr("transform", `translate(${-grid.x_offset/2}, ${-grid.y_offset/2})`) 404 | .attr("width", d => d.w - grid.x_offset) 405 | .attr("height", d => d.h - grid.y_offset) 406 | .attr("stroke", d => d.color) 407 | .attr("stroke-width", 1.0) 408 | .attr("fill", "none") 409 | 410 | let line = d3.line() 411 | .x(d=>d[0]) 412 | .y(d=>d[1]); 413 | 414 | const connections = svg.selectAll("path.traceroutepath") 415 | .data(tr_list).join("path") 416 | .attr("stroke", (d,i)=>colors[i]) 417 | .attr("stroke-linejoin", "round") 418 | .attr("stroke-width", "2px") 419 | .attr("stroke-opacity", 0.7) 420 | .attr("class", "traceroutepath") 421 | .attr("fill", "none") 422 | .attr("d", d=>line(d.simple_path)); 423 | 424 | let router = svg.selectAll("g.router") 425 | .data(routers).join("g") 426 | .attr("class", "router") 427 | .attr("transform", d => 428 | `translate(${d.x*grid.x_offset}, ${d.y*grid.y_offset})`) 429 | 430 | router.append("text") 431 | .attr("transform", "translate(-20, -12)") 432 | .attr("text-anchor", "start") 433 | .text(d=> { v = d.mac.split(":"); return `${v[0]}::${v[5]}` }) 434 | .clone(true).lower() 435 | .attr("stroke", "white"); 436 | 437 | router.filter(d=> { 438 | return d.obj_type != "EtherIPv4::GATEWAY"; 439 | }) 440 | .append("use") 441 | .attr("href", "#router-inline") 442 | .attr("transform", "translate(-7, -7)"); 443 | 444 | router.filter(d=> d.obj_type == "EtherIPv4::GATEWAY") 445 | .append("use") 446 | .attr("href", "#gateway-inline") 447 | .attr("transform", "translate(-9, -5)"); 448 | 449 | const subnet_label = subnet_group.append("g") 450 | .attr("class", "label") 451 | .attr("transform", `translate(0, 0)`); 452 | 453 | subnet_label.append("text") 454 | .attr("x", 10) 455 | .attr("y", 5) 456 | .text(d => d.net) 457 | .clone(true).lower() 458 | .attr("stroke", "white"); 459 | 460 | subnet_label.append("circle") 461 | .attr("fill", "#000") 462 | .attr("r", grid.r); 463 | 464 | const subnet_points = subnet_group.append("g") 465 | .attr("class", "grid-points") 466 | .selectAll("g.grid-points") 467 | .data(d => d.empty_grid) 468 | .join("g") 469 | .attr("class", "grid-point") 470 | .attr("transform", d => ` 471 | translate(${d.x*grid.x_offset},${d.y*grid.y_offset}) 472 | `) 473 | 474 | subnet_points.append("circle") 475 | .attr("fill", d => d.color) 476 | .attr("r", grid.r/2.0); 477 | 478 | const node = subnet_group.append("g") 479 | .attr("class", "nodes") 480 | .selectAll("g.node") 481 | .data(d => d.devices) 482 | .join("g") 483 | .attr("class", "node") 484 | .attr("transform", d => ` 485 | translate(${d.x*grid.x_offset},${d.y*grid.y_offset}) 486 | `); 487 | 488 | node.append("circle") 489 | .attr("fill", "#555") 490 | .attr("r", grid.r); 491 | 492 | node.filter(d=> d.subnet.net !="0.0.0.0/0") 493 | .append("text") 494 | .attr("x", 5) 495 | .attr("y", 5) 496 | .attr("text-anchor", "start") 497 | .attr("transform", "rotate(0)") 498 | .text(d => d.val) 499 | .clone(true).lower() 500 | .attr("stroke", "white"); 501 | 502 | node.filter(d=> d.subnet.net == "0.0.0.0/0") 503 | .append("text") 504 | .attr("x", 5) 505 | .attr("y", 5) 506 | .attr("text-anchor", "start") 507 | .attr("transform", "rotate(-15)") 508 | .text(d => d.dev_src_ip) 509 | 510 | const center_dot = svg.append("g") 511 | .attr("id", "tap") 512 | .attr("transform", `translate(0, 0)`) 513 | 514 | center_dot.append("circle") 515 | .attr("r", `${grid.r*2}`) 516 | .attr("fill", "white") 517 | .attr("stroke", "black") 518 | .attr("stroke-width", "1px") 519 | 520 | center_dot.append("circle") 521 | .attr("r", `${grid.r*1.0}`) 522 | .attr("fill", "#a31d21") 523 | 524 | center_dot.append("path") 525 | .attr("stroke", "#777777") 526 | .attr("stroke-width", "2px") 527 | .attr("stroke-opacity", 0.7) 528 | .attr("stroke-linejoin", "round") 529 | .attr("fill", "none") 530 | .attr("d", "M-12,-12L-4,-4") 531 | 532 | center_dot.append("text") 533 | .attr("x", -40) 534 | .attr("y", -14) 535 | .text("Link Layer") 536 | } 537 | 538 | function layoutPCBPaths(subnets, routers, net_routes, grid) { 539 | let pcb_args = { 540 | border_gap: 55, // TODO 541 | timeout: 1000, // Unused by lib 542 | vias_cost: 0, 543 | samples: 8, // max 32 544 | grid_resolution: 1, // max 4 545 | distance_metric: 0, // max 4 546 | quantization: 1, // max 64 547 | flood_range: 2, // max 5 548 | x_range: 1, // max 5 549 | y_range: 1 // max 5 550 | }; 551 | let a = pcb_args; 552 | 553 | //run pcb solver web worker thread, register output listner 554 | if (worker !== null) worker.terminate(); 555 | worker = new Worker('js-pcb/worker.js'); 556 | worker.addEventListener('message', function(event) 557 | { 558 | if (event.data.length) 559 | { 560 | //view the pcb output 561 | console.log("Calling view_pcb", event.data); 562 | js_pcb.view_pcb(event.data, 1, grid.x_correct); 563 | } 564 | }, false); 565 | 566 | //post to solver thread 567 | let compiled_template = compileTemplate(subnets, routers, net_routes, grid); 568 | worker.postMessage([js_pcb.dsn2pcb(compiled_template, a.border_gap), 569 | a.timeout, 1, a.samples, a.vias_cost, 570 | a.grid_resolution, a.quantization, a.distance_metric, 571 | a.flood_range, a.x_range, a.y_range]); 572 | 573 | } 574 | 575 | 576 | function buildMap(valueMap) { 577 | promises = []; 578 | 579 | // TODO process subnets in subroutine 580 | let subnets = valueMap.get("subnet"); 581 | 582 | let sn_map = new Map(); 583 | subnets.forEach((sn, i)=> { 584 | sn.devices = []; 585 | sn.ip = ip2int(sn.net.split('/')[0]); 586 | sn_map.set(sn.net, sn); 587 | sn.name = "S"+(i+1) 588 | }); 589 | 590 | subnets.sort((a,b) => a.ip - b.ip); 591 | 592 | let devices = valueMap.get("device"); 593 | 594 | let has_pub_internet = sn_map.has("0.0.0.0/0"); 595 | 596 | devices.forEach(d=> { 597 | 598 | let t = sn_map.get(d.possible_subnet); 599 | if (t) { 600 | t.devices.push(d); 601 | } else { 602 | if (has_pub_internet) { 603 | t = sn_map.get("0.0.0.0/0"); 604 | } else { 605 | console.log(`No subnet for ${d.dev_src_ip} found`); 606 | } 607 | } 608 | }); 609 | 610 | let c_vlans = new Set(subnets.map(d=>d.vlan)); 611 | 612 | let interface_box = { 613 | w: 6 * grid.x_offset, 614 | h: 3 * grid.x_offset, 615 | }; 616 | 617 | 618 | deviceMap = new Map(); 619 | 620 | subnets.forEach(function(subnet, j) { 621 | let ctr = processDevices(subnet.net.split("/")[0], subnet.devices, j); 622 | 623 | subnet.devices.forEach(d=> { 624 | d.subnet = subnet; 625 | deviceMap.set(d.dev_src_ip, d); 626 | }); 627 | 628 | subnet.empty_grid = []; 629 | for (let i = 0; i < ctr; i ++) { 630 | let o = {color: colors[j%9]}; 631 | o.x = i % 8; 632 | o.y = Math.floor(i / 8); 633 | subnet.empty_grid.push(o); 634 | } 635 | subnet.color = colors[j%9]; 636 | 637 | subnet.w = (8+1) * grid.x_offset; 638 | subnet.h = (Math.ceil(subnet.empty_grid.length / 8) + 1) * grid.y_offset; 639 | 640 | subnet.name = "S"+(j+1) 641 | }); 642 | 643 | let packer = new Packer(grid.width, grid.height); 644 | subnets.sort(function(a,b) { return (b.h*b.w < a.h*a.w); }); 645 | 646 | let packable_elems = [interface_box].concat(subnets); 647 | let success = packer.fit(packable_elems); 648 | 649 | if (!success) { 650 | errorMessage(`Could not fit ${subnets.length} subnets and ${devices.length} 651 | devices on the page`); 652 | return; 653 | } 654 | 655 | let net_routes = valueMap.get("net_route"); 656 | 657 | let routers = valueMap.get("router"); 658 | 659 | let net_route_sets = merge_routes(net_routes, sn_map, routers); 660 | 661 | let traced_routes = valueMap.get("tracedroute"); 662 | 663 | let tr_list = processTracedroutes(traced_routes, deviceMap, grid); 664 | 665 | let metadata = { 666 | title: title, 667 | date: earliestDate.toLocaleDateString(), 668 | host: window.location.hostname, 669 | num_dev: devices.length, 670 | num_subnets: subnets.length, 671 | num_nets: net_route_sets.length 672 | }; 673 | 674 | setupHTML(metadata); 675 | 676 | deleteForm(); 677 | 678 | drawSVG(subnets, routers, tr_list, grid); 679 | 680 | layoutPCBPaths(subnets, routers, net_route_sets, grid); 681 | } 682 | -------------------------------------------------------------------------------- /viz/index.html: -------------------------------------------------------------------------------- 1 | 2 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 |
    129 |

    Generate a network map fit for print

    130 | 131 | generated by aaalm created by Secure Network in collaboration with Storheil Design 132 | 133 |
    134 | 135 |
    136 |
    137 | 138 | 139 | 144 | 145 |
    146 | 147 | 149 | 150 |
    151 | 152 | 153 | 158 |

    Please note that this form does not post data anywhere. 159 | It only processes local files. 160 |

    161 |
      162 | 163 | 164 |

      165 |
      166 |
      167 | 168 | 169 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 |
      199 | 200 |
      201 |
      202 |

      Legend

      203 |
      204 |
      205 |

      Device

      206 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 |
      217 |
      218 |

      Router

      219 | 221 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 |
      240 |
      241 |

      Gateway

      242 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 |
      254 |
      255 |

      Total Devices

      256 |

      0

      257 |
      258 |
      259 |

      Total Subnets

      260 |

      0

      261 |
      262 |
      263 |

      Non Overlapping Nets

      264 |

      0

      265 |
      266 |
      267 |

      268 |
      269 |
      270 |
      271 | 272 | 273 | 274 | 275 | -------------------------------------------------------------------------------- /viz/js-pcb/dsn2pcb.js: -------------------------------------------------------------------------------- 1 | // pin = [string m_name, string m_form, float m_x, float m_y, float m_angle] 2 | // component = [string m_name, map m_pin_map] 3 | // rule = [float m_radius, float m_gap, points_2d[] m_shape] 4 | // instance = [string m_name, string m_comp, string m_side, float m_x, float m_y, float m_angle] 5 | // circuit = [string m_via, rule m_rule] 6 | 7 | "use strict"; 8 | 9 | var js_pcb = js_pcb || {}; 10 | (function() 11 | { 12 | function dsn2pcb(dsn, gap) 13 | { 14 | let EOF = -1; 15 | let stream = [dsn, 0]; 16 | 17 | //peek next char from stream 18 | function peek(stream) 19 | { 20 | if (stream[1] === stream[0].length) return EOF; 21 | return stream[0].charAt(stream[1]); 22 | } 23 | 24 | //get next char from stream 25 | function get(stream) 26 | { 27 | if (stream[1] === stream[0].length) return EOF; 28 | return stream[0].charAt(stream[1]++); 29 | } 30 | 31 | //read input till given byte appears 32 | function read_until(stream, c) 33 | { 34 | for (;;) 35 | { 36 | let input = get(stream); 37 | if (input === EOF) break; 38 | if (input === c) return false; 39 | } 40 | return true; 41 | } 42 | 43 | //read whitespace 44 | function read_whitespace(stream) 45 | { 46 | for (;;) 47 | { 48 | let b = peek(stream); 49 | if (b !== '\t' && b !== '\n' && b !== '\r' && b !== ' ') break; 50 | get(stream); 51 | } 52 | } 53 | 54 | function read_node_name(stream) 55 | { 56 | let s = ""; 57 | for (;;) 58 | { 59 | let b = peek(stream); 60 | if (b === '\t' || b === '\n' || b === '\r' || b === ' ' || b === ')') break; 61 | s += get(stream); 62 | } 63 | return s; 64 | } 65 | 66 | function read_string(stream) 67 | { 68 | let s = ""; 69 | for (;;) 70 | { 71 | let b = peek(stream); 72 | if (b === '\t' || b === '\n' || b === '\r' || b === ' ' || b === ')') break; 73 | s += get(stream); 74 | } 75 | return [s, []]; 76 | } 77 | 78 | function read_quoted_string(stream) 79 | { 80 | let s = ""; 81 | for (;;) 82 | { 83 | let b = peek(stream); 84 | if (b === '"') break; 85 | s += get(stream); 86 | } 87 | return [s, []]; 88 | } 89 | 90 | function read_tree(stream) 91 | { 92 | read_until(stream, '('); 93 | read_whitespace(stream); 94 | let t = [read_node_name(stream), []]; 95 | for (;;) 96 | { 97 | read_whitespace(stream); 98 | let b = peek(stream); 99 | if (b === EOF) break; 100 | if (b === ')') 101 | { 102 | get(stream); 103 | break; 104 | } 105 | if (b === '(') 106 | { 107 | t[1].push(read_tree(stream)); 108 | continue; 109 | } 110 | if (b === '"') 111 | { 112 | get(stream); 113 | t[1].push(read_quoted_string(stream)); 114 | get(stream); 115 | continue; 116 | } 117 | t[1].push(read_string(stream)); 118 | } 119 | return t; 120 | } 121 | 122 | function search_tree(t, s) 123 | { 124 | if (t[0] === s) return t; 125 | for (let i = 0; i < t[1].length; i++) 126 | { 127 | let st = search_tree(t[1][i], s); 128 | if (st.length) return st; 129 | } 130 | return []; 131 | } 132 | 133 | function print_tree(t, indent = 0) 134 | { 135 | if (t[0].length) 136 | { 137 | console.log(" ".repeat(indent) + t[0]); 138 | } 139 | for (let ct of t[1]) 140 | { 141 | print_tree(ct, indent + 1); 142 | } 143 | } 144 | 145 | function shape_to_cords(shape, a1, a2) 146 | { 147 | let cords = []; 148 | let rads = (a1 + a2) % (2 * Math.PI); 149 | let s = Math.sin(rads); 150 | let c = Math.cos(rads); 151 | for (let p of shape) 152 | { 153 | let px = c * p[0] - s * p[1]; 154 | let py = s * p[0] + c * p[1]; 155 | cords.push([px, py]); 156 | } 157 | return cords; 158 | } 159 | 160 | function terms_equal(t1, t2) 161 | { 162 | return js_pcb.equal_3d(t1[2], t2[2]); 163 | } 164 | 165 | function term_index(terms, term) 166 | { 167 | for (let i = 0; i < terms.length; i++) 168 | { 169 | if (terms_equal(terms[i], term)) return i; 170 | } 171 | return -1; 172 | } 173 | 174 | let tree = read_tree(stream); 175 | let structure_root = search_tree(tree, "structure"); 176 | const units = 5; 177 | let num_layers = 0; 178 | let minx = 10000.0; 179 | let miny = 10000.0; 180 | let maxx = -10000.0; 181 | let maxy = -10000.0; 182 | let default_rule = [0.25, 0.25, []]; 183 | let default_via = "Via[0-1]_600:400_um"; 184 | for (let structure_node of structure_root[1]) 185 | { 186 | if (structure_node[0] === "layer") num_layers++; 187 | else if (structure_node[0] === "via") 188 | { 189 | for (let via_node of structure_node[1]) 190 | { 191 | default_via = via_node[0]; 192 | } 193 | } 194 | else if (structure_node[0] === "rule") 195 | { 196 | for (let rule_node of structure_node[1]) 197 | { 198 | if (rule_node[0] === "width") 199 | { 200 | default_rule[0] = parseFloat(rule_node[1][0]) / (2 * units); 201 | } 202 | else if ((rule_node[0] === "clear" 203 | || rule_node[0] === "clearance") 204 | && rule_node[1].length == 1) 205 | { 206 | default_rule[1] = parseFloat(rule_node[1][0]) / (2 * units); 207 | } 208 | } 209 | } 210 | else if (structure_node[0] === "boundary") 211 | { 212 | for (let boundary_node of structure_node[1]) 213 | { 214 | if (boundary_node[0] === "path") 215 | { 216 | for (let cords = 2; cords < boundary_node[1].length; cords += 2) 217 | { 218 | let px = parseFloat(boundary_node[1][cords][0]) / units; 219 | let py = parseFloat(boundary_node[1][cords+1][0]) / -units; 220 | minx = Math.min(px, minx); 221 | maxx = Math.max(px, maxx); 222 | miny = Math.min(py, miny); 223 | maxy = Math.max(py, maxy); 224 | } 225 | } 226 | else if (boundary_node[0] === "rect") 227 | { 228 | let x1 = parseFloat(boundary_node[1][1]) / units; 229 | let y1 = parseFloat(boundary_node[1][2]) / -units; 230 | let x2 = parseFloat(boundary_node[1][3]) / units; 231 | let y2 = parseFloat(boundary_node[1][4]) / -units; 232 | minx = Math.min(x1, minx); 233 | maxx = Math.max(x1, maxx); 234 | miny = Math.min(y1, miny); 235 | maxy = Math.max(y1, maxy); 236 | minx = Math.min(x2, minx); 237 | maxx = Math.max(x2, maxx); 238 | miny = Math.min(y2, miny); 239 | maxy = Math.max(y2, maxy); 240 | } 241 | } 242 | } 243 | } 244 | 245 | let library_root = search_tree(tree, "library"); 246 | let component_map = new Map(); 247 | let rule_map = new Map(); 248 | for (let library_node of library_root[1]) 249 | { 250 | if (library_node[0] === "image") 251 | { 252 | let component_name = library_node[1][0][0]; 253 | let the_comp = [component_name, new Map()]; 254 | for (let i = 1; i < library_node[1].length; ++i) 255 | { 256 | let image_node = library_node[1][i]; 257 | if (image_node[0] === "pin") 258 | { 259 | let the_pin = ['', image_node[1][0][0], 0, 0, 0]; 260 | if (image_node[1][1][0] === "rotate") 261 | { 262 | the_pin[0] = image_node[1][2][0]; 263 | the_pin[2] = parseFloat(image_node[1][3][0]); 264 | the_pin[3] = parseFloat(image_node[1][4][0]); 265 | the_pin[4] = parseFloat(image_node[1][1][1][0][0]) * (Math.PI / 180.0); 266 | } 267 | else 268 | { 269 | the_pin[0] = image_node[1][1][0]; 270 | the_pin[2] = parseFloat(image_node[1][2][0]); 271 | the_pin[3] = parseFloat(image_node[1][3][0]); 272 | the_pin[4] = 0.0; 273 | } 274 | the_pin[2] /= units; 275 | the_pin[3] /= -units; 276 | the_comp[1].set(the_pin[0], the_pin); 277 | } 278 | } 279 | component_map.set(component_name, the_comp); 280 | } 281 | else if (library_node[0] === "padstack") 282 | { 283 | for (let i = 1; i < library_node[1].length; ++i) 284 | { 285 | let padstack_node = library_node[1][i]; 286 | if (padstack_node[0] === "shape") 287 | { 288 | let points = []; 289 | let the_rule = default_rule.slice(); 290 | if (padstack_node[1][0][0] === "circle") 291 | { 292 | the_rule[0] = parseFloat(padstack_node[1][0][1][1][0]) / (2 * units); 293 | } 294 | else if (padstack_node[1][0][0] === "path") 295 | { 296 | the_rule[0] = parseFloat(padstack_node[1][0][1][1][0]) / (2 * units); 297 | let x1 = parseFloat(padstack_node[1][0][1][2][0]); 298 | let y1 = parseFloat(padstack_node[1][0][1][3][0]); 299 | let x2 = parseFloat(padstack_node[1][0][1][4][0]); 300 | let y2 = parseFloat(padstack_node[1][0][1][5][0]); 301 | if (x1 != 0.0 302 | || x2 != 0.0 303 | || y1 != 0.0 304 | || y2 != 0.0) 305 | { 306 | x1 /= units; 307 | y1 /= -units; 308 | x2 /= units; 309 | y2 /= -units; 310 | points.push([x1, y1]); 311 | points.push([x2, y2]); 312 | } 313 | } 314 | else if (padstack_node[1][0][0] === "rect") 315 | { 316 | the_rule[0] = 0.0; 317 | let x1 = parseFloat(padstack_node[1][0][1][1][0]) / units; 318 | let y1 = parseFloat(padstack_node[1][0][1][2][0]) / -units; 319 | let x2 = parseFloat(padstack_node[1][0][1][3][0]) / units; 320 | let y2 = parseFloat(padstack_node[1][0][1][4][0]) / -units; 321 | points.push([x1, y1]); 322 | points.push([x2, y1]); 323 | points.push([x2, y2]); 324 | points.push([x1, y2]); 325 | points.push([x1, y1]); 326 | } 327 | else if (padstack_node[1][0][0] === "polygon") 328 | { 329 | the_rule[0] = 0.0; 330 | for (let i = 2; i < padstack_node[1][0][1].length; i += 2) 331 | { 332 | let x1 = parseFloat(padstack_node[1][0][1][i][0]) / units; 333 | let y1 = parseFloat(padstack_node[1][0][1][i + 1][0]) / -units; 334 | points.push([x1, y1]); 335 | } 336 | points.push(points[0]); 337 | } 338 | the_rule[2] = points; 339 | rule_map.set(library_node[1][0][0], the_rule); 340 | } 341 | } 342 | } 343 | } 344 | 345 | let placement_root = search_tree(tree, "placement"); 346 | let instance_map = new Map(); 347 | for (let placement_node of placement_root[1]) 348 | { 349 | if (placement_node[0] === "component") 350 | { 351 | let component_name = placement_node[1][0][0]; 352 | for (let i = 1; i < placement_node[1].length; ++i) 353 | { 354 | let component_node = placement_node[1][i]; 355 | if (component_node[0] == "place") 356 | { 357 | let the_instance = ['', '', '', 0, 0, 0]; 358 | let instance_name = component_node[1][0][0]; 359 | the_instance[0] = instance_name; 360 | the_instance[1] = component_name; 361 | the_instance[2] = component_node[1][3][0]; 362 | the_instance[3] = parseFloat(component_node[1][1][0]) / units; 363 | the_instance[4] = parseFloat(component_node[1][2][0]) / -units; 364 | the_instance[5] = parseFloat(component_node[1][4][0]) * -(Math.PI / 180.0); 365 | instance_map.set(instance_name, the_instance); 366 | } 367 | } 368 | } 369 | } 370 | 371 | let all_terminals = []; 372 | for (let value of instance_map.values()) 373 | { 374 | let component = component_map.get(value[1]); 375 | for (let pin of component[1].values()) 376 | { 377 | let m_x = pin[2]; 378 | let m_y = pin[3]; 379 | if (value[2] !== "front") m_x = -m_x; 380 | let s = Math.sin(value[5]); 381 | let c = Math.cos(value[5]); 382 | let px = (c * m_x - s * m_y) + value[3]; 383 | let py = (s * m_x + c * m_y) + value[4]; 384 | let pin_rule = rule_map.get(pin[1]); 385 | let tp = [px, py, 0.0]; 386 | let cords = shape_to_cords(pin_rule[2], pin[4], value[5]); 387 | all_terminals.push([pin_rule[0], pin_rule[1], tp, cords]); 388 | minx = Math.min(px, minx); 389 | maxx = Math.max(px, maxx); 390 | miny = Math.min(py, miny); 391 | maxy = Math.max(py, maxy); 392 | } 393 | } 394 | 395 | let network_root = search_tree(tree, "network"); 396 | let circuit_map = new Map(); 397 | for (let network_node of network_root[1]) 398 | { 399 | if (network_node[0] === "class") 400 | { 401 | let the_circuit = [default_via, default_rule.slice()]; 402 | for (let class_node of network_node[1]) 403 | { 404 | if (class_node[0] === "rule") 405 | { 406 | for (let dims of class_node[1]) 407 | { 408 | if (dims[0] === "width") 409 | { 410 | the_circuit[1][0] = parseFloat(dims[1][0][0]) / (2 * units); 411 | } 412 | if (dims[0] === "clearance") 413 | { 414 | the_circuit[1][1] = parseFloat(dims[1][0][0]) / (2 * units); 415 | } 416 | } 417 | } 418 | else if (class_node[0] === "circuit") 419 | { 420 | for (let circuit_node of class_node[1]) 421 | { 422 | if (circuit_node[0] === "use_via") 423 | { 424 | the_circuit[0] = circuit_node[1][0][0]; 425 | } 426 | } 427 | } 428 | } 429 | for (let netname of network_node[1]) 430 | { 431 | if (!netname[1].length) circuit_map.set(netname[0], the_circuit); 432 | } 433 | } 434 | } 435 | 436 | let the_tracks = []; 437 | for (let network_node of network_root[1]) 438 | { 439 | if (network_node[0] == "net") 440 | { 441 | for (let net_node of network_node[1]) 442 | { 443 | if (net_node[0] == "pins") 444 | { 445 | let the_terminals = []; 446 | for (let p of net_node[1]) 447 | { 448 | let pin_info = p[0].split('-'); 449 | let instance_name = pin_info[0]; 450 | let pin_name = pin_info[1]; 451 | let instance = instance_map.get(instance_name); 452 | let component = component_map.get(instance[1]); 453 | let pin = component[1].get(pin_name); 454 | let m_x = pin[2]; 455 | let m_y = pin[3]; 456 | if (instance[2] !== "front") m_x = -m_x; 457 | let s = Math.sin(instance[5]); 458 | let c = Math.cos(instance[5]); 459 | let px = (c * m_x - s * m_y) + instance[3]; 460 | let py = (s * m_x + c * m_y) + instance[4]; 461 | let pin_rule = rule_map.get(pin[1]); 462 | let tp = [px, py, 0.0]; 463 | let cords = shape_to_cords(pin_rule[2], pin[4], instance[5]); 464 | let term = [pin_rule[0], pin_rule[1], tp, cords]; 465 | the_terminals.push(term); 466 | let index = term_index(all_terminals, term); 467 | if (index !== -1) all_terminals.splice(index, 1); 468 | } 469 | let circuit = circuit_map.get(network_node[1][0][0]); 470 | let net_rule = circuit[1]; 471 | let via_rule = rule_map.get(circuit[0]); 472 | the_tracks.push([net_rule[0], via_rule[0], net_rule[1], the_terminals, []]); 473 | } 474 | } 475 | } 476 | } 477 | the_tracks.push([0.0, 0.0, 0.0, all_terminals, []]); 478 | 479 | 480 | //output pcb format 481 | for (let track of the_tracks) 482 | { 483 | for (let terminal of track[3]) 484 | { 485 | terminal[2][0] -= (minx - gap); 486 | terminal[2][1] -= (miny - gap); 487 | } 488 | } 489 | return [[Math.trunc(maxx - minx + (gap * 2) + 0.5), 490 | Math.trunc(maxy - miny + (gap * 2) + 0.5), 491 | num_layers], 492 | the_tracks]; 493 | } 494 | 495 | js_pcb.dsn2pcb = dsn2pcb; 496 | })(); 497 | -------------------------------------------------------------------------------- /viz/js-pcb/inter-map.js: -------------------------------------------------------------------------------- 1 | function compileTemplate(subnets, routers, net_route_sets, grid) { 2 | // TODO handle case where routes are empty 3 | let w = grid.width, h = grid.height; 4 | 5 | 6 | let xOff = x=> x*grid.x_offset+25; 7 | let yOff = y=> y*grid.y_offset+25; 8 | 9 | let routeableNodes = []; 10 | 11 | routers.forEach((d,i)=>{ 12 | let o = { 13 | name: d.name, 14 | x: xOff(d.x), 15 | y: yOff(d.y) 16 | }; 17 | routeableNodes.push(o); 18 | 19 | }); 20 | 21 | let netString = ``; 22 | let netNames = ""; 23 | 24 | net_route_sets.forEach((set,idx)=> { 25 | let paths = []; 26 | set.forEach(d=>paths.push(d)); 27 | 28 | netNames += ` NP${idx}`; 29 | let b = ` 30 | (net NP${idx} 31 | (pins ${paths.join(" ")}) 32 | )`; 33 | 34 | netString += b; 35 | }); 36 | 37 | let subnetConnectors = []; 38 | 39 | subnets.forEach((d,i)=>{ 40 | let o = { 41 | name: d.name, 42 | x: d.fit.x + grid.x_offset/2, 43 | y: d.fit.y + grid.y_offset/2 44 | }; 45 | subnetConnectors.push(o); 46 | }) 47 | 48 | subnetCString = ""; 49 | subnetConnectors.forEach((d,i)=> { 50 | subnetCString += ` 51 | (place ${d.name} ${d.x} ${d.y} front 0) 52 | `; 53 | }); 54 | 55 | 56 | let placeTargets = ""; 57 | routeableNodes.forEach((d,i)=> { 58 | placeTargets += ` 59 | (place ${d.name} ${d.x} ${d.y} front 0) 60 | `; 61 | }); 62 | 63 | const compiled_template = ` 64 | (pcb /place/holder 65 | (structure 66 | (layer A.Cu) 67 | (layer B.Cu) 68 | (layer C.Cu) 69 | (boundary 70 | (path pcb 0 0 0 ${w} 0 ${w} ${h} 0 ${h} ) 71 | ) 72 | (via "Via") 73 | (rule 74 | (width 2) 75 | (clearance 2) 76 | (clearance 2) 77 | (clearance 2) 78 | ) 79 | ) 80 | (placement 81 | (component OBSDEV 82 | (place LCN ${grid.x_offset/2} ${grid.y_offset/2} front 0) 83 | ) 84 | (component NODE 85 | ${placeTargets} 86 | ) 87 | (component SUBNET 88 | ${subnetCString} 89 | ) 90 | ) 91 | (library 92 | (image OBSDEV 93 | (pin Round[A] 1 0 0) 94 | ) 95 | (image SUBNET 96 | (pin Round[A] 1 0 0) 97 | (pin RectHuge 2 0 0) 98 | ) 99 | (image NODE 100 | (pin Rect[T] 1 -12 -12) 101 | (pin Rect[T] 2 12 12) 102 | (pin RectBlocker 3 0 0) 103 | ) 104 | (padstack Round[A] 105 | (shape (circle A.Cu 10)) 106 | (shape (circle B.Cu 10)) 107 | (shape (circle C.Cu 10)) 108 | ) 109 | (padstack RectHuge 110 | (shape (rect A.Cu ${grid.x_offset/4 - 5} ${-grid.y_offset/4} ${grid.x_offset*1} ${grid.y_offset/4})) 111 | (shape (rect B.Cu ${grid.x_offset/4 - 5} ${-grid.y_offset/4} ${grid.x_offset*1} ${grid.y_offset/4})) 112 | (shape (rect C.Cu ${grid.x_offset/4 - 5} ${-grid.y_offset/4} ${grid.x_offset*1} ${grid.y_offset/4})) 113 | ) 114 | (padstack Rect[T] 115 | (shape (rect A.Cu -4 -4 4 4)) 116 | (shape (rect B.Cu -4 -4 4 4)) 117 | (shape (rect C.Cu -4 -4 4 4)) 118 | ) 119 | (padstack RectBlocker 120 | (shape (rect A.Cu -8 -8 8 8)) 121 | (shape (rect B.Cu -8 -8 8 8)) 122 | (shape (rect C.Cu -8 -8 8 8)) 123 | ) 124 | (padstack "Via" 125 | (shape (circle A.Cu 8)) 126 | (shape (circle B.Cu 8)) 127 | (shape (circle C.Cu 8)) 128 | ) 129 | ) 130 | (network 131 | ${netString} 132 | (class kicad_default "" ${netNames} 133 | (circuit 134 | (use_via Via) 135 | ) 136 | (rule 137 | (width 2) 138 | (clearance 4) 139 | ) 140 | ) 141 | ) 142 | ) 143 | `; 144 | console.log(compiled_template); 145 | return compiled_template; 146 | } 147 | -------------------------------------------------------------------------------- /viz/js-pcb/layer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var js_pcb = js_pcb || {}; 4 | (function() 5 | { 6 | class Line 7 | { 8 | constructor(p1, p2, r, g) 9 | { 10 | this.m_p1 = p1; 11 | this.m_p2 = p2; 12 | this.m_radius = r; 13 | this.m_gap = g; 14 | } 15 | 16 | equal(l) 17 | { 18 | return this.m_radius === l.m_radius 19 | && this.m_gap === l.m_gap 20 | && js_pcb.equal_2d(this.m_p1, l.m_p1) 21 | && js_pcb.equal_2d(this.m_p2, l.m_p2); 22 | } 23 | } 24 | 25 | function bucket_index(b, l) 26 | { 27 | for (let i = 0; i < b.length; i++) 28 | { 29 | if (l.equal(b[i].m_line)) return i; 30 | } 31 | return -1; 32 | } 33 | 34 | class Record 35 | { 36 | constructor(id, l) 37 | { 38 | this.m_id = id; 39 | this.m_line = l; 40 | let pv = js_pcb.perp_2d(js_pcb.sub_2d(l.m_p2, l.m_p1)); 41 | this.m_lv_norm = js_pcb.scale_2d(pv, 1.0 / js_pcb.length_2d(pv)); 42 | this.m_lv_dist = js_pcb.dot_2d(this.m_lv_norm, l.m_p1); 43 | } 44 | 45 | hit(l, d) 46 | { 47 | let dp1 = js_pcb.dot_2d(this.m_lv_norm, l.m_p1) - this.m_lv_dist; 48 | let dp2 = js_pcb.dot_2d(this.m_lv_norm, l.m_p2) - this.m_lv_dist; 49 | if (dp1 > d && dp2 > d) return false; 50 | if (dp1 < -d && dp2 < -d) return false; 51 | return js_pcb.collide_thick_lines_2d(l.m_p1, l.m_p2, this.m_line.m_p1, this.m_line.m_p2, d); 52 | } 53 | } 54 | 55 | class Layer 56 | { 57 | constructor(dims, s) 58 | { 59 | let w, h; 60 | [w, h] = dims; 61 | this.m_width = w; 62 | this.m_height = h; 63 | this.m_scale = s; 64 | this.m_test = 0; 65 | this.m_buckets = []; 66 | while (this.m_buckets.push([]) < (w * h)) {} 67 | } 68 | 69 | get_aabb(l) 70 | { 71 | let x1, y1, x2, y2; 72 | [x1, y1] = l.m_p1; 73 | [x2, y2] = l.m_p2; 74 | if (x1 > x2) {let t = x1; x1 = x2; x2 = t;} 75 | if (y1 > y2) {let t = y1; y1 = y2; y2 = t;} 76 | let r = l.m_radius + l.m_gap; 77 | let minx = Math.trunc((x1 - r) * this.m_scale); 78 | let miny = Math.trunc((y1 - r) * this.m_scale); 79 | let maxx = Math.trunc((x2 + r) * this.m_scale); 80 | let maxy = Math.trunc((y2 + r) * this.m_scale); 81 | minx = Math.max(0, minx); 82 | miny = Math.max(0, miny); 83 | maxx = Math.max(0, maxx); 84 | maxy = Math.max(0, maxy); 85 | minx = Math.min(this.m_width - 1, minx); 86 | maxx = Math.min(this.m_width - 1, maxx); 87 | miny = Math.min(this.m_height - 1, miny); 88 | maxy = Math.min(this.m_height - 1, maxy); 89 | return [minx, miny, maxx, maxy]; 90 | } 91 | 92 | add_line(l) 93 | { 94 | let bb = this.get_aabb(l); 95 | let r = new Record(0, l); 96 | for (let y = bb[1]; y <= bb[3]; ++y) 97 | { 98 | for (let x = bb[0]; x <= bb[2]; ++x) 99 | { 100 | this.m_buckets[y * this.m_width + x].push(r); 101 | } 102 | } 103 | } 104 | 105 | sub_line(l) 106 | { 107 | let bb = this.get_aabb(l); 108 | for (let y = bb[1]; y <= bb[3]; ++y) 109 | { 110 | for (let x = bb[0]; x <= bb[2]; ++x) 111 | { 112 | let b = this.m_buckets[y * this.m_width + x]; 113 | let index = bucket_index(b, l); 114 | if (index !== -1) b.splice(index, 1); 115 | } 116 | } 117 | } 118 | 119 | hit_line(l) 120 | { 121 | this.m_test += 1; 122 | let bb = this.get_aabb(l); 123 | for (let y = bb[1]; y <= bb[3]; ++y) 124 | { 125 | for (let x = bb[0]; x <= bb[2]; ++x) 126 | { 127 | let b = this.m_buckets[y * this.m_width + x]; 128 | for (let i = 0; i < b.length; i++) 129 | { 130 | let record = b[i]; 131 | if (record.m_id === this.m_test) continue; 132 | record.m_id = this.m_test; 133 | let d = l.m_radius + record.m_line.m_radius + Math.max(l.m_gap, record.m_line.m_gap); 134 | if (record.hit(l, d)) return true; 135 | } 136 | } 137 | } 138 | return false; 139 | } 140 | } 141 | 142 | class Layers 143 | { 144 | constructor(dims, s) 145 | { 146 | let w, h, d; 147 | [w, h, d] = dims; 148 | this.m_depth = d; 149 | this.m_layers = []; 150 | while (this.m_layers.push(new Layer([w, h], s)) < d) {} 151 | } 152 | 153 | add_line(p1, p2, r, g) 154 | { 155 | let z1 = Math.trunc(p1[2]); 156 | let z2 = Math.trunc(p2[2]); 157 | if (z1 > z2) {let t = z1; z1 = z2; z2 = t;} 158 | let l = new Line([p1[0], p1[1]], [p2[0], p2[1]], r, g); 159 | for (let z = z1; z <= z2; ++z) this.m_layers[z].add_line(l); 160 | } 161 | 162 | sub_line(p1, p2, r, g) 163 | { 164 | let z1 = Math.trunc(p1[2]); 165 | let z2 = Math.trunc(p2[2]); 166 | if (z1 > z2) {let t = z1; z1 = z2; z2 = t} 167 | let l = new Line([p1[0], p1[1]], [p2[0], p2[1]], r, g); 168 | for (let z = z1; z <= z2; ++z) this.m_layers[z].sub_line(l); 169 | } 170 | 171 | hit_line(p1, p2, r, g) 172 | { 173 | let z1 = Math.trunc(p1[2]); 174 | let z2 = Math.trunc(p2[2]); 175 | if (z1 > z2) {let t = z1; z1 = z2; z2 = t;} 176 | let l = new Line([p1[0], p1[1]], [p2[0], p2[1]], r, g); 177 | for (let z = z1; z <= z2; ++z) if (this.m_layers[z].hit_line(l)) return true; 178 | return false; 179 | } 180 | } 181 | 182 | js_pcb.Layers = Layers; 183 | })(); 184 | -------------------------------------------------------------------------------- /viz/js-pcb/main.js: -------------------------------------------------------------------------------- 1 | //filename and worker 2 | let file = null; 3 | let worker = null; 4 | 5 | //go button handler 6 | function handleOnGo(evt) 7 | { 8 | //params 9 | let arg_g = +document.getElementById('arg_g').value; 10 | let arg_t = +document.getElementById('arg_t').value; 11 | let arg_v = +document.getElementById('arg_v').value; 12 | let arg_s = +document.getElementById('arg_s').value; 13 | let arg_z = +document.getElementById('arg_z').value; 14 | let arg_r = +document.getElementById('arg_r').value; 15 | let arg_q = +document.getElementById('arg_q').value; 16 | let arg_d = +document.getElementById('arg_d').value; 17 | let arg_fr = +document.getElementById('arg_fr').value; 18 | let arg_xr = +document.getElementById('arg_xr').value; 19 | let arg_yr = +document.getElementById('arg_yr').value; 20 | 21 | //run pcb solver web worker thread, register output listner 22 | if (worker !== null) worker.terminate(); 23 | worker = new Worker('worker.js'); 24 | worker.addEventListener('message', function(event) 25 | { 26 | if (event.data.length) 27 | { 28 | //view the pcb output 29 | let scale = +document.getElementById('scale').value; 30 | console.log("Calling view_pcb", event.data); 31 | js_pcb.view_pcb(event.data, scale, 2); 32 | } 33 | }, false); 34 | 35 | //post to solver thread 36 | file = compiled_template; 37 | worker.postMessage([js_pcb.dsn2pcb(file, arg_g), 38 | arg_t, arg_v, arg_s, arg_z, arg_r, arg_q, arg_d, arg_fr, arg_xr, arg_yr]); 39 | } 40 | 41 | //file onload handler 42 | function handleOnLoad(evt) 43 | { 44 | //the onload get a string of the dsn file 45 | file = evt.target.result; 46 | } 47 | 48 | //file selection handler 49 | function handleFileSelect(evt) 50 | { 51 | //filelist 52 | let files = evt.target.files; 53 | let f = files[0]; 54 | 55 | //dsn files only. 56 | if (f.name.match('.*[.]dsn')) 57 | { 58 | //file reader 59 | let reader = new FileReader(); 60 | 61 | //onload handler 62 | reader.onload = handleOnLoad; 63 | 64 | //read the dsn file 65 | reader.readAsText(f); 66 | } 67 | } 68 | 69 | //register action handlers 70 | document.getElementById('files').addEventListener('change', handleFileSelect, false); 71 | document.getElementById('go').onclick = handleOnGo; 72 | -------------------------------------------------------------------------------- /viz/js-pcb/mymath.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var js_pcb = js_pcb || {}; 4 | (function() 5 | { 6 | /////////////////////// 7 | //distance metric stuff 8 | /////////////////////// 9 | 10 | function manhattan_distance_2d(p1, p2) 11 | { 12 | let dx = p1[0] - p2[0]; 13 | let dy = p1[1] - p2[1]; 14 | return Math.abs(dx) + Math.abs(dy); 15 | } 16 | 17 | function manhattan_distance_3d(p1, p2) 18 | { 19 | let dx = p1[0] - p2[0]; 20 | let dy = p1[1] - p2[1]; 21 | let dz = p1[2] - p2[2]; 22 | return Math.abs(dx) + Math.abs(dy) + Math.abs(dz); 23 | } 24 | 25 | function euclidean_distance_2d(p1, p2) 26 | { 27 | let dx = p1[0] - p2[0]; 28 | let dy = p1[1] - p2[1]; 29 | return Math.sqrt(dx * dx + dy * dy); 30 | } 31 | 32 | function euclidean_distance_3d(p1, p2) 33 | { 34 | let dx = p1[0] - p2[0]; 35 | let dy = p1[1] - p2[1]; 36 | let dz = p1[2] - p2[2]; 37 | return Math.sqrt(dx * dx + dy * dy + dz * dz); 38 | } 39 | 40 | function squared_euclidean_distance_2d(p1, p2) 41 | { 42 | let dx = p1[0] - p2[0]; 43 | let dy = p1[1] - p2[1]; 44 | return dx * dx + dy * dy; 45 | } 46 | 47 | function squared_euclidean_distance_3d(p1, p2) 48 | { 49 | let dx = p1[0] - p2[0]; 50 | let dy = p1[1] - p2[1]; 51 | let dz = p1[2] - p2[2]; 52 | return dx * dx + dy * dy + dz * dz; 53 | } 54 | 55 | function chebyshev_distance_2d(p1, p2) 56 | { 57 | let dx = Math.abs(p1[0] - p2[0]); 58 | let dy = Math.abs(p1[1] - p2[1]); 59 | return Math.max(dx, dy); 60 | } 61 | 62 | function chebyshev_distance_3d(p1, p2) 63 | { 64 | let dx = Math.abs(p1[0] - p2[0]); 65 | let dy = Math.abs(p1[1] - p2[1]); 66 | let dz = Math.abs(p1[2] - p2[2]); 67 | let d = Math.max(dx, dy); 68 | return Math.max(d, dz); 69 | } 70 | 71 | function reciprical_distance_2d(p1, p2) 72 | { 73 | let d = manhattan_distance_2d(p1, p2); 74 | if (d === 0.0) return 1.0; 75 | return 1.0 / d; 76 | } 77 | 78 | function reciprical_distance_3d(p1, p2) 79 | { 80 | let d = manhattan_distance_3d(p1, p2); 81 | if (d === 0.0) return 1.0; 82 | return 1.0 / d; 83 | } 84 | 85 | /////////////////////// 86 | //specific vector stuff 87 | /////////////////////// 88 | 89 | function equal_2d(n1, n2) 90 | { 91 | return (n1[0] === n2[0] && n1[1] === n2[1]); 92 | } 93 | 94 | function equal_3d(n1, n2) 95 | { 96 | return (n1[0] === n2[0] && n1[1] === n2[1] && n1[2] === n2[2]); 97 | } 98 | 99 | function add_2d(p1, p2) 100 | { 101 | return [p1[0] + p2[0], p1[1] + p2[1]]; 102 | } 103 | 104 | function sub_2d(p1, p2) 105 | { 106 | return [p1[0] - p2[0], p1[1] - p2[1]]; 107 | } 108 | 109 | function sub_3d(p1, p2) 110 | { 111 | return [p1[0] - p2[0], p1[1] - p2[1], p1[2] - p2[2]]; 112 | } 113 | 114 | function scale_2d(p, s) 115 | { 116 | return [p[0] * s, p[1] * s]; 117 | } 118 | 119 | function scale_3d(p, s) 120 | { 121 | return [p[0] * s, p[1] * s, p[2] * s]; 122 | } 123 | 124 | function perp_2d(p) 125 | { 126 | return [-p[1], p[0]]; 127 | } 128 | 129 | function dot_2d(p1, p2) 130 | { 131 | return p1[0] * p2[0] + p1[1] * p2[1]; 132 | } 133 | 134 | function det_2d(p1, p2) 135 | { 136 | return p1[0] * p2[1] - p1[1] * p2[0]; 137 | } 138 | 139 | function dot_3d(p1, p2) 140 | { 141 | return p1[0] * p2[0] + p1[1] * p2[1] + p1[2] * p2[2]; 142 | } 143 | 144 | function length_2d(p) 145 | { 146 | return Math.sqrt(dot_2d(p, p)); 147 | } 148 | 149 | function length_3d(p) 150 | { 151 | return Math.sqrt(dot_3d(p, p)); 152 | } 153 | 154 | function norm_2d(p) 155 | { 156 | let l = length_2d(p); 157 | if (l === 0.0) return [0.0, 0.0]; 158 | return scale_2d(p, 1.0 / l); 159 | } 160 | 161 | function norm_3d(p) 162 | { 163 | let l = length_3d(p); 164 | if (l === 0.0) return [0.0, 0.0, 0.0]; 165 | return scale_3d(p, 1.0 / l); 166 | } 167 | 168 | function distance_2d(p1, p2) 169 | { 170 | return length_2d(sub_2d(p2, p1)); 171 | } 172 | 173 | function distance_squared_2d(p1, p2) 174 | { 175 | let p = sub_2d(p2, p1); 176 | return dot_2d(p, p); 177 | } 178 | 179 | function distance_to_line_2d(p, p1, p2) 180 | { 181 | let lv = sub_2d(p2, p1); 182 | let pv = sub_2d(p, p1); 183 | let c1 = dot_2d(pv, lv); 184 | if (c1 <= 0.0) return distance_2d(p, p1); 185 | let c2 = dot_2d(lv, lv); 186 | if (c2 <= c1) return distance_2d(p, p2); 187 | return distance_2d(p, add_2d(p1, scale_2d(lv, c1/c2))); 188 | } 189 | 190 | function distance_squared_to_line_2d(p, p1, p2) 191 | { 192 | let lv = sub_2d(p2, p1); 193 | let pv = sub_2d(p, p1); 194 | let c1 = dot_2d(pv, lv); 195 | if (c1 <= 0.0) return distance_squared_2d(p, p1); 196 | let c2 = dot_2d(lv, lv); 197 | if (c2 <= c1) return distance_squared_2d(p, p2); 198 | return distance_squared_2d(p, add_2d(p1, scale_2d(lv, c1/c2))); 199 | } 200 | 201 | function collide_lines_2d(l1_p1, l1_p2, l2_p1, l2_p2) 202 | { 203 | let av = sub_2d(l1_p2, l1_p1); 204 | let bv = sub_2d(l2_p2, l2_p1); 205 | let cv = sub_2d(l2_p2, l1_p1); 206 | let axb = det_2d(av, bv); 207 | let axc = det_2d(av, cv); 208 | let cxb = det_2d(cv, bv); 209 | if (axb === 0.0) return false; 210 | if (axb > 0.0) 211 | { 212 | if ((axc < 0.0) || (axc > axb)) return false; 213 | if ((cxb < 0.0) || (cxb > axb)) return false; 214 | } 215 | else 216 | { 217 | if ((axc > 0.0) || (axc < axb)) return false; 218 | if ((cxb > 0.0) || (cxb < axb)) return false; 219 | } 220 | return true; 221 | } 222 | 223 | function collide_thick_lines_2d(tl1_p1, tl1_p2, tl2_p1, tl2_p2, r) 224 | { 225 | if (collide_lines_2d(tl1_p1, tl1_p2, tl2_p1, tl2_p2)) return true; 226 | r *= r; 227 | if (distance_squared_to_line_2d(tl2_p1, tl1_p1, tl1_p2) <= r) return true; 228 | if (distance_squared_to_line_2d(tl2_p2, tl1_p1, tl1_p2) <= r) return true; 229 | if (distance_squared_to_line_2d(tl1_p1, tl2_p1, tl2_p2) <= r) return true; 230 | if (distance_squared_to_line_2d(tl1_p2, tl2_p1, tl2_p2) <= r) return true; 231 | return false; 232 | } 233 | 234 | js_pcb.collide_thick_lines_2d = collide_thick_lines_2d; 235 | js_pcb.add_2d = add_2d; 236 | js_pcb.sub_2d = sub_2d; 237 | js_pcb.perp_2d = perp_2d; 238 | js_pcb.length_2d = length_2d; 239 | js_pcb.scale_2d = scale_2d; 240 | js_pcb.dot_2d = dot_2d; 241 | js_pcb.equal_2d = equal_2d; 242 | js_pcb.equal_3d = equal_3d; 243 | js_pcb.sub_3d = sub_3d; 244 | js_pcb.norm_3d = norm_3d; 245 | js_pcb.manhattan_distance_3d = manhattan_distance_3d; 246 | js_pcb.squared_euclidean_distance_3d = squared_euclidean_distance_3d; 247 | js_pcb.euclidean_distance_3d = euclidean_distance_3d; 248 | js_pcb.chebyshev_distance_3d= chebyshev_distance_3d; 249 | js_pcb.reciprical_distance_3d = reciprical_distance_3d; 250 | })(); 251 | -------------------------------------------------------------------------------- /viz/js-pcb/router.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var js_pcb = js_pcb || {}; 4 | (function() 5 | { 6 | Array.prototype.shuffle = function() 7 | { 8 | let i = this.length, j, temp; 9 | if (i === 0) return; 10 | while (--i) 11 | { 12 | j = Math.floor(Math.random() * (i + 1)); 13 | temp = this[i]; 14 | this[i] = this[j]; 15 | this[j] = temp; 16 | } 17 | } 18 | 19 | Array.prototype.move = function(old_index, new_index) 20 | { 21 | this.splice(new_index, 0, this.splice(old_index, 1)[0]); 22 | } 23 | 24 | const spacial_hash_res = 0.75; 25 | let i = 0; 26 | //aabb of terminals 27 | function aabb_terminals(terms, quantization) 28 | { 29 | console.log("aabb_terminals"); 30 | if (terms.length == 0) { 31 | console.log("empty"); 32 | } 33 | i++; 34 | let minx = (Math.trunc(terms[0][2][0]) / quantization) * quantization; 35 | let miny = (Math.trunc(terms[0][2][1]) / quantization) * quantization; 36 | let maxx = ((Math.trunc(terms[0][2][0]) + (quantization - 1)) / quantization) * quantization; 37 | let maxy = ((Math.trunc(terms[0][2][1]) + (quantization - 1)) / quantization) * quantization; 38 | for (let i = 1; i < terms.length; ++i) 39 | { 40 | let tminx = (Math.trunc(terms[i][2][0]) / quantization) * quantization; 41 | let tminy = (Math.trunc(terms[i][2][1]) / quantization) * quantization; 42 | let tmaxx = ((Math.trunc(terms[i][2][0]) + (quantization - 1)) / quantization) * quantization; 43 | let tmaxy = ((Math.trunc(terms[i][2][1]) + (quantization - 1)) / quantization) * quantization; 44 | minx = Math.min(tminx, minx); 45 | miny = Math.min(tminy, miny); 46 | maxx = Math.max(tmaxx, maxx); 47 | maxy = Math.max(tmaxy, maxy); 48 | } 49 | return [(maxx - minx) * (maxy - miny), [minx, miny, maxx, maxy]]; 50 | } 51 | 52 | //set class 53 | class NodeSet 54 | { 55 | constructor(init) 56 | { 57 | if (init === undefined) this._data = new Map(); 58 | else this._data = new Map(init._data.entries()); 59 | this.size = this._data.size; 60 | } 61 | 62 | add(n) 63 | { 64 | let k = n.toString(); 65 | if (!this._data.has(k)) 66 | { 67 | this._data.set(k, n); 68 | this.size += 1; 69 | } 70 | } 71 | 72 | has(n) 73 | { 74 | return this._data.has(n.toString()); 75 | } 76 | 77 | [Symbol.iterator]() 78 | { 79 | return this._data.values(); 80 | } 81 | } 82 | 83 | //pcb class 84 | class Pcb 85 | { 86 | constructor(dims, rfvs, rpvs, dfunc, res, verb, quant, viascost) 87 | { 88 | let w, h, d; 89 | [w, h, d] = dims; 90 | this.m_width = w * res; 91 | this.m_height = h * res; 92 | this.m_stride = this.m_width * this.m_height; 93 | this.m_depth = d; 94 | this.m_routing_flood_vectors = rfvs; 95 | this.m_routing_path_vectors = rpvs; 96 | this.m_dfunc = dfunc; 97 | this.m_resolution = res; 98 | this.m_verbosity = verb; 99 | this.m_quantization = quant * res; 100 | this.m_viascost = viascost * res; 101 | this.m_layers = new js_pcb.Layers([Math.trunc(w * spacial_hash_res), Math.trunc(h * spacial_hash_res), d], spacial_hash_res / res); 102 | this.m_deform = new Map(); 103 | this.m_netlist = []; 104 | this.m_nodes = new Uint32Array(this.m_stride * this.m_depth); 105 | this.m_via_vectors = [[[0, 0, -1], [0, 0, 1]], [[0, 0, -1], [0, 0, 1]]]; 106 | } 107 | 108 | //add net 109 | add_track(track) 110 | { 111 | let track_radius, via_radius, track_gap, terminals, paths; 112 | [track_radius, via_radius, track_gap, terminals, paths] = track; 113 | this.m_netlist.push(new Net(track_radius, via_radius, track_gap, terminals, this)); 114 | } 115 | 116 | //remove netlist from board 117 | remove_netlist() 118 | { 119 | for (let net of this.m_netlist) net.remove(); 120 | } 121 | 122 | //attempt to route board within time 123 | route(timeout) 124 | { 125 | this.remove_netlist(); 126 | this.unmark_distances(); 127 | this.reset_areas(); 128 | this.shuffle_netlist(); 129 | this.m_netlist.sort(function(n1, n2) 130 | { 131 | if (n1.m_area === n2.m_area) return n1.m_radius - n2.m_radius; 132 | return n1.m_area - n2.m_area; 133 | }); 134 | let hoisted_nets = new Set(); 135 | let index = 0; 136 | // let start_time = std::chrono::high_resolution_clock::now(); 137 | while (index < this.m_netlist.length) 138 | { 139 | if (this.m_netlist[index].route()) index++; 140 | else 141 | { 142 | if (index === 0) 143 | { 144 | this.reset_areas(); 145 | this.shuffle_netlist(); 146 | this.m_netlist.sort(function(n1, n2) 147 | { 148 | if (n1.m_area === n2.m_area) return n1.m_radius - n2.m_radius; 149 | return n1.m_area - n2.m_area; 150 | }); 151 | hoisted_nets.clear(); 152 | } 153 | else 154 | { 155 | let pos = this.hoist_net(index); 156 | if ((pos === index) || (hoisted_nets.has(this.m_netlist[pos]))) 157 | { 158 | if (pos !== 0) 159 | { 160 | this.m_netlist[pos].m_area = this.m_netlist[pos-1].m_area; 161 | pos = this.hoist_net(pos); 162 | } 163 | hoisted_nets.delete(this.m_netlist[pos]); 164 | } 165 | else hoisted_nets.add(this.m_netlist[pos]); 166 | while (index > pos) 167 | { 168 | this.m_netlist[index].remove(); 169 | this.m_netlist[index].shuffle_topology(); 170 | index--; 171 | } 172 | } 173 | } 174 | // let end_time = std::chrono::high_resolution_clock::now(); 175 | // std::chrono::duration elapsed = end_time - start_time; 176 | // if (elapsed.count() >= timeout) return false; 177 | if (this.m_verbosity >= 1) postMessage(this.output_pcb()); 178 | } 179 | return true; 180 | } 181 | 182 | //cost of board in complexity terms 183 | cost() 184 | { 185 | let sum = 0; 186 | for (let net of this.m_netlist) for (let path of net.m_paths) sum += path.length; 187 | return sum; 188 | } 189 | 190 | //increase area quantization 191 | increase_quantization() 192 | { 193 | this.m_quantization++; 194 | } 195 | 196 | //output netlist and paths of board for viewer app 197 | output_pcb() 198 | { 199 | let scale = 1.0 / this.m_resolution; 200 | let tracks = []; 201 | for (let net of this.m_netlist) tracks.push(net.output_net()); 202 | return [[Math.trunc(this.m_width * scale), Math.trunc(this.m_height * scale), this.m_depth], tracks]; 203 | } 204 | 205 | //convert grid node to space node 206 | grid_to_space_point(n) 207 | { 208 | let p = this.m_deform.get(n.toString()); 209 | if (p !== undefined) 210 | { 211 | return p; 212 | } 213 | return n; 214 | } 215 | 216 | //set grid node to value 217 | set_node(n, value) 218 | { 219 | this.m_nodes[(this.m_stride*n[2])+(n[1]*this.m_width)+n[0]] = value; 220 | } 221 | 222 | //get grid node value 223 | get_node(n) 224 | { 225 | return this.m_nodes[(this.m_stride*n[2])+(n[1]*this.m_width)+n[0]]; 226 | } 227 | 228 | //generate all grid points surrounding node, that are not value 0 229 | all_marked(vec, n) 230 | { 231 | let w = this.m_width; 232 | let h = this.m_height; 233 | let d = this.m_depth; 234 | let gn = this.get_node; 235 | let sort_nodes = []; 236 | let x, y, z; 237 | [x, y, z] = n; 238 | for (let v of vec[z%2]) 239 | { 240 | let nx, ny, nz; 241 | [nx, ny, nz] = v; 242 | nx += x, ny += y, nz += z; 243 | if ((0 <= nx) && (nx < w) 244 | && (0 <= ny) && (ny < h) 245 | && (0 <= nz) && (nz < d)) 246 | { 247 | let n = [nx, ny, nz]; 248 | let mark = gn.call(this, n); 249 | if (mark !== 0) sort_nodes.push([mark, n]); 250 | } 251 | } 252 | return sort_nodes; 253 | } 254 | 255 | //generate all grid points surrounding node, that are value 0 256 | all_not_marked(vec, n) 257 | { 258 | let w = this.m_width; 259 | let h = this.m_height; 260 | let d = this.m_depth; 261 | let gn = this.get_node; 262 | let nodes = []; 263 | let x, y, z; 264 | [x, y, z] = n; 265 | for (let v of vec[z%2]) 266 | { 267 | let nx, ny, nz; 268 | [nx, ny, nz] = v; 269 | nx += x, ny += y, nz += z; 270 | if ((0 <= nx) && (nx < w) 271 | && (0 <= ny) && (ny < h) 272 | && (0 <= nz) && (nz < d)) 273 | { 274 | let n = [nx, ny, nz]; 275 | if (gn.call(this, n) === 0) nodes.push(n); 276 | } 277 | } 278 | return nodes; 279 | } 280 | 281 | //generate all grid points surrounding node sorted 282 | all_nearer_sorted(vec, n, dfunc) 283 | { 284 | let gsp = this.grid_to_space_point; 285 | let gp = gsp.call(this, n); 286 | let distance = this.get_node(n); 287 | let marked_nodes = this.all_marked(vec, n).filter((mn) => 288 | { 289 | if ((distance - mn[0]) <= 0) return false; 290 | mn[0] = dfunc(gsp.call(this, mn[1]), gp); 291 | return true; 292 | }); 293 | marked_nodes.sort(function(s1, s2) { return s1[0] - s2[0]; }); 294 | return marked_nodes.map(function(mn) { return mn[1]; }); 295 | } 296 | 297 | //generate all grid points surrounding node that are not shorting with an existing track 298 | all_not_shorting(gather, n, radius, gap) 299 | { 300 | let gsp = this.grid_to_space_point; 301 | let layers = this.m_layers; 302 | let hit_line = this.m_layers.hit_line; 303 | let nodes = []; 304 | let np = gsp.call(this, n); 305 | for (let new_node of gather) 306 | { 307 | let nnp = gsp.call(this, new_node); 308 | if (!hit_line.call(layers, np, nnp, radius, gap)) nodes.push(new_node); 309 | } 310 | return nodes; 311 | } 312 | 313 | //flood fill distances from starts till ends covered 314 | mark_distances(vec, radius, via, gap, starts, ends) 315 | { 316 | let gn = this.get_node; 317 | let sn = this.set_node; 318 | let anm = this.all_not_marked; 319 | let ans = this.all_not_shorting; 320 | let via_vectors = this.m_via_vectors; 321 | let distance = 1; 322 | let frontier = new NodeSet(starts); 323 | let vias_nodes = new Map(); 324 | while (frontier.size || vias_nodes.size) 325 | { 326 | for (let node of frontier) sn.call(this, node, distance); 327 | if (ends.every((node) => { return gn.call(this, node); })) break; 328 | let new_nodes = new NodeSet(); 329 | for (let node of frontier) 330 | { 331 | for (let new_node of ans.call(this, anm.call(this, vec, node), node, radius, gap)) 332 | { 333 | new_nodes.add(new_node); 334 | } 335 | } 336 | let new_vias_nodes = new NodeSet(); 337 | for (let node of frontier) 338 | { 339 | for (let new_node of ans.call(this, anm.call(this, via_vectors, node), node, via, gap)) 340 | { 341 | new_vias_nodes.add(new_node); 342 | } 343 | } 344 | if (new_vias_nodes.size) vias_nodes.set(distance+this.m_viascost, new_vias_nodes); 345 | let delay_nodes = vias_nodes.get(distance); 346 | if (delay_nodes !== undefined) 347 | { 348 | for (let node of delay_nodes) if (gn.call(this, node) === 0) new_nodes.add(node); 349 | vias_nodes.delete(distance); 350 | } 351 | frontier = new_nodes; 352 | distance++; 353 | } 354 | } 355 | 356 | //set all grid values back to 0 357 | unmark_distances() 358 | { 359 | this.m_nodes.fill(0); 360 | } 361 | 362 | //reset areas 363 | reset_areas() 364 | { 365 | for (let net of this.m_netlist) 366 | { 367 | [net.m_area, net.m_bbox] = aabb_terminals(net.m_terminals, this.m_quantization); 368 | } 369 | } 370 | 371 | //shuffle order of netlist 372 | shuffle_netlist() 373 | { 374 | this.m_netlist.shuffle(); 375 | for (let net of this.m_netlist) net.shuffle_topology(); 376 | } 377 | 378 | //move net to top of area group 379 | hoist_net(n) 380 | { 381 | let i = 0; 382 | if (n != 0) 383 | { 384 | for (i = n; i >= 0; --i) if (this.m_netlist[i].m_area < this.m_netlist[n].m_area) break; 385 | i++; 386 | if (n != i) 387 | { 388 | this.m_netlist.move(n, i); 389 | } 390 | } 391 | return i; 392 | } 393 | } 394 | 395 | //scale terminals for resolution of grid 396 | function scale_terminals(terms, res) 397 | { 398 | for (let term of terms) 399 | { 400 | term[0] *= res; 401 | term[1] *= res; 402 | term[2][0] *= res; 403 | term[2][1] *= res; 404 | for (let p of term[3]) 405 | { 406 | p[0] *= res; 407 | p[1] *= res; 408 | } 409 | } 410 | } 411 | 412 | //net methods 413 | class Net 414 | { 415 | constructor(radius, via, gap, terms, pcb) 416 | { 417 | this.m_pcb = pcb; 418 | this.m_radius = radius * pcb.m_resolution; 419 | this.m_via = via * pcb.m_resolution; 420 | this.m_gap = gap * pcb.m_resolution; 421 | this.m_terminals = terms; 422 | this.m_paths = []; 423 | scale_terminals(this.m_terminals, pcb.m_resolution); 424 | if (this.m_terminals.length == 0) { console.log("failed", terms); } 425 | [this.m_area, this.m_bbox] = aabb_terminals(this.m_terminals, pcb.m_quantization); 426 | this.remove(); 427 | for (let term of this.m_terminals) 428 | { 429 | for (let z = 0; z < pcb.m_depth; ++z) 430 | { 431 | let p = [Math.trunc(term[2][0] + 0.5), Math.trunc(term[2][1] + 0.5), z]; 432 | let sp = [term[2][0], term[2][1], z]; 433 | pcb.m_deform.set(p.toString(), sp); 434 | } 435 | } 436 | } 437 | 438 | //randomize order of terminals 439 | shuffle_topology() 440 | { 441 | this.m_terminals.shuffle(); 442 | } 443 | 444 | //add terminal entries to spacial cache 445 | add_terminal_collision_lines() 446 | { 447 | for (let node of this.m_terminals) 448 | { 449 | let r, g, x, y, shape; 450 | [r, g, [x, y, ,], shape] = node; 451 | if (!shape.length) 452 | this.m_pcb.m_layers.add_line([x, y, 0], [x, y, this.m_pcb.m_depth - 1], r, g); 453 | else 454 | { 455 | for (let z = 0; z < this.m_pcb.m_depth; ++z) 456 | { 457 | let p1 = [x + shape[0][0], y + shape[0][1], z]; 458 | for (let i = 1; i < shape.length; ++i) 459 | { 460 | let p0 = p1; 461 | p1 = [x + shape[i][0], y + shape[i][1], z]; 462 | this.m_pcb.m_layers.add_line(p0, p1, r, g); 463 | } 464 | } 465 | } 466 | } 467 | } 468 | 469 | //remove terminal entries from spacial cache 470 | sub_terminal_collision_lines() 471 | { 472 | for (let node of this.m_terminals) 473 | { 474 | let r, g, x, y, shape; 475 | [r, g, [x, y, ,], shape] = node; 476 | if (!shape.length) 477 | this.m_pcb.m_layers.sub_line([x, y, 0], [x, y, this.m_pcb.m_depth - 1], r, g); 478 | else 479 | { 480 | for (let z = 0; z < this.m_pcb.m_depth; ++z) 481 | { 482 | let p1 = [x + shape[0][0], y + shape[0][1], z]; 483 | for (let i = 1; i < shape.length; ++i) 484 | { 485 | let p0 = p1; 486 | p1 = [x + shape[i][0], y + shape[i][1], z]; 487 | this.m_pcb.m_layers.sub_line(p0, p1, r, g); 488 | } 489 | } 490 | } 491 | } 492 | } 493 | 494 | //add paths entries to spacial cache 495 | add_paths_collision_lines() 496 | { 497 | for (let path of this.m_paths) 498 | { 499 | let p1 = this.m_pcb.grid_to_space_point(path[0]); 500 | for (let i = 1; i < path.length; ++i) 501 | { 502 | let p0 = p1; 503 | p1 = this.m_pcb.grid_to_space_point(path[i]); 504 | if (path[i-1][2] !== path[i][2]) this.m_pcb.m_layers.add_line(p0, p1, this.m_via, this.m_gap); 505 | else this.m_pcb.m_layers.add_line(p0, p1, this.m_radius, this.m_gap); 506 | } 507 | } 508 | } 509 | 510 | //remove paths entries from spacial cache 511 | sub_paths_collision_lines() 512 | { 513 | for (let path of this.m_paths) 514 | { 515 | let p1 = this.m_pcb.grid_to_space_point(path[0]); 516 | for (let i = 1; i < path.length; ++i) 517 | { 518 | let p0 = p1; 519 | p1 = this.m_pcb.grid_to_space_point(path[i]); 520 | if (path[i-1][2] !== path[i][2]) this.m_pcb.m_layers.sub_line(p0, p1, this.m_via, this.m_gap); 521 | else this.m_pcb.m_layers.sub_line(p0, p1, this.m_radius, this.m_gap); 522 | } 523 | } 524 | } 525 | 526 | //remove net entries from spacial grid 527 | remove() 528 | { 529 | this.sub_paths_collision_lines(); 530 | this.sub_terminal_collision_lines(); 531 | this.m_paths = []; 532 | this.add_terminal_collision_lines(); 533 | } 534 | 535 | //remove redundant points from paths 536 | optimise_paths(paths) 537 | { 538 | let opt_paths = []; 539 | for (let path of paths) 540 | { 541 | let opt_path = []; 542 | let d = [0.0, 0.0, 0.0]; 543 | let p1 = this.m_pcb.grid_to_space_point(path[0]); 544 | for (let i = 1; i < path.length; ++i) 545 | { 546 | let p0 = p1; 547 | p1 = this.m_pcb.grid_to_space_point(path[i]); 548 | let d1 = js_pcb.norm_3d(js_pcb.sub_3d(p1, p0)); 549 | if (!js_pcb.equal_3d(d1, d)) 550 | { 551 | opt_path.push(path[i-1]); 552 | d = d1; 553 | } 554 | } 555 | opt_path.push(path[path.length-1]); 556 | opt_paths.push(opt_path); 557 | } 558 | return opt_paths; 559 | } 560 | 561 | //backtrack path from ends to starts 562 | backtrack_path(visited, end_node, radius, via, gap) 563 | { 564 | let via_vectors = this.m_pcb.m_via_vectors; 565 | let path = []; 566 | let path_node = end_node; 567 | for (;;) 568 | { 569 | path.push(path_node); 570 | let nearer_nodes = []; 571 | for (let node of this.m_pcb.all_not_shorting( 572 | this.m_pcb.all_nearer_sorted(this.m_pcb.m_routing_path_vectors, path_node, this.m_pcb.m_dfunc), 573 | path_node, radius, gap)) 574 | { 575 | nearer_nodes.push(node); 576 | } 577 | for (let node of this.m_pcb.all_not_shorting( 578 | this.m_pcb.all_nearer_sorted(via_vectors, path_node, this.m_pcb.m_dfunc), 579 | path_node, via, gap)) 580 | { 581 | nearer_nodes.push(node); 582 | } 583 | if (!nearer_nodes.length) return [[], false]; 584 | let search = nearer_nodes.find(function(node) { return visited.has(node); }); 585 | if (search !== undefined) 586 | { 587 | //found existing track 588 | path.push(search); 589 | return [path, true]; 590 | } 591 | path_node = nearer_nodes[0]; 592 | } 593 | } 594 | 595 | //attempt to route this net on the current boards state 596 | route() 597 | { 598 | //check for unused terminals track 599 | if (this.m_radius === 0.0) return true; 600 | this.m_paths = []; 601 | this.sub_terminal_collision_lines(); 602 | let visited = new NodeSet(); 603 | for (let index = 1; index < this.m_terminals.length; ++index) 604 | { 605 | let ends = []; 606 | for (let z = 0; z < this.m_pcb.m_depth; ++z) 607 | { 608 | let x = Math.trunc(this.m_terminals[index][2][0]+0.5); 609 | let y = Math.trunc(this.m_terminals[index][2][1]+0.5); 610 | ends.push([x, y, z]); 611 | } 612 | let search = ends.find(function(node) { return visited.has(node); }); 613 | if (search !== undefined) continue; 614 | for (let z = 0; z < this.m_pcb.m_depth; ++z) 615 | { 616 | let x = Math.trunc(this.m_terminals[index-1][2][0]+0.5); 617 | let y = Math.trunc(this.m_terminals[index-1][2][1]+0.5); 618 | visited.add([x, y, z]); 619 | } 620 | this.m_pcb.mark_distances(this.m_pcb.m_routing_flood_vectors, this.m_radius, this.m_via, this.m_gap, 621 | visited, ends); 622 | let sorted_ends = []; 623 | for (let node of ends) sorted_ends.push([this.m_pcb.get_node(node), node]); 624 | sorted_ends.sort(function(s1, s2) { return s1[0] - s2[0]; }); 625 | let result = this.backtrack_path(visited, sorted_ends[0][1], this.m_radius, this.m_via, this.m_gap); 626 | this.m_pcb.unmark_distances(); 627 | if (!result[1]) 628 | { 629 | this.remove(); 630 | return false; 631 | } 632 | for (let node of result[0]) visited.add(node); 633 | this.m_paths.push(result[0]); 634 | } 635 | this.m_paths = this.optimise_paths(this.m_paths); 636 | this.add_paths_collision_lines(); 637 | this.add_terminal_collision_lines(); 638 | return true; 639 | } 640 | 641 | //output net, terminals and paths, for viewer app 642 | output_net() 643 | { 644 | let pcb = this.m_pcb; 645 | let gsp = this.m_pcb.grid_to_space_point; 646 | let scale = 1.0 / this.m_pcb.m_resolution; 647 | let track = []; 648 | track.push(this.m_radius*scale); 649 | track.push(this.m_via*scale); 650 | track.push(this.m_gap*scale); 651 | track.push(this.m_terminals.map(function(t) 652 | { 653 | return [t[0]*scale, t[1]*scale, [t[2][0]*scale, t[2][1]*scale, t[2][2]], 654 | t[3].map(function(n) { return [n[0]*scale, n[1]*scale]; })]; 655 | })); 656 | track.push(this.m_paths.map(function(path) 657 | { 658 | return path.map(function(n) 659 | { 660 | let p = gsp.call(pcb, n); 661 | return [p[0]*scale, p[1]*scale, p[2]]; 662 | }); 663 | })); 664 | return track; 665 | } 666 | } 667 | 668 | js_pcb.Pcb = Pcb; 669 | })(); 670 | -------------------------------------------------------------------------------- /viz/js-pcb/view.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var js_pcb = js_pcb || {}; 4 | (function() 5 | { 6 | function view_pcb(pcb_data, scale, x_correct) 7 | { 8 | //Width and height etc 9 | let width, height, depth; 10 | [width, height, depth] = pcb_data[0]; 11 | console.log([width, height, depth]) 12 | 13 | let path_func = d3.line() 14 | .x(function(d) { return d[0]; }) 15 | .y(function(d) { return d[1]; }); 16 | 17 | //create/replace SVG element 18 | let board = d3.select("svg#map #route-holder"); 19 | 20 | if (board) board.remove(); 21 | 22 | board = d3.select("svg#map").append("g") 23 | .attr("id", "route-holder") 24 | .attr("transform", `translate(-250, ${x_correct}) scale(5, -5)`) 25 | 26 | let pcb = board.append("g") 27 | .attr("stroke-linecap", "round") 28 | .attr("stroke-linejoin", "round") 29 | .attr("stroke-width", "0") 30 | .attr("fill", "none"); 31 | 32 | //create layers, last layer is the terminals layer 33 | let layers = []; 34 | let layer_colors = ["#999", "#999", "#999", "#999", "cyan", "magenta"]; 35 | for (let layer = 0; layer < depth; ++layer) 36 | { 37 | layers.push(pcb.append("g") 38 | .attr("stroke", layer_colors[layer % layer_colors.length]) 39 | .attr("stroke-opacity", 1)); 40 | } 41 | layers.push(pcb.append("g") 42 | .attr("stroke", "lightgreen")); 43 | 44 | //add tracks 45 | for (let track of pcb_data[1]) 46 | { 47 | let track_radius, paths; 48 | [track_radius, , , , paths] = track; 49 | for (let path of paths) 50 | { 51 | let node, start = 0; 52 | let d = path[start][2]; 53 | for (node = 1; node < path.length; ++node) 54 | { 55 | if (path[node][2] === d) continue; 56 | if (node - start > 1) 57 | { 58 | layers[d].append("path") 59 | .attr("stroke-width", track_radius * 2) 60 | .attr("d", path_func(path.slice(start, node))); 61 | } 62 | start = node; 63 | d = path[start][2]; 64 | } 65 | if (node - start > 1) 66 | { 67 | layers[d].append("path") 68 | .attr("stroke-width", track_radius * 2) 69 | .attr("d", path_func(path.slice(start, node))); 70 | } 71 | } 72 | } 73 | 74 | //add terminals and vias 75 | for (let track of pcb_data[1]) 76 | { 77 | let track_radius, via_radius, track_gap, terminals, paths; 78 | [track_radius, via_radius, track_gap, terminals, paths] = track; 79 | for (let terminal of terminals) 80 | { 81 | let terminal_radius, terminal_gap, terminal_x, terminal_y, terminal_z, terminal_shape; 82 | [terminal_radius, terminal_gap, [terminal_x, terminal_y, terminal_z], terminal_shape] = terminal; 83 | if (!terminal_shape.length) 84 | { 85 | layers[layers.length-1].append("circle") 86 | .attr("cx", terminal_x) 87 | .attr("cy", terminal_y) 88 | .attr("r", terminal_radius) 89 | .attr("fill", "#999"); 90 | } 91 | else if (terminal_shape.length === 2) 92 | { 93 | layers[layers.length-1].append("path") 94 | .attr("stroke-width", terminal_radius * 2) 95 | .attr("d", path_func(terminal_shape.map( 96 | function(e){ return [e[0] + terminal_x, e[1] + terminal_y]; }))); 97 | } 98 | else 99 | { 100 | layers[layers.length-1].append("path") 101 | .attr("fill", "#000") 102 | .attr("opacity", 0.25) 103 | .attr("d", path_func(terminal_shape.map( 104 | function(e){ return [e[0] + terminal_x, e[1] + terminal_y]; }))); 105 | } 106 | } 107 | for (let path of paths) 108 | { 109 | let terminal_z = path[0][2]; 110 | for (let node = 1; node < path.length; ++node) 111 | { 112 | if (terminal_z !== path[node][2]) 113 | { 114 | layers[layers.length-1].append("circle") 115 | .attr("cx", path[node][0]) 116 | .attr("cy", path[node][1]) 117 | .attr("r", via_radius/2) 118 | .attr("fill", "orange"); 119 | } 120 | terminal_z = path[node][2]; 121 | } 122 | } 123 | } 124 | } 125 | 126 | js_pcb.view_pcb = view_pcb; 127 | })(); 128 | -------------------------------------------------------------------------------- /viz/js-pcb/worker.js: -------------------------------------------------------------------------------- 1 | importScripts('mymath.js', 'layer.js', 'router.js'); 2 | 3 | function pcb_thread(paramater_array) 4 | { 5 | //generate range of routing vectors 6 | function gen_vectors(vec_range, x_range, y_range) 7 | { 8 | let v = []; 9 | for (let y = y_range; y >= -y_range; --y) 10 | { 11 | for (let x = x_range; x >= -x_range; --x) 12 | { 13 | let p = [x, y]; 14 | if (js_pcb.length_2d(p) > 0.1 && js_pcb.length_2d(p) <= vec_range) 15 | { 16 | v.push([x, y, 0]); 17 | } 18 | } 19 | } 20 | return v; 21 | } 22 | 23 | //args 24 | let pcb_data, arg_t, arg_v, arg_s, arg_z, arg_r, arg_q, arg_d, arg_fr, arg_xr, arg_yr; 25 | [pcb_data, arg_t, arg_v, arg_s, arg_z, arg_r, arg_q, arg_d, arg_fr, arg_xr, arg_yr] = paramater_array; 26 | 27 | //create flooding and backtracking vectors 28 | let flood_range = arg_fr; 29 | let flood_range_x_even_layer = arg_xr; 30 | let flood_range_y_odd_layer = arg_yr; 31 | let path_range = flood_range + 0; 32 | let path_range_x_even_layer = flood_range_x_even_layer + 0; 33 | let path_range_y_odd_layer = flood_range_y_odd_layer + 0; 34 | 35 | let routing_flood_vectorss = 36 | [gen_vectors(flood_range, flood_range_x_even_layer, flood_range), 37 | gen_vectors(flood_range, flood_range, flood_range_y_odd_layer)]; 38 | 39 | let routing_path_vectorss = 40 | [gen_vectors(path_range, path_range_x_even_layer, path_range), 41 | gen_vectors(path_range, path_range, path_range_y_odd_layer)]; 42 | 43 | //choose distance metric function 44 | let dfuncs = [js_pcb.squared_euclidean_distance_3d, 45 | js_pcb.manhattan_distance_3d, 46 | js_pcb.euclidean_distance_3d, 47 | js_pcb.chebyshev_distance_3d, 48 | js_pcb.reciprical_distance_3d]; 49 | 50 | //create pcb object and populate with tracks from input 51 | let current_pcb = new js_pcb.Pcb(pcb_data[0], routing_flood_vectorss, routing_path_vectorss, 52 | dfuncs[arg_d], arg_r, arg_v, arg_q, arg_z); 53 | for (let track of pcb_data[1]) current_pcb.add_track(track); 54 | 55 | //run number of samples of solution and pick best one 56 | let best_pcb = current_pcb.output_pcb(); 57 | postMessage(best_pcb); 58 | let best_cost = 1000000000; 59 | for (let i = 0; i < arg_s; ++i) 60 | { 61 | if (!current_pcb.route(arg_t)) 62 | { 63 | current_pcb.increase_quantization(); 64 | continue; 65 | } 66 | let cost = current_pcb.cost(); 67 | if (cost <= best_cost) 68 | { 69 | best_cost = cost; 70 | best_pcb = current_pcb.output_pcb(); 71 | } 72 | } 73 | postMessage(best_pcb); 74 | } 75 | 76 | //thread event listner 77 | addEventListener('message', function(event) 78 | { 79 | pcb_thread(event.data); 80 | }, false); 81 | -------------------------------------------------------------------------------- /viz/merri.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSkelsey/aaalm/d42dfd79ae8e26b59383804cc0dfa4badea599fb/viz/merri.ttf -------------------------------------------------------------------------------- /viz/packer.js: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * This is a very simple binary tree based bin packing algorithm that is initialized 3 | * with a fixed width and height and will fit each block into the first node where 4 | * it fits and then split that node into 2 parts (down and right) to track the 5 | * remaining whitespace. 6 | * Best results occur when the input blocks are sorted by height, or even better 7 | * when sorted by max(width,height). 8 | * Inputs: 9 | * ------ 10 | * w: width of target rectangle 11 | * h: height of target rectangle 12 | * blocks: array of any objects that have .w and .h attributes 13 | * Outputs: 14 | * ------- 15 | * marks each block that fits with a .fit attribute pointing to a 16 | * node with .x and .y coordinates 17 | * Example: 18 | * ------- 19 | * var blocks = [ 20 | * { w: 100, h: 100 }, 21 | * { w: 100, h: 100 }, 22 | * { w: 80, h: 80 }, 23 | * { w: 80, h: 80 }, 24 | * etc 25 | * etc 26 | * ]; 27 | * var packer = new Packer(500, 500); 28 | * packer.fit(blocks); 29 | * for(var n = 0 ; n < blocks.length ; n++) { 30 | * var block = blocks[n]; 31 | * if (block.fit) { 32 | * Draw(block.fit.x, block.fit.y, block.w, block.h); 33 | * } 34 | * } 35 | ******************************************************************************/ 36 | Packer = function(w, h) { 37 | this.init(w, h); 38 | }; 39 | 40 | Packer.prototype = { 41 | 42 | init: function(w, h) { 43 | this.root = { x: 0, y: 0, w: w, h: h }; 44 | }, 45 | 46 | fit: function(blocks) { 47 | var n, node, block; 48 | for (n = 0; n < blocks.length; n++) { 49 | block = blocks[n]; 50 | block.rotate = false; 51 | if (node = this.findNode(this.root, block)) { 52 | block.fit = this.splitNode(node, block); 53 | } 54 | } 55 | let success = true; 56 | blocks.forEach(block => { 57 | if (!block.hasOwnProperty("fit") || !block.fit.hasOwnProperty("x")) { 58 | success = false; 59 | } 60 | }); 61 | 62 | return success; 63 | }, 64 | 65 | findNode: function(root, block) { 66 | if (root.used) { 67 | return this.findNode(root.right, block) || this.findNode(root.down, block); 68 | } else if ((block.w <= root.w) && (block.h <= root.h)) { 69 | return root; 70 | } else if ((block.h <= root.w) && (block.w <= root.h)) { 71 | let temp = block.w; 72 | block.w = block.h; 73 | block.h = temp; 74 | block.rotate = !block.rotate; 75 | return root; 76 | } else 77 | return null; 78 | }, 79 | 80 | splitNode: function(node, block) { 81 | node.used = true; 82 | node.down = { x: node.x, y: node.y + block.h, w: node.w, h: node.h - block.h }; 83 | node.right = { x: node.x + block.w, y: node.y, w: node.w - block.w, h: block.h }; 84 | return node; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /zkg.meta: -------------------------------------------------------------------------------- 1 | [package] 2 | script_dir = scripts 3 | description = Tag and group devices based on a LAN's structure 4 | tags = topology, mapping, visualization, traceroute 5 | version = 0.2.0 6 | --------------------------------------------------------------------------------