From f2b0cec266b8955f3a18b944fd3c42747f703152 Mon Sep 17 00:00:00 2001 From: Unbewohnte Date: Mon, 8 Nov 2021 13:39:03 +0300 Subject: [PATCH] [node|protocol] implemented transfer offer packet construction; moved packets implementation from node package to protocol; fixed a race during offer rejection in receiving node --- src/fsys/dir.go | 1 - src/node/node.go | 123 +++++++++----- src/node/node_test.go | 82 +++++----- src/node/packets.go | 122 -------------- src/node/transfer.go | 209 ------------------------ src/protocol/constants.go | 10 +- src/protocol/headers.go | 16 +- src/protocol/packet.go | 66 +------- src/protocol/packetConstruct.go | 84 ++++++++++ src/protocol/packetDecode.go | 154 +++++++++++++++++ src/protocol/recv.go | 69 ++++++++ src/protocol/send.go | 189 +++++++++++++++++++++ src/testfiles/testDownload/testfile.txt | 11 -- 13 files changed, 649 insertions(+), 487 deletions(-) delete mode 100644 src/node/packets.go delete mode 100644 src/node/transfer.go create mode 100644 src/protocol/packetConstruct.go create mode 100644 src/protocol/packetDecode.go create mode 100644 src/protocol/recv.go create mode 100644 src/protocol/send.go diff --git a/src/fsys/dir.go b/src/fsys/dir.go index f8820e9..996207b 100644 --- a/src/fsys/dir.go +++ b/src/fsys/dir.go @@ -14,7 +14,6 @@ type Directory struct { Size uint64 Files []*File Directories []*Directory - Checksum string // Set manually } var ErrorNotDirectory error = fmt.Errorf("not a directory") diff --git a/src/node/node.go b/src/node/node.go index 0ac19e8..f4c6da1 100644 --- a/src/node/node.go +++ b/src/node/node.go @@ -7,6 +7,7 @@ import ( "os" "path/filepath" "strings" + "sync" "time" "fmt" @@ -54,6 +55,7 @@ type transferInfo struct { // Sender and receiver in one type ! type Node struct { + mutex *sync.Mutex packetPipe chan *protocol.Packet // a way to receive incoming packets from another goroutine isSending bool // sending or a receiving node netInfo *netInfoInfo @@ -81,6 +83,7 @@ func NewNode(options *NodeOptions) (*Node, error) { } node := Node{ + mutex: &sync.Mutex{}, packetPipe: make(chan *protocol.Packet, 100), isSending: options.IsSending, netInfo: &netInfoInfo{ @@ -176,12 +179,12 @@ func (node *Node) Start() { case true: // SENDER - // retrieve necessary information, wait for connection localIP, err := addr.GetLocal() if err != nil { panic(err) } + // retrieve information about the file|directory var file *fsys.File var dir *fsys.Directory switch node.transferInfo.Sending.IsDirectory { @@ -197,7 +200,12 @@ func (node *Node) Start() { } } - fmt.Printf("Sending \"%s\" (%.2f MB) locally on %s:%d\n", file.Name, float32(file.Size)/1024/1024, localIP, node.netInfo.Port) + if dir != nil { + fmt.Printf("Sending \"%s\" (%.2f MB) locally on %s:%d\n", dir.Name, float32(dir.Size)/1024/1024, localIP, node.netInfo.Port) + } else { + fmt.Printf("Sending \"%s\" (%.2f MB) locally on %s:%d\n", file.Name, float32(file.Size)/1024/1024, localIP, node.netInfo.Port) + + } // wain for another node to connect err = node.waitForConnection() @@ -210,20 +218,16 @@ func (node *Node) Start() { node.netInfo.EncryptionKey = encrKey fmt.Printf("Generated encryption key: %s\n", encrKey) - err = sendEncryptionKey(node.netInfo.Conn, encrKey) + err = protocol.SendEncryptionKey(node.netInfo.Conn, encrKey) if err != nil { panic(err) } // listen for incoming packets - go receivePackets(node.netInfo.Conn, node.packetPipe) + go protocol.ReceivePackets(node.netInfo.Conn, node.packetPipe) - // send info on file/directory - if dir != nil { - go sendDirectoryPacket(node.netInfo.Conn, dir, node.netInfo.EncryptionKey) - } else { - go sendFilePacket(node.netInfo.Conn, file, node.netInfo.EncryptionKey) - } + // send info about file/directory + go protocol.SendTransferOffer(node.netInfo.Conn, file, dir, node.netInfo.EncryptionKey) // mainloop for { @@ -238,6 +242,7 @@ func (node *Node) Start() { node.state.Stopped = true } + // if encryption key is set - decrypt packet on the spot if node.netInfo.EncryptionKey != nil { err = incomingPacket.DecryptBody(node.netInfo.EncryptionKey) if err != nil { @@ -281,10 +286,10 @@ func (node *Node) Start() { } case false: - // sending a single file - err = sendPiece(file, node.netInfo.Conn, node.netInfo.EncryptionKey) + // sending a piece of a single file + err = protocol.SendPiece(file, node.netInfo.Conn, node.netInfo.EncryptionKey) switch err { - case ErrorSentAll: + case protocol.ErrorSentAll: // the file has been sent fully fileIDBuff := new(bytes.Buffer) err = binary.Write(fileIDBuff, binary.BigEndian, file.ID) @@ -312,9 +317,12 @@ func (node *Node) Start() { Header: protocol.HeaderDone, }) + fmt.Printf("Transfer ended successfully\n") + node.state.Stopped = true case nil: + break default: node.state.Stopped = true @@ -340,11 +348,15 @@ func (node *Node) Start() { } // listen for incoming packets - go receivePackets(node.netInfo.Conn, node.packetPipe) + go protocol.ReceivePackets(node.netInfo.Conn, node.packetPipe) // mainloop for { - if node.state.Stopped { + node.mutex.Lock() + stopped := node.state.Stopped + node.mutex.Unlock() + + if stopped { node.disconnect() break } @@ -354,6 +366,8 @@ func (node *Node) Start() { if !ok { break } + + // if encryption key is set - decrypt packet on the spot if node.netInfo.EncryptionKey != nil { err = incomingPacket.DecryptBody(node.netInfo.EncryptionKey) if err != nil { @@ -364,51 +378,71 @@ func (node *Node) Start() { // react based on a header of a received packet switch incomingPacket.Header { - case protocol.HeaderFile: - // process an information about a singe file. Accept or reject the transfer + case protocol.HeaderTransferOffer: + // accept of reject offer go func() { - file, err := decodeFilePacket(incomingPacket) + file, dir, err := protocol.DecodeTransferPacket(incomingPacket) if err != nil { panic(err) } - fmt.Printf("| Filename: %s\n| Size: %.2f MB\n| Checksum: %s\n", file.Name, float32(file.Size)/1024/1024, file.Checksum) + if file != nil { + fmt.Printf("\n| Filename: %s\n| Size: %.2f MB\n| Checksum: %s\n", file.Name, float32(file.Size)/1024/1024, file.Checksum) + } else if dir != nil { + fmt.Printf("\n| Directory name: %s\n| Size: %.2f MB\n", dir.Name, float32(dir.Size)/1024/1024) + } + var answer string fmt.Printf("| Download ? [Y/n]: ") fmt.Scanln(&answer) fmt.Printf("\n\n") - responsePacketFileIDBuffer := new(bytes.Buffer) - binary.Write(responsePacketFileIDBuffer, binary.BigEndian, file.ID) - if strings.EqualFold(answer, "y") || answer == "" { // yes - err = os.MkdirAll(node.transferInfo.Receiving.DownloadsPath, os.ModePerm) - if err != nil { - panic(err) - } + if file != nil { + // file + err = os.MkdirAll(filepath.Join(node.transferInfo.Receiving.DownloadsPath), os.ModePerm) + if err != nil { + panic(err) + } + fullFilePath := filepath.Join(node.transferInfo.Receiving.DownloadsPath, file.Name) + + // check if the file already exists; if yes - remove it and replace with a new one + _, err := os.Stat(fullFilePath) + if err == nil { + // exists + // remove it + os.Remove(fullFilePath) + } - fullFilePath := filepath.Join(node.transferInfo.Receiving.DownloadsPath, file.Name) + file.Path = fullFilePath + file.Open() - // check if the file already exists; if yes - remove it and replace with a new one - _, err := os.Stat(fullFilePath) - if err == nil { - // exists - // remove it - os.Remove(fullFilePath) - } + node.mutex.Lock() + node.transferInfo.Receiving.AcceptedFiles = append(node.transferInfo.Receiving.AcceptedFiles, file) + node.mutex.Unlock() - file.Path = fullFilePath - file.Open() + } else if dir != nil { + // directory - node.transferInfo.Receiving.AcceptedFiles = append(node.transferInfo.Receiving.AcceptedFiles, file) + // add a new directory to downloads path + node.transferInfo.Receiving.DownloadsPath = filepath.Join(node.transferInfo.Receiving.DownloadsPath, dir.Name) + err = os.MkdirAll(node.transferInfo.Receiving.DownloadsPath, os.ModePerm) + if err != nil { + panic(err) + } + } // send aceptance packet + responsePacketFileIDBuffer := new(bytes.Buffer) + binary.Write(responsePacketFileIDBuffer, binary.BigEndian, file.ID) + acceptancePacket := protocol.Packet{ Header: protocol.HeaderAccept, Body: responsePacketFileIDBuffer.Bytes(), } + if node.netInfo.EncryptionKey != nil { err = acceptancePacket.EncryptBody(node.netInfo.EncryptionKey) if err != nil { @@ -432,6 +466,9 @@ func (node *Node) Start() { } else { // no + responsePacketFileIDBuffer := new(bytes.Buffer) + binary.Write(responsePacketFileIDBuffer, binary.BigEndian, file.ID) + rejectionPacket := protocol.Packet{ Header: protocol.HeaderReject, Body: responsePacketFileIDBuffer.Bytes(), @@ -449,10 +486,16 @@ func (node *Node) Start() { panic(err) } + node.mutex.Lock() node.state.Stopped = true + node.mutex.Unlock() } }() + case protocol.HeaderFile: + // process an information about a singe file + // (TODO) + case protocol.HeaderFileBytes: // check if this file has been accepted to receive fileIDReader := bytes.NewReader(incomingPacket.Body) @@ -537,11 +580,17 @@ func (node *Node) Start() { node.netInfo.EncryptionKey = encrKey + fmt.Printf("Got an encryption key: %s\n", encrKey) + case protocol.HeaderDone: + node.mutex.Lock() node.state.Stopped = true + node.mutex.Unlock() case protocol.HeaderDisconnecting: + node.mutex.Lock() node.state.Stopped = true + node.mutex.Unlock() fmt.Printf("%s disconnected\n", node.netInfo.Conn.RemoteAddr()) } diff --git a/src/node/node_test.go b/src/node/node_test.go index c1fd6ac..f22493a 100644 --- a/src/node/node_test.go +++ b/src/node/node_test.go @@ -1,51 +1,45 @@ package node -import ( - "fmt" - "os" - "testing" -) - // Not complete -func Test_Sendfile(t *testing.T) { - rnodeOptions := NodeOptions{ - IsSending: false, - WorkingPort: 8888, - ServerSide: &ServerSideNodeOptions{ - ServingPath: "", - Recursive: false, - }, - ClientSide: &ClientSideNodeOptions{ - ConnectionAddr: "localhost", - DownloadsFolderPath: "../testfiles/testDownload/", - }, - } - receivingNode, err := NewNode(&rnodeOptions) - if err != nil { - fmt.Printf("Error constructing a new node: %s\n", err) - os.Exit(-1) - } +// func Test_Sendfile(t *testing.T) { +// rnodeOptions := NodeOptions{ +// IsSending: false, +// WorkingPort: 8888, +// ServerSide: &ServerSideNodeOptions{ +// ServingPath: "", +// Recursive: false, +// }, +// ClientSide: &ClientSideNodeOptions{ +// ConnectionAddr: "localhost", +// DownloadsFolderPath: "../testfiles/testDownload/", +// }, +// } +// receivingNode, err := NewNode(&rnodeOptions) +// if err != nil { +// fmt.Printf("Error constructing a new node: %s\n", err) +// os.Exit(-1) +// } - snodeOptions := NodeOptions{ - IsSending: true, - WorkingPort: 8888, - ServerSide: &ServerSideNodeOptions{ - ServingPath: "../testfiles/testfile.txt", - Recursive: false, - }, - ClientSide: &ClientSideNodeOptions{ - ConnectionAddr: "", - DownloadsFolderPath: "", - }, - } +// snodeOptions := NodeOptions{ +// IsSending: true, +// WorkingPort: 8888, +// ServerSide: &ServerSideNodeOptions{ +// ServingPath: "../testfiles/testfile.txt", +// Recursive: false, +// }, +// ClientSide: &ClientSideNodeOptions{ +// ConnectionAddr: "", +// DownloadsFolderPath: "", +// }, +// } - sendingNode, err := NewNode(&snodeOptions) - if err != nil { - fmt.Printf("Error constructing a new node: %s\n", err) - os.Exit(-1) - } +// sendingNode, err := NewNode(&snodeOptions) +// if err != nil { +// fmt.Printf("Error constructing a new node: %s\n", err) +// os.Exit(-1) +// } - go receivingNode.Start() +// go receivingNode.Start() - sendingNode.Start() -} +// sendingNode.Start() +// } diff --git a/src/node/packets.go b/src/node/packets.go deleted file mode 100644 index 0cf722e..0000000 --- a/src/node/packets.go +++ /dev/null @@ -1,122 +0,0 @@ -// node-specific packets and packet handling -package node - -import ( - "bytes" - "encoding/binary" - "fmt" - "net" - - "github.com/Unbewohnte/ftu/fsys" - "github.com/Unbewohnte/ftu/protocol" -) - -// sends an encryption key to the other side -func sendEncryptionKey(connection net.Conn, encrKey []byte) error { - encrKeyPacketBuffer := new(bytes.Buffer) - - encrKeyLength := uint64(len(encrKey)) - - err := binary.Write(encrKeyPacketBuffer, binary.BigEndian, &encrKeyLength) - if err != nil { - return err - } - - encrKeyPacketBuffer.Write(encrKey) - - err = protocol.SendPacket(connection, protocol.Packet{ - Header: protocol.HeaderEncryptionKey, - Body: encrKeyPacketBuffer.Bytes(), - }) - if err != nil { - return err - } - - return nil -} - -var ErrorNotConnected error = fmt.Errorf("not connected") - -// Reads packets from connection in an endless loop, sends them to the channel, if encrKey is not nil - -// decrypts packet`s body beforehand -func receivePackets(connection net.Conn, packetPipe chan *protocol.Packet) error { - for { - if connection == nil { - return ErrorNotConnected - } - - packetBytes, err := protocol.ReadFromConn(connection) - if err != nil { - close(packetPipe) - return err - } - - incomingPacket, err := protocol.BytesToPacket(packetBytes) - if err != nil { - close(packetPipe) - return err - } - - packetPipe <- incomingPacket - } -} - -// decodes packet with the header FILE into the fsys.File struct. If encrKey is not nil - -// filepacket`s body will be decrypted beforehand -func decodeFilePacket(filePacket *protocol.Packet) (*fsys.File, error) { - // FILE~(idInBinary)(filenameLengthInBinary)(filename)(filesize)(checksumLengthInBinary)checksum - - // retrieve data from packet body - - // id - packetReader := bytes.NewBuffer(filePacket.Body) - - var fileID uint64 - err := binary.Read(packetReader, binary.BigEndian, &fileID) - if err != nil { - panic(err) - } - - // filename - var filenameLength uint64 - err = binary.Read(packetReader, binary.BigEndian, &filenameLength) - if err != nil { - panic(err) - } - - filenameBytes := make([]byte, filenameLength) - _, err = packetReader.Read(filenameBytes) - if err != nil { - panic(err) - } - - filename := string(filenameBytes) - - // filesize - var filesize uint64 - err = binary.Read(packetReader, binary.BigEndian, &filesize) - if err != nil { - panic(err) - } - - // checksum - var checksumLength uint64 - err = binary.Read(packetReader, binary.BigEndian, &checksumLength) - if err != nil { - panic(err) - } - checksumBytes := make([]byte, checksumLength) - _, err = packetReader.Read(checksumBytes) - if err != nil { - panic(err) - } - checksum := string(checksumBytes) - - return &fsys.File{ - ID: fileID, - Name: filename, - Size: filesize, - Checksum: checksum, - Handler: nil, - }, nil -} diff --git a/src/node/transfer.go b/src/node/transfer.go deleted file mode 100644 index 78ec8a1..0000000 --- a/src/node/transfer.go +++ /dev/null @@ -1,209 +0,0 @@ -package node - -import ( - "bytes" - "encoding/binary" - "fmt" - "io" - "net" - "os" - - "github.com/Unbewohnte/ftu/checksum" - "github.com/Unbewohnte/ftu/fsys" - "github.com/Unbewohnte/ftu/protocol" -) - -// sends a notification about the file to the other side. If encrKey is not nil - encrypts packet`s body -func sendFilePacket(connection net.Conn, file *fsys.File, encrKey []byte) error { - if connection == nil { - return ErrorNotConnected - } - - err := file.Open() - if err != nil { - return err - } - - // FILE~(idInBinary)(filenameLengthInBinary)(filename)(filesize)(checksumLengthInBinary)(checksum) - - // send file packet with file description - filePacket := protocol.Packet{ - Header: protocol.HeaderFile, - } - fPacketBodyBuff := new(bytes.Buffer) - - // file id - binary.Write(fPacketBodyBuff, binary.BigEndian, &file.ID) - - // filename - filenameLen := uint64(len([]byte(file.Name))) - binary.Write(fPacketBodyBuff, binary.BigEndian, &filenameLen) - fPacketBodyBuff.Write([]byte(file.Name)) - - // size - binary.Write(fPacketBodyBuff, binary.BigEndian, &file.Size) - - // checksum - fileChecksum, err := checksum.GetPartialCheckSum(file.Handler) - if err != nil { - return err - } - - checksumLen := uint64(len([]byte(fileChecksum))) - binary.Write(fPacketBodyBuff, binary.BigEndian, &checksumLen) - fPacketBodyBuff.Write([]byte(fileChecksum)) - - filePacket.Body = fPacketBodyBuff.Bytes() - - if encrKey != nil { - // if the key is given - encrypt ready-to-go packet - err = filePacket.EncryptBody(encrKey) - if err != nil { - return err - } - } - - // and send it - err = protocol.SendPacket(connection, filePacket) - if err != nil { - return err - } - - // we do not check for packet size because there is no way that it`ll exceed current - // maximum of 128 KiB - return nil -} - -// sends a notification about the directory. If encrkey != nil - encrypts -// packet`s body -func sendDirectoryPacket(connection net.Conn, dir *fsys.Directory, encrKey []byte) error { - if connection == nil { - return ErrorNotConnected - } - - dirPacket := protocol.Packet{ - Header: protocol.HeaderDirectory, - } - - // DIRECTORY~(dirname size in binary)(dirname)(dirsize)(checksumLengthInBinary)(checksum) - - dirPacketBuffer := new(bytes.Buffer) - - // dirname - dirnameLength := uint64(len(dir.Name)) - err := binary.Write(dirPacketBuffer, binary.BigEndian, &dirnameLength) - if err != nil { - return err - } - dirPacketBuffer.Write([]byte(dir.Name)) - - // dirsize - err = binary.Write(dirPacketBuffer, binary.BigEndian, dir.Size) - if err != nil { - return err - } - - // checksum - checksumLength := uint64(len(dir.Checksum)) - err = binary.Write(dirPacketBuffer, binary.BigEndian, &checksumLength) - if err != nil { - return err - } - dirPacketBuffer.Write([]byte(dir.Checksum)) - - dirPacket.Body = dirPacketBuffer.Bytes() - - if encrKey != nil { - // if the key is given - encrypt ready-to-go packet - err = dirPacket.EncryptBody(encrKey) - if err != nil { - return err - } - } - - // and send it - err = protocol.SendPacket(connection, dirPacket) - if err != nil { - return err - } - - // we do not check for packet size because there is no way that it`ll exceed current - // maximum of 128 KiB - return nil -} - -var ErrorSentAll error = fmt.Errorf("sent the whole file") - -// sends a piece of file to the connection; The next calls will send -// another piece util the file has been fully sent. If encrKey is not nil - encrypts each packet with -// this key -func sendPiece(file *fsys.File, connection net.Conn, encrKey []byte) error { - if file.Handler == nil { - fHandler, err := os.Open(file.Path) - if err != nil { - return err - } - - file.Handler = fHandler - } - - if file.SentBytes == 0 { - file.Handler.Seek(0, io.SeekStart) - } - - if file.Size == file.SentBytes { - return ErrorSentAll - } - - fileBytesPacket := protocol.Packet{ - Header: protocol.HeaderFileBytes, - } - - packetBodyBuff := new(bytes.Buffer) - - // write file ID first - err := binary.Write(packetBodyBuff, binary.BigEndian, &file.ID) - if err != nil { - return err - } - - // fill the remaining space of packet with the contents of a file - canSendBytes := uint64(protocol.MAXPACKETSIZE) - fileBytesPacket.Size() - uint64(packetBodyBuff.Len()) - - if encrKey != nil { - // account for padding - canSendBytes -= 32 - } - - if (file.Size - file.SentBytes) < canSendBytes { - canSendBytes = (file.Size - file.SentBytes) - } - - fileBytes := make([]byte, canSendBytes) - - read, err := file.Handler.ReadAt(fileBytes, int64(file.SentBytes)) - if err != nil { - return err - } - - packetBodyBuff.Write(fileBytes) - - fileBytesPacket.Body = packetBodyBuff.Bytes() - - if encrKey != nil { - err = fileBytesPacket.EncryptBody(encrKey) - if err != nil { - return err - } - } - - // send it to the other side - err = protocol.SendPacket(connection, fileBytesPacket) - if err != nil { - return err - } - - file.SentBytes += uint64(read) - - return nil -} diff --git a/src/protocol/constants.go b/src/protocol/constants.go index 13d70c7..7f70766 100644 --- a/src/protocol/constants.go +++ b/src/protocol/constants.go @@ -1,4 +1,4 @@ -// This file contains global constants of the protocol +// global constants of the protocol package protocol // MAXPACKETSIZE. @@ -8,5 +8,11 @@ const MAXPACKETSIZE uint = 131072 // 128 KiB // HEADERDELIMETER. // Character that delimits header of the packet from the body of the packet. -// ie: FILEINFO~img.png +// ie: (packet header)~(packet body) const HEADERDELIMETER string = "~" + +// FILECODE. +const FILECODE string = "f" + +// DIRCODE. +const DIRCODE string = "d" diff --git a/src/protocol/headers.go b/src/protocol/headers.go index f49d058..88d06a7 100644 --- a/src/protocol/headers.go +++ b/src/protocol/headers.go @@ -6,7 +6,7 @@ type Header string // Headers //// In the following examples "~" is the HEADERDELIMETER -//// and (size) is 8 bytes long binary encoded uint64 +//// and (size) is 8 bytes long big-endian binary encoded uint64 // ENCRKEY. // The FIRST header to be sent. Sent immediately after the connection has been established @@ -52,6 +52,18 @@ const HeaderReady Header = "READY" // ie: BYE!~ const HeaderDisconnecting Header = "BYE!" +// TRANSFEROFFER. +// Sent by sender AFTER ENCRKEY packet if present and BEFORE any other transfer-specific +// packet ONLY ONCE. Asks the receiving node whether it accepts or rejects the transfer of +// offered single file or a directory. +// The body must contain a file or directory code that tells whether +// a file or a directory will be sent in case of acceptance. The rest must be identical either to the FILE or DIRECTORY packet. +// e for directory: TRANSFER~(dircode)(dirname size in binary)(dirname)(dirsize) +// e for a single file: TRANSFER~(filecode)(id in binary)(filename length in binary)(filename)(filesize)(checksum length in binary)(checksum) +// dircode and filecode are pre-declared in the constants of the protocol (d) and (f). +// The actual transfer must start only after the other node has accepted the dir/file with ACCEPT packet. +const HeaderTransferOffer Header = "TRANSFEROFFER" + // FILE. // Sent by sender, indicating that the file is going to be sent. // The body structure must follow such structure: @@ -73,5 +85,5 @@ const HeaderEndfile Header = "ENDFILE" // DIRECTORY // Sent by sender, indicates that a directory with current information // is going to be sent. The structure of the body must follow the example: -// ie: DIRECTORY~(dirname size in binary)(dirname)(dirsize)(checksumLengthInBinary)(checksum) +// ie: DIRECTORY~(dirname size in binary)(dirname)(dirsize) const HeaderDirectory Header = "DIRECTORY" diff --git a/src/protocol/packet.go b/src/protocol/packet.go index 76f01dd..771106b 100644 --- a/src/protocol/packet.go +++ b/src/protocol/packet.go @@ -1,7 +1,7 @@ -// This file describes the general packet structure and provides methods to work with them before|after the transportation +// General packet structure and methods to work with them before|after the transportation -// General packet structure: -// (size of the whole packet in binary)(packet header)(header delimeter (~))(packet contents) +// Packet structure during transportation: +// (size of the whole packet in binary (big endian uint64))(packet header)(header delimeter (~))(packet contents) package protocol @@ -9,7 +9,6 @@ import ( "bytes" "encoding/binary" "fmt" - "net" "strings" "github.com/Unbewohnte/ftu/encryption" @@ -21,6 +20,8 @@ type Packet struct { Body []byte } +var ErrorInvalidPacket error = fmt.Errorf("invalid packet header or body") + // Returns a size of the given packet as if it would be sent and presented in bytes. // ie: FILE~bytes_here func (packet *Packet) Size() uint64 { @@ -32,14 +33,12 @@ func (packet *Packet) Size() uint64 { return uint64(packetBytes.Len()) } -var ErrorNotPacketBytes error = fmt.Errorf("not packet bytes") - // Converts packet bytes into Packet struct func BytesToPacket(packetbytes []byte) (*Packet, error) { // check if there`s a header delimiter present pString := string(packetbytes) if !strings.Contains(pString, HEADERDELIMETER) { - return nil, ErrorNotPacketBytes + return nil, ErrorInvalidPacket } var header Header @@ -59,7 +58,7 @@ func BytesToPacket(packetbytes []byte) (*Packet, error) { }, nil } -var ErrorExceededMaxPacketsize error = fmt.Errorf("the packet is too big") +var ErrorExceededMaxPacketsize error = fmt.Errorf("packet is too big") // Converts given packet struct into ready-to-transfer bytes, constructed by following the protocol func (packet *Packet) ToBytes() ([]byte, error) { @@ -86,22 +85,6 @@ func (packet *Packet) ToBytes() ([]byte, error) { return packetBuffer.Bytes(), nil } -// Sends given packet to connection. -// ALL packets MUST be sent by this method -func SendPacket(connection net.Conn, packet Packet) error { - packetBytes, err := packet.ToBytes() - if err != nil { - return err - } - - // fmt.Printf("[SEND] packet %+s; len: %d\n", packetBytes[:30], len(packetBytes)) - - // write the result (ie: (packetsize)(header)~(bodybytes)) - connection.Write(packetBytes) - - return nil -} - // Encrypts packet`s BODY with AES encryption func (packet *Packet) EncryptBody(key []byte) error { // encrypting packet`s body @@ -129,38 +112,3 @@ func (packet *Packet) DecryptBody(key []byte) error { return nil } - -// Reads a packet from given connection, returns its bytes. -// ASSUMING THAT THE PACKETS ARE SENT BY `SendPacket` function !!!! -func ReadFromConn(connection net.Conn) ([]byte, error) { - var packetSize uint64 - err := binary.Read(connection, binary.BigEndian, &packetSize) - if err != nil { - return nil, err - } - - // have a packetsize, now reading the whole packet - packetBuffer := new(bytes.Buffer) - - // splitting a big-sized packet into chunks and constructing it from pieces - left := packetSize - for { - if left == 0 { - break - } - - buff := make([]byte, 8192) - if left < uint64(len(buff)) { - buff = make([]byte, left) - } - - read, _ := connection.Read(buff) - left -= uint64(read) - - packetBuffer.Write(buff[:read]) - } - - // fmt.Printf("[RECV] read from connection: %s; length: %d\n", packetBuffer.Bytes()[:30], packetBuffer.Len()) - - return packetBuffer.Bytes(), nil -} diff --git a/src/protocol/packetConstruct.go b/src/protocol/packetConstruct.go new file mode 100644 index 0000000..ce98c58 --- /dev/null +++ b/src/protocol/packetConstruct.go @@ -0,0 +1,84 @@ +// Methods to construct various packets defined in a protocol +package protocol + +import ( + "bytes" + "encoding/binary" + + "github.com/Unbewohnte/ftu/checksum" + "github.com/Unbewohnte/ftu/fsys" +) + +// constructs a ready to send FILE packet +func CreateFilePacket(file *fsys.File) (*Packet, error) { + err := file.Open() + if err != nil { + return nil, err + } + defer file.Handler.Close() + + // FILE~(idInBinary)(filenameLengthInBinary)(filename)(filesize)(checksumLengthInBinary)(checksum) + + filePacket := Packet{ + Header: HeaderFile, + } + fPacketBodyBuff := new(bytes.Buffer) + + // file id + binary.Write(fPacketBodyBuff, binary.BigEndian, &file.ID) + + // filename + filenameLen := uint64(len([]byte(file.Name))) + binary.Write(fPacketBodyBuff, binary.BigEndian, &filenameLen) + fPacketBodyBuff.Write([]byte(file.Name)) + + // size + binary.Write(fPacketBodyBuff, binary.BigEndian, &file.Size) + + // checksum + fileChecksum, err := checksum.GetPartialCheckSum(file.Handler) + if err != nil { + return nil, err + } + + checksumLen := uint64(len([]byte(fileChecksum))) + binary.Write(fPacketBodyBuff, binary.BigEndian, &checksumLen) + fPacketBodyBuff.Write([]byte(fileChecksum)) + + filePacket.Body = fPacketBodyBuff.Bytes() + + // we do not check for packet size because there is no way that it`ll exceed current + // maximum of 128 KiB + return &filePacket, nil +} + +// constructs a ready to send DIRECTORY packet +func CreateDirectoryPacket(dir *fsys.Directory) (*Packet, error) { + dirPacket := Packet{ + Header: HeaderDirectory, + } + + // DIRECTORY~(dirname size in binary)(dirname)(dirsize)(checksumLengthInBinary)(checksum) + + dirPacketBuffer := new(bytes.Buffer) + + // dirname + dirnameLength := uint64(len(dir.Name)) + err := binary.Write(dirPacketBuffer, binary.BigEndian, &dirnameLength) + if err != nil { + return nil, err + } + dirPacketBuffer.Write([]byte(dir.Name)) + + // dirsize + err = binary.Write(dirPacketBuffer, binary.BigEndian, dir.Size) + if err != nil { + return nil, err + } + + dirPacket.Body = dirPacketBuffer.Bytes() + + // we do not check for packet size because there is no way that it`ll exceed current + // maximum of 128 KiB + return &dirPacket, nil +} diff --git a/src/protocol/packetDecode.go b/src/protocol/packetDecode.go new file mode 100644 index 0000000..53feceb --- /dev/null +++ b/src/protocol/packetDecode.go @@ -0,0 +1,154 @@ +// Methods to decode read from connection packets defined in protocol +package protocol + +import ( + "bytes" + "encoding/binary" + "fmt" + + "github.com/Unbewohnte/ftu/fsys" +) + +var ErrorWrongPacket error = fmt.Errorf("wrong type of packet header") + +// decodes packet with the header FILE into the fsys.File struct +func DecodeFilePacket(filePacket *Packet) (*fsys.File, error) { + if filePacket.Header != HeaderFile { + return nil, ErrorWrongPacket + } + // FILE~(idInBinary)(filenameLengthInBinary)(filename)(filesize)(checksumLengthInBinary)checksum + + // retrieve data from packet body + + // id + packetReader := bytes.NewBuffer(filePacket.Body) + + var fileID uint64 + err := binary.Read(packetReader, binary.BigEndian, &fileID) + if err != nil { + return nil, err + } + + // filename + var filenameLength uint64 + err = binary.Read(packetReader, binary.BigEndian, &filenameLength) + if err != nil { + return nil, err + } + + filenameBytes := make([]byte, filenameLength) + _, err = packetReader.Read(filenameBytes) + if err != nil { + return nil, err + } + + filename := string(filenameBytes) + + // filesize + var filesize uint64 + err = binary.Read(packetReader, binary.BigEndian, &filesize) + if err != nil { + return nil, err + } + + // checksum + var checksumLength uint64 + err = binary.Read(packetReader, binary.BigEndian, &checksumLength) + if err != nil { + return nil, err + } + checksumBytes := make([]byte, checksumLength) + _, err = packetReader.Read(checksumBytes) + if err != nil { + return nil, err + } + checksum := string(checksumBytes) + + return &fsys.File{ + ID: fileID, + Name: filename, + Size: filesize, + Checksum: checksum, + Handler: nil, + }, nil +} + +// decodes DIRECTORY packet into fsys.Directory struct +func DecodeDirectoryPacket(dirPacket *Packet) (*fsys.Directory, error) { + if dirPacket.Header != HeaderDirectory { + return nil, ErrorWrongPacket + } + + // DIRECTORY~(dirname size in binary)(dirname)(dirsize) + + packetReader := bytes.NewReader(dirPacket.Body) + + // name + var dirNameSize uint64 + err := binary.Read(packetReader, binary.BigEndian, &dirNameSize) + if err != nil { + return nil, err + } + dirName := make([]byte, dirNameSize) + _, err = packetReader.Read(dirName) + if err != nil { + return nil, err + } + + // size + var dirSize uint64 + err = binary.Read(packetReader, binary.BigEndian, &dirSize) + if err != nil { + return nil, err + } + + dir := fsys.Directory{ + Name: string(dirName), + Size: dirSize, + } + + return &dir, nil +} + +// decodes TRANSFERINFO packet into either fsys.File or fsys.Directory struct. +// decodeTransferPacket cannot return 2 nils or both non-nils as 2 first return values in case +// of a successfull decoding +func DecodeTransferPacket(transferPacket *Packet) (*fsys.File, *fsys.Directory, error) { + if transferPacket.Header != HeaderTransferOffer { + return nil, nil, ErrorWrongPacket + } + + var file *fsys.File = nil + var dir *fsys.Directory = nil + var err error + + // determine if it`s a file or a directory + switch string(transferPacket.Body[0]) { + case FILECODE: + filePacket := Packet{ + Header: HeaderFile, + Body: transferPacket.Body[1:], + } + + file, err = DecodeFilePacket(&filePacket) + if err != nil { + return nil, nil, err + } + + case DIRCODE: + dirPacket := Packet{ + Header: HeaderDirectory, + Body: transferPacket.Body[1:], + } + + dir, err = DecodeDirectoryPacket(&dirPacket) + if err != nil { + return nil, nil, err + } + + default: + return nil, nil, ErrorInvalidPacket + } + + return file, dir, nil +} diff --git a/src/protocol/recv.go b/src/protocol/recv.go new file mode 100644 index 0000000..530abc2 --- /dev/null +++ b/src/protocol/recv.go @@ -0,0 +1,69 @@ +// Methods allowing to receive and preprocess packets from connection +package protocol + +import ( + "bytes" + "encoding/binary" + "fmt" + "net" +) + +// Reads a packet from given connection, returns its bytes. +// ASSUMING THAT THE PACKETS ARE SENT BY `SendPacket` function !!!! +func ReadFromConn(connection net.Conn) ([]byte, error) { + var packetSize uint64 + err := binary.Read(connection, binary.BigEndian, &packetSize) + if err != nil { + return nil, err + } + + // have a packetsize, now reading the whole packet + packetBuffer := new(bytes.Buffer) + + // splitting a big-sized packet into chunks and constructing it from pieces + left := packetSize + for { + if left == 0 { + break + } + + buff := make([]byte, 8192) + if left < uint64(len(buff)) { + buff = make([]byte, left) + } + + read, _ := connection.Read(buff) + left -= uint64(read) + + packetBuffer.Write(buff[:read]) + } + + // fmt.Printf("[RECV] read from connection: %s; length: %d\n", packetBuffer.Bytes()[:30], packetBuffer.Len()) + + return packetBuffer.Bytes(), nil +} + +var ErrorNotConnected error = fmt.Errorf("not connected") + +// Reads packets from connection in an endless loop, sends them to the channel +func ReceivePackets(connection net.Conn, packetPipe chan *Packet) error { + for { + if connection == nil { + return ErrorNotConnected + } + + packetBytes, err := ReadFromConn(connection) + if err != nil { + close(packetPipe) + return err + } + + incomingPacket, err := BytesToPacket(packetBytes) + if err != nil { + close(packetPipe) + return err + } + + packetPipe <- incomingPacket + } +} diff --git a/src/protocol/send.go b/src/protocol/send.go new file mode 100644 index 0000000..c4d90f2 --- /dev/null +++ b/src/protocol/send.go @@ -0,0 +1,189 @@ +// Methonds to send packets in various ways defined in protocol +package protocol + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + "net" + + "github.com/Unbewohnte/ftu/encryption" + "github.com/Unbewohnte/ftu/fsys" +) + +// Sends given packet to connection. +// ALL packets MUST be sent by this method +func SendPacket(connection net.Conn, packet Packet) error { + packetBytes, err := packet.ToBytes() + if err != nil { + return err + } + + // fmt.Printf("[SEND] packet %+s; len: %d\n", packetBytes[:30], len(packetBytes)) + + // write the result (ie: (packetsize)(header)~(bodybytes)) + connection.Write(packetBytes) + + return nil +} + +// sends an encryption key to the other side +func SendEncryptionKey(connection net.Conn, encrKey []byte) error { + encrKeyPacketBuffer := new(bytes.Buffer) + + encrKeyLength := uint64(len(encrKey)) + + err := binary.Write(encrKeyPacketBuffer, binary.BigEndian, &encrKeyLength) + if err != nil { + return err + } + + encrKeyPacketBuffer.Write(encrKey) + + err = SendPacket(connection, Packet{ + Header: HeaderEncryptionKey, + Body: encrKeyPacketBuffer.Bytes(), + }) + if err != nil { + return err + } + + return nil +} + +// sends a TRANSFEROFFER packet to connection with information about either file or directory. +// If file is the only thing that the sender is going to send - leave dir arg as nil, the same +// applies if directory is the only thing that the sender is going to send - leave file as nil. +// sendTransferOffer PANICS if both file and dir are present or nil. If encrKey != nil - encrypts +// constructed packet +func SendTransferOffer(connection net.Conn, file *fsys.File, dir *fsys.Directory, encrKey []byte) error { + if file == nil && dir == nil { + panic("either file or dir must be specified") + } else if file != nil && dir != nil { + panic("only one either file or dir must be specified") + } + + transferOfferPacket := Packet{ + Header: HeaderTransferOffer, + } + + if file != nil { + filePacket, err := CreateFilePacket(file) + if err != nil { + return err + } + + transferOfferBody := append([]byte(FILECODE), filePacket.Body...) + + // if encrKey is present - encrypt + if encrKey != nil { + encryptedBody, err := encryption.Encrypt(encrKey, transferOfferBody) + if err != nil { + return err + } + transferOfferBody = encryptedBody + } + + transferOfferPacket.Body = transferOfferBody + + } else if dir != nil { + dirPacket, err := CreateDirectoryPacket(dir) + if err != nil { + return err + } + + transferOfferBody := append([]byte(DIRCODE), dirPacket.Body...) + // if encrKey is present - encrypt + if encrKey != nil { + encryptedBody, err := encryption.Encrypt(encrKey, transferOfferBody) + if err != nil { + return err + } + transferOfferBody = encryptedBody + } + + transferOfferPacket.Body = transferOfferBody + } + + // send packet + err := SendPacket(connection, transferOfferPacket) + if err != nil { + return err + } + + return nil +} + +var ErrorSentAll error = fmt.Errorf("sent the whole file") + +// sends a piece of file to the connection; The next calls will send +// another piece util the file has been fully sent. If encrKey is not nil - encrypts each packet with +// this key +func SendPiece(file *fsys.File, connection net.Conn, encrKey []byte) error { + err := file.Open() + if err != nil { + return err + } + defer file.Handler.Close() + + if file.SentBytes == 0 { + file.Handler.Seek(0, io.SeekStart) + } + + if file.Size == file.SentBytes { + return ErrorSentAll + } + + fileBytesPacket := Packet{ + Header: HeaderFileBytes, + } + + packetBodyBuff := new(bytes.Buffer) + + // write file ID first + err = binary.Write(packetBodyBuff, binary.BigEndian, &file.ID) + if err != nil { + return err + } + + // fill the remaining space of packet with the contents of a file + canSendBytes := uint64(MAXPACKETSIZE) - fileBytesPacket.Size() - uint64(packetBodyBuff.Len()) + + if encrKey != nil { + // account for padding + canSendBytes -= 32 + } + + if (file.Size - file.SentBytes) < canSendBytes { + canSendBytes = (file.Size - file.SentBytes) + } + + fileBytes := make([]byte, canSendBytes) + + read, err := file.Handler.ReadAt(fileBytes, int64(file.SentBytes)) + if err != nil { + return err + } + + packetBodyBuff.Write(fileBytes) + + fileBytesPacket.Body = packetBodyBuff.Bytes() + + if encrKey != nil { + err = fileBytesPacket.EncryptBody(encrKey) + if err != nil { + return err + } + } + + // send it to the other side + err = SendPacket(connection, fileBytesPacket) + if err != nil { + return err + } + + file.SentBytes += uint64(read) + + return nil +} diff --git a/src/testfiles/testDownload/testfile.txt b/src/testfiles/testDownload/testfile.txt index 99a7032..e69de29 100755 --- a/src/testfiles/testDownload/testfile.txt +++ b/src/testfiles/testDownload/testfile.txt @@ -1,11 +0,0 @@ -727 WYSI Airman Badeu square - - - -doable - - -FCeeeeeeeeeeeeeeeeeeeeeeeee - - -testfile it is \ No newline at end of file