thekuwayama/svcb_rr_patch

View on GitHub
lib/svcb_rr_patch/svc_params.rb

Summary

Maintainability
A
55 mins
Test Coverage
# frozen_string_literal: true

module SvcbRrPatch::SvcParams
  PARAMETER_REGISTRY = lambda {
    registry = %w[
      mandatory
      alpn
      no-default-alpn
      port
      ipv4hint
      ech
      ipv6hint
    ]
    # rubocop:disable Security/Eval
    (8...65280).each do |nnnn|
      eval "registry[nnnn] = \"undefine#{nnnn}\"", binding, __FILE__, __LINE__
    end
    (65280...65535).each do |nnnn|
      eval "registry[nnnn] = \"key#{nnnn}\"", binding, __FILE__, __LINE__
    end
    # rubocop:enable Security/Eval
    registry
  }.call.freeze

  PARAMETER_REGISTRY_INVERT = lambda {
    Hash[(0..PARAMETER_REGISTRY.size - 1).zip(PARAMETER_REGISTRY)].invert
  }.call.freeze
end

Dir["#{File.dirname(__FILE__)}/svc_params/*.rb"].sort.each { |f| require f }

class SvcbRrPatch::SvcParams::Hash
  def initialize(hash)
    @hash = hash
  end

  # @return [String]
  def encode
    @hash
      .map { |k, v| [SvcbRrPatch::SvcParams::PARAMETER_REGISTRY_INVERT[k], v] }
      .sort { |lh, rh| lh.first <=> rh.first }
      .map do |k, v|
        [k].pack('n') + v.encode.then { |s| [s.length].pack('n') + s }
      end
      .join
  end

  # @param octet [String]
  #
  # @return [Hash]
  def self.decode(octet)
    svc_params = {}
    i = 0
    while i < octet.length
      raise ::Resolv::DNS::DecodeError if i + 4 > octet.length

      k = octet.slice(i, 2).unpack1('n')
      # SvcParamKeys SHALL appear in increasing numeric order.
      raise ::Resolv::DNS::DecodeError unless svc_params.keys.find do |key|
        SvcbRrPatch::SvcParams::PARAMETER_REGISTRY_INVERT[key] >= k
      end.nil?

      i += 2
      vlen = octet.slice(i, 2).unpack1('n')
      i += 2
      raise ::Resolv::DNS::DecodeError if i + vlen > octet.length

      v = octet.slice(i, vlen)
      i += vlen
      # Values are in a format specific to the SvcParamKey.
      svc_param_key = SvcbRrPatch::SvcParams::PARAMETER_REGISTRY[k]
      svc_param_values = decode_svc_params(svc_param_key, v)
      svc_params.store(svc_param_key, svc_param_values)
    end
    raise ::Resolv::DNS::DecodeError if i != octet.length

    new(svc_params)
  end

  # :nodoc:
  # rubocop:disable Metrics/CyclomaticComplexity
  def self.decode_svc_params(key, octet)
    case key
    when 'mandatory'
      SvcbRrPatch::SvcParams::Mandatory.decode(octet)
    when 'alpn'
      SvcbRrPatch::SvcParams::Alpn.decode(octet)
    when 'no-default-alpn'
      SvcbRrPatch::SvcParams::NoDefaultAlpn.decode(octet)
    when 'port'
      SvcbRrPatch::SvcParams::Port.decode(octet)
    when 'ipv4hint'
      SvcbRrPatch::SvcParams::Ipv4hint.decode(octet)
    when 'ech'
      SvcbRrPatch::SvcParams::Ech.decode(octet)
    when 'ipv6hint'
      SvcbRrPatch::SvcParams::Ipv6hint.decode(octet)
    else
      octet
    end
  end
  # rubocop:enable Metrics/CyclomaticComplexity

  # :nodoc:
  def [](key)
    @hash[key]
  end

  # :nodoc:
  def keys
    @hash.keys
  end

  # :nodoc:
  def to_s
    @hash.map { |k, v| "#{k}=#{v}" }.join(' ')
  end
end