package sftp

import (
	
	
	
	
	
	
	
)

var maxTxPacket uint32 = 1 << 15

// Handlers contains the 4 SFTP server request handlers.
type Handlers struct {
	FileGet  FileReader
	FilePut  FileWriter
	FileCmd  FileCmder
	FileList FileLister
}

// RequestServer abstracts the sftp protocol with an http request-like protocol
type RequestServer struct {
	Handlers Handlers

	*serverConn
	pktMgr *packetManager

	startDirectory string

	mu           sync.RWMutex
	handleCount  int
	openRequests map[string]*Request
}

// A RequestServerOption is a function which applies configuration to a RequestServer.
type RequestServerOption func(*RequestServer)

// WithRSAllocator enable the allocator.
// After processing a packet we keep in memory the allocated slices
// and we reuse them for new packets.
// The allocator is experimental
func () RequestServerOption {
	return func( *RequestServer) {
		 := newAllocator()
		.pktMgr.alloc = 
		.conn.alloc = 
	}
}

// WithStartDirectory sets a start directory to use as base for relative paths.
// If unset the default is "/"
func ( string) RequestServerOption {
	return func( *RequestServer) {
		.startDirectory = cleanPath()
	}
}

// NewRequestServer creates/allocates/returns new RequestServer.
// Normally there will be one server per user-session.
func ( io.ReadWriteCloser,  Handlers,  ...RequestServerOption) *RequestServer {
	 := &serverConn{
		conn: conn{
			Reader:      ,
			WriteCloser: ,
		},
	}
	 := &RequestServer{
		Handlers: ,

		serverConn: ,
		pktMgr:     newPktMgr(),

		startDirectory: "/",

		openRequests: make(map[string]*Request),
	}

	for ,  := range  {
		()
	}
	return 
}

// New Open packet/Request
func ( *RequestServer) ( *Request) string {
	.mu.Lock()
	defer .mu.Unlock()

	.handleCount++

	.handle = strconv.Itoa(.handleCount)
	.openRequests[.handle] = 

	return .handle
}

// Returns Request from openRequests, bool is false if it is missing.
//
// The Requests in openRequests work essentially as open file descriptors that
// you can do different things with. What you are doing with it are denoted by
// the first packet of that type (read/write/etc).
func ( *RequestServer) ( string) (*Request, bool) {
	.mu.RLock()
	defer .mu.RUnlock()

	,  := .openRequests[]
	return , 
}

// Close the Request and clear from openRequests map
func ( *RequestServer) ( string) error {
	.mu.Lock()
	defer .mu.Unlock()

	if ,  := .openRequests[];  {
		delete(.openRequests, )
		return .close()
	}

	return EBADF
}

// Close the read/write/closer to trigger exiting the main server loop
func ( *RequestServer) () error { return .conn.Close() }

func ( *RequestServer) ( chan<- orderedRequest) error {
	defer close() // shuts down sftpServerWorkers

	var  error
	var  requestPacket
	var  uint8
	var  []byte

	for {
		, ,  = .serverConn.recvPacket(.pktMgr.getNextOrderID())
		if  != nil {
			// we don't care about releasing allocated pages here, the server will quit and the allocator freed
			return 
		}

		,  = makePacket(rxPacket{fxp(), })
		if  != nil {
			switch {
			case errors.Is(, errUnknownExtendedPacket):
				// do nothing
			default:
				debug("makePacket err: %v", )
				.conn.Close() // shuts down recvPacket
				return 
			}
		}

		 <- .pktMgr.newOrderedRequest()
	}
}

// Serve requests for user session
func ( *RequestServer) () error {
	defer func() {
		if .pktMgr.alloc != nil {
			.pktMgr.alloc.Free()
		}
	}()

	,  := context.WithCancel(context.Background())
	defer ()

	var  sync.WaitGroup
	 := func( chan orderedRequest) {
		.Add(1)
		go func() {
			defer .Done()
			if  := .packetWorker(, );  != nil {
				.conn.Close() // shuts down recvPacket
			}
		}()
	}
	 := .pktMgr.workerChan()

	 := .serveLoop()

	.Wait() // wait for all workers to exit

	.mu.Lock()
	defer .mu.Unlock()

	// make sure all open requests are properly closed
	// (eg. possible on dropped connections, client crashes, etc.)
	for ,  := range .openRequests {
		if  == io.EOF {
			 = io.ErrUnexpectedEOF
		}
		.transferError()

		delete(.openRequests, )
		.close()
	}

	return 
}

