Net::SSH::Buffer is a flexible class for building and parsing binary data packets. It provides a stream-like interface for sequentially reading data items from the buffer, as well as a useful helper method for building binary packets given a signature.
Writing to a buffer always appends to the end, regardless of where the read cursor is. Reading, on the other hand, always begins at the first byte of the buffer and increments the read cursor, with subsequent reads taking up where the last left off.
As a consumer of the Net::SSH library, you will rarely come into contact with these buffer objects directly, but it could happen. Also, if you are ever implementing a protocol on top of SSH (e.g. SFTP), this buffer class can be quite handy.
Methods
public class
public instance
Attributes
content | [R] | exposes the raw content of the buffer |
position | [RW] | the current position of the pointer in the buffer |
Public class methods
This is a convenience method for creating and populating a new buffer from a single command. The arguments must be even in length, with the first of each pair of arguments being a symbol naming the type of the data that follows. If the type is :raw, the value is written directly to the hash.
b = Buffer.from(:byte, 1, :string, "hello", :raw, "\1\2\3\4") #-> "\1\0\0\0\5hello\1\2\3\4"
The supported data types are:
- :raw => write the next value verbatim (write)
- :int64 => write an 8-byte integer (write_int64)
- :long => write a 4-byte integer (write_long)
- :byte => write a single byte (write_byte)
- :string => write a 4-byte length followed by character data (write_string)
- :bool => write a single byte, interpreted as a boolean (write_bool)
- :bignum => write an SSH-encoded bignum (write_bignum)
- :key => write an SSH-encoded key value (write_key)
Any of these, except for :raw, accepts an Array argument, to make it easier to write multiple values of the same type in a briefer manner.
# File lib/net/ssh/buffer.rb, line 43 43: def self.from(*args) 44: raise ArgumentError, "odd number of arguments given" unless args.length % 2 == 0 45: 46: buffer = new 47: 0.step(args.length-1, 2) do |index| 48: type = args[index] 49: value = args[index+1] 50: if type == :raw 51: buffer.append(value.to_s) 52: elsif Array === value 53: buffer.send("write_#{type}", *value) 54: else 55: buffer.send("write_#{type}", value) 56: end 57: end 58: 59: buffer 60: end
Creates a new buffer, initialized to the given content. The position is initialized to the beginning of the buffer.
# File lib/net/ssh/buffer.rb, line 70 70: def initialize(content="") 71: @content = content.to_s 72: @position = 0 73: end
Public instance methods
Compares the contents of the two buffers, returning true only if they are identical in size and content.
# File lib/net/ssh/buffer.rb, line 93 93: def ==(buffer) 94: to_s == buffer.to_s 95: end
Appends the given text to the end of the buffer. Does not alter the read position. Returns the buffer object itself.
# File lib/net/ssh/buffer.rb, line 142 142: def append(text) 143: @content << text 144: self 145: end
Returns the number of bytes available to be read (e.g., how many bytes remain between the current position and the end of the buffer).
# File lib/net/ssh/buffer.rb, line 82 82: def available 83: length - position 84: end
Resets the buffer, making it empty. Also, resets the read position to 0.
# File lib/net/ssh/buffer.rb, line 116 116: def clear! 117: @content = "" 118: @position = 0 119: end
Consumes n bytes from the buffer, where n is the current position unless otherwise specified. This is useful for removing data from the buffer that has previously been read, when you are expecting more data to be appended. It helps to keep the size of buffers down when they would otherwise tend to grow without bound.
Returns the buffer object itself.
# File lib/net/ssh/buffer.rb, line 128 128: def consume!(n=position) 129: if n >= length 130: # optimize for a fairly common case 131: clear! 132: elsif n > 0 133: @content = @content[n..-1] || "" 134: @position -= n 135: @position = 0 if @position < 0 136: end 137: self 138: end
Returns true if the buffer contains no data (e.g., it is of zero length).
# File lib/net/ssh/buffer.rb, line 98 98: def empty? 99: @content.empty? 100: end
Returns true if the pointer is at the end of the buffer. Subsequent reads will return nil, in this case.
# File lib/net/ssh/buffer.rb, line 110 110: def eof? 111: @position >= length 112: end
Returns the length of the buffer’s content.
# File lib/net/ssh/buffer.rb, line 76 76: def length 77: @content.length 78: end
Reads and returns the next count bytes from the buffer, starting from the read position. If count is nil, this will return all remaining text in the buffer. This method will increment the pointer.
# File lib/net/ssh/buffer.rb, line 171 171: def read(count=nil) 172: count ||= length 173: count = length - @position if @position + count > length 174: @position += count 175: @content[@position-count, count] 176: end
Reads (as read) and returns the given number of bytes from the buffer, and then consumes (as consume!) all data up to the new read position.
# File lib/net/ssh/buffer.rb, line 180 180: def read!(count=nil) 181: data = read(count) 182: consume! 183: data 184: end
Read a bignum (OpenSSL::BN) from the buffer, in SSH2 format. It is essentially just a string, which is reinterpreted to be a bignum in binary format.
# File lib/net/ssh/buffer.rb, line 228 228: def read_bignum 229: data = read_string 230: return unless data 231: OpenSSL::BN.new(data, 2) 232: end
Read a single byte and convert it into a boolean, using ‘C’ rules (i.e., zero is false, non-zero is true).
# File lib/net/ssh/buffer.rb, line 220 220: def read_bool 221: b = read_byte or return nil 222: b != 0 223: end
Reads the next string from the buffer, and returns a new Buffer object that wraps it.
# File lib/net/ssh/buffer.rb, line 267 267: def read_buffer 268: Buffer.new(read_string) 269: end
Read and return the next byte in the buffer. Returns nil if called at the end of the buffer.
# File lib/net/ssh/buffer.rb, line 205 205: def read_byte 206: b = read(1) or return nil 207: b.getbyte(0) 208: end
Return the next 8 bytes as a 64-bit integer (in network byte order). Returns nil if there are less than 8 bytes remaining to be read in the buffer.
# File lib/net/ssh/buffer.rb, line 189 189: def read_int64 190: hi = read_long or return nil 191: lo = read_long or return nil 192: return (hi << 32) + lo 193: end
Read a key from the buffer. The key will start with a string describing its type. The remainder of the key is defined by the type that was read.
# File lib/net/ssh/buffer.rb, line 237 237: def read_key 238: type = read_string 239: return (type ? read_keyblob(type) : nil) 240: end
Read a keyblob of the given type from the buffer, and return it as a key. Only RSA and DSA keys are supported.
# File lib/net/ssh/buffer.rb, line 244 244: def read_keyblob(type) 245: case type 246: when "ssh-dss" 247: key = OpenSSL::PKey::DSA.new 248: key.p = read_bignum 249: key.q = read_bignum 250: key.g = read_bignum 251: key.pub_key = read_bignum 252: 253: when "ssh-rsa" 254: key = OpenSSL::PKey::RSA.new 255: key.e = read_bignum 256: key.n = read_bignum 257: 258: else 259: raise NotImplementedError, "unsupported key type `#{type}'" 260: end 261: 262: return key 263: end
Return the next four bytes as a long integer (in network byte order). Returns nil if there are less than 4 bytes remaining to be read in the buffer.
# File lib/net/ssh/buffer.rb, line 198 198: def read_long 199: b = read(4) or return nil 200: b.unpack("N").first 201: end
Read and return an SSH2-encoded string. The string starts with a long integer that describes the number of bytes remaining in the string. Returns nil if there are not enough bytes to satisfy the request.
# File lib/net/ssh/buffer.rb, line 213 213: def read_string 214: length = read_long or return nil 215: read(length) 216: end
Reads all data up to and including the given pattern, which may be a String, Fixnum, or Regexp and is interpreted exactly as String#index does. Returns nil if nothing matches. Increments the position to point immediately after the pattern, if it does match. Returns all data up to and including the text that matched the pattern.
# File lib/net/ssh/buffer.rb, line 158 158: def read_to(pattern) 159: index = @content.index(pattern, @position) or return nil 160: length = case pattern 161: when String then pattern.length 162: when Fixnum then 1 163: when Regexp then $&.length 164: end 165: index && read(index+length) 166: end
Returns all text from the current pointer to the end of the buffer as a new Net::SSH::Buffer object.
# File lib/net/ssh/buffer.rb, line 149 149: def remainder_as_buffer 150: Buffer.new(@content[@position..-1]) 151: end
Resets the pointer to the start of the buffer. Subsequent reads will begin at position 0.
# File lib/net/ssh/buffer.rb, line 104 104: def reset! 105: @position = 0 106: end
Returns a copy of the buffer’s content.
# File lib/net/ssh/buffer.rb, line 87 87: def to_s 88: (@content || "").dup 89: end
Writes the given data literally into the string. Does not alter the read position. Returns the buffer object.
# File lib/net/ssh/buffer.rb, line 273 273: def write(*data) 274: data.each { |datum| @content << datum } 275: self 276: end
Writes each argument to the buffer as a bignum (SSH2-style). No checking is done to ensure that the arguments are, in fact, bignums. Does not alter the read position. Returns the buffer object.
# File lib/net/ssh/buffer.rb, line 328 328: def write_bignum(*n) 329: @content << n.map { |b| b.to_ssh }.join 330: self 331: end
Writes each argument to the buffer as a (C-style) boolean, with 1 meaning true, and 0 meaning false. Does not alter the read position. Returns the buffer object.
# File lib/net/ssh/buffer.rb, line 320 320: def write_bool(*b) 321: b.each { |v| @content << (v ? "\1" : "\0") } 322: self 323: end
Writes each argument to the buffer as a byte. Does not alter the read position. Returns the buffer object.
# File lib/net/ssh/buffer.rb, line 300 300: def write_byte(*n) 301: n.each { |b| @content << b.chr } 302: self 303: end
Writes each argument to the buffer as a network-byte-order-encoded 64-bit integer (8 bytes). Does not alter the read position. Returns the buffer object.
# File lib/net/ssh/buffer.rb, line 281 281: def write_int64(*n) 282: n.each do |i| 283: hi = (i >> 32) & 0xFFFFFFFF 284: lo = i & 0xFFFFFFFF 285: @content << [hi, lo].pack("N2") 286: end 287: self 288: end
Writes the given arguments to the buffer as SSH2-encoded keys. Does not alter the read position. Returns the buffer object.
# File lib/net/ssh/buffer.rb, line 335 335: def write_key(*key) 336: key.each { |k| append(k.to_blob) } 337: self 338: end
Writes each argument to the buffer as a network-byte-order-encoded long (4-byte) integer. Does not alter the read position. Returns the buffer object.
# File lib/net/ssh/buffer.rb, line 293 293: def write_long(*n) 294: @content << n.pack("N*") 295: self 296: end
Writes each argument to the buffer as an SSH2-encoded string. Each string is prefixed by its length, encoded as a 4-byte long integer. Does not alter the read position. Returns the buffer object.
# File lib/net/ssh/buffer.rb, line 308 308: def write_string(*text) 309: text.each do |string| 310: s = string.to_s 311: write_long(s.length) 312: write(s) 313: end 314: self 315: end