package sftp

import (
	
	
	
	
	
	
	
	
)

var (
	errLongPacket            = errors.New("packet too long")
	errShortPacket           = errors.New("packet too short")
	errUnknownExtendedPacket = errors.New("unknown extended packet")
)

const (
	maxMsgLength           = 256 * 1024
	debugDumpTxPacket      = false
	debugDumpRxPacket      = false
	debugDumpTxPacketBytes = false
	debugDumpRxPacketBytes = false
)

func marshalUint32( []byte,  uint32) []byte {
	return append(, byte(>>24), byte(>>16), byte(>>8), byte())
}

func marshalUint64( []byte,  uint64) []byte {
	return marshalUint32(marshalUint32(, uint32(>>32)), uint32())
}

func marshalString( []byte,  string) []byte {
	return append(marshalUint32(, uint32(len())), ...)
}

func marshalFileInfo( []byte,  os.FileInfo) []byte {
	// attributes variable struct, and also variable per protocol version
	// spec version 3 attributes:
	// uint32   flags
	// uint64   size           present only if flag SSH_FILEXFER_ATTR_SIZE
	// uint32   uid            present only if flag SSH_FILEXFER_ATTR_UIDGID
	// uint32   gid            present only if flag SSH_FILEXFER_ATTR_UIDGID
	// uint32   permissions    present only if flag SSH_FILEXFER_ATTR_PERMISSIONS
	// uint32   atime          present only if flag SSH_FILEXFER_ACMODTIME
	// uint32   mtime          present only if flag SSH_FILEXFER_ACMODTIME
	// uint32   extended_count present only if flag SSH_FILEXFER_ATTR_EXTENDED
	// string   extended_type
	// string   extended_data
	// ...      more extended data (extended_type - extended_data pairs),
	// 	   so that number of pairs equals extended_count

	,  := fileStatFromInfo()

	 = marshalUint32(, )
	if &sshFileXferAttrSize != 0 {
		 = marshalUint64(, .Size)
	}
	if &sshFileXferAttrUIDGID != 0 {
		 = marshalUint32(, .UID)
		 = marshalUint32(, .GID)
	}
	if &sshFileXferAttrPermissions != 0 {
		 = marshalUint32(, .Mode)
	}
	if &sshFileXferAttrACmodTime != 0 {
		 = marshalUint32(, .Atime)
		 = marshalUint32(, .Mtime)
	}

	if &sshFileXferAttrExtended != 0 {
		 = marshalUint32(, uint32(len(.Extended)))

		for ,  := range .Extended {
			 = marshalString(, .ExtType)
			 = marshalString(, .ExtData)
		}
	}

	return 
}

func marshalStatus( []byte,  StatusError) []byte {
	 = marshalUint32(, .Code)
	 = marshalString(, .msg)
	 = marshalString(, .lang)
	return 
}

func marshal( []byte,  interface{}) []byte {
	if  == nil {
		return 
	}
	switch v := .(type) {
	case uint8:
		return append(, )
	case uint32:
		return marshalUint32(, )
	case uint64:
		return marshalUint64(, )
	case string:
		return marshalString(, )
	case os.FileInfo:
		return marshalFileInfo(, )
	default:
		switch  := reflect.ValueOf(); .Kind() {
		case reflect.Struct:
			for ,  := 0, .NumField();  < ; ++ {
				 = (, .Field().Interface())
			}
			return 
		case reflect.Slice:
			for ,  := 0, .Len();  < ; ++ {
				 = (, .Index().Interface())
			}
			return 
		default:
			panic(fmt.Sprintf("marshal(%#v): cannot handle type %T", , ))
		}
	}
}

func unmarshalUint32( []byte) (uint32, []byte) {
	 := uint32([3]) | uint32([2])<<8 | uint32([1])<<16 | uint32([0])<<24
	return , [4:]
}

func unmarshalUint32Safe( []byte) (uint32, []byte, error) {
	var  uint32
	if len() < 4 {
		return 0, nil, errShortPacket
	}
	,  = unmarshalUint32()
	return , , nil
}

func unmarshalUint64( []byte) (uint64, []byte) {
	,  := unmarshalUint32()
	,  := unmarshalUint32()
	return uint64()<<32 | uint64(), 
}

func unmarshalUint64Safe( []byte) (uint64, []byte, error) {
	var  uint64
	if len() < 8 {
		return 0, nil, errShortPacket
	}
	,  = unmarshalUint64()
	return , , nil
}

func unmarshalString( []byte) (string, []byte) {
	,  := unmarshalUint32()
	return string([:]), [:]
}

func unmarshalStringSafe( []byte) (string, []byte, error) {
	, ,  := unmarshalUint32Safe()
	if  != nil {
		return "", nil, 
	}
	if int64() > int64(len()) {
		return "", nil, errShortPacket
	}
	return string([:]), [:], nil
}

