diff --git a/main.py b/main.py index f2d4a67..d6d986f 100644 --- a/main.py +++ b/main.py @@ -1,7 +1,126 @@ -import mymodule as m -x = m.gtime() -print(x.year) -print(type(x)) -print(x.strftime("%A")) -y = m.mdate(2022, 11, 30) -print(y) +#BSD 3-Clause, (C) Alfred Manville 2022 +import networker as net +import sys +from threading import Thread +import traceback + +translators = (net.PickleTranslate(), net.JSONTranslate()) + +inter = "" +port = 0 +translator = None +conn = None +allowFiles = False +log = [] + +def onConn(addr): + log.append(addr + " # Connection Established") + +def onEnd(addr): + log.append(addr + " # Connection Ended") + +def onRecv(addr, msg): + global allowFiles + if type(msg) != net.Message: + log.append("Invalid Message received from: " + addr) + return + if msg.mtype == net.MTYPE_Text: + log.append(addr + " ; " + str(msg.header) + " ; " + str(msg.content)) + elif msg.mtype == net.MTYPE_File: + if allowFiles: + log.append(addr + " ; " + str(msg.header)) + msg.saveContent() + else: + log.append(addr + " ; File Rejected") + else: + log.append("Unknown Message type received from: " + addr) + +def main(): + global allowFiles + conn = net.Connection((inter, port), translator, onConn, onRecv, onEnd) + ct = Thread(target=conn.listener, args=()) + ct.start() + print("Listener started @ " + inter + ":" + str(port)) + print("Format: \"command;ip:port;argument\"") + print("Commands: connect, disconnect, list, log, file_allowed, toggle_file_allowed, message, send, help, exit") + while conn.active: + cmd = input("> ").lower() + csplt = cmd.split(";", 2) + try: + if len(csplt) > 0: + if csplt[0] == "list": + print(conn.addresses()) + continue + elif csplt[0] == "help": + print("Help:") + print("connect;ip:port -- Connects to the specified IP:Port") + print("disconnect;ip:port -- Disonnects from the specified IP:Port") + print("list -- Lists the available IP:Port connections") + print("log -- Gets the message log") + print("file_allowed -- Gets if file receiving is allowed") + print("toggle_file_allowed -- Toggles if file receiving is allowed") + print("message;ip:port;header:body -- Sends a message to the specified IP:Port") + print("send;ip:port;path -- Sends a file message to the specified IP:Port") + print("help -- Shows this message") + print("exit -- Exits this program closing all connections") + continue + elif csplt[0] == "exit": + conn.close() + break + elif csplt[0] == "log": + while len(log) > 0: + print(log.pop(0)) + continue + elif csplt[0] == "file_allowed": + print("Receive File Status: " + str(allowFiles)) + continue + elif csplt[0] == "toggle_file_allowed": + allowFiles = not allowFiles + print("Receive File Status set to: " + str(allowFiles)) + continue + if len(csplt) > 1: + if csplt[0] == "connect": + print("Attempting Connection to: " + csplt[1]) + ippsplt = csplt[1].split(":", 1) + conn.connect((ippsplt[0], int(ippsplt[1]))) + continue + elif csplt[0] == "disconnect": + print("Attempting Disconnection from: " + csplt[1]) + conn.actives[csplt[1]] = False + conn.sockets[csplt[1]].close() + continue + if len(csplt) > 2: + if csplt[0] == "message": + datasplt = csplt[2].split(":", 1) + print("Attempting to send message to: " + csplt[1]) + conn.send(csplt[1], net.Message(net.MTYPE_Text, datasplt[0], datasplt[1])) + continue + elif csplt[0] == "send": + print("Attempting to send file to: " + csplt[1]) + conn.send(csplt[1], net.Message(net.MTYPE_File, csplt[2], None)) + continue + print("Invalid Command!") + + except Exception as e: + print("Command Error!") + print(traceback.format_exc()) + exit + + +if __name__ == "__main__": + print("Python Communicator (C) Alfred Manville 2022 BSD-3-Clause") + if len(sys.argv) > 1: + inter = sys.argv[1] + else: + inter = input("Enter the listening interface: ") + if len(sys.argv) > 2: + port = int(sys.argv[2]) + else: + port = int(input("Enter the listening port: ")) + if len(sys.argv) > 3: + translator = translators[int(sys.argv[3]) - 1] + else: + translator = translators[int(input("Enter the message translator position " + str(translators) + " : ")) - 1] + main() + + diff --git a/mymodule.py b/mymodule.py deleted file mode 100644 index 35654d1..0000000 --- a/mymodule.py +++ /dev/null @@ -1,5 +0,0 @@ -from datetime import datetime as dt -def gtime(): - return dt.now() -def mdate(y, mo, d): - return dt(y, mo, d) diff --git a/networker.py b/networker.py new file mode 100644 index 0000000..674dba5 --- /dev/null +++ b/networker.py @@ -0,0 +1,223 @@ +#BSD 3-Clause, (C) Alfred Manville 2022 +import pickle +import json +import socket +import time +from threading import Thread +import base64 +import traceback + +#Defines a message class that has a type, header and a body. +class Message: + + def __init__(self, mtype, header, content): + self.mtype = mtype + self.header = header + if mtype == MTYPE_File: + try: + f = open(header, "r") + try: + self.content = str(f.read()) + except: + print("An issue writing the message for \"" + self.header + "\" occured.") + f.close() + except: + print("An issue when opening a file for reading: \"" + self.header + "\" occured.") + else: + self.content = content + + def saveContent(self): + if self.mtype != MTYPE_File: pass + try: + f = open(str(self.header), "w") + try: + f.write(bytes(self.content)) + except: + print("An issue writing the message for \"" + str(self.header) + "\" occured.") + f.close() + except: + print("An issue when opening a file for writing: \"" + str(self.header) + "\" occured.") + + def toDict(self): + toReturn = {"mtype":self.mtype, "header":self.header, "ident__":"Message"} + if type(self.content) == bytes or type(self.content) == bytearray: + toReturn["contentb64"] = True + toReturn["content"] = base64.b64encode(bytes(self.content)).decode() + else: + toReturn["contentb64"] = False + toReturn["content"] = self.content + return toReturn + +#Message from dict: +def MessageFromDict(d): + if type(d) != None and d.get("ident__") == "Message": + if d.get("contentb64") != None: + if d.get("contentb64"): + return Message(int(d.get("mtype")),d.get("header"), base64.b64decode(d.get("content"))) + else: + return Message(int(d.get("mtype")),d.get("header"), d.get("content")) + print("Invalid message dictionary!") + return None + +#mtype Definitions: +MTYPE_Text = 0; +MTYPE_File = 1; + +#Pickle Translator for Message to and from bytes. +class PickleTranslate: + def toString(self, m): + try: + return pickle.dumps(m) + except: + print(traceback.format_exc()) + return None + def fromString(self, b): + try: + return pickle.loads(b) + except: + print(traceback.format_exc()) + return None + +#JSON Translator for Message to and from bytes. +class JSONTranslate: + def toString(self, m): + try: + return json.dumps(m.toDict()) + except: + print(traceback.format_exc()) + return None + def fromString(self, b): + try: + return MessageFromDict(json.loads(b)) + except: + print(traceback.format_exc()) + return None + +#Connection class +class Connection: + active = True + sockets = dict() + threads = dict() + actives = dict() + def __init__(self, binder, translator, onconn, onrecv, onend): + self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.socket.bind(binder) + self.socket.listen(8) + self.translator = translator + self.onconn = onconn + self.onrecv = onrecv + self.onend = onend + def listener(self): + while self.active: + s, a = self.socket.accept() + ac = a[0] + ":" + str(a[1]) + try: + self.onconn(str(ac)) + except: + print("Failure to call onconn for: " + str(ac)) + self.sockets[str(ac)] = s + self.actives[str(ac)] = True + self.threads[str(ac)] = Thread(target=self.processor, args=(str(ac),)) + self.threads[str(ac)].start(); + self.socket.close() + + def processor(self, addr): + while self.active and self.actives[addr]: + try: + head = self.sockets[addr].recv(2) + if type(head) == None or len(head) != 2: + print("An issue reading the packet header has occured for: " + addr) + break + dataSize = 0 + try: + dataSize = int(head[0]) * 256 + int(head[1]) + except: + print("An issue reading the packet header has occured for: " + addr) + break + if dataSize < 1: + continue + data = bytearray() + while dataSize > 0: + dataL = self.sockets[addr].recv(dataSize) + dataSize -= len(dataL) + data.extend(dataL) + try: + self.onrecv(addr, self.translator.fromString(data)) + except: + print("Failure to call onrecv for: " + addr) + except: + print("A network issue has occured for: " + addr) + self.actives[addr] = False + break + self.onend(addr) + + def send(self, addr, m): + if self.active and self.actives[addr]: + try: + toSend = bytes(self.translator.toString(m)) + sendLength = len(toSend) + if sendLength < 1: + print("Message for: " + addr + " empty?") + return + if sendLength > 65535: + print("Message for: " + addr + " too big!") + return + head = bytearray(2) + head[0] = sendLength//256 + sendLength -= int(head[0])*256 + head[1] = sendLength + sendLength = -2 + try: + sendLength += self.sockets[addr].send(bytes(head)) + sendLength += self.sockets[addr].send(toSend) + except: + try: + if sendLength >= 0: + self.sockets[addr].send(bytes(len(toSend) - sendLength)) + except: + print("A network issue has occured for: " + addr) + self.actives[addr] = False + except: + print("A send failure has occured for: " + addr) + + def close(self): + self.active = False + for x in self.actives: self.actives[x] = False + for x in self.sockets: self.sockets[x].close() + ndone = True + while ndone: + ndone = False + for x in self.threads: + if self.threads[x].is_alive(): + ndone = True + break + time.sleep(0.0001) + self.threads.clear() + self.socket.close() + + def addresses(self): + if self.active: + tL = [] + for x in self.actives: + if self.actives[x]: + tL.append(x) + return tL + else: + return [] + + def connect(self, target): + ac = target[0]+":"+str(target[1]) + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect(target) + try: + self.onconn(str(ac)) + except: + print("Failure to call onconn for: " + str(ac)) + self.sockets[str(ac)] = s + self.actives[str(ac)] = True + self.threads[str(ac)] = Thread(target=self.processor, args=(str(ac),)) + self.threads[str(ac)].start() + except: + print("A connection error has occured for: " + ac) +