'example.com' == 'example.com.' => false... is this intended?

Hi Tanaka,

I don't understand why DNS::Name#== requires both to be absolute if one
is.

Is this really necessary/useful? It surprises me.

Also, I have a set of comparison operations for Resolv::DNS::Name.

I copied the style (and docs) from Module/hierarchy comparisons, because
I think there is some similarity.

Comments?

If you will accept, I will send patch and changelog.

Thanks,
Sam

Below is implementation, followed by unit test so you can see behaviour.

    # DNS names are hierarchical in a similar sense to ruby classes/modules, and the
    # comparison operators are defined similarly to those of Module. A name is
    # +<+ another if it is a subdomain.
    # www.example.com < example.com # -> true
    # example.com < example.com # -> false
    # example.com <= example.com # -> true
    # com < example.com # -> false
    # bar.com < example.com # -> nil

···

#
    # Note that #== does not consider two names equal if they differ in whether
    # they are #absolute?, but #equal? considers only the label when comparing
    # names.
    class Name
      def inspect
        n = to_s
        n << '.' if absolute?
        return n
      end

      def equal?(name)
        n = Name.create(name)

        @labels == n.to_a
      end

      def related?(name)
        n = Name.create(name)

        l = length < n.length ? length : n.length

        @labels[-l, l] == n.to_a[-l, l]
      end

      def lt?(name)
        n = Name.create(name)
        length > n.length && to_a[-n.length, n.length] == n.to_a
      end

      # Summary:
      # name < other => true, false, or nil
      #
      # Returns true if +name+ is a subdomain of +other+. Returns
      # <code>nil</code> if there's no relationship between the two.
      def <(name)
        n = Name.create(name)

        return nil unless self.related?(n)

        lt?(n)
      end

      # Summary:
      # name > other => true, false, or nil
      #
      # Same as +other < name+, see #<.
      def >(name)
        n = Name.create(name)

        n < self
      end

      # Summary:
      # name <= other => true, false, or nil
      #
      # Returns true if +name+ is a subdomain of +other+ or is the same as
      # +other+. Returns <code>nil</code> if there's no relationship between
      # the two.
      def <=(name)
        n = Name.create(name)
        self.equal?(n) || self < n
      end

      # Summary:
      # name >= other => true, false, or nil
      #
      # Returns true if +name+ is an ancestor of +other+, or the two DNS names
      # are the same. Returns <code>nil</code> if there's no relationship
      # between the two.
      def >=(name)
        n = Name.create(name)
        self.equal?(n) || self > n
      end

      # Summary:
      # name <=> other => -1, 0, +1, nil
      #
      # Returns -1 if +name+ is a subdomain of +other+, 0 if
      # +name+ is the same as +other+, and +1 if +other+ is a subdomain of
      # +name+, or nil if +name+ has no relationship with +other+.
      def <=>(name)
        n = Name.create(name)

        return nil unless self.related?(n)

        return -1 if self.lt?(n)
        return +1 if n.lt?(self)
        # must be #equal?
        return 0
      end

    end

require 'test/unit'

Name = Resolv::DNS::Name