func unmarshalAttrs( []byte) (*FileStat, []byte) {
	,  := unmarshalUint32()
	return unmarshalFileStat(, )
}

func unmarshalFileStat( uint32,  []byte) (*FileStat, []byte) {
	var  FileStat
	if &sshFileXferAttrSize == sshFileXferAttrSize {
		.Size, , _ = unmarshalUint64Safe()
	}
	if &sshFileXferAttrUIDGID == sshFileXferAttrUIDGID {
		.UID, , _ = unmarshalUint32Safe()
	}
	if &sshFileXferAttrUIDGID == sshFileXferAttrUIDGID {
		.GID, , _ = unmarshalUint32Safe()
	}
	if &sshFileXferAttrPermissions == sshFileXferAttrPermissions {
		.Mode, , _ = unmarshalUint32Safe()
	}
	if &sshFileXferAttrACmodTime == sshFileXferAttrACmodTime {
		.Atime, , _ = unmarshalUint32Safe()
		.Mtime, , _ = unmarshalUint32Safe()
	}
	if &sshFileXferAttrExtended == sshFileXferAttrExtended {
		var  uint32
		, , _ = unmarshalUint32Safe()
		 := make([]StatExtended, )
		for  := uint32(0);  < ; ++ {
			var  string
			var  string
			, , _ = unmarshalStringSafe()
			, , _ = unmarshalStringSafe()
			[] = StatExtended{
				ExtType: ,
				ExtData: ,
			}
		}
		.Extended = 
	}
	return &, 
}

func unmarshalStatus( uint32,  []byte) error {
	,  := unmarshalUint32()
	if  !=  {
		return &unexpectedIDErr{, }
	}
	,  := unmarshalUint32()
	, ,  := unmarshalStringSafe()
	, ,  := unmarshalStringSafe()
	return &StatusError{
		Code: ,
		msg:  ,
		lang: ,
	}
}

type packetMarshaler interface {
	marshalPacket() (header, payload []byte, err error)
}

func marshalPacket( encoding.BinaryMarshaler) (,  []byte,  error) {
	if ,  := .(packetMarshaler);  {
		return .marshalPacket()
	}

	,  = .MarshalBinary()
	return
}

// sendPacket marshals p according to RFC 4234.
func sendPacket( io.Writer,  encoding.BinaryMarshaler) error {
	, ,  := marshalPacket()
	if  != nil {
		return fmt.Errorf("binary marshaller failed: %w", )
	}

	 := len() + len() - 4 // subtract the uint32(length) from the start
	if debugDumpTxPacketBytes {
		debug("send packet: %s %d bytes %x%x", fxp([4]), , [5:], )
	} else if debugDumpTxPacket {
		debug("send packet: %s %d bytes", fxp([4]), )
	}

	binary.BigEndian.PutUint32([:4], uint32())

	if ,  := .Write();  != nil {
		return fmt.Errorf("failed to send packet: %w", )
	}

	if len() > 0 {
		if ,  := .Write();  != nil {
			return fmt.Errorf("failed to send packet payload: %w", )
		}
	}

	return nil
}

func recvPacket( io.Reader,  *allocator,  uint32) (uint8, []byte, error) {
	var  []byte
	if  != nil {
		 = .GetPage()
	} else {
		 = make([]byte, 4)
	}
	if ,  := io.ReadFull(, [:4]);  != nil {
		return 0, nil, 
	}
	,  := unmarshalUint32()
	if  > maxMsgLength {
		debug("recv packet %d bytes too long", )
		return 0, nil, errLongPacket
	}
	if  == 0 {
		debug("recv packet of 0 bytes too short")
		return 0, nil, errShortPacket
	}
	if  == nil {
		 = make([]byte, )
	}
	if ,  := io.ReadFull(, [:]);  != nil {
		// ReadFull only returns EOF if it has read no bytes.
		// In this case, that means a partial packet, and thus unexpected.
		if  == io.EOF {
			 = io.ErrUnexpectedEOF
		}
		debug("recv packet %d bytes: err %v", , )
		return 0, nil, 
	}
	if debugDumpRxPacketBytes {
		debug("recv packet: %s %d bytes %x", fxp([0]), , [1:])
	} else if debugDumpRxPacket {
		debug("recv packet: %s %d bytes", fxp([0]), )
	}
	return [0], [1:], nil
}

type extensionPair struct {
	Name string
	Data string
}

func unmarshalExtensionPair( []byte) (extensionPair, []byte, error) {
	var  extensionPair
	var  error
	.Name, ,  = unmarshalStringSafe()
	if  != nil {
		return , , 
	}
	.Data, ,  = unmarshalStringSafe()
	return , , 
}

