Yessiest
2 years ago
commit
cb11018eba
2 changed files with 199 additions and 0 deletions
@ -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 |
@ -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 |
Write
Preview
Loading…
Cancel
Save
Reference in new issue