knot-ruby/lib/knot/protocol.rb

279 lines
6.9 KiB
Ruby
Raw Normal View History

2020-06-20 22:30:05 +02:00
require 'iounpack'
require 'pathname'
require 'socket'
require 'logger'
2020-06-20 22:30:05 +02:00
require_relative 'errors'
module Knot
class Protocol
end
end
module Knot::Protocol::Type
def [] code
case code
when 0, End then return End
when 1, Data then return Data
when 2, Extra then return Extra
when 3, Block then return Block
end
end
class Base
def self.expect_data?
false
end
def expect_data?() self.class.expect_data? end
def code() self.class.code end
end
class End < Base
def self.code() 0 end
end
class Data < Base
def code() 1 end
def expect_data?
true
end
attr_reader :data
def initialize data
@data = data
end
end
class Extra < Base
def code() 2 end
def expect_data?
true
end
attr_reader :data
def initialize data
@data = data
end
end
class Block < Base
def code() 3 end
end
end
#class Knot::KnotC
# attr_accessor :binary
#
# def initialize path = nil, binary: nil
# @path = path
# @binary = binary || 'knotc'
# @conf = Knot::Conf.new self
# @zones = Hash.new {|h, zone| h[zone] = Knot::Zone.new zone, self }
# end
#
# def call command:, flags: nil, section: nil, item: nil, id: nil, zone: nil, owner: nil, ttl: nil, type: nil, data: nil, filter: nil
# cs =
# case command.to_s
# when 'conf-begin', 'conf-commit', 'conf-abort', 'status', 'stop', 'reload'
# [@binary, command, ]
# else raise ArgumentError, "Unknown Command: #{command}"
# end
# end
#end
class Knot::Protocol::Code
include Comparable
attr_reader :name, :code, :cname, :description
def initialize name, code, cname, description
raise ArgumentError, "Expecting Symbol for #{self.class.name} instead of: #{name.inspect}" unless Symbol === name
raise ArgumentError, "Expecting Integer for #{self.class.name} instead of: #{code.inspect}" unless Integer === code
@name, @code, @cname, @description = name, code, cname, description
freeze
end
def === x
case x
when self.class then @id == x.id
when Symbol then @name == x
when String then @name == x.to_sym
when Integer then @code == x
else nil
end
end
def <=>( x) @id <=> x.id end
def to_s() @name.to_s end
def to_sym() @name end
def to_i() @code end
end
module Knot::Protocol::Codes
include Enumerable
def [] k
case k
when Symbol
self::Name[k] or raise Knot::Errors::EINVAL, "Unknown Codes: #{k}"
when Integer
self::Code[k] or raise Knot::Errors::EINVAL, "Unknown Codes: #{k}"
else
raise ArgumentError, "Unknown Codes-Type: #{k}"
end
end
def each &exe
block_given? ? self::Codes.each( &exe) : self::Codes.to_enum( :each)
end
end
module Knot::Protocol::Idx
extend Knot::Protocol::Codes
Codes = [
Knot::Protocol::Code.new( :command, 0x10, :CMD, 'Control command name.'),
Knot::Protocol::Code.new( :flags, 0x11, :FLAGS, 'Control command flags.'),
Knot::Protocol::Code.new( :error, 0x12, :ERROR, 'Error message.'),
Knot::Protocol::Code.new( :section, 0x13, :SECTION, 'Configuration section name.'),
Knot::Protocol::Code.new( :item, 0x14, :ITEM, 'Configuration item name.'),
Knot::Protocol::Code.new( :id, 0x15, :ID, 'Congiguration item identifier.'),
Knot::Protocol::Code.new( :zone, 0x16, :ZONE, 'Zone name.'),
Knot::Protocol::Code.new( :owner, 0x17, :OWNER, 'Zone record owner'),
Knot::Protocol::Code.new( :ttl, 0x18, :TTL, 'Zone record TTL.'),
Knot::Protocol::Code.new( :type, 0x19, :TYPE, 'Zone record type name.'),
Knot::Protocol::Code.new( :data, 0x1a, :DATA, 'Configuration item/zone record data.'),
Knot::Protocol::Code.new( :filter, 0x1b, :FILTER, 'An option or a filter for output data processing.'),
2020-06-20 22:30:05 +02:00
]
Name = {}
Code = {}
Codes.each do |id|
Code[id.to_i] = id
Name[id.to_sym] = id
2020-06-20 22:30:05 +02:00
end
end
module Knot::Protocol::Types
extend Knot::Protocol::Codes
Codes = [
Knot::Protocol::Code.new( :end, 0x00, :END, 'Type END.'),
Knot::Protocol::Code.new( :data, 0x01, :DATA, 'Type DATA.'),
Knot::Protocol::Code.new( :extra, 0x02, :EXTRA, 'Type EXTRA.'),
Knot::Protocol::Code.new( :block, 0x03, :BLOCK, 'Type BLOCK.'),
]
Name = {}
Code = {}
Codes.each do |id|
Code[id.to_i] = id
Name[id.to_sym] = id
2020-06-20 22:30:05 +02:00
end
end
class Knot::Protocol
attr_reader :sock, :conf, :zones, :logger
2020-06-20 22:30:05 +02:00
def initialize path_or_sock = nil, logger: nil
2020-06-20 22:30:05 +02:00
case path_or_sock
when String, Pathname
@sock = UNIXSocket.new path_or_sock.to_s
when Socket
@sock = path_or_sock
when nil
@sock = UNIXSocket.new '/run/knot/knot.sock'
end
@logger = logger || Logger.new(STDERR)
2020-06-20 22:30:05 +02:00
@conf = Knot::Conf.new self
@zones = Hash.new {|h, zone| h[zone] = Knot::Zone.new zone, self }
end
def snd sock: nil, **data
rsock = sock || @sock
#s = ''.b
#sock = StringIO.new s
#sock.write [1].pack( 'c')
2020-06-20 22:30:05 +02:00
data[:flags] ||= ''
ds =
Idx.
select {|n| data[n.to_sym] }.
map {|n| v = data[n.to_sym].to_s.b; [n.to_i, v.size, v ] }
s = [Types[:data].to_i, ds, Types[:block].to_i].flatten.pack( "c #{'c na*'*ds.length} c").b
#Idx.each do |n|
# v = data[n.to_sym]&.to_s&.b
# sock.write [n.to_i, v.size, v].pack( 'c na*') if v
#end
#sock.write [3].pack( 'c')
#sock.flush
if 0 >= @logger.sev_threshold
@logger.debug "send data #{data.inspect}"
@logger.debug "send raw #{s.inspect}"
end
2020-06-20 22:30:05 +02:00
rsock.write s
rsock.flush
end
2020-06-20 22:30:05 +02:00
class RecordIO
attr_reader :str
def initialize sock, str = nil
@str, @sock = str || ''.b, sock
2020-06-20 22:30:05 +02:00
end
def unpack pattern
IOUnpack.new( pattern).unpack self
2020-06-20 22:30:05 +02:00
end
def unpack1 pattern
IOUnpack.new( pattern).unpack1 self
2020-06-20 22:30:05 +02:00
end
def read n
s = @sock.read n
@str.insert -1, s unless s.nil?
s || ''
2020-06-20 22:30:05 +02:00
end
end
def rcv sock: nil
ret, r = [], nil
sock = sock || @sock
sock = RecordIO.new sock if 0 >= @logger.sev_threshold
2020-06-20 22:30:05 +02:00
loop do
t = sock.unpack1 'c'
case t
when Knot::Protocol::Types[:end], Knot::Protocol::Types[:block]
2020-06-20 22:30:05 +02:00
return ret
when Knot::Protocol::Types[:data], Knot::Protocol::Types[:extra]
2020-06-20 22:30:05 +02:00
type = t
ret.push( r = {})
else
2020-06-20 22:30:05 +02:00
raise Knot::Errors::EINVAL, "Missing Type before: #{t}" if ret.empty?
i = Idx[t] or raise Knot::Errors::EINVAL, "Unknown index: #{t}"
2020-06-20 22:30:05 +02:00
l = sock.unpack1 'n'
r[i.to_sym] = sock.read( l)
2020-06-20 22:30:05 +02:00
end
end
ensure
if RecordIO === sock
@logger.debug "rcvd raw #{sock.str.inspect}"
@logger.debug "rcvd data #{ret.inspect}"
end
2020-06-20 22:30:05 +02:00
ret
end
def call sock: nil, **data
snd sock: sock, **data
rcv( sock: sock).each do |r|
2020-06-20 22:30:05 +02:00
if r[:error]
if e = Knot::Errors.err2exc[r[:error]]
raise e, r[:error]
end
raise Knot::Error, r[:error]
end
end
end
def zone( zone) @zones[zone.to_s.to_sym] end
def conf_set( **opts) call **opts.update( command: 'conf-set') end
def conf_unset( **opts) call **opts.update( command: 'conf-unset') end
2020-06-20 22:30:05 +02:00
def zone_set( **opts) call **opts.update( command: 'zone-set') end
def zone_unset( **opts) call **opts.update( command: 'zone-unset') end
def zone_get( **opts) call **opts.update( command: 'zone-get') end
2020-06-20 22:30:05 +02:00
end