// Here starts the definition of packets along with their MarshalBinary
// implementations.
// Manually writing the marshalling logic wins us a lot of time and
// allocation.

type sshFxInitPacket struct {
	Version    uint32
	Extensions []extensionPair
}

func ( *sshFxInitPacket) () ([]byte, error) {
	 := 4 + 1 + 4 // uint32(length) + byte(type) + uint32(version)
	for ,  := range .Extensions {
		 += 4 + len(.Name) + 4 + len(.Data)
	}

	 := make([]byte, 4, )
	 = append(, sshFxpInit)
	 = marshalUint32(, .Version)

	for ,  := range .Extensions {
		 = marshalString(, .Name)
		 = marshalString(, .Data)
	}

	return , nil
}

func ( *sshFxInitPacket) ( []byte) error {
	var  error
	if .Version, ,  = unmarshalUint32Safe();  != nil {
		return 
	}
	for len() > 0 {
		var  extensionPair
		, ,  = unmarshalExtensionPair()
		if  != nil {
			return 
		}
		.Extensions = append(.Extensions, )
	}
	return nil
}

type sshFxVersionPacket struct {
	Version    uint32
	Extensions []sshExtensionPair
}

type sshExtensionPair struct {
	Name, Data string
}

func ( *sshFxVersionPacket) () ([]byte, error) {
	 := 4 + 1 + 4 // uint32(length) + byte(type) + uint32(version)
	for ,  := range .Extensions {
		 += 4 + len(.Name) + 4 + len(.Data)
	}

	 := make([]byte, 4, )
	 = append(, sshFxpVersion)
	 = marshalUint32(, .Version)

	for ,  := range .Extensions {
		 = marshalString(, .Name)
		 = marshalString(, .Data)
	}

	return , nil
}

func marshalIDStringPacket( byte,  uint32,  string) ([]byte, error) {
	 := 4 + 1 + 4 + // uint32(length) + byte(type) + uint32(id)
		4 + len()

	 := make([]byte, 4, )
	 = append(, )
	 = marshalUint32(, )
	 = marshalString(, )

	return , nil
}

func unmarshalIDString( []byte,  *uint32,  *string) error {
	var  error
	*, ,  = unmarshalUint32Safe()
	if  != nil {
		return 
	}
	*, _,  = unmarshalStringSafe()
	return 
}

type sshFxpReaddirPacket struct {
	ID     uint32
	Handle string
}

func ( *sshFxpReaddirPacket) () uint32 { return .ID }

func ( *sshFxpReaddirPacket) () ([]byte, error) {
	return marshalIDStringPacket(sshFxpReaddir, .ID, .Handle)
}

func ( *sshFxpReaddirPacket) ( []byte) error {
	return unmarshalIDString(, &.ID, &.Handle)
}

type sshFxpOpendirPacket struct {
	ID   uint32
	Path string
}

func ( *sshFxpOpendirPacket) () uint32 { return .ID }

func ( *sshFxpOpendirPacket) () ([]byte, error) {
	return marshalIDStringPacket(sshFxpOpendir, .ID, .Path)
}

func ( *sshFxpOpendirPacket) ( []byte) error {
	return unmarshalIDString(, &.ID, &.Path)
}

type sshFxpLstatPacket struct {
	ID   uint32
	Path string
}

func ( *sshFxpLstatPacket) () uint32 { return .ID }

func ( *sshFxpLstatPacket) () ([]byte, error) {
	return marshalIDStringPacket(sshFxpLstat, .ID, .Path)
}

func ( *sshFxpLstatPacket) ( []byte) error {
	return unmarshalIDString(, &.ID, &.Path)
}

type sshFxpStatPacket struct {
	ID   uint32
	Path string
}

func ( *sshFxpStatPacket) () uint32 { return .ID }

func ( *sshFxpStatPacket) () ([]byte, error) {
	return marshalIDStringPacket(sshFxpStat, .ID, .Path)
}

func ( *sshFxpStatPacket) ( []byte) error {
	return unmarshalIDString(, &.ID, &.Path)
}

type sshFxpFstatPacket struct {
	ID     uint32
	Handle string
}

func ( *sshFxpFstatPacket) () uint32 { return .ID }

func ( *sshFxpFstatPacket) () ([]byte, error) {
	return marshalIDStringPacket(sshFxpFstat, .ID, .Handle)
}

func ( *sshFxpFstatPacket) ( []byte) error {
	return unmarshalIDString(, &.ID, &.Handle)
}

type sshFxpClosePacket struct {
	ID     uint32
	Handle string
}

func ( *sshFxpClosePacket) () uint32 { return .ID }

func ( *sshFxpClosePacket) () ([]byte, error) {
	return marshalIDStringPacket(sshFxpClose, .ID, .Handle)
}

