You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
340 lines
8.9 KiB
340 lines
8.9 KiB
package sender |
|
|
|
import ( |
|
"fmt" |
|
"net" |
|
"os" |
|
"os/signal" |
|
"strconv" |
|
"time" |
|
|
|
"github.com/Unbewohnte/ftu/checksum" |
|
"github.com/Unbewohnte/ftu/encryption" |
|
"github.com/Unbewohnte/ftu/protocol" |
|
) |
|
|
|
// The main sender struct |
|
type Sender struct { |
|
Port int |
|
FileToTransfer *file |
|
Listener net.Listener |
|
Connection net.Conn |
|
IncomingPackets chan protocol.Packet |
|
EncryptionKey []byte |
|
TransferInfo *transferInfo |
|
TransferAllowed bool // the receiver had agreed to receive a file |
|
ReceiverIsReady bool // receiver is waiting for a new packet |
|
Stopped bool // controlls the mainloop |
|
} |
|
|
|
// Creates a new sender with default|necessary fields |
|
func NewSender(port int, filepath string) *Sender { |
|
fileToTransfer, err := getFile(filepath) |
|
if err != nil { |
|
panic(err) |
|
} |
|
|
|
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) |
|
if err != nil { |
|
panic(err) |
|
} |
|
incomingPacketsChan := make(chan protocol.Packet, 100) |
|
|
|
remoteIP, err := GetRemoteIP() |
|
if err != nil { |
|
// don`t panic if couldn`t get remote IP |
|
remoteIP = "" |
|
} |
|
|
|
localIP, err := GetLocalIP() |
|
if err != nil { |
|
panic(err) |
|
} |
|
|
|
// !!! |
|
key := encryption.Generate32AESkey() |
|
fmt.Printf("Generated an encryption key: %s\n", key) |
|
|
|
fmt.Printf("Created a new sender at %s:%d (remote)\n%s:%d (local)\n\n", remoteIP, port, localIP, port) |
|
return &Sender{ |
|
Port: port, |
|
FileToTransfer: fileToTransfer, |
|
Listener: listener, |
|
Connection: nil, |
|
IncomingPackets: incomingPacketsChan, |
|
TransferInfo: &transferInfo{ |
|
SentFileBytesPackets: 0, |
|
ApproximateNumberOfFilePackets: uint64(float32(fileToTransfer.Filesize) / float32(protocol.MAXPACKETSIZE)), |
|
}, |
|
EncryptionKey: key, |
|
TransferAllowed: false, |
|
ReceiverIsReady: false, |
|
Stopped: false, |
|
} |
|
} |
|
|
|
// When the interrupt signal is sent - exit cleanly |
|
func (s *Sender) HandleInterrupt() { |
|
signalChan := make(chan os.Signal, 1) |
|
signal.Notify(signalChan, os.Interrupt) |
|
|
|
go func() { |
|
<-signalChan |
|
s.Stop() |
|
}() |
|
} |
|
|
|
// Closes the connection, warns about it the receiver and exits the mainloop |
|
func (s *Sender) Stop() { |
|
if s.Connection != nil { |
|
disconnectionPacket := protocol.Packet{ |
|
Header: protocol.HeaderDisconnecting, |
|
} |
|
err := protocol.SendEncryptedPacket(s.Connection, disconnectionPacket, s.EncryptionKey) |
|
if err != nil { |
|
panic(fmt.Sprintf("could not send a disconnection packet: %s", err)) |
|
} |
|
|
|
s.Connection.Close() |
|
} |
|
|
|
s.Stopped = true |
|
} |
|
|
|
// Accepts one connection |
|
func (s *Sender) WaitForConnection() { |
|
connection, err := s.Listener.Accept() |
|
if err != nil { |
|
fmt.Printf("Could not accept a connection: %s", err) |
|
os.Exit(-1) |
|
} |
|
s.Connection = connection |
|
fmt.Println("New connection from ", s.Connection.RemoteAddr()) |
|
} |
|
|
|
// Closes the listener. Used only when there is still no connection from `AcceptConnections` |
|
func (s *Sender) StopListening() { |
|
s.Listener.Close() |
|
} |
|
|
|
// Sends generated earlier eas encryption key to receiver |
|
func (s *Sender) SendEncryptionKey() error { |
|
|
|
keyPacket := protocol.Packet{ |
|
Header: protocol.HeaderEncryptionKey, |
|
Body: s.EncryptionKey, |
|
} |
|
err := protocol.SendPacket(s.Connection, keyPacket) |
|
if err != nil { |
|
return fmt.Errorf("could not send a packet: %s", err) |
|
} |
|
|
|
return nil |
|
} |
|
|
|
// Sends multiple packets with all information about the file to receiver |
|
// (filename, filesize, checksum) |
|
func (s *Sender) SendOffer() error { |
|
// filename |
|
filenamePacket := protocol.Packet{ |
|
Header: protocol.HeaderFilename, |
|
Body: []byte(s.FileToTransfer.Filename), |
|
} |
|
err := protocol.SendEncryptedPacket(s.Connection, filenamePacket, s.EncryptionKey) |
|
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.SendEncryptedPacket(s.Connection, filesizePacket, s.EncryptionKey) |
|
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.SendEncryptedPacket(s.Connection, checksumPacket, s.EncryptionKey) |
|
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.SendEncryptedPacket(s.Connection, donePacket, s.EncryptionKey) |
|
if err != nil { |
|
return fmt.Errorf("could not send an information about the file: %s", err) |
|
} |
|
return nil |
|
} |
|
|
|
// 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.TransferInfo.SentFileBytesPackets) |
|
s.Stop() |
|
} |
|
|
|
// empty body |
|
fileBytesPacket := protocol.Packet{ |
|
Header: protocol.HeaderFileBytes, |
|
} |
|
|
|
// how many bytes we can send at maximum (including some little space for padding) |
|
maxFileBytes := protocol.MAXPACKETSIZE - (uint(protocol.MeasurePacketSize(fileBytesPacket)) + 90) |
|
|
|
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(maxFileBytes) { |
|
fileBytes = make([]byte, uint64(maxFileBytes)-(uint64(maxFileBytes)-s.FileToTransfer.LeftBytes)) |
|
} |
|
|
|
// reading bytes from the point where we left |
|
read, err := s.FileToTransfer.Handler.ReadAt(fileBytes, int64(s.FileToTransfer.SentBytes)) |
|
if err != nil { |
|
return fmt.Errorf("could not read from a file: %s", err) |
|
} |
|
|
|
// filling BODY with bytes |
|
fileBytesPacket.Body = fileBytes |
|
|
|
err = protocol.SendEncryptedPacket(s.Connection, fileBytesPacket, s.EncryptionKey) |
|
if err != nil { |
|
return fmt.Errorf("could not send a file packet : %s", err) |
|
} |
|
|
|
// doing a "logging" for the next piece |
|
s.FileToTransfer.LeftBytes -= uint64(read) |
|
s.FileToTransfer.SentBytes += uint64(read) |
|
s.TransferInfo.SentFileBytesPackets++ |
|
|
|
return nil |
|
} |
|
|
|
// Prints a brief information about the state of the transfer |
|
func (s *Sender) PrintTransferInfo(pauseDuration time.Duration) { |
|
next := time.Now().UTC() |
|
for { |
|
if !s.TransferAllowed { |
|
time.Sleep(time.Second) |
|
continue |
|
} |
|
|
|
now := time.Now().UTC() |
|
|
|
if !now.After(next) { |
|
continue |
|
} |
|
next = now.Add(pauseDuration) |
|
|
|
fmt.Printf(` |
|
| Sent packets/Approximate number of packets |
|
| (%d|%d) (%.2f%%/100%%) |
|
`, s.TransferInfo.SentFileBytesPackets, |
|
s.TransferInfo.ApproximateNumberOfFilePackets, |
|
float32(s.TransferInfo.SentFileBytesPackets)/float32(s.TransferInfo.ApproximateNumberOfFilePackets)*100) |
|
|
|
time.Sleep(pauseDuration) |
|
} |
|
} |
|
|
|
// Listens in an endless loop; reads incoming packets, decrypts their BODY and puts into channel |
|
func (s *Sender) ReceivePackets() { |
|
for { |
|
incomingPacketBytes, err := protocol.ReadFromConn(s.Connection) |
|
if err != nil { |
|
fmt.Printf("Error reading a packet: %s\nExiting...", err) |
|
s.Stop() |
|
os.Exit(-1) |
|
} |
|
|
|
incomingPacket := protocol.BytesToPacket(incomingPacketBytes) |
|
|
|
decryptedBody, err := encryption.Decrypt(s.EncryptionKey, incomingPacket.Body) |
|
if err != nil { |
|
fmt.Printf("Error decrypting an incoming packet: %s\nExiting...", err) |
|
s.Stop() |
|
os.Exit(-1) |
|
} |
|
|
|
incomingPacket.Body = decryptedBody |
|
|
|
s.IncomingPackets <- incomingPacket |
|
} |
|
} |
|
|
|
// The "head" of the sender. "Glues" all things together. |
|
// Current structure allows the sender to receive any type of packet |
|
// in any order and react correspondingly |
|
func (s *Sender) MainLoop() { |
|
// receive and print in separate goroutines |
|
go s.ReceivePackets() |
|
go s.PrintTransferInfo(time.Second * 3) |
|
|
|
// instantly sending an encryption key, following the protocol`s rule |
|
err := s.SendEncryptionKey() |
|
if err != nil { |
|
fmt.Printf("Could not send an encryption key: %s\nExiting...", err) |
|
s.Stop() |
|
} |
|
|
|
// send an information about the shared file to the receiver |
|
err = s.SendOffer() |
|
if err != nil { |
|
fmt.Printf("Could not send an info about the file: %s\nExiting...", err) |
|
s.Stop() |
|
} |
|
|
|
for { |
|
if s.Stopped { |
|
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 |
|
} |
|
|
|
incomingPacket := <-s.IncomingPackets |
|
|
|
// handling each packet header differently |
|
switch incomingPacket.Header { |
|
|
|
case protocol.HeaderAccept: |
|
// allowed to send file packets |
|
fmt.Println("The transfer has been accepted !") |
|
s.TransferAllowed = true |
|
|
|
case protocol.HeaderReject: |
|
fmt.Println("The transfer has been rejected") |
|
s.Stop() |
|
|
|
case protocol.HeaderReady: |
|
s.ReceiverIsReady = true |
|
|
|
case protocol.HeaderDisconnecting: |
|
// receiver is dropping the file transfer ? |
|
fmt.Println("Receiver has disconnected") |
|
s.Stop() |
|
} |
|
} |
|
}
|
|
|