var SFTPStream = require('../lib/sftp'); var Stats = SFTPStream.Stats; var STATUS_CODE = SFTPStream.STATUS_CODE; var OPEN_MODE = SFTPStream.OPEN_MODE; var constants = require('constants'); var basename = require('path').basename; var assert = require('assert'); var group = basename(__filename, '.js') + '/'; var t = -1; var tests = [ // successful client requests { run: function() { setup(this); var self = this; var what = this.what; var client = this.client; var server = this.server; this.onReady = function() { var path_ = '/tmp/foo.txt'; var handle_ = Buffer.from('node.js'); server.on('OPEN', function(id, path, pflags, attrs) { assert(++self.state.requests === 1, makeMsg(what, 'Saw too many requests')); assert(id === 0, makeMsg(what, 'Wrong request id: ' + id)); assert(path === path_, makeMsg(what, 'Wrong path: ' + path)); assert(pflags === (OPEN_MODE.TRUNC | OPEN_MODE.CREAT | OPEN_MODE.WRITE), makeMsg(what, 'Wrong flags: ' + flagsToHuman(pflags))); server.handle(id, handle_); server.end(); }); client.open(path_, 'w', function(err, handle) { assert(++self.state.responses === 1, makeMsg(what, 'Saw too many responses')); assert(!err, makeMsg(what, 'Unexpected open() error: ' + err)); assert.deepEqual(handle, handle_, makeMsg(what, 'handle mismatch')); }); }; }, what: 'open' }, { run: function() { setup(this); var self = this; var what = this.what; var client = this.client; var server = this.server; this.onReady = function() { var handle_ = Buffer.from('node.js'); server.on('CLOSE', function(id, handle) { assert(++self.state.requests === 1, makeMsg(what, 'Saw too many requests')); assert(id === 0, makeMsg(what, 'Wrong request id: ' + id)); assert.deepEqual(handle, handle_, makeMsg(what, 'handle mismatch')); server.status(id, STATUS_CODE.OK); server.end(); }); client.close(handle_, function(err) { assert(++self.state.responses === 1, makeMsg(what, 'Saw too many responses')); assert(!err, makeMsg(what, 'Unexpected close() error: ' + err)); }); }; }, what: 'close' }, { run: function() { setup(this); var self = this; var what = this.what; var client = this.client; var server = this.server; this.onReady = function() { var handle_ = Buffer.from('node.js'); var expected = Buffer.from('node.jsnode.jsnode.jsnode.jsnode.jsnode.js'); var buffer = Buffer.alloc(expected.length); server.on('READ', function(id, handle, offset, len) { assert(++self.state.requests <= 2, makeMsg(what, 'Saw too many requests')); assert(id === 0, makeMsg(what, 'Wrong request id: ' + id)); assert.deepEqual(handle, handle_, makeMsg(what, 'handle mismatch')); assert(offset === 5, makeMsg(what, 'Wrong read offset: ' + offset)); assert(len === buffer.length, makeMsg(what, 'Wrong read len: ' + len)); server.data(id, expected); server.end(); }); client.readData(handle_, buffer, 0, buffer.length, 5, clientReadCb); function clientReadCb(err, nb) { assert(++self.state.responses <= 2, makeMsg(what, 'Saw too many responses')); assert(!err, makeMsg(what, 'Unexpected readData() error: ' + err)); assert.deepEqual(buffer, expected, makeMsg(what, 'read data mismatch')); } }; }, what: 'readData' }, { run: function() { setup(this); var self = this; var what = this.what; var client = this.client; var server = this.server; this.onReady = function() { var handle_ = Buffer.from('node.js'); var buf = Buffer.from('node.jsnode.jsnode.jsnode.jsnode.jsnode.js'); server.on('WRITE', function(id, handle, offset, data) { assert(++self.state.requests === 1, makeMsg(what, 'Saw too many requests')); assert(id === 0, makeMsg(what, 'Wrong request id: ' + id)); assert.deepEqual(handle, handle_, makeMsg(what, 'handle mismatch')); assert(offset === 5, makeMsg(what, 'Wrong write offset: ' + offset)); assert.deepEqual(data, buf, makeMsg(what, 'write data mismatch')); server.status(id, STATUS_CODE.OK); server.end(); }); client.writeData(handle_, buf, 0, buf.length, 5, function(err, nb) { assert(++self.state.responses === 1, makeMsg(what, 'Saw too many responses')); assert(!err, makeMsg(what, 'Unexpected writeData() error: ' + err)); assert.equal(nb, buf.length); }); }; }, what: 'write' }, { run: function() { setup(this); var self = this; var what = this.what; var client = this.client; var server = this.server; this.onReady = function() { var handle_ = Buffer.from('node.js'); var buf = Buffer.allocUnsafe(3 * 32 * 1024); server.on('WRITE', function(id, handle, offset, data) { ++self.state.requests; assert.equal(id, self.state.requests - 1, makeMsg(what, 'Wrong request id: ' + id)); assert.deepEqual(handle, handle_, makeMsg(what, 'handle mismatch')); assert.equal(offset, (self.state.requests - 1) * 32 * 1024, makeMsg(what, 'Wrong write offset: ' + offset)); assert((offset + data.length) <= buf.length); assert.deepEqual(data, buf.slice(offset, offset + data.length), makeMsg(what, 'write data mismatch')); server.status(id, STATUS_CODE.OK); if (self.state.requests === 3) server.end(); }); client.writeData(handle_, buf, 0, buf.length, 0, function(err, nb) { ++self.state.responses; assert(!err, makeMsg(what, 'Unexpected writeData() error: ' + err)); assert.equal(nb, buf.length); }); }; }, expected: { requests: 3, responses: 1 }, what: 'write (overflow)' }, { run: function() { setup(this); var self = this; var what = this.what; var client = this.client; var server = this.server; this.onReady = function() { var path_ = '/foo/bar/baz'; var attrs_ = new Stats({ size: 10 * 1024, uid: 9001, gid: 9001, atime: (Date.now() / 1000) | 0, mtime: (Date.now() / 1000) | 0 }); server.on('LSTAT', function(id, path) { assert(++self.state.requests === 1, makeMsg(what, 'Saw too many requests')); assert(id === 0, makeMsg(what, 'Wrong request id: ' + id)); assert(path === path_, makeMsg(what, 'Wrong path: ' + path)); server.attrs(id, attrs_); server.end(); }); client.lstat(path_, function(err, attrs) { assert(++self.state.responses === 1, makeMsg(what, 'Saw too many responses')); assert(!err, makeMsg(what, 'Unexpected lstat() error: ' + err)); assert.deepEqual(attrs, attrs_, makeMsg(what, 'attrs mismatch')); }); }; }, what: 'lstat' }, { run: function() { setup(this); var self = this; var what = this.what; var client = this.client; var server = this.server; this.onReady = function() { var handle_ = Buffer.from('node.js'); var attrs_ = new Stats({ size: 10 * 1024, uid: 9001, gid: 9001, atime: (Date.now() / 1000) | 0, mtime: (Date.now() / 1000) | 0 }); server.on('FSTAT', function(id, handle) { assert(++self.state.requests === 1, makeMsg(what, 'Saw too many requests')); assert(id === 0, makeMsg(what, 'Wrong request id: ' + id)); assert.deepEqual(handle, handle_, makeMsg(what, 'handle mismatch')); server.attrs(id, attrs_); server.end(); }); client.fstat(handle_, function(err, attrs) { assert(++self.state.responses === 1, makeMsg(what, 'Saw too many responses')); assert(!err, makeMsg(what, 'Unexpected fstat() error: ' + err)); assert.deepEqual(attrs, attrs_, makeMsg(what, 'attrs mismatch')); }); }; }, what: 'fstat' }, { run: function() { setup(this); var self = this; var what = this.what; var client = this.client; var server = this.server; this.onReady = function() { var path_ = '/foo/bar/baz'; var attrs_ = new Stats({ uid: 9001, gid: 9001, atime: (Date.now() / 1000) | 0, mtime: (Date.now() / 1000) | 0 }); server.on('SETSTAT', function(id, path, attrs) { assert(++self.state.requests === 1, makeMsg(what, 'Saw too many requests')); assert(id === 0, makeMsg(what, 'Wrong request id: ' + id)); assert(path === path_, makeMsg(what, 'Wrong path: ' + path)); assert.deepEqual(attrs, attrs_, makeMsg(what, 'attrs mismatch')); server.status(id, STATUS_CODE.OK); server.end(); }); client.setstat(path_, attrs_, function(err) { assert(++self.state.responses === 1, makeMsg(what, 'Saw too many responses')); assert(!err, makeMsg(what, 'Unexpected setstat() error: ' + err)); }); }; }, what: 'setstat' }, { run: function() { setup(this); var self = this; var what = this.what; var client = this.client; var server = this.server; this.onReady = function() { var handle_ = Buffer.from('node.js'); var attrs_ = new Stats({ uid: 9001, gid: 9001, atime: (Date.now() / 1000) | 0, mtime: (Date.now() / 1000) | 0 }); server.on('FSETSTAT', function(id, handle, attrs) { assert(++self.state.requests === 1, makeMsg(what, 'Saw too many requests')); assert(id === 0, makeMsg(what, 'Wrong request id: ' + id)); assert.deepEqual(handle, handle_, makeMsg(what, 'handle mismatch')); assert.deepEqual(attrs, attrs_, makeMsg(what, 'attrs mismatch')); server.status(id, STATUS_CODE.OK); server.end(); }); client.fsetstat(handle_, attrs_, function(err) { assert(++self.state.responses === 1, makeMsg(what, 'Saw too many responses')); assert(!err, makeMsg(what, 'Unexpected fsetstat() error: ' + err)); }); }; }, what: 'fsetstat' }, { run: function() { setup(this); var self = this; var what = this.what; var client = this.client; var server = this.server; this.onReady = function() { var handle_ = Buffer.from('node.js'); var path_ = '/tmp'; server.on('OPENDIR', function(id, path) { assert(++self.state.requests === 1, makeMsg(what, 'Saw too many requests')); assert(id === 0, makeMsg(what, 'Wrong request id: ' + id)); assert(path === path_, makeMsg(what, 'Wrong path: ' + path)); server.handle(id, handle_); server.end(); }); client.opendir(path_, function(err, handle) { assert(++self.state.responses === 1, makeMsg(what, 'Saw too many responses')); assert(!err, makeMsg(what, 'Unexpected opendir() error: ' + err)); assert.deepEqual(handle, handle_, makeMsg(what, 'handle mismatch')); }); }; }, what: 'opendir' }, { run: function() { setup(this); var self = this; var what = this.what; var client = this.client; var server = this.server; this.onReady = function() { var handle_ = Buffer.from('node.js'); var list_ = [ { filename: '.', longname: 'drwxr-xr-x 56 nodejs nodejs 4096 Nov 10 01:05 .', attrs: new Stats({ mode: 0755 | constants.S_IFDIR, size: 4096, uid: 9001, gid: 8001, atime: 1415599549, mtime: 1415599590 }) }, { filename: '..', longname: 'drwxr-xr-x 4 root root 4096 May 16 2013 ..', attrs: new Stats({ mode: 0755 | constants.S_IFDIR, size: 4096, uid: 0, gid: 0, atime: 1368729954, mtime: 1368729999 }) }, { filename: 'foo', longname: 'drwxrwxrwx 2 nodejs nodejs 4096 Mar 8 2009 foo', attrs: new Stats({ mode: 0777 | constants.S_IFDIR, size: 4096, uid: 9001, gid: 8001, atime: 1368729954, mtime: 1368729999 }) }, { filename: 'bar', longname: '-rw-r--r-- 1 nodejs nodejs 513901992 Dec 4 2009 bar', attrs: new Stats({ mode: 0644 | constants.S_IFREG, size: 513901992, uid: 9001, gid: 8001, atime: 1259972199, mtime: 1259972199 }) } ]; server.on('READDIR', function(id, handle) { assert(++self.state.requests === 1, makeMsg(what, 'Saw too many requests')); assert(id === 0, makeMsg(what, 'Wrong request id: ' + id)); assert.deepEqual(handle, handle_, makeMsg(what, 'handle mismatch')); server.name(id, list_); server.end(); }); client.readdir(handle_, function(err, list) { assert(++self.state.responses === 1, makeMsg(what, 'Saw too many responses')); assert(!err, makeMsg(what, 'Unexpected readdir() error: ' + err)); assert.deepEqual(list, list_.slice(2), makeMsg(what, 'dir list mismatch')); }); }; }, what: 'readdir' }, { run: function() { setup(this); var self = this; var what = this.what; var client = this.client; var server = this.server; this.onReady = function() { var handle_ = Buffer.from('node.js'); var list_ = [ { filename: '.', longname: 'drwxr-xr-x 56 nodejs nodejs 4096 Nov 10 01:05 .', attrs: new Stats({ mode: 0755 | constants.S_IFDIR, size: 4096, uid: 9001, gid: 8001, atime: 1415599549, mtime: 1415599590 }) }, { filename: '..', longname: 'drwxr-xr-x 4 root root 4096 May 16 2013 ..', attrs: new Stats({ mode: 0755 | constants.S_IFDIR, size: 4096, uid: 0, gid: 0, atime: 1368729954, mtime: 1368729999 }) }, { filename: 'foo', longname: 'drwxrwxrwx 2 nodejs nodejs 4096 Mar 8 2009 foo', attrs: new Stats({ mode: 0777 | constants.S_IFDIR, size: 4096, uid: 9001, gid: 8001, atime: 1368729954, mtime: 1368729999 }) }, { filename: 'bar', longname: '-rw-r--r-- 1 nodejs nodejs 513901992 Dec 4 2009 bar', attrs: new Stats({ mode: 0644 | constants.S_IFREG, size: 513901992, uid: 9001, gid: 8001, atime: 1259972199, mtime: 1259972199 }) } ]; server.on('READDIR', function(id, handle) { assert(++self.state.requests === 1, makeMsg(what, 'Saw too many requests')); assert(id === 0, makeMsg(what, 'Wrong request id: ' + id)); assert.deepEqual(handle, handle_, makeMsg(what, 'handle mismatch')); server.name(id, list_); server.end(); }); client.readdir(handle_, { full: true }, function(err, list) { assert(++self.state.responses === 1, makeMsg(what, 'Saw too many responses')); assert(!err, makeMsg(what, 'Unexpected readdir() error: ' + err)); assert.deepEqual(list, list_, makeMsg(what, 'dir list mismatch')); }); }; }, what: 'readdir (full)' }, { run: function() { setup(this); var self = this; var what = this.what; var client = this.client; var server = this.server; this.onReady = function() { var path_ = '/foo/bar/baz'; server.on('REMOVE', function(id, path) { assert(++self.state.requests === 1, makeMsg(what, 'Saw too many requests')); assert(id === 0, makeMsg(what, 'Wrong request id: ' + id)); assert(path === path_, makeMsg(what, 'Wrong path: ' + path)); server.status(id, STATUS_CODE.OK); server.end(); }); client.unlink(path_, function(err) { assert(++self.state.responses === 1, makeMsg(what, 'Saw too many responses')); assert(!err, makeMsg(what, 'Unexpected unlink() error: ' + err)); }); }; }, what: 'remove' }, { run: function() { setup(this); var self = this; var what = this.what; var client = this.client; var server = this.server; this.onReady = function() { var path_ = '/foo/bar/baz'; server.on('MKDIR', function(id, path) { assert(++self.state.requests === 1, makeMsg(what, 'Saw too many requests')); assert(id === 0, makeMsg(what, 'Wrong request id: ' + id)); assert(path === path_, makeMsg(what, 'Wrong path: ' + path)); server.status(id, STATUS_CODE.OK); server.end(); }); client.mkdir(path_, function(err) { assert(++self.state.responses === 1, makeMsg(what, 'Saw too many responses')); assert(!err, makeMsg(what, 'Unexpected mkdir() error: ' + err)); }); }; }, what: 'mkdir' }, { run: function() { setup(this); var self = this; var what = this.what; var client = this.client; var server = this.server; this.onReady = function() { var path_ = '/foo/bar/baz'; server.on('RMDIR', function(id, path) { assert(++self.state.requests === 1, makeMsg(what, 'Saw too many requests')); assert(id === 0, makeMsg(what, 'Wrong request id: ' + id)); assert(path === path_, makeMsg(what, 'Wrong path: ' + path)); server.status(id, STATUS_CODE.OK); server.end(); }); client.rmdir(path_, function(err) { assert(++self.state.responses === 1, makeMsg(what, 'Saw too many responses')); assert(!err, makeMsg(what, 'Unexpected rmdir() error: ' + err)); }); }; }, what: 'rmdir' }, { run: function() { setup(this); var self = this; var what = this.what; var client = this.client; var server = this.server; this.onReady = function() { var path_ = '/foo/bar/baz'; var name_ = { filename: '/tmp/foo' }; server.on('REALPATH', function(id, path) { assert(++self.state.requests === 1, makeMsg(what, 'Saw too many requests')); assert(id === 0, makeMsg(what, 'Wrong request id: ' + id)); assert(path === path_, makeMsg(what, 'Wrong path: ' + path)); server.name(id, name_); server.end(); }); client.realpath(path_, function(err, name) { assert(++self.state.responses === 1, makeMsg(what, 'Saw too many responses')); assert(!err, makeMsg(what, 'Unexpected realpath() error: ' + err)); assert.deepEqual(name, name_.filename, makeMsg(what, 'name mismatch')); }); }; }, what: 'realpath' }, { run: function() { setup(this); var self = this; var what = this.what; var client = this.client; var server = this.server; this.onReady = function() { var path_ = '/foo/bar/baz'; var attrs_ = new Stats({ size: 10 * 1024, uid: 9001, gid: 9001, atime: (Date.now() / 1000) | 0, mtime: (Date.now() / 1000) | 0 }); server.on('STAT', function(id, path) { assert(++self.state.requests === 1, makeMsg(what, 'Saw too many requests')); assert(id === 0, makeMsg(what, 'Wrong request id: ' + id)); assert(path === path_, makeMsg(what, 'Wrong path: ' + path)); server.attrs(id, attrs_); server.end(); }); client.stat(path_, function(err, attrs) { assert(++self.state.responses === 1, makeMsg(what, 'Saw too many responses')); assert(!err, makeMsg(what, 'Unexpected stat() error: ' + err)); assert.deepEqual(attrs, attrs_, makeMsg(what, 'attrs mismatch')); }); }; }, what: 'stat' }, { run: function() { setup(this); var self = this; var what = this.what; var client = this.client; var server = this.server; this.onReady = function() { var oldPath_ = '/foo/bar/baz'; var newPath_ = '/tmp/foo'; server.on('RENAME', function(id, oldPath, newPath) { assert(++self.state.requests === 1, makeMsg(what, 'Saw too many requests')); assert(id === 0, makeMsg(what, 'Wrong request id: ' + id)); assert(oldPath === oldPath_, makeMsg(what, 'Wrong old path: ' + oldPath)); assert(newPath === newPath_, makeMsg(what, 'Wrong new path: ' + newPath)); server.status(id, STATUS_CODE.OK); server.end(); }); client.rename(oldPath_, newPath_, function(err) { assert(++self.state.responses === 1, makeMsg(what, 'Saw too many responses')); assert(!err, makeMsg(what, 'Unexpected rename() error: ' + err)); }); }; }, what: 'rename' }, { run: function() { setup(this); var self = this; var what = this.what; var client = this.client; var server = this.server; this.onReady = function() { var linkPath_ = '/foo/bar/baz'; var name = { filename: '/tmp/foo' }; server.on('READLINK', function(id, linkPath) { assert(++self.state.requests === 1, makeMsg(what, 'Saw too many requests')); assert(id === 0, makeMsg(what, 'Wrong request id: ' + id)); assert(linkPath === linkPath_, makeMsg(what, 'Wrong link path: ' + linkPath)); server.name(id, name); server.end(); }); client.readlink(linkPath_, function(err, targetPath) { assert(++self.state.responses === 1, makeMsg(what, 'Saw too many responses')); assert(!err, makeMsg(what, 'Unexpected readlink() error: ' + err)); assert(targetPath === name.filename, makeMsg(what, 'Wrong target path: ' + targetPath)); }); }; }, what: 'readlink' }, { run: function() { setup(this); var self = this; var what = this.what; var client = this.client; var server = this.server; this.onReady = function() { var linkPath_ = '/foo/bar/baz'; var targetPath_ = '/tmp/foo'; server.on('SYMLINK', function(id, linkPath, targetPath) { assert(++self.state.requests === 1, makeMsg(what, 'Saw too many requests')); assert(id === 0, makeMsg(what, 'Wrong request id: ' + id)); assert(linkPath === linkPath_, makeMsg(what, 'Wrong link path: ' + linkPath)); assert(targetPath === targetPath_, makeMsg(what, 'Wrong target path: ' + targetPath)); server.status(id, STATUS_CODE.OK); server.end(); }); client.symlink(targetPath_, linkPath_, function(err) { assert(++self.state.responses === 1, makeMsg(what, 'Saw too many responses')); assert(!err, makeMsg(what, 'Unexpected symlink() error: ' + err)); }); }; }, what: 'symlink' }, { run: function() { setup(this); var self = this; var what = this.what; var client = this.client; var server = this.server; this.onReady = function() { var path_ = '/foo/bar/baz'; var handle_ = Buffer.from('hi mom!'); var data_ = Buffer.from('hello world'); server.once('OPEN', function(id, path, pflags, attrs) { assert(id === 0, makeMsg(what, 'Wrong request id: ' + id)); assert(path === path_, makeMsg(what, 'Wrong path: ' + path)); assert(pflags === OPEN_MODE.READ, makeMsg(what, 'Wrong flags: ' + flagsToHuman(pflags))); server.handle(id, handle_); }).once('FSTAT', function(id, handle) { assert(id === 1, makeMsg(what, 'Wrong request id: ' + id)); var attrs = new Stats({ size: data_.length, uid: 9001, gid: 9001, atime: (Date.now() / 1000) | 0, mtime: (Date.now() / 1000) | 0 }); server.attrs(id, attrs); }).once('READ', function(id, handle, offset, len) { assert(id === 2, makeMsg(what, 'Wrong request id: ' + id)); assert.deepEqual(handle, handle_, makeMsg(what, 'handle mismatch')); assert(offset === 0, makeMsg(what, 'Wrong read offset: ' + offset)); server.data(id, data_); }).once('CLOSE', function(id, handle) { ++self.state.requests; assert(id === 3, makeMsg(what, 'Wrong request id: ' + id)); assert.deepEqual(handle, handle_, makeMsg(what, 'handle mismatch')); server.status(id, STATUS_CODE.OK); server.end(); }); client.readFile(path_, function(err, buf) { ++self.state.responses; assert(!err, makeMsg(what, 'Unexpected error: ' + err)); assert.deepEqual(buf, data_, makeMsg(what, 'data mismatch')); }); }; }, what: 'readFile' }, { run: function() { setup(this); var self = this; var what = this.what; var client = this.client; var server = this.server; this.onReady = function() { var path_ = '/foo/bar/baz'; var handle_ = Buffer.from('hi mom!'); var data_ = Buffer.from('hello world'); var reads = 0; server.once('OPEN', function(id, path, pflags, attrs) { assert(id === 0, makeMsg(what, 'Wrong request id: ' + id)); assert(path === path_, makeMsg(what, 'Wrong path: ' + path)); assert(pflags === OPEN_MODE.READ, makeMsg(what, 'Wrong flags: ' + flagsToHuman(pflags))); server.handle(id, handle_); }).once('FSTAT', function(id, handle) { assert(id === 1, makeMsg(what, 'Wrong request id: ' + id)); var attrs = new Stats({ uid: 9001, gid: 9001, atime: (Date.now() / 1000) | 0, mtime: (Date.now() / 1000) | 0 }); server.attrs(id, attrs); }).on('READ', function(id, handle, offset, len) { assert(++reads <= 2, makeMsg(what, 'Saw too many READs')); assert(id === 2 || id === 3, makeMsg(what, 'Wrong request id: ' + id)); assert.deepEqual(handle, handle_, makeMsg(what, 'handle mismatch')); switch(id) { case 2: assert(offset === 0, makeMsg(what, 'Wrong read offset for first read: ' + offset)); server.data(id, data_); break; case 3: assert(offset === data_.length, makeMsg(what, 'Wrong read offset for second read: ' + offset)); server.status(id, STATUS_CODE.EOF); break; } }).once('CLOSE', function(id, handle) { ++self.state.requests; assert(id === 4, makeMsg(what, 'Wrong request id: ' + id)); assert.deepEqual(handle, handle_, makeMsg(what, 'handle mismatch')); server.status(id, STATUS_CODE.OK); server.end(); }); client.readFile(path_, function(err, buf) { ++self.state.responses; assert(!err, makeMsg(what, 'Unexpected error: ' + err)); assert.deepEqual(buf, data_, makeMsg(what, 'data mismatch')); }); }; }, what: 'readFile (no size from fstat)' }, { run: function() { setup(this); var self = this; var what = this.what; var client = this.client; var server = this.server; this.onReady = function() { var opens = 0; var reads = 0; var closes = 0; var path_ = '/foo/bar/baz'; var handle_ = Buffer.from('hi mom!'); var data_ = Buffer.from('hello world'); server.on('OPEN', function(id, path, pflags, attrs) { assert(++opens === 1, makeMsg(what, 'Saw too many OPENs')); assert(id === 0, makeMsg(what, 'Wrong request id: ' + id)); assert(path === path_, makeMsg(what, 'Wrong path: ' + path)); assert(pflags === OPEN_MODE.READ, makeMsg(what, 'Wrong flags: ' + flagsToHuman(pflags))); server.handle(id, handle_); }).on('READ', function(id, handle, offset, len) { assert(++reads <= 2, makeMsg(what, 'Saw too many READs')); assert(id === reads, makeMsg(what, 'Wrong request id: ' + id)); assert.deepEqual(handle, handle_, makeMsg(what, 'handle mismatch')); if (reads === 1) { assert(offset === 0, makeMsg(what, 'Wrong read offset: ' + offset)); server.data(id, data_); } else server.status(id, STATUS_CODE.EOF); }).on('CLOSE', function(id, handle) { ++self.state.requests; assert(++closes === 1, makeMsg(what, 'Saw too many CLOSEs')); assert(id === 3, makeMsg(what, 'Wrong request id: ' + id)); assert.deepEqual(handle, handle_, makeMsg(what, 'handle mismatch')); server.status(id, STATUS_CODE.OK); server.end(); }); var buf = []; client.createReadStream(path_).on('readable', function() { var chunk; while ((chunk = this.read()) !== null) { buf.push(chunk); } }).on('end', function() { assert(++self.state.responses === 1, makeMsg(what, 'Saw too many responses')); buf = Buffer.concat(buf); assert.deepEqual(buf, data_, makeMsg(what, 'data mismatch')); }); }; }, what: 'ReadStream' }, { run: function() { setup(this); var self = this; var what = this.what; var client = this.client; var server = this.server; this.onReady = function() { var opens = 0; var path_ = '/foo/bar/baz'; var error; server.on('OPEN', function(id, path, pflags, attrs) { ++opens; ++self.state.requests; assert(id === 0, makeMsg(what, 'Wrong request id: ' + id)); assert(path === path_, makeMsg(what, 'Wrong path: ' + path)); assert(pflags === OPEN_MODE.READ, makeMsg(what, 'Wrong flags: ' + flagsToHuman(pflags))); server.status(id, STATUS_CODE.NO_SUCH_FILE); server.end(); }); client.createReadStream(path_).on('error', function(err) { error = err; }).on('close', function() { assert(opens === 1, makeMsg(what, 'Saw ' + opens + ' OPENs')); assert(error, makeMsg(what, 'Expected error')); assert(++self.state.responses === 1, makeMsg(what, 'Saw too many responses')); }); }; }, what: 'ReadStream (error)' }, { run: function() { setup(this); var self = this; var what = this.what; var client = this.client; var server = this.server; this.onReady = function() { var opens = 0; var writes = 0; var closes = 0; var fsetstat = false; var path_ = '/foo/bar/baz'; var handle_ = Buffer.from('hi mom!'); var data_ = Buffer.from('hello world'); var expFlags = OPEN_MODE.TRUNC | OPEN_MODE.CREAT | OPEN_MODE.WRITE; server.on('OPEN', function(id, path, pflags, attrs) { assert(++opens === 1, makeMsg(what, 'Saw too many OPENs')); assert(id === 0, makeMsg(what, 'Wrong request id: ' + id)); assert(path === path_, makeMsg(what, 'Wrong path: ' + path)); assert(pflags === expFlags, makeMsg(what, 'Wrong flags: ' + flagsToHuman(pflags))); server.handle(id, handle_); }).once('FSETSTAT', function(id, handle, attrs) { fsetstat = true; assert(id === 1, makeMsg(what, 'Wrong request id: ' + id)); assert.deepEqual(handle, handle_, makeMsg(what, 'handle mismatch')); assert.strictEqual(attrs.mode, parseInt('0666', 8), makeMsg(what, 'Wrong file mode')); server.status(id, STATUS_CODE.OK); }).on('WRITE', function(id, handle, offset, data) { assert(++writes <= 3, makeMsg(what, 'Saw too many WRITEs')); assert(id === writes + 1, makeMsg(what, 'Wrong request id: ' + id)); assert.deepEqual(handle, handle_, makeMsg(what, 'handle mismatch')); assert(offset === ((writes - 1) * data_.length), makeMsg(what, 'Wrong write offset: ' + offset)); assert.deepEqual(data, data_, makeMsg(what, 'Wrong data')); server.status(id, STATUS_CODE.OK); }).on('CLOSE', function(id, handle) { ++self.state.requests; assert(++closes === 1, makeMsg(what, 'Saw too many CLOSEs')); assert(id === 5, makeMsg(what, 'Wrong request id: ' + id)); assert.deepEqual(handle, handle_, makeMsg(what, 'handle mismatch')); server.status(id, STATUS_CODE.OK); server.end(); }).on('end', function() { assert(++self.state.responses === 1, makeMsg(what, 'Saw too many responses')); assert(opens === 1, makeMsg(what, 'Wrong OPEN count')); assert(writes === 3, makeMsg(what, 'Wrong WRITE count')); assert(closes === 1, makeMsg(what, 'Wrong CLOSE count')); assert(fsetstat, makeMsg(what, 'Expected FSETSTAT')); }); var writer = client.createWriteStream(path_); if (writer.cork) writer.cork(); writer.write(data_); writer.write(data_); writer.write(data_); if (writer.uncork) writer.uncork(); writer.end(); }; }, what: 'WriteStream' }, // other client request scenarios { run: function() { setup(this); var self = this; var what = this.what; var client = this.client; var server = this.server; this.onReady = function() { var handle_ = Buffer.from('node.js'); server.on('READDIR', function(id, handle) { assert(++self.state.requests === 1, makeMsg(what, 'Saw too many requests')); assert(id === 0, makeMsg(what, 'Wrong request id: ' + id)); assert.deepEqual(handle, handle_, makeMsg(what, 'handle mismatch')); server.status(id, STATUS_CODE.EOF); server.end(); }); client.readdir(handle_, function(err, list) { assert(++self.state.responses === 1, makeMsg(what, 'Saw too many responses')); assert(err && err.code === STATUS_CODE.EOF, makeMsg(what, 'Expected EOF, got: ' + err)); }); }; }, what: 'readdir (EOF)' }, { run: function() { setup(this); var self = this; var what = this.what; var client = this.client; var server = this.server; this.onReady = function() { var path_ = '/tmp/foo.txt'; var reqs = 0; var continues = 0; client.unpipe(server); function clientCb(err, handle) { assert(++self.state.responses <= reqs, makeMsg(what, 'Saw too many responses')); if (self.state.responses === reqs) { assert(continues === 1, makeMsg(what, 'no continue event seen')); server.end(); } } client.on('continue', function() { assert(++continues === 1, makeMsg(what, 'saw > 1 continue event')); }); while (true) { ++reqs; if (!client.open(path_, 'w', clientCb)) break; } client.pipe(server); }; }, expected: { requests: -1, responses: -1 }, what: '"continue" event after push() === false' }, { run: function() { var self = this; var client = new SFTPStream(); client.once('ready', function() { client.open('/foo/bar', 'w', function(err, handle) { assert(err, 'Expected error'); assert.strictEqual(err.code, 4); assert.strictEqual(err.message, 'Uh oh'); assert.strictEqual(err.lang, ''); next(); }); client.write(Buffer.from([ 0, 0, 0, 18, 101, 0, 0, 0, 0, 0, 0, 0, SFTPStream.STATUS_CODE.FAILURE, 0, 0, 0, 5, 85, 104, 32, 111, 104 ])); }); client.write(Buffer.from([ 0, 0, 0, 5, 2, 0, 0, 0, 3 ])); }, what: 'Can parse status response without language' }, { run: function() { var self = this; var client = new SFTPStream(); client.once('ready', function() { client.open('/foo/bar', 'w', function(err, handle) { assert(err, 'Expected error'); assert.strictEqual(err.code, 4); assert.strictEqual(err.message, 'Failure'); assert.strictEqual(err.lang, ''); next(); }); client.write(Buffer.from([ 0, 0, 0, 9, 101, 0, 0, 0, 0, 0, 0, 0, SFTPStream.STATUS_CODE.FAILURE ])); }); client.write(Buffer.from([ 0, 0, 0, 5, 2, 0, 0, 0, 3 ])); }, what: 'Can parse status response without message' }, { run: function() { var self = this; var err; var client = new SFTPStream(); client.once('ready', function() { assert(false, 'Handshake should not succeed'); }).once('error', function(err_) { err = err_; }).once('end', function() { assert.strictEqual(err && err.message, 'Unexpected packet before version'); next(); }); client.write(Buffer.from([ 1, 2, 3, 4, 5, 6, 7, 8, 9 ])); }, what: 'End SFTP stream on bad handshake (client)' }, { run: function() { var self = this; var err; var client = new SFTPStream({ server: true }); client.once('ready', function() { assert(false, 'Handshake should not succeed'); }).once('error', function(err_) { err = err_; }).once('end', function() { assert.strictEqual(err && err.message, 'Unexpected packet before init'); next(); }); client.write(Buffer.from([ 1, 2, 3, 4, 5, 6, 7, 8, 9 ])); }, what: 'End SFTP stream on bad handshake (server)' }, { run: function() { setup(this); var self = this; var what = this.what; var client = this.client; var server = this.server; this.onReady = function() { var handle_ = Buffer.from([0,0,0,1]); var buffer = Buffer.allocUnsafe(4); server.once('READ', function(id, handle, offset, len) { ++self.state.requests; assert(id === 0, makeMsg(what, 'Wrong request id: ' + id)); assert.deepEqual(handle, handle_, makeMsg(what, 'handle mismatch')); assert(offset === 0, makeMsg(what, 'Wrong read offset: ' + offset)); assert(len === buffer.length, makeMsg(what, 'Wrong read length: ' + len)); server.data(id, Buffer.alloc(len + 1)); }); client.readData(handle_, buffer, 0, buffer.length, 0, clientReadCb); function clientReadCb(err, buf) { ++self.state.responses; assert(err && err.message.indexOf('more data than requested') !== -1, makeMsg(what, 'Expected error')); server.end(); } }; }, what: 'Abort on data chunk larger than requested' }, ]; function setup(self) { var expectedRequests = (self.expected && self.expected.requests) || 1; var expectedResponses = (self.expected && self.expected.responses) || 1; var clientEnded = false; var serverEnded = false; self.state = { clientReady: false, serverReady: false, requests: 0, responses: 0 }; self.client = new SFTPStream(); self.server = new SFTPStream({ server: true }); self.server.on('error', onError) .on('ready', onReady) .on('end', onEnd); self.client.on('error', onError) .on('ready', onReady) .on('end', onEnd); function onError(err) { var which = (this === self.server ? 'server' : 'client'); assert(false, makeMsg(self.what, 'Unexpected ' + which + ' error: ' + err)); } function onReady() { if (this === self.client) { assert(!self.state.clientReady, makeMsg(self.what, 'Received multiple ready events for client')); self.state.clientReady = true; } else { assert(!self.state.serverReady, makeMsg(self.what, 'Received multiple ready events for server')); self.state.serverReady = true; } if (self.state.clientReady && self.state.serverReady) self.onReady && self.onReady(); } function onEnd() { if (this === self.client) { assert(!clientEnded, makeMsg(self.what, 'Received multiple close events for client')); clientEnded = true; } else { assert(!serverEnded, makeMsg(self.what, 'Received multiple close events for server')); serverEnded = true; } if (clientEnded && serverEnded) { var msg; if (expectedRequests > 0) { msg = 'Expected ' + expectedRequests + ' request(s) but received ' + self.state.requests; assert(self.state.requests === expectedRequests, makeMsg(self.what, msg)); } if (expectedResponses > 0) { msg = 'Expected ' + expectedResponses + ' response(s) but received ' + self.state.responses; assert(self.state.responses === expectedResponses, makeMsg(self.what, msg)); } next(); } } process.nextTick(function() { self.client.pipe(self.server).pipe(self.client); }); } function flagsToHuman(flags) { var ret = []; for (var i = 0, keys = Object.keys(OPEN_MODE); i < keys.length; ++i) if (flags & OPEN_MODE[keys[i]]) ret.push(keys[i]); return ret.join(' | '); } function next() { if (++t === tests.length) return; var v = tests[t]; v.run.call(v); } function makeMsg(what, msg) { return '[' + group + what + ']: ' + msg; } process.once('exit', function() { assert(t === tests.length, makeMsg('_exit', 'Only finished ' + t + '/' + tests.length + ' tests')); }); next();