require 'iounpack' require 'stringio' 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 :debug, :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 module Knot::Protocol::Idx Idx = [ :command, # 10, :CMD, # Control command name. :flags, # 11, :FLAGS, # Control command flags. :error, # 12, :ERROR, # Error message. :section, # 13, :SECTION, # Configuration section name. :item, # 14, :ITEM, # Configuration item name. :id, # 15, :ID, # Congiguration item identifier. :zone, # 16, :ZONE, # Zone name. :owner, # 17, :OWNER, # Zone record owner :ttl, # 18, :TTL, # Zone record TTL. :type, # 19, :TYPE, # Zone record type name. :data, # 1a, :DATA, # Configuration item/zone record data. :filter, # 1b, :FILTER, # An option or a filter for output data processing. ] Name = {} Code = {} Idx.each_with_index do |v, i| Code[0x10+i] = v Name[v] = i end def self.[] k case k when Symbol Name[k] or raise Knot::Errors::EINVAL, "Unknown Idx: #{k}" when Integer Idx[k] or raise Knot::Errors::EINVAL, "Unknown Idx: #{k}" else raise ArgumentError, "Unknown Idx-Type: #{k}" end end end class Knot::Protocol attr_reader :sock, :conf, :zones attr_accessor :debug def initialize path_or_sock = nil 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 @debug = false @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 = '' sock = StringIO.new s sock.write [1].pack( 'c') data[:flags] ||= '' Idx::Idx.each_with_index do |n, i| v = data[n]&.to_s sock.write [0x10+i, v.size, v].pack( 'c na*') if v end sock.write [3].pack( 'c') sock.flush STDERR.puts( {data: data, _: s}.inspect) if @debug rsock.write s rsock.flush end class RecordIO attr_reader :str def initialize sock, str = nil @str, @sock = str || '', sock end def unpack pattern IOUnpack.new(pattern).unpack self end def unpack1 pattern IOUnpack.new(pattern).unpack1 self end def read n s = @sock.read n @str.insert -1, s s end end def rcv sock: nil ret, r = [], nil sock = sock || @sock sock = RecordIO.new sock if @debug loop do t = sock.unpack1 'c' case t when 0, 3 return ret when 1, 2 type = t ret.push( r = {}) else raise Knot::Errors::EINVAL, "Missing Type before: #{t}" if ret.empty? i = Idx::Idx[t - 0x10] or raise Knot::Errors::EINVAL, "Unknown index: #{t-0x10}" l = sock.unpack1 'n' r[i] = sock.read( l) end end ensure STDERR.puts( {rcvd: ret, read: sock.str}.inspect) if @debug ret end def call sock: nil, **data snd sock: sock, **data rcv( sock: sock).each do |r| 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 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 end