diff --git a/lib/apex.rb b/lib/apex.rb index 8a4206b4ee1e95c1863808ac46230d0c7c206fc4..ce8625cfbbaed64270c1f4b963abd07e771fb607 100644 --- a/lib/apex.rb +++ b/lib/apex.rb @@ -1 +1,4 @@ -require 'apex/aprs_kiss' \ No newline at end of file +require 'apex/aprs_kiss' +require 'apex/igate/client' +require 'apex/igate/filter' +require 'apex/igate/gilter/constants' diff --git a/lib/apex/igate/client.rb b/lib/apex/igate/client.rb new file mode 100644 index 0000000000000000000000000000000000000000..1093b9fb49ff3b03a7ef6addb99ee2ee73e01e7d --- /dev/null +++ b/lib/apex/igate/client.rb @@ -0,0 +1,98 @@ +require 'socket' + +module Apex + module Igate + class Client + attr_reader :hostname, :port, :version + + class Error < StandardError; end + + def initialize(hostname:, port:, version: "Apex::Igate::Client v#{VERSION}") + @hostname = hostname + @port = port + @version = version + @line_cache = "" + end + + def login(call_sign, filters = []) + send_message(login_message(call_sign, filters)) + return self + end + + def apply_filter(filter) + send_message(filter_message(filter)) + end + + def read + end + + def read + read_char = socket_recv() + while not read_char.nil? + if read_char.eql? "\r" or read_char.eql? "\n" + if @line_cache.length > 0 + complete_line = @line_cache + @line_cache = "" + return complete_line + end + else + @line_cache << read_char + end + + read_char = socket_recv() + end + return nil + end + + def read_all(&block) + while line = socket.gets + yield line + end + end + + # :nocov: + def stream(&block) + loop do + read(&block) + end + end + # :nocov: + + def send_message(message) + socket.puts message + end + + private + + def login_message(call_sign, filters = []) + filters.unshift('filter') if filters.any? + + [ + "user #{call_sign} pass #{passcode_for(call_sign)} vers #{version}", + filters.compact.collect(&:to_s) + ].flatten.compact.join(" ") + end + + def filter_message(filter) + "filter #{filter}" + end + + def passcode_for(call_sign) + @passcode = Apex::Igate::Passcode.new(call_sign) + end + + def socket + @socket ||= TCPSocket.open(hostname, port) + end + + def socket_recv + ready = IO.select([@socket], nil, nil, 0) + return nil unless ready + + read_char = @socket.recv(1) + return nil if read_char.eql? "" + return read_chat + end + end + end +end diff --git a/lib/apex/igate/filter.rb b/lib/apex/igate/filter.rb new file mode 100644 index 0000000000000000000000000000000000000000..d304bceaa3845960ee987a7c6ada14f8bcfadc54 --- /dev/null +++ b/lib/apex/igate/filter.rb @@ -0,0 +1,49 @@ +require 'apex/igate/filter/constants' + +module Apex + module Igate + class Filter + class InvalidTypeError < StandardError; end; + class ValueArityError < StandardError; end; + + attr_reader :filter_type, :values + + def initialize(type:, values: ) + @filter_type = type + @values = values || [] + + validate_filter_type + validate_arity + end + + def to_s + values.dup.unshift(prefix).compact.join("/") + end + + private + def validate_arity + raise ValueArityError.new("Filter type '#{filter_type}' requires #{arity} values only #{values.length} given.") unless arity_matches? + end + + def validate_filter_type + raise InvalidTypeError.new("'#{filter_type}' is not a valid type.") unless type_exists? + end + + def arity_matches? + (arity == -1 && values.length > 0) || [arity].flatten.include?(values.length) + end + + def type_exists? + Constants::FILTER_TYPE_MAP.has_key?(filter_type) + end + + def arity + Constants::FILTER_TYPE_MAP[filter_type][:arity] + end + + def prefix + Constants::FILTER_TYPE_MAP[filter_type][:prefix] + end + end + end +end diff --git a/lib/apex/igate/filter/constants.rb b/lib/apex/igate/filter/constants.rb new file mode 100644 index 0000000000000000000000000000000000000000..e2cff42d807e2861aec5b87384524e4140b5c7a9 --- /dev/null +++ b/lib/apex/igate/filter/constants.rb @@ -0,0 +1,70 @@ +module Apex + module Igate + class Filter + module Constants + FILTER_TYPE_MAP = { + range: { + prefix: 'r', + arity: 3 + }, + prefix: { + prefix: 'p', + arity: -1 + }, + callsign: { + prefix: 'b', + arity: -1 + }, + object: { + prefix: 'o', + arity: -1 + }, + strict: { + prefix: 'os', + arity: -1 + }, + type: { + prefix: 't', + arity: [1,3] + }, + symbol: { + prefix: 's', + arity: 3 + }, + digipeater: { + prefix: 'd', + arity: -1 + }, + area: { + prefix: 'a', + arity: 4 + }, + entry: { + prefix: 'e', + arity: -1 + }, + group: { + prefix: 'g', + arity: -1 + }, + unproto: { + prefix: 'u', + arity: -1 + }, + q: { + prefix: 'q', + arity: (1..2) + }, + me: { + prefix: 'm', + arity: 1 + }, + friend: { + prefix: 'f', + arity: 2 + }, + }.freeze + end + end + end +end diff --git a/lib/apex/igate/passcode.rb b/lib/apex/igate/passcode.rb new file mode 100644 index 0000000000000000000000000000000000000000..55ca24e9f71363dfd829ecb0ffc13f0b38afbe20 --- /dev/null +++ b/lib/apex/igate/passcode.rb @@ -0,0 +1,35 @@ +module Apex + module Igate + class Passcode + attr_reader :call_sign + + def initialize(call_sign) + @call_sign = call_sign + end + + def to_s + generate.to_s + end + + def generate + hash = 0x73e2 + flag = true + call_sign_for_generation.split('').each do |c| + hash = if flag + (hash ^ (c.ord << 8)) + else + (hash ^ c.ord) + end + flag = !flag + end + hash & 0x7fff + end + + private + + def call_sign_for_generation + call_sign.upcase.split('-').first + end + end + end +end