From cb11018eba089a9f85c1fbcd8d34263822165c8d Mon Sep 17 00:00:00 2001 From: Yessiest Date: Sun, 9 Oct 2022 19:14:04 +0400 Subject: [PATCH] Initial commit --- proto.rb | 101 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ server.rb | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 199 insertions(+) create mode 100644 proto.rb create mode 100644 server.rb diff --git a/proto.rb b/proto.rb new file mode 100644 index 0000000..256e475 --- /dev/null +++ b/proto.rb @@ -0,0 +1,101 @@ +require "date" +def validate(*a) + # Check arguments against either a list of acceptable classes or just a class + raise ArgumentError, "expected an even number of arguments" if a.count%2 != 0 + for i in (0..a.count-1).step(2) do + if a[i+1].kind_of?(Class) then + raise TypeError, "expected argument #{i/2} of type #{a[i+1].to_s}" unless a[i].kind_of?(a[i+1]) + elsif a[i+1].kind_of?(Array) then + raise TypeError, "expected argument #{i/2} of any of the types #{a[i+1].to_s}" unless a[i+1].include?(a[i].class) + end + end +end +module Heimdall + # Core protocol + class NetEntity + # Abstraction that stores basic object properties + @createdAt + def initialize() + @createdAt = DateTime.new + end + attr_reader :createdAt + end + + class User < NetEntity + # User abstraction (not bound to a group chat) + @username + @nickname + def initialize(username,nickname = nil) + validate( + username, String, + nickname, [String,NilClass] + ) + super() + nickname = username if not nickname + @username = username + @nickname = nickname + end + attr_reader :username + attr_accessor :nickname + end + + class Channel < NetEntity + # Channel abstraction (not bound to a group) + # Practically acts as a message stack + # Read access to all elements + # Write access only to top + @messages + def initialize() + validate( + id, String + ) + super() + @messages = [] + end + def [](index) + @messages[id] + end + def <<(message) + @messages.append(message) + end + def top(count = 1) + return @messages[-1] if count == 1 + @messages[(-1*count)..-1] + end + def filter(&block) + @messages.filter(block) + end + def snapshot() + @messages.clone + end + end + + class Message < NetEntity + # Message abstraction with edits and content history (not bound to a group) + @content + @author + @editedAt + @contentHist + def initialize(content,author,channel) + validate( + content, String, + author, Heimdall::User, + channel, Heimdall::Channel + ) + super() + @editedAt = DateTime.new + @contentHist = [content] + @content = content + @author = author + end + def edit(content) + @content = content + @editedAt = DateTime.new + @contentHist.append(content) + end + attr_reader :content + attr_reader :author + attr_reader :contentHist + attr_reader :editedAt + end +end diff --git a/server.rb b/server.rb new file mode 100644 index 0000000..65fd5b1 --- /dev/null +++ b/server.rb @@ -0,0 +1,98 @@ +# Prototype of the heimdall server +$LOAD_PATH << "." +$VERSION = "0.1" +$PORT = 9128 +require "proto" +require "socket" + +PROTO_CODES = { + :SUCCESS => 0, + :BADJSON => 1, + :NOMETHOD => 2, + :INVPARAM => 3, + :NOTIMPL => 4 +} + +PROTO_MESSAGES = { + :SUCCESS => "Done", + :BADJSON => "Invalid JSON object", + :NOMETHOD => "Invalid method", + :INVPARAM => "Invalid parameters", + :NOTIMPL => "Not implemented" +} + +class Server + @channelPool + @channelListeners + @methods + def initialize() + @channelPool = {} + @channelListeners = {} + @methods = [ + "join", + "useradd", + "userdel", + "userinfo", + "lusers", + "lchannels", + "chantop", + "chansend", + "chanedit" + ] + end + + def send(client,message) + client.puts JSON.dump(message) + end + + def err(client,code) + client.puts JSON.dump({ + :error=>PROTO_MESSAGES[code], + :code=>PROTO_CODES[code] + }) + client.close + end + + def serve(client) + # basic validation steps + begin + # 1. JSON validation + data = client.gets + data = JSON.load(data) + rescue JSON::ParserError + err(client,:BADJSON) + end + # 2. Method validation + method = data[:method] + err(client,:NOMETHOD) if not @methods.include?(method) + # last but not least: calling the given method + if not self.methods.include?(method) then + err(client,:NOTIMPL) + return + end + send(client,self.method(method).call(client,data)) + end + + def join(client,data) + chaind = data["chanid"] + chanid = rand(89999999)+10000000 if not chanid + if not @channelPool[chanid] then + @channelPool[chanid] = Heimdall::Channel.new + @channelListeners + end + return { + :code=>PROTO_CODE[:SUCCESS], + :method=>"join", + :chanid=>chanid, + } + end +end + +puts "Starting heimdall v#{$VERSION} server on port #{$PORT}..." +server = TCPServer.new 9128 +loop do + Thread.start(server.accept) do |client| + puts "Received client connection on #{client.addr}" + + end +end