func ( *sshFxpClosePacket) ( []byte) error {
	return unmarshalIDString(, &.ID, &.Handle)
}

type sshFxpRemovePacket struct {
	ID       uint32
	Filename string
}

func ( *sshFxpRemovePacket) () uint32 { return .ID }

func ( *sshFxpRemovePacket) () ([]byte, error) {
	return marshalIDStringPacket(sshFxpRemove, .ID, .Filename)
}

func ( *sshFxpRemovePacket) ( []byte) error {
	return unmarshalIDString(, &.ID, &.Filename)
}

type sshFxpRmdirPacket struct {
	ID   uint32
	Path string
}

func ( *sshFxpRmdirPacket) () uint32 { return .ID }

func ( *sshFxpRmdirPacket) () ([]byte, error) {
	return marshalIDStringPacket(sshFxpRmdir, .ID, .Path)
}

func ( *sshFxpRmdirPacket) ( []byte) error {
	return unmarshalIDString(, &.ID, &.Path)
}

type sshFxpSymlinkPacket struct {
	ID uint32

	// The order of the arguments to the SSH_FXP_SYMLINK method was inadvertently reversed.
	// Unfortunately, the reversal was not noticed until the server was widely deployed.
	// Covered in Section 4.1 of https://github.com/openssh/openssh-portable/blob/master/PROTOCOL

	Targetpath string
	Linkpath   string
}

func ( *sshFxpSymlinkPacket) () uint32 { return .ID }

func ( *sshFxpSymlinkPacket) () ([]byte, error) {
	 := 4 + 1 + 4 + // uint32(length) + byte(type) + uint32(id)
		4 + len(.Targetpath) +
		4 + len(.Linkpath)

	 := make([]byte, 4, )
	 = append(, sshFxpSymlink)
	 = marshalUint32(, .ID)
	 = marshalString(, .Targetpath)
	 = marshalString(, .Linkpath)

	return , nil
}

func ( *sshFxpSymlinkPacket) ( []byte) error {
	var  error
	if .ID, ,  = unmarshalUint32Safe();  != nil {
		return 
	} else if .Targetpath, ,  = unmarshalStringSafe();  != nil {
		return 
	} else if .Linkpath, _,  = unmarshalStringSafe();  != nil {
		return 
	}
	return nil
}

type sshFxpHardlinkPacket struct {
	ID      uint32
	Oldpath string
	Newpath string
}

func ( *sshFxpHardlinkPacket) () uint32 { return .ID }

func ( *sshFxpHardlinkPacket) () ([]byte, error) {
	const  = "hardlink@openssh.com"
	 := 4 + 1 + 4 + // uint32(length) + byte(type) + uint32(id)
		4 + len() +
		4 + len(.Oldpath) +
		4 + len(.Newpath)

	 := make([]byte, 4, )
	 = append(, sshFxpExtended)
	 = marshalUint32(, .ID)
	 = marshalString(, )
	 = marshalString(, .Oldpath)
	 = marshalString(, .Newpath)

	return , nil
}

type sshFxpReadlinkPacket struct {
	ID   uint32
	Path string
}

func ( *sshFxpReadlinkPacket) () uint32 { return .ID }

func ( *sshFxpReadlinkPacket) () ([]byte, error) {
	return marshalIDStringPacket(sshFxpReadlink, .ID, .Path)
}

func ( *sshFxpReadlinkPacket) ( []byte) error {
	return unmarshalIDString(, &.ID, &.Path)
}

type sshFxpRealpathPacket struct {
	ID   uint32
	Path string
}

func ( *sshFxpRealpathPacket) () uint32 { return .ID }

func ( *sshFxpRealpathPacket) () ([]byte, error) {
	return marshalIDStringPacket(sshFxpRealpath, .ID, .Path)
}

func ( *sshFxpRealpathPacket) ( []byte) error {
	return unmarshalIDString(, &.ID, &.Path)
}

type sshFxpNameAttr struct {
	Name     string
	LongName string
	Attrs    []interface{}
}

func ( *sshFxpNameAttr) () ([]byte, error) {
	var  []byte
	 = marshalString(, .Name)
	 = marshalString(, .LongName)
	for ,  := range .Attrs {
		 = marshal(, )
	}
	return , nil
}

type sshFxpNamePacket struct {
	ID        uint32
	NameAttrs []*sshFxpNameAttr
}

func ( *sshFxpNamePacket) () ([]byte, []byte, error) {
	 := 4 + 1 + 4 + // uint32(length) + byte(type) + uint32(id)
		4

	 := make([]byte, 4, )
	 = append(, sshFxpName)
	 = marshalUint32(, .ID)
	 = marshalUint32(, uint32(len(.NameAttrs)))

	var  []byte
	for ,  := range .NameAttrs {
		,  := .MarshalBinary()
		if  != nil {
			return nil, nil, 
		}

		 = append(, ...)
	}

	return , , nil
}

