diff --git a/src/main.go b/src/main.go index 3c3ac25..0877bb7 100644 --- a/src/main.go +++ b/src/main.go @@ -30,7 +30,7 @@ import ( ) var ( - VERSION string = "v2.1.4" + VERSION string = "v2.2.0" versionInformation string = fmt.Sprintf("ftu %s\n\nCopyright (C) 2021 Kasyanov Nikolay Alexeevich (Unbewohnte (https://unbewohnte.xyz/))\nThis program comes with ABSOLUTELY NO WARRANTY.\nThis is free software, and you are welcome to redistribute it under certain conditions; type \"ftu -l\" for details.\n", VERSION) diff --git a/src/node/node.go b/src/node/node.go index ae6360a..19eed88 100644 --- a/src/node/node.go +++ b/src/node/node.go @@ -39,12 +39,6 @@ import ( "github.com/Unbewohnte/ftu/protocol" ) -// node-controlling states -type nodeInnerstates struct { - Stopped bool // the way to exit the mainloop in case of an external error or a successful end of a transfer - AllowedToTransfer bool // the way to notify the mainloop of a sending node to start sending pieces of files -} - // netInfowork specific settings type netInfo struct { ConnAddr string // address to connect to. Does not include port @@ -55,18 +49,24 @@ type netInfo struct { // Sending-side node information type sending struct { - ServingPath string // path to the thing that will be sent - IsDirectory bool // is ServingPath a directory - Recursive bool // recursively send directory - CanSendBytes bool // is the other node ready to receive another piece - FilesToSend []*fsys.File - CurrentFileID uint64 // an id of a file that is currently being transported + ServingPath string // path to the thing that will be sent + IsDirectory bool // is ServingPath a directory + Recursive bool // recursively send directory + CanSendBytes bool // is the other node ready to receive another piece + AllowedToTransfer bool // the way to notify the mainloop of a sending node to start sending pieces of files + InTransfer bool // already transferring|receiving files + FilesToSend []*fsys.File + CurrentFileID uint64 // an id of a file that is currently being transported + SentBytes uint64 // how many bytes sent already + TotalTransferSize uint64 // how many bytes will be sent in total } // Receiving-side node information type receiving struct { - AcceptedFiles []*fsys.File // files that`ve been accepted to be received - DownloadsPath string // where to download + AcceptedFiles []*fsys.File // files that`ve been accepted to be received + DownloadsPath string // where to download + TotalDownloadSize uint64 // how many bytes will be received in total + ReceivedBytes uint64 // how many bytes downloaded so far } // Both sending-side and receiving-side information @@ -81,8 +81,8 @@ type Node struct { mutex *sync.Mutex packetPipe chan *protocol.Packet // a way to receive incoming packets from another goroutine isSending bool // sending or a receiving node + stopped bool // the way to exit the mainloop in case of an external error or a successful end of a transfer netInfo *netInfo - state *nodeInnerstates transferInfo *transferInfo } @@ -129,19 +129,20 @@ func NewNode(options *NodeOptions) (*Node, error) { EncryptionKey: nil, Conn: nil, }, - state: &nodeInnerstates{ - AllowedToTransfer: false, - Stopped: false, - }, + stopped: false, transferInfo: &transferInfo{ Sending: &sending{ - ServingPath: options.ServerSide.ServingPath, - Recursive: options.ServerSide.Recursive, - IsDirectory: isDir, + ServingPath: options.ServerSide.ServingPath, + Recursive: options.ServerSide.Recursive, + IsDirectory: isDir, + TotalTransferSize: 0, + SentBytes: 0, }, Receiving: &receiving{ - AcceptedFiles: nil, - DownloadsPath: options.ClientSide.DownloadsFolderPath, + AcceptedFiles: nil, + DownloadsPath: options.ClientSide.DownloadsFolderPath, + ReceivedBytes: 0, + TotalDownloadSize: 0, }, }, } @@ -184,7 +185,7 @@ func (node *Node) disconnect() error { return err } - node.state.Stopped = true + node.stopped = true } return nil @@ -216,668 +217,657 @@ func (node *Node) printTransferInfo(delay time.Duration) error { switch node.isSending { case true: - if !node.state.AllowedToTransfer { + if !node.transferInfo.Sending.AllowedToTransfer { + // do not print if the transfer has not been accepted yet break } - fmt.Printf("\r| files(s) left to send: %4d", len(node.transferInfo.Sending.FilesToSend)) + fmt.Printf("\r| (%.2f/%.2f MB)", + float32(node.transferInfo.Sending.SentBytes)/1024/1024, + float32(node.transferInfo.Sending.TotalTransferSize)/1024/1024, + ) case false: - if len(node.transferInfo.Receiving.AcceptedFiles) <= 0 { - break - } - fmt.Printf("\r| file(s) left to receive: %4d", len(node.transferInfo.Receiving.AcceptedFiles)) + fmt.Printf("\r| (%.2f/%.2f MB)", + float32(node.transferInfo.Receiving.ReceivedBytes)/1024/1024, + float32(node.transferInfo.Receiving.TotalDownloadSize)/1024/1024, + ) } return nil } -// Starts the node in either sending or receiving state and performs the transfer -func (node *Node) Start() { - switch node.isSending { - case true: - // SENDER NODE +func (node *Node) send() { + // SENDER NODE + + localIP, err := addr.GetLocal() + if err != nil { + panic(err) + } - localIP, err := addr.GetLocal() + // retrieve information about the file|directory + var FILETOSEND *fsys.File + var DIRTOSEND *fsys.Directory + switch node.transferInfo.Sending.IsDirectory { + case true: + DIRTOSEND, err = fsys.GetDir(node.transferInfo.Sending.ServingPath, node.transferInfo.Sending.Recursive) if err != nil { panic(err) } - - // retrieve information about the file|directory - var fileToSend *fsys.File - var dirToSend *fsys.Directory - switch node.transferInfo.Sending.IsDirectory { - case true: - dirToSend, err = fsys.GetDir(node.transferInfo.Sending.ServingPath, node.transferInfo.Sending.Recursive) - if err != nil { - panic(err) - } - case false: - fileToSend, err = fsys.GetFile(node.transferInfo.Sending.ServingPath) - if err != nil { - panic(err) - } - } - - if dirToSend != nil { - size := float32(dirToSend.Size) / 1024 / 1024 - sizeLevel := "MiB" - if size >= 1024 { - // GiB - size = size / 1024 - sizeLevel = "GiB" - } - - fmt.Printf("\nSending \"%s\" (%.3f %s) locally on %s:%d", dirToSend.Name, size, sizeLevel, localIP, node.netInfo.Port) - } else { - size := float32(fileToSend.Size) / 1024 / 1024 - sizeLevel := "MiB" - if size >= 1024 { - // GiB - size = size / 1024 - sizeLevel = "GiB" - } - fmt.Printf("\nSending \"%s\" (%.3f %s) locally on %s:%d and remotely", fileToSend.Name, size, sizeLevel, localIP, node.netInfo.Port) - - } - - // wain for another node to connect - err = node.waitForConnection() + case false: + FILETOSEND, err = fsys.GetFile(node.transferInfo.Sending.ServingPath) if err != nil { panic(err) } + } - // generate and send encryption key - encrKey := encryption.Generate32AESkey() - node.netInfo.EncryptionKey = encrKey - fmt.Printf("\nGenerated encryption key: %s\n", encrKey) + if DIRTOSEND != nil { + node.transferInfo.Sending.TotalTransferSize = DIRTOSEND.Size - err = protocol.SendEncryptionKey(node.netInfo.Conn, encrKey) - if err != nil { - panic(err) + displaySize := float32(DIRTOSEND.Size) / 1024 / 1024 + sizeLevel := "MiB" + if displaySize >= 1024 { + // GiB + displaySize = displaySize / 1024 + sizeLevel = "GiB" } - // listen for incoming packets - go protocol.ReceivePackets(node.netInfo.Conn, node.packetPipe) + fmt.Printf("\nSending \"%s\" (%.3f %s) locally on %s:%d and remotely (if configured)", DIRTOSEND.Name, displaySize, sizeLevel, localIP, node.netInfo.Port) + } else { + node.transferInfo.Sending.TotalTransferSize = FILETOSEND.Size + + displaySize := float32(FILETOSEND.Size) / 1024 / 1024 + sizeLevel := "MiB" + if displaySize >= 1024 { + // GiB + displaySize = displaySize / 1024 + sizeLevel = "GiB" + } + fmt.Printf("\nSending \"%s\" (%.3f %s) locally on %s:%d and remotely (if configured)", FILETOSEND.Name, displaySize, sizeLevel, localIP, node.netInfo.Port) - // send info about file/directory - go protocol.SendTransferOffer(node.netInfo.Conn, fileToSend, dirToSend, node.netInfo.EncryptionKey) + } - // mainloop - for { - if node.state.Stopped { - fmt.Printf("\n") - node.disconnect() - break - } + // wain for another node to connect + err = node.waitForConnection() + if err != nil { + panic(err) + } - // receive incoming packets and decrypt them if necessary - incomingPacket, ok := <-node.packetPipe - if !ok { - fmt.Printf("\nThe connection has been closed unexpectedly\n") - os.Exit(-1.) - } + // generate and send encryption key + encrKey := encryption.Generate32AESkey() + node.netInfo.EncryptionKey = encrKey + fmt.Printf("\nGenerated encryption key: %s\n", encrKey) - // if encryption key is set - decrypt packet on the spot - if node.netInfo.EncryptionKey != nil { - err = incomingPacket.DecryptBody(node.netInfo.EncryptionKey) - if err != nil { - panic(err) - } - } + err = protocol.SendEncryptionKey(node.netInfo.Conn, encrKey) + if err != nil { + panic(err) + } - // react based on a header of a received packet - switch incomingPacket.Header { + // listen for incoming packets + go protocol.ReceivePackets(node.netInfo.Conn, node.packetPipe) - case protocol.HeaderReady: - // the other node is ready to receive file data - node.transferInfo.Sending.CanSendBytes = true + // send info about file/directory + go protocol.SendTransferOffer(node.netInfo.Conn, FILETOSEND, DIRTOSEND, node.netInfo.EncryptionKey) - case protocol.HeaderAccept: - // the receiving node has accepted the transfer - node.state.AllowedToTransfer = true + // mainloop + for { + if node.stopped { + fmt.Printf("\n") + node.disconnect() + break + } - fmt.Printf("\nTransfer allowed. Sending...") + // receive incoming packets and decrypt them if necessary + incomingPacket, ok := <-node.packetPipe + if !ok { + fmt.Printf("\nThe connection has been closed unexpectedly\n") + os.Exit(-1.) + } - // notify it about all the files that are going to be sent - switch node.transferInfo.Sending.IsDirectory { - case true: - // send file packets for the files in the directory + // if encryption key is set - decrypt packet on the spot + if node.netInfo.EncryptionKey != nil { + err = incomingPacket.DecryptBody(node.netInfo.EncryptionKey) + if err != nil { + panic(err) + } + } - err = dirToSend.SetRelativePaths(dirToSend.Path, node.transferInfo.Sending.Recursive) - if err != nil { - panic(err) - } - filesToSend := dirToSend.GetAllFiles(node.transferInfo.Sending.Recursive) + // react based on a header of a received packet + switch incomingPacket.Header { - // notify the other node about all the files that are going to be sent - for counter, file := range filesToSend { - // assign ID and add it to the node sendlist + case protocol.HeaderReady: + // the other node is ready to receive file data + node.transferInfo.Sending.CanSendBytes = true - file.ID = uint64(counter) - node.transferInfo.Sending.FilesToSend = append(node.transferInfo.Sending.FilesToSend, file) + case protocol.HeaderAccept: + // the receiving node has accepted the transfer + node.transferInfo.Sending.AllowedToTransfer = true - // set current file id to the first file - node.transferInfo.Sending.CurrentFileID = 0 + // prepare files to send + switch node.transferInfo.Sending.IsDirectory { + case true: + // send file packets for the files in the directory - filePacket, err := protocol.CreateFilePacket(file) - if err != nil { - panic(err) - } + err = DIRTOSEND.SetRelativePaths(DIRTOSEND.Path, node.transferInfo.Sending.Recursive) + if err != nil { + panic(err) + } + filesToSend := DIRTOSEND.GetAllFiles(node.transferInfo.Sending.Recursive) - // encrypt if necessary - if node.netInfo.EncryptionKey != nil { - encryptedBody, err := encryption.Encrypt(node.netInfo.EncryptionKey, filePacket.Body) - if err != nil { - panic(err) - } - filePacket.Body = encryptedBody - } + // notify the other node about all the files that are going to be sent + for counter, file := range filesToSend { + // assign ID and add it to the node sendlist + file.ID = uint64(counter) + node.transferInfo.Sending.FilesToSend = append(node.transferInfo.Sending.FilesToSend, file) + } - err = protocol.SendPacket(node.netInfo.Conn, *filePacket) - if err != nil { - panic(err) - } + // set current file id to the first file + node.transferInfo.Sending.CurrentFileID = 0 - if node.verboseOutput { - fmt.Printf("\n[File] Sent filepacket for \"%s\"", file.Name) - } + case false: + FILETOSEND.ID = 0 + node.transferInfo.Sending.FilesToSend = append(node.transferInfo.Sending.FilesToSend, FILETOSEND) - time.Sleep(time.Microsecond * 3) - } + // set current file index to the first and only file + node.transferInfo.Sending.CurrentFileID = 0 + } + fmt.Printf("\n") - filesInfoDonePacket := protocol.Packet{ - Header: protocol.HeaderFilesInfoDone, - } - protocol.SendPacket(node.netInfo.Conn, filesInfoDonePacket) + case protocol.HeaderReject: + node.stopped = true + fmt.Printf("\nTransfer rejected. Disconnecting...") - if node.verboseOutput { - fmt.Printf("\n[File] Done sending filepackets") - } + case protocol.HeaderDisconnecting: + node.stopped = true + fmt.Printf("\n%s disconnected", node.netInfo.Conn.RemoteAddr()) - case false: - // send a filepacket of a single file - fileToSend.ID = 0 - node.transferInfo.Sending.FilesToSend = append(node.transferInfo.Sending.FilesToSend, fileToSend) + case protocol.HeaderAlreadyHave: + // the other node already has a file with such ID. + // do not send it - // set current file index to the first and only file - node.transferInfo.Sending.CurrentFileID = 0 + fileIDReader := bytes.NewReader(incomingPacket.Body) + var fileID uint64 + binary.Read(fileIDReader, binary.BigEndian, &fileID) - filePacket, err := protocol.CreateFilePacket(node.transferInfo.Sending.FilesToSend[0]) - if err != nil { - panic(err) - } - - // encrypt if necessary - if node.netInfo.EncryptionKey != nil { - encryptedBody, err := encryption.Encrypt(node.netInfo.EncryptionKey, filePacket.Body) - if err != nil { - panic(err) - } - filePacket.Body = encryptedBody - } + for index, fileToSend := range node.transferInfo.Sending.FilesToSend { + if fileToSend.ID == fileID { + node.transferInfo.Sending.FilesToSend = append(node.transferInfo.Sending.FilesToSend[:index], node.transferInfo.Sending.FilesToSend[index+1:]...) - err = protocol.SendPacket(node.netInfo.Conn, *filePacket) - if err != nil { - panic(err) - } - - filesInfoDonePacket := protocol.Packet{ - Header: protocol.HeaderFilesInfoDone, - } - protocol.SendPacket(node.netInfo.Conn, filesInfoDonePacket) + node.transferInfo.Sending.CurrentFileID++ - if node.verboseOutput { - fmt.Printf("\n[File] Sent filepacket for \"%s\"", fileToSend.Name) - } + node.transferInfo.Sending.SentBytes += fileToSend.Size if node.verboseOutput { - fmt.Printf("\n[File] Done sending filepackets") + fmt.Printf("\n[File] receiver already has \"%s\"", fileToSend.Name) } - } + } - case protocol.HeaderReject: - node.state.Stopped = true - fmt.Printf("\nTransfer rejected. Disconnecting...") + } - case protocol.HeaderDisconnecting: - node.state.Stopped = true - fmt.Printf("\n%s disconnected", node.netInfo.Conn.RemoteAddr()) + // Transfer section - case protocol.HeaderAlreadyHave: - // the other node already has a file with such ID. - // do not send it + if len(node.transferInfo.Sending.FilesToSend) == 0 { + // if there`s nothing else to send - create and send DONE packet + protocol.SendPacket(node.netInfo.Conn, protocol.Packet{ + Header: protocol.HeaderDone, + }) - fileIDReader := bytes.NewReader(incomingPacket.Body) - var fileID uint64 - binary.Read(fileIDReader, binary.BigEndian, &fileID) + fmt.Printf("\nTransfer ended successfully") + node.stopped = true - for index, fileToSend := range node.transferInfo.Sending.FilesToSend { - if fileToSend.ID == fileID { - node.transferInfo.Sending.FilesToSend = append(node.transferInfo.Sending.FilesToSend[:index], node.transferInfo.Sending.FilesToSend[index+1:]...) + continue + } - node.transferInfo.Sending.CurrentFileID++ + if node.transferInfo.Sending.AllowedToTransfer && !node.transferInfo.Sending.InTransfer { + // notify the node about the next file to be sent - if node.verboseOutput { - fmt.Printf("\n[File] receiver already has \"%s\"", fileToSend.Name) - } - } + // determine an index of a file with current ID + var currentFileIndex uint64 = 0 + for index, fileToSend := range node.transferInfo.Sending.FilesToSend { + if fileToSend.ID == node.transferInfo.Sending.CurrentFileID { + currentFileIndex = uint64(index) + break } + } + fpacket, err := protocol.CreateFilePacket(node.transferInfo.Sending.FilesToSend[currentFileIndex]) + if err != nil { + panic(err) } - if !node.verboseOutput { - go node.printTransferInfo(time.Second) + if node.netInfo.EncryptionKey != nil { + err = fpacket.EncryptBody(node.netInfo.EncryptionKey) + if err != nil { + panic(err) + } } - // Transfer section + err = protocol.SendPacket(node.netInfo.Conn, *fpacket) + if err != nil { + panic(err) + } - if len(node.transferInfo.Sending.FilesToSend) == 0 { - // if there`s nothing else to send - create and send DONE packet - protocol.SendPacket(node.netInfo.Conn, protocol.Packet{ - Header: protocol.HeaderDone, - }) + // initiate the transfer for this file on the next iteration + node.transferInfo.Sending.InTransfer = true + continue + } - fmt.Printf("\nTransfer ended successfully") - node.state.Stopped = true + if !node.verboseOutput { + go node.printTransferInfo(time.Second) + } - continue + // if allowed to transfer and the other node is ready to receive packets - send one piece + // and wait for it to be ready again + if node.transferInfo.Sending.AllowedToTransfer && node.transferInfo.Sending.CanSendBytes && node.transferInfo.Sending.InTransfer { + // sending a piece of a single file + + // determine an index of a file with current ID + var currentFileIndex uint64 = 0 + for index, fileToSend := range node.transferInfo.Sending.FilesToSend { + if fileToSend.ID == node.transferInfo.Sending.CurrentFileID { + currentFileIndex = uint64(index) + break + } } - // if allowed to transfer and the other node is ready to receive packets - send one piece - // and wait for it to be ready again - if node.state.AllowedToTransfer && node.transferInfo.Sending.CanSendBytes { - // sending a piece of a single file + sentBytes, err := protocol.SendPiece(node.transferInfo.Sending.FilesToSend[currentFileIndex], node.netInfo.Conn, node.netInfo.EncryptionKey) + node.transferInfo.Sending.SentBytes += sentBytes + switch err { + case protocol.ErrorSentAll: + // the file has been sent fully - // determine an index of a file with current ID - var currentFileIndex uint64 = 0 - for index, fileToSend := range node.transferInfo.Sending.FilesToSend { - if fileToSend.ID == node.transferInfo.Sending.CurrentFileID { - currentFileIndex = uint64(index) - break - } + if node.verboseOutput { + fmt.Printf("\n[File] fully sent \"%s\" -- %d bytes", node.transferInfo.Sending.FilesToSend[currentFileIndex].Name, node.transferInfo.Sending.FilesToSend[currentFileIndex].Size) } - err = protocol.SendPiece(node.transferInfo.Sending.FilesToSend[currentFileIndex], node.netInfo.Conn, node.netInfo.EncryptionKey) - switch err { - case protocol.ErrorSentAll: - // the file has been sent fully + fileIDBuff := new(bytes.Buffer) + err = binary.Write(fileIDBuff, binary.BigEndian, node.transferInfo.Sending.FilesToSend[currentFileIndex].ID) + if err != nil { + panic(err) + } - if node.verboseOutput { - fmt.Printf("\n[File] fully sent \"%s\" -- %d bytes", node.transferInfo.Sending.FilesToSend[currentFileIndex].Name, node.transferInfo.Sending.FilesToSend[currentFileIndex].Size) - } + endFilePacket := protocol.Packet{ + Header: protocol.HeaderEndfile, + Body: fileIDBuff.Bytes(), + } - fileIDBuff := new(bytes.Buffer) - err = binary.Write(fileIDBuff, binary.BigEndian, node.transferInfo.Sending.FilesToSend[currentFileIndex].ID) + if node.netInfo.EncryptionKey != nil { + err = endFilePacket.EncryptBody(node.netInfo.EncryptionKey) if err != nil { panic(err) } + } - endFilePacket := protocol.Packet{ - Header: protocol.HeaderEndfile, - Body: fileIDBuff.Bytes(), - } - - if node.netInfo.EncryptionKey != nil { - err = endFilePacket.EncryptBody(node.netInfo.EncryptionKey) - if err != nil { - panic(err) - } - } - - protocol.SendPacket(node.netInfo.Conn, endFilePacket) + protocol.SendPacket(node.netInfo.Conn, endFilePacket) - node.transferInfo.Sending.FilesToSend = append(node.transferInfo.Sending.FilesToSend[:currentFileIndex], node.transferInfo.Sending.FilesToSend[currentFileIndex+1:]...) + // remove this file from the queue + node.transferInfo.Sending.FilesToSend = append(node.transferInfo.Sending.FilesToSend[:currentFileIndex], node.transferInfo.Sending.FilesToSend[currentFileIndex+1:]...) - // start sending the next file - node.transferInfo.Sending.CurrentFileID++ + // set counter to the next file ID + node.transferInfo.Sending.CurrentFileID++ + node.transferInfo.Sending.InTransfer = false - case nil: - node.transferInfo.Sending.CanSendBytes = false + case nil: + node.transferInfo.Sending.CanSendBytes = false - default: - node.state.Stopped = true + default: + node.stopped = true - fmt.Printf("\nAn error occured while sending a piece of \"%s\": %s", node.transferInfo.Sending.FilesToSend[currentFileIndex].Name, err) - panic(err) - } + fmt.Printf("\nAn error occured while sending a piece of \"%s\": %s", node.transferInfo.Sending.FilesToSend[currentFileIndex].Name, err) + panic(err) } } + } +} - case false: - // RECEIVER NODE +func (node *Node) receive() { + // RECEIVER NODE - // connect to the sending node - err := node.connect() - if err != nil { - fmt.Printf("\nCould not connect to %s:%d", node.netInfo.ConnAddr, node.netInfo.Port) - os.Exit(-1) - } + // connect to the sending node + err := node.connect() + if err != nil { + fmt.Printf("\nCould not connect to %s:%d", node.netInfo.ConnAddr, node.netInfo.Port) + os.Exit(-1) + } - // listen for incoming packets - go protocol.ReceivePackets(node.netInfo.Conn, node.packetPipe) + // listen for incoming packets + go protocol.ReceivePackets(node.netInfo.Conn, node.packetPipe) - // mainloop - for { - node.mutex.Lock() - stopped := node.state.Stopped - node.mutex.Unlock() + // mainloop + for { + node.mutex.Lock() + stopped := node.stopped + node.mutex.Unlock() - if stopped { - fmt.Printf("\n") - node.disconnect() - break - } + if stopped { + fmt.Printf("\n") + node.disconnect() + break + } + + // receive incoming packets and decrypt them if necessary + incomingPacket, ok := <-node.packetPipe + if !ok { + fmt.Printf("\nConnection has been closed unexpectedly\n") + os.Exit(-1) + } - // receive incoming packets and decrypt them if necessary - incomingPacket, ok := <-node.packetPipe - if !ok { - fmt.Printf("\nThe connection has been closed unexpectedly\n") - os.Exit(-1) + // if encryption key is set - decrypt packet on the spot + if node.netInfo.EncryptionKey != nil { + err = incomingPacket.DecryptBody(node.netInfo.EncryptionKey) + if err != nil { + panic(err) } + } - // if encryption key is set - decrypt packet on the spot - if node.netInfo.EncryptionKey != nil { - err = incomingPacket.DecryptBody(node.netInfo.EncryptionKey) + // react based on a header of a received packet + switch incomingPacket.Header { + + case protocol.HeaderTransferOffer: + // accept of reject offer + go func() { + file, dir, err := protocol.DecodeTransferPacket(incomingPacket) if err != nil { panic(err) } - } - // react based on a header of a received packet - switch incomingPacket.Header { + if file != nil { + node.transferInfo.Receiving.TotalDownloadSize = file.Size - case protocol.HeaderTransferOffer: - // accept of reject offer - go func() { - file, dir, err := protocol.DecodeTransferPacket(incomingPacket) - if err != nil { - panic(err) + size := float32(file.Size) / 1024 / 1024 + sizeLevel := "MiB" + if size >= 1024 { + // GiB + size = size / 1024 + sizeLevel = "GiB" } + fmt.Printf("\n| Filename: %s\n| Size: %.3f %s\n| Checksum: %s\n", file.Name, size, sizeLevel, file.Checksum) - if file != nil { - size := float32(file.Size) / 1024 / 1024 - sizeLevel := "MiB" - if size >= 1024 { - // GiB - size = size / 1024 - sizeLevel = "GiB" - } - fmt.Printf("\n| Filename: %s\n| Size: %.3f %s\n| Checksum: %s\n", file.Name, size, sizeLevel, file.Checksum) - } else if dir != nil { - size := float32(dir.Size) / 1024 / 1024 - sizeLevel := "MiB" - if size >= 1024 { - // GiB - size = size / 1024 - sizeLevel = "GiB" - } - fmt.Printf("\n| Directory name: %s\n| Size: %.3f %s\n", dir.Name, size, sizeLevel) - } + } else if dir != nil { + node.transferInfo.Receiving.TotalDownloadSize = dir.Size - var answer string - fmt.Printf("| Download ? [Y/n]: ") - fmt.Scanln(&answer) - fmt.Printf("\n\n") - - if strings.EqualFold(answer, "y") || answer == "" { - // yes - - // in case it`s a directory - create it now - if dir != nil { - err = os.MkdirAll(filepath.Join(node.transferInfo.Receiving.DownloadsPath, dir.Name), os.ModePerm) - if err != nil { - // well, just download all files in the default downloads folder then - fmt.Printf("\n[ERROR] could not create a directory") - } else { - // also download everything in a newly created directory - node.transferInfo.Receiving.DownloadsPath = filepath.Join(node.transferInfo.Receiving.DownloadsPath, dir.Name) - } + size := float32(dir.Size) / 1024 / 1024 + sizeLevel := "MiB" + if size >= 1024 { + // GiB + size = size / 1024 + sizeLevel = "GiB" + } + fmt.Printf("\n| Directory name: %s\n| Size: %.3f %s\n", dir.Name, size, sizeLevel) + } - } + var answer string + fmt.Printf("| Download ? [Y/n]: ") + fmt.Scanln(&answer) + fmt.Printf("\n\n") - // send aceptance packet - acceptancePacket := protocol.Packet{ - Header: protocol.HeaderAccept, - } + if strings.EqualFold(answer, "y") || answer == "" { + // yes - err = protocol.SendPacket(node.netInfo.Conn, acceptancePacket) + // in case it`s a directory - create it now + if dir != nil { + err = os.MkdirAll(filepath.Join(node.transferInfo.Receiving.DownloadsPath, dir.Name), os.ModePerm) if err != nil { - panic(err) + // well, just download all files in the default downloads folder then + fmt.Printf("\n[ERROR] could not create a directory, downloading directly to the specified location") + } else { + // also download everything in a newly created directory + node.transferInfo.Receiving.DownloadsPath = filepath.Join(node.transferInfo.Receiving.DownloadsPath, dir.Name) } - } else { - // no + } - rejectionPacket := protocol.Packet{ - Header: protocol.HeaderReject, - } + // send aceptance packet + acceptancePacket := protocol.Packet{ + Header: protocol.HeaderAccept, + } - err = protocol.SendPacket(node.netInfo.Conn, rejectionPacket) - if err != nil { - panic(err) - } + err = protocol.SendPacket(node.netInfo.Conn, acceptancePacket) + if err != nil { + panic(err) + } + + } else { + // no - node.mutex.Lock() - node.state.Stopped = true - node.mutex.Unlock() + rejectionPacket := protocol.Packet{ + Header: protocol.HeaderReject, } - }() - case protocol.HeaderFile: - // add file to the accepted files; + err = protocol.SendPacket(node.netInfo.Conn, rejectionPacket) + if err != nil { + panic(err) + } - file, err := protocol.DecodeFilePacket(incomingPacket) - if err != nil { - panic(err) + node.mutex.Lock() + node.stopped = true + node.mutex.Unlock() } + }() - if node.verboseOutput { - fmt.Printf("\n[File] Received info on \"%s\" - %d bytes", file.Name, file.Size) - } + case protocol.HeaderFile: + // add file to the accepted files; + file, err := protocol.DecodeFilePacket(incomingPacket) + if err != nil { + panic(err) + } - if strings.TrimSpace(file.RelativeParentPath) == "" { - // does not have a parent dir - file.Path = filepath.Join(node.transferInfo.Receiving.DownloadsPath, file.Name) - } else { - file.Path = filepath.Join(node.transferInfo.Receiving.DownloadsPath, file.RelativeParentPath) - } + if node.verboseOutput { + fmt.Printf("\n[File] Received info on \"%s\" - %d bytes", file.Name, file.Size) + } + + if strings.TrimSpace(file.RelativeParentPath) == "" { + // does not have a parent dir + file.Path = filepath.Join(node.transferInfo.Receiving.DownloadsPath, file.Name) + } else { + file.Path = filepath.Join(node.transferInfo.Receiving.DownloadsPath, file.RelativeParentPath) + } + + // create all underlying directories right ahead + err = os.MkdirAll(filepath.Dir(file.Path), os.ModePerm) + if err != nil { + panic(err) + } - // create all underlying directories right ahead - err = os.MkdirAll(filepath.Dir(file.Path), os.ModePerm) + // check if the file already exists + _, err = os.Stat(file.Path) + if err == nil { + // exists + // check if it is the exact file + existingFileHandler, err := os.Open(file.Path) if err != nil { panic(err) } - // check if the file already exists - _, err = os.Stat(file.Path) - if err == nil { - // exists - // check if it is the exact file - existingFileHandler, err := os.Open(file.Path) - if err != nil { - panic(err) - } - - existingFileChecksum, err := checksum.GetPartialCheckSum(existingFileHandler) - if err != nil { - panic(err) - } + existingFileChecksum, err := checksum.GetPartialCheckSum(existingFileHandler) + if err != nil { + panic(err) + } - if existingFileChecksum == file.Checksum { - // it`s the exact same file. No need to receive it again - // notify the other node + if existingFileChecksum == file.Checksum { + // it`s the exact same file. No need to receive it again + // notify the other node - alreadyHavePacketBodyBuffer := new(bytes.Buffer) - binary.Write(alreadyHavePacketBodyBuffer, binary.BigEndian, file.ID) + alreadyHavePacketBodyBuffer := new(bytes.Buffer) + binary.Write(alreadyHavePacketBodyBuffer, binary.BigEndian, file.ID) - alreadyHavePacket := protocol.Packet{ - Header: protocol.HeaderAlreadyHave, - Body: alreadyHavePacketBodyBuffer.Bytes(), - } + alreadyHavePacket := protocol.Packet{ + Header: protocol.HeaderAlreadyHave, + Body: alreadyHavePacketBodyBuffer.Bytes(), + } - if node.netInfo.EncryptionKey != nil { - encryptedBody, err := encryption.Encrypt(node.netInfo.EncryptionKey, alreadyHavePacket.Body) - if err != nil { - panic(err) - } - alreadyHavePacket.Body = encryptedBody + if node.netInfo.EncryptionKey != nil { + encryptedBody, err := encryption.Encrypt(node.netInfo.EncryptionKey, alreadyHavePacket.Body) + if err != nil { + panic(err) } + alreadyHavePacket.Body = encryptedBody + } - protocol.SendPacket(node.netInfo.Conn, alreadyHavePacket) + protocol.SendPacket(node.netInfo.Conn, alreadyHavePacket) - if node.verboseOutput { - fmt.Printf("\n[File] already have \"%s\"", file.Name) - } + node.transferInfo.Receiving.ReceivedBytes += file.Size - } else { - // not the same file. Remove it and await new bytes - os.Remove(file.Path) + if node.verboseOutput { + fmt.Printf("\n[File] already have \"%s\"", file.Name) } - existingFileHandler.Close() } else { - // does not exist + // not the same file. Remove it and await new bytes + os.Remove(file.Path) node.mutex.Lock() node.transferInfo.Receiving.AcceptedFiles = append(node.transferInfo.Receiving.AcceptedFiles, file) node.mutex.Unlock() } - case protocol.HeaderFileBytes: - // check if this file has been accepted to receive + existingFileHandler.Close() + } else { + // does not exist - fileBytesBuffer := bytes.NewBuffer(incomingPacket.Body) + node.mutex.Lock() + node.transferInfo.Receiving.AcceptedFiles = append(node.transferInfo.Receiving.AcceptedFiles, file) + node.mutex.Unlock() + } - var fileID uint64 - err := binary.Read(fileBytesBuffer, binary.BigEndian, &fileID) - if err != nil { - panic(err) - } + err = protocol.SendPacket(node.netInfo.Conn, protocol.Packet{ + Header: protocol.HeaderReady, + }) + if err != nil { + panic(err) + } - for _, acceptedFile := range node.transferInfo.Receiving.AcceptedFiles { - if acceptedFile.ID == fileID { - // accepted + case protocol.HeaderFileBytes: + // check if this file has been accepted to receive - // append provided bytes to the file + fileBytesBuffer := bytes.NewBuffer(incomingPacket.Body) - err = acceptedFile.Open() - if err != nil { - panic(err) - } + var fileID uint64 + err := binary.Read(fileBytesBuffer, binary.BigEndian, &fileID) + if err != nil { + panic(err) + } - fileBytes := fileBytesBuffer.Bytes() + for _, acceptedFile := range node.transferInfo.Receiving.AcceptedFiles { + if acceptedFile.ID == fileID { + // accepted - wrote, err := acceptedFile.Handler.WriteAt(fileBytes, int64(acceptedFile.SentBytes)) - if err != nil { - panic(err) - } - acceptedFile.SentBytes += uint64(wrote) + // append provided bytes to the file - err = acceptedFile.Close() - if err != nil { - panic(err) - } + err = acceptedFile.Open() + if err != nil { + panic(err) } - } - readyPacket := protocol.Packet{ - Header: protocol.HeaderReady, - } - protocol.SendPacket(node.netInfo.Conn, readyPacket) + fileBytes := fileBytesBuffer.Bytes() - case protocol.HeaderFilesInfoDone: - // have all information about the files + wrote, err := acceptedFile.Handler.WriteAt(fileBytes, int64(acceptedFile.SentBytes)) + if err != nil { + panic(err) + } + acceptedFile.SentBytes += uint64(wrote) + node.transferInfo.Receiving.ReceivedBytes += uint64(wrote) - // notify the other node that this one is ready - err = protocol.SendPacket(node.netInfo.Conn, protocol.Packet{ - Header: protocol.HeaderReady, - }) - if err != nil { - panic(err) + err = acceptedFile.Close() + if err != nil { + panic(err) + } } + } - case protocol.HeaderEndfile: - // one of the files has been received completely, - // compare checksums and check if it is the last - // file in the transfer + readyPacket := protocol.Packet{ + Header: protocol.HeaderReady, + } + protocol.SendPacket(node.netInfo.Conn, readyPacket) - fileIDReader := bytes.NewReader(incomingPacket.Body) - var fileID uint64 - err := binary.Read(fileIDReader, binary.BigEndian, &fileID) - if err != nil { - panic(err) - } + case protocol.HeaderEndfile: + // one of the files has been received completely - for index, acceptedFile := range node.transferInfo.Receiving.AcceptedFiles { - if acceptedFile.ID == fileID { - // accepted + fileIDReader := bytes.NewReader(incomingPacket.Body) + var fileID uint64 + err := binary.Read(fileIDReader, binary.BigEndian, &fileID) + if err != nil { + panic(err) + } - if node.verboseOutput { - fmt.Printf("\n[File] fully received \"%s\" -- %d bytes", acceptedFile.Name, acceptedFile.Size) - } + for index, acceptedFile := range node.transferInfo.Receiving.AcceptedFiles { + if acceptedFile.ID == fileID { + // accepted - err = acceptedFile.Open() - if err != nil { - panic(err) - } + if node.verboseOutput { + fmt.Printf("\n[File] fully received \"%s\" -- %d bytes", acceptedFile.Name, acceptedFile.Size) + } + + err = acceptedFile.Open() + if err != nil { + panic(err) + } - // remove this file from the pool - node.transferInfo.Receiving.AcceptedFiles = append(node.transferInfo.Receiving.AcceptedFiles[:index], node.transferInfo.Receiving.AcceptedFiles[index+1:]...) + // remove this file from the pool + node.transferInfo.Receiving.AcceptedFiles = append(node.transferInfo.Receiving.AcceptedFiles[:index], node.transferInfo.Receiving.AcceptedFiles[index+1:]...) - // compare checksums - realChecksum, err := checksum.GetPartialCheckSum(acceptedFile.Handler) - if err != nil { - panic(err) - } + // compare checksums + realChecksum, err := checksum.GetPartialCheckSum(acceptedFile.Handler) + if err != nil { + panic(err) + } - if realChecksum != acceptedFile.Checksum { - fmt.Printf("\n| \"%s\" is corrupted", acceptedFile.Name) - acceptedFile.Close() - break - } else { - acceptedFile.Close() - break - } + if realChecksum != acceptedFile.Checksum { + fmt.Printf("\n| \"%s\" is corrupted", acceptedFile.Name) + acceptedFile.Close() + break + } else { + acceptedFile.Close() + break } } + } - err = protocol.SendPacket(node.netInfo.Conn, protocol.Packet{ - Header: protocol.HeaderReady, - }) - if err != nil { - panic(err) - } + err = protocol.SendPacket(node.netInfo.Conn, protocol.Packet{ + Header: protocol.HeaderReady, + }) + if err != nil { + panic(err) + } - case protocol.HeaderEncryptionKey: - // retrieve the key - packetReader := bytes.NewReader(incomingPacket.Body) + case protocol.HeaderEncryptionKey: + // retrieve the key + packetReader := bytes.NewReader(incomingPacket.Body) - var keySize uint64 - binary.Read(packetReader, binary.BigEndian, &keySize) + var keySize uint64 + binary.Read(packetReader, binary.BigEndian, &keySize) - encrKey := make([]byte, keySize) - packetReader.Read(encrKey) + encrKey := make([]byte, keySize) + packetReader.Read(encrKey) - node.netInfo.EncryptionKey = encrKey + node.netInfo.EncryptionKey = encrKey - fmt.Printf("\nGot an encryption key: %s", encrKey) + fmt.Printf("\nGot an encryption key: %s", encrKey) - case protocol.HeaderDone: - node.mutex.Lock() - node.state.Stopped = true - node.mutex.Unlock() + case protocol.HeaderDone: + node.mutex.Lock() + node.stopped = true + node.mutex.Unlock() - case protocol.HeaderDisconnecting: - node.mutex.Lock() - node.state.Stopped = true - node.mutex.Unlock() + case protocol.HeaderDisconnecting: + node.mutex.Lock() + node.stopped = true + node.mutex.Unlock() - fmt.Printf("\n%s disconnected", node.netInfo.Conn.RemoteAddr()) - } + fmt.Printf("\n%s disconnected", node.netInfo.Conn.RemoteAddr()) + } - if !node.verboseOutput { - go node.printTransferInfo(time.Second) - } + if !node.verboseOutput && node.transferInfo.Receiving.ReceivedBytes != 0 { + go node.printTransferInfo(time.Second) } } } + +// Starts the node in either sending or receiving state and performs the transfer +func (node *Node) Start() { + switch node.isSending { + case true: + node.send() + case false: + node.receive() + } +} diff --git a/src/protocol/headers.go b/src/protocol/headers.go index 8f98483..cf4a27a 100644 --- a/src/protocol/headers.go +++ b/src/protocol/headers.go @@ -29,7 +29,7 @@ type Header string //// and (size) is 8 bytes long big-endian binary encoded uint64 // ENCRKEY. -// The FIRST header to be sent. Sent immediately after the connection has been established +// The FIRST header to be sent if you`re going to encrypt the transfer. Sent immediately after the connection has been established // by sender. Body contains a size of a key and the key itself. // ie: ENCRKEY~(size)(encryption key) const HeaderEncryptionKey Header = "ENCRKEY" @@ -41,7 +41,7 @@ const HeaderReject Header = "REJECT" // ACCEPT. // The opposite of the previous REJECT. Sent by receiver when -// he has agreed to download the file|directory. +// it has agreed to download the file|directory. // ie: ACCEPT~ const HeaderAccept Header = "ACCEPT" @@ -55,19 +55,11 @@ const HeaderDone Header = "DONE" // READY. // Sent by receiver when it has read and processed the last -// FILEBYTES packet or when it has information about all the files and it`s ready to -// receive bytes (FILESINFODONE). The sender is not allowed to "spam" FILEBYTES -// packets without the permission of receiver. +// FILEBYTES or FILE packet. The sender is not allowed to "spam" FILEBYTES or FILE +// packets without the permission (packet with this header) from receiver. // ie: READY!~ const HeaderReady Header = "READY" -// FILESINFODONE. -// Sent by sender after it`s announced about all the files that are -// going to be sent. It is not allowed to send any file bytes before -// packet with this header was sent. -// ie: FILESINFODONE~ -const HeaderFilesInfoDone Header = "FILESINFODONE" - // BYE!. // Packet with this header can be sent both by receiver and sender. // It`s used when the sender or the receiver are going to disconnect diff --git a/src/protocol/send.go b/src/protocol/send.go index 07d9c2d..2f7b711 100644 --- a/src/protocol/send.go +++ b/src/protocol/send.go @@ -137,13 +137,15 @@ func SendTransferOffer(connection net.Conn, file *fsys.File, dir *fsys.Directory var ErrorSentAll error = fmt.Errorf("sent the whole file") -// sends a piece of file to the connection; The next calls will send +// Sends a piece of file to the connection; The next calls will send // another piece util the file has been fully sent. If encrKey is not nil - encrypts each packet with -// this key -func SendPiece(file *fsys.File, connection net.Conn, encrKey []byte) error { +// this key. Returns amount of filebytes written to the connection +func SendPiece(file *fsys.File, connection net.Conn, encrKey []byte) (uint64, error) { + var sentBytes uint64 = 0 + err := file.Open() if err != nil { - return err + return sentBytes, err } defer file.Close() @@ -152,7 +154,7 @@ func SendPiece(file *fsys.File, connection net.Conn, encrKey []byte) error { } if file.Size == file.SentBytes { - return ErrorSentAll + return sentBytes, ErrorSentAll } fileBytesPacket := Packet{ @@ -164,7 +166,7 @@ func SendPiece(file *fsys.File, connection net.Conn, encrKey []byte) error { // write file ID first err = binary.Write(packetBodyBuff, binary.BigEndian, file.ID) if err != nil { - return err + return sentBytes, err } // fill the remaining space of packet with the contents of a file @@ -183,9 +185,10 @@ func SendPiece(file *fsys.File, connection net.Conn, encrKey []byte) error { read, err := file.Handler.ReadAt(fileBytes, int64(file.SentBytes)) if err != nil { - return err + return sentBytes, err } file.SentBytes += uint64(read) + sentBytes += uint64(canSendBytes) packetBodyBuff.Write(fileBytes) @@ -194,15 +197,15 @@ func SendPiece(file *fsys.File, connection net.Conn, encrKey []byte) error { if encrKey != nil { err = fileBytesPacket.EncryptBody(encrKey) if err != nil { - return err + return sentBytes, err } } // send it to the other side err = SendPacket(connection, fileBytesPacket) if err != nil { - return err + return 0, err } - return nil + return sentBytes, nil }