Unbewohnte
3 years ago
10 changed files with 409 additions and 270 deletions
@ -1,3 +1,4 @@ |
|||||||
FTU |
FTU |
||||||
FTU.exe |
FTU.exe |
||||||
release/ |
release/ |
||||||
|
downloads/ |
@ -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 |
||||||
|
} |
@ -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() |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -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 |
||||||
|
} |
@ -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() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue