require "spec"
require "socket"
require "../../support/win32"

describe Socket::Address do
  describe ".parse" do
    it "accepts URI" do
      address = Socket::Address.parse URI.parse("tcp://192.168.0.1:8081")
      address.should eq Socket::IPAddress.new("192.168.0.1", 8081)
    end

    it "parses TCP" do
      address = Socket::Address.parse "tcp://192.168.0.1:8081"
      address.should eq Socket::IPAddress.new("192.168.0.1", 8081)
    end

    it "parses UDP" do
      address = Socket::Address.parse "udp://192.168.0.1:8081"
      address.should eq Socket::IPAddress.new("192.168.0.1", 8081)
    end

    pending_win32 "parses UNIX" do
      address = Socket::Address.parse "unix://socket.sock"
      address.should eq Socket::UNIXAddress.new("socket.sock")
    end

    it "fails with unknown scheme" do
      expect_raises(Socket::Error, "Unsupported address type: ssl") do
        Socket::Address.parse "ssl://192.168.0.1:8081"
      end
    end
  end
end

describe Socket::IPAddress do
  it "transforms an IPv4 address into a C struct and back" do
    addr1 = Socket::IPAddress.new("127.0.0.1", 8080)
    addr2 = Socket::IPAddress.from(addr1.to_unsafe, addr1.size)

    addr2.family.should eq(addr1.family)
    addr2.port.should eq(addr1.port)
    typeof(addr2.address).should eq(String)
    addr2.address.should eq(addr1.address)
  end

  it "transforms an IPv6 address into a C struct and back" do
    addr1 = Socket::IPAddress.new("2001:db8:8714:3a90::12", 8080)
    addr2 = Socket::IPAddress.from(addr1.to_unsafe, addr1.size)

    addr2.family.should eq(addr1.family)
    addr2.port.should eq(addr1.port)
    typeof(addr2.address).should eq(String)
    addr2.address.should eq(addr1.address)
  end

  it "won't resolve domains" do
    expect_raises(Socket::Error, /Invalid IP address/) do
      Socket::IPAddress.new("localhost", 1234)
    end
  end

  it "to_s" do
    Socket::IPAddress.new("127.0.0.1", 80).to_s.should eq("127.0.0.1:80")
    Socket::IPAddress.new("2001:db8:8714:3a90::12", 443).to_s.should eq("[2001:db8:8714:3a90::12]:443")
  end

  describe ".parse" do
    it "parses IPv4" do
      address = Socket::IPAddress.parse "ip://192.168.0.1:8081"
      address.should eq Socket::IPAddress.new("192.168.0.1", 8081)
    end

    it "parses IPv6" do
      address = Socket::IPAddress.parse "ip://[::1]:8081"
      address.should eq Socket::IPAddress.new("::1", 8081)
    end

    it "fails host name" do
      expect_raises(Socket::Error, "Invalid IP address: example.com") do
        Socket::IPAddress.parse "ip://example.com:8081"
      end
    end

    it "ignores path and params" do
      address = Socket::IPAddress.parse "ip://192.168.0.1:8081/foo?bar=baz"
      address.should eq Socket::IPAddress.new("192.168.0.1", 8081)
    end

    it "fails with missing host" do
      expect_raises(Socket::Error, "Invalid IP address: missing host") do
        Socket::IPAddress.parse "ip:///path"
      end
    end

    it "fails with missing port" do
      expect_raises(Socket::Error, "Invalid IP address: missing port") do
        Socket::IPAddress.parse "ip://127.0.0.1"
      end
    end
  end

  it "#loopback?" do
    Socket::IPAddress.new("127.0.0.1", 0).loopback?.should be_true
    Socket::IPAddress.new("127.255.255.254", 0).loopback?.should be_true
    Socket::IPAddress.new("128.0.0.1", 0).loopback?.should be_false
    Socket::IPAddress.new("0.0.0.0", 0).loopback?.should be_false
    Socket::IPAddress.new("::1", 0).loopback?.should be_true
    Socket::IPAddress.new("0000:0000:0000:0000:0000:0000:0000:0001", 0).loopback?.should be_true
    Socket::IPAddress.new("::2", 0).loopback?.should be_false
    Socket::IPAddress.new(Socket::IPAddress::LOOPBACK, 0).loopback?.should be_true
    Socket::IPAddress.new(Socket::IPAddress::LOOPBACK6, 0).loopback?.should be_true
  end

  it "#unspecified?" do
    Socket::IPAddress.new("0.0.0.0", 0).unspecified?.should be_true
    Socket::IPAddress.new("127.0.0.1", 0).unspecified?.should be_false
    Socket::IPAddress.new("::", 0).unspecified?.should be_true
    Socket::IPAddress.new("0000:0000:0000:0000:0000:0000:0000:0000", 0).unspecified?.should be_true
    Socket::IPAddress.new(Socket::IPAddress::UNSPECIFIED, 0).unspecified?.should be_true
    Socket::IPAddress.new(Socket::IPAddress::UNSPECIFIED6, 0).unspecified?.should be_true
  end

  it ".valid_port?" do
    Socket::IPAddress.valid_port?(0).should be_true
    Socket::IPAddress.valid_port?(80).should be_true
    Socket::IPAddress.valid_port?(65_535).should be_true

    Socket::IPAddress.valid_port?(-1).should be_false
    Socket::IPAddress.valid_port?(65_536).should be_false
  end

  it "#private?" do
    Socket::IPAddress.new("192.168.0.1", 0).private?.should be_true
    Socket::IPAddress.new("192.100.0.1", 0).private?.should be_false
    Socket::IPAddress.new("172.16.0.1", 0).private?.should be_true
    Socket::IPAddress.new("172.10.0.1", 0).private?.should be_false
    Socket::IPAddress.new("10.0.0.1", 0).private?.should be_true
    Socket::IPAddress.new("1.1.1.1", 0).private?.should be_false
    Socket::IPAddress.new("fd00::1", 0).private?.should be_true
    Socket::IPAddress.new("fb00::1", 0).private?.should be_false
    Socket::IPAddress.new("2001:4860:4860::8888", 0).private?.should be_false
  end

  it "#==" do
    Socket::IPAddress.new("127.0.0.1", 8080).should eq Socket::IPAddress.new("127.0.0.1", 8080)
    Socket::IPAddress.new("127.0.0.1", 8080).hash.should eq Socket::IPAddress.new("127.0.0.1", 8080).hash

    Socket::IPAddress.new("127.0.0.1", 8080).should_not eq Socket::IPAddress.new("127.0.0.1", 8081)
    Socket::IPAddress.new("127.0.0.1", 8080).hash.should_not eq Socket::IPAddress.new("127.0.0.1", 8081).hash

    Socket::IPAddress.new("127.0.0.1", 8080).should_not eq Socket::IPAddress.new("127.0.0.2", 8080)
    Socket::IPAddress.new("127.0.0.1", 8080).hash.should_not eq Socket::IPAddress.new("127.0.0.2", 8080).hash
  end