func ( *sshFxpNamePacket) () ([]byte, error) {
	, ,  := .marshalPacket()
	return append(, ...), 
}

type sshFxpOpenPacket struct {
	ID     uint32
	Path   string
	Pflags uint32
	Flags  uint32 // ignored
}

func ( *sshFxpOpenPacket) () uint32 { return .ID }

func ( *sshFxpOpenPacket) () ([]byte, error) {
	 := 4 + 1 + 4 + // uint32(length) + byte(type) + uint32(id)
		4 + len(.Path) +
		4 + 4

	 := make([]byte, 4, )
	 = append(, sshFxpOpen)
	 = marshalUint32(, .ID)
	 = marshalString(, .Path)
	 = marshalUint32(, .Pflags)
	 = marshalUint32(, .Flags)

	return , nil
}

func ( *sshFxpOpenPacket) ( []byte) error {
	var  error
	if .ID, ,  = unmarshalUint32Safe();  != nil {
		return 
	} else if .Path, ,  = unmarshalStringSafe();  != nil {
		return 
	} else if .Pflags, ,  = unmarshalUint32Safe();  != nil {
		return 
	} else if .Flags, _,  = unmarshalUint32Safe();  != nil {
		return 
	}
	return nil
}

type sshFxpReadPacket struct {
	ID     uint32
	Len    uint32
	Offset uint64
	Handle string
}

func ( *sshFxpReadPacket) () uint32 { return .ID }

func ( *sshFxpReadPacket) () ([]byte, error) {
	 := 4 + 1 + 4 + // uint32(length) + byte(type) + uint32(id)
		4 + len(.Handle) +
		8 + 4 // uint64 + uint32

	 := make([]byte, 4, )
	 = append(, sshFxpRead)
	 = marshalUint32(, .ID)
	 = marshalString(, .Handle)
	 = marshalUint64(, .Offset)
	 = marshalUint32(, .Len)

	return , nil
}

func ( *sshFxpReadPacket) ( []byte) error {
	var  error
	if .ID, ,  = unmarshalUint32Safe();  != nil {
		return 
	} else if .Handle, ,  = unmarshalStringSafe();  != nil {
		return 
	} else if .Offset, ,  = unmarshalUint64Safe();  != nil {
		return 
	} else if .Len, _,  = unmarshalUint32Safe();  != nil {
		return 
	}
	return nil
}

// We need allocate bigger slices with extra capacity to avoid a re-allocation in sshFxpDataPacket.MarshalBinary
// So, we need: uint32(length) + byte(type) + uint32(id) + uint32(data_length)
const dataHeaderLen = 4 + 1 + 4 + 4

func ( *sshFxpReadPacket) ( *allocator,  uint32) []byte {
	 := .Len
	if  > maxTxPacket {
		 = maxTxPacket
	}

	if  != nil {
		// GetPage returns a slice with capacity = maxMsgLength this is enough to avoid new allocations in
		// sshFxpDataPacket.MarshalBinary
		return .GetPage()[:]
	}

	// allocate with extra space for the header
	return make([]byte, , +dataHeaderLen)
}

type sshFxpRenamePacket struct {
	ID      uint32
	Oldpath string
	Newpath string
}

func ( *sshFxpRenamePacket) () uint32 { return .ID }

func ( *sshFxpRenamePacket) () ([]byte, error) {
	 := 4 + 1 + 4 + // uint32(length) + byte(type) + uint32(id)
		4 + len(.Oldpath) +
		4 + len(.Newpath)

	 := make([]byte, 4, )
	 = append(, sshFxpRename)
	 = marshalUint32(, .ID)
	 = marshalString(, .Oldpath)
	 = marshalString(, .Newpath)

	return , nil
}

func ( *sshFxpRenamePacket) ( []byte) error {
	var  error
	if .ID, ,  = unmarshalUint32Safe();  != nil {
		return 
	} else if .Oldpath, ,  = unmarshalStringSafe();  != nil {
		return 
	} else if .Newpath, _,  = unmarshalStringSafe();  != nil {
		return 
	}
	return nil
}

type sshFxpPosixRenamePacket struct {
	ID      uint32
	Oldpath string
	Newpath string
}

func ( *sshFxpPosixRenamePacket) () uint32 { return .ID }

func ( *sshFxpPosixRenamePacket) () ([]byte, error) {
	const  = "posix-rename@openssh.com"
	 := 4 + 1 + 4 + // uint32(length) + byte(type) + uint32(id)
		4 + len() +
		4 + len(.Oldpath) +
		4 + len(.Newpath)

	 := make([]byte, 4, )
	 = append(, sshFxpExtended)
	 = marshalUint32(, .ID)
	 = marshalString(, )
	 = marshalString(, .Oldpath)
	 = marshalString(, .Newpath)

	return , nil
}

