diff --git a/README.md b/README.md index c6246eb..bd28450 100644 --- a/README.md +++ b/README.md @@ -21,9 +21,9 @@ In order to establish a connection - there needs to be a 1) sender (server) (the 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). -In my implementation there is only one basic packet template with fixed fields. The packets are divided into several groups by its headers, this way my basic packet`s template can be used in many ways, without need of creating a brand-new packet with a different kind of a template. +The packet has its header and body. They are divided into several groups of use by headers, this way we can specify what kind of data is stored inside packet`s body and react accordingly. -Thus, with a connection and a way of communication, the server will send a fileinfo packet to a client that describes a filename and its size. The client will have the choice of accepting or rejecting the packet. If rejected - the connection will be closed and the program will exit. If accepted - the file will be transferred via packets. +Thus, with a connection and a way of communication, the sender will send some packets with necessary information about the file to the receiver that describe a filename, its size and a checksum. The client (receiver) will have the choice of accepting or rejecting the packet. If rejected - the connection will be closed and the program will exit. If accepted - the file will be transferred via packets. --- @@ -42,23 +42,27 @@ Thus, with a connection and a way of communication, the server will send a filei - `./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="192.168.1.104"` - creates a client (receiver) that will try to connect to `192.168.1.104` (local device) 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` --- ## Known issues|problems|lack of features|reasons why it`s bad -- **VERY** slow; FIXED - [ ] -- **VERY** expensive on resources; FIXED - [ ] -- If `MAXFILEDATASIZE` is bigger than appr. 1024 - the packets on the other end will not be unmarshalled due to error ??; FIXED - [ ] +- **VERY** slow; somewhat FIXED - [x], now **a little** faster than before +- **VERY** expensive on resources; somewhat FIXED - [x], no more **json manipulations**, only **raw bytes**`s wizardry ! +- If `MAXFILEDATASIZE` is bigger than appr. 1024 - the packets on the other end will not be unmarshalled due to error ??; FIXED - [x], unnecessary, wrong, deprecated, **destroyed !!!** (But now present in the other form, unfortunately) - 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 via checksum- [x] - No encryption; FIXED - [ ] +- Messy and hard to follow code && file structure; partially FIXED (protocol is looking fairly good rn) - [ X ] +- Lack of downloads` management; FIXED - [ ] +- No way to stop the download/upload and resume it later or even during the next connection; FIXED - [ ] +- No tests; FIXED - [ ] ## Good points -- It... works ? +- It works. --- diff --git a/checksum/checksum.go b/checksum/checksum.go index a5d1f39..4329bc5 100644 --- a/checksum/checksum.go +++ b/checksum/checksum.go @@ -7,7 +7,9 @@ import ( "os" ) -type CheckSum [32]byte +const CHECKSUMLEN uint = 32 + +type CheckSum [CHECKSUMLEN]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. @@ -24,7 +26,7 @@ func GetPartialCheckSum(file *os.File) (CheckSum, error) { fileStats, err := file.Stat() if err != nil { - return [32]byte{}, fmt.Errorf("could not get the stats: %s", err) + return [CHECKSUMLEN]byte{}, fmt.Errorf("could not get the stats: %s", err) } fileSize := fileStats.Size() @@ -34,7 +36,7 @@ func GetPartialCheckSum(file *os.File) (CheckSum, error) { checksum, err := getFullCheckSum(file) if err != nil { - return [32]byte{}, err + return [CHECKSUMLEN]byte{}, err } return checksum, nil } @@ -55,18 +57,18 @@ func GetPartialCheckSum(file *os.File) (CheckSum, error) { return checksum, nil } -// returns a sha256 checksum of given file +// 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) + return [CHECKSUMLEN]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 +// 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 { @@ -78,3 +80,25 @@ func AreEqual(checksum1, checksum2 CheckSum) bool { } return true } + +// Tries to convert given bytes into CheckSum type +func BytesToChecksum(bytes []byte) (CheckSum, error) { + if uint(len(bytes)) > CHECKSUMLEN { + return CheckSum{}, fmt.Errorf("provided bytes` length is bigger than the checksum`s") + } + + var checksum [CHECKSUMLEN]byte + for index, b := range bytes { + checksum[index] = b + } + return CheckSum(checksum), nil +} + +// Converts given checksum into []byte +func ChecksumToBytes(checksum CheckSum) []byte { + var checksumBytes []byte + for _, b := range checksum { + checksumBytes = append(checksumBytes, b) + } + return checksumBytes +} diff --git a/protocol/constants.go b/protocol/constants.go new file mode 100644 index 0000000..76e357f --- /dev/null +++ b/protocol/constants.go @@ -0,0 +1,20 @@ +// This file contains global constants of the protocol +package protocol + +// MAXPACKETSIZE. +// How many bytes can contain one packet (header + body) at maximum +// (packets with size bigger than MAXPACKETSIZE are invalid and will not be sent) +const MAXPACKETSIZE uint = 1024 // the same problem as in the previous versions: if the packet is big enough - the conn.Read() +// will result in some sort of error where it does not read the intended amount of bytes (less, in fact), +// which is strange, but I guess that +// I just do something wrong in my code + +// PACKETSIZEDELIMETER. +// Character that delimits one and the other sides of the next incoming packet. +// ie: |packet_size_here|packet_here, where "|" is PACKETSIZEDELIMETER +const PACKETSIZEDELIMETER string = "|" + +// HEADERDELIMETER. +// Character that delimits header of the packet from the body of the packet. +// ie: FILEINFO~img.png +const HEADERDELIMETER string = "~" diff --git a/protocol/headers.go b/protocol/headers.go new file mode 100644 index 0000000..97244d9 --- /dev/null +++ b/protocol/headers.go @@ -0,0 +1,77 @@ +// This file describes various headers of the protocol and how to use them +package protocol + +type Header string + +// Headers + +//// In the following below examples "|" is PACKETSIZEDELIMETER and "~" is HEADERDELIMETER + +// FILENAME. +// This header is sent only by sender. The packet with this header +// must contain a name of the transported file in BODY. +// ie: |18|FILENAME~image.png +const HeaderFilename Header = "FILENAME" + +// FILESIZE. +// This header is sent only by sender. The packet with this header +// must contain a size of the transported file in its BODY. +// ie: |15|FILESIZE~512442 +const HeaderFileSize Header = "FILESIZE" + +// CHECKSUM. +// Just like before, this header must be sent in a packet only by sender, +// BODY must contain a checksum of the transported file. +// ie: |74|CHECKSUM~1673f585148148d0c105af0d55646d6cbbf37e33a7366d3b72d8c5caca13434a +const HeaderChecksum Header = "CHECKSUM" + +// DOYOACCEPT. +// Sent by sender after all the information about the transfered file has been sent. +// Receiving a packet with this header means that there will be no more additional information about the +// file and the sender is waiting for response (acceptance or rejection of the file). +// ie: |13|DOYOUACCEPT?~ +const HeaderAcceptance Header = "DOYOUACCEPT?" + +// FILEBYTES. +// Sent only by sender. The packet`s body must contain +// a portion of transported file`s bytes. +// ie: |70|FILEBYTES~fj2pgfjek;hjg02yg082qyuhg83hvuahjvlhsaoughuihgp9earhguhergh\n +const HeaderFileBytes Header = "FILEBYTES" + +// FILEREJECT. +// Sent only by receiver if the user has decided to not download the file. +// The BODY may or may not be empty (preferably empty, of course), in any way, it will not be +// used in any way. +// ie: |11|FILEREJECT~ +const HeaderReject Header = "FILEREJECT" + +// FILEACCEPT. +// The opposite of the previous FILEREJECT. Send by receiver when +// the user has agreed to download the file. +// ie: |11|FILEACCEPT~ +const HeaderAccept Header = "FILEACCEPT" + +// DONE. +// Sent by sender. Warns the receiver that the file transfer has been done and +// there is no more information to give. +// ie: |5|DONE~ +// Usually after the packet with this header has been sent, the receiver will send +// another packet back with header BYE!, telling that it`s going to disconnect +const HeaderDone Header = "DONE" + +// READY. +// Sent by receiver when it hass read and processed the last +// FILEBYTES packet. The sender does not allowed to "spam" FILEBYTES +// packets without the permission of receiver. +// ie: |7|READY!~ +const HeaderReady Header = "READY" + +// BYE!. +// Packet with this header can be sent both by receiver and sender. +// It`s used when the sender or the receiver are going to disconnect +// and will not be able to communicate. +// (Usually it`s when the error has happened, OR, in a good situation, after the DONE header +// has been sent by sender, warning receiver that there is no data to send) +// The BODY is better to be empty. +// ie: |5|BYE!~ +const HeaderDisconnecting Header = "BYE!" diff --git a/protocol/packet.go b/protocol/packet.go new file mode 100644 index 0000000..4d867c0 --- /dev/null +++ b/protocol/packet.go @@ -0,0 +1,127 @@ +// This file describes the general packet structure and provides methods to work with them before|after the transportation + +// Examples of packets, ready for transportation in pseudo-code: +// []byte(|34|FILEDATA~fe2[gkr3j930f]fwpglkrt[o]) +// []byte(|57|FILENAME~theBestFileNameEver_Existed_in'''theUniverse.txt) +// general structure: +// PACKETSIZEDELIMETER packetsize PACKETSIZEDELIMETER packet.Header HEADERDELIMETER packet.Body (without spaces between) +package protocol + +import ( + "bytes" + "fmt" + "net" + "strconv" +) + +// Internal representation of packet before|after the transportation +type Packet struct { + Header Header + Body []byte +} + +// Returns a size of the given packet as if it would be sent and presented in bytes. +// ie: FILESIZE~[49 49 56 55 56 53 50 49 54] +// DOES COUNT THE PACKETSIZEDELIMETER +func MeasurePacketSize(packet Packet) uint64 { + packetBytes := new(bytes.Buffer) + packetBytes.Write([]byte(packet.Header)) + packetBytes.Write([]byte(HEADERDELIMETER)) + packetBytes.Write(packet.Body) + + return uint64(packetBytes.Len()) +} + +// Converts packet bytes into Packet struct +func BytesToPacket(packetbytes []byte) Packet { + var header Header + var body []byte + + for counter, b := range packetbytes { + if string(b) == HEADERDELIMETER { + header = Header(packetbytes[0:counter]) + body = packetbytes[counter+1:] + break + } + } + return Packet{ + Header: header, + Body: body, + } +} + +// Sends given packet to connection, following all the protocol`s rules. +// ALL packets MUST be sent by this method +func SendPacket(connection net.Conn, packetToSend Packet) error { + packetSize := MeasurePacketSize(packetToSend) + + if packetSize > uint64(MAXPACKETSIZE) { + return fmt.Errorf("invalid packet: HEADER: %s BODY: %s: EXCEEDED MAX PACKETSIZE", packetToSend.Header, packetToSend.Body) + } + + // packetsize between delimeters (ie: |17|) + packetSizeBytes := []byte(strconv.Itoa(int(packetSize))) + + // creating a buffer and writing the whole packet into it + packet := new(bytes.Buffer) + + packet.Write([]byte(PACKETSIZEDELIMETER)) + packet.Write(packetSizeBytes) + packet.Write([]byte(PACKETSIZEDELIMETER)) + + packet.Write([]byte(packetToSend.Header)) + packet.Write([]byte(HEADERDELIMETER)) + packet.Write(packetToSend.Body) + + // write the result (ie: |17|FILENAME~file.png) + connection.Write(packet.Bytes()) + + // for debug purposes (ᗜˬᗜ) + // fmt.Printf("SENDING PACKET: %s%s%s%s%s%s\n", + // []byte(PACKETSIZEDELIMETER), packetSizeBytes, []byte(PACKETSIZEDELIMETER), + // []byte(packetToSend.Header), []byte(HEADERDELIMETER), packetToSend.Body) + return nil +} + +// Reads a packet from given connection. +// ASSUMING THAT THE PACKETS ARE SENT BY `SendPacket` function !!!! +func ReadFromConn(connection net.Conn) (Packet, error) { + var err error + var delimeterCounter int = 0 + var packetSizeStrBuffer string = "" + var packetSize int = 0 + + for { + buffer := make([]byte, 1) + connection.Read(buffer) + + if string(buffer) == PACKETSIZEDELIMETER { + delimeterCounter++ + + // the first delimeter has been found, skipping the rest of the loop + if delimeterCounter == 1 { + continue + } + } + + // the last delimeter, the next read will be the packet itself, so breaking + if delimeterCounter == 2 { + break + } + + packetSizeStrBuffer += string(buffer) + } + + packetSize, err = strconv.Atoi(packetSizeStrBuffer) + if err != nil { + return Packet{}, fmt.Errorf("could not convert packetsizeStr into int: %s", err) + } + + // have a packetsize, now reading the whole packet + packetBuff := make([]byte, packetSize) + connection.Read(packetBuff) + + packet := BytesToPacket(packetBuff) + + return packet, nil +} diff --git a/protocol/protocol.go b/protocol/protocol.go deleted file mode 100644 index 462569c..0000000 --- a/protocol/protocol.go +++ /dev/null @@ -1,167 +0,0 @@ -package protocol - -import ( - "encoding/json" - "fmt" - "net" - "strconv" - "strings" - - "github.com/Unbewohnte/FTU/checksum" -) - -// a package that describes how server and client should communicate - -const MAXPACKETSIZE int = 2048 // whole packet -const MAXFILEDATASIZE int = 512 // only `FileData` | MUST be less than `MAXPACKETSIZE` -const PACKETSIZEDELIMETER string = "|" - -// Headers -type Header string - -const HeaderFileData Header = "FILEDATA" -const HeaderFileInfo Header = "FILEINFO" -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"` - FileCheckSum checksum.CheckSum `json:"CheckSum"` - FileData []byte `json:"Filedata"` -} - -// converts valid packet bytes into `Packet` struct -func ReadPacketBytes(packetBytes []byte) (Packet, error) { - var packet Packet - err := json.Unmarshal(packetBytes, &packet) - if err != nil { - return Packet{}, fmt.Errorf("could not unmarshal packet bytes: %s", err) - } - return packet, nil -} - -// Converts `Packet` struct into []byte -func EncodePacket(packet Packet) ([]byte, error) { - packetBytes, err := json.Marshal(packet) - if err != nil { - return nil, fmt.Errorf("could not marshal packet bytes: %s", err) - } - return packetBytes, nil -} - -// Measures the packet length -func MeasurePacket(packet Packet) (uint64, error) { - packetBytes, err := EncodePacket(packet) - if err != nil { - return 0, fmt.Errorf("could not measure the packet: %s", err) - } - return uint64(len(packetBytes)), nil -} - -// Checks if given packet is valid, returns a boolean and an explanation message -func IsValidPacket(packet Packet) (bool, string) { - packetSize, err := MeasurePacket(packet) - if err != nil { - return false, "Measurement error" - } - if packetSize > uint64(MAXPACKETSIZE) { - return false, "Exceeded MAXPACKETSIZE" - } - if len(packet.FileData) > MAXFILEDATASIZE { - return false, "Exceeded MAXFILEDATASIZE" - } - - if strings.TrimSpace(string(packet.Header)) == "" { - return false, "Empty header" - } - return true, "" -} - -// Sends a given packet to connection using a special sending format -// ALL packets MUST be sent by this method -func SendPacket(connection net.Conn, packet Packet) error { - isvalid, msg := IsValidPacket(packet) - if !isvalid { - return fmt.Errorf("this packet is invalid !: %v; The error: %v", packet, msg) - } - - packetSize, err := MeasurePacket(packet) - if err != nil { - return err - } - - // write packetsize between delimeters (ie: |727|{"HEADER":"PING"...}) - connection.Write([]byte(fmt.Sprintf("%s%d%s", PACKETSIZEDELIMETER, packetSize, PACKETSIZEDELIMETER))) - - // write the packet itself - packetBytes, err := EncodePacket(packet) - if err != nil { - return fmt.Errorf("could not send a packet: %s", err) - } - connection.Write(packetBytes) - - return nil -} - -// Reads a packet from given connection. -// ASSUMING THAT THE PACKETS ARE SENT BY `SendPacket` function !!!! -func ReadFromConn(connection net.Conn) (Packet, error) { - var err error - var delimeterCounter int = 0 - var packetSizeStrBuffer string = "" - var packetSize int = 0 - - for { - // reading byte-by-byte - buffer := make([]byte, 1) - connection.Read(buffer) // no fixed time limit, so no need to check for an error - - // found a delimeter - if string(buffer) == PACKETSIZEDELIMETER { - delimeterCounter++ - - // the first delimeter has been found, skipping the rest of the loop - if delimeterCounter == 1 { - continue - } - } - - // found the first delimeter, skip was performed, now reading an actual packetsize - if delimeterCounter == 1 { - // adding a character of the packet size to the `string buffer`; ie: | <- read, reading now -> 1 23|PACKET_HERE - packetSizeStrBuffer += string(buffer) - - } else if delimeterCounter == 2 { - // found the last delimeter, thus already read the whole packetsize - // converting from string to int - packetSize, err = strconv.Atoi(packetSizeStrBuffer) - if err != nil { - return Packet{}, fmt.Errorf("could not convert packet size into integer: %s", err) - } - // packet size has been found, breaking from the loop - break - } - } - - // have a packetsize, now reading the whole packet - packetBuffer := make([]byte, packetSize) - connection.Read(packetBuffer) - - packet, err := ReadPacketBytes(packetBuffer) - if err != nil { - return Packet{}, err - } - - isvalid, msg := IsValidPacket(packet) - if isvalid { - return packet, nil - } - - return Packet{}, fmt.Errorf("received an invalid packet. Reason: %s", msg) -} diff --git a/receiver/receiver.go b/receiver/receiver.go index 973b23e..09aab96 100644 --- a/receiver/receiver.go +++ b/receiver/receiver.go @@ -5,6 +5,7 @@ import ( "net" "os" "path/filepath" + "strconv" "strings" "github.com/Unbewohnte/FTU/checksum" @@ -13,13 +14,13 @@ import ( // Representation of a receiver type Receiver struct { - DownloadsFolder string - Connection net.Conn - IncomingPackets chan protocol.Packet - FileToDownload *File - Stopped bool - ReadyToReceive bool - PacketCounter uint64 + DownloadsFolder string + Connection net.Conn + IncomingPackets chan protocol.Packet + FileToDownload *File + ReadyToReceive bool + Stopped bool + FileBytesPacketCounter uint64 } // Creates a new client with default fields @@ -34,17 +35,21 @@ func NewReceiver(downloadsFolder string) *Receiver { panic("Downloads folder is not a directory") } - incomingPacketsChan := make(chan protocol.Packet, 5) + incomingPacketsChan := make(chan protocol.Packet, 5000) var PacketCounter uint64 = 0 - fmt.Println("Created a new client") + fmt.Println("Created a new receiver") return &Receiver{ DownloadsFolder: downloadsFolder, Connection: nil, IncomingPackets: incomingPacketsChan, Stopped: false, ReadyToReceive: false, - PacketCounter: PacketCounter, + FileToDownload: &File{ + Filename: "", + Filesize: 0, + }, + FileBytesPacketCounter: PacketCounter, } } @@ -76,12 +81,12 @@ func (r *Receiver) Connect(addr string) error { return nil } -// Handles the fileinfo packet. The choice of acceptance is given to the user -func (r *Receiver) HandleFileOffer(fileinfoPacket protocol.Packet) error { +// Prints known information about the file that is about to be transported. +// Handles the input from the user after the sender sent "DOYOUACCEPT?" packet. +// The choice of acceptance is given to the user +func (r *Receiver) HandleFileOffer() 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: @@ -90,7 +95,7 @@ func (r *Receiver) HandleFileOffer(fileinfoPacket protocol.Packet) error { | Checksum: %x | | Download ? [Y/N]: `, - fileinfoPacket.Filename, float32(fileinfoPacket.Filesize)/1024/1024, fileinfoPacket.FileCheckSum, + r.FileToDownload.Filename, float32(r.FileToDownload.Filesize)/1024/1024, r.FileToDownload.CheckSum, ) // get and process the input @@ -109,18 +114,10 @@ func (r *Receiver) HandleFileOffer(fileinfoPacket protocol.Packet) error { 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, } @@ -129,27 +126,24 @@ func (r *Receiver) HandleFileOffer(fileinfoPacket protocol.Packet) error { 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 + if filePacket.Header != protocol.HeaderFileBytes { + return fmt.Errorf("packet with given header should not contain filebytes !: %v", filePacket) + } // 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) + file, err := os.OpenFile(filepath.Join(r.DownloadsFolder, r.FileToDownload.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) + // just write the bytes + file.Write(filePacket.Body) file.Close() - r.PacketCounter++ - - r.ReadyToReceive = true + r.FileBytesPacketCounter++ return nil } @@ -161,6 +155,7 @@ func (r *Receiver) ReceivePackets() { 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) + r.Stop() os.Exit(-1) } r.IncomingPackets <- incomingPacket @@ -173,18 +168,20 @@ func (r *Receiver) ReceivePackets() { func (r *Receiver) MainLoop() { go r.ReceivePackets() + // r.Stop() + 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 } @@ -193,23 +190,45 @@ func (r *Receiver) MainLoop() { 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.HeaderFilename: + r.FileToDownload.Filename = string(incomingPacket.Body) + + case protocol.HeaderFileSize: + filesize, err := strconv.Atoi(string(incomingPacket.Body)) + if err != nil { + fmt.Printf("could not convert a filesize: %s\n", err) + r.Stop() + } + r.FileToDownload.Filesize = uint64(filesize) + + case protocol.HeaderChecksum: + checksum, err := checksum.BytesToChecksum(incomingPacket.Body) + if err != nil { + fmt.Printf("could not get file`s checksum: %s\n", err) + r.Stop() + } + r.FileToDownload.CheckSum = checksum + + case protocol.HeaderDone: + if r.FileToDownload.Filename != "" && r.FileToDownload.Filesize != 0 && r.FileToDownload.CheckSum != [32]byte{} { + r.HandleFileOffer() + r.ReadyToReceive = true + } - case protocol.HeaderFileData: - go r.WritePieceOfFile(incomingPacket) + case protocol.HeaderFileBytes: + r.WritePieceOfFile(incomingPacket) + r.ReadyToReceive = true case protocol.HeaderDisconnecting: // the sender has completed its mission, // checking hashes and exiting - fmt.Println("Got ", r.PacketCounter, " packets in total") + fmt.Println("Got ", r.FileBytesPacketCounter, " file packets in total") fmt.Println("Checking checksums...") file, err := os.Open(filepath.Join(r.DownloadsFolder, r.FileToDownload.Filename)) diff --git a/sender/file.go b/sender/file.go index 93c1d5a..6b0943b 100644 --- a/sender/file.go +++ b/sender/file.go @@ -9,14 +9,13 @@ import ( // Struct that represents the served file. Used internally in the sender type File struct { - path string - Filename string - Filesize uint64 - SentBytes uint64 - LeftBytes uint64 - SentPackets uint64 - Handler *os.File - CheckSum checksum.CheckSum + path string + Filename string + Filesize uint64 + SentBytes uint64 + LeftBytes uint64 + Handler *os.File + CheckSum checksum.CheckSum } // Prepares a file for serving. Used for preparing info before sending a fileinfo packet by sender diff --git a/sender/sender.go b/sender/sender.go index 6e3876b..ab644f7 100644 --- a/sender/sender.go +++ b/sender/sender.go @@ -6,7 +6,9 @@ import ( "net" "net/http" "os" + "strconv" + "github.com/Unbewohnte/FTU/checksum" "github.com/Unbewohnte/FTU/protocol" ) @@ -39,13 +41,15 @@ func GetRemoteIP() (string, error) { // The main sender struct type Sender struct { - Port int - FileToTransfer *File - Listener net.Listener - Connection net.Conn - IncomingPackets chan protocol.Packet - CanTransfer bool - Stopped bool + Port int + FileToTransfer *File + Listener net.Listener + Connection net.Conn + IncomingPackets chan protocol.Packet + SentFileBytesPackets uint64 + TransferAllowed bool + ReceiverIsReady bool + Stopped bool } // Creates a new sender with default fields @@ -59,7 +63,7 @@ func NewSender(port int, filepath string) *Sender { if err != nil { panic(err) } - incomingPacketsChan := make(chan protocol.Packet, 5) + incomingPacketsChan := make(chan protocol.Packet, 5000) remoteIP, err := GetRemoteIP() if err != nil { @@ -70,14 +74,18 @@ func NewSender(port int, filepath string) *Sender { panic(err) } + var filepacketCounter uint64 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, - Connection: nil, - IncomingPackets: incomingPacketsChan, - Stopped: false, + Port: port, + FileToTransfer: fileToTransfer, + Listener: listener, + Connection: nil, + IncomingPackets: incomingPacketsChan, + SentFileBytesPackets: filepacketCounter, + TransferAllowed: false, + ReceiverIsReady: false, + Stopped: false, } } @@ -116,33 +124,71 @@ func (s *Sender) StopListening() { s.Listener.Close() } -// Sends a packet with all information about a file to current connection +// Sends multiple packets with all information about the file to receiver +// (filename, filesize, checksum) func (s *Sender) SendOffer() error { - err := protocol.SendPacket(s.Connection, protocol.Packet{ - Header: protocol.HeaderFileInfo, - Filename: s.FileToTransfer.Filename, - Filesize: s.FileToTransfer.Filesize, - FileCheckSum: s.FileToTransfer.CheckSum, - }) + // filename + filenamePacket := protocol.Packet{ + Header: protocol.HeaderFilename, + Body: []byte(s.FileToTransfer.Filename), + } + err := protocol.SendPacket(s.Connection, filenamePacket) + if err != nil { + return fmt.Errorf("could not send an information about the file: %s", err) + } + + // filesize + filesizePacket := protocol.Packet{ + Header: protocol.HeaderFileSize, + Body: []byte(strconv.Itoa(int(s.FileToTransfer.Filesize))), + } + + err = protocol.SendPacket(s.Connection, filesizePacket) + if err != nil { + return fmt.Errorf("could not send an information about the file: %s", err) + } + + // checksum + checksumPacket := protocol.Packet{ + Header: protocol.HeaderChecksum, + Body: checksum.ChecksumToBytes(s.FileToTransfer.CheckSum), + } + err = protocol.SendPacket(s.Connection, checksumPacket) if err != nil { return fmt.Errorf("could not send an information about the file: %s", err) } + // indicate that we`ve sent everything we needed to send + donePacket := protocol.Packet{ + Header: protocol.HeaderDone, + } + err = protocol.SendPacket(s.Connection, donePacket) + if err != nil { + return fmt.Errorf("could not send an information about the file: %s", err) + } return nil } -// Sends one file packet to the receiver +// Sends one packet that contains a piece of file 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) + fmt.Printf("Done. Sent %d file packets\n", s.SentFileBytesPackets) s.Stop() } - fileBytes := make([]byte, protocol.MAXFILEDATASIZE) + // empty body + fileBytesPacket := protocol.Packet{ + Header: protocol.HeaderFileBytes, + } + + // how many bytes we can send at maximum + maxFileBytes := protocol.MAXPACKETSIZE - uint(protocol.MeasurePacketSize(fileBytesPacket)) + + fileBytes := make([]byte, maxFileBytes) // if there is less data to send than the limit - create a buffer of needed size - if s.FileToTransfer.LeftBytes < uint64(protocol.MAXFILEDATASIZE) { - fileBytes = make([]byte, protocol.MAXFILEDATASIZE-(protocol.MAXFILEDATASIZE-int(s.FileToTransfer.LeftBytes))) + if s.FileToTransfer.LeftBytes < uint64(maxFileBytes) { + fileBytes = make([]byte, uint64(maxFileBytes)-(uint64(maxFileBytes)-s.FileToTransfer.LeftBytes)) } // reading bytes from the point where we left @@ -151,15 +197,10 @@ func (s *Sender) SendPiece() error { return fmt.Errorf("could not read from a file: %s", err) } - // constructing a file packet and sending it - fileDataPacket := protocol.Packet{ - Header: protocol.HeaderFileData, - Filename: s.FileToTransfer.Filename, - Filesize: s.FileToTransfer.Filesize, - FileData: fileBytes, - } + // filling BODY with bytes + fileBytesPacket.Body = fileBytes - err = protocol.SendPacket(s.Connection, fileDataPacket) + err = protocol.SendPacket(s.Connection, fileBytesPacket) if err != nil { return fmt.Errorf("could not send a file packet : %s", err) } @@ -167,7 +208,7 @@ func (s *Sender) SendPiece() error { // doing a "logging" for the next time s.FileToTransfer.LeftBytes -= uint64(read) s.FileToTransfer.SentBytes += uint64(read) - s.FileToTransfer.SentPackets++ + s.SentFileBytesPackets++ return nil } @@ -201,6 +242,15 @@ func (s *Sender) MainLoop() { break } + if s.TransferAllowed && s.ReceiverIsReady { + err := s.SendPiece() + if err != nil { + fmt.Printf("could not send a piece of file: %s", err) + s.Stop() + } + s.ReceiverIsReady = false + } + // no incoming packets ? Skipping the packet handling part if len(s.IncomingPackets) == 0 { continue @@ -212,27 +262,18 @@ func (s *Sender) MainLoop() { switch incomingPacket.Header { case protocol.HeaderAccept: - fmt.Printf("The transfer has been accepted !\n") // allowed to send file packets - s.CanTransfer = true + s.TransferAllowed = true + + case protocol.HeaderReady: + s.ReceiverIsReady = true case protocol.HeaderReject: - fmt.Println("Transfer has been rejected") s.Stop() - // 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) - os.Exit(-1) - } - case protocol.HeaderDisconnecting: // receiver is dropping the file transfer ? + fmt.Println("Receiver has disconnected") s.Stop() } }