class TestDnsName < Test::Unit::TestCase

  def test_what_I_think_are_odd_behaviours
    # Why can't test against strings?
    assert_equal(false, Name.create("example.CoM") == "example.com")
    assert_equal(false, Name.create("example.CoM").eql?("example.com"))

    # Why does making it absolute mean they aren't equal?
    assert_equal(false, Name.create("example.CoM").eql?(Name.create("example.com.")))
    assert_equal(false, Name.create("example.CoM") == Name.create("example.com."))
  end

  def test_CoMparisons

    assert_equal(true, Name.create("example.CoM").eql?(Name.create("example.com")))
    assert_equal(true, Name.create("example.CoM") == Name.create("example.com"))

    assert_equal(true, Name.create("example.CoM").equal?("example.com."))
    assert_equal(true, Name.create("example.CoM").equal?("example.com"))

    assert_equal(true, Name.create("www.example.CoM") < "example.com")
    assert_equal(true, Name.create("www.example.CoM") <= "example.com")
    assert_equal(-1, Name.create("www.example.CoM") <=> "example.com")
    assert_equal(false, Name.create("www.example.CoM") >= "example.com")
    assert_equal(false, Name.create("www.example.CoM") > "example.com")

    assert_equal(false, Name.create("example.CoM") < "example.com")
    assert_equal(true, Name.create("example.CoM") <= "example.com")
    assert_equal(0, Name.create("example.CoM") <=> "example.com")
    assert_equal(true, Name.create("example.CoM") >= "example.com")
    assert_equal(false, Name.create("example.CoM") > "example.com")

    assert_equal(false, Name.create("CoM") < "example.com")
    assert_equal(false, Name.create("CoM") <= "example.com")
    assert_equal(+1, Name.create("CoM") <=> "example.com")
    assert_equal(true, Name.create("CoM") >= "example.com")
    assert_equal(true, Name.create("CoM") > "example.com")

    assert_equal(nil, Name.create("bar.CoM") < "example.com")
    assert_equal(nil, Name.create("bar.CoM") <= "example.com")
    assert_equal(nil, Name.create("bar.CoM") <=> "example.com")
    assert_equal(nil, Name.create("bar.CoM") >= "example.com")
    assert_equal(nil, Name.create("bar.CoM") > "example.com")

    assert_equal(nil, Name.create("net.") < "com")
    assert_equal(nil, Name.create("net.") <= "com")
    assert_equal(nil, Name.create("net.") <=> "com")
    assert_equal(nil, Name.create("net.") >= "com")
    assert_equal(nil, Name.create("net.") > "com")

  end
end

In article <20050131023128.GB13952@ensemble.local>,
  Sam Roberts <sroberts@uniserve.com> writes:

I don't understand why DNS::Name#== requires both to be absolute if one
is.

Is this really necessary/useful? It surprises me.

I think it's right behavior.

Also, I have a set of comparison operations for Resolv::DNS::Name.

I copied the style (and docs) from Module/hierarchy comparisons, because
I think there is some similarity.

Comments?

I'm not sure that they are used frequently enough to occupy comparison
operators.

···

--
Tanaka Akira

Quoteing akr@m17n.org, on Fri, Feb 04, 2005 at 01:25:20PM +0900:

In article <20050131023128.GB13952@ensemble.local>,
  Sam Roberts <sroberts@uniserve.com> writes:

> I don't understand why DNS::Name#== requires both to be absolute if one
> is.
>
> Is this really necessary/useful? It surprises me.

I think it's right behavior.

Why? When are they not the same?

They are equivalent in the DNS, Resolv::DNS#getaddress() would return
the same A record for both.

> Also, I have a set of comparison operations for Resolv::DNS::Name.

I'm not sure that they are used frequently enough to occupy comparison
operators.

Name doesn't have any comparison operators, so they occupy empty space.

Do you have another idea on what Name < Name could mean?

I found it necessary to find whether a Name was a sub-domain of another.
How would you recommend to do this, if there is no method?

  lhs.length > rhs.length && lhs.to_a[-rhs.length, rhs.length] == rhs.to_a

is the best I came up with, and its not the kind of thing I would want
to type regularly.

Cheers,
Sam

In article <20050204052951.GA2531@ensemble.local>,
  Sam Roberts <sroberts@uniserve.com> writes:

Why? When are they not the same?

For example, if you have a machine named "museum", it is confusing with
"museum." domain.

They are equivalent in the DNS, Resolv::DNS#getaddress() would return
the same A record for both.

There are several top-domains which have A record: ac, museum, etc.
If your local domain have a machine named "ac", it is important to
distinguish a relative "ac" domain and the absolute "ac" domain.

