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() } } }