package afp

import (
	"encoding/binary"
	"net"
	"strings"
	"time"

	"github.com/vulncheck-oss/go-exploit/output"
	"github.com/vulncheck-oss/go-exploit/protocol"
)

const (
	DSICloseSession                    = byte(0x1)
	DSICommand                         = byte(0x2)
	DSIGetStatus                       = byte(0x3)
	DSIOpenSession                     = byte(0x4)
	DSITickle                          = byte(0x5)
	DSIWrite                           = byte(0x6)
	DSIAttention                       = byte(0x8)
	AFPByteLock                        = 0x01
	AFPCloseVol                        = 0x02
	AFPCloseDir                        = 0x03
	AFPCloseFork                       = 0x04
	AFPCopyFile                        = 0x05
	AFPCreateDir                       = 0x06
	AFPCreateFile                      = 0x07
	AFPDelete                          = 0x08
	AFPEnumerate                       = 0x09
	AFPFlush                           = 0x0a
	AFPFlushFork                       = 0x0b
	AFPGetForkParams                   = 0x0e
	AFPGetSrvrInfo                     = 0x0f
	AFPGetSrvrParams                   = 0x10
	AFPGetVolParams                    = 0x11
	AFPLogin                           = 0x12
	AFPLoginCont                       = 0x13
	AFPLogout                          = 0x14
	AFPMapID                           = 0x15
	AFPMapName                         = 0x16
	AFPMoveAndRename                   = 0x17
	AFPOpenVol                         = 0x18
	AFPOpenDir                         = 0x19
	AFPOpenFork                        = 0x1a
	AFPRead                            = 0x1b
	AFPRename                          = 0x1c
	AFPSetDirParams                    = 0x1d
	AFPSetFileParams                   = 0x1e
	AFPSetForkParams                   = 0x1f
	AFPSetVolParams                    = 0x20
	AFPWrite                           = 0x21
	AFPGetFileDirParams                = 0x22
	AFPSetFileDirParams                = 0x23
	AFPChangePW                        = 0x24
	AFPGetUserInfo                     = 0x25
	AFPGetSrvrMesg                     = 0x26
	AFPCreateID                        = 0x27
	AFPDeleteID                        = 0x28
	AFPResolveID                       = 0x29
	AFPExchangeFiles                   = 0x2a
	AFPCatSearch                       = 0x2b
	AFPOpenDT                          = 0x30
	AFPCloseDT                         = 0x31
	AFPGetIcon                         = 0x33
	AFPGetIconInfo                     = 0x34
	AFPAddAppl                         = 0x35
	AFPRmvAppl                         = 0x36
	AFPGetAppl                         = 0x37
	AFPAddComment                      = 0x38
	AFPRmvComment                      = 0x39
	AFPGetComment                      = 0x3a
	AFPReadExt                         = 0x3c
	AFPWriteExt                        = 0x3d
	AFPGetExtAttr                      = 0x45
	AFPSetExtAttr                      = 0x46
	VolBitmapAttributes                = 0x1
	VolBitmapSignature                 = 0x2
	VolBitmapCreationDate              = 0x4
	VolBitmapModificationDate          = 0x8
	VolBitmapBackupDate                = 0x10
	VolBitmapID                        = 0x20
	VolBitmapBytesFree                 = 0x40
	VolBitmapBytesTotal                = 0x80
	VolBitmapName                      = 0x100
	VolBitmapExtendedBytesFree         = 0x200
	VolBitmapExtendedBytesTotal        = 0x400
	VolBitmapBlockSize                 = 0x800
	FileBitmapAttributes               = 0x1
	FileBitmapParentDirID              = 0x2
	FileBitmapCreationDate             = 0x4
	FileBitmapModificationDate         = 0x8
	FileBitmapBackupDate               = 0x10
	FileBitmapFinderInfo               = 0x20
	FileBitmapLongName                 = 0x40
	FileBitmapShortName                = 0x80
	FileBitmapNodeID                   = 0x100
	FileBitmapDataForkSize             = 0x200
	FileBitmapResourceForkSize         = 0x400
	FileBitmapExtendedDataForkSize     = 0x800
	FileBitmapLaunchLimit              = 0x1000
	FileBitmapUTF8Name                 = 0x2000
	FileBitmapExtendedResourceForkSize = 0x4000
	FileBitmapUnixPrivileges           = 0x8000
	FileBitmapALL                      = 0xFFFF
	DirBitmapAttributes                = 0x1
	DirBitmapParentDirID               = 0x0
	DirBitmapCreationDate              = 0x4
	DirBitmapModificationDate          = 0x8
	DirBitmapBackupDate                = 0x10
	DirBitmapFinderInfo                = 0x20
	DirBitmapLongName                  = 0x40
	DirBitmapShortName                 = 0x80
	DirBitmapNodeID                    = 0x100
	DirBitmapOffspringCount            = 0x200
	DirBitmapOwnerID                   = 0x400
	DirBitmapGroupID                   = 0x800
	DirBitmapAccessRights              = 0x1000
	DirBitmapUTF8Name                  = 0x2000
	DirBitmapUnixPrivileges            = 0x8000
	DirBitmapALL                       = 0xBFFF
	AccessModeRead                     = 0x1
	AccessModeWrite                    = 0x2
	AccessModeDenyRead                 = 0x10
	AccessModeDenyWrite                = 0x20
)

type Header struct {
	Flags           uint8
	Command         uint8
	RequestID       uint16
	ErrorCode       uint32
	TotalDataLength uint32
	Reserved        uint32
}

type FPPacket struct {
	Header Header
	Body   []byte
}

// encode an FPPacket to bytes.
func (d *Header) Encode() []byte {
	return []byte{
		d.Flags,
		d.Command,
		byte(d.RequestID >> 8), byte(d.RequestID),
		byte(d.ErrorCode >> 24), byte(d.ErrorCode >> 16), byte(d.ErrorCode >> 8), byte(d.ErrorCode),
		byte(d.TotalDataLength >> 24), byte(d.TotalDataLength >> 16), byte(d.TotalDataLength >> 8), byte(d.TotalDataLength),
		0, 0, 0, 0,
	}
}

// Decode bytes into an FPPacket.
func Decode(data []byte) (FPPacket, bool) {
	if len(data) < 16 {
		output.PrintfFrameworkError("Couldn't decode bad AFP header with length %d", len(data))

		return FPPacket{}, false
	}
	header := Header{
		Flags:           data[0],
		Command:         data[1],
		RequestID:       uint16(data[3]) | uint16(data[2])<<8,
		ErrorCode:       uint32(data[7]) | uint32(data[6])<<8 | uint32(data[5])<<16 | uint32(data[4])<<24,
		TotalDataLength: uint32(data[11]) | uint32(data[10])<<8 | uint32(data[9])<<16 | uint32(data[8])<<24,
	}
	body := data[16:]

	return FPPacket{header, body}, true
}

// Connects to an AFP server and opens a session.
func Connect(host string, port int, ssl bool) (net.Conn, bool) {
	conn, ok := protocol.MixedConnect(host, port, ssl)
	if !ok {
		output.PrintfFrameworkDebug("Failed to connect to: %s:%d", host, port)

		return nil, false
	}
	_ = conn.SetWriteDeadline(time.Now().Add(time.Duration(protocol.GlobalCommTimeout) * time.Second))
	_ = conn.SetReadDeadline(time.Now().Add(time.Duration(protocol.GlobalCommTimeout) * time.Second))
	ok = OpenSession(conn)
	if !ok {
		output.PrintfFrameworkDebug("Failed to connect to: %s:%d", host, port)

		return nil, false
	}

	return conn, true
}

// Connects to an AFP server and gets server information.
func GetServerStatus(host string, port int, ssl bool) (*FPPacket, bool) {
	conn, ok := protocol.MixedConnect(host, port, ssl)
	if !ok {
		output.PrintfFrameworkDebug("Failed to connect to: %s:%d", host, port)

		return nil, false
	}
	_ = conn.SetWriteDeadline(time.Now().Add(time.Duration(protocol.GlobalCommTimeout) * time.Second))
	_ = conn.SetReadDeadline(time.Now().Add(time.Duration(protocol.GlobalCommTimeout) * time.Second))
	data := []byte{}
	packet := CreateFPPacket(DSIGetStatus, data)

	// send message
	ok = WritePacket(conn, packet)
	if !ok {
		output.PrintfFrameworkDebug("Failed to get server status of: %s:%d", host, port)

		return nil, false
	}
	// read response
	response, ok := ReadPacket(conn)
	if !ok {
		output.PrintfFrameworkDebug("Failed to get server status of: %s:%d", host, port)

		return nil, false
	}
	if response.Header.ErrorCode != 0 {
		output.PrintfFrameworkDebug("Failed to get server status of: %s:%d", host, port)

		return nil, false
	}

	return response, true
}

// Creates an AFP session.
func OpenSession(conn net.Conn) bool {
	// from nmap lua library -- data = string.pack( ">BBI4", option, option_len, quantum  )
	data := []byte{}
	data = append(data, byte(0x01))
	data = append(data, byte(0x04))
	quantumBytes := make([]byte, 4)
	binary.BigEndian.PutUint32(quantumBytes, uint32(1024))
	data = append(data, quantumBytes...)

	packet := CreateFPPacket(DSIOpenSession, data)

	// send message
	ok := WritePacket(conn, packet)
	if !ok {
		return false
	}

	// read response
	_, ok = ReadPacket(conn)

	return ok
}

// Disconnects from an active AFP session.
func Disconnect(conn net.Conn) bool {
	h := Header{
		Flags:     0x0,
		Command:   DSICloseSession,
		RequestID: 1,
	}
	ok := WritePacket(conn, FPPacket{h, nil})
	conn.Close()

	return ok
}

// Reads bytes from connection and converts them to an FPPacket.
func ReadPacket(conn net.Conn) (*FPPacket, bool) {
	// read response header
	headerBytes, ok := protocol.TCPReadAmountBlind(conn, 16)
	if !ok {
		return nil, ok
	}
	packet, ok := Decode(headerBytes)
	if !ok {
		return nil, ok
	}

	if packet.Header.TotalDataLength > 0 {
		packet.Body, ok = protocol.TCPReadAmountBlind(conn, int(packet.Header.TotalDataLength))
		if !ok {
			return nil, ok
		}
	}
	if packet.Header.Flags == 0x0 {
		p, ok := ReadPacket(conn)
		if !ok {
			return nil, ok
		}
		packet = *p
	}

	return &packet, ok
}

// Creates a FPPacket given a command byte and payload.
func CreateFPPacket(command byte, payload []byte) FPPacket {
	dsiHeader := Header{
		Flags:           0x00,
		Command:         command,
		RequestID:       uint16(0x01),
		ErrorCode:       uint32(0x00),
		TotalDataLength: uint32(len(payload)),
		Reserved:        uint32(0x00),
	}

	return FPPacket{dsiHeader, payload}
}

// Converts FPPacket to bytes and sends them.
func WritePacket(conn net.Conn, packet FPPacket) bool {
	msg := []byte{}
	msg = append(msg, packet.Header.Encode()...)
	msg = append(msg, packet.Body...)
	ok := protocol.TCPWrite(conn, msg)

	return ok
}

// Logs into the AFP server as the guest user.
func AnonymousLogin(conn net.Conn) bool {
	// self.proto:fp_login( "AFP3.1", "No User Authent" )
	// from nmap lua library -- data = string.pack( "Bs1s1", COMMAND.FPLogin, afp_version, uam )
	data := []byte{}
	data = append(data, AFPLogin)
	data = append(data, uint8(len("AFP3.1")))
	data = append(data, "AFP3.1"...)
	data = append(data, uint8(len("No User Authent")))
	data = append(data, "No User Authent"...)

	packet := CreateFPPacket(DSICommand, data)

	// send message
	ok := WritePacket(conn, packet)
	if !ok {
		return false
	}

	// read response
	_, ok = ReadPacket(conn)

	return ok
}

// Sends the Logout command to the AFP server.
func Logout(conn net.Conn) bool {
	// from nmap lua library -- data = string.pack(">Bx", COMMAND.FPLogout)
	data := []byte{}
	data = append(data, AFPLogout)
	data = append(data, byte(0))

	packet := CreateFPPacket(DSICommand, data)

	// send message
	ok := WritePacket(conn, packet)
	if !ok {
		return false
	}

	// read response
	_, ok = ReadPacket(conn)

	return ok
}

// Sends the OpenVolume command to the AFP server.
func OpenVolume(conn net.Conn, bitmap uint16, volumeName []byte) (uint16, bool) {
	// from nmap lua library -- data = string.pack(">BxI2s1", COMMAND.FPOpenVol, bitmap, volume_name)
	data := []byte{}
	data = append(data, AFPOpenVol)
	data = append(data, byte(0))
	bitmapBytes := make([]byte, 2)
	binary.BigEndian.PutUint16(bitmapBytes, bitmap)
	data = append(data, bitmapBytes...)
	data = append(data, uint8(len(volumeName)))
	data = append(data, volumeName...)

	packet := CreateFPPacket(DSICommand, data)

	// send message
	ok := WritePacket(conn, packet)
	if !ok {
		return 0, false
	}

	// read response
	response, ok := ReadPacket(conn)
	if !ok {
		return 0, false
	}
	// get volume bitmap and volume id
	// from nmap lua library -- data = volume.bitmap, volume.volume_id, pos = string.unpack(">I2I2", response.packet.data)
	if len(response.Body) < 4 {
		output.PrintfFrameworkDebug("Error Code %x", response.Header.ErrorCode)
		output.PrintfFrameworkDebug("No volume data for %s", volumeName)

		return 0, false
	}
	volumeID := uint16(response.Body[3]) | uint16(response.Body[2])<<8

	return volumeID, true
}

// Walks the root directory of a volume by opening the volume by ID.
func WalkRootDir(conn net.Conn, path string) (uint16, uint32, bool) {
	// split the dir path
	pathElements := strings.Split(path, "/")

	// Open the volume by name (first component of the path)
	volumeID, ok := OpenVolume(conn, VolBitmapID, []byte(pathElements[0]))
	if !ok {
		output.PrintfFrameworkDebug("failed to open volume: %s", pathElements[0])

		return 0, 0, false
	}
	directoryID := uint32(2)

	return volumeID, directoryID, true
}

// Sends the CreateFile command to the AFP server.
func CreateFile(conn net.Conn, volumeID uint16, dirID uint32, fileName string) bool {
	// from nmap lua library -- data = string.pack(">BBI2I4", COMMAND.FPCreateFile, flag, vol_id, did) .. encode_path(path)
	// .. encode_path(path) -- string.pack("Bs1", path.type-{2}-, path.name)
	data := []byte{}
	data = append(data, AFPCreateFile)
	data = append(data, byte(0))
	volIDBytes := make([]byte, 2)
	binary.BigEndian.PutUint16(volIDBytes, volumeID)
	data = append(data, volIDBytes...)
	dirIDBytes := make([]byte, 4)
	binary.BigEndian.PutUint32(dirIDBytes, dirID)
	data = append(data, dirIDBytes...)
	data = append(data, byte(0x2))
	data = append(data, uint8(len(fileName)))
	data = append(data, []byte(fileName)...)

	packet := CreateFPPacket(DSICommand, data)

	// send message
	ok := WritePacket(conn, packet)
	if !ok {
		return false
	}

	// read response
	response, ok := ReadPacket(conn)
	if !ok {
		return false
	}
	if response.Header.ErrorCode != 0 {
		return false
	}

	return true
}

// Sends the OpenFork command to the AFP server.
func OpenFork(conn net.Conn, flag byte, volumeID uint16, dirID uint32, bitmap uint16, accessMode uint16, path string) (*FPPacket, bool) {
	pathElements := strings.Split(path, "/")
	fileName := pathElements[len(pathElements)-1]

	// from nmap lua library -- data = string.pack( ">BBI2I4I2I2", COMMAND.FPOpenFork, flag, volume_id, did, file_bitmap, access_mode ) .. encode_path(path)
	data := []byte{}
	data = append(data, AFPOpenFork)
	data = append(data, flag)
	volIDBytes := make([]byte, 2)
	binary.BigEndian.PutUint16(volIDBytes, volumeID)
	data = append(data, volIDBytes...)
	dirIDBytes := make([]byte, 4)
	binary.BigEndian.PutUint32(dirIDBytes, dirID)
	data = append(data, dirIDBytes...)
	fileBitmapBytes := make([]byte, 2)
	binary.BigEndian.PutUint16(fileBitmapBytes, bitmap)
	data = append(data, fileBitmapBytes...)
	accessModeBytes := make([]byte, 2)
	binary.BigEndian.PutUint16(accessModeBytes, accessMode)
	data = append(data, accessModeBytes...)
	data = append(data, byte(0x2))
	data = append(data, uint8(len(fileName)))
	data = append(data, []byte(fileName)...)

	packet := CreateFPPacket(DSICommand, data)

	// send message
	ok := WritePacket(conn, packet)
	if !ok {
		return nil, false
	}

	// read response
	response, ok := ReadPacket(conn)
	if !ok {
		output.PrintfFrameworkDebug("Open Fork error not ok")

		return nil, false
	}
	if response.Header.ErrorCode != 0 {
		output.PrintfFrameworkDebug("Open Fork error code: %x", response.Header.ErrorCode)

		return nil, false
	}

	return response, true
}

// Sends the CloseFork command to the AFP server.
func CloseFork(conn net.Conn, forkID uint16) bool {
	// from nmap lua library -- data = string.pack( ">BxI2", COMMAND.FPCloseFork, fork )
	data := []byte{}
	data = append(data, AFPCloseFork)
	data = append(data, byte(0))
	forkIDBytes := make([]byte, 2)
	binary.BigEndian.PutUint16(forkIDBytes, forkID)
	data = append(data, forkIDBytes...)

	packet := CreateFPPacket(DSICommand, data)

	// send message
	ok := WritePacket(conn, packet)
	if !ok {
		return false
	}

	// read response
	_, ok = ReadPacket(conn)

	return ok
}

// Sends the FlushFork command to the AFP server.
func FlushFork(conn net.Conn, forkID uint16) bool {
	data := []byte{}
	data = append(data, AFPFlushFork)
	data = append(data, byte(0))
	forkIDBytes := make([]byte, 2)
	binary.BigEndian.PutUint16(forkIDBytes, forkID)
	data = append(data, forkIDBytes...)

	packet := CreateFPPacket(DSICommand, data)

	// send message
	ok := WritePacket(conn, packet)
	if !ok {
		return false
	}

	// read response
	_, ok = ReadPacket(conn)

	return ok
}

// Sends the SetForkParams command to the AFP server.
func SetForkParams(conn net.Conn, forkID uint16, bitmap uint16, size uint64) bool {
	data := []byte{}
	data = append(data, AFPSetForkParams)
	data = append(data, byte(0x00))
	forkIDBytes := make([]byte, 2)
	binary.BigEndian.PutUint16(forkIDBytes, forkID)
	data = append(data, forkIDBytes...)
	fileBitmapBytes := make([]byte, 2)
	binary.BigEndian.PutUint16(fileBitmapBytes, bitmap)
	data = append(data, fileBitmapBytes...)
	sizeBytes := make([]byte, 8)
	binary.BigEndian.PutUint64(sizeBytes, size)
	data = append(data, sizeBytes...)

	packet := CreateFPPacket(DSICommand, data)

	// send message
	ok := WritePacket(conn, packet)
	if !ok {
		return false
	}

	// read response
	_, ok = ReadPacket(conn)

	return ok
}

// Sends the GetForkParams command to the AFP server.
func GetForkParams(conn net.Conn, forkID uint16, bitmap uint16) (*FPPacket, bool) {
	data := []byte{}
	data = append(data, AFPGetForkParams)
	data = append(data, byte(0x00))
	forkIDBytes := make([]byte, 2)
	binary.BigEndian.PutUint16(forkIDBytes, forkID)
	data = append(data, forkIDBytes...)
	bitmapBytes := make([]byte, 2)
	binary.BigEndian.PutUint16(bitmapBytes, bitmap)
	data = append(data, bitmapBytes...)

	packet := CreateFPPacket(DSICommand, data)

	// send message
	ok := WritePacket(conn, packet)
	if !ok {
		return nil, false
	}

	// read response
	response, ok := ReadPacket(conn)

	return response, ok
}

// Sends the ReadExt command to the AFP server.
func ReadExt(conn net.Conn, forkID uint16, offset uint64, count uint64) (*FPPacket, bool) {
	// from nmap lua library -- data = string.pack( ">BxI2I8I8", COMMAND.FPReadExt, fork, offset, count  )
	data := []byte{}
	data = append(data, AFPReadExt)
	data = append(data, byte(0))
	forkIDBytes := make([]byte, 2)
	binary.BigEndian.PutUint16(forkIDBytes, forkID)
	data = append(data, forkIDBytes...)
	offsetBytes := make([]byte, 8)
	binary.BigEndian.PutUint64(offsetBytes, offset)
	data = append(data, offsetBytes...)
	countBytes := make([]byte, 8)
	binary.BigEndian.PutUint64(countBytes, count)
	data = append(data, countBytes...)

	packet := CreateFPPacket(DSICommand, data)

	// send message
	ok := WritePacket(conn, packet)
	if !ok {
		return nil, false
	}

	// read response
	response, ok := ReadPacket(conn)

	return response, ok
}

// Sends the WriteExt command to the AFP server.
func WriteExt(conn net.Conn, forkID uint16, fdata []byte) bool {
	// from nmap lua library -- data = string.pack( ">BBI2I8I8", COMMAND.FPWriteExt, flag, fork, offset, count) .. fdata
	data := []byte{}
	data = append(data, AFPWriteExt)
	data = append(data, byte(0))
	forkIDBytes := make([]byte, 2)
	binary.BigEndian.PutUint16(forkIDBytes, forkID)
	data = append(data, forkIDBytes...)
	offsetBytes := make([]byte, 8)
	binary.BigEndian.PutUint64(offsetBytes, 0)
	data = append(data, offsetBytes...)
	fdataLenBytes := make([]byte, 8)
	binary.BigEndian.PutUint64(fdataLenBytes, uint64(len(fdata)))
	data = append(data, fdataLenBytes...)
	data = append(data, fdata...)

	packet := CreateFPPacket(DSIWrite, data)
	packet.Header.ErrorCode = uint32(20)

	// send message
	ok := WritePacket(conn, packet)
	if !ok {
		return false
	}

	// read response
	response, ok := ReadPacket(conn)
	if !ok {
		return false
	}
	if response.Header.ErrorCode != 0 {
		return false
	}

	return true
}

// Sends the Move and Rename command to the AFP server.
func MoveAndRenameFile(conn net.Conn, srcVolID uint16, srcDirID uint32, srcPath string, dstDirID uint32, dstPath string, dstName string) bool {
	// data = string.pack(">BxI2I4I2I4", COMMAND.FPCopyFile, src_vol, src_did, dst_vol, dst_did )
	//         .. encode_path({type=PATH_TYPE.UTF8Name, name=src_path})
	//         .. encode_path({type=PATH_TYPE.UTF8Name, name=dst_path})
	//         .. encode_path({type=PATH_TYPE.UTF8Name, name=new_name})
	utf8Bytes := []byte{0x08, 0x00, 0x01, 0x03}

	data := []byte{}
	data = append(data, AFPMoveAndRename)
	data = append(data, byte(0x00))
	srcVolIDBytes := make([]byte, 2)
	binary.BigEndian.PutUint16(srcVolIDBytes, srcVolID)
	data = append(data, srcVolIDBytes...)
	srcDirIDBytes := make([]byte, 4)
	binary.BigEndian.PutUint32(srcDirIDBytes, srcDirID)
	data = append(data, srcDirIDBytes...)
	dstDirIDBytes := make([]byte, 4)
	binary.BigEndian.PutUint32(dstDirIDBytes, dstDirID)
	data = append(data, dstDirIDBytes...)
	// Unicode names
	data = append(data, byte(0x03))
	data = append(data, utf8Bytes...)
	srcPathLenBytes := make([]byte, 2)
	binary.BigEndian.PutUint16(srcPathLenBytes, uint16(len(srcPath)))
	data = append(data, srcPathLenBytes...)
	data = append(data, srcPath...)
	data = append(data, byte(0x03))
	data = append(data, utf8Bytes...)
	dstPathLenBytes := make([]byte, 2)
	binary.BigEndian.PutUint16(dstPathLenBytes, uint16(len(dstPath)))
	data = append(data, dstPathLenBytes...)
	data = append(data, dstPath...)
	data = append(data, byte(0x03))
	data = append(data, utf8Bytes...)
	dstNameLenBytes := make([]byte, 2)
	binary.BigEndian.PutUint16(dstNameLenBytes, uint16(len(dstName)))
	data = append(data, dstNameLenBytes...)
	data = append(data, dstName...)

	packet := CreateFPPacket(DSICommand, data)

	// send message
	ok := WritePacket(conn, packet)
	if !ok {
		return false
	}

	// read response
	_, ok = ReadPacket(conn)

	return ok
}

// Sends the AddAppl command to the AFP server.
func AddAppl(conn net.Conn, volID uint16, dirID uint32, creator [4]byte, applTag [4]byte, path string) bool {
	data := []byte{}
	data = append(data, AFPAddAppl)
	data = append(data, byte(0x00))
	volIDBytes := make([]byte, 2)
	binary.BigEndian.PutUint16(volIDBytes, volID)
	data = append(data, volIDBytes...)
	dirIDBytes := make([]byte, 4)
	binary.BigEndian.PutUint32(dirIDBytes, dirID)
	data = append(data, dirIDBytes...)
	data = append(data, creator[0])
	data = append(data, creator[1])
	data = append(data, creator[2])
	data = append(data, creator[3])
	data = append(data, applTag[0])
	data = append(data, applTag[1])
	data = append(data, applTag[2])
	data = append(data, applTag[3])
	data = append(data, byte(0x2))
	data = append(data, uint8(len(path)))
	data = append(data, []byte(path)...)

	packet := CreateFPPacket(DSICommand, data)

	// send message
	ok := WritePacket(conn, packet)
	if !ok {
		return false
	}

	// read response
	_, ok = ReadPacket(conn)

	return ok
}

// Sends the GetAppl command to the AFP server.
func GetAppl(conn net.Conn, volID uint16, creator [4]byte, aIndex uint16, bitmap uint16) (*FPPacket, bool) {
	data := []byte{}
	data = append(data, AFPGetAppl)
	data = append(data, byte(0x00))
	volIDBytes := make([]byte, 2)
	binary.BigEndian.PutUint16(volIDBytes, volID)
	data = append(data, volIDBytes...)
	data = append(data, creator[0])
	data = append(data, creator[1])
	data = append(data, creator[2])
	data = append(data, creator[3])
	aIndexBytes := make([]byte, 2)
	binary.BigEndian.PutUint16(aIndexBytes, aIndex)
	data = append(data, aIndexBytes...)
	bitmapBytes := make([]byte, 2)
	binary.BigEndian.PutUint16(bitmapBytes, bitmap)
	data = append(data, bitmapBytes...)

	packet := CreateFPPacket(DSICommand, data)

	// send message
	ok := WritePacket(conn, packet)
	if !ok {
		return nil, false
	}

	// read response
	response, ok := ReadPacket(conn)

	return response, ok
}

// Sends the setfileparams command to the AFP server.
func SetFilParams(conn net.Conn, volID uint16, dirID uint32, bitmap uint16, path string, buffer []byte) bool {
	data := []byte{}
	data = append(data, AFPSetFileParams)
	data = append(data, byte(0x00))
	volIDBytes := make([]byte, 2)
	binary.BigEndian.PutUint16(volIDBytes, volID)
	data = append(data, volIDBytes...)
	dirIDBytes := make([]byte, 4)
	binary.BigEndian.PutUint32(dirIDBytes, dirID)
	data = append(data, dirIDBytes...)
	bitmapBytes := make([]byte, 2)
	binary.BigEndian.PutUint16(bitmapBytes, bitmap)
	data = append(data, bitmapBytes...)
	data = append(data, byte(0x2))
	data = append(data, uint8(len(path)))
	data = append(data, []byte(path)...)
	data = append(data, buffer...)

	packet := CreateFPPacket(DSICommand, data)

	// send message
	ok := WritePacket(conn, packet)
	if !ok {
		return false
	}

	// read response
	_, ok = ReadPacket(conn)

	return ok
}

// Sends the Delete command to the AFP server.
func Delete(conn net.Conn, volumeID uint16, dirID uint32, path string) bool {
	// requires protocol 3.2 and specific support configured at build time.
	pathElements := strings.Split(path, "/")
	fileName := pathElements[len(pathElements)-1]

	data := []byte{}
	data = append(data, AFPDelete)
	data = append(data, byte(0x00))
	volIDBytes := make([]byte, 2)
	binary.BigEndian.PutUint16(volIDBytes, volumeID)
	data = append(data, volIDBytes...)
	dirIDBytes := make([]byte, 4)
	binary.BigEndian.PutUint32(dirIDBytes, dirID)
	data = append(data, dirIDBytes...)
	data = append(data, byte(0x2))
	data = append(data, uint8(len(fileName)))
	data = append(data, []byte(fileName)...)

	packet := CreateFPPacket(DSICommand, data)

	// send message
	ok := WritePacket(conn, packet)
	if !ok {
		return false
	}

	// read response
	_, ok = ReadPacket(conn)

	return ok
}

// Reads a file in the root directory on a AFP server.
// Works by looking up the file in the root volume, opening it's metadata file, and reading the file data.
// The parameters are conn - a connection to the AFP server created by afp.Connect(), path - a string of the file to open,
// and forkFlag, which is passed to OpenFork. To read just the file contents, it should be 0x00, to read
// fork resource  data, it should be 0x02.
func ReadFile(conn net.Conn, path string, forkFlag byte) ([]byte, bool) {
	// Step 1: Walk the directory tree to find volume and directory IDs
	volumeID, dirID, ok := WalkRootDir(conn, path)
	if !ok {
		return nil, false
	}

	// Step 2: Open the fork file
	response, ok := OpenFork(conn, forkFlag, volumeID, dirID, uint16(0x00), AccessModeRead, path)
	if !ok || len(response.Body) < 4 {
		return nil, false
	}
	forkID := uint16(response.Body[3]) | uint16(response.Body[2])<<8

	// Step 3: Read data
	readData := []byte{}
	offset := uint64(0)
	count := uint64(1024)
	for {
		response, ok := ReadExt(conn, forkID, offset, count)
		if !ok {
			break
		}
		if response.Header.ErrorCode == 0 || response.Header.ErrorCode == 0xFFFFEC6F {
			readData = append(readData, response.Body...)

			break
		}

		readData = append(readData, response.Body...)
		offset += count
	}

	// step 4: close the fork file
	ok = CloseFork(conn, forkID)

	return readData, ok
}

// Writes a new file to the root directory of AFP Server.
// The parameters are conn - a connection to the AFP server created by afp.Connect(), path - a string of the file to create,
// fdata - the file data to write, and withFork - whether to create a fork metadata file.
func WriteNewFile(conn net.Conn, path string, fdata []byte, withFork bool) bool {
	// Step 1: Walk the directory tree to find volume and directory IDs
	volumeID, dirID, ok := WalkRootDir(conn, path)
	if !ok {
		return false
	}

	pathElements := strings.Split(path, "/")
	fileName := pathElements[len(pathElements)-1]

	// Step 2: Create the file
	ok = CreateFile(conn, volumeID, dirID, fileName)
	if !ok {
		return false
	}

	openFlag := byte(0x00)
	if withFork {
		openFlag = byte(0x02)
	}

	// Step 3: Open the fork file
	response, ok := OpenFork(conn, openFlag, volumeID, dirID, uint16(0), AccessModeWrite, path)
	if !ok || len(response.Body) < 4 {
		return false
	}
	forkID := uint16(response.Body[3]) | uint16(response.Body[2])<<8

	// Step 4: Write data
	ok = WriteExt(conn, forkID, fdata)
	if !ok {
		return false
	}

	ok = CloseFork(conn, forkID)

	return ok
}

// Writes to an existing file to the root directory of AFP Server.
// The parameters are conn - a connection to the AFP server created by afp.Connect(), path - a string of the file to create,
// fdata - the file data to write, and withFork - whether to create a fork metadata file.
func WriteFile(conn net.Conn, path string, fdata []byte, withFork bool) bool {
	// Step 1: Walk the directory tree to find volume and directory IDs
	volumeID, dirID, ok := WalkRootDir(conn, path)
	if !ok {
		return false
	}

	openFlag := byte(0x00)
	if withFork {
		openFlag = byte(0x02)
	}

	// Step 2: Open the fork file
	response, ok := OpenFork(conn, openFlag, volumeID, dirID, uint16(0), AccessModeWrite, path)
	if !ok || len(response.Body) < 4 {
		return false
	}
	forkID := uint16(response.Body[3]) | uint16(response.Body[2])<<8

	// Step 3: Write data
	ok = WriteExt(conn, forkID, fdata)
	if !ok {
		return false
	}

	ok = CloseFork(conn, forkID)

	return ok
}

// Deletes a file from the root directory of AFP Server via filename.
func DeleteFile(conn net.Conn, path string) bool {
	// Step 1: Walk the directory tree to find volume and directory IDs
	volumeID, dirID, ok := WalkRootDir(conn, path)
	if !ok {
		return false
	}

	// Step 2: Delete file
	ok = Delete(conn, volumeID, dirID, path)
	if !ok {
		return false
	}

	return ok
}

// Move and Renames an existing file to the root directory of AFP Server.
func RenameFileHelper(conn net.Conn, srcPath string, dstPath string, dstName string) bool {
	// Step 1: Walk the directory trees to find volume and directory IDs
	volumeID1, dirID1, ok := WalkRootDir(conn, srcPath)
	if !ok {
		return false
	}

	_, dirID2, ok := WalkRootDir(conn, dstPath)
	if !ok {
		return false
	}

	if dirID1 == dirID2 {
		dstPath = ""
	}
	pathElements := strings.Split(srcPath, "/")
	srcFileName := pathElements[len(pathElements)-1]

	// Step 2: move files
	ok = MoveAndRenameFile(conn, volumeID1, dirID1, srcFileName, dirID2, dstPath, dstName)

	return ok
}
