tags:

views:

136

answers:

2

I'm taking 5 strings (protocol, source IP and port, destination IP and port) and using them to store some values in a hash. The problem is that if the IPs or ports are switched between source and destination, the key is supposed to be the same.

If I was doing this in C#/Java/whatever I'd have to create a new class and overwrite the hashcode()/equals() methods, but that seems error prone from the little I've read about it and I was wondering if there would be a better alternative here.

A: 

I am directly copying a paragraph from Programming Ruby 1.9:

Hash keys must respond to the message hash by returning a hash code, and the hash code for a given key must not change. The keys used in hashes must also be comparable using eql?. If eql? returns true for two keys, then those keys must also have the same hash code. This means that certain classes (such as Array and Hash) can't conveniently be used as keys, because their hash values can change based on their contents.

So you might generate your hash as something like ["#{source_ip} #{source_port}", "#{dest_ip} #{dest_port}", protocol.to_s].sort.join.hash such that the result will be identical when the source and destination are switched.

For example:

source_ip = "1.2.3.4"
source_port = 1234

dest_ip = "5.6.7.8"
dest_port = 5678

protocol = "http"

def make_hash(s_ip, s_port, d_ip, d_port, proto)
    ["#{s_ip} #{s_port}", "#{d_ip} #{d_port}", proto.to_s].sort.join.hash
end

puts make_hash(source_ip, source_port, dest_ip, dest_port, protocol)
puts make_hash(dest_ip, dest_port, source_ip, source_port, protocol)

This will output the same hash even though the arguments are in a different order between the two calls. Correctly encapsulating this functionality into a class is left as an exercise to the reader.

Mark Rushakoff
I don't understand. I'm using strings, and that only mentions arrays and hashes. Though I'm new to Ruby. Are strings arrays? Or is it something else I'm missing?
zxcvbnm
Th code fragment `["#{source_ip} #{source_port}", "#{dest_ip} #{dest_port}", protocol.to_s].sort.join` creates a temporary array, then sorts it (which allows source and dest to be swapped), then `join`s it into a string. Finally calling `hash` on that string should suffice for use in a hash.
Mark Rushakoff
Ah, I see. That's what I needed. Thank you.
zxcvbnm
A: 

I think this is what you mean...

irb(main):001:0> traffic = []
=> []
irb(main):002:0> traffic << {:src_ip => "10.0.0.1", :src_port => "9999", :dst_ip => "172.16.1.1", :dst_port => 80, :protocol => "tcp"}
=> [{:protocol=>"tcp", :src_ip=>"10.0.0.1", :src_port=>"9999", :dst_ip=>"172.16.1.1", :dst_port=>80}]
irb(main):003:0> traffic << {:src_ip => "10.0.0.2", :src_port => "9999", :dst_ip => "172.16.1.1", :dst_port => 80, :protocol => "tcp"}
=> [{:protocol=>"tcp", :src_ip=>"10.0.0.1", :src_port=>"9999", :dst_ip=>"172.16.1.1", :dst_port=>80}, {:protocol=>"tcp", :src_ip=>"10.0.0.2", :src_port=>"9999", :dst_ip=>"172.16.1.1", :dst_port=>80}]

The next, somewhat related, question is how to store the IP. You probably want to use the IPAddr object instead of just a string so you can sort the results more easily.

Dan McNevin