This is the driver object for the SFTP protocol. It manages the SSH channel used to communicate with the server, as well as the negotiation of the protocol. The operations themselves are specific to the protocol version in use, and are handled by protocol-version-specific dispatcher objects.

Methods
Included Modules
Attributes
[R] channel The underlying SSH channel supporting this SFTP connection.
[R] state The current state of the driver. This will be one of unconfirmed, init, version, open, or closed.
Public Class methods
new( connection, buffers, version, dispatchers, log )

Create a new SFTP protocol driver object on the given SSH connection. buffers is a reference to a buffer factory, version is the highest supported SFTP protocol version, dispatchers is a Proc object that returns a dispatcher instance for a specific protocol version, and log is a logger instance.

The new protocol driver will be in an unconfirmed state, initially. When the server validates the requested channel, the driver goes to the init state, and requests the SFTP subsystem. When the subsystem has been accepted, the driver sends its supported protocol version to the server, and goes to the version state. Lastly, when the server responds with its supported protocol version and the version to use has been successfully negotiated, the driver will go to the open state, after which SFTP operations may be successfully performed on the driver.

    # File lib/net/sftp/protocol/driver.rb, line 51
51:     def initialize( connection, buffers, version, dispatchers, log )
52:       @buffers = buffers
53:       @version = version
54:       @dispatchers = dispatchers
55:       @log = log
56: 
57:       @next_request_id = 0
58:       @next_request_mutex = Mutex.new
59:       @parsed_data = nil
60:       @on_open = nil
61: 
62:       @state = :unconfirmed
63: 
64:       @log.debug "opening channel for sftp" if @log.debug?
65:       @channel = connection.open_channel( "session", &method( :do_confirm ) )
66:     end
Public Instance methods
close()

Closes the underlying SSH channel that the SFTP session uses to communicate with the server. This moves the driver to the closed state. If the driver is already closed, this does nothing.

    # File lib/net/sftp/protocol/driver.rb, line 71
71:     def close
72:       if @state != :closed
73:         @log.debug "closing sftp channel" if @log.debug?
74:         @channel.close
75:         @state = :closed
76:       end
77:     end
do_confirm( channel )

The callback used internally to indicate that the requested channel has been confirmed. This will request the SFTP subsystem, register some request callbacks, and move the driver’s state to init. This may only be called when the driver’s state is unconfirmed.

     # File lib/net/sftp/protocol/driver.rb, line 100
100:     def do_confirm( channel )
101:       assert_state :unconfirmed
102:       @log.debug "requesting sftp subsystem" if @log.debug?
103: 
104:       channel.subsystem  "sftp"
105:       channel.on_success &method( :do_success )
106:       channel.on_data    &method( :do_data )
107: 
108:       @state = :init
109:     end
do_data( channel, data )

This is called internally when a data packet is received from the server. All SFTP packets are transfered as SSH data packets, so this parses the data packet to determine the SFTP packet type, and then sends the contents on to the active dispatcher for further processing. This routine correctly handles SFTP packets that span multiple SSH data packets.

     # File lib/net/sftp/protocol/driver.rb, line 163
163:     def do_data( channel, data )
164:       if @parsed_data
165:         @parsed_data[:content].append data
166:         return if @parsed_data[:length] > @parsed_data[:content].length
167: 
168:         type = @parsed_data[:type]
169:         content = @parsed_data[:content]
170:         @parsed_data = nil
171:       else
172:         reader = @buffers.reader( data )
173:         length = reader.read_long-1
174:         type = reader.read_byte
175:         content = reader.remainder_as_buffer
176: 
177:         if length > content.length
178:           @parsed_data = { :length => length,
179:                            :type => type,
180:                            :content => content }
181:           return
182:         end
183:       end
184: 
185:       if type == FXP_VERSION
186:         do_version content
187:       else
188:         assert_state :open
189:         @dispatcher.dispatch channel, type, content
190:       end
191:     end
do_success( channel )

The callback used internally to indicate that the SFTP subsystem was successfully requested. This may only be called when the driver’s state is init. It sends an INIT packet containing the highest supported SFTP protocol version to the server, and moves the driver’s state to version.

     # File lib/net/sftp/protocol/driver.rb, line 116
116:     def do_success( channel )
117:       assert_state :init
118:       @log.debug "initializing sftp subsystem" if @log.debug?
119: 
120:       packet = @buffers.writer
121:       packet.write_long @version
122:       send_data FXP_INIT, packet
123: 
124:       @state = :version
125:     end
do_version( content )

This is used internally to indicate that a VERSION packet was received from the server. This may only be called when the driver’s state is version. It determines the highest possible protocol version supported by both the client and the server, selects the dispatcher that handles that protocol version, moves the state to open, and then invokes the on_open callback (if one was registered).

     # File lib/net/sftp/protocol/driver.rb, line 133
133:     def do_version( content )
134:       assert_state :version
135:       @log.debug "negotiating sftp protocol version" if @log.debug?
136:       @log.debug "my sftp version is #{@version}" if @log.debug?
137: 
138:       server_version = content.read_long
139:       @log.debug "server reports sftp version #{server_version}" if @log.debug?
140: 
141:       negotiated_version = [ @version, server_version ].min 
142:       @log.info "negotiated version is #{negotiated_version}" if @log.info?
143: 
144:       extensions = Hash.new
145:       until content.eof?
146:         ext_name = content.read_string
147:         ext_data = content.read_string
148:         extensions[ ext_name ] = ext_data
149:       end
150: 
151:       @dispatcher = @dispatchers[ negotiated_version, extensions ]
152: 
153:       @state = :open
154: 
155:       @on_open.call( self ) if @on_open
156:     end
method_missing( sym, *args, &block )

Delegates missing methods to the current dispatcher (if the state is open). This allows clients to register callbacks for the supported operations of the negotiated protocol version.

     # File lib/net/sftp/protocol/driver.rb, line 196
196:     def method_missing( sym, *args, &block )
197:       if @state == :open && @dispatcher.respond_to?( sym )
198:         assert_state :open
199:         @dispatcher.__send__( sym, *args, &block )
200:       else
201:         super
202:       end
203:     end
next_request_id()

Returns the next available request id in a thread-safe manner. The request-id is used to identify packets associated with request sequences.

    # File lib/net/sftp/protocol/driver.rb, line 81
81:     def next_request_id
82:       @next_request_mutex.synchronize do
83:         request_id = @next_request_id
84:         @next_request_id += 1
85:         return request_id
86:       end
87:     end
on_open( &block )

Specify the callback to invoke when the session has been successfully opened (i.e., once the driver’s state has moved to open). The callback should accept a single parameter—the driver itself.

    # File lib/net/sftp/protocol/driver.rb, line 92
92:     def on_open( &block )
93:       @on_open = block
94:     end
respond_to?( sym )

Returns true if the driver responds to the given message, or if the state is open and the active dispatcher responds to the given message.

     # File lib/net/sftp/protocol/driver.rb, line 208
208:     def respond_to?( sym )
209:       super || @state == :open && @dispatcher.respond_to?( sym )
210:     end
send_data( type, data )

A convenience method for sending an SFTP packet of the given type, with the given payload. This repackages the data as an SSH data packet and sends it across the channel.

     # File lib/net/sftp/protocol/driver.rb, line 215
215:     def send_data( type, data )
216:       data = data.to_s
217: 
218:       msg = @buffers.writer
219:       msg.write_long data.length + 1
220:       msg.write_byte type
221:       msg.write data
222: 
223:       @channel.send_data msg
224:     end