Showing posts with label jruby. Show all posts
Showing posts with label jruby. Show all posts

Sunday, June 03, 2007

Ruby vs. JRuby - The path to consistent behavior + OpenSSL

In Ola Bini's recent post There can be only one, a tale about Ruby, IronRuby, MS and whatnot, he stresses the importance of documenting Ruby's API so that other implementations can match MRI's behavior. I have to strongly agree. We've recently been bitten by this, which I'll outline using OpenSSL as an example.

Ruby's OpenSSL library does not strictly enforce a ciphers's initialization vector (IV) size specification. It simply truncates to the appropriate size.

Ola's JRuby OpenSSL library (which is awesome btw!) leverages the Bouncy Castle's JCE implementation which strictly enforces IV specifications.

Below is a simple example that works fine on MRI, and fails on JRuby.

require 'openssl'
text = "abc123"
cipher = OpenSSL::Cipher::Cipher.new("des-ecb")

#The DES Cipher specifies a length of 8 bytes, the below IV exceeds that
key, iv = "1234567890", "1234567890"

cipher.encrypt
cipher.key, cipher.iv = key, iv
encrypted = cipher.update(text) << cipher.final

cipher.reset
cipher.decrypt

#I double the length of the cipher here, just to prove that MRI truncates it.
cipher.key, cipher.iv = key*2, iv*2
decrypted = cipher.update(encrypted) << cipher.final
puts "#{text} == #{decrypted}"



> jruby test_openssl_iv.rb
java.security.InvalidKeyException: DES key too long - should be 8 bytes
at org.bouncycastle.jce.provider.JCEBlockCipher.engineInit(Unknown Source)
at org.bouncycastle.jce.provider.JCEBlockCipher.engineInit(Unknown Source)
at javax.crypto.Cipher.init(DashoA12275)
at javax.crypto.Cipher.init(DashoA12275)
at org.jruby.ext.openssl.Cipher.doInitialize(Cipher.java:416)
at org.jruby.ext.openssl.Cipher.update(Cipher.java:432)

> ruby test_openssl_iv.rb 
Result is: abc123 == abc123



Yes, this is obviously an edge case, and one could argue that the Java implementation is more correct than MRI's, but the goal is any piece of code should run the same on either platform.

Charles Nutter (Headius) has started a RubySpec Wiki where I've noted this issue there.

I've submitted a bug against JRuby to track the issue, but arguably its not a JRuby bug. Its a difference between BouncyCastle and SSLeay's implementation. I'd be interested in some community feedback on how to correctly implement this. For now, we've worked around this by simply truncating if the RUBY_PLATFORM =~ /java/, which I know if horrible, but it allows me to move on.

Monday, May 28, 2007

Ruby-esque JMX -- Part 2

Below is the code from the JMX tinkering. I snake-ized the keys to be more Ruby-esque, per Ed's suggestion.

require 'java'
include Java

# Obviously stolen from Rails ActiveSupport for underscoring strings
class String
def underscore
self.to_s.gsub(/::/, '/').
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
gsub(/([a-z\d])([A-Z])/,'\1_\2').
tr("-", "_").
downcase
end
end

module JMX
class MBean
include_class 'javax.management.ObjectName'
include_package "java.lang.management"
include_package "javax.management"
include_package "javax.management.remote"
include_class "java.util.HashMap"
attr_accessor :name
def initialize(object_name)
@object_name = object_name
@name = @object_name.to_s
end

def attributes
attrs = MBean.connection.getMBeanInfo(@object_name).attributes rescue []
attrs.inject({}) do |list, a|
list[a.name.underscore] = MBean.connection.getAttribute(@object_name, "#{a.name}") rescue "Unknown"
list
end
end

def self.find_all_by_name(name)
object_name = ObjectName.new(name)
beans = MBean.connection.queryMBeans(object_name,nil )
beans.collect {|bean| MBean.new(bean.get_object_name)}
end

def self.find_by_name(name)
#obviously inefficient
find_all_by_name(name).first
end

def self.connection
@@mbsc ||= begin
#load from some config file later maybe
url = "service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi"
connector = JMXConnectorFactory::connect(JMXServiceURL.new(url), HashMap.new)
connector.getMBeanServerConnection
end
end
protected

def method_missing(method, *args, &block)
attributes.keys.include?(method.to_s) ? attributes[method.to_s] : super
end
end
end

#Find all the MBeans matching some object name
mbeans = JMX::MBean.find_all_by_name("java.lang:*")
puts "Found #{mbeans.size} beans"

#Find a bean, and get its attribute
bean = JMX::MBean.find_by_name("java.lang:type=ClassLoading")
puts "There are #{bean.loaded_class_count} classes loaded in that VM"

Friday, May 25, 2007

Ruby-esque JMX

The topic of JMX on JRuby came up recently and I decided to play around. I found a great starter on Jeff Mesnil's blog, but I decided I hated the syntax.

Ruby has spoiled me. ActiveRecord has spoiled me.

So I cooked up this little (fully working) example:

#Find all the MBeans matching some object name
mbeans = JMX::MBean.find_all_by_name("cacheStatistics:*")

mbeans.each do |bean|
puts "#{bean.name} "

#Either use methods on the bean object
puts " - CacheHits: #{bean.CacheHits}"

#Or access the attributes hash.
puts " - CacheMisses: #{bean.attributes["CacheHits"]}"
end


The code is ~50lines which I'll post at some point.

I never thought working with java objects could be made to "feel" nice.

I was also chatting with "headius" on #jruby, and he mentioned that Rob Harrop, of Spring fame, had a talk at JavaOne about about something similar called MScript. I'd love to get my hands on those slides.