func ( *RequestServer) ( context.Context,  chan orderedRequest) error {
	for  := range  {
		 := .orderID()
		if ,  := .requestPacket.(*sshFxpExtendedPacket);  {
			if .SpecificPacket != nil {
				.requestPacket = .SpecificPacket
			}
		}

		var  responsePacket
		switch pkt := .requestPacket.(type) {
		case *sshFxInitPacket:
			 = &sshFxVersionPacket{Version: sftpProtocolVersion, Extensions: sftpExtensions}
		case *sshFxpClosePacket:
			 := .getHandle()
			 = statusFromError(.ID, .closeRequest())
		case *sshFxpRealpathPacket:
			var  string
			var  error

			switch pather := .Handlers.FileList.(type) {
			case RealPathFileLister:
				,  = .RealPath(.getPath())
			case legacyRealPathFileLister:
				 = .RealPath(.getPath())
			default:
				 = cleanPathWithBase(.startDirectory, .getPath())
			}
			if  != nil {
				 = statusFromError(.ID, )
			} else {
				 = cleanPacketPath(, )
			}
		case *sshFxpOpendirPacket:
			 := requestFromPacket(, , .startDirectory)
			 := .nextRequest()
			 = .opendir(.Handlers, )
			if ,  := .(*sshFxpHandlePacket); ! {
				// if we return an error we have to remove the handle from the active ones
				.closeRequest()
			}
		case *sshFxpOpenPacket:
			 := requestFromPacket(, , .startDirectory)
			 := .nextRequest()
			 = .open(.Handlers, )
			if ,  := .(*sshFxpHandlePacket); ! {
				// if we return an error we have to remove the handle from the active ones
				.closeRequest()
			}
		case *sshFxpFstatPacket:
			 := .getHandle()
			,  := .getRequest()
			if ! {
				 = statusFromError(.ID, EBADF)
			} else {
				 = &Request{
					Method:   "Stat",
					Filepath: cleanPathWithBase(.startDirectory, .Filepath),
				}
				 = .call(.Handlers, , .pktMgr.alloc, )
			}
		case *sshFxpFsetstatPacket:
			 := .getHandle()
			,  := .getRequest()
			if ! {
				 = statusFromError(.ID, EBADF)
			} else {
				 = &Request{
					Method:   "Setstat",
					Filepath: cleanPathWithBase(.startDirectory, .Filepath),
				}
				 = .call(.Handlers, , .pktMgr.alloc, )
			}
		case *sshFxpExtendedPacketPosixRename:
			 := &Request{
				Method:   "PosixRename",
				Filepath: cleanPathWithBase(.startDirectory, .Oldpath),
				Target:   cleanPathWithBase(.startDirectory, .Newpath),
			}
			 = .call(.Handlers, , .pktMgr.alloc, )
		case *sshFxpExtendedPacketStatVFS:
			 := &Request{
				Method:   "StatVFS",
				Filepath: cleanPathWithBase(.startDirectory, .Path),
			}
			 = .call(.Handlers, , .pktMgr.alloc, )
		case hasHandle:
			 := .getHandle()
			,  := .getRequest()
			if ! {
				 = statusFromError(.id(), EBADF)
			} else {
				 = .call(.Handlers, , .pktMgr.alloc, )
			}
		case hasPath:
			 := requestFromPacket(, , .startDirectory)
			 = .call(.Handlers, , .pktMgr.alloc, )
			.close()
		default:
			 = statusFromError(.id(), ErrSSHFxOpUnsupported)
		}

		.pktMgr.readyPacket(
			.pktMgr.newOrderedResponse(, ))
	}
	return nil
}

// clean and return name packet for file
func cleanPacketPath( *sshFxpRealpathPacket,  string) responsePacket {
	return &sshFxpNamePacket{
		ID: .id(),
		NameAttrs: []*sshFxpNameAttr{
			{
				Name:     ,
				LongName: ,
				Attrs:    emptyFileStat,
			},
		},
	}
}

// Makes sure we have a clean POSIX (/) absolute path to work with
func cleanPath( string) string {
	return cleanPathWithBase("/", )
}

func cleanPathWithBase(,  string) string {
	 = filepath.ToSlash(filepath.Clean())
	if !path.IsAbs() {
		return path.Join(, )
	}
	return 
}