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.
239 lines
6.0 KiB
239 lines
6.0 KiB
package sender |
|
|
|
import ( |
|
"fmt" |
|
"io" |
|
"net" |
|
"net/http" |
|
"os" |
|
|
|
"github.com/Unbewohnte/FTU/protocol" |
|
) |
|
|
|
// gets a local ip. Borrowed from StackOverflow, thank you, whoever I brought it from |
|
func GetLocalIP() (string, error) { |
|
conn, err := net.Dial("udp", "8.8.8.8:80") |
|
if err != nil { |
|
return "", err |
|
} |
|
defer conn.Close() |
|
|
|
localAddr := conn.LocalAddr().(*net.UDPAddr) |
|
|
|
return localAddr.IP.String(), nil |
|
} |
|
|
|
// gets a remote ip. Borrowed from StackOverflow, thank you, whoever I brought it from |
|
func GetRemoteIP() (string, error) { |
|
resp, err := http.Get("https://api.ipify.org?format=text") |
|
if err != nil { |
|
return "", fmt.Errorf("could not make a request to get your remote IP: %s", err) |
|
} |
|
defer resp.Body.Close() |
|
ip, err := io.ReadAll(resp.Body) |
|
if err != nil { |
|
return "", fmt.Errorf("could not read a response: %s", err) |
|
} |
|
return string(ip), nil |
|
} |
|
|
|
// 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 |
|
} |
|
|
|
// Creates a new sender with default 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, 5) |
|
|
|
remoteIP, err := GetRemoteIP() |
|
if err != nil { |
|
panic(err) |
|
} |
|
localIP, err := GetLocalIP() |
|
if err != nil { |
|
panic(err) |
|
} |
|
|
|
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, |
|
} |
|
} |
|
|
|
// Closes the connection, warns about it the receiver and exits the mainloop |
|
func (s *Sender) Stop() { |
|
disconnectionPacket := protocol.Packet{ |
|
Header: protocol.HeaderDisconnecting, |
|
} |
|
err := protocol.SendPacket(s.Connection, disconnectionPacket) |
|
if err != nil { |
|
panic(fmt.Sprintf("could not send a disconnection packet: %s", err)) |
|
} |
|
|
|
s.Stopped = true |
|
s.Disconnect() |
|
} |
|
|
|
// Closes current connection |
|
func (s *Sender) Disconnect() { |
|
s.Connection.Close() |
|
} |
|
|
|
// 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 a packet with all information about a file to current connection |
|
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, |
|
}) |
|
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 |
|
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) |
|
s.Stop() |
|
} |
|
|
|
fileBytes := make([]byte, protocol.MAXFILEDATASIZE) |
|
// 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))) |
|
} |
|
|
|
// 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) |
|
} |
|
|
|
// constructing a file packet and sending it |
|
fileDataPacket := protocol.Packet{ |
|
Header: protocol.HeaderFileData, |
|
Filename: s.FileToTransfer.Filename, |
|
Filesize: s.FileToTransfer.Filesize, |
|
FileData: fileBytes, |
|
} |
|
|
|
err = protocol.SendPacket(s.Connection, fileDataPacket) |
|
if err != nil { |
|
return fmt.Errorf("could not send a file packet : %s", err) |
|
} |
|
|
|
// doing a "logging" for the next time |
|
s.FileToTransfer.LeftBytes -= uint64(read) |
|
s.FileToTransfer.SentBytes += uint64(read) |
|
s.FileToTransfer.SentPackets++ |
|
|
|
return nil |
|
} |
|
|
|
// Listens in an endless loop; reads incoming packages and puts them into channel |
|
func (s *Sender) ReceivePackets() { |
|
for { |
|
incomingPacket, err := protocol.ReadFromConn(s.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) |
|
} |
|
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() { |
|
|
|
go s.ReceivePackets() |
|
|
|
// send an information about the shared file to the receiver |
|
s.SendOffer() |
|
|
|
for { |
|
if s.Stopped { |
|
// exit the mainloop |
|
break |
|
} |
|
|
|
// 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: |
|
fmt.Printf("The transfer has been accepted !\n") |
|
// allowed to send file packets |
|
s.CanTransfer = 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 ? |
|
s.Stop() |
|
} |
|
} |
|
}
|
|
|