Browse Source

[node|protocol] implemented transfer offer packet construction; moved packets implementation from node package to protocol; fixed a race during offer rejection in receiving node

main
Unbewohnte 3 years ago
parent
commit
f2b0cec266
  1. 1
      src/fsys/dir.go
  2. 123
      src/node/node.go
  3. 82
      src/node/node_test.go
  4. 122
      src/node/packets.go
  5. 209
      src/node/transfer.go
  6. 10
      src/protocol/constants.go
  7. 16
      src/protocol/headers.go
  8. 66
      src/protocol/packet.go
  9. 84
      src/protocol/packetConstruct.go
  10. 154
      src/protocol/packetDecode.go
  11. 69
      src/protocol/recv.go
  12. 189
      src/protocol/send.go
  13. 11
      src/testfiles/testDownload/testfile.txt

1
src/fsys/dir.go

@ -14,7 +14,6 @@ type Directory struct {
Size uint64 Size uint64
Files []*File Files []*File
Directories []*Directory Directories []*Directory
Checksum string // Set manually
} }
var ErrorNotDirectory error = fmt.Errorf("not a directory") var ErrorNotDirectory error = fmt.Errorf("not a directory")

123
src/node/node.go

@ -7,6 +7,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"sync"
"time" "time"
"fmt" "fmt"
@ -54,6 +55,7 @@ type transferInfo struct {
// Sender and receiver in one type ! // Sender and receiver in one type !
type Node struct { type Node struct {
mutex *sync.Mutex
packetPipe chan *protocol.Packet // a way to receive incoming packets from another goroutine packetPipe chan *protocol.Packet // a way to receive incoming packets from another goroutine
isSending bool // sending or a receiving node isSending bool // sending or a receiving node
netInfo *netInfoInfo netInfo *netInfoInfo
@ -81,6 +83,7 @@ func NewNode(options *NodeOptions) (*Node, error) {
} }
node := Node{ node := Node{
mutex: &sync.Mutex{},
packetPipe: make(chan *protocol.Packet, 100), packetPipe: make(chan *protocol.Packet, 100),
isSending: options.IsSending, isSending: options.IsSending,
netInfo: &netInfoInfo{ netInfo: &netInfoInfo{
@ -176,12 +179,12 @@ func (node *Node) Start() {
case true: case true:
// SENDER // SENDER
// retrieve necessary information, wait for connection
localIP, err := addr.GetLocal() localIP, err := addr.GetLocal()
if err != nil { if err != nil {
panic(err) panic(err)
} }
// retrieve information about the file|directory
var file *fsys.File var file *fsys.File
var dir *fsys.Directory var dir *fsys.Directory
switch node.transferInfo.Sending.IsDirectory { 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 // wain for another node to connect
err = node.waitForConnection() err = node.waitForConnection()
@ -210,20 +218,16 @@ func (node *Node) Start() {
node.netInfo.EncryptionKey = encrKey node.netInfo.EncryptionKey = encrKey
fmt.Printf("Generated encryption key: %s\n", 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 { if err != nil {
panic(err) panic(err)
} }
// listen for incoming packets // listen for incoming packets
go receivePackets(node.netInfo.Conn, node.packetPipe) go protocol.ReceivePackets(node.netInfo.Conn, node.packetPipe)
// send info on file/directory // send info about file/directory
if dir != nil { go protocol.SendTransferOffer(node.netInfo.Conn, file, dir, node.netInfo.EncryptionKey)
go sendDirectoryPacket(node.netInfo.Conn, dir, node.netInfo.EncryptionKey)
} else {
go sendFilePacket(node.netInfo.Conn, file, node.netInfo.EncryptionKey)
}
// mainloop // mainloop
for { for {
@ -238,6 +242,7 @@ func (node *Node) Start() {
node.state.Stopped = true node.state.Stopped = true
} }
// if encryption key is set - decrypt packet on the spot
if node.netInfo.EncryptionKey != nil { if node.netInfo.EncryptionKey != nil {
err = incomingPacket.DecryptBody(node.netInfo.EncryptionKey) err = incomingPacket.DecryptBody(node.netInfo.EncryptionKey)
if err != nil { if err != nil {
@ -281,10 +286,10 @@ func (node *Node) Start() {
} }
case false: case false:
// sending a single file // sending a piece of a single file
err = sendPiece(file, node.netInfo.Conn, node.netInfo.EncryptionKey) err = protocol.SendPiece(file, node.netInfo.Conn, node.netInfo.EncryptionKey)
switch err { switch err {
case ErrorSentAll: case protocol.ErrorSentAll:
// the file has been sent fully // the file has been sent fully
fileIDBuff := new(bytes.Buffer) fileIDBuff := new(bytes.Buffer)
err = binary.Write(fileIDBuff, binary.BigEndian, file.ID) err = binary.Write(fileIDBuff, binary.BigEndian, file.ID)
@ -312,9 +317,12 @@ func (node *Node) Start() {
Header: protocol.HeaderDone, Header: protocol.HeaderDone,
}) })
fmt.Printf("Transfer ended successfully\n")
node.state.Stopped = true node.state.Stopped = true
case nil: case nil:
break
default: default:
node.state.Stopped = true node.state.Stopped = true
@ -340,11 +348,15 @@ func (node *Node) Start() {
} }
// listen for incoming packets // listen for incoming packets
go receivePackets(node.netInfo.Conn, node.packetPipe) go protocol.ReceivePackets(node.netInfo.Conn, node.packetPipe)
// mainloop // mainloop
for { for {
if node.state.Stopped { node.mutex.Lock()
stopped := node.state.Stopped
node.mutex.Unlock()
if stopped {
node.disconnect() node.disconnect()
break break
} }
@ -354,6 +366,8 @@ func (node *Node) Start() {
if !ok { if !ok {
break break
} }
// if encryption key is set - decrypt packet on the spot
if node.netInfo.EncryptionKey != nil { if node.netInfo.EncryptionKey != nil {
err = incomingPacket.DecryptBody(node.netInfo.EncryptionKey) err = incomingPacket.DecryptBody(node.netInfo.EncryptionKey)
if err != nil { if err != nil {
@ -364,51 +378,71 @@ func (node *Node) Start() {
// react based on a header of a received packet // react based on a header of a received packet
switch incomingPacket.Header { switch incomingPacket.Header {
case protocol.HeaderFile: case protocol.HeaderTransferOffer:
// process an information about a singe file. Accept or reject the transfer // accept of reject offer
go func() { go func() {
file, err := decodeFilePacket(incomingPacket) file, dir, err := protocol.DecodeTransferPacket(incomingPacket)
if err != nil { if err != nil {
panic(err) 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 var answer string
fmt.Printf("| Download ? [Y/n]: ") fmt.Printf("| Download ? [Y/n]: ")
fmt.Scanln(&answer) fmt.Scanln(&answer)
fmt.Printf("\n\n") fmt.Printf("\n\n")
responsePacketFileIDBuffer := new(bytes.Buffer)
binary.Write(responsePacketFileIDBuffer, binary.BigEndian, file.ID)
if strings.EqualFold(answer, "y") || answer == "" { if strings.EqualFold(answer, "y") || answer == "" {
// yes // yes
err = os.MkdirAll(node.transferInfo.Receiving.DownloadsPath, os.ModePerm) if file != nil {
if err != nil { // file
panic(err) 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 node.mutex.Lock()
_, err := os.Stat(fullFilePath) node.transferInfo.Receiving.AcceptedFiles = append(node.transferInfo.Receiving.AcceptedFiles, file)
if err == nil { node.mutex.Unlock()
// exists
// remove it
os.Remove(fullFilePath)
}
file.Path = fullFilePath } else if dir != nil {
file.Open() // 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 // send aceptance packet
responsePacketFileIDBuffer := new(bytes.Buffer)
binary.Write(responsePacketFileIDBuffer, binary.BigEndian, file.ID)
acceptancePacket := protocol.Packet{ acceptancePacket := protocol.Packet{
Header: protocol.HeaderAccept, Header: protocol.HeaderAccept,
Body: responsePacketFileIDBuffer.Bytes(), Body: responsePacketFileIDBuffer.Bytes(),
} }
if node.netInfo.EncryptionKey != nil { if node.netInfo.EncryptionKey != nil {
err = acceptancePacket.EncryptBody(node.netInfo.EncryptionKey) err = acceptancePacket.EncryptBody(node.netInfo.EncryptionKey)
if err != nil { if err != nil {
@ -432,6 +466,9 @@ func (node *Node) Start() {
} else { } else {
// no // no
responsePacketFileIDBuffer := new(bytes.Buffer)
binary.Write(responsePacketFileIDBuffer, binary.BigEndian, file.ID)
rejectionPacket := protocol.Packet{ rejectionPacket := protocol.Packet{
Header: protocol.HeaderReject, Header: protocol.HeaderReject,
Body: responsePacketFileIDBuffer.Bytes(), Body: responsePacketFileIDBuffer.Bytes(),
@ -449,10 +486,16 @@ func (node *Node) Start() {
panic(err) panic(err)
} }
node.mutex.Lock()
node.state.Stopped = true node.state.Stopped = true
node.mutex.Unlock()
} }
}() }()
case protocol.HeaderFile:
// process an information about a singe file
// (TODO)
case protocol.HeaderFileBytes: case protocol.HeaderFileBytes:
// check if this file has been accepted to receive // check if this file has been accepted to receive
fileIDReader := bytes.NewReader(incomingPacket.Body) fileIDReader := bytes.NewReader(incomingPacket.Body)
@ -537,11 +580,17 @@ func (node *Node) Start() {
node.netInfo.EncryptionKey = encrKey node.netInfo.EncryptionKey = encrKey
fmt.Printf("Got an encryption key: %s\n", encrKey)
case protocol.HeaderDone: case protocol.HeaderDone:
node.mutex.Lock()
node.state.Stopped = true node.state.Stopped = true
node.mutex.Unlock()
case protocol.HeaderDisconnecting: case protocol.HeaderDisconnecting:
node.mutex.Lock()
node.state.Stopped = true node.state.Stopped = true
node.mutex.Unlock()
fmt.Printf("%s disconnected\n", node.netInfo.Conn.RemoteAddr()) fmt.Printf("%s disconnected\n", node.netInfo.Conn.RemoteAddr())
} }

82
src/node/node_test.go

@ -1,51 +1,45 @@
package node package node
import (
"fmt"
"os"
"testing"
)
// Not complete // Not complete
func Test_Sendfile(t *testing.T) { // func Test_Sendfile(t *testing.T) {
rnodeOptions := NodeOptions{ // rnodeOptions := NodeOptions{
IsSending: false, // IsSending: false,
WorkingPort: 8888, // WorkingPort: 8888,
ServerSide: &ServerSideNodeOptions{ // ServerSide: &ServerSideNodeOptions{
ServingPath: "", // ServingPath: "",
Recursive: false, // Recursive: false,
}, // },
ClientSide: &ClientSideNodeOptions{ // ClientSide: &ClientSideNodeOptions{
ConnectionAddr: "localhost", // ConnectionAddr: "localhost",
DownloadsFolderPath: "../testfiles/testDownload/", // DownloadsFolderPath: "../testfiles/testDownload/",
}, // },
} // }
receivingNode, err := NewNode(&rnodeOptions) // receivingNode, err := NewNode(&rnodeOptions)
if err != nil { // if err != nil {
fmt.Printf("Error constructing a new node: %s\n", err) // fmt.Printf("Error constructing a new node: %s\n", err)
os.Exit(-1) // os.Exit(-1)
} // }
snodeOptions := NodeOptions{ // snodeOptions := NodeOptions{
IsSending: true, // IsSending: true,
WorkingPort: 8888, // WorkingPort: 8888,
ServerSide: &ServerSideNodeOptions{ // ServerSide: &ServerSideNodeOptions{
ServingPath: "../testfiles/testfile.txt", // ServingPath: "../testfiles/testfile.txt",
Recursive: false, // Recursive: false,
}, // },
ClientSide: &ClientSideNodeOptions{ // ClientSide: &ClientSideNodeOptions{
ConnectionAddr: "", // ConnectionAddr: "",
DownloadsFolderPath: "", // DownloadsFolderPath: "",
}, // },
} // }
sendingNode, err := NewNode(&snodeOptions) // sendingNode, err := NewNode(&snodeOptions)
if err != nil { // if err != nil {
fmt.Printf("Error constructing a new node: %s\n", err) // fmt.Printf("Error constructing a new node: %s\n", err)
os.Exit(-1) // os.Exit(-1)
} // }
go receivingNode.Start() // go receivingNode.Start()
sendingNode.Start() // sendingNode.Start()
} // }

122
src/node/packets.go

@ -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
}

209
src/node/transfer.go

@ -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
}

10
src/protocol/constants.go

@ -1,4 +1,4 @@
// This file contains global constants of the protocol // global constants of the protocol
package protocol package protocol
// MAXPACKETSIZE. // MAXPACKETSIZE.
@ -8,5 +8,11 @@ const MAXPACKETSIZE uint = 131072 // 128 KiB
// HEADERDELIMETER. // HEADERDELIMETER.
// Character that delimits header of the packet from the body of the packet. // 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 = "~" const HEADERDELIMETER string = "~"
// FILECODE.
const FILECODE string = "f"
// DIRCODE.
const DIRCODE string = "d"

16
src/protocol/headers.go

@ -6,7 +6,7 @@ type Header string
// Headers // Headers
//// In the following examples "~" is the HEADERDELIMETER //// 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. // ENCRKEY.
// The FIRST header to be sent. Sent immediately after the connection has been established // The FIRST header to be sent. Sent immediately after the connection has been established
@ -52,6 +52,18 @@ const HeaderReady Header = "READY"
// ie: BYE!~ // ie: BYE!~
const HeaderDisconnecting Header = "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. // FILE.
// Sent by sender, indicating that the file is going to be sent. // Sent by sender, indicating that the file is going to be sent.
// The body structure must follow such structure: // The body structure must follow such structure:
@ -73,5 +85,5 @@ const HeaderEndfile Header = "ENDFILE"
// DIRECTORY // DIRECTORY
// Sent by sender, indicates that a directory with current information // Sent by sender, indicates that a directory with current information
// is going to be sent. The structure of the body must follow the example: // 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" const HeaderDirectory Header = "DIRECTORY"

66
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: // Packet structure during transportation:
// (size of the whole packet in binary)(packet header)(header delimeter (~))(packet contents) // (size of the whole packet in binary (big endian uint64))(packet header)(header delimeter (~))(packet contents)
package protocol package protocol
@ -9,7 +9,6 @@ import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"net"
"strings" "strings"
"github.com/Unbewohnte/ftu/encryption" "github.com/Unbewohnte/ftu/encryption"
@ -21,6 +20,8 @@ type Packet struct {
Body []byte 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. // Returns a size of the given packet as if it would be sent and presented in bytes.
// ie: FILE~bytes_here // ie: FILE~bytes_here
func (packet *Packet) Size() uint64 { func (packet *Packet) Size() uint64 {
@ -32,14 +33,12 @@ func (packet *Packet) Size() uint64 {
return uint64(packetBytes.Len()) return uint64(packetBytes.Len())
} }
var ErrorNotPacketBytes error = fmt.Errorf("not packet bytes")
// Converts packet bytes into Packet struct // Converts packet bytes into Packet struct
func BytesToPacket(packetbytes []byte) (*Packet, error) { func BytesToPacket(packetbytes []byte) (*Packet, error) {
// check if there`s a header delimiter present // check if there`s a header delimiter present
pString := string(packetbytes) pString := string(packetbytes)
if !strings.Contains(pString, HEADERDELIMETER) { if !strings.Contains(pString, HEADERDELIMETER) {
return nil, ErrorNotPacketBytes return nil, ErrorInvalidPacket
} }
var header Header var header Header
@ -59,7 +58,7 @@ func BytesToPacket(packetbytes []byte) (*Packet, error) {
}, nil }, 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 // Converts given packet struct into ready-to-transfer bytes, constructed by following the protocol
func (packet *Packet) ToBytes() ([]byte, error) { func (packet *Packet) ToBytes() ([]byte, error) {
@ -86,22 +85,6 @@ func (packet *Packet) ToBytes() ([]byte, error) {
return packetBuffer.Bytes(), nil 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 // Encrypts packet`s BODY with AES encryption
func (packet *Packet) EncryptBody(key []byte) error { func (packet *Packet) EncryptBody(key []byte) error {
// encrypting packet`s body // encrypting packet`s body
@ -129,38 +112,3 @@ func (packet *Packet) DecryptBody(key []byte) error {
return nil 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
}

84
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
}

154
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
}

69
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
}
}

189
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
}

11
src/testfiles/testDownload/testfile.txt

@ -1,11 +0,0 @@
727 WYSI Airman Badeu square
doable
FCeeeeeeeeeeeeeeeeeeeeeeeee
testfile it is
Loading…
Cancel
Save