end

{% if flag?(:unix) %}
  describe Socket::UNIXAddress do
    it "transforms into a C struct and back" do
      path = "unix_address.sock"

      addr1 = Socket::UNIXAddress.new(path)
      addr2 = Socket::UNIXAddress.from(addr1.to_unsafe, addr1.size)

      addr2.family.should eq(addr1.family)
      addr2.path.should eq(addr1.path)
      addr2.to_s.should eq(path)
    end

    it "raises when path is too long" do
      path = "unix_address-too-long-#{("a" * 2048)}.sock"

      expect_raises(ArgumentError, "Path size exceeds the maximum size") do
        Socket::UNIXAddress.new(path)
      end
    end

    it "to_s" do
      Socket::UNIXAddress.new("some_path").to_s.should eq("some_path")
    end

    it "#==" do
      Socket::UNIXAddress.new("some_path").should eq Socket::UNIXAddress.new("some_path")
      Socket::UNIXAddress.new("some_path").hash.should eq Socket::UNIXAddress.new("some_path").hash

      Socket::UNIXAddress.new("some_path").should_not eq Socket::UNIXAddress.new("other_path")
      Socket::UNIXAddress.new("some_path").hash.should_not eq Socket::UNIXAddress.new("other_path").hash
    end

    describe ".parse" do
      it "parses relative" do
        address = Socket::UNIXAddress.parse "unix://foo.sock"
        address.should eq Socket::UNIXAddress.new("foo.sock")
      end

      it "parses relative subpath" do
        address = Socket::UNIXAddress.parse "unix://foo/bar.sock"
        address.should eq Socket::UNIXAddress.new("foo/bar.sock")
      end

      it "parses relative dot" do
        address = Socket::UNIXAddress.parse "unix://./bar.sock"
        address.should eq Socket::UNIXAddress.new("./bar.sock")
      end

      it "relative with" do
        address = Socket::UNIXAddress.parse "unix://foo:21/bar.sock"
        address.should eq Socket::UNIXAddress.new("foo:21/bar.sock")
      end

      it "parses absolute" do
        address = Socket::UNIXAddress.parse "unix:///foo.sock"
        address.should eq Socket::UNIXAddress.new("/foo.sock")
      end

      it "ignores params" do
        address = Socket::UNIXAddress.parse "unix:///foo.sock?bar=baz"
        address.should eq Socket::UNIXAddress.new("/foo.sock")
      end

      it "fails with missing path" do
        expect_raises(Socket::Error, "Invalid UNIX address: missing path") do
          Socket::UNIXAddress.parse "unix://?foo=bar"
        end
      end
    end
  end
{% end %}

