lib/core/coap/coding.rb

Summary

Maintainability
A
0 mins
Test Coverage
# coapmessage.rb
# Copyright (C) 2010..2013 Carsten Bormann <cabo@tzi.org>

module CoRE
  module CoAP
    module Coding
      BIN  = Encoding::BINARY
      UTF8 = Encoding::UTF_8

      UNRESERVED = '\w\-\.~' # Alphanumerics, '_', '-', '.', '~'
      SUB_DELIM = "!$&'()*+,;="
      SUB_DELIM_NO_AMP = SUB_DELIM.delete('&')

      PATH_UNENCODED = "#{UNRESERVED}#{SUB_DELIM}:@"
      PATH_ENCODED_RE = /[^#{PATH_UNENCODED}]/mn

      QUERY_UNENCODED = "#{UNRESERVED}#{SUB_DELIM_NO_AMP}:@/?"
      QUERY_ENCODED_RE = /[^#{QUERY_UNENCODED}]/mn

      # Also extend on include.
      def self.included(base)
        base.send :extend, Coding
      end

      # The variable-length binary (vlb) numbers defined in CoRE-CoAP Appendix A.
      def vlb_encode(n)
        n = Integer(n)
        v = empty_buffer

        if n < 0
          raise ArgumentError, "Can't encode negative number #{n}."
        end

        while n > 0
          v << (n & 0xFF)
          n >>= 8
        end

        v.reverse!
      end

      def vlb_decode(s)
        n = 0
        s.each_byte { |b| n <<= 8; n += b }
        n
      end

      # Byte strings lexicographically goedelized as numbers (one+256 coding)
      def o256_encode(num)
        str = empty_buffer

        while num > 0
          num -= 1
          str << (num & 0xFF)
          num >>= 8
        end

        str.reverse
      end

      def o256_decode(str)
        num = 0

        str.each_byte do |b|
          num <<= 8
          num += b + 1
        end

        num
      end

      # n must be 2**k
      # Returns k
      def number_of_bits_up_to(n)
#       Math.frexp(n-1)[1]
        Math.log2(n).floor
      end

      def scheme_and_authority_encode(host, port)
        unless host =~ /\[.*\]/
          host = "[#{host}]" if host =~ /:/
        end

        port = Integer(port)

        scheme_and_authority = "coap://#{host}"
        scheme_and_authority << ":#{port}" unless port == CoAP::PORT
        scheme_and_authority
      end

      def scheme_and_authority_decode(s)
        if s =~ %r{\A(?:coap://)((?:\[|%5B)([^\]]*)(?:\]|%5D)|([^:/]*))(:(\d+))?(/.*)?\z}i
          host = $2 || $3       # Should check syntax...
          port = $5 || CoAP::PORT
          [$6, host, port.to_i]
        end
      end

      def path_encode(uri_elements)
        return '/' if uri_elements.nil? || uri_elements.empty?
        '/' + uri_elements.map { |el| uri_encode_element(el, PATH_ENCODED_RE) }.join('/')
      end

      def query_encode(query_elements)
        return '' if query_elements.nil? || query_elements.empty?
        '?' + query_elements.map { |el| uri_encode_element(el, QUERY_ENCODED_RE) }.join('&')
      end

      def uri_encode_element(el, re)
        el.dup.force_encoding(BIN).gsub(re) { |x| "%%%02X" % x.ord }
      end

      def percent_decode(el)
        el.gsub(/%(..)/) { $1.to_i(16).chr(BIN) }.force_encoding(UTF8)
      end

      def path_decode(path)
        # Needs -1 to avoid eating trailing slashes!
        a = path.split('/', -1) 

        return a if a.empty?

        if a[0] != ''
          raise ArgumentError, "Path #{path.inspect} not starting with /"
        end

        # Special case for '/'
        return [] if a[1] == '' 

        a[1..-1].map { |el| percent_decode(el) }
      end

      def query_decode(query)
        return [] if query.empty?

        query = query[1..-1] if query[0] == '?'

        a = query.split('&', -1).map do |el|
          el.gsub(/%(..)/) { $1.to_i(16).chr(BIN) }.force_encoding(UTF8)
        end

        a
      end
    end
  end
end