Class Net::SSH::Buffer

  1. lib/net/ssh/buffer.rb
Parent: Object

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.

Attributes

content [R] exposes the raw content of the buffer
position [RW] the current position of the pointer in the buffer

Public class methods

from (*args)

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.

[show source]
    # 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
new (content="")

Creates a new buffer, initialized to the given content. The position is initialized to the beginning of the buffer.

[show source]
    # 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

== (buffer)

Compares the contents of the two buffers, returning true only if they are identical in size and content.

[show source]
    # File lib/net/ssh/buffer.rb, line 93
93:     def ==(buffer)
94:       to_s == buffer.to_s
95:     end
append (text)

Appends the given text to the end of the buffer. Does not alter the read position. Returns the buffer object itself.

[show source]
     # File lib/net/ssh/buffer.rb, line 142
142:     def append(text)
143:       @content << text
144:       self
145:     end
available ()

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).

[show source]
    # File lib/net/ssh/buffer.rb, line 82
82:     def available
83:       length - position
84:     end
clear! ()

Resets the buffer, making it empty. Also, resets the read position to 0.

[show source]
     # File lib/net/ssh/buffer.rb, line 116
116:     def clear!
117:       @content = ""
118:       @position = 0
119:     end
consume! (n=position)

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.

[show source]
     # 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
empty? ()

Returns true if the buffer contains no data (e.g., it is of zero length).

[show source]
     # File lib/net/ssh/buffer.rb, line 98
 98:     def empty?
 99:       @content.empty?
100:     end
eof? ()

Returns true if the pointer is at the end of the buffer. Subsequent reads will return nil, in this case.

[show source]
     # File lib/net/ssh/buffer.rb, line 110
110:     def eof?
111:       @position >= length
112:     end
length ()

Returns the length of the buffer’s content.

[show source]
    # File lib/net/ssh/buffer.rb, line 76
76:     def length
77:       @content.length
78:     end
read (count=nil)

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.

[show source]
     # 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
read! (count=nil)

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.

[show source]
     # File lib/net/ssh/buffer.rb, line 180
180:     def read!(count=nil)
181:       data = read(count)
182:       consume!
183:       data
184:     end
read_bignum ()

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.

[show source]
     # 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_bool ()

Read a single byte and convert it into a boolean, using ‘C’ rules (i.e., zero is false, non-zero is true).

[show source]
     # File lib/net/ssh/buffer.rb, line 220
220:     def read_bool
221:       b = read_byte or return nil
222:       b != 0
223:     end
read_buffer ()

Reads the next string from the buffer, and returns a new Buffer object that wraps it.

[show source]
     # File lib/net/ssh/buffer.rb, line 267
267:     def read_buffer
268:       Buffer.new(read_string)
269:     end
read_byte ()

Read and return the next byte in the buffer. Returns nil if called at the end of the buffer.

[show source]
     # 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
read_int64 ()

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.

[show source]
     # 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_key ()

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.

[show source]
     # 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_keyblob (type)

Read a keyblob of the given type from the buffer, and return it as a key. Only RSA and DSA keys are supported.

[show source]
     # 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
read_long ()

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.

[show source]
     # 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_string ()

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.

[show source]
     # File lib/net/ssh/buffer.rb, line 213
213:     def read_string
214:       length = read_long or return nil
215:       read(length)
216:     end
read_to (pattern)

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.

[show source]
     # 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
remainder_as_buffer ()

Returns all text from the current pointer to the end of the buffer as a new Net::SSH::Buffer object.

[show source]
     # File lib/net/ssh/buffer.rb, line 149
149:     def remainder_as_buffer
150:       Buffer.new(@content[@position..-1])
151:     end
reset! ()

Resets the pointer to the start of the buffer. Subsequent reads will begin at position 0.

[show source]
     # File lib/net/ssh/buffer.rb, line 104
104:     def reset!
105:       @position = 0
106:     end
to_s ()

Returns a copy of the buffer’s content.

[show source]
    # File lib/net/ssh/buffer.rb, line 87
87:     def to_s
88:       (@content || "").dup
89:     end
write (*data)

Writes the given data literally into the string. Does not alter the read position. Returns the buffer object.

[show source]
     # File lib/net/ssh/buffer.rb, line 273
273:     def write(*data)
274:       data.each { |datum| @content << datum }
275:       self
276:     end
write_bignum (*n)

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.

[show source]
     # 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
write_bool (*b)

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.

[show source]
     # 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
write_byte (*n)

Writes each argument to the buffer as a byte. Does not alter the read position. Returns the buffer object.

[show source]
     # File lib/net/ssh/buffer.rb, line 300
300:     def write_byte(*n)
301:       n.each { |b| @content << b.chr }
302:       self
303:     end
write_int64 (*n)

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.

[show source]
     # 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
write_key (*key)

Writes the given arguments to the buffer as SSH2-encoded keys. Does not alter the read position. Returns the buffer object.

[show source]
     # 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
write_long (*n)

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.

[show source]
     # File lib/net/ssh/buffer.rb, line 293
293:     def write_long(*n)
294:       @content << n.pack("N*")
295:       self
296:     end
write_string (*text)

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.

[show source]
     # 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