describe Socket do
  # Tests from libc-test:
  # http://repo.or.cz/libc-test.git/blob/master:/src/functional/inet_pton.c
  it ".ip?" do
    # dotted-decimal notation
    Socket.ip?("0.0.0.0").should be_true
    Socket.ip?("127.0.0.1").should be_true
    Socket.ip?("10.0.128.31").should be_true
    Socket.ip?("255.255.255.255").should be_true

    # numbers-and-dots notation, but not dotted-decimal
    # Socket.ip?("1.2.03.4").should be_false # fails on darwin
    Socket.ip?("1.2.0x33.4").should be_false
    Socket.ip?("1.2.0XAB.4").should be_false
    Socket.ip?("1.2.0xabcd").should be_false
    Socket.ip?("1.0xabcdef").should be_false
    Socket.ip?("00377.0x0ff.65534").should be_false

    # invalid
    Socket.ip?(".1.2.3").should be_false
    Socket.ip?("1..2.3").should be_false
    Socket.ip?("1.2.3.").should be_false
    Socket.ip?("1.2.3.4.5").should be_false
    Socket.ip?("1.2.3.a").should be_false
    Socket.ip?("1.256.2.3").should be_false
    Socket.ip?("1.2.4294967296.3").should be_false
    Socket.ip?("1.2.-4294967295.3").should be_false
    Socket.ip?("1.2. 3.4").should be_false

    # ipv6
    Socket.ip?(":").should be_false
    Socket.ip?("::").should be_true
    Socket.ip?("::1").should be_true
    Socket.ip?(":::").should be_false
    Socket.ip?(":192.168.1.1").should be_false
    Socket.ip?("::192.168.1.1").should be_true
    Socket.ip?("0:0:0:0:0:0:192.168.1.1").should be_true
    Socket.ip?("0:0::0:0:0:192.168.1.1").should be_true
    # Socket.ip?("::012.34.56.78").should be_false # fails on darwin
    Socket.ip?(":ffff:192.168.1.1").should be_false
    Socket.ip?("::ffff:192.168.1.1").should be_true
    Socket.ip?(".192.168.1.1").should be_false
    Socket.ip?(":.192.168.1.1").should be_false
    Socket.ip?("a:0b:00c:000d:E:F::").should be_true
    # Socket.ip?("a:0b:00c:000d:0000e:f::").should be_false # fails on GNU libc
    Socket.ip?("1:2:3:4:5:6::").should be_true
    Socket.ip?("1:2:3:4:5:6:7::").should be_true
    Socket.ip?("1:2:3:4:5:6:7:8::").should be_false
    Socket.ip?("1:2:3:4:5:6:7::9").should be_false
    Socket.ip?("::1:2:3:4:5:6").should be_true
    {% if flag?(:win32) %}
      Socket.ip?("::1:2:3:4:5:6:7").should be_false
    {% else %}
      Socket.ip?("::1:2:3:4:5:6:7").should be_true
    {% end %}
    Socket.ip?("::1:2:3:4:5:6:7:8").should be_false
    Socket.ip?("a:b::c:d:e:f").should be_true
    Socket.ip?("ffff:c0a8:5e4").should be_false
    Socket.ip?(":ffff:c0a8:5e4").should be_false
    Socket.ip?("0:0:0:0:0:ffff:c0a8:5e4").should be_true
    Socket.ip?("0:0:0:0:ffff:c0a8:5e4").should be_false
    Socket.ip?("0::ffff:c0a8:5e4").should be_true
    Socket.ip?("::0::ffff:c0a8:5e4").should be_false
    Socket.ip?("c0a8").should be_false
  end

  it "==" do
    a = Socket::IPAddress.new("127.0.0.1", 8080)
    b = Socket::UNIXAddress.new("some_path")
    c = "some_path"
    (a == a).should be_true
    (b == b).should be_true
    (a == b).should be_false
    (a == c).should be_false
    (b == c).should be_false
  end
end
