🔷 (File Transferring Utility) Transfer files through the Net 🔷
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.
 
 
 

236 lines
5.8 KiB

package server
import (
"fmt"
"io"
"net"
"net/http"
"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 string(localAddr.IP), 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 server struct
type Server struct {
Port int
FileToTransfer *File
Listener net.Listener
Connection net.Conn
IncomingPackets chan protocol.Packet
CanTransfer bool
Stopped bool
}
// Creates a new server with default fields
func NewServer(port int, filepath string) *Server {
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 server at %s:%d (remote)\n%s:%d (local)\n", remoteIP, port, localIP, port)
return &Server{
Port: port,
FileToTransfer: fileToTransfer,
Listener: listener,
Connection: nil,
IncomingPackets: incomingPacketsChan,
Stopped: false,
}
}
// Closes the connection, warns about it the client and exits the mainloop
func (s *Server) 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 *Server) Disconnect() {
s.Connection.Close()
}
// Accepts one connection
func (s *Server) WaitForConnection() error {
connection, err := s.Listener.Accept()
if err != nil {
return fmt.Errorf("could not accept a connection: %s", err)
}
s.Connection = connection
fmt.Println("New connection from ", s.Connection.RemoteAddr())
return nil
}
// Closes the listener. Used only when there is still no connection from `AcceptConnections`
func (s *Server) StopListening() {
s.Listener.Close()
}
// Sends a packet with all information about a file to current connection
func (s *Server) SendOffer() error {
err := protocol.SendPacket(s.Connection, protocol.Packet{
Header: protocol.HeaderFileInfo,
Filename: s.FileToTransfer.Filename,
Filesize: s.FileToTransfer.Filesize,
})
if err != nil {
return fmt.Errorf("could not send an information about the file: %s", err)
}
return nil
}
// Sends one file packet to the client
func (s *Server) 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 *Server) ReceivePackets() {
for {
incomingPacket := protocol.ReadFromConn(s.Connection)
isvalid, _ := protocol.IsValidPacket(incomingPacket)
if !isvalid {
continue
}
s.IncomingPackets <- incomingPacket
}
}
// The "head" of the server. "Glues" all things together.
// Current structure allows the server to receive any type of packet
// in any order and react correspondingly
func (s *Server) MainLoop() {
go s.ReceivePackets()
// send an information about the shared file to the client
s.SendOffer()
for {
if s.Stopped {
// exit the mainloop
break
}
// send, then process received packets
// 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("Client has accepted the transfer !\n")
// allowed to send file packets
s.CanTransfer = true
case protocol.HeaderReject:
fmt.Println("Client has rejected the transfer")
s.Stop()
// client is ready to receive the next file packet
case protocol.HeaderReady:
if !s.CanTransfer {
break
}
err := s.SendPiece()
if err != nil {
fmt.Println(err)
}
case protocol.HeaderDisconnecting:
s.Stop()
}
}
}