From 65ca89a407fb69e7460b73f9f2d656c3bbddf797 Mon Sep 17 00:00:00 2001 From: Unbewohnte Date: Tue, 8 Jun 2021 14:36:14 +0300 Subject: [PATCH] Added checksum check --- .gitignore | 1 + README.md | 20 +-- checksum/checksum.go | 80 +++++++++ client/client.go | 198 ----------------------- main.go | 48 +++--- protocol/protocol.go | 15 +- receiver/file.go | 10 ++ receiver/receiver.go | 233 +++++++++++++++++++++++++++ {server => sender}/file.go | 17 +- server/server.go => sender/sender.go | 57 +++---- 10 files changed, 409 insertions(+), 270 deletions(-) create mode 100644 checksum/checksum.go delete mode 100644 client/client.go create mode 100644 receiver/file.go create mode 100644 receiver/receiver.go rename {server => sender}/file.go (65%) rename server/server.go => sender/sender.go (79%) diff --git a/.gitignore b/.gitignore index 95c3f50..b24468a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ FTU FTU.exe release/ +downloads/ \ No newline at end of file diff --git a/README.md b/README.md index 224e888..c6246eb 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Learning ## How does this work ? In order to transfer one file on one computer to another - they need to establish a connection. -In order to establish a connection - there needs to be a 1) server (the owner of the file), waiting for connections, and a 2) client, who will try to connect to a server. If the requirements are met - a client will connect to a server and the packet exchange will begin. +In order to establish a connection - there needs to be a 1) sender (server) (the owner of the file), waiting for connections, and a 2) receiver (client), who will try to connect to a sender (server). If the requirements are met - a client will connect to a server and the packet exchange will begin. The server and the client needs to communicate with packets according to certain rules, given by a [protocol](https://github.com/Unbewohnte/FTU/tree/main/protocol). @@ -32,18 +32,18 @@ Thus, with a connection and a way of communication, the server will send a filei ### Flags -- `-server` (bool) - if true - creates a server (also need to provide a `-sharefile` flag in that case), if false - creates a client -- `-port` (int) - specifies a port; if `-server` == true - listens on that port, else - connects to given port -- `addr` (string) - specifies an address to connect to (used when `-server=false`) -- `-sharefile` (string) - specifies path to a file you want to share (used in pair with `-server=true`), if given a valid path - a server will offer to share this file to a client +- `-sending` (bool) - if true - creates a server (sender) (also need to provide a `-sharefile` flag in that case), if false - creates a client (receiver) +- `-port` (int) - specifies a port; if `-sending` == true - listens on that port, else - connects to given port +- `addr` (string) - specifies an address to connect to (used when `-sending=false`) +- `-sharefile` (string) - specifies path to a file you want to share (used in pair with `-sending=true`), if given a valid path - a server will offer to share this file to a client - `-downloadto` (string) - specifies path to a folder where the client wants to store downloaded file ### Examples -- `./FTU -server=true -sharefile="/home/some_path_here/FILETOSHARE.zip"` - creates a server that will share `FILETOSHARE.zip` on port `8080` -- `./FTU -server=true -sharefile="/home/some_path_here/FILETOSHARE.zip" - port=727` - same as before, but on port `727` -- `./FTU -server=false -downloadto="/home/some_path_here/Downloads/" -addr="localhost"` - creates a client that will try to connect to `localhost` on port `8080` and if successful - downloads a file to given path -- `./FTU -server=false -downloadto="/home/some_path_here/Downloads/" -addr=145.125.53.212 -port=8888` - same as before, but will try to connect to `145.125.53.212` on port `8888` +- `./FTU -sending=true -sharefile="/home/some_path_here/FILETOSHARE.zip"` - creates a server that will share `FILETOSHARE.zip` on port `8080` +- `./FTU -sending=true -sharefile="/home/some_path_here/FILETOSHARE.zip" - port=727` - same as before, but on port `727` +- `./FTU -sending=false -downloadto="/home/some_path_here/Downloads/" -addr="localhost"` - creates a client that will try to connect to `localhost` on port `8080` and if successful - downloads a file to given path +- `./FTU -sending=false -downloadto="/home/some_path_here/Downloads/" -addr=145.125.53.212 -port=8888` - same as before, but will try to connect to `145.125.53.212` on port `8888` --- @@ -54,7 +54,7 @@ Thus, with a connection and a way of communication, the server will send a filei - If `MAXFILEDATASIZE` is bigger than appr. 1024 - the packets on the other end will not be unmarshalled due to error ??; FIXED - [ ] - Lack of proper error-handling; somewhat FIXED - [x] - Lack of information about the process of transferring (ETA, lost packets, etc.); FIXED - [ ] -- No way to verify if the transferred file is not corrupted; FIXED - [ ] +- No way to verify if the transferred file is not corrupted; FIXED via checksum- [x] - No encryption; FIXED - [ ] ## Good points diff --git a/checksum/checksum.go b/checksum/checksum.go new file mode 100644 index 0000000..a5d1f39 --- /dev/null +++ b/checksum/checksum.go @@ -0,0 +1,80 @@ +package checksum + +import ( + "crypto/sha256" + "fmt" + "io" + "os" +) + +type CheckSum [32]byte + +// returns a checksum of given file. NOTE, that it creates checksum +// not of a full file (from all file bytes), but from separate byte blocks. +// This is done as an optimisation because the file can be very large in size. +// The general idea: +// BOF... CHUNK -> STEP -> CHUNK... EOF +// checksum := sha256.Sum256(ALLCHUNKS) +// GetPartialCheckSum is default method used to get a file checksum by sender and receiver +func GetPartialCheckSum(file *os.File) (CheckSum, error) { + // "capturing" CHUNKSIZE bytes and then skipping STEP bytes before the next chunk until the last one + const CHUNKS uint = 100 + const CHUNKSIZE uint = 100 + const STEP uint = 250 + + fileStats, err := file.Stat() + if err != nil { + return [32]byte{}, fmt.Errorf("could not get the stats: %s", err) + } + + fileSize := fileStats.Size() + + if fileSize < int64(CHUNKS*CHUNKSIZE+STEP*(CHUNKS-1)) { + // file is too small, doing full checksum + + checksum, err := getFullCheckSum(file) + if err != nil { + return [32]byte{}, err + } + return checksum, nil + } + + var capturedChunks string + var read uint64 = 0 + for i := 0; uint(i) < CHUNKS; i++ { + buffer := make([]byte, CHUNKSIZE) + r, _ := file.ReadAt(buffer, int64(read)) + + capturedChunks += string(buffer) + + read += uint64(r) + read += uint64(STEP) + } + + checksum := sha256.Sum256([]byte(capturedChunks)) + return checksum, nil +} + +// returns a sha256 checksum of given file +func getFullCheckSum(file *os.File) (CheckSum, error) { + filebytes, err := io.ReadAll(file) + if err != nil { + return [32]byte{}, fmt.Errorf("could not read the file: %s", err) + } + checksum := sha256.Sum256(filebytes) + + return checksum, nil +} + +// simply compares 2 given checksums. If they are equal - returns true +func AreEqual(checksum1, checksum2 CheckSum) bool { + var i int = 0 + for _, checksum1Byte := range checksum1 { + checksum2Byte := checksum2[i] + if checksum1Byte != checksum2Byte { + return false + } + i++ + } + return true +} diff --git a/client/client.go b/client/client.go deleted file mode 100644 index 18054ef..0000000 --- a/client/client.go +++ /dev/null @@ -1,198 +0,0 @@ -package client - -import ( - "fmt" - "net" - "os" - "path/filepath" - "strings" - - "github.com/Unbewohnte/FTU/protocol" -) - -// Representation of a tcp client -type Client struct { - DownloadsFolder string - Connection net.Conn - IncomingPackets chan protocol.Packet - Stopped bool - ReadyToReceive bool - PacketCounter uint64 -} - -// Creates a new client with default fields -func NewClient(downloadsFolder string) *Client { - os.MkdirAll(downloadsFolder, os.ModePerm) - - info, err := os.Stat(downloadsFolder) - if err != nil { - panic(err) - } - if !info.IsDir() { - panic("Downloads folder is not a directory") - } - - incomingPacketsChan := make(chan protocol.Packet, 5) - - var PacketCounter uint64 = 0 - fmt.Println("Created a new client") - return &Client{ - DownloadsFolder: downloadsFolder, - Connection: nil, - IncomingPackets: incomingPacketsChan, - Stopped: false, - ReadyToReceive: false, - PacketCounter: PacketCounter, - } -} - -// Closes the connection -func (c *Client) Disconnect() { - c.Connection.Close() -} - -// Closes the connection, warns the server and exits the mainloop -func (c *Client) Stop() { - disconnectionPacket := protocol.Packet{ - Header: protocol.HeaderDisconnecting, - } - protocol.SendPacket(c.Connection, disconnectionPacket) - c.Stopped = true - c.Disconnect() -} - -// Connects to a given address over tcp. Sets a connection to a client -func (c *Client) Connect(addr string) error { - fmt.Printf("Trying to connect to %s...\n", addr) - connection, err := net.Dial("tcp", addr) - if err != nil { - return fmt.Errorf("could not connect to %s: %s", addr, err) - } - c.Connection = connection - fmt.Println("Connected to ", c.Connection.RemoteAddr()) - - return nil -} - -// Handles the fileinfo packet. The choice of acceptance is given to the user -func (c *Client) HandleFileOffer(fileinfoPacket protocol.Packet) error { - - // inform the user about the file - fmt.Printf("Incoming fileinfo packet:\nFilename: %s\nFilesize: %.3fMB\nAccept ? [Y/N]: ", - fileinfoPacket.Filename, float32(fileinfoPacket.Filesize)/1024/1024) - - // get and process the input - var input string - fmt.Scanln(&input) - input = strings.TrimSpace(input) - input = strings.ToLower(input) - - // reject the file - if input != "y" { - rejectionPacket := protocol.Packet{ - Header: protocol.HeaderReject, - } - err := protocol.SendPacket(c.Connection, rejectionPacket) - if err != nil { - return fmt.Errorf("could not send a rejection packet: %s", err) - } - - c.ReadyToReceive = false - - return nil - } - - // accept the file - acceptancePacket := protocol.Packet{ - Header: protocol.HeaderAccept, - } - err := protocol.SendPacket(c.Connection, acceptancePacket) - if err != nil { - return fmt.Errorf("could not send an acceptance packet: %s", err) - } - - // can and ready to receive file packets - c.ReadyToReceive = true - - return nil -} - -// Handles the download by writing incoming bytes into the file -func (c *Client) WritePieceOfFile(filePacket protocol.Packet) error { - c.ReadyToReceive = false - - // open|create a file with the same name as the filepacket`s file name - file, err := os.OpenFile(filepath.Join(c.DownloadsFolder, filePacket.Filename), os.O_CREATE|os.O_APPEND|os.O_WRONLY, os.ModePerm) - if err != nil { - return err - } - // just write the filedata - file.Write(filePacket.FileData) - file.Close() - c.PacketCounter++ - - c.ReadyToReceive = true - - return nil -} - -// Listens in an endless loop; reads incoming packages and puts them into channel -func (c *Client) ReceivePackets() { - for { - incomingPacket, err := protocol.ReadFromConn(c.Connection) - if err != nil { - // in current implementation there is no way to receive a working file even if only one packet is missing - fmt.Printf("Error reading a packet: %s\nExiting...", err) - os.Exit(-1) - } - c.IncomingPackets <- incomingPacket - } -} - -// The "head" of the client. Similarly as in server`s logic "glues" everything together. -// Current structure allows the client to receive any type of packet -// in any order and react correspondingly -func (c *Client) MainLoop() { - go c.ReceivePackets() - - for { - if c.Stopped { - // exit the mainloop - break - } - // 1) send -> 2) handle received if necessary - - // send a packet telling server to send another piece of file - if c.ReadyToReceive { - readyPacket := protocol.Packet{ - Header: protocol.HeaderReady, - } - protocol.SendPacket(c.Connection, readyPacket) - c.ReadyToReceive = false - } - - // no incoming packets ? Skipping the packet handling part - if len(c.IncomingPackets) == 0 { - continue - } - - // take the packet and handle depending on the header - incomingPacket := <-c.IncomingPackets - - // handling each packet header differently - switch incomingPacket.Header { - - case protocol.HeaderFileInfo: - go c.HandleFileOffer(incomingPacket) - - case protocol.HeaderFileData: - go c.WritePieceOfFile(incomingPacket) - - case protocol.HeaderDisconnecting: - // the server is ded, no need to stay alive as well - fmt.Println("Done. Got ", c.PacketCounter, " packets in total") - c.Stopped = true - c.Stop() - } - } -} diff --git a/main.go b/main.go index 4a046dc..5265f7b 100644 --- a/main.go +++ b/main.go @@ -6,28 +6,28 @@ import ( "os" "strings" - "github.com/Unbewohnte/FTU/client" - "github.com/Unbewohnte/FTU/server" + "github.com/Unbewohnte/FTU/receiver" + "github.com/Unbewohnte/FTU/sender" ) // flags -var PORT *int = flag.Int("port", 8080, "Specifies a port for a server") -var SERVERADDR *string = flag.String("addr", "", "Specifies an IP for connection") -var ISSERVER *bool = flag.Bool("server", false, "Server or a client") -var DOWNLOADSFOLDER *string = flag.String("downloadto", "", "Specifies where the client will store downloaded file") -var SHAREDFILE *string = flag.String("sharefile", "", "Specifies what file server will serve") +var PORT *int = flag.Int("port", 8080, "Specifies a port for a sender") +var SENDERADDR *string = flag.String("addr", "", "Specifies an IP for connection") +var SENDING *bool = flag.Bool("sending", false, "Send or receive") +var DOWNLOADSFOLDER *string = flag.String("downloadto", "", "Specifies where the receiver will store downloaded file") +var SHAREDFILE *string = flag.String("sharefile", "", "Specifies what file sender will send") // helpMessage var HELPMSG string = ` -"-port", default: 8080, Specifies a port for a server +"-port", default: 8080, Specifies a port for a sender "-addr", default: "", Specifies an IP for connection -"-server", default: false, Share file or connect and receive one ? -"-downloadto", default: "", Specifies where the client will store downloaded file -"-sharefile", default: "", Specifies what file server will share` +"-sending", default: false, Send or receive +"-downloadto", default: "", Specifies where the receiver will store downloaded file +"-sharefile", default: "", Specifies what file sender will send` // Input-validation func checkFlags() { - if *ISSERVER { + if *SENDING { if strings.TrimSpace(*SHAREDFILE) == "" { fmt.Println("No file specified !\n", HELPMSG) os.Exit(1) @@ -36,8 +36,8 @@ func checkFlags() { fmt.Println("Invalid port !\n", HELPMSG) os.Exit(1) } - } else if !*ISSERVER { - if strings.TrimSpace(*SERVERADDR) == "" { + } else if !*SENDING { + if strings.TrimSpace(*SENDERADDR) == "" { fmt.Println("Invalid IP address !\n", HELPMSG) os.Exit(1) } @@ -55,18 +55,18 @@ func init() { } func main() { - if *ISSERVER { - // 1) create server -> 2) wait for a client ->| - // 3) send handshake packet -> 4) if accepted - upload file - server := server.NewServer(*PORT, *SHAREDFILE) - server.WaitForConnection() - server.MainLoop() + if *SENDING { + // 1) create sender -> 2) wait for a connection ->| + // 3) send fileinfo packet -> 4) if accepted - upload file + sender := sender.NewSender(*PORT, *SHAREDFILE) + sender.WaitForConnection() + sender.MainLoop() } else { - // 1) create client -> 2) try to connect to a server -> 3) wait for a handshake ->| + // 1) create receiver -> 2) try to connect to a sender -> 3) wait for a fileinfo packet ->| // 4) accept or refuse -> 5) download|don`t_download file - client := client.NewClient(*DOWNLOADSFOLDER) - client.Connect(fmt.Sprintf("%s:%d", *SERVERADDR, *PORT)) - client.MainLoop() + receiver := receiver.NewReceiver(*DOWNLOADSFOLDER) + receiver.Connect(fmt.Sprintf("%s:%d", *SENDERADDR, *PORT)) + receiver.MainLoop() } } diff --git a/protocol/protocol.go b/protocol/protocol.go index 0d59c62..462569c 100644 --- a/protocol/protocol.go +++ b/protocol/protocol.go @@ -6,6 +6,8 @@ import ( "net" "strconv" "strings" + + "github.com/Unbewohnte/FTU/checksum" ) // a package that describes how server and client should communicate @@ -19,18 +21,19 @@ type Header string const HeaderFileData Header = "FILEDATA" const HeaderFileInfo Header = "FILEINFO" -const HeaderReject Header = "FILE_REJECT" -const HeaderAccept Header = "FILE_ACCEPT" +const HeaderReject Header = "FILEREJECT" +const HeaderAccept Header = "FILEACCEPT" const HeaderReady Header = "READY" const HeaderDisconnecting Header = "BYE!" // Packet structure. // A Packet without a header is an invalid packet type Packet struct { - Header Header `json:"Header"` - Filename string `json:"Filename"` - Filesize uint64 `json:"Filesize"` - FileData []byte `json:"Filedata"` + Header Header `json:"Header"` + Filename string `json:"Filename"` + Filesize uint64 `json:"Filesize"` + FileCheckSum checksum.CheckSum `json:"CheckSum"` + FileData []byte `json:"Filedata"` } // converts valid packet bytes into `Packet` struct diff --git a/receiver/file.go b/receiver/file.go new file mode 100644 index 0000000..825b03a --- /dev/null +++ b/receiver/file.go @@ -0,0 +1,10 @@ +package receiver + +import "github.com/Unbewohnte/FTU/checksum" + +// Receiver`s file struct. Used internally by receiver +type File struct { + Filename string + Filesize uint64 + CheckSum checksum.CheckSum +} diff --git a/receiver/receiver.go b/receiver/receiver.go new file mode 100644 index 0000000..973b23e --- /dev/null +++ b/receiver/receiver.go @@ -0,0 +1,233 @@ +package receiver + +import ( + "fmt" + "net" + "os" + "path/filepath" + "strings" + + "github.com/Unbewohnte/FTU/checksum" + "github.com/Unbewohnte/FTU/protocol" +) + +// Representation of a receiver +type Receiver struct { + DownloadsFolder string + Connection net.Conn + IncomingPackets chan protocol.Packet + FileToDownload *File + Stopped bool + ReadyToReceive bool + PacketCounter uint64 +} + +// Creates a new client with default fields +func NewReceiver(downloadsFolder string) *Receiver { + os.MkdirAll(downloadsFolder, os.ModePerm) + + downloadsFolderInfo, err := os.Stat(downloadsFolder) + if err != nil { + panic(err) + } + if !downloadsFolderInfo.IsDir() { + panic("Downloads folder is not a directory") + } + + incomingPacketsChan := make(chan protocol.Packet, 5) + + var PacketCounter uint64 = 0 + fmt.Println("Created a new client") + return &Receiver{ + DownloadsFolder: downloadsFolder, + Connection: nil, + IncomingPackets: incomingPacketsChan, + Stopped: false, + ReadyToReceive: false, + PacketCounter: PacketCounter, + } +} + +// Closes the connection +func (r *Receiver) Disconnect() { + r.Connection.Close() +} + +// Closes the connection, warns the sender and exits the mainloop +func (r *Receiver) Stop() { + disconnectionPacket := protocol.Packet{ + Header: protocol.HeaderDisconnecting, + } + protocol.SendPacket(r.Connection, disconnectionPacket) + r.Stopped = true + r.Disconnect() +} + +// Connects to a given address over tcp. Sets a connection to a corresponding field in receiver +func (r *Receiver) Connect(addr string) error { + fmt.Printf("Trying to connect to %s...\n", addr) + connection, err := net.Dial("tcp", addr) + if err != nil { + return fmt.Errorf("could not connect to %s: %s", addr, err) + } + r.Connection = connection + fmt.Println("Connected to ", r.Connection.RemoteAddr()) + + return nil +} + +// Handles the fileinfo packet. The choice of acceptance is given to the user +func (r *Receiver) HandleFileOffer(fileinfoPacket protocol.Packet) error { + + // inform the user about the file + // fmt.Printf("Incoming fileinfo packet:\nFilename: %s\nFilesize: %.3fMB\nCheckSum: %s\nAccept ? [Y/N]: ", + // fileinfoPacket.Filename, float32(fileinfoPacket.Filesize)/1024/1024, fileinfoPacket.FileCheckSum) + + fmt.Printf(` + Incoming fileinfo packet: + | Filename: %s + | Filesize: %.3fMB + | Checksum: %x + | + | Download ? [Y/N]: `, + fileinfoPacket.Filename, float32(fileinfoPacket.Filesize)/1024/1024, fileinfoPacket.FileCheckSum, + ) + + // get and process the input + var input string + fmt.Scanln(&input) + input = strings.TrimSpace(input) + input = strings.ToLower(input) + + // reject the file + if input != "y" { + rejectionPacket := protocol.Packet{ + Header: protocol.HeaderReject, + } + err := protocol.SendPacket(r.Connection, rejectionPacket) + if err != nil { + return fmt.Errorf("could not send a rejection packet: %s", err) + } + + r.ReadyToReceive = false + + return nil + } + // accept the file + + r.FileToDownload = &File{ + Filename: fileinfoPacket.Filename, + Filesize: fileinfoPacket.Filesize, + CheckSum: fileinfoPacket.FileCheckSum, + } + + acceptancePacket := protocol.Packet{ + Header: protocol.HeaderAccept, + } + err := protocol.SendPacket(r.Connection, acceptancePacket) + if err != nil { + return fmt.Errorf("could not send an acceptance packet: %s", err) + } + + // can and ready to receive file packets + r.ReadyToReceive = true + + return nil +} + +// Handles the download by writing incoming bytes into the file +func (r *Receiver) WritePieceOfFile(filePacket protocol.Packet) error { + r.ReadyToReceive = false + + // open|create a file with the same name as the filepacket`s file name + file, err := os.OpenFile(filepath.Join(r.DownloadsFolder, filePacket.Filename), os.O_CREATE|os.O_APPEND|os.O_WRONLY, os.ModePerm) + if err != nil { + return err + } + // just write the filedata + file.Write(filePacket.FileData) + file.Close() + r.PacketCounter++ + + r.ReadyToReceive = true + + return nil +} + +// Listens in an endless loop; reads incoming packages and puts them into channel +func (r *Receiver) ReceivePackets() { + for { + incomingPacket, err := protocol.ReadFromConn(r.Connection) + if err != nil { + // in current implementation there is no way to receive a working file even if only one packet is missing + fmt.Printf("Error reading a packet: %s\nExiting...", err) + os.Exit(-1) + } + r.IncomingPackets <- incomingPacket + } +} + +// The "head" of the receiver. Similarly as in server`s logic "glues" everything together. +// Current structure allows the receiver to receive any type of packet +// in any order and react correspondingly +func (r *Receiver) MainLoop() { + go r.ReceivePackets() + + for { + if r.Stopped { + // exit the mainloop + break + } + + // send a packet telling sender to send another piece of file + if r.ReadyToReceive { + readyPacket := protocol.Packet{ + Header: protocol.HeaderReady, + } + protocol.SendPacket(r.Connection, readyPacket) + r.ReadyToReceive = false + } + + // no incoming packets ? Skipping the packet handling part + if len(r.IncomingPackets) == 0 { + continue + } + + // take the packet and handle depending on the header + incomingPacket := <-r.IncomingPackets + + // handling each packet header differently + switch incomingPacket.Header { + + case protocol.HeaderFileInfo: + go r.HandleFileOffer(incomingPacket) + + case protocol.HeaderFileData: + go r.WritePieceOfFile(incomingPacket) + + case protocol.HeaderDisconnecting: + // the sender has completed its mission, + // checking hashes and exiting + + fmt.Println("Got ", r.PacketCounter, " packets in total") + fmt.Println("Checking checksums...") + + file, err := os.Open(filepath.Join(r.DownloadsFolder, r.FileToDownload.Filename)) + if err != nil { + fmt.Printf("error while opening downloaded file for checking: %s\n", err) + os.Exit(-1) + } + realCheckSum, err := checksum.GetPartialCheckSum(file) + if err != nil { + fmt.Printf("error perfoming partial checksum: %s\n", err) + os.Exit(-1) + } + + fmt.Printf("\n%x ----- %x\n", r.FileToDownload.CheckSum, realCheckSum) + if !checksum.AreEqual(realCheckSum, r.FileToDownload.CheckSum) { + fmt.Println("Downloaded file is corrupted !") + } + r.Stop() + } + } +} diff --git a/server/file.go b/sender/file.go similarity index 65% rename from server/file.go rename to sender/file.go index 396ef13..93c1d5a 100644 --- a/server/file.go +++ b/sender/file.go @@ -1,11 +1,13 @@ -package server +package sender import ( "fmt" "os" + + "github.com/Unbewohnte/FTU/checksum" ) -// Struct that represents the served file. Used internally in the server +// Struct that represents the served file. Used internally in the sender type File struct { path string Filename string @@ -14,9 +16,10 @@ type File struct { LeftBytes uint64 SentPackets uint64 Handler *os.File + CheckSum checksum.CheckSum } -// Prepares a file for serving. Used for preparing info before sending a handshake +// Prepares a file for serving. Used for preparing info before sending a fileinfo packet by sender func getFile(path string) (*File, error) { info, err := os.Stat(path) if err != nil { @@ -24,8 +27,13 @@ func getFile(path string) (*File, error) { } handler, err := os.Open(path) if err != nil { - return nil, fmt.Errorf("couldn`t be able to open the file: %s", err) + return nil, fmt.Errorf("wasn`t able to open the file: %s", err) + } + checksum, err := checksum.GetPartialCheckSum(handler) + if err != nil { + return nil, fmt.Errorf("could not get a partial file checksum: %s", err) } + return &File{ path: path, Filename: info.Name(), @@ -33,5 +41,6 @@ func getFile(path string) (*File, error) { SentBytes: 0, LeftBytes: uint64(info.Size()), Handler: handler, + CheckSum: checksum, }, nil } diff --git a/server/server.go b/sender/sender.go similarity index 79% rename from server/server.go rename to sender/sender.go index d7c1a35..6e3876b 100644 --- a/server/server.go +++ b/sender/sender.go @@ -1,4 +1,4 @@ -package server +package sender import ( "fmt" @@ -37,8 +37,8 @@ func GetRemoteIP() (string, error) { return string(ip), nil } -// The main server struct -type Server struct { +// The main sender struct +type Sender struct { Port int FileToTransfer *File Listener net.Listener @@ -48,8 +48,8 @@ type Server struct { Stopped bool } -// Creates a new server with default fields -func NewServer(port int, filepath string) *Server { +// Creates a new sender with default fields +func NewSender(port int, filepath string) *Sender { fileToTransfer, err := getFile(filepath) if err != nil { panic(err) @@ -70,8 +70,8 @@ func NewServer(port int, filepath string) *Server { panic(err) } - fmt.Printf("Created a new server at %s:%d (remote)\n%s:%d (local)\n", remoteIP, port, localIP, port) - return &Server{ + fmt.Printf("Created a new sender at %s:%d (remote)\n%s:%d (local)\n", remoteIP, port, localIP, port) + return &Sender{ Port: port, FileToTransfer: fileToTransfer, Listener: listener, @@ -81,8 +81,8 @@ func NewServer(port int, filepath string) *Server { } } -// Closes the connection, warns about it the client and exits the mainloop -func (s *Server) Stop() { +// Closes the connection, warns about it the receiver and exits the mainloop +func (s *Sender) Stop() { disconnectionPacket := protocol.Packet{ Header: protocol.HeaderDisconnecting, } @@ -96,12 +96,12 @@ func (s *Server) Stop() { } // Closes current connection -func (s *Server) Disconnect() { +func (s *Sender) Disconnect() { s.Connection.Close() } // Accepts one connection -func (s *Server) WaitForConnection() { +func (s *Sender) WaitForConnection() { connection, err := s.Listener.Accept() if err != nil { fmt.Printf("Could not accept a connection: %s", err) @@ -112,16 +112,17 @@ func (s *Server) WaitForConnection() { } // Closes the listener. Used only when there is still no connection from `AcceptConnections` -func (s *Server) StopListening() { +func (s *Sender) StopListening() { s.Listener.Close() } // Sends a packet with all information about a file to current connection -func (s *Server) SendOffer() error { +func (s *Sender) SendOffer() error { err := protocol.SendPacket(s.Connection, protocol.Packet{ - Header: protocol.HeaderFileInfo, - Filename: s.FileToTransfer.Filename, - Filesize: s.FileToTransfer.Filesize, + Header: protocol.HeaderFileInfo, + Filename: s.FileToTransfer.Filename, + Filesize: s.FileToTransfer.Filesize, + FileCheckSum: s.FileToTransfer.CheckSum, }) if err != nil { return fmt.Errorf("could not send an information about the file: %s", err) @@ -130,8 +131,8 @@ func (s *Server) SendOffer() error { return nil } -// Sends one file packet to the client -func (s *Server) SendPiece() error { +// Sends one file packet to the receiver +func (s *Sender) SendPiece() error { // if no data to send - exit if s.FileToTransfer.LeftBytes == 0 { fmt.Printf("Done. Sent %d file packets\n", s.FileToTransfer.SentPackets) @@ -172,7 +173,7 @@ func (s *Server) SendPiece() error { } // Listens in an endless loop; reads incoming packages and puts them into channel -func (s *Server) ReceivePackets() { +func (s *Sender) ReceivePackets() { for { incomingPacket, err := protocol.ReadFromConn(s.Connection) if err != nil { @@ -184,14 +185,14 @@ func (s *Server) ReceivePackets() { } } -// The "head" of the server. "Glues" all things together. -// Current structure allows the server to receive any type of packet +// The "head" of the sender. "Glues" all things together. +// Current structure allows the sender to receive any type of packet // in any order and react correspondingly -func (s *Server) MainLoop() { +func (s *Sender) MainLoop() { go s.ReceivePackets() - // send an information about the shared file to the client + // send an information about the shared file to the receiver s.SendOffer() for { @@ -199,7 +200,6 @@ func (s *Server) MainLoop() { // exit the mainloop break } - // send, then process received packets // no incoming packets ? Skipping the packet handling part if len(s.IncomingPackets) == 0 { @@ -212,26 +212,27 @@ func (s *Server) MainLoop() { switch incomingPacket.Header { case protocol.HeaderAccept: - fmt.Printf("Client has accepted the transfer !\n") + fmt.Printf("The transfer has been accepted !\n") // allowed to send file packets s.CanTransfer = true case protocol.HeaderReject: - fmt.Println("Client has rejected the transfer") + fmt.Println("Transfer has been rejected") s.Stop() - // client is ready to receive the next file packet + // receiver is ready to receive the next file packet, sending... case protocol.HeaderReady: if !s.CanTransfer { break } err := s.SendPiece() if err != nil { - fmt.Printf("Could not send a piece of file: %s", err) + fmt.Printf("could not send a piece of file: %s", err) os.Exit(-1) } case protocol.HeaderDisconnecting: + // receiver is dropping the file transfer ? s.Stop() } }