type sshFxpWritePacket struct {
	ID     uint32
	Length uint32
	Offset uint64
	Handle string
	Data   []byte
}

func ( *sshFxpWritePacket) () uint32 { return .ID }

func ( *sshFxpWritePacket) () ([]byte, []byte, error) {
	 := 4 + 1 + 4 + // uint32(length) + byte(type) + uint32(id)
		4 + len(.Handle) +
		8 + // uint64
		4

	 := make([]byte, 4, )
	 = append(, sshFxpWrite)
	 = marshalUint32(, .ID)
	 = marshalString(, .Handle)
	 = marshalUint64(, .Offset)
	 = marshalUint32(, .Length)

	return , .Data, nil
}

func ( *sshFxpWritePacket) () ([]byte, error) {
	, ,  := .marshalPacket()
	return append(, ...), 
}

func ( *sshFxpWritePacket) ( []byte) error {
	var  error
	if .ID, ,  = unmarshalUint32Safe();  != nil {
		return 
	} else if .Handle, ,  = unmarshalStringSafe();  != nil {
		return 
	} else if .Offset, ,  = unmarshalUint64Safe();  != nil {
		return 
	} else if .Length, ,  = unmarshalUint32Safe();  != nil {
		return 
	} else if uint32(len()) < .Length {
		return errShortPacket
	}

	.Data = [:.Length]
	return nil
}

type sshFxpMkdirPacket struct {
	ID    uint32
	Flags uint32 // ignored
	Path  string
}

func ( *sshFxpMkdirPacket) () uint32 { return .ID }

func ( *sshFxpMkdirPacket) () ([]byte, error) {
	 := 4 + 1 + 4 + // uint32(length) + byte(type) + uint32(id)
		4 + len(.Path) +
		4 // uint32

	 := make([]byte, 4, )
	 = append(, sshFxpMkdir)
	 = marshalUint32(, .ID)
	 = marshalString(, .Path)
	 = marshalUint32(, .Flags)

	return , nil
}

func ( *sshFxpMkdirPacket) ( []byte) error {
	var  error
	if .ID, ,  = unmarshalUint32Safe();  != nil {
		return 
	} else if .Path, ,  = unmarshalStringSafe();  != nil {
		return 
	} else if .Flags, _,  = unmarshalUint32Safe();  != nil {
		return 
	}
	return nil
}

type sshFxpSetstatPacket struct {
	ID    uint32
	Flags uint32
	Path  string
	Attrs interface{}
}

type sshFxpFsetstatPacket struct {
	ID     uint32
	Flags  uint32
	Handle string
	Attrs  interface{}
}

func ( *sshFxpSetstatPacket) () uint32  { return .ID }
func ( *sshFxpFsetstatPacket) () uint32 { return .ID }

func ( *sshFxpSetstatPacket) () ([]byte, []byte, error) {
	 := 4 + 1 + 4 + // uint32(length) + byte(type) + uint32(id)
		4 + len(.Path) +
		4 // uint32

	 := make([]byte, 4, )
	 = append(, sshFxpSetstat)
	 = marshalUint32(, .ID)
	 = marshalString(, .Path)
	 = marshalUint32(, .Flags)

	 := marshal(nil, .Attrs)

	return , , nil
}

func ( *sshFxpSetstatPacket) () ([]byte, error) {
	, ,  := .marshalPacket()
	return append(, ...), 
}

func ( *sshFxpFsetstatPacket) () ([]byte, []byte, error) {
	 := 4 + 1 + 4 + // uint32(length) + byte(type) + uint32(id)
		4 + len(.Handle) +
		4 // uint32

	 := make([]byte, 4, )
	 = append(, sshFxpFsetstat)
	 = marshalUint32(, .ID)
	 = marshalString(, .Handle)
	 = marshalUint32(, .Flags)

	 := marshal(nil, .Attrs)

	return , , nil
}

func ( *sshFxpFsetstatPacket) () ([]byte, error) {
	, ,  := .marshalPacket()
	return append(, ...), 
}

func ( *sshFxpSetstatPacket) ( []byte) error {
	var  error
	if .ID, ,  = unmarshalUint32Safe();  != nil {
		return 
	} else if .Path, ,  = unmarshalStringSafe();  != nil {
		return 
	} else if .Flags, ,  = unmarshalUint32Safe();  != nil {
		return 
	}
	.Attrs = 
	return nil
}

func ( *sshFxpFsetstatPacket) ( []byte) error {
	var  error
	if .ID, ,  = unmarshalUint32Safe();  != nil {
		return 
	} else if .Handle, ,  = unmarshalStringSafe();  != nil {
		return 
	} else if .Flags, ,  = unmarshalUint32Safe();  != nil {
		return 
	}
	.Attrs = 
	return nil
}

