Browse Source

Uhh, a chunky one. Rewrote the whole protocol

main 1.1.0
Unbewohnte 4 years ago
parent
commit
3637dbf8be
  1. 18
      README.md
  2. 36
      checksum/checksum.go
  3. 20
      protocol/constants.go
  4. 77
      protocol/headers.go
  5. 127
      protocol/packet.go
  6. 167
      protocol/protocol.go
  7. 99
      receiver/receiver.go
  8. 15
      sender/file.go
  9. 139
      sender/sender.go

18
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.
---

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

20
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 = "~"

77
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!"

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

167
protocol/protocol.go

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

99
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))

15
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

139
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()
}
}

Loading…
Cancel
Save