diff --git a/internal/bsr/ssh/chunk.go b/internal/bsr/ssh/chunk.go index 21896c841..a49a59bcb 100644 --- a/internal/bsr/ssh/chunk.go +++ b/internal/bsr/ssh/chunk.go @@ -8,14 +8,51 @@ import ( "fmt" "github.com/hashicorp/boundary/internal/bsr" + pssh "github.com/hashicorp/boundary/internal/bsr/gen/ssh/v1" + "github.com/hashicorp/boundary/internal/bsr/internal/is" + "google.golang.org/protobuf/proto" ) +func init() { + for _, ct := range []bsr.ChunkType{ + DataChunkType, + BreakReqChunkType, + CancelTCPIPForwardReqChunkType, + DirectTCPIPReqChunkType, + EnvReqChunkType, + ExecReqChunkType, + ExitSignalReqChunkType, + ExitStatusReqChunkType, + ForwardedTCPIPReqChunkType, + PtyReqChunkType, + SessionReqChunkType, + ShellReqChunkType, + SignalReqChunkType, + SubsystemReqChunkType, + TCPIPForwardReqChunkType, + WindowChangeReqChunkType, + X11ForwardingReqChunkType, + X11ReqChunkType, + XonXoffReqChunkType, + UnknownReqChunkType, + } { + if err := bsr.RegisterChunkType(Protocol, ct, DecodeChunk); err != nil { + panic(err) + } + } +} + const ( + // Protocol is used to identify chunks that are recorded from SSH. Protocol bsr.Protocol = "SSH\x87" + // MaxPacketSize is used by the DataWriter to determine if SSH data should + // be broken into multiple chunks. MaxPacketSize = 256 * 1024 +) - // Chunk types +// Chunk types +const ( DataChunkType bsr.ChunkType = "DATA" BreakReqChunkType bsr.ChunkType = "BREK" CancelTCPIPForwardReqChunkType bsr.ChunkType = "CTPF" @@ -40,7 +77,7 @@ const ( // DataChunk contains the raw byte data from an SSH session type DataChunk struct { - bsr.BaseChunk + *bsr.BaseChunk Data []byte } @@ -54,7 +91,7 @@ func NewDataChunk(ctx context.Context, d bsr.Direction, t *bsr.Timestamp, data [ } return &DataChunk{ - BaseChunk: *baseChunk, + BaseChunk: baseChunk, Data: data, }, nil } @@ -63,3 +100,206 @@ func NewDataChunk(ctx context.Context, d bsr.Direction, t *bsr.Timestamp, data [ func (c *DataChunk) MarshalData(_ context.Context) ([]byte, error) { return c.Data, nil } + +// DecodeChunk will decode any known SSH Chunk type. If the chunk type is +// not an ssh chunk type, and error is returned. +func DecodeChunk(_ context.Context, bc *bsr.BaseChunk, data []byte) (bsr.Chunk, error) { + const op = "ssh.DecodeChunk" + + if is.Nil(bc) { + return nil, fmt.Errorf("%s: nil base chunk: %w", op, bsr.ErrInvalidParameter) + } + + if bc.Protocol != Protocol { + return nil, fmt.Errorf("%s: invalid protocol %s", op, bc.Protocol) + } + + switch bc.Type { + case DataChunkType: + return &DataChunk{ + BaseChunk: bc, + Data: data, + }, nil + } + + if len(data) <= 0 { + return nil, fmt.Errorf("%s: not enough data", op) + } + + switch bc.Type { + case BreakReqChunkType: + mm := &pssh.BreakRequest{} + if err := proto.Unmarshal(data, mm); err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + return &BreakRequest{ + BaseChunk: bc, + BreakRequest: mm, + }, nil + case CancelTCPIPForwardReqChunkType: + mm := &pssh.CancelTCPIPForwardRequest{} + if err := proto.Unmarshal(data, mm); err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + return &CancelTCPIPForwardRequest{ + BaseChunk: bc, + CancelTCPIPForwardRequest: mm, + }, nil + case DirectTCPIPReqChunkType: + mm := &pssh.DirectTCPIPRequest{} + if err := proto.Unmarshal(data, mm); err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + return &DirectTCPIPRequest{ + BaseChunk: bc, + DirectTCPIPRequest: mm, + }, nil + case EnvReqChunkType: + mm := &pssh.EnvRequest{} + if err := proto.Unmarshal(data, mm); err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + return &EnvRequest{ + BaseChunk: bc, + EnvRequest: mm, + }, nil + case ExecReqChunkType: + mm := &pssh.ExecRequest{} + if err := proto.Unmarshal(data, mm); err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + return &ExecRequest{ + BaseChunk: bc, + ExecRequest: mm, + }, nil + case ExitSignalReqChunkType: + mm := &pssh.ExitSignalRequest{} + if err := proto.Unmarshal(data, mm); err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + return &ExitSignalRequest{ + BaseChunk: bc, + ExitSignalRequest: mm, + }, nil + case ExitStatusReqChunkType: + mm := &pssh.ExitStatusRequest{} + if err := proto.Unmarshal(data, mm); err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + return &ExitStatusRequest{ + BaseChunk: bc, + ExitStatusRequest: mm, + }, nil + case ForwardedTCPIPReqChunkType: + mm := &pssh.ForwardedTCPIPRequest{} + if err := proto.Unmarshal(data, mm); err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + return &ForwardedTCPIPRequest{ + BaseChunk: bc, + ForwardedTCPIPRequest: mm, + }, nil + case PtyReqChunkType: + mm := &pssh.PtyRequest{} + if err := proto.Unmarshal(data, mm); err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + return &PtyRequest{ + BaseChunk: bc, + PtyRequest: mm, + }, nil + case SessionReqChunkType: + mm := &pssh.SessionRequest{} + if err := proto.Unmarshal(data, mm); err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + return &SessionRequest{ + BaseChunk: bc, + SessionRequest: mm, + }, nil + case ShellReqChunkType: + mm := &pssh.ShellRequest{} + if err := proto.Unmarshal(data, mm); err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + return &ShellRequest{ + BaseChunk: bc, + ShellRequest: mm, + }, nil + case SignalReqChunkType: + mm := &pssh.SignalRequest{} + if err := proto.Unmarshal(data, mm); err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + return &SignalRequest{ + BaseChunk: bc, + SignalRequest: mm, + }, nil + case SubsystemReqChunkType: + mm := &pssh.SubsystemRequest{} + if err := proto.Unmarshal(data, mm); err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + return &SubsystemRequest{ + BaseChunk: bc, + SubsystemRequest: mm, + }, nil + case TCPIPForwardReqChunkType: + mm := &pssh.TCPIPForwardRequest{} + if err := proto.Unmarshal(data, mm); err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + return &TCPIPForwardRequest{ + BaseChunk: bc, + TCPIPForwardRequest: mm, + }, nil + case WindowChangeReqChunkType: + mm := &pssh.WindowChangeRequest{} + if err := proto.Unmarshal(data, mm); err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + return &WindowChangeRequest{ + BaseChunk: bc, + WindowChangeRequest: mm, + }, nil + case X11ForwardingReqChunkType: + mm := &pssh.X11ForwardingRequest{} + if err := proto.Unmarshal(data, mm); err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + return &X11ForwardingRequest{ + BaseChunk: bc, + X11ForwardingRequest: mm, + }, nil + case X11ReqChunkType: + mm := &pssh.X11Request{} + if err := proto.Unmarshal(data, mm); err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + return &X11Request{ + BaseChunk: bc, + X11Request: mm, + }, nil + case XonXoffReqChunkType: + mm := &pssh.XonXoffRequest{} + if err := proto.Unmarshal(data, mm); err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + return &XonXoffRequest{ + BaseChunk: bc, + XonXoffRequest: mm, + }, nil + case UnknownReqChunkType: + mm := &pssh.UnknownRequest{} + if err := proto.Unmarshal(data, mm); err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + return &UnknownRequest{ + BaseChunk: bc, + UnknownRequest: mm, + }, nil + + default: + return nil, fmt.Errorf("%s: unsupported chunk type %s", op, bc.Type) + } +} diff --git a/internal/bsr/ssh/chunk_break_request.go b/internal/bsr/ssh/chunk_break_request.go index 2bf2ec63b..ae9e94ab2 100644 --- a/internal/bsr/ssh/chunk_break_request.go +++ b/internal/bsr/ssh/chunk_break_request.go @@ -18,7 +18,7 @@ const BreakRequestType = "break" // BreakRequest is a chunk to contain data for an SSH Break request type BreakRequest struct { - bsr.BaseChunk + *bsr.BaseChunk *pssh.BreakRequest } @@ -63,7 +63,7 @@ func NewBreakRequest(ctx context.Context, d bsr.Direction, t *bsr.Timestamp, r * return nil, fmt.Errorf("%s: unable to unmarshal payload: %w", op, err) } reqData := &BreakRequest{ - BaseChunk: *baseChunk, + BaseChunk: baseChunk, BreakRequest: &pssh.BreakRequest{ RequestType: r.Type, WantReply: r.WantReply, diff --git a/internal/bsr/ssh/chunk_cancel_tcpip_forward_request.go b/internal/bsr/ssh/chunk_cancel_tcpip_forward_request.go index 048c44b47..f2b6c35a8 100644 --- a/internal/bsr/ssh/chunk_cancel_tcpip_forward_request.go +++ b/internal/bsr/ssh/chunk_cancel_tcpip_forward_request.go @@ -18,7 +18,7 @@ const CancelTCPIPForwardRequestType = "cancel-tcpip-forward" // CancelTCPIPForwardRequest is a chunk to contain data for an SSH Cancel TCIPIP Forward request type CancelTCPIPForwardRequest struct { - bsr.BaseChunk + *bsr.BaseChunk *pssh.CancelTCPIPForwardRequest } @@ -63,7 +63,7 @@ func NewCancelTCPIPForwardRequest(ctx context.Context, d bsr.Direction, t *bsr.T return nil, fmt.Errorf("%s: unable to unmarshal payload: %w", op, err) } reqData := &CancelTCPIPForwardRequest{ - BaseChunk: *baseChunk, + BaseChunk: baseChunk, CancelTCPIPForwardRequest: &pssh.CancelTCPIPForwardRequest{ RequestType: r.Type, WantReply: r.WantReply, diff --git a/internal/bsr/ssh/chunk_decode_test.go b/internal/bsr/ssh/chunk_decode_test.go new file mode 100644 index 000000000..1d0ba7625 --- /dev/null +++ b/internal/bsr/ssh/chunk_decode_test.go @@ -0,0 +1,742 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssh_test + +import ( + "context" + "errors" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/hashicorp/boundary/internal/bsr" + pssh "github.com/hashicorp/boundary/internal/bsr/gen/ssh/v1" + "github.com/hashicorp/boundary/internal/bsr/ssh" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + gossh "golang.org/x/crypto/ssh" + "google.golang.org/protobuf/proto" +) + +func TestDecodeChunk(t *testing.T) { + ctx := context.Background() + + cases := []struct { + bc *bsr.BaseChunk + encoded []byte + want bsr.Chunk + }{ + { + &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.DataChunkType, + }, + []byte("foo"), + &ssh.DataChunk{ + BaseChunk: &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.DataChunkType, + }, + Data: []byte("foo"), + }, + }, + { + &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.BreakReqChunkType, + }, + func() []byte { + msg := &pssh.BreakRequest{ + RequestType: ssh.BreakRequestType, + WantReply: false, + BreakLengthMs: 43, + } + data, err := proto.Marshal(msg) + require.NoError(t, err) + return data + }(), + &ssh.BreakRequest{ + BaseChunk: &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.BreakReqChunkType, + }, + BreakRequest: &pssh.BreakRequest{ + RequestType: ssh.BreakRequestType, + WantReply: false, + BreakLengthMs: 43, + }, + }, + }, + { + &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.CancelTCPIPForwardReqChunkType, + }, + func() []byte { + msg := &pssh.CancelTCPIPForwardRequest{ + RequestType: ssh.CancelTCPIPForwardRequestType, + WantReply: false, + AddressToBind: "127.0.0.1", + PortToBind: 87565, + } + data, err := proto.Marshal(msg) + require.NoError(t, err) + return data + }(), + &ssh.CancelTCPIPForwardRequest{ + BaseChunk: &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.CancelTCPIPForwardReqChunkType, + }, + CancelTCPIPForwardRequest: &pssh.CancelTCPIPForwardRequest{ + RequestType: ssh.CancelTCPIPForwardRequestType, + WantReply: false, + AddressToBind: "127.0.0.1", + PortToBind: 87565, + }, + }, + }, + { + &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.DirectTCPIPReqChunkType, + }, + func() []byte { + msg := &pssh.DirectTCPIPRequest{ + RequestType: ssh.DirectTCPIPRequestType, + SenderChannel: 5, + InitialWindowSize: 60, + MaximumPacketSize: 512, + Host: "127.0.0.1", + Port: 87654, + OriginatorIpAddress: "10.0.0.1", + OriginatorPort: 22, + } + data, err := proto.Marshal(msg) + require.NoError(t, err) + return data + }(), + &ssh.DirectTCPIPRequest{ + BaseChunk: &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.DirectTCPIPReqChunkType, + }, + DirectTCPIPRequest: &pssh.DirectTCPIPRequest{ + RequestType: ssh.DirectTCPIPRequestType, + SenderChannel: 5, + InitialWindowSize: 60, + MaximumPacketSize: 512, + Host: "127.0.0.1", + Port: 87654, + OriginatorIpAddress: "10.0.0.1", + OriginatorPort: 22, + }, + }, + }, + { + &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.EnvReqChunkType, + }, + func() []byte { + msg := &pssh.EnvRequest{ + RequestType: ssh.EnvRequestType, + WantReply: false, + VariableName: "FOO", + VariableValue: "bar", + } + data, err := proto.Marshal(msg) + require.NoError(t, err) + return data + }(), + &ssh.EnvRequest{ + BaseChunk: &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.EnvReqChunkType, + }, + EnvRequest: &pssh.EnvRequest{ + RequestType: ssh.EnvRequestType, + WantReply: false, + VariableName: "FOO", + VariableValue: "bar", + }, + }, + }, + { + &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.ExecReqChunkType, + }, + func() []byte { + msg := &pssh.ExecRequest{ + RequestType: ssh.ExecRequestType, + WantReply: false, + Command: "/bin/run-all-the-things.sh", + } + data, err := proto.Marshal(msg) + require.NoError(t, err) + return data + }(), + &ssh.ExecRequest{ + BaseChunk: &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.ExecReqChunkType, + }, + ExecRequest: &pssh.ExecRequest{ + RequestType: ssh.ExecRequestType, + WantReply: false, + Command: "/bin/run-all-the-things.sh", + }, + }, + }, + { + &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.ExitSignalReqChunkType, + }, + func() []byte { + msg := &pssh.ExitSignalRequest{ + RequestType: ssh.ExitSignalRequestType, + WantReply: false, + SignalName: "HUP", + CoreDumped: false, + ErrorMessage: "Failed Release", + LanguageTag: "en-US", + } + data, err := proto.Marshal(msg) + require.NoError(t, err) + return data + }(), + &ssh.ExitSignalRequest{ + BaseChunk: &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.ExitSignalReqChunkType, + }, + ExitSignalRequest: &pssh.ExitSignalRequest{ + RequestType: ssh.ExitSignalRequestType, + WantReply: false, + SignalName: "HUP", + CoreDumped: false, + ErrorMessage: "Failed Release", + LanguageTag: "en-US", + }, + }, + }, + { + &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.ExitStatusReqChunkType, + }, + func() []byte { + msg := &pssh.ExitStatusRequest{ + RequestType: ssh.ExitStatusRequestType, + WantReply: false, + ExitStatus: 34, + } + data, err := proto.Marshal(msg) + require.NoError(t, err) + return data + }(), + &ssh.ExitStatusRequest{ + BaseChunk: &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.ExitStatusReqChunkType, + }, + ExitStatusRequest: &pssh.ExitStatusRequest{ + RequestType: ssh.ExitStatusRequestType, + WantReply: false, + ExitStatus: 34, + }, + }, + }, + { + &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.ForwardedTCPIPReqChunkType, + }, + func() []byte { + msg := &pssh.ForwardedTCPIPRequest{ + RequestType: ssh.ForwardedTCPIPRequestType, + SenderChannel: 5, + InitialWindowSize: 70, + MaximumPacketSize: 1024, + Address: "10.0.0.45", + Port: 2222, + OriginatorIpAddress: "127.0.0.1", + OriginatorPort: 8765, + } + data, err := proto.Marshal(msg) + require.NoError(t, err) + return data + }(), + &ssh.ForwardedTCPIPRequest{ + BaseChunk: &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.ForwardedTCPIPReqChunkType, + }, + ForwardedTCPIPRequest: &pssh.ForwardedTCPIPRequest{ + RequestType: ssh.ForwardedTCPIPRequestType, + SenderChannel: 5, + InitialWindowSize: 70, + MaximumPacketSize: 1024, + Address: "10.0.0.45", + Port: 2222, + OriginatorIpAddress: "127.0.0.1", + OriginatorPort: 8765, + }, + }, + }, + { + &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.PtyReqChunkType, + }, + func() []byte { + msg := &pssh.PtyRequest{ + RequestType: ssh.PtyRequestType, + WantReply: false, + TermEnvVar: "xterm", + TerminalWidthCharacters: 80, + TerminalHeightRows: 60, + TerminalWidthPixels: 960, + TerminalHeightPixels: 1080, + EncodedTerminalMode: func() []byte { + var tm []byte + modes := []struct { + Key byte + Val uint32 + }{ + {gossh.ECHO, 0}, // disable echoing + {gossh.TTY_OP_ISPEED, 14400}, // input speed = 14.4kbaud + {gossh.TTY_OP_OSPEED, 14400}, // output speed = 14.4kbaud + } + for _, m := range modes { + tm = append(tm, gossh.Marshal(&m)...) + } + return tm + }(), + } + data, err := proto.Marshal(msg) + require.NoError(t, err) + return data + }(), + &ssh.PtyRequest{ + BaseChunk: &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.PtyReqChunkType, + }, + PtyRequest: &pssh.PtyRequest{ + RequestType: ssh.PtyRequestType, + WantReply: false, + TermEnvVar: "xterm", + TerminalWidthCharacters: 80, + TerminalHeightRows: 60, + TerminalWidthPixels: 960, + TerminalHeightPixels: 1080, + EncodedTerminalMode: func() []byte { + var tm []byte + modes := []struct { + Key byte + Val uint32 + }{ + {gossh.ECHO, 0}, // disable echoing + {gossh.TTY_OP_ISPEED, 14400}, // input speed = 14.4kbaud + {gossh.TTY_OP_OSPEED, 14400}, // output speed = 14.4kbaud + } + for _, m := range modes { + tm = append(tm, gossh.Marshal(&m)...) + } + return tm + }(), + }, + }, + }, + { + &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.SessionReqChunkType, + }, + func() []byte { + msg := &pssh.SessionRequest{ + RequestType: ssh.SessionRequestType, + SenderChannel: 5, + InitialWindowSize: 60, + MaximumPacketSize: 512, + } + data, err := proto.Marshal(msg) + require.NoError(t, err) + return data + }(), + &ssh.SessionRequest{ + BaseChunk: &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.SessionReqChunkType, + }, + SessionRequest: &pssh.SessionRequest{ + RequestType: ssh.SessionRequestType, + SenderChannel: 5, + InitialWindowSize: 60, + MaximumPacketSize: 512, + }, + }, + }, + { + &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.ShellReqChunkType, + }, + func() []byte { + msg := &pssh.ShellRequest{ + RequestType: ssh.ShellRequestType, + WantReply: false, + } + data, err := proto.Marshal(msg) + require.NoError(t, err) + return data + }(), + &ssh.ShellRequest{ + BaseChunk: &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.ShellReqChunkType, + }, + ShellRequest: &pssh.ShellRequest{ + RequestType: ssh.ShellRequestType, + WantReply: false, + }, + }, + }, + { + &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.SignalReqChunkType, + }, + func() []byte { + msg := &pssh.SignalRequest{ + RequestType: ssh.SignalRequestType, + WantReply: false, + SignalName: "USR1", + } + data, err := proto.Marshal(msg) + require.NoError(t, err) + return data + }(), + &ssh.SignalRequest{ + BaseChunk: &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.SignalReqChunkType, + }, + SignalRequest: &pssh.SignalRequest{ + RequestType: ssh.SignalRequestType, + WantReply: false, + SignalName: "USR1", + }, + }, + }, + { + &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.SubsystemReqChunkType, + }, + func() []byte { + msg := &pssh.SubsystemRequest{ + RequestType: ssh.SubsystemRequestType, + WantReply: false, + SubsystemName: "sftp", + } + data, err := proto.Marshal(msg) + require.NoError(t, err) + return data + }(), + &ssh.SubsystemRequest{ + BaseChunk: &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.SubsystemReqChunkType, + }, + SubsystemRequest: &pssh.SubsystemRequest{ + RequestType: ssh.SubsystemRequestType, + WantReply: false, + SubsystemName: "sftp", + }, + }, + }, + { + &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.TCPIPForwardReqChunkType, + }, + func() []byte { + msg := &pssh.TCPIPForwardRequest{ + RequestType: ssh.TCPIPForwardRequestType, + WantReply: false, + AddressToBind: "127.0.0.1", + PortToBind: 2222, + } + data, err := proto.Marshal(msg) + require.NoError(t, err) + return data + }(), + &ssh.TCPIPForwardRequest{ + BaseChunk: &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.TCPIPForwardReqChunkType, + }, + TCPIPForwardRequest: &pssh.TCPIPForwardRequest{ + RequestType: ssh.TCPIPForwardRequestType, + WantReply: false, + AddressToBind: "127.0.0.1", + PortToBind: 2222, + }, + }, + }, + { + &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.WindowChangeReqChunkType, + }, + func() []byte { + msg := &pssh.WindowChangeRequest{ + RequestType: ssh.WindowChangeRequestType, + WantReply: false, + TerminalWidthColumns: 120, + TerminalHeightRows: 120, + TerminalWidthPixels: 1920, + TerminalHeightPixels: 1080, + } + data, err := proto.Marshal(msg) + require.NoError(t, err) + return data + }(), + &ssh.WindowChangeRequest{ + BaseChunk: &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.WindowChangeReqChunkType, + }, + WindowChangeRequest: &pssh.WindowChangeRequest{ + RequestType: ssh.WindowChangeRequestType, + WantReply: false, + TerminalWidthColumns: 120, + TerminalHeightRows: 120, + TerminalWidthPixels: 1920, + TerminalHeightPixels: 1080, + }, + }, + }, + { + &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.X11ForwardingReqChunkType, + }, + func() []byte { + msg := &pssh.X11ForwardingRequest{ + RequestType: ssh.X11ForwardingRequestType, + WantReply: false, + SingleConnection: true, + X11AuthenticationProtocol: "MIT-MAGIC-COOKIE-1", + X11AuthenticationCookie: "6D6167696320636F6F6B6965", + X11ScreenNumber: 10, + } + data, err := proto.Marshal(msg) + require.NoError(t, err) + return data + }(), + &ssh.X11ForwardingRequest{ + BaseChunk: &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.X11ForwardingReqChunkType, + }, + X11ForwardingRequest: &pssh.X11ForwardingRequest{ + RequestType: ssh.X11ForwardingRequestType, + WantReply: false, + SingleConnection: true, + X11AuthenticationProtocol: "MIT-MAGIC-COOKIE-1", + X11AuthenticationCookie: "6D6167696320636F6F6B6965", + X11ScreenNumber: 10, + }, + }, + }, + { + &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.X11ReqChunkType, + }, + func() []byte { + msg := &pssh.X11Request{ + RequestType: ssh.X11RequestType, + SenderChannel: 6, + InitialWindowSize: 40, + MaximumPacketSize: 512, + OriginatorAddress: "127.0.0.1", + OriginatorPort: 2222, + } + data, err := proto.Marshal(msg) + require.NoError(t, err) + return data + }(), + &ssh.X11Request{ + BaseChunk: &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.X11ReqChunkType, + }, + X11Request: &pssh.X11Request{ + RequestType: ssh.X11RequestType, + SenderChannel: 6, + InitialWindowSize: 40, + MaximumPacketSize: 512, + OriginatorAddress: "127.0.0.1", + OriginatorPort: 2222, + }, + }, + }, + { + &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.XonXoffReqChunkType, + }, + func() []byte { + msg := &pssh.XonXoffRequest{ + RequestType: ssh.X11RequestType, + WantReply: false, + ClientCanDo: true, + } + data, err := proto.Marshal(msg) + require.NoError(t, err) + return data + }(), + &ssh.XonXoffRequest{ + BaseChunk: &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.XonXoffReqChunkType, + }, + XonXoffRequest: &pssh.XonXoffRequest{ + RequestType: ssh.X11RequestType, + WantReply: false, + ClientCanDo: true, + }, + }, + }, + { + &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.UnknownReqChunkType, + }, + func() []byte { + msg := &pssh.UnknownRequest{ + RequestType: "unknown", + WantReply: false, + Data: []byte("foo"), + } + data, err := proto.Marshal(msg) + require.NoError(t, err) + return data + }(), + &ssh.UnknownRequest{ + BaseChunk: &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.UnknownReqChunkType, + }, + UnknownRequest: &pssh.UnknownRequest{ + RequestType: "unknown", + WantReply: false, + Data: []byte("foo"), + }, + }, + }, + } + + for _, tc := range cases { + t.Run(string(tc.bc.Type), func(t *testing.T) { + got, err := ssh.DecodeChunk(ctx, tc.bc, tc.encoded) + require.NoError(t, err) + + assert.Empty(t, + cmp.Diff( + got, tc.want, + cmpopts.IgnoreUnexported( + bsr.BaseChunk{}, + pssh.BreakRequest{}, + pssh.CancelTCPIPForwardRequest{}, + pssh.DirectTCPIPRequest{}, + pssh.EnvRequest{}, + pssh.ExecRequest{}, + pssh.ExitSignalRequest{}, + pssh.ExitStatusRequest{}, + pssh.ForwardedTCPIPRequest{}, + pssh.PtyRequest{}, + pssh.SessionRequest{}, + pssh.ShellRequest{}, + pssh.SignalRequest{}, + pssh.SubsystemRequest{}, + pssh.TCPIPForwardRequest{}, + pssh.WindowChangeRequest{}, + pssh.X11ForwardingRequest{}, + pssh.X11Request{}, + pssh.XonXoffRequest{}, + pssh.UnknownRequest{}, + ), + ), + ) + }) + } +} + +func TestDecodeChunkErrors(t *testing.T) { + ctx := context.Background() + + cases := []struct { + name string + bc *bsr.BaseChunk + encoded []byte + want error + }{ + { + "not-ssh-protocol", + &bsr.BaseChunk{ + Protocol: "TEST", + Type: ssh.DataChunkType, + }, + []byte("foo"), + errors.New("ssh.DecodeChunk: invalid protocol TEST"), + }, + { + "unsupported-chunk-type", + &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: "TEST", + }, + []byte("foo"), + errors.New("ssh.DecodeChunk: unsupported chunk type TEST"), + }, + { + "nil-base-chunk", + nil, + []byte("foo"), + errors.New("ssh.DecodeChunk: nil base chunk: invalid parameter"), + }, + { + "no-data", + &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.BreakReqChunkType, + }, + []byte(""), + errors.New("ssh.DecodeChunk: not enough data"), + }, + { + "invalid-data", + &bsr.BaseChunk{ + Protocol: ssh.Protocol, + Type: ssh.BreakReqChunkType, + }, + []byte("foo"), + errors.New("ssh.DecodeChunk: proto: cannot parse invalid wire-format data"), + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + _, err := ssh.DecodeChunk(ctx, tc.bc, tc.encoded) + require.EqualError(t, err, tc.want.Error()) + }) + } +} diff --git a/internal/bsr/ssh/chunk_direct_tcpip_request.go b/internal/bsr/ssh/chunk_direct_tcpip_request.go index a5f0d37d9..27ecd6cd4 100644 --- a/internal/bsr/ssh/chunk_direct_tcpip_request.go +++ b/internal/bsr/ssh/chunk_direct_tcpip_request.go @@ -18,7 +18,7 @@ const DirectTCPIPRequestType = "direct-tcpip" // DirectTCPIPRequest is a chunk to contain data for an SSH Direct TCPIP request type DirectTCPIPRequest struct { - bsr.BaseChunk + *bsr.BaseChunk *pssh.DirectTCPIPRequest } @@ -63,7 +63,7 @@ func NewDirectTCPIPRequest(ctx context.Context, d bsr.Direction, t *bsr.Timestam return nil, fmt.Errorf("%s: unable to unmarshal payload: %w", op, err) } reqData := &DirectTCPIPRequest{ - BaseChunk: *baseChunk, + BaseChunk: baseChunk, DirectTCPIPRequest: &pssh.DirectTCPIPRequest{ RequestType: r.Type, SenderChannel: sigval.SenderChannel, diff --git a/internal/bsr/ssh/chunk_env_request.go b/internal/bsr/ssh/chunk_env_request.go index 7e1c8f1d3..ae6f5b9eb 100644 --- a/internal/bsr/ssh/chunk_env_request.go +++ b/internal/bsr/ssh/chunk_env_request.go @@ -18,7 +18,7 @@ const EnvRequestType = "env" // EnvRequest is a chunk to contain data for an SSH Env request type EnvRequest struct { - bsr.BaseChunk + *bsr.BaseChunk *pssh.EnvRequest } @@ -63,7 +63,7 @@ func NewEnvRequest(ctx context.Context, d bsr.Direction, t *bsr.Timestamp, r *gs return nil, fmt.Errorf("%s: unable to unmarshal payload: %w", op, err) } reqData := &EnvRequest{ - BaseChunk: *baseChunk, + BaseChunk: baseChunk, EnvRequest: &pssh.EnvRequest{ RequestType: r.Type, WantReply: r.WantReply, diff --git a/internal/bsr/ssh/chunk_exec_request.go b/internal/bsr/ssh/chunk_exec_request.go index 419ce4131..a6f3ff088 100644 --- a/internal/bsr/ssh/chunk_exec_request.go +++ b/internal/bsr/ssh/chunk_exec_request.go @@ -18,7 +18,7 @@ const ExecRequestType = "exec" // ExecRequest is a chunk to contain data for an SSH Exec request type ExecRequest struct { - bsr.BaseChunk + *bsr.BaseChunk *pssh.ExecRequest } @@ -63,7 +63,7 @@ func NewExecRequest(ctx context.Context, d bsr.Direction, t *bsr.Timestamp, r *g return nil, fmt.Errorf("%s: unable to unmarshal payload: %w", op, err) } reqData := &ExecRequest{ - BaseChunk: *baseChunk, + BaseChunk: baseChunk, ExecRequest: &pssh.ExecRequest{ RequestType: r.Type, WantReply: r.WantReply, diff --git a/internal/bsr/ssh/chunk_exit_signal_request.go b/internal/bsr/ssh/chunk_exit_signal_request.go index 88b72df48..e4a5b02d4 100644 --- a/internal/bsr/ssh/chunk_exit_signal_request.go +++ b/internal/bsr/ssh/chunk_exit_signal_request.go @@ -18,7 +18,7 @@ const ExitSignalRequestType = "exit-signal" // ExitSignalRequest is a chunk to contain data for an SSH Exit Signal request type ExitSignalRequest struct { - bsr.BaseChunk + *bsr.BaseChunk *pssh.ExitSignalRequest } @@ -63,7 +63,7 @@ func NewExitSignalRequest(ctx context.Context, d bsr.Direction, t *bsr.Timestamp return nil, fmt.Errorf("%s: unable to unmarshal payload: %w", op, err) } reqData := &ExitSignalRequest{ - BaseChunk: *baseChunk, + BaseChunk: baseChunk, ExitSignalRequest: &pssh.ExitSignalRequest{ RequestType: r.Type, WantReply: r.WantReply, diff --git a/internal/bsr/ssh/chunk_exit_status_request.go b/internal/bsr/ssh/chunk_exit_status_request.go index 9db2a33dd..8b4b2cd22 100644 --- a/internal/bsr/ssh/chunk_exit_status_request.go +++ b/internal/bsr/ssh/chunk_exit_status_request.go @@ -18,7 +18,7 @@ const ExitStatusRequestType = "exit-status" // ExitStatusRequest is a chunk to contain data for an SSH Exit Status request type ExitStatusRequest struct { - bsr.BaseChunk + *bsr.BaseChunk *pssh.ExitStatusRequest } @@ -63,7 +63,7 @@ func NewExitStatusRequest(ctx context.Context, d bsr.Direction, t *bsr.Timestamp return nil, fmt.Errorf("%s: unable to unmarshal payload: %w", op, err) } reqData := &ExitStatusRequest{ - BaseChunk: *baseChunk, + BaseChunk: baseChunk, ExitStatusRequest: &pssh.ExitStatusRequest{ RequestType: r.Type, WantReply: r.WantReply, diff --git a/internal/bsr/ssh/chunk_forwarded_tcpip_request.go b/internal/bsr/ssh/chunk_forwarded_tcpip_request.go index f5d4eecab..d25f35ccb 100644 --- a/internal/bsr/ssh/chunk_forwarded_tcpip_request.go +++ b/internal/bsr/ssh/chunk_forwarded_tcpip_request.go @@ -18,7 +18,7 @@ const ForwardedTCPIPRequestType = "forwarded-tcpip" // ForwardedTCPIPRequest is a chunk to contain data for an SSH Forwarded TCPIP request type ForwardedTCPIPRequest struct { - bsr.BaseChunk + *bsr.BaseChunk *pssh.ForwardedTCPIPRequest } @@ -63,7 +63,7 @@ func NewForwardedTCPIPRequest(ctx context.Context, d bsr.Direction, t *bsr.Times return nil, fmt.Errorf("%s: unable to unmarshal payload: %w", op, err) } reqData := &ForwardedTCPIPRequest{ - BaseChunk: *baseChunk, + BaseChunk: baseChunk, ForwardedTCPIPRequest: &pssh.ForwardedTCPIPRequest{ RequestType: r.Type, SenderChannel: sigval.SenderChannel, diff --git a/internal/bsr/ssh/chunk_pty_request.go b/internal/bsr/ssh/chunk_pty_request.go index 0fd88d7cb..2d9b564cd 100644 --- a/internal/bsr/ssh/chunk_pty_request.go +++ b/internal/bsr/ssh/chunk_pty_request.go @@ -18,7 +18,7 @@ const PtyRequestType = "pty-req" // PtyPRequest is a chunk to contain data for an SSH Pty request type PtyRequest struct { - bsr.BaseChunk + *bsr.BaseChunk *pssh.PtyRequest } @@ -63,7 +63,7 @@ func NewPtyRequest(ctx context.Context, d bsr.Direction, t *bsr.Timestamp, r *gs return nil, fmt.Errorf("%s: unable to unmarshal payload: %w", op, err) } reqData := &PtyRequest{ - BaseChunk: *baseChunk, + BaseChunk: baseChunk, PtyRequest: &pssh.PtyRequest{ RequestType: r.Type, WantReply: r.WantReply, diff --git a/internal/bsr/ssh/chunk_session_request.go b/internal/bsr/ssh/chunk_session_request.go index 6e0731274..602060dc1 100644 --- a/internal/bsr/ssh/chunk_session_request.go +++ b/internal/bsr/ssh/chunk_session_request.go @@ -18,7 +18,7 @@ const SessionRequestType = "session" // SessionRequest is a chunk to contain data for an SSH Session request type SessionRequest struct { - bsr.BaseChunk + *bsr.BaseChunk *pssh.SessionRequest } @@ -63,7 +63,7 @@ func NewSessionRequest(ctx context.Context, d bsr.Direction, t *bsr.Timestamp, r return nil, fmt.Errorf("%s: unable to unmarshal payload: %w", op, err) } reqData := &SessionRequest{ - BaseChunk: *baseChunk, + BaseChunk: baseChunk, SessionRequest: &pssh.SessionRequest{ RequestType: r.Type, SenderChannel: sigval.SenderChannel, diff --git a/internal/bsr/ssh/chunk_shell_request.go b/internal/bsr/ssh/chunk_shell_request.go index 278c10d13..9bb8285e1 100644 --- a/internal/bsr/ssh/chunk_shell_request.go +++ b/internal/bsr/ssh/chunk_shell_request.go @@ -18,7 +18,7 @@ const ShellRequestType = "shell" // ShellRequest is a chunk to contain data for an SSH Shell request type ShellRequest struct { - bsr.BaseChunk + *bsr.BaseChunk *pssh.ShellRequest } @@ -59,7 +59,7 @@ func NewShellRequest(ctx context.Context, d bsr.Direction, t *bsr.Timestamp, r * } reqData := &ShellRequest{ - BaseChunk: *baseChunk, + BaseChunk: baseChunk, ShellRequest: &pssh.ShellRequest{ RequestType: r.Type, WantReply: r.WantReply, diff --git a/internal/bsr/ssh/chunk_signal_request.go b/internal/bsr/ssh/chunk_signal_request.go index a19afcab8..e493ffe42 100644 --- a/internal/bsr/ssh/chunk_signal_request.go +++ b/internal/bsr/ssh/chunk_signal_request.go @@ -18,7 +18,7 @@ const SignalRequestType = "signal" // SignalRequest is a chunk to contain data for an SSH Signal request type SignalRequest struct { - bsr.BaseChunk + *bsr.BaseChunk *pssh.SignalRequest } @@ -63,7 +63,7 @@ func NewSignalRequest(ctx context.Context, d bsr.Direction, t *bsr.Timestamp, r return nil, fmt.Errorf("%s: unable to unmarshal payload: %w", op, err) } reqData := &SignalRequest{ - BaseChunk: *baseChunk, + BaseChunk: baseChunk, SignalRequest: &pssh.SignalRequest{ RequestType: r.Type, WantReply: r.WantReply, diff --git a/internal/bsr/ssh/chunk_subsystem_request.go b/internal/bsr/ssh/chunk_subsystem_request.go index fab88566c..601374191 100644 --- a/internal/bsr/ssh/chunk_subsystem_request.go +++ b/internal/bsr/ssh/chunk_subsystem_request.go @@ -18,7 +18,7 @@ const SubsystemRequestType = "subsystem" // SubsystemRequest is a chunk to contain data for an SSH Subsystem request type SubsystemRequest struct { - bsr.BaseChunk + *bsr.BaseChunk *pssh.SubsystemRequest } @@ -63,7 +63,7 @@ func NewSubsystemRequest(ctx context.Context, d bsr.Direction, t *bsr.Timestamp, return nil, fmt.Errorf("%s: unable to unmarshal payload: %w", op, err) } reqData := &SubsystemRequest{ - BaseChunk: *baseChunk, + BaseChunk: baseChunk, SubsystemRequest: &pssh.SubsystemRequest{ RequestType: r.Type, WantReply: r.WantReply, diff --git a/internal/bsr/ssh/chunk_tcpip_forward_request.go b/internal/bsr/ssh/chunk_tcpip_forward_request.go index 8f6ca7083..5173b19de 100644 --- a/internal/bsr/ssh/chunk_tcpip_forward_request.go +++ b/internal/bsr/ssh/chunk_tcpip_forward_request.go @@ -18,7 +18,7 @@ const TCPIPForwardRequestType = "tcpip-forward" // TCPIPForwardRequest is a chunk to contain data for an SSH TCPIP Forward request type TCPIPForwardRequest struct { - bsr.BaseChunk + *bsr.BaseChunk *pssh.TCPIPForwardRequest } @@ -63,7 +63,7 @@ func NewTCPIPForwardRequest(ctx context.Context, d bsr.Direction, t *bsr.Timesta return nil, fmt.Errorf("%s: unable to unmarshal payload: %w", op, err) } reqData := &TCPIPForwardRequest{ - BaseChunk: *baseChunk, + BaseChunk: baseChunk, TCPIPForwardRequest: &pssh.TCPIPForwardRequest{ RequestType: r.Type, WantReply: r.WantReply, diff --git a/internal/bsr/ssh/chunk_unknown_request.go b/internal/bsr/ssh/chunk_unknown_request.go index 32aeb8f1e..137c830e6 100644 --- a/internal/bsr/ssh/chunk_unknown_request.go +++ b/internal/bsr/ssh/chunk_unknown_request.go @@ -16,7 +16,7 @@ import ( // UnknownRequest is a chunk to contain data for any unrecognized SSH request type UnknownRequest struct { - bsr.BaseChunk + *bsr.BaseChunk *pssh.UnknownRequest } @@ -54,7 +54,7 @@ func NewUnknownRequest(ctx context.Context, d bsr.Direction, t *bsr.Timestamp, r } return &UnknownRequest{ - BaseChunk: *baseChunk, + BaseChunk: baseChunk, UnknownRequest: &pssh.UnknownRequest{ RequestType: r.Type, WantReply: r.WantReply, diff --git a/internal/bsr/ssh/chunk_window_change_request.go b/internal/bsr/ssh/chunk_window_change_request.go index eb0c55ec9..54a7ea94c 100644 --- a/internal/bsr/ssh/chunk_window_change_request.go +++ b/internal/bsr/ssh/chunk_window_change_request.go @@ -18,7 +18,7 @@ const WindowChangeRequestType = "window-change" // WindowChangeRequest is a chunk to contain data for an SSH Window Change request type WindowChangeRequest struct { - bsr.BaseChunk + *bsr.BaseChunk *pssh.WindowChangeRequest } @@ -63,7 +63,7 @@ func NewWindowChangeRequest(ctx context.Context, d bsr.Direction, t *bsr.Timesta return nil, fmt.Errorf("%s: unable to unmarshal payload: %w", op, err) } reqData := &WindowChangeRequest{ - BaseChunk: *baseChunk, + BaseChunk: baseChunk, WindowChangeRequest: &pssh.WindowChangeRequest{ RequestType: r.Type, WantReply: r.WantReply, diff --git a/internal/bsr/ssh/chunk_x11_forwarding_request.go b/internal/bsr/ssh/chunk_x11_forwarding_request.go index 7ec6dfa00..0883db0d5 100644 --- a/internal/bsr/ssh/chunk_x11_forwarding_request.go +++ b/internal/bsr/ssh/chunk_x11_forwarding_request.go @@ -18,7 +18,7 @@ const X11ForwardingRequestType = "x11-req" // X11ForwardingRequest is a chunk to contain data for an SSH X11 Forwarding request type X11ForwardingRequest struct { - bsr.BaseChunk + *bsr.BaseChunk *pssh.X11ForwardingRequest } @@ -63,7 +63,7 @@ func NewX11ForwardingRequest(ctx context.Context, d bsr.Direction, t *bsr.Timest return nil, fmt.Errorf("%s: unable to unmarshal payload: %w", op, err) } reqData := &X11ForwardingRequest{ - BaseChunk: *baseChunk, + BaseChunk: baseChunk, X11ForwardingRequest: &pssh.X11ForwardingRequest{ RequestType: r.Type, WantReply: r.WantReply, diff --git a/internal/bsr/ssh/chunk_x11_request.go b/internal/bsr/ssh/chunk_x11_request.go index b3e2a1c89..de4dd3cc9 100644 --- a/internal/bsr/ssh/chunk_x11_request.go +++ b/internal/bsr/ssh/chunk_x11_request.go @@ -18,7 +18,7 @@ const X11RequestType = "x11" // X11Request is a chunk to contain data for an SSH X11 request type X11Request struct { - bsr.BaseChunk + *bsr.BaseChunk *pssh.X11Request } @@ -63,7 +63,7 @@ func NewX11Request(ctx context.Context, d bsr.Direction, t *bsr.Timestamp, r *gs return nil, fmt.Errorf("%s: unable to unmarshal payload: %w", op, err) } reqData := &X11Request{ - BaseChunk: *baseChunk, + BaseChunk: baseChunk, X11Request: &pssh.X11Request{ RequestType: r.Type, SenderChannel: sigval.SenderChannel, diff --git a/internal/bsr/ssh/chunk_xonn_xoff_request.go b/internal/bsr/ssh/chunk_xonn_xoff_request.go index 63d0afdd7..a32cd5cbf 100644 --- a/internal/bsr/ssh/chunk_xonn_xoff_request.go +++ b/internal/bsr/ssh/chunk_xonn_xoff_request.go @@ -18,7 +18,7 @@ const XonXoffRequestType = "xon-xoff" // XonXoffRequest is a chunk to contain data for an SSH Xon Xoff request type XonXoffRequest struct { - bsr.BaseChunk + *bsr.BaseChunk *pssh.XonXoffRequest } @@ -63,7 +63,7 @@ func NewXonXoffRequest(ctx context.Context, d bsr.Direction, t *bsr.Timestamp, r return nil, fmt.Errorf("%s: unable to unmarshal payload: %w", op, err) } reqData := &XonXoffRequest{ - BaseChunk: *baseChunk, + BaseChunk: baseChunk, XonXoffRequest: &pssh.XonXoffRequest{ RequestType: r.Type, WantReply: r.WantReply,