type sshFxpHandlePacket struct {
	ID     uint32
	Handle string
}

func ( *sshFxpHandlePacket) () ([]byte, error) {
	 := 4 + 1 + 4 + // uint32(length) + byte(type) + uint32(id)
		4 + len(.Handle)

	 := make([]byte, 4, )
	 = append(, sshFxpHandle)
	 = marshalUint32(, .ID)
	 = marshalString(, .Handle)

	return , nil
}

type sshFxpStatusPacket struct {
	ID uint32
	StatusError
}

func ( *sshFxpStatusPacket) () ([]byte, error) {
	 := 4 + 1 + 4 + // uint32(length) + byte(type) + uint32(id)
		4 +
		4 + len(.StatusError.msg) +
		4 + len(.StatusError.lang)

	 := make([]byte, 4, )
	 = append(, sshFxpStatus)
	 = marshalUint32(, .ID)
	 = marshalStatus(, .StatusError)

	return , nil
}

type sshFxpDataPacket struct {
	ID     uint32
	Length uint32
	Data   []byte
}

func ( *sshFxpDataPacket) () ([]byte, []byte, error) {
	 := 4 + 1 + 4 + // uint32(length) + byte(type) + uint32(id)
		4

	 := make([]byte, 4, )
	 = append(, sshFxpData)
	 = marshalUint32(, .ID)
	 = marshalUint32(, .Length)

	return , .Data, nil
}

// MarshalBinary encodes the receiver into a binary form and returns the result.
// To avoid a new allocation the Data slice must have a capacity >= Length + 9
//
// This is hand-coded rather than just append(header, payload...),
// in order to try and reuse the r.Data backing store in the packet.
func ( *sshFxpDataPacket) () ([]byte, error) {
	 := append(.Data, make([]byte, dataHeaderLen)...)
	copy([dataHeaderLen:], .Data[:.Length])
	// b[0:4] will be overwritten with the length in sendPacket
	[4] = sshFxpData
	binary.BigEndian.PutUint32([5:9], .ID)
	binary.BigEndian.PutUint32([9:13], .Length)
	return , nil
}

func ( *sshFxpDataPacket) ( []byte) error {
	var  error
	if .ID, ,  = unmarshalUint32Safe();  != nil {
		return 
	} else if .Length, ,  = unmarshalUint32Safe();  != nil {
		return 
	} else if uint32(len()) < .Length {
		return errShortPacket
	}

	.Data = [:.Length]
	return nil
}

type sshFxpStatvfsPacket struct {
	ID   uint32
	Path string
}

func ( *sshFxpStatvfsPacket) () uint32 { return .ID }

func ( *sshFxpStatvfsPacket) () ([]byte, error) {
	const  = "statvfs@openssh.com"
	 := 4 + 1 + 4 + // uint32(length) + byte(type) + uint32(id)
		4 + len() +
		4 + len(.Path)

	 := make([]byte, 4, )
	 = append(, sshFxpExtended)
	 = marshalUint32(, .ID)
	 = marshalString(, )
	 = marshalString(, .Path)

	return , nil
}

// A StatVFS contains statistics about a filesystem.
type StatVFS struct {
	ID      uint32
	Bsize   uint64 /* file system block size */
	Frsize  uint64 /* fundamental fs block size */
	Blocks  uint64 /* number of blocks (unit f_frsize) */
	Bfree   uint64 /* free blocks in file system */
	Bavail  uint64 /* free blocks for non-root */
	Files   uint64 /* total file inodes */
	Ffree   uint64 /* free file inodes */
	Favail  uint64 /* free file inodes for to non-root */
	Fsid    uint64 /* file system id */
	Flag    uint64 /* bit mask of f_flag values */
	Namemax uint64 /* maximum filename length */
}

// TotalSpace calculates the amount of total space in a filesystem.
func ( *StatVFS) () uint64 {
	return .Frsize * .Blocks
}

// FreeSpace calculates the amount of free space in a filesystem.
func ( *StatVFS) () uint64 {
	return .Frsize * .Bfree
}

// marshalPacket converts to ssh_FXP_EXTENDED_REPLY packet binary format
func ( *StatVFS) () ([]byte, []byte, error) {
	 := []byte{0, 0, 0, 0, sshFxpExtendedReply}

	var  bytes.Buffer
	 := binary.Write(&, binary.BigEndian, )

	return , .Bytes(), 
}

// MarshalBinary encodes the StatVFS as an SSH_FXP_EXTENDED_REPLY packet.
func ( *StatVFS) () ([]byte, error) {
	, ,  := .marshalPacket()
	return append(, ...), 
}

