Files
Bubberstation/code/controllers/subsystem/processing/networks.dm
SkyratBot 0339798a42 [MIRROR] Removes network cards and printers from tablets [MDB IGNORE] (#16451)
* Removes network cards and printers from tablets

* merge tablet_presets

* printer.dm should have been deleted, missed change

* update contractor tablet

Co-authored-by: John Willard <53777086+JohnFulpWillard@users.noreply.github.com>
Co-authored-by: tastyfish <crazychris32@gmail.com>
2022-09-27 23:16:32 -04:00

517 lines
20 KiB
Plaintext

SUBSYSTEM_DEF(networks)
name = "Networks"
priority = FIRE_PRIORITY_NETWORKS
wait = 5
flags = SS_KEEP_TIMING
init_order = INIT_ORDER_NETWORKS
var/list/relays = list()
/// Legacy ntnet lookup for software. Should be changed latter so don't rely on this
/// being here.
var/datum/ntnet/station_root/station_network
var/datum/ntnet/station_root/syndie_network
var/list/network_initialize_queue = list()
/// all interfaces by their hardware address.
/// Do NOT use to verify a reciver_id is valid, use the network.root_devices for that
var/list/interfaces_by_hardware_id = list()
/// Area to network, network to area list
/// This is an associated list to quickly find an area using either its id or network
/// Used mainly to make sure all area id's are unique even if a mapper uses the same
/// area many times
var/list/area_network_lookup = list()
/// List of networks using their fully qualified network name. Used for quick lookups
/// of networks for sending packets
var/list/networks = list()
/// List of the root networks starting at their root names. Used to find and/or build
/// network tress
var/list/root_networks = list()
// Why not list? Because its a Copy() every time we add a packet, and thats stupid.
var/datum/netdata/first = null // start of the queue. Pulled off in fire.
var/datum/netdata/last = null // end of the queue. pushed on by transmit
var/packet_count = 0
// packet stats
var/count_broadcasts_packets = 0 // count of broadcast packets sent
var/count_failed_packets = 0 // count of message fails
var/count_good_packets = 0
// Logs moved here
// Amount of logs the system tries to keep in memory. Keep below 999 to prevent byond from acting weirdly.
// High values make displaying logs much laggier.
var/setting_maxlogcount = 100
var/list/logs = list()
/// Random name search to make sure we have unique names.
/// DO NOT REMOVE NAMES HERE UNLESS YOU KNOW WHAT YOUR DOING
var/list/used_names = list()
/// You shouldn't need to do this. But mapping is async and there is no guarantee that Initialize
/// will run before these networks are dynamically created. So its here.
/datum/controller/subsystem/networks/PreInit()
/// Limbo network needs to be made at boot up for all error devices
new/datum/ntnet(LIMBO_NETWORK_ROOT)
station_network = new(STATION_NETWORK_ROOT)
syndie_network = new(SYNDICATE_NETWORK_ROOT)
/// As well as the station network incase something funny goes during startup
new/datum/ntnet(CENTCOM_NETWORK_ROOT)
/datum/controller/subsystem/networks/stat_entry(msg)
msg = "NET: QUEUE([packet_count]) FAILS([count_failed_packets]) BROADCAST([count_broadcasts_packets])"
return ..()
/datum/controller/subsystem/networks/Initialize()
station_network.register_map_supremecy() // sigh
assign_areas_root_ids(GLOB.sortedAreas) // setup area names before Initialize
station_network.build_software_lists()
syndie_network.build_software_lists()
// At round start, fix the network_id's so the station root is on them
initialized = TRUE
// Now when the objects Initialize they will join the right network
return SS_INIT_SUCCESS
/*
* Process incoming queued packet and return NAK/ACK signals
*
* This should only be called when you want the target object to process the NAK/ACK signal, usually
* during fire. At this point data.receiver_id has already been converted if it was a broadcast but
* is undefined in this function.
* Arguments:
* * receiver_id - text hardware id for the target device
* * data - packet to be sent
*/
/datum/controller/subsystem/networks/proc/_process_packet(receiver_id, datum/netdata/data)
/// Used only for sending NAK/ACK and error reply's
var/datum/component/ntnet_interface/sending_interface = interfaces_by_hardware_id[data.sender_id]
/// Check if the network_id is valid and if not send an error and return
var/datum/ntnet/target_network = networks[data.network_id]
if(!target_network)
count_failed_packets++
add_log("Bad target network '[data.network_id]'", null, data.sender_id)
if(!QDELETED(sending_interface))
SEND_SIGNAL(sending_interface.parent, COMSIG_COMPONENT_NTNET_NAK, data , NETWORK_ERROR_BAD_NETWORK)
return
/// Check if the receiver_id is in the network. If not send an error and return
var/datum/component/ntnet_interface/target_interface = target_network.root_devices[receiver_id]
if(QDELETED(target_interface))
count_failed_packets++
add_log("Bad target device '[receiver_id]'", target_network, data.sender_id)
if(!QDELETED(sending_interface))
SEND_SIGNAL(sending_interface.parent, COMSIG_COMPONENT_NTNET_NAK, data, NETWORK_ERROR_BAD_RECEIVER_ID)
return
// Check if we care about permissions. If we do check if we are allowed the message to be processed
if(data.passkey) // got to check permissions
var/obj/O = target_interface.parent
if(O)
if(!O.check_access_ntnet(data.passkey))
count_failed_packets++
add_log("Access denied to ([receiver_id]) from ([data.network_id])", target_network, data.sender_id)
if(!QDELETED(sending_interface))
SEND_SIGNAL(sending_interface.parent, COMSIG_COMPONENT_NTNET_NAK, data, NETWORK_ERROR_UNAUTHORIZED)
return
else
add_log("A access key message was sent to a non-device", target_network, data.sender_id)
if(!QDELETED(sending_interface))
SEND_SIGNAL(sending_interface.parent, COMSIG_COMPONENT_NTNET_NAK, data, NETWORK_ERROR_UNAUTHORIZED)
SEND_SIGNAL(target_interface.parent, COMSIG_COMPONENT_NTNET_RECEIVE, data)
// All is good, send the packet then send an ACK to the sender
if(!QDELETED(sending_interface))
SEND_SIGNAL(sending_interface.parent, COMSIG_COMPONENT_NTNET_ACK, data)
count_good_packets++
/// Helper define to make sure we pop the packet and qdel it
#define POP_PACKET(CURRENT) first = CURRENT.next; packet_count--; if(!first) { last = null; packet_count = 0; }; qdel(CURRENT);
/datum/controller/subsystem/networks/fire(resumed = 0)
var/datum/netdata/current
var/datum/component/ntnet_interface/target_interface
while(first)
current = first
/// Check if we are a list. If so process the list
if(islist(current.receiver_id)) // are we a broadcast list
var/list/receivers = current.receiver_id
var/receiver_id = receivers[receivers.len] // pop it
receivers.len--
_process_packet(receiver_id, current)
if(receivers.len == 0) // pop it if done
count_broadcasts_packets++
POP_PACKET(current)
else // else set up a broadcast or send a single targete
// check if we are sending to a network or to a single target
target_interface = interfaces_by_hardware_id[current.receiver_id]
if(target_interface) // a single sender id
_process_packet(current.receiver_id, current) // single target
POP_PACKET(current)
else // ok so lets find the network to send it too
var/datum/ntnet/net = networks[current.network_id] // get the sending network
net = net?.networks[current.receiver_id] // find the target network to broadcast
if(net) // we found it
current.receiver_id = net.collect_interfaces() // make a list of all the sending targets
else
// We got an error, the network is bad so send a NAK
target_interface = interfaces_by_hardware_id[current.sender_id]
if(!QDELETED(target_interface))
SEND_SIGNAL(target_interface.parent, COMSIG_COMPONENT_NTNET_NAK, current , NETWORK_ERROR_BAD_NETWORK)
POP_PACKET(current) // and get rid of it
if (MC_TICK_CHECK)
return
#undef POP_PACKET
/*
* Main function to queue a packet. As long as we have valid receiver_id and network_id we will take it
*
* Main queuing function for any message sent. if the data.receiver_id is null, then it will be broadcasted
* error checking is only done during the process this just throws it on the queue.
* Arguments:
* * data - packet to be sent
*/
/datum/controller/subsystem/networks/proc/transmit(datum/netdata/data)
data.next = null // sanity check
if(!last)
first = last = data
else
last.next = data
last = data
packet_count++
// We do error checking when the packet is sent
return NETWORK_ERROR_OK
/datum/controller/subsystem/networks/proc/check_relay_operation(zlevel=0) //can be expanded later but right now it's true/false.
for(var/i in relays)
var/obj/machinery/ntnet_relay/n = i
if(zlevel && n.z != zlevel)
continue
if(n.is_operational)
return TRUE
return FALSE
/datum/controller/subsystem/networks/proc/log_data_transfer( datum/netdata/data)
logs += "[station_time_timestamp()] - [data.generate_netlog()]"
if(logs.len > setting_maxlogcount)
logs = logs.Copy(logs.len - setting_maxlogcount, 0)
/**
* Records a message into the station logging system for the network
*
* This CAN be read in station by personal so do not use it for game debugging
* during fire. At this point data.receiver_id has already been converted if it was a broadcast but
* is undefined in this function. It is also dumped to normal logs but remember players can read/intercept
* these messages
* Arguments:
* * log_string - message to log
* * network - optional, It can be a ntnet or just the text equivalent
* * hardware_id = optional, text, will look it up and return with the parent.name as well
*/
/datum/controller/subsystem/networks/proc/add_log(log_string, network = null)
set waitfor = FALSE // so process keeps running
var/list/log_text = list()
log_text += "\[[station_time_timestamp()]\]"
if(network)
var/datum/ntnet/net = network
if(!istype(net))
net = networks[network]
if(net) // bad network?
log_text += "{[net.network_id]}"
else // bad network?
log_text += "{[network] *BAD*}"
log_text += "*SYSTEM* - "
log_text += log_string
log_string = log_text.Join()
logs.Add(log_string)
//log_telecomms("NetLog: [log_string]") // causes runtime on startup humm
// We have too many logs, remove the oldest entries until we get into the limit
if(logs.len > setting_maxlogcount)
logs = logs.Copy(logs.len-setting_maxlogcount,0)
/**
* Removes all station logs for the current game
*/
/datum/controller/subsystem/networks/proc/purge_logs()
logs = list()
add_log("-!- LOGS DELETED BY SYSTEM OPERATOR -!-")
/**
* Updates the maximum amount of logs and purges those that go beyond that number
*
* Shouldn't been needed to be run by players but maybe admins need it?
* Arguments:
* * lognumber - new setting_maxlogcount count
*/
/datum/controller/subsystem/networks/proc/update_max_log_count(lognumber)
if(!lognumber)
return FALSE
// Trim the value if necessary
lognumber = max(MIN_NTNET_LOGS, min(lognumber, MAX_NTNET_LOGS))
setting_maxlogcount = lognumber
add_log("Configuration Updated. Now keeping [setting_maxlogcount] logs in system memory.")
/**
* Gives an area a root and a network_area_id
*
* When a device is added to the network on map load, it needs to know where it is.
* So that it is added to that ruins/base's network instead of the general station network
* This way people on the station cannot just hack Charlie's doors and visa versa. All area's
* "should" have this information and if not one is created from existing map tags or
* ruin template id's. This SHOULD run before the Initialize of a atom, or the root will not
* be put in the object.area
*
* An example on what the area.network_root_id does/
* Before Init: obj.network_id = "ATMOS.SCRUBBER" area.network_root_id="SS13_STATION" area.network_area_id = "BRIDGE"
* After Init: obj.network_id = "SS13_STATION.ATMOS.SCRUBBER" also obj.network_id = "SS13_STATION.AREA.BRIDGE"
*
* Arguments:
* * area - Area to modify the root id.
* * template - optional, map_template of that area
*/
/datum/controller/subsystem/networks/proc/lookup_area_root_id(area/A, datum/map_template/M=null)
/// Check if the area is valid and if it doesn't have a network root id.
if(!istype(A) || A.network_root_id != null)
return
/// If we are a ruin or a shuttle, we get our own network
if(M)
/// if we have a template, try to get the network id from the template
if(M.station_id && M.station_id != LIMBO_NETWORK_ROOT) // check if the template specifies it
A.network_root_id = simple_network_name_fix(M.station_id)
else if(istype(M, /datum/map_template/shuttle)) // if not, then check if its a shuttle type
var/datum/map_template/shuttle/T = M // we are a shuttle so use shuttle id
A.network_root_id = simple_network_name_fix(T.shuttle_id)
else if(istype(M,/datum/map_template/ruin)) // if not again, check if its a ruin type
var/datum/map_template/ruin/R = M
A.network_root_id = simple_network_name_fix(R.id)
if(!A.network_root_id) // not assigned? Then lets use some defaults
// Anything in Centcom is completely isolated
// Special case for holodecks.
if(istype(A,/area/station/holodeck))
A.network_root_id = "HOLODECK" // isolated from the station network
else if(SSmapping.level_trait(A.z, ZTRAIT_CENTCOM))
A.network_root_id = CENTCOM_NETWORK_ROOT
// Otherwise the default is the station
else
A.network_root_id = STATION_NETWORK_ROOT
/datum/controller/subsystem/networks/proc/assign_area_network_id(area/A, datum/map_template/M=null)
if(!istype(A))
return
if(!A.network_root_id)
lookup_area_root_id(A, M)
// finally set the network area id, bit copy paste from area Initialize
// This is done in case we have more than one area type, each area instance has its own network name
if(!A.network_area_id)
A.network_area_id = A.network_root_id + ".AREA." + simple_network_name_fix(A.name) // Make the string
if(!(A.area_flags & UNIQUE_AREA)) // if we aren't a unique area, make sure our name is different
A.network_area_id = SSnetworks.assign_random_name(5, A.network_area_id + "_") // tack on some garbage incase there are two area types
/datum/controller/subsystem/networks/proc/assign_areas_root_ids(list/areas, datum/map_template/M=null)
for(var/area/A in areas)
assign_area_network_id(A, M)
/**
* Converts a list of string's into a full network_id
*
* Converts a list of individual branches into a proper network id. Validates
* individual parts to make sure they are clean.
*
* ex. list("A","B","C") -> A.B.C
*
* Arguments:
* * tree - List of strings
*/
/datum/controller/subsystem/networks/proc/network_list_to_string(list/tree)
#ifdef DEBUG_NETWORKS
ASSERT(tree && tree.len > 0) // this should be obvious but JUST in case.
for(var/part in tree)
if(!verify_network_name(part) || findtext(name,".")!=0) // and no stray dots
stack_trace("network_list_to_string: Cannot create network with ([part]) of ([tree.Join(".")])")
break
#endif
return tree.Join(".")
/**
* Converts string into a list of network branches
*
* Converts a a proper network id into a list of the individual branches
*
* ex. A.B.C -> list("A","B","C")
*
* Arguments:
* * tree - List of strings
*/
/datum/controller/subsystem/networks/proc/network_string_to_list(name)
#ifdef DEBUG_NETWORKS
if(!verify_network_name(name))
stack_trace("network_string_to_list: [name] IS INVALID")
#endif
return splittext(name,".") // should we do a splittext_char? I doubt we really need unicode in network names
/**
* Hard creates a network. Helper function for create_network_simple and create_network
*
* Hard creates a using a list of branches and returns. No error checking as it should
* of been done before this call
*
* Arguments:
* * network_tree - list,text List of branches of network
*/
/datum/controller/subsystem/networks/proc/_hard_create_network(list/network_tree)
var/network_name_part = network_tree[1]
var/network_id = network_name_part
var/datum/ntnet/parent = root_networks[network_name_part]
var/datum/ntnet/network
if(!parent) // we have no network root? Must be mapload of a ruin or such
parent = new(network_name_part)
// go up the branches, creating nodes
for(var/i in 2 to network_tree.len)
network_name_part = network_tree[i]
network = parent.children[network_name_part]
network_id += "." + network_name_part
if(!network)
network = new(network_id, network_name_part, parent)
parent = network
return network
/**
* Creates or finds a network anywhere in the world using a fully qualified name
*
* This is the simple case finding of a network in the world. It must take a full
* qualified network name and it will either return an existing network or build
* a new one from scratch. We must be able to create names on the fly as there is
* no way for the map loader to tell us ahead of time what networks to create or
* use for any maps or templates. So this thing will throw silent mapping errors
* and log them, but will always return a network for something.
*
* Arguments:
* * network_id - text, Fully qualified network name
*/
/datum/controller/subsystem/networks/proc/create_network_simple(network_id)
var/datum/ntnet/network = networks[network_id]
if(network!=null)
return network // don't worry about it
/// Checks to make sure the network is valid. We log BOTH to mapping and telecoms
/// so if your checking for network errors you can find it in mapping to (because its their fault!)
if(!verify_network_name(network_id))
log_mapping("create_network_simple: [network_id] IS INVALID, replacing with LIMBO")
log_telecomms("create_network_simple: [network_id] IS INVALID, replacing with LIMBO")
return networks[LIMBO_NETWORK_ROOT]
var/list/network_tree = network_string_to_list(network_id)
if(!network_tree || network_tree.len == 0)
log_mapping("create_network_simple: [network_id] IS INVALID, replacing with LIMBO")
log_telecomms("create_network_simple: [network_id] IS INVALID, replacing with LIMBO")
return networks[LIMBO_NETWORK_ROOT]
network = _hard_create_network(network_tree)
#ifdef DEBUG_NETWORKS
if(!network)
CRASH("NETWORK CANNOT BE NULL")
#endif
log_telecomms("create_network_simple: created final [network.network_id]")
return network // and we are done!
/**
* Creates or finds a network anywhere in the world using bits of text
*
* This works the same as create_network_simple however it allows the addition
* of qualified network names. So you can call it with a root_id and a sub
* network. However this function WILL return null if it cannot be created
* so it should be used with error checking is involved.
*
* ex. create_network("ROOT_NETWORK", "ATMOS.SCRUBBERS") -> ROOT_NETWORK.ATMOS.SCRUBBERS
*
* Arguments:
* * tree - List of string
*/
/datum/controller/subsystem/networks/proc/create_network(...)
var/list/network_tree = list()
for(var/i in 1 to args.len)
var/part = args[i]
#ifdef DEBUG_NETWORKS
if(!part || !istext(part))
/// stack trace here because this is a bad error
stack_trace("create_network: We only take text on [part] index [i]")
return null
#endif
network_tree += network_string_to_list(part)
var/datum/ntnet/network = _hard_create_network(network_tree)
log_telecomms("create_network: created final [network.network_id]")
return network
/**
* Generate a hardware id for devices.
*
* Creates a 32 bit hardware id for network devices. This is random so masking
* the number won't make routing "easier" (Think Ethernet) It does check if
* an existing device has the number but will NOT assign it as thats
* up to the collar
*
* Returns (string) The generated name
*/
/datum/controller/subsystem/networks/proc/get_next_HID()
do
var/string = md5("[num2text(rand(HID_RESTRICTED_END, 999999999), 12)]")
if(!string)
log_runtime("Could not generagea m5 hash from address, problem with md5?")
return //errored
. = "[copytext_char(string, 1, 9)]" //16 ^ 8 possibilities I think.
while(interfaces_by_hardware_id[.])
/**
* Generate a name devices
*
* Creates a randomly generated tag or name for devices or anything really
* it keeps track of a special list that makes sure no name is used more than
* once
*
* args:
* * len (int)(Optional) Default=5 The length of the name
* * prefix (string)(Optional) static text in front of the random name
* * postfix (string)(Optional) static text in back of the random name
* Returns (string) The generated name
*/
/datum/controller/subsystem/networks/proc/assign_random_name(len=5, prefix="", postfix="")
var/static/valid_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
var/list/new_name = list()
var/text
// machine id's should be fun random chars hinting at a larger world
do
new_name.Cut()
new_name += prefix
for(var/i = 1 to len)
new_name += valid_chars[rand(1,length(valid_chars))]
new_name += postfix
text = new_name.Join()
while(used_names[text])
used_names[text] = TRUE
return text