| package sftp |
| |
| import ( |
| "bytes" |
| "encoding" |
| "errors" |
| "io/ioutil" |
| "os" |
| "reflect" |
| "testing" |
| ) |
| |
| func TestMarshalUint32(t *testing.T) { |
| var tests = []struct { |
| v uint32 |
| want []byte |
| }{ |
| {0, []byte{0, 0, 0, 0}}, |
| {42, []byte{0, 0, 0, 42}}, |
| {42 << 8, []byte{0, 0, 42, 0}}, |
| {42 << 16, []byte{0, 42, 0, 0}}, |
| {42 << 24, []byte{42, 0, 0, 0}}, |
| {^uint32(0), []byte{255, 255, 255, 255}}, |
| } |
| |
| for _, tt := range tests { |
| got := marshalUint32(nil, tt.v) |
| if !bytes.Equal(tt.want, got) { |
| t.Errorf("marshalUint32(%d) = %#v, want %#v", tt.v, got, tt.want) |
| } |
| } |
| } |
| |
| func TestMarshalUint64(t *testing.T) { |
| var tests = []struct { |
| v uint64 |
| want []byte |
| }{ |
| {0, []byte{0, 0, 0, 0, 0, 0, 0, 0}}, |
| {42, []byte{0, 0, 0, 0, 0, 0, 0, 42}}, |
| {42 << 8, []byte{0, 0, 0, 0, 0, 0, 42, 0}}, |
| {42 << 16, []byte{0, 0, 0, 0, 0, 42, 0, 0}}, |
| {42 << 24, []byte{0, 0, 0, 0, 42, 0, 0, 0}}, |
| {42 << 32, []byte{0, 0, 0, 42, 0, 0, 0, 0}}, |
| {42 << 40, []byte{0, 0, 42, 0, 0, 0, 0, 0}}, |
| {42 << 48, []byte{0, 42, 0, 0, 0, 0, 0, 0}}, |
| {42 << 56, []byte{42, 0, 0, 0, 0, 0, 0, 0}}, |
| {^uint64(0), []byte{255, 255, 255, 255, 255, 255, 255, 255}}, |
| } |
| |
| for _, tt := range tests { |
| got := marshalUint64(nil, tt.v) |
| if !bytes.Equal(tt.want, got) { |
| t.Errorf("marshalUint64(%d) = %#v, want %#v", tt.v, got, tt.want) |
| } |
| } |
| } |
| |
| func TestMarshalString(t *testing.T) { |
| var tests = []struct { |
| v string |
| want []byte |
| }{ |
| {"", []byte{0, 0, 0, 0}}, |
| {"/", []byte{0x0, 0x0, 0x0, 0x01, '/'}}, |
| {"/foo", []byte{0x0, 0x0, 0x0, 0x4, '/', 'f', 'o', 'o'}}, |
| {"\x00bar", []byte{0x0, 0x0, 0x0, 0x4, 0, 'b', 'a', 'r'}}, |
| {"b\x00ar", []byte{0x0, 0x0, 0x0, 0x4, 'b', 0, 'a', 'r'}}, |
| {"ba\x00r", []byte{0x0, 0x0, 0x0, 0x4, 'b', 'a', 0, 'r'}}, |
| {"bar\x00", []byte{0x0, 0x0, 0x0, 0x4, 'b', 'a', 'r', 0}}, |
| } |
| |
| for _, tt := range tests { |
| got := marshalString(nil, tt.v) |
| if !bytes.Equal(tt.want, got) { |
| t.Errorf("marshalString(%q) = %#v, want %#v", tt.v, got, tt.want) |
| } |
| } |
| } |
| |
| func TestMarshal(t *testing.T) { |
| type Struct struct { |
| X, Y, Z uint32 |
| } |
| |
| var tests = []struct { |
| v interface{} |
| want []byte |
| }{ |
| {uint8(42), []byte{42}}, |
| {uint32(42 << 8), []byte{0, 0, 42, 0}}, |
| {uint64(42 << 32), []byte{0, 0, 0, 42, 0, 0, 0, 0}}, |
| {"foo", []byte{0x0, 0x0, 0x0, 0x3, 'f', 'o', 'o'}}, |
| {Struct{1, 2, 3}, []byte{0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x3}}, |
| {[]uint32{1, 2, 3}, []byte{0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x3}}, |
| } |
| |
| for _, tt := range tests { |
| got := marshal(nil, tt.v) |
| if !bytes.Equal(tt.want, got) { |
| t.Errorf("marshal(%#v) = %#v, want %#v", tt.v, got, tt.want) |
| } |
| } |
| } |
| |
| func TestUnmarshalUint32(t *testing.T) { |
| testBuffer := []byte{ |
| 0, 0, 0, 0, |
| 0, 0, 0, 42, |
| 0, 0, 42, 0, |
| 0, 42, 0, 0, |
| 42, 0, 0, 0, |
| 255, 0, 0, 254, |
| } |
| |
| var wants = []uint32{ |
| 0, |
| 42, |
| 42 << 8, |
| 42 << 16, |
| 42 << 24, |
| 255<<24 | 254, |
| } |
| |
| var i int |
| for len(testBuffer) > 0 { |
| got, rest := unmarshalUint32(testBuffer) |
| |
| if got != wants[i] { |
| t.Fatalf("unmarshalUint32(%#v) = %d, want %d", testBuffer[:4], got, wants[i]) |
| } |
| |
| i++ |
| testBuffer = rest |
| } |
| } |
| |
| func TestUnmarshalUint64(t *testing.T) { |
| testBuffer := []byte{ |
| 0, 0, 0, 0, 0, 0, 0, 0, |
| 0, 0, 0, 0, 0, 0, 0, 42, |
| 0, 0, 0, 0, 0, 0, 42, 0, |
| 0, 0, 0, 0, 0, 42, 0, 0, |
| 0, 0, 0, 0, 42, 0, 0, 0, |
| 0, 0, 0, 42, 0, 0, 0, 0, |
| 0, 0, 42, 0, 0, 0, 0, 0, |
| 0, 42, 0, 0, 0, 0, 0, 0, |
| 42, 0, 0, 0, 0, 0, 0, 0, |
| 255, 0, 0, 0, 0, 0, 0, 254, |
| } |
| |
| var wants = []uint64{ |
| 0, |
| 42, |
| 42 << 8, |
| 42 << 16, |
| 42 << 24, |
| 42 << 32, |
| 42 << 40, |
| 42 << 48, |
| 42 << 56, |
| 255<<56 | 254, |
| } |
| |
| var i int |
| for len(testBuffer) > 0 { |
| got, rest := unmarshalUint64(testBuffer) |
| |
| if got != wants[i] { |
| t.Fatalf("unmarshalUint64(%#v) = %d, want %d", testBuffer[:8], got, wants[i]) |
| } |
| |
| i++ |
| testBuffer = rest |
| } |
| } |
| |
| var unmarshalStringTests = []struct { |
| b []byte |
| want string |
| rest []byte |
| }{ |
| {marshalString(nil, ""), "", nil}, |
| {marshalString(nil, "blah"), "blah", nil}, |
| } |
| |
| func TestUnmarshalString(t *testing.T) { |
| testBuffer := []byte{ |
| 0, 0, 0, 0, |
| 0, 0, 0, 1, '/', |
| 0, 0, 0, 4, '/', 'f', 'o', 'o', |
| 0, 0, 0, 4, 0, 'b', 'a', 'r', |
| 0, 0, 0, 4, 'b', 0, 'a', 'r', |
| 0, 0, 0, 4, 'b', 'a', 0, 'r', |
| 0, 0, 0, 4, 'b', 'a', 'r', 0, |
| } |
| |
| var wants = []string{ |
| "", |
| "/", |
| "/foo", |
| "\x00bar", |
| "b\x00ar", |
| "ba\x00r", |
| "bar\x00", |
| } |
| |
| var i int |
| for len(testBuffer) > 0 { |
| got, rest := unmarshalString(testBuffer) |
| |
| if got != wants[i] { |
| t.Fatalf("unmarshalUint64(%#v...) = %q, want %q", testBuffer[:4], got, wants[i]) |
| } |
| |
| i++ |
| testBuffer = rest |
| } |
| } |
| |
| func TestUnmarshalAttrs(t *testing.T) { |
| var tests = []struct { |
| b []byte |
| want *FileStat |
| }{ |
| { |
| b: []byte{0x00, 0x00, 0x00, 0x00}, |
| want: &FileStat{}, |
| }, |
| { |
| b: []byte{ |
| 0x00, 0x00, 0x00, byte(sshFileXferAttrSize), |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 20, |
| }, |
| want: &FileStat{ |
| Size: 20, |
| }, |
| }, |
| { |
| b: []byte{ |
| 0x00, 0x00, 0x00, byte(sshFileXferAttrSize | sshFileXferAttrPermissions), |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 20, |
| 0x00, 0x00, 0x01, 0xA4, |
| }, |
| want: &FileStat{ |
| Size: 20, |
| Mode: 0644, |
| }, |
| }, |
| { |
| b: []byte{ |
| 0x00, 0x00, 0x00, byte(sshFileXferAttrSize | sshFileXferAttrPermissions | sshFileXferAttrUIDGID), |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 20, |
| 0x00, 0x00, 0x03, 0xE8, |
| 0x00, 0x00, 0x03, 0xE9, |
| 0x00, 0x00, 0x01, 0xA4, |
| }, |
| want: &FileStat{ |
| Size: 20, |
| Mode: 0644, |
| UID: 1000, |
| GID: 1001, |
| }, |
| }, |
| { |
| b: []byte{ |
| 0x00, 0x00, 0x00, byte(sshFileXferAttrSize | sshFileXferAttrPermissions | sshFileXferAttrUIDGID | sshFileXferAttrACmodTime), |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 20, |
| 0x00, 0x00, 0x03, 0xE8, |
| 0x00, 0x00, 0x03, 0xE9, |
| 0x00, 0x00, 0x01, 0xA4, |
| 0x00, 0x00, 0x00, 42, |
| 0x00, 0x00, 0x00, 13, |
| }, |
| want: &FileStat{ |
| Size: 20, |
| Mode: 0644, |
| UID: 1000, |
| GID: 1001, |
| Atime: 42, |
| Mtime: 13, |
| }, |
| }, |
| } |
| |
| for _, tt := range tests { |
| got, _ := unmarshalAttrs(tt.b) |
| if !reflect.DeepEqual(got, tt.want) { |
| t.Errorf("unmarshalAttrs(% X):\n- got: %#v\n- want: %#v", tt.b, got, tt.want) |
| } |
| } |
| } |
| |
| func TestUnmarshalStatus(t *testing.T) { |
| var requestID uint32 = 1 |
| |
| id := marshalUint32(nil, requestID) |
| idCode := marshalUint32(id, sshFxFailure) |
| idCodeMsg := marshalString(idCode, "err msg") |
| idCodeMsgLang := marshalString(idCodeMsg, "lang tag") |
| |
| var tests = []struct { |
| desc string |
| reqID uint32 |
| status []byte |
| want error |
| }{ |
| { |
| desc: "well-formed status", |
| status: idCodeMsgLang, |
| want: &StatusError{ |
| Code: sshFxFailure, |
| msg: "err msg", |
| lang: "lang tag", |
| }, |
| }, |
| { |
| desc: "missing language tag", |
| status: idCodeMsg, |
| want: &StatusError{ |
| Code: sshFxFailure, |
| msg: "err msg", |
| }, |
| }, |
| { |
| desc: "missing error message and language tag", |
| status: idCode, |
| want: &StatusError{ |
| Code: sshFxFailure, |
| }, |
| }, |
| } |
| |
| for _, tt := range tests { |
| t.Run(tt.desc, func(t *testing.T) { |
| got := unmarshalStatus(1, tt.status) |
| if !reflect.DeepEqual(got, tt.want) { |
| t.Errorf("unmarshalStatus(1, % X):\n- got: %#v\n- want: %#v", tt.status, got, tt.want) |
| } |
| }) |
| } |
| |
| got := unmarshalStatus(2, idCodeMsgLang) |
| want := &unexpectedIDErr{ |
| want: 2, |
| got: 1, |
| } |
| if !reflect.DeepEqual(got, want) { |
| t.Errorf("unmarshalStatus(2, % X):\n- got: %#v\n- want: %#v", idCodeMsgLang, got, want) |
| } |
| } |
| |
| func TestSendPacket(t *testing.T) { |
| var tests = []struct { |
| packet encoding.BinaryMarshaler |
| want []byte |
| }{ |
| { |
| packet: &sshFxInitPacket{ |
| Version: 3, |
| Extensions: []extensionPair{ |
| {"posix-rename@openssh.com", "1"}, |
| }, |
| }, |
| want: []byte{ |
| 0x0, 0x0, 0x0, 0x26, |
| 0x1, |
| 0x0, 0x0, 0x0, 0x3, |
| 0x0, 0x0, 0x0, 0x18, |
| 'p', 'o', 's', 'i', 'x', '-', 'r', 'e', 'n', 'a', 'm', 'e', '@', 'o', 'p', 'e', 'n', 's', 's', 'h', '.', 'c', 'o', 'm', |
| 0x0, 0x0, 0x0, 0x1, |
| '1', |
| }, |
| }, |
| { |
| packet: &sshFxpOpenPacket{ |
| ID: 1, |
| Path: "/foo", |
| Pflags: flags(os.O_RDONLY), |
| }, |
| want: []byte{ |
| 0x0, 0x0, 0x0, 0x15, |
| 0x3, |
| 0x0, 0x0, 0x0, 0x1, |
| 0x0, 0x0, 0x0, 0x4, '/', 'f', 'o', 'o', |
| 0x0, 0x0, 0x0, 0x1, |
| 0x0, 0x0, 0x0, 0x0, |
| }, |
| }, |
| { |
| packet: &sshFxpWritePacket{ |
| ID: 124, |
| Handle: "foo", |
| Offset: 13, |
| Length: uint32(len("bar")), |
| Data: []byte("bar"), |
| }, |
| want: []byte{ |
| 0x0, 0x0, 0x0, 0x1b, |
| 0x6, |
| 0x0, 0x0, 0x0, 0x7c, |
| 0x0, 0x0, 0x0, 0x3, 'f', 'o', 'o', |
| 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd, |
| 0x0, 0x0, 0x0, 0x3, 'b', 'a', 'r', |
| }, |
| }, |
| { |
| packet: &sshFxpSetstatPacket{ |
| ID: 31, |
| Path: "/bar", |
| Flags: sshFileXferAttrUIDGID, |
| Attrs: struct { |
| UID uint32 |
| GID uint32 |
| }{ |
| UID: 1000, |
| GID: 100, |
| }, |
| }, |
| want: []byte{ |
| 0x0, 0x0, 0x0, 0x19, |
| 0x9, |
| 0x0, 0x0, 0x0, 0x1f, |
| 0x0, 0x0, 0x0, 0x4, '/', 'b', 'a', 'r', |
| 0x0, 0x0, 0x0, 0x2, |
| 0x0, 0x0, 0x3, 0xe8, |
| 0x0, 0x0, 0x0, 0x64, |
| }, |
| }, |
| } |
| |
| for _, tt := range tests { |
| b := new(bytes.Buffer) |
| sendPacket(b, tt.packet) |
| if got := b.Bytes(); !bytes.Equal(tt.want, got) { |
| t.Errorf("sendPacket(%v): got %x want %x", tt.packet, tt.want, got) |
| } |
| } |
| } |
| |
| func sp(data encoding.BinaryMarshaler) []byte { |
| b := new(bytes.Buffer) |
| sendPacket(b, data) |
| return b.Bytes() |
| } |
| |
| func TestRecvPacket(t *testing.T) { |
| var recvPacketTests = []struct { |
| b []byte |
| |
| want uint8 |
| body []byte |
| wantErr error |
| }{ |
| { |
| b: sp(&sshFxInitPacket{ |
| Version: 3, |
| Extensions: []extensionPair{ |
| {"posix-rename@openssh.com", "1"}, |
| }, |
| }), |
| want: sshFxpInit, |
| body: []byte{ |
| 0x0, 0x0, 0x0, 0x3, |
| 0x0, 0x0, 0x0, 0x18, |
| 'p', 'o', 's', 'i', 'x', '-', 'r', 'e', 'n', 'a', 'm', 'e', '@', 'o', 'p', 'e', 'n', 's', 's', 'h', '.', 'c', 'o', 'm', |
| 0x0, 0x0, 0x0, 0x01, |
| '1', |
| }, |
| }, |
| { |
| b: []byte{ |
| 0x0, 0x0, 0x0, 0x0, |
| }, |
| wantErr: errShortPacket, |
| }, |
| { |
| b: []byte{ |
| 0xff, 0xff, 0xff, 0xff, |
| }, |
| wantErr: errLongPacket, |
| }, |
| } |
| |
| for _, tt := range recvPacketTests { |
| r := bytes.NewReader(tt.b) |
| |
| got, body, err := recvPacket(r, nil, 0) |
| if tt.wantErr == nil { |
| if err != nil { |
| t.Fatalf("recvPacket(%#v): unexpected error: %v", tt.b, err) |
| } |
| } else { |
| if !errors.Is(err, tt.wantErr) { |
| t.Fatalf("recvPacket(%#v) = %v, want %v", tt.b, err, tt.wantErr) |
| } |
| } |
| |
| if got != tt.want { |
| t.Errorf("recvPacket(%#v) = %#v, want %#v", tt.b, got, tt.want) |
| } |
| |
| if !bytes.Equal(body, tt.body) { |
| t.Errorf("recvPacket(%#v) = %#v, want %#v", tt.b, body, tt.body) |
| } |
| } |
| } |
| |
| func TestSSHFxpOpenPacketreadonly(t *testing.T) { |
| var tests = []struct { |
| pflags uint32 |
| ok bool |
| }{ |
| { |
| pflags: sshFxfRead, |
| ok: true, |
| }, |
| { |
| pflags: sshFxfWrite, |
| ok: false, |
| }, |
| { |
| pflags: sshFxfRead | sshFxfWrite, |
| ok: false, |
| }, |
| } |
| |
| for _, tt := range tests { |
| p := &sshFxpOpenPacket{ |
| Pflags: tt.pflags, |
| } |
| |
| if want, got := tt.ok, p.readonly(); want != got { |
| t.Errorf("unexpected value for p.readonly(): want: %v, got: %v", |
| want, got) |
| } |
| } |
| } |
| |
| func TestSSHFxpOpenPackethasPflags(t *testing.T) { |
| var tests = []struct { |
| desc string |
| haveFlags uint32 |
| testFlags []uint32 |
| ok bool |
| }{ |
| { |
| desc: "have read, test against write", |
| haveFlags: sshFxfRead, |
| testFlags: []uint32{sshFxfWrite}, |
| ok: false, |
| }, |
| { |
| desc: "have write, test against read", |
| haveFlags: sshFxfWrite, |
| testFlags: []uint32{sshFxfRead}, |
| ok: false, |
| }, |
| { |
| desc: "have read+write, test against read", |
| haveFlags: sshFxfRead | sshFxfWrite, |
| testFlags: []uint32{sshFxfRead}, |
| ok: true, |
| }, |
| { |
| desc: "have read+write, test against write", |
| haveFlags: sshFxfRead | sshFxfWrite, |
| testFlags: []uint32{sshFxfWrite}, |
| ok: true, |
| }, |
| { |
| desc: "have read+write, test against read+write", |
| haveFlags: sshFxfRead | sshFxfWrite, |
| testFlags: []uint32{sshFxfRead, sshFxfWrite}, |
| ok: true, |
| }, |
| } |
| |
| for _, tt := range tests { |
| t.Log(tt.desc) |
| |
| p := &sshFxpOpenPacket{ |
| Pflags: tt.haveFlags, |
| } |
| |
| if want, got := tt.ok, p.hasPflags(tt.testFlags...); want != got { |
| t.Errorf("unexpected value for p.hasPflags(%#v): want: %v, got: %v", |
| tt.testFlags, want, got) |
| } |
| } |
| } |
| |
| func benchMarshal(b *testing.B, packet encoding.BinaryMarshaler) { |
| b.ResetTimer() |
| |
| for i := 0; i < b.N; i++ { |
| sendPacket(ioutil.Discard, packet) |
| } |
| } |
| |
| func BenchmarkMarshalInit(b *testing.B) { |
| benchMarshal(b, &sshFxInitPacket{ |
| Version: 3, |
| Extensions: []extensionPair{ |
| {"posix-rename@openssh.com", "1"}, |
| }, |
| }) |
| } |
| |
| func BenchmarkMarshalOpen(b *testing.B) { |
| benchMarshal(b, &sshFxpOpenPacket{ |
| ID: 1, |
| Path: "/home/test/some/random/path", |
| Pflags: flags(os.O_RDONLY), |
| }) |
| } |
| |
| func BenchmarkMarshalWriteWorstCase(b *testing.B) { |
| data := make([]byte, 32*1024) |
| |
| benchMarshal(b, &sshFxpWritePacket{ |
| ID: 1, |
| Handle: "someopaquehandle", |
| Offset: 0, |
| Length: uint32(len(data)), |
| Data: data, |
| }) |
| } |
| |
| func BenchmarkMarshalWrite1k(b *testing.B) { |
| data := make([]byte, 1025) |
| |
| benchMarshal(b, &sshFxpWritePacket{ |
| ID: 1, |
| Handle: "someopaquehandle", |
| Offset: 0, |
| Length: uint32(len(data)), |
| Data: data, |
| }) |
| } |