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