type sshFxpFsyncPacket struct {
	ID     uint32
	Handle string
}

func ( *sshFxpFsyncPacket) () uint32 { return .ID }

func ( *sshFxpFsyncPacket) () ([]byte, error) {
	const  = "fsync@openssh.com"
	 := 4 + 1 + 4 + // uint32(length) + byte(type) + uint32(id)
		4 + len() +
		4 + len(.Handle)

	 := make([]byte, 4, )
	 = append(, sshFxpExtended)
	 = marshalUint32(, .ID)
	 = marshalString(, )
	 = marshalString(, .Handle)

	return , nil
}

type sshFxpExtendedPacket struct {
	ID              uint32
	ExtendedRequest string
	SpecificPacket  interface {
		serverRespondablePacket
		readonly() bool
	}
}

func ( *sshFxpExtendedPacket) () uint32 { return .ID }
func ( *sshFxpExtendedPacket) () bool {
	if .SpecificPacket == nil {
		return true
	}
	return .SpecificPacket.readonly()
}

func ( *sshFxpExtendedPacket) ( *Server) responsePacket {
	if .SpecificPacket == nil {
		return statusFromError(.ID, nil)
	}
	return .SpecificPacket.respond()
}

func ( *sshFxpExtendedPacket) ( []byte) error {
	var  error
	 := 
	if .ID, ,  = unmarshalUint32Safe();  != nil {
		return 
	} else if .ExtendedRequest, _,  = unmarshalStringSafe();  != nil {
		return 
	}

	// specific unmarshalling
	switch .ExtendedRequest {
	case "statvfs@openssh.com":
		.SpecificPacket = &sshFxpExtendedPacketStatVFS{}
	case "posix-rename@openssh.com":
		.SpecificPacket = &sshFxpExtendedPacketPosixRename{}
	case "hardlink@openssh.com":
		.SpecificPacket = &sshFxpExtendedPacketHardlink{}
	default:
		return fmt.Errorf("packet type %v: %w", .SpecificPacket, errUnknownExtendedPacket)
	}

	return .SpecificPacket.UnmarshalBinary()
}

type sshFxpExtendedPacketStatVFS struct {
	ID              uint32
	ExtendedRequest string
	Path            string
}

func ( *sshFxpExtendedPacketStatVFS) () uint32     { return .ID }
func ( *sshFxpExtendedPacketStatVFS) () bool { return true }
func ( *sshFxpExtendedPacketStatVFS) ( []byte) error {
	var  error
	if .ID, ,  = unmarshalUint32Safe();  != nil {
		return 
	} else if .ExtendedRequest, ,  = unmarshalStringSafe();  != nil {
		return 
	} else if .Path, _,  = unmarshalStringSafe();  != nil {
		return 
	}
	return nil
}

type sshFxpExtendedPacketPosixRename struct {
	ID              uint32
	ExtendedRequest string
	Oldpath         string
	Newpath         string
}

func ( *sshFxpExtendedPacketPosixRename) () uint32     { return .ID }
func ( *sshFxpExtendedPacketPosixRename) () bool { return false }
func ( *sshFxpExtendedPacketPosixRename) ( []byte) error {
	var  error
	if .ID, ,  = unmarshalUint32Safe();  != nil {
		return 
	} else if .ExtendedRequest, ,  = unmarshalStringSafe();  != nil {
		return 
	} else if .Oldpath, ,  = unmarshalStringSafe();  != nil {
		return 
	} else if .Newpath, _,  = unmarshalStringSafe();  != nil {
		return 
	}
	return nil
}

func ( *sshFxpExtendedPacketPosixRename) ( *Server) responsePacket {
	 := os.Rename(.toLocalPath(.Oldpath), .toLocalPath(.Newpath))
	return statusFromError(.ID, )
}

type sshFxpExtendedPacketHardlink struct {
	ID              uint32
	ExtendedRequest string
	Oldpath         string
	Newpath         string
}

// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL
func ( *sshFxpExtendedPacketHardlink) () uint32     { return .ID }
func ( *sshFxpExtendedPacketHardlink) () bool { return true }
func ( *sshFxpExtendedPacketHardlink) ( []byte) error {
	var  error
	if .ID, ,  = unmarshalUint32Safe();  != nil {
		return 
	} else if .ExtendedRequest, ,  = unmarshalStringSafe();  != nil {
		return 
	} else if .Oldpath, ,  = unmarshalStringSafe();  != nil {
		return 
	} else if .Newpath, _,  = unmarshalStringSafe();  != nil {
		return 
	}
	return nil
}

func ( *sshFxpExtendedPacketHardlink) ( *Server) responsePacket {
	 := os.Link(.toLocalPath(.Oldpath), .toLocalPath(.Newpath))
	return statusFromError(.ID, )
}