diff options
| -rw-r--r-- | .clang-format | 51 | ||||
| -rw-r--r-- | .clang-tidy | 101 | ||||
| -rwxr-xr-x[-rw-r--r--] | .gitignore | 0 | ||||
| -rw-r--r-- | Makefile | 61 | ||||
| -rw-r--r-- | Version_History.md | 20 | ||||
| -rw-r--r-- | main-pre.py | 291 | ||||
| -rw-r--r-- | main.py | 276 | ||||
| -rwxr-xr-x | setup.sh | 40 | ||||
| -rw-r--r-- | src/FileDescriptor.cpp | 89 | ||||
| -rw-r--r-- | src/FileDescriptor.hpp | 39 | ||||
| -rw-r--r-- | src/log.hpp | 13 | ||||
| -rw-r--r-- | src/main.cpp | 9 | ||||
| -rw-r--r-- | src/server.cpp | 50 | ||||
| -rw-r--r-- | src/server.hpp | 23 | ||||
| -rw-r--r-- | src/session.cpp | 112 | ||||
| -rw-r--r-- | src/session.hpp | 32 | ||||
| -rw-r--r-- | src/sessionManager.cpp | 29 | ||||
| -rw-r--r-- | src/sessionManager.hpp | 20 |
18 files changed, 629 insertions, 627 deletions
diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..c1bd334 --- /dev/null +++ b/.clang-format @@ -0,0 +1,51 @@ +--- +Language: Cpp +BasedOnStyle: LLVM + +PointerAlignment: Right +RemoveBracesLLVM: true +AlignAfterOpenBracket: BlockIndent +AlignEscapedNewlines: Right +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AlignConsecutiveAssignments: true +AlignConsecutiveBitFields: true +AlignConsecutiveDeclarations: true +AlignConsecutiveMacros: true +# AlignConsecutiveShortCaseStatements: # need clang-format 17(not avaliable yet) + # Enabled: true +# AlignConsecutiveTableGenBreakingDAGArgColons: true # need clang-format 19(not avaliable yet) +# AlignConsecutiveTableGenCondOperatorColons: true # need clang-format 19(not avaliable yet) +# AlignConsecutiveTableGenDefinitionColons: true # need clang-format 19(not avaliable yet) +BinPackParameters: false +Standard: Auto +UseTab: Always +IndentWidth: 4 +TabWidth: 4 +ColumnLimit: 0 +# AccessModifierOffset: -4 +BreakConstructorInitializers: BeforeComma +ConstructorInitializerAllOnOneLineOrOnePerLine: true +PackConstructorInitializers: Never + +BraceWrapping: { + AfterCaseLabel: false, + AfterClass: false, + AfterControlStatement: Never, + AfterEnum: false, + AfterFunction: false, + AfterNamespace: false, + AfterObjCDeclaration: false, + AfterStruct: false, + AfterUnion: false, + AfterExternBlock: false, + BeforeCatch: false, + BeforeElse: false, + BeforeLambdaBody: false, + BeforeWhile: false, + IndentBraces: false, + SplitEmptyFunction: true, + SplitEmptyRecord: true, +} +BreakBeforeBraces: Custom +# InsertBraces: false diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..84f69b0 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,101 @@ +WarningsAsErrors: '*' +HeaderFilterRegex: '.*\.hpp' +FormatStyle: file +Checks: > + -*, + bugprone-*, + -bugprone-easily-swappable-parameters, + -bugprone-forward-declararion-namespace, + -bugprone-forward-declararion-namespace, + -bugprone-macro-parentheses, + -bugprone-narrowing-conversions, + -bugprone-branch-clone, + -bugprone-assignment-in-if-condition, + concurrency-*, + -concurrency-mt-unsafe, + cppcoreguidelines-*, + -cppcoreguidelines-owning-memory, + -cppcoreguidelines-avoid-magic-numbers, + -cppcoreguidelines-pro-bounds-constant-array-index, + -cppcoreguidelines-avoid-const-or-ref-data-members, + -cppcoreguidelines-non-private-member-variables-in-classes, + -cppcoreguidelines-avoid-goto, + -cppcoreguidelines-pro-bounds-array-to-pointer-decay, + -cppcoreguidelines-avoid-do-while, + -cppcoreguidelines-avoid-non-const-global-variables, + -cppcoreguidelines-special-member-functions, + -cppcoreguidelines-explicit-virtual-functions, + -cppcoreguidelines-avoid-c-arrays, + -cppcoreguidelines-pro-bounds-pointer-arithmetic, + -cppcoreguidelines-narrowing-conversions, + -cppcoreguidelines-pro-type-union-access, + -cppcoreguidelines-pro-type-member-init, + -cppcoreguidelines-macro-usage, + -cppcoreguidelines-macro-to-enum, + -cppcoreguidelines-init-variables, + -cppcoreguidelines-pro-type-cstyle-cast, + -cppcoreguidelines-pro-type-vararg, + -cppcoreguidelines-pro-type-reinterpret-cast, + google-global-names-in-headers, + -google-readability-casting, + google-runtime-operator, + misc-*, + -misc-unused-parameters, + -misc-no-recursion, + -misc-non-private-member-variables-in-classes, + -misc-include-cleaner, + -misc-use-anonymous-namespace, + -misc-const-correctness, + modernize-*, + -modernize-return-braced-init-list, + -modernize-use-trailing-return-type, + -modernize-use-using, + -modernize-use-override, + -modernize-avoid-c-arrays, + -modernize-macro-to-enum, + -modernize-loop-convert, + -modernize-use-nodiscard, + -modernize-pass-by-value, + -modernize-use-auto, + performance-*, + -performance-avoid-endl, + -performance-unnecessary-value-param, + portability-std-allocator-const, + readability-*, + -readability-function-cognitive-complexity, + -readability-function-size, + -readability-identifier-length, + -readability-magic-numbers, + -readability-uppercase-literal-suffix, + -readability-braces-around-statements, + -readability-redundant-access-specifiers, + -readability-else-after-return, + -readability-container-data-pointer, + -readability-implicit-bool-conversion, + -readability-avoid-nested-conditional-operator, + -readability-redundant-member-init, + -readability-redundant-string-init, + -readability-avoid-const-params-in-decls, + -readability-named-parameter, + -readability-convert-member-functions-to-static, + -readability-qualified-auto, + -readability-make-member-function-const, + -readability-isolate-declaration, + -readability-inconsistent-declaration-parameter-name, + -clang-diagnostic-error, + +CheckOptions: + performance-for-range-copy.WarnOnAllAutoCopies: true + performance-inefficient-string-concatenation.StrictMode: true + readability-braces-around-statements.ShortStatementLines: 0 + readability-identifier-naming.ClassCase: CamelCase + readability-identifier-naming.ClassIgnoredRegexp: I.* + readability-identifier-naming.ClassPrefix: C # We can't use regex here?!?!?!? + readability-identifier-naming.EnumCase: CamelCase + readability-identifier-naming.EnumPrefix: e + readability-identifier-naming.EnumConstantCase: UPPER_CASE + readability-identifier-naming.FunctionCase: camelBack + readability-identifier-naming.NamespaceCase: CamelCase + # readability-identifier-naming.NamespacePrefix: N + readability-identifier-naming.StructPrefix: S + readability-identifier-naming.StructCase: CamelCase diff --git a/.gitignore b/.gitignore index 7ee6886..7ee6886 100644..100755 --- a/.gitignore +++ b/.gitignore diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..afd0ce8 --- /dev/null +++ b/Makefile @@ -0,0 +1,61 @@ +BIN = chat-server +SRCS = $(wildcard src/*.cpp) +HEADERS = $(wildcard src/*.hpp) + +PREFIX ?= /usr/local +BINDIR ?= $(PREFIX)/bin +DATADIR ?= $(PREFIX)/share + +OBJS = $(SRCS:.cpp=.o) + +CXXFLAGS += -std=c++23 -Oz -s -Wall -flto -fPIC +LDFLAGS += -Wl,--as-needed,-z,now,-z,pack-relative-relocs + +# Debug flags +DEBUG_CXXFLAGS = -std=c++23 -g3 -O0 -Wall -fno-lto -fPIC -DDEBUG +DEBUG_LDFLAGS = -Wl,--as-needed,-z,now,-z,pack-relative-relocs + +JOB_COUNT := $(BIN) $(OBJS) +JOBS_DONE := $(shell ls -l $(JOB_COUNT) 2> /dev/null | wc -l) + +define progress + $(eval JOBS_DONE := $(shell echo $$(($(JOBS_DONE) + 1)))) + @printf "[$(JOBS_DONE)/$(shell echo $(JOB_COUNT) | wc -w)] %s %s\n" $(1) $(2) +endef + +all: $(BIN) + +install: $(all) + @echo "Installing..." + @install -D -t $(DESTDIR)$(BINDIR) $(BIN) + +clean: + @echo "Cleaning up" + -@rm $(BIN) $(OBJS) + +$(BIN): $(OBJS) + $(call progress, Linking $@) + @$(CXX) -o $(BIN) \ + $(OBJS) \ + $(CXXFLAGS) \ + $(LDFLAGS) \ + -Wl,--no-as-needed \ + + +%.o: %.cpp + $(call progress, Compiling $@) + @$(CXX) -c $< -o $@ \ + $(CXXFLAGS) + +clang-format: + @echo "Formatting code..." + @clang-format -i $(SRCS) $(HEADERS) + +clang-tidy: + @echo "Running clang-tidy..." + @clang-tidy $(SRCS) -- $(CXXFLAGS) + +debug: CXXFLAGS = $(DEBUG_CXXFLAGS) +debug: LDFLAGS = $(DEBUG_LDFLAGS) +debug: $(BIN) + @echo "Debug build complete" diff --git a/Version_History.md b/Version_History.md deleted file mode 100644 index 3fa378a..0000000 --- a/Version_History.md +++ /dev/null @@ -1,20 +0,0 @@ -# Version History - -#### Version 1.2 - -<details> -1. admin commands: mute, unmute, tempmute -2. automute: swear 3 times as normal user -3. userlist, kick username -</details> - -#### Version 1.1 - -<details> -1. Implement not swearing words -2. Remove Inapropriate/invalid names -3. fixed all "Broken Pipe" errors -4. auto-clean history file -5. Fixed welcome join message appered twice -6. Improved syntax, and generally improved code -</details> diff --git a/main-pre.py b/main-pre.py deleted file mode 100644 index 5893623..0000000 --- a/main-pre.py +++ /dev/null @@ -1,291 +0,0 @@ - -# Copyright (C) 2023 littleblack111 <https://github.com/littleblack111/> - -import socket -from os import path -from sys import exit -import threading - -HOST = '0.0.0.0' # listen on both loopback and local IP address -PORT = 8000 - -VERSION = "1.2" # Implemented mute, unmute and tempmute, added userlist -HOME = path.expanduser('~') - -swearList = ['fuck', 'shit', 'bitch', 'nigg'] -server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -server_socket.bind((HOST, PORT)) -print(f"Server binded on {HOST} with port {PORT}") -server_socket.listen() - -clients = [] -names = [] -userlist = [] -cmdlist = ['list', 'kick', 'mute', 'unmute', 'tmpmute', 'tempmute'] -sweart1 = [] -sweart2 = [] -muted = [] - -def stop(self): - client_thread.join() - server_thread.join() - self.running = False - socket.socket(socket.AF_INET, - socket.SOCK_STREAM).connect( (self.hostname, self.port)) - self.socket.close() - -def broadcast(message): - for client in clients: - client.send(message) - with open(f"{HOME}/scripts/store/chat-server/chat.log", "a") as log: - try: - log.write(message.decode()) - except AttributeError: - log.write(message) - #with open("chat.log", "a") as log: - #log.write(message.decode()) - -def kick(address, kuname, client_socket): - global faddress - for c in clients: - c_address = c.getpeername()[0] - if c_address == address: - c.send("You have been kicked\n".encode()) - clients.remove(c) - c.close() - names.remove(kuname) - userlist.remove(kuname) - if faddress: - address = userlist[userlist.index(address)-1] - userlist.remove(address) - faddress = False - try: - muted.remove(kuname) - except ValueError: - pass - client_socket.send(f"Kicked client at {address} Succefully\n".encode()) - try: - broadcast(f"{address} is kicked") - except: - pass - return - elif c_address != address and address in userlist: - for u in userlist: - if address == u: - address = userlist[userlist.index(u)-1] - faddress = True - continue - else: - client_socket.send(f"No client found at {address}\n".encode()) - - -def mute(mname: str): - muted.append(mname) - -def unmute(mname: str): - muted.remove(mname) - try: - tplaceholdertmp.cancel() - except: - pass - -def tempmute(mname: str, time: int): - global tplaceholdertmp - mute(mname) - #threading.Timer(time*60, unmute(name)) - tplaceholdertmp = threading.Timer(time*60, unmute, mname).start() - -def verifyaccount(vname: str, vpasswd: str): - vpasswdhash = hash(vpasswd) - del vpasswd - with open(f"{HOME}/scripts/store/chat-server/accountdb.plaindb", r) as db: - while line := db.readline(): - verificationcontent = line.rstrip() - - - -def createaccount(cname: str, cpasswd: str): - cpasswdhash = hash(cpasswd) - del cpasswd - with open(f"{HOME}/scripts/store/chat-server/accountdb.plaindb", "a") as db: - db.writelines(f"{cname}:{cpasswdhash}") - print(f"Successfully created account with username: {cname} and passwordhash of {cpasswdhash}") - - -def handle_client(client_socket, client_address): - print(f'New connection from {client_address}') - if client_address[0] in [c.getpeername()[0] for c in clients]: - print(f'{client_address} is attempting to connect twice') - client_socket.send("Your already connected\n".encode()) - client_socket.close() - return - clients.append(client_socket) - userlist.append(client_address[0]) - client_socket.send("Name: ".encode()) - try: - name = client_socket.recv(1024).decode().replace("\n", "") - if name: - if name == "" or " " in name or SwearOrNot(name): - client_socket.send("Your name have problems\n".encode()) - print(f"{client_address}'s name contains problem") - clients.remove(client_socket) - client_socket.close() - return - for n in names: - if n == name: - snames = " ".join(names) - client_socket.send(f"Username taken, use an unused name, current names: {snames}\n".encode()) - print(f'{client_address} is trying to use a taken name - {name}') - clients.remove(client_socket) - client_socket.close() - return - userlist.insert(userlist.index(client_address[0])+1, name) - print(userlist) - print(userlist[userlist.index(name)]) - else: - raise Exception(f"Client '{client_address}' disconnected without registering") - except Exception as e: - print(e) - clients.remove(client_socket) - print(f'{client_address} has left the chat without registering') - #broadcast(f'{client_address} has left the chat\n'.encode()) - broadcast(f'{client_address} has left the chat without even registering\n'.encode()) - client_socket.close() - return - - names.append(name) - #broadcast(f'{client_address} has joined the chat\n'.encode()) - with open(f"{HOME}/scripts/store/chat-server/chat.log", "r") as log: - log_contents = log.read() - client_socket.send(log_contents.encode()) - broadcast(f'{name} has joined the chat\n'.encode()) - #with open("chat.log", "r") as log: - # log_contents = log.read() - # client_socket.send(log_contents.encode()) - - while True: - try: - message = client_socket.recv(1024) - if message and name not in muted: - rmsg = message.decode() - if rmsg.startswith("$"): #or client_address[0] == "192.168.1.16" and rmsg.startswith("$"): - command = rmsg[1:].strip() - #command_parts = message[1:].strip().split() - print(f"Command spawned by {client_address},({name}): {command}") - if command == "list": - client_socket.send("Connected clients:\n".encode()) - for c in clients: - c_address = c.getpeername()[0] - client_socket.send(f"{c_address}\n".encode()) - if client_address[0] == "127.0.0.1": - if "kick" in command and len(command.split()) == 2: - kick_address = command.split()[1] - if kick_address != "127.0.0.1": # and client_address[0] != "192.168.1.16": - kick(kick_address, userlist[userlist.index(kick_address)+1], client_socket) - elif "mute" in command and len(command.split()) == 2: - mute_name = command.split()[1] - if mute_name != "127.0.0.1": - mute(mute_name) - elif "unmute" in command and len(command.split()) == 2: - unmute_name = command.split()[1] - if unmute_name in muted: - print(unmute_name) - unmute(unmute_name) - elif command not in cmdlist: - client_socket.send("Unknown command or syntax \n".encode()) - print(f"{client_address},({name}) send an unknown command: {command}") - if message.decode() != "" or message.decode() != " ": - #broadcast(f'{client_address}: {message.decode()}'.encode()) - if SwearOrNot(message.decode()) and client_address[0] != "127.0.0.1": - broadcast(f'{name}: {len(message.decode())*"#"}\n'.encode()) - if name not in sweart1 and name not in sweart2 and client_address[0] != "127.0.0.1": - sweart1.append(name) - print("t1") - elif name not in sweart2 and name in sweart1 and client_address[0] != "127.0.0.1": - sweart2.append(name) - sweart1.remove(name) - print("t2") - elif name not in sweart1 and name in sweart2 and client_address[0] != "127.0.0.1": - tempmute(name, 15) - print("tmpmute") - else: - broadcast(f'{name}: {message.decode()}'.encode()) - print(f'{client_address}({name}): {message.decode()}') - elif message and name in muted: - client_socket.send("You have been muted\n".encode()) - else: - raise Exception(f'Client {name} disconnected') - except Exception as e: - print(e) - clients.remove(client_socket) - print(f'{client_address} has left the chat') - #broadcast(f'{client_address} has left the chat\n'.encode()) - broadcast(f'{name} has left the chat\n'.encode()) - client_socket.close() - names.remove(name) - userlist.remove(name) - userlist.remove(client_address[0]) - try: - muted.remove(name) - except ValueError: - pass - return - -def SwearOrNot(var): - if any(word in var for word in swearList): - return True - else: - return False - - -def accept_connections(): - global client_thread - while True: - try: - client_socket, client_address = server_socket.accept() - client_thread = threading.Thread(target=handle_client, args=(client_socket, client_address)) - client_thread.start() - except KeyboardInterrupt: - print("Closing server...") - #broadcast("Closing server...") - #client_thread.join() - exit() - -try: - if __name__ == '__main__': - #with open("chat.log", "w") as log: - # log.truncate(0) - with open(f"{HOME}/scripts/store/chat-server/chat.log", "w") as log: - log.truncate(0) - try: - server_thread = threading.Thread(target=accept_connections) - server_thread.start() - #client_socket, client_address = server_socket.accept() - #handle_client(client_socket, client_address) - except KeyboardInterrupt: - print("Closing server...") - #broadcast("Closing server...") - #client_thread.join() - exit() - - # Handle the server's own connection - try: - while True: - try: - message = input('> ') - broadcast(f'{message}\n'.encode()) - except KeyboardInterrupt: - print("Closing server...") - #broadcast("Closing server...") - #client_thread.join() - exit() - except KeyboardInterrupt: - print("Closing server...") - #broadcast("Closing server...") - #client_thread.join() - exit() -except KeyboardInterrupt: - print("Closing server...") - #broadcast("Closing server...") - #client_thread.join() - exit() diff --git a/main.py b/main.py deleted file mode 100644 index b933241..0000000 --- a/main.py +++ /dev/null @@ -1,276 +0,0 @@ - -# Copyright (C) 2023 littleblack111 <https://github.com/littleblack111/> - -import socket -from os import path -from sys import exit -import threading - -HOST = '0.0.0.0' # listen on both loopback and local IP address -PORT = 8000 - -VERSOIN = "1.2" # Implemented mute, unmute and tempmute, added userlist -HOME = path.expanduser('~') - -swearList = ['fuck', 'shit', 'bitch', 'nigg'] -server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -server_socket.bind((HOST, PORT)) -print(f"Server binded on {HOST} with port {PORT}") -server_socket.listen() - -clients = [] -names = [] -userlist = [] -cmdlist = ['list', 'kick', 'mute', 'unmute', 'tmpmute', 'tempmute'] -sweart1 = [] -sweart2 = [] -muted = [] - -def stop(self): - client_thread.join() - main.join() - self.running = False - socket.socket(socket.AF_INET, - socket.SOCK_STREAM).connect( (self.hostname, self.port)) - self.socket.close() - -def broadcast(message): - for client in clients: - client.send(message) - with open(f"{HOME}/scripts/logs/chat.log", "a") as log_file: - try: - log_file.write(message.decode()) - except AttributeError: - log_file.write(message) - #with open("chat.log", "a") as log_file: - #log_file.write(message.decode()) - -def kick(address, kuname, client_socket): - global faddress - for c in clients: - c_address = c.getpeername()[0] - if c_address == address: - c.send("You have been kicked\n".encode()) - clients.remove(c) - c.close() - names.remove(kuname) - userlist.remove(kuname) - if faddress: - address = userlist[userlist.index(address)-1] - userlist.remove(address) - faddress = False - try: - muted.remove(kuname) - except ValueError: - pass - client_socket.send(f"Kicked client at {address} Succefully\n".encode()) - try: - broadcast(f"{address} is kicked") - except: - pass - return - elif c_address != address and address in userlist: - for u in userlist: - if address == u: - address = userlist[userlist.index(u)-1] - faddress = True - continue - else: - client_socket.send(f"No client found at {address}\n".encode()) - - -def mute(mname: str): - muted.append(mname) - -def unmute(umname: str): - muted.remove(umname) - try: - tplaceholdertmp.cancel() - except: - pass - -def tempmute(mname: str, time: int): - global tplaceholdertmp - mute(mname) - #threading.Timer(time*60, unmute(name)) - tplaceholdertmp = threading.Timer(time*60, unmute, mname).start() - - - -def handle_client(client_socket, client_address): - print(f'New connection from {client_address}') - if client_address[0] in [c.getpeername()[0] for c in clients]: - print(f'{client_address} is attempting to connect twice') - client_socket.send("Your already connected\n".encode()) - client_socket.close() - return - clients.append(client_socket) - userlist.append(client_address[0]) - client_socket.send("Name: ".encode()) - try: - name = client_socket.recv(1024).decode().replace("\n", "") - if name: - if name == "" or " " in name or SwearOrNot(name): - client_socket.send("Your name have problems\n".encode()) - print(f"{client_address}'s name contains problem") - clients.remove(client_socket) - client_socket.close() - return - for n in names: - if n == name: - snames = " ".join(names) - client_socket.send(f"Username taken, use an unused name, current names: {snames}\n".encode()) - print(f'{client_address} is trying to use a taken name - {name}') - clients.remove(client_socket) - client_socket.close() - return - userlist.insert(userlist.index(client_address[0])+1, name) - print(userlist) - print(userlist[userlist.index(name)]) - else: - raise Exception(f"Client '{client_address}' disconnected without registering") - except Exception as e: - print(e) - clients.remove(client_socket) - print(f'{client_address} has left the chat without registering') - #broadcast(f'{client_address} has left the chat\n'.encode()) - broadcast(f'{client_address} has left the chat without even registering\n'.encode()) - client_socket.close() - return - - names.append(name) - #broadcast(f'{client_address} has joined the chat\n'.encode()) - with open(f"{HOME}/scripts/logs/chat.log", "r") as log_file: - log_contents = log_file.read() - client_socket.send(log_contents.encode()) - broadcast(f'{name} has joined the chat\n'.encode()) - #with open("chat.log", "r") as log_file: - # log_contents = log_file.read() - # client_socket.send(log_contents.encode()) - - while True: - try: - message = client_socket.recv(1024) - if message and name not in muted: - rmsg = message.decode() - if rmsg.startswith("$"): #or client_address[0] == "192.168.1.16" and rmsg.startswith("$"): - command = rmsg[1:].strip() - #command_parts = message[1:].strip().split() - print(f"Command spawned by {client_address},({name}): {command}") - if command == "list": - client_socket.send("Connected clients:\n".encode()) - for c in clients: - c_address = c.getpeername()[0] - client_socket.send(f"{c_address}\n".encode()) - if client_address[0] == "127.0.0.1": - if "kick" in command and len(command.split()) == 2: - kick_address = command.split()[1] - if kick_address != "127.0.0.1": # and client_address[0] != "192.168.1.16": - kick(kick_address, userlist[userlist.index(kick_address)+1], client_socket) - elif "mute" in command and len(command.split()) == 2: - mute_name = command.split()[1] - if mute_name != "127.0.0.1": - mute(mute_name) - elif "unmute" in command and len(command.split()) == 2: - unmute_name = command.split()[1] - if unmute_name in muted: - print(unmute_name) - unmute(unmute_name) - elif command not in cmdlist: - client_socket.send("Unknown command or syntax \n".encode()) - print(f"{client_address},({name}) send an unknown command: {command}") - if message.decode() != "" or message.decode() != " ": - #broadcast(f'{client_address}: {message.decode()}'.encode()) - if SwearOrNot(message.decode()) and client_address[0] != "127.0.0.1": - broadcast(f'{name}: {len(message.decode())*"#"}\n'.encode()) - if name not in sweart1 and name not in sweart2 and client_address[0] != "127.0.0.1": - sweart1.append(name) - print("t1") - elif name not in sweart2 and name in sweart1 and client_address[0] != "127.0.0.1": - sweart2.append(name) - sweart1.remove(name) - print("t2") - elif name not in sweart1 and name in sweart2 and client_address[0] != "127.0.0.1": - tempmute(name, 15) - print("tmpmute") - else: - broadcast(f'{name}: {message.decode()}'.encode()) - print(f'{client_address}({name}): {message.decode()}') - elif message and name in muted: - client_socket.send("You have been muted\n".encode()) - else: - raise Exception(f'Client {name} disconnected') - except Exception as e: - print(e) - clients.remove(client_socket) - print(f'{client_address} has left the chat') - #broadcast(f'{client_address} has left the chat\n'.encode()) - broadcast(f'{name} has left the chat\n'.encode()) - client_socket.close() - names.remove(name) - userlist.remove(name) - userlist.remove(client_address[0]) - try: - muted.remove(name) - except ValueError: - pass - return - -def SwearOrNot(var): - if any(word in var for word in swearList): - return True - else: - return False - - -def accept_connections(): - global client_thread - while True: - try: - client_socket, client_address = server_socket.accept() - client_thread = threading.Thread(target=handle_client, args=(client_socket, client_address)) - client_thread.start() - except KeyboardInterrupt: - print("Closing server...") - #broadcast("Closing server...") - #client_thread.join() - exit() - -try: - if __name__ == '__main__': - #with open("chat.log", "w") as log_file: - # log_file.truncate(0) - with open(f"{HOME}/scripts/logs/chat.log", "w") as log_file: - log_file.truncate(0) - try: - main = threading.Thread(target=accept_connections) - main.start() - #client_socket, client_address = server_socket.accept() - #handle_client(client_socket, client_address) - except KeyboardInterrupt: - print("Closing server...") - #broadcast("Closing server...") - #client_thread.join() - exit() - - # Handle the server's own connection - try: - while True: - try: - message = input('> ') - broadcast(f'{message}\n'.encode()) - except KeyboardInterrupt: - print("Closing server...") - #broadcast("Closing server...") - #client_thread.join() - exit() - except KeyboardInterrupt: - print("Closing server...") - #broadcast("Closing server...") - #client_thread.join() - exit() -except KeyboardInterrupt: - print("Closing server...") - #broadcast("Closing server...") - #client_thread.join() - exit() diff --git a/setup.sh b/setup.sh deleted file mode 100755 index 952aaa8..0000000 --- a/setup.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env bash -# Private Chat Server(PCS) - Simple Python chat server -# # Copyright (C) 2023 littleblack111 <https://github.com/littleblack111/> -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -set -e -if ! command -v python /dev/null && ! command -v python3 && ! command -v py; then - echo "Error, python not found." >&2 - exit 1 -elif ! command -v git; then - echo "Error, git not found." >&2 -fi - -echo "Cloning repository" - -git clone https://github.com/littleblack111/chat-server.git - -printf "Run server? (y/N) " -if read -q "choice? "; then - cd chat-server - if command -v python > /dev/null; then - python main.py - elif command -v python3 > /dev/null; then - python3 main.py - elif command -v py > /dev/null; then - python3 main.py - fi -fi diff --git a/src/FileDescriptor.cpp b/src/FileDescriptor.cpp new file mode 100644 index 0000000..8592f42 --- /dev/null +++ b/src/FileDescriptor.cpp @@ -0,0 +1,89 @@ +#include <cstdlib> +#include <hyprutils/os/FileDescriptor.hpp> +#include <fcntl.h> +#include <sys/poll.h> +#include <unistd.h> +#include <utility> + +using namespace Hyprutils::OS; + +CFileDescriptor::CFileDescriptor(int const fd) : m_fd(fd) {} + +CFileDescriptor::CFileDescriptor(CFileDescriptor&& other) : m_fd(std::exchange(other.m_fd, -1)) {} + +CFileDescriptor& CFileDescriptor::operator=(CFileDescriptor&& other) { + if (this == &other) // Shit will go haywire if there is duplicate ownership + abort(); + + reset(); + m_fd = std::exchange(other.m_fd, -1); + return *this; +} + +CFileDescriptor::~CFileDescriptor() { + reset(); +} + +bool CFileDescriptor::isValid() const { + return m_fd != -1; +} + +int CFileDescriptor::get() const { + return m_fd; +} + +int CFileDescriptor::getFlags() const { + return fcntl(m_fd, F_GETFD); +} + +bool CFileDescriptor::setFlags(int flags) { + if (fcntl(m_fd, F_SETFD, flags) == -1) + return false; + + return true; +} + +int CFileDescriptor::take() { + return std::exchange(m_fd, -1); +} + +void CFileDescriptor::reset() { + if (m_fd != -1) { + close(m_fd); + m_fd = -1; + } +} + +CFileDescriptor CFileDescriptor::duplicate(int flags) const { + if (m_fd == -1) + return {}; + + return CFileDescriptor{fcntl(m_fd, flags, 0)}; +} + +bool CFileDescriptor::isClosed() const { + return isClosed(m_fd); +} + +bool CFileDescriptor::isReadable() const { + return isReadable(m_fd); +} + +bool CFileDescriptor::isClosed(int fd) { + pollfd pfd = { + .fd = fd, + .events = POLLIN, + .revents = 0, + }; + + if (poll(&pfd, 1, 0) < 0) + return true; + + return pfd.revents & (POLLHUP | POLLERR); +} + +bool CFileDescriptor::isReadable(int fd) { + pollfd pfd = {.fd = fd, .events = POLLIN, .revents = 0}; + + return poll(&pfd, 1, 0) > 0 && (pfd.revents & POLLIN); +} diff --git a/src/FileDescriptor.hpp b/src/FileDescriptor.hpp new file mode 100644 index 0000000..59fb7e9 --- /dev/null +++ b/src/FileDescriptor.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include <fcntl.h> +namespace Hyprutils { +namespace OS { +class CFileDescriptor { + public: + CFileDescriptor() = default; + explicit CFileDescriptor(int const fd); + CFileDescriptor(CFileDescriptor &&); + CFileDescriptor &operator=(CFileDescriptor &&); + ~CFileDescriptor(); + + CFileDescriptor(const CFileDescriptor &) = delete; + CFileDescriptor &operator=(const CFileDescriptor &) = delete; + + bool operator==(const CFileDescriptor &rhs) const { + return m_fd == rhs.m_fd; + } + + bool isValid() const; + int get() const; + int getFlags() const; + bool setFlags(int flags); + int take(); + void reset(); + CFileDescriptor duplicate(int flags = F_DUPFD_CLOEXEC) const; + + bool isReadable() const; + bool isClosed() const; + + static bool isReadable(int fd); + static bool isClosed(int fd); + + private: + int m_fd = -1; +}; +}; // namespace OS +}; // namespace Hyprutils diff --git a/src/log.hpp b/src/log.hpp new file mode 100644 index 0000000..5285dc8 --- /dev/null +++ b/src/log.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include <map> +#include <string> +typedef enum { + LOG_LEVEL_DEBUG = 0, + LOG_LEVEL_INFO, + LOG_LEVEL_WARN, + LOG_LEVEL_ERROR, + LOG_LEVEL_FATAL, + LOG_LEVEL_MAX +} eLogLevel; + diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..1cf50b5 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,9 @@ +#include "sessionManager.hpp" +#include "server.hpp" + +int main() { + g_pServer = std::make_unique<CServer>(8080); + g_pSessionManager = std::make_unique<CSessionManager>(); + g_pSessionManager->enterLoop(); + return 0; +} diff --git a/src/server.cpp b/src/server.cpp new file mode 100644 index 0000000..f85d1fc --- /dev/null +++ b/src/server.cpp @@ -0,0 +1,50 @@ +#include "server.hpp" +#include "sessionManager.hpp" +#include <cstring> +#include <print> +#include <sys/socket.h> + +CServer::CServer(uint16_t port) + : m_sockfd(Hyprutils::OS::CFileDescriptor{socket(AF_INET, SOCK_STREAM, 0)}) { + if (!m_sockfd.isValid()) + throw std::runtime_error("Failed to create socket"); + + memset(&m_addr, 0, sizeof(m_addr)); + m_addr.sin_family = AF_INET; + m_addr.sin_port = htons(port); + m_addr.sin_addr.s_addr = INADDR_ANY; + m_dBind = bind(m_sockfd.get(), reinterpret_cast<sockaddr *>(&m_addr), sizeof(m_addr)); + m_dListen = listen(m_sockfd.get(), 5); + m_dBind < 0 || m_dListen < 0 ? throw std::runtime_error("Failed to bind or listen") : std::println("Server started on port: {}", port); + + // char buffer[1024] = {0}; + // int clientSocket = accept(m_sockfd.get(), nullptr, nullptr); + // recv(clientSocket, buffer, sizeof(buffer), 0); + // std::println("Message from client: {}", buffer); +}; + +// void CServer::run() { +// while (true) { +// char buffer[1024] = {0}; +// int clientSocket = accept(m_sockfd.get(), nullptr, nullptr); +// recv(clientSocket, buffer, sizeof(buffer), 0); +// std::println("Message from client: {}", buffer); +// } +// } + +CServer::~CServer() { + m_sockfd.reset(); +} + + +void CServer::log(const eLogLevel loglevel, const std::string &message) const { + std::println("[{}]: {}", "a", message); +} + +bool CServer::isValid() { + return m_sockfd.isValid() && m_dBind >= 0 && m_dListen >= 0; +} + +Hyprutils::OS::CFileDescriptor CServer::getSocket() const { + return m_sockfd.duplicate(); +} diff --git a/src/server.hpp b/src/server.hpp new file mode 100644 index 0000000..e100266 --- /dev/null +++ b/src/server.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include "FileDescriptor.hpp" +#include "log.hpp" +#include <netinet/in.h> +#include <string> +#include <memory> + +class CServer { + public: + CServer(uint16_t port); + ~CServer(); + void log(const eLogLevel loglevel, const std::string &message) const; + bool isValid(); + Hyprutils::OS::CFileDescriptor getSocket() const; + + private: + Hyprutils::OS::CFileDescriptor m_sockfd; + sockaddr_in m_addr; + int m_dBind = -1; + int m_dListen = -1; +}; +inline std::unique_ptr<CServer> g_pServer; diff --git a/src/session.cpp b/src/session.cpp new file mode 100644 index 0000000..da7ebf6 --- /dev/null +++ b/src/session.cpp @@ -0,0 +1,112 @@ +#include "session.hpp" +#include <cmath> +#include <ostream> +#include <sys/socket.h> +#include <print> +#include <cerrno> +#include <cstring> + +CSession::CSession(Hyprutils::OS::CFileDescriptor sockfd) : m_sockfd(std::move(sockfd)) { + if (!m_sockfd.isValid()) + throw std::runtime_error("Failed to create socket"); +} + +CSession::~CSession() { + m_sockfd.reset(); +} + +void CSession::onConnect() const { + std::println("Client connected"); +} + +void CSession::onDisconnect() const { + std::println("Client disconnected"); +} + +void CSession::onErrno() const { + std::println("Error: {}", strerror(errno)); +} + +void CSession::onRecv(SRecvData &data) const { + std::println("Received: {}", data.data, data.dataSize); +} + +void CSession::recvManager() { + while (true) { + CSession::SRecvData recvData = read(); + if (!recvData.good) { + break; + } + } +} + +CSession::SRecvData CSession::read() { + SRecvData recvData; + ssize_t dataSize = recv(m_sockfd.get(), recvData.data, recvData.dataSize, 0); + if (dataSize < 0) { + recvData.good = false; + onErrno(); + } + if (dataSize == 0) { + recvData.good = false; + onDisconnect(); + } + onRecv(recvData); + return recvData; +} + +CSession::SRecvData CSession::read(std::string &msg) { + write(msg); + SRecvData recvData; + ssize_t dataSize = recv(m_sockfd.get(), recvData.data, recvData.dataSize, 0); + + if (dataSize > 0) { + recvData.good = false; + + if (dataSize < 0) + onErrno(); + + if (dataSize == 0) + onDisconnect(); + } + onRecv(recvData); + + return recvData; +} + +void CSession::run() { + onConnect(); + // std::jthread recvThread(&CSession::recvManager, this); + registerSession(); + recvManager(); +} + +bool CSession::registerSession() { + SRecvData rervData = read((std::string&)"Name: "); + if (!rervData.good) { + return false; + } + m_name = rervData.data; + + std::println("Client registered as: {}", m_name); + + return true; +} + +bool CSession::isValid() const { + return m_sockfd.isValid() && !m_name.empty(); +} + +bool CSession::write(const std::string &msg) const { + bool bad = false; + ssize_t bytes_sent = send(m_sockfd.get(), msg.c_str(), msg.size(), 0); + if (bytes_sent < 0) { + bad = true; + onErrno(); + } + return bad; +} + +std::string CSession::getName() const { + return m_name; +} diff --git a/src/session.hpp b/src/session.hpp new file mode 100644 index 0000000..8e0859c --- /dev/null +++ b/src/session.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include "FileDescriptor.hpp" +#include <string> + +class CSession { + public: + struct SRecvData { + char data[1024] = {0}; + ssize_t dataSize = sizeof(data); + bool good = true; + }; + CSession(Hyprutils::OS::CFileDescriptor m_sockfd); + ~CSession(); + std::string getName() const; + bool isValid() const; + SRecvData read(); + SRecvData read(std::string &msg); + bool write(const std::string &msg) const; + void run(); + + private: + Hyprutils::OS::CFileDescriptor m_sockfd; + std::string m_name; + void recvManager(); + bool registerSession(); +void onConnect() const; +void onDisconnect() const; +void onErrno() const; +void onRecv(SRecvData &data) const; + friend class CSessionManager; +}; diff --git a/src/sessionManager.cpp b/src/sessionManager.cpp new file mode 100644 index 0000000..a249f61 --- /dev/null +++ b/src/sessionManager.cpp @@ -0,0 +1,29 @@ +#include "sessionManager.hpp" +#include "session.hpp" +#include "server.hpp" +#include <print> + +CSessionManager::CSessionManager() = default; + +CSessionManager::~CSessionManager() { + for (auto &[thread, session] : m_vSessions) { + thread.join(); + } +} + +void CSessionManager::newSession(Hyprutils::OS::CFileDescriptor sockfd) { + std::shared_ptr session = std::make_shared<CSession>(std::move(sockfd)); + m_vSessions.emplace_back(std::jthread(&CSession::run, session), std::move(session)); +} + +void CSessionManager::enterLoop() { + while (true) { + newSession(Hyprutils::OS::CFileDescriptor{accept(g_pServer->getSocket().take(), nullptr, nullptr) /* awaits new client/session */}); + } +} + +void CSessionManager::broadcast(const std::string &msg) const { + for (const auto &[thread, session] : m_vSessions) { + session->write(msg); + } +} diff --git a/src/sessionManager.hpp b/src/sessionManager.hpp new file mode 100644 index 0000000..0e6eef3 --- /dev/null +++ b/src/sessionManager.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "session.hpp" +#include <memory> +#include <thread> +#include <vector> + +class CSessionManager { +public: + CSessionManager(); + ~CSessionManager(); + void enterLoop(); + void newSession(Hyprutils::OS::CFileDescriptor sockfd); + void broadcast(const std::string &message) const; + void kick(const CSession *session) const; + void addSession(const CSession &session); +private: + std::vector<std::pair<std::jthread, std::shared_ptr<CSession>>> m_vSessions; +}; +inline std::unique_ptr<CSessionManager> g_pSessionManager; |