Name doesn't have any comparison operators, so they occupy empty space.

Do you have another idea on what Name < Name could mean?

For example, lexical order.

I found it necessary to find whether a Name was a sub-domain of another.
How would you recommend to do this, if there is no method?

  lhs.length > rhs.length && lhs.to_a[-rhs.length, rhs.length] == rhs.to_a

is the best I came up with, and its not the kind of thing I would want
to type regularly.

Adding some method (not operator) is acceptable if it has a good name.

···

--
Tanaka Akira

"example.com" is a relative domain name. Depending on your
resolver/nameserver's configuration, I believe it could resolve to
"example.com.DEFAULTDOMAIN." which wouldn't be the same as
"example.com."

Cheers,
Navin.

···

Sam Roberts <sroberts@uniserve.com> wrote:

> > I don't understand why DNS::Name#== requires both to be absolute if one
> > is.
> >
> > Is this really necessary/useful? It surprises me.
>
> I think it's right behavior.

Why? When are they not the same?

They are equivalent in the DNS, Resolv::DNS#getaddress() would return
the same A record for both.

Quoteing akr@m17n.org, on Fri, Feb 04, 2005 at 05:02:45PM +0900:

In article <20050204052951.GA2531@ensemble.local>,
  Sam Roberts <sroberts@uniserve.com> writes:

> Why? When are they not the same?

For example, if you have a machine named "museum", it is confusing with
"museum." domain.

> They are equivalent in the DNS, Resolv::DNS#getaddress() would return
> the same A record for both.

There are several top-domains which have A record: ac, museum, etc.
If your local domain have a machine named "ac", it is important to
distinguish a relative "ac" domain and the absolute "ac" domain.

You only sometimes distinguish between the two, sometimes you convert
them to each other:

   >> Name = Resolv::DNS::Name
   => Resolv::DNS::Name
   >> Name.create('museum') == Name.create('museum')
   => true
   # But, wouldn't this depend on the location? On my net, museum would
   # be museum.local.
   >> Name.create('museum') == Name.create('museum.')
   => false
   >> Name.create('museum').to_s == Name.create('museum.').to_s
   => true
   # Isn't it important to distinguish?

   n0=Resolv::DNS.new.getname("193.108.154.9")

   n1=Resolv.getname("193.108.154.9")
     => "a193-108-154-9.deploy.akamaitechnologies.com"
   # note it isn't absolute, though the DNS response was

   n0 == n1
    => in `==': undefined method `absolute?' for "com":String (NoMethodError)
   # I have to change my String to a Name to compare it? Irritating.

   n1 = Resolv::DNS::Name.create(n0)
   n0 == n1
    => false
   # DNS returns absolute names
   n2 = Resolv::DNS::Name.create(n0.to_s)
   n0 == n2
    => false
   n0.to_s == n2.to_s
    => true

Maybe you haven't seen how unuseful the behaviour of == is because you
use it inside resolv.rb, and all names you see are absolute?

Outside of resolv.rb (including input/output values of the common Resolv
class methods) names are non-absolute strings, and the DNS Name objects
can't be compared to them.

> Name doesn't have any comparison operators, so they occupy empty space.
>
> Do you have another idea on what Name < Name could mean?

For example, lexical order.

> I found it necessary to find whether a Name was a sub-domain of another.
> How would you recommend to do this, if there is no method?
>
> lhs.length > rhs.length && lhs.to_a[-rhs.length, rhs.length] == rhs.to_a
>
> is the best I came up with, and its not the kind of thing I would want
> to type regularly.

Adding some method (not operator) is acceptable if it has a good name.

The code I posted included the method #lt?, you could include it as is,
or change its name. For consistency with #eql?, you may want to make it
NOT convert its argument from String to Name, or you may want to change
both to allow comparison to String.

def lt?(name)
  n = Name.create(name) # maybe remove?
  length > n.length && to_a[-n.length, n.length] == n.to_a
