Unbewohnte
4 years ago
10 changed files with 409 additions and 270 deletions
@ -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