Refactoring Net::SSH: Part 3
This is the next article in a series about the process I am taking as I refactor the Net::SSH library to take advantage of dependency injection. In this installment, I’ll talk about what challenges I have faced as I’ve tried to refactor the “Key Exchange” algorithms, not only to make them “dependency injectable”, but also to make them unit-testable.
There are two different versions of the key exchange algorithm that are supported by Net::SSH. The refactoring process is very similar for both of them, so this will deal with the simpler of the two: Diffie-Hellman Group 1 SHA1.
Refactoring the Buffer Implementations
The original implementation had several explicit dependencies to the OpenSSL module, and part of the challenge was finding a good, clean way to decouple those dependencies. In particular, the algorithm relied heavily on the Net::SSH buffer implementation, which also had explicit dependencies on OpenSSL.
So, the first order of business was to refactor the buffer implementation. This was actually pretty straight-forward.
First, I removed the OpenSSL specific portions from the general buffer implementations, and put them in OpenSSL specializations of the buffer classes. Thus, I now had (for example) ReaderBuffer, and OSSL::ReaderBuffer, where OSSL::ReaderBuffer extended ReaderBuffer.
Then, I created a new factory service, “buffer_factory”, and a new “factory factory” for buffers (“buffer_factories”). The factory factory would return buffer factories depending on the cryptography backend in use, and the corresponding buffer factory then returned buffer implementations specific to the chosen cryptography backend.
Once the buffer implementation was refactored to my satisfaction, I turned my attention to the Diffie-Hellman algorithm.
Refactoring the Key Exchange
This was harder, becuase I had implemented the algorithm entirely in one method. Additionally, the algorithm depended on a “session” reference for sending and receiving messages (presumably over a network).
If my only goal was to make it “dependency injectable”, things would be simple. I merely had to create services for obtaining key and digest references, as well as for creating “bignum” instances appropriate for the cryptography backend in use. These all turned out to be trivially simple.
However, my secondary goal was to make this unit-testable. As it was, unit testing it was problematic, since it involved a short dialog with a remote server. To make it more feasible to test, I broke the algorithm into 6 discrete pieces, each of which I implemented in a separate, independent, publicly accessible method. Then, the orignal
exchange_keys method just invoked those six methods, in order.
This allowed me to unit test each of those 6 pieces separately. It was still tricky-
perhaps the trickiest bit of unit testing I’ve written to date, requiring more than a few mock objects to stand in for the dependencies-but it was at least possible. And for the first time, I feel confident in the key exchange code!