end

def ==(other)
  other = Name.create(other) # maybe add?
  return @labels == other.to_a && @absolute == other.absolute?
end
alias eql? ==

Cheers,
Sam

The attached patch fixes this.

Note well that it does it in such a way that it is backwards compatible, but
also allows callers to see the strings as an array. I need this.

RFC1035 says "semantics of the text depends on the domain where it is found",
and the DNS-SD draft says mDNS TXT responses contain key/value pairs, where
each key/value pair is a <character-string> from the TXT rdata.

RFC1035:

  3.3.14. TXT RDATA format

···

+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
      / TXT-DATA /
      +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

  where:

  TXT-DATA One or more <character-string>s.

  TXT RRs are used to hold descriptive text. The semantics of the text
  depends on the domain where it is found.

Reproduce this with packet collected from wild:

  require 'resolv.rb'
  require 'pp'

  data = "\000\000\000\000\000\003\000\003\000\000\000\000\025Sam Roberts\342\200\231s Music\005_daap\004_tcp\005local\000\000!\000\001\300\f\000\020\000\001\300\"\000\f\000\001\300\f\000!\000\001\000\000\000;\000\021\000\000\000\000\016i\010ensemble\300-\300\f\000\020\000\001\000\000\000;\000\224\ttxtvers=1\016Version=196608\023iTSh Version=131073\027Machine ID=9C5AC2725708\034Database ID=681233690CC1C418\"Machine Name=Sam Roberts\342\200\231s Music\016Password=false\300\"\000\f\000\001\000\000\034\037\000\002\300\f"

  msg = Resolv::DNS::Message.decode(data)

  pp msg

  txtanswer = msg.answer[1]
  txtrr = txtanswer[2]

  pp txtrr.data
  pp txtrr.datas

Changelog:

  resolv.rb dies on TXT records with multiple strings

Patch:

Index: resolv.rb

RCS file: /src/ruby/lib/resolv.rb,v
retrieving revision 1.17.2.8
diff -u -r1.17.2.8 resolv.rb
--- resolv.rb 29 Jan 2005 05:22:35 -0000 1.17.2.8
+++ resolv.rb 5 Feb 2005 03:11:52 -0000
@@ -1266,6 +1266,14 @@
           return d
         end

+ def get_strings
+ strings = []
+ until @index == @limit
+ strings << get_string
+ end
+ strings
+ end
+
         def get_name
           return Name.new(self.get_labels)
         end
@@ -1511,14 +1519,21 @@
         def initialize(data)
           @data = data
         end
- attr_reader :data
+
+ def data
+ @data.join
+ end
+
+ def datas
+ @data
+ end

         def encode_rdata(msg)
           msg.put_string(@data)
         end

         def self.decode_rdata(msg)
- data = msg.get_string
+ data = msg.get_strings
           return self.new(data)
         end
       end

In article <20050204150754.GA2662@ensemble.local>,
  Sam Roberts <sroberts@uniserve.com> writes:

Maybe you haven't seen how unuseful the behaviour of == is because you
use it inside resolv.rb, and all names you see are absolute?

Outside of resolv.rb (including input/output values of the common Resolv
class methods) names are non-absolute strings, and the DNS Name objects
can't be compared to them.

It is caused by the absolute/relative difference is not cared outside
of a resolver. The unusefulness comes from you need supply omitted
absoluteness information for conversion from string to name, and
Resolv::DNS::Name#to_s omit the absoluteness information.

I believe Resolv::DNS::Name's absoluteness sensitive behavior prevents
a kind of bugs. So I don't want to introduce absoluteness insensitive
behavour such as your comparison.

Adding some conversion methods may be a solution. I don't have
concrete idea, though.

The code I posted included the method #lt?, you could include it as is,
or change its name. For consistency with #eql?, you may want to make it
NOT convert its argument from String to Name, or you may want to change
both to allow comparison to String.

I don't think lt? is a good name.
Because it may mean comparison in lexical order and other orders.

···

--
Tanaka Akira

In article <20050205032049.GA13619@ensemble.local>,
  Sam Roberts <sroberts@uniserve.com> writes:

The attached patch fixes this.

Note well that it does it in such a way that it is backwards compatible, but
also allows callers to see the strings as an array. I need this.

RFC1035 says "semantics of the text depends on the domain where it is found",
and the DNS-SD draft says mDNS TXT responses contain key/value pairs, where
each key/value pair is a <character-string> from the TXT rdata.

It is fixed based on your patch. Thank you.

Note that I added TXT#strings to retrieve all strings, instead of
TXT#datas.

···

--
Tanaka Akira

Quoteing akr@m17n.org, on Sat, Feb 05, 2005 at 01:27:35PM +0900:

> The code I posted included the method #lt?, you could include it as is,
> or change its name. For consistency with #eql?, you may want to make it
> NOT convert its argument from String to Name, or you may want to change
> both to allow comparison to String.

I don't think lt? is a good name.
Because it may mean comparison in lexical order and other orders.

Perhaps #subdomain?

.sam

Quoteing akr@m17n.org, on Sun, Feb 06, 2005 at 03:36:04AM +0900:

In article <20050205032049.GA13619@ensemble.local>,
It is fixed based on your patch. Thank you.

Note that I added TXT#strings to retrieve all strings, instead of
TXT#datas.

I am happy with that.

Thanks,
Sam

In article <20050205145144.GA13678@ensemble.local>,
  Sam Roberts <sroberts@uniserve.com> writes:

Perhaps #subdomain?

The term "subdomain" is good.

But I feel two possibile meanings with A.subdomain?(B) :

* A is a subdomain of B
* B is a subdomain of A

Is it clear for native speakers?

···

--
Tanaka Akira

Quoteing akr@m17n.org, on Sun, Feb 06, 2005 at 04:55:36AM +0900:

In article <20050205145144.GA13678@ensemble.local>,
  Sam Roberts <sroberts@uniserve.com> writes:

> Perhaps #subdomain?

The term "subdomain" is good.

But I feel two possibile meanings with A.subdomain?(B) :

* A is a subdomain of B
* B is a subdomain of A

Is it clear for native speakers?

Native speakers are as confused by their language as everybody else.

#subdomainof?

would make it completely clear.

Sam

Might I suggest #include? instead? Thus, (using <> strings to
represent instances of the class under discussion):

  <.ca>.include?(<halostatue.ca>) # => true
  <halostatue.ca>.include?(<www.halostatue.ca>) # => true

But, I don't use this functionality at all, so you can safely ignore
my suggestion here.

-austin

···

On Sun, 6 Feb 2005 04:55:36 +0900, Tanaka Akira <akr@m17n.org> wrote:

In article <20050205145144.GA13678@ensemble.local >,
  Sam Roberts <sroberts@uniserve.com > writes:
> Perhaps #subdomain?
The term "subdomain" is good.

But I feel two possibile meanings with A.subdomain?(B) :

* A is a subdomain of B
* B is a subdomain of A

Is it clear for native speakers?

--
Austin Ziegler * halostatue@gmail.com
               * Alternate: austin@halostatue.ca

In article <20050205214007.GA13730@ensemble.local>,
  Sam Roberts <sroberts@uniserve.com> writes:

#subdomainof?

would make it completely clear.

subdomain_of? and inspect is implemented.

I inserted a underscore to consistent with kind_of?.

···

--
Tanaka Akira

Quoteing akr@m17n.org, on Tue, Feb 08, 2005 at 12:26:36AM +0900:

In article <20050205214007.GA13730@ensemble.local>,
  Sam Roberts <sroberts@uniserve.com> writes:

> #subdomainof?
>
> would make it completely clear.

subdomain_of? and inspect is implemented.

Thank you.

Sam