diff --git a/src/node/node.go b/src/node/node.go index cf666bb..0ac19e8 100644 --- a/src/node/node.go +++ b/src/node/node.go @@ -19,57 +19,90 @@ import ( ) // node-controlling states -type NodeInnerStates struct { +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 } -// Network specific settings -type Net struct { +// netInfowork specific settings +type netInfoInfo struct { ConnAddr string // address to connect to. Does not include port Conn net.Conn // the core TCP connection of the node. Self-explanatory Port uint // a port to connect to/listen on EncryptionKey []byte // if != nil - incoming packets will be decrypted with it and outcoming packets will be encrypted } -// Both sending-side and receiving-side information -type TransferInfo struct { - Ready bool // is the other node ready to receive another piece - ServingPath string // path to the thing that will be sent - Recursive bool // recursively send directory +// 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 +} + +// Receiving-side node information +type receiving struct { AcceptedFiles []*fsys.File // files that`ve been accepted to be received DownloadsPath string // where to download } +// Both sending-side and receiving-side information +type transferInfo struct { + Receiving *receiving + Sending *sending +} + // Sender and receiver in one type ! type Node struct { - PacketPipe chan *protocol.Packet // a way to receive incoming packets from another goroutine - IsSending bool // sending or a receiving node - Net *Net - State *NodeInnerStates - TransferInfo *TransferInfo + packetPipe chan *protocol.Packet // a way to receive incoming packets from another goroutine + isSending bool // sending or a receiving node + netInfo *netInfoInfo + state *nodeInnerstates + transferInfo *transferInfo } // Creates a new either a sending or receiving node with specified options func NewNode(options *NodeOptions) (*Node, error) { + + var isDir bool + if options.IsSending { + sendingPathStats, err := os.Stat(options.ServerSide.ServingPath) + if err != nil { + return nil, err + } + + switch sendingPathStats.IsDir() { + case true: + isDir = true + + case false: + isDir = false + } + } + node := Node{ - PacketPipe: make(chan *protocol.Packet, 100), - IsSending: options.IsSending, - Net: &Net{ + packetPipe: make(chan *protocol.Packet, 100), + isSending: options.IsSending, + netInfo: &netInfoInfo{ Port: options.WorkingPort, ConnAddr: options.ClientSide.ConnectionAddr, EncryptionKey: nil, Conn: nil, }, - State: &NodeInnerStates{ + state: &nodeInnerstates{ AllowedToTransfer: false, Stopped: false, }, - TransferInfo: &TransferInfo{ - ServingPath: options.ServerSide.ServingPath, - Recursive: options.ServerSide.Recursive, - AcceptedFiles: nil, - DownloadsPath: options.ClientSide.DownloadsFolderPath, + transferInfo: &transferInfo{ + Sending: &sending{ + ServingPath: options.ServerSide.ServingPath, + Recursive: options.ServerSide.Recursive, + IsDirectory: isDir, + }, + Receiving: &receiving{ + AcceptedFiles: nil, + DownloadsPath: options.ClientSide.DownloadsFolderPath, + }, }, } return &node, nil @@ -77,41 +110,41 @@ func NewNode(options *NodeOptions) (*Node, error) { // Connect node to another listening one with a pre-defined address&&port func (node *Node) connect() error { - if node.Net.Port == 0 { - node.Net.Port = 7270 + if node.netInfo.Port == 0 { + node.netInfo.Port = 7270 } - fmt.Printf("Connecting to %s:%d...\n", node.Net.ConnAddr, node.Net.Port) + fmt.Printf("Connecting to %s:%d...\n", node.netInfo.ConnAddr, node.netInfo.Port) - conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", node.Net.ConnAddr, node.Net.Port), time.Second*5) + conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", node.netInfo.ConnAddr, node.netInfo.Port), time.Second*5) if err != nil { return err } fmt.Printf("Connected\n") - node.Net.Conn = conn + node.netInfo.Conn = conn return nil } // Notify the other node and close the connection func (node *Node) disconnect() error { - if node.Net.Conn != nil { + if node.netInfo.Conn != nil { // notify the other node and close the connection - err := protocol.SendPacket(node.Net.Conn, protocol.Packet{ + err := protocol.SendPacket(node.netInfo.Conn, protocol.Packet{ Header: protocol.HeaderDisconnecting, }) if err != nil { return err } - err = node.Net.Conn.Close() + err = node.netInfo.Conn.Close() if err != nil { return err } - node.State.Stopped = true + node.state.Stopped = true } return nil @@ -119,7 +152,7 @@ func (node *Node) disconnect() error { // Wait for a connection on a pre-defined port func (node *Node) waitForConnection() error { - listener, err := net.Listen("tcp", fmt.Sprintf(":%d", node.Net.Port)) + listener, err := net.Listen("tcp", fmt.Sprintf(":%d", node.netInfo.Port)) if err != nil { return err } @@ -132,14 +165,14 @@ func (node *Node) waitForConnection() error { fmt.Printf("New connection from %s\n", connection.RemoteAddr().String()) - node.Net.Conn = connection + node.netInfo.Conn = connection return nil } // Starts the node in either sending or receiving state and performs the transfer func (node *Node) Start() { - switch node.IsSending { + switch node.isSending { case true: // SENDER @@ -149,11 +182,22 @@ func (node *Node) Start() { panic(err) } - file, err := fsys.GetFile(node.TransferInfo.ServingPath) - if err != nil { - panic(err) + var file *fsys.File + var dir *fsys.Directory + switch node.transferInfo.Sending.IsDirectory { + case true: + dir, err = fsys.GetDir(node.transferInfo.Sending.ServingPath, node.transferInfo.Sending.Recursive) + if err != nil { + panic(err) + } + case false: + file, err = fsys.GetFile(node.transferInfo.Sending.ServingPath) + if err != nil { + panic(err) + } } - fmt.Printf("Sending \"%s\" (%.2f MB) locally on %s:%d\n", file.Name, float32(file.Size)/1024/1024, localIP, node.Net.Port) + + fmt.Printf("Sending \"%s\" (%.2f MB) locally on %s:%d\n", file.Name, float32(file.Size)/1024/1024, localIP, node.netInfo.Port) // wain for another node to connect err = node.waitForConnection() @@ -163,35 +207,39 @@ func (node *Node) Start() { // generate and send encryption key encrKey := encryption.Generate32AESkey() - node.Net.EncryptionKey = encrKey + node.netInfo.EncryptionKey = encrKey fmt.Printf("Generated encryption key: %s\n", encrKey) - err = sendEncryptionKey(node.Net.Conn, encrKey) + err = sendEncryptionKey(node.netInfo.Conn, encrKey) if err != nil { panic(err) } // listen for incoming packets - go receivePackets(node.Net.Conn, node.PacketPipe) + go receivePackets(node.netInfo.Conn, node.packetPipe) // send info on file/directory - go sendFilePacket(node.Net.Conn, file, node.Net.EncryptionKey) + if dir != nil { + go sendDirectoryPacket(node.netInfo.Conn, dir, node.netInfo.EncryptionKey) + } else { + go sendFilePacket(node.netInfo.Conn, file, node.netInfo.EncryptionKey) + } // mainloop for { - if node.State.Stopped { + if node.state.Stopped { node.disconnect() break } // receive incoming packets and decrypt them if necessary - incomingPacket, ok := <-node.PacketPipe + incomingPacket, ok := <-node.packetPipe if !ok { - node.State.Stopped = true + node.state.Stopped = true } - if node.Net.EncryptionKey != nil { - err = incomingPacket.DecryptBody(node.Net.EncryptionKey) + if node.netInfo.EncryptionKey != nil { + err = incomingPacket.DecryptBody(node.netInfo.EncryptionKey) if err != nil { panic(err) } @@ -201,69 +249,83 @@ func (node *Node) Start() { switch incomingPacket.Header { case protocol.HeaderReady: // the other node is ready to receive file data - node.TransferInfo.Ready = true + node.transferInfo.Sending.CanSendBytes = true case protocol.HeaderAccept: - node.State.AllowedToTransfer = true + node.state.AllowedToTransfer = true fmt.Printf("Transfer allowed. Sending...\n") case protocol.HeaderReject: - node.State.Stopped = true + node.state.Stopped = true fmt.Printf("Transfer rejected. Disconnecting...") case protocol.HeaderDisconnecting: - node.State.Stopped = true + node.state.Stopped = true - fmt.Printf("%s disconnected\n", node.Net.Conn.RemoteAddr()) + fmt.Printf("%s disconnected\n", node.netInfo.Conn.RemoteAddr()) } // 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.Ready { - err = sendPiece(file, node.Net.Conn, node.Net.EncryptionKey) - switch err { - case ErrorSentAll: - // the file has been sent fully - fileIDBuff := new(bytes.Buffer) - err = binary.Write(fileIDBuff, binary.BigEndian, file.ID) - if err != nil { - panic(err) - } + if node.state.AllowedToTransfer && node.transferInfo.Sending.CanSendBytes { + // handle a single file or a directory + switch node.transferInfo.Sending.IsDirectory { + case true: + // sending a file in a directory (possibly more than 1) + if !node.transferInfo.Sending.Recursive { + + } else { - endFilePacket := protocol.Packet{ - Header: protocol.HeaderEndfile, - Body: fileIDBuff.Bytes(), } - if node.Net.EncryptionKey != nil { - err = endFilePacket.EncryptBody(node.Net.EncryptionKey) + case false: + // sending a single file + err = sendPiece(file, node.netInfo.Conn, node.netInfo.EncryptionKey) + switch err { + case ErrorSentAll: + // the file has been sent fully + fileIDBuff := new(bytes.Buffer) + err = binary.Write(fileIDBuff, binary.BigEndian, file.ID) if err != nil { panic(err) } - } - protocol.SendPacket(node.Net.Conn, endFilePacket) + endFilePacket := protocol.Packet{ + Header: protocol.HeaderEndfile, + Body: fileIDBuff.Bytes(), + } - // because there`s still no handling for directories - send - // done packet - protocol.SendPacket(node.Net.Conn, protocol.Packet{ - Header: protocol.HeaderDone, - }) + if node.netInfo.EncryptionKey != nil { + err = endFilePacket.EncryptBody(node.netInfo.EncryptionKey) + if err != nil { + panic(err) + } + } - node.State.Stopped = true + protocol.SendPacket(node.netInfo.Conn, endFilePacket) - case nil: + // because there`s still no handling for directories - send + // done packet + protocol.SendPacket(node.netInfo.Conn, protocol.Packet{ + Header: protocol.HeaderDone, + }) - default: - node.State.Stopped = true + node.state.Stopped = true - fmt.Printf("An error occured while sending a piece of \"%s\": %s\n", file.Name, err) - panic(err) + case nil: + + default: + node.state.Stopped = true + + fmt.Printf("An error occured while sending a piece of \"%s\": %s\n", file.Name, err) + panic(err) + } + + node.transferInfo.Sending.CanSendBytes = false } - node.TransferInfo.Ready = false } } @@ -273,27 +335,27 @@ func (node *Node) Start() { // connect to the sending node err := node.connect() if err != nil { - fmt.Printf("Could not connect to %s:%d\n", node.Net.ConnAddr, node.Net.Port) + fmt.Printf("Could not connect to %s:%d\n", node.netInfo.ConnAddr, node.netInfo.Port) os.Exit(-1) } // listen for incoming packets - go receivePackets(node.Net.Conn, node.PacketPipe) + go receivePackets(node.netInfo.Conn, node.packetPipe) // mainloop for { - if node.State.Stopped { + if node.state.Stopped { node.disconnect() break } // receive incoming packets and decrypt them if necessary - incomingPacket, ok := <-node.PacketPipe + incomingPacket, ok := <-node.packetPipe if !ok { break } - if node.Net.EncryptionKey != nil { - err = incomingPacket.DecryptBody(node.Net.EncryptionKey) + if node.netInfo.EncryptionKey != nil { + err = incomingPacket.DecryptBody(node.netInfo.EncryptionKey) if err != nil { panic(err) } @@ -322,12 +384,12 @@ func (node *Node) Start() { if strings.EqualFold(answer, "y") || answer == "" { // yes - err = os.MkdirAll(node.TransferInfo.DownloadsPath, os.ModePerm) + err = os.MkdirAll(node.transferInfo.Receiving.DownloadsPath, os.ModePerm) if err != nil { panic(err) } - fullFilePath := filepath.Join(node.TransferInfo.DownloadsPath, file.Name) + fullFilePath := filepath.Join(node.transferInfo.Receiving.DownloadsPath, file.Name) // check if the file already exists; if yes - remove it and replace with a new one _, err := os.Stat(fullFilePath) @@ -340,28 +402,28 @@ func (node *Node) Start() { file.Path = fullFilePath file.Open() - node.TransferInfo.AcceptedFiles = append(node.TransferInfo.AcceptedFiles, file) + node.transferInfo.Receiving.AcceptedFiles = append(node.transferInfo.Receiving.AcceptedFiles, file) // send aceptance packet acceptancePacket := protocol.Packet{ Header: protocol.HeaderAccept, Body: responsePacketFileIDBuffer.Bytes(), } - if node.Net.EncryptionKey != nil { - err = acceptancePacket.EncryptBody(node.Net.EncryptionKey) + if node.netInfo.EncryptionKey != nil { + err = acceptancePacket.EncryptBody(node.netInfo.EncryptionKey) if err != nil { panic(err) } } - err = protocol.SendPacket(node.Net.Conn, acceptancePacket) + err = protocol.SendPacket(node.netInfo.Conn, acceptancePacket) if err != nil { panic(err) } // notify the node that we`re ready to transportation. No need // for encryption because the body is nil - err = protocol.SendPacket(node.Net.Conn, protocol.Packet{ + err = protocol.SendPacket(node.netInfo.Conn, protocol.Packet{ Header: protocol.HeaderReady, }) if err != nil { @@ -375,19 +437,19 @@ func (node *Node) Start() { Body: responsePacketFileIDBuffer.Bytes(), } - if node.Net.EncryptionKey != nil { - err = rejectionPacket.EncryptBody(node.Net.EncryptionKey) + if node.netInfo.EncryptionKey != nil { + err = rejectionPacket.EncryptBody(node.netInfo.EncryptionKey) if err != nil { panic(err) } } - err = protocol.SendPacket(node.Net.Conn, rejectionPacket) + err = protocol.SendPacket(node.netInfo.Conn, rejectionPacket) if err != nil { panic(err) } - node.State.Stopped = true + node.state.Stopped = true } }() @@ -400,7 +462,7 @@ func (node *Node) Start() { panic(err) } - for _, acceptedFile := range node.TransferInfo.AcceptedFiles { + for _, acceptedFile := range node.transferInfo.Receiving.AcceptedFiles { if acceptedFile.ID == fileID { // accepted @@ -415,7 +477,7 @@ func (node *Node) Start() { } // notify the other one that this node is ready - err = protocol.SendPacket(node.Net.Conn, protocol.Packet{ + err = protocol.SendPacket(node.netInfo.Conn, protocol.Packet{ Header: protocol.HeaderReady, }) if err != nil { @@ -434,7 +496,7 @@ func (node *Node) Start() { panic(err) } - for index, acceptedFile := range node.TransferInfo.AcceptedFiles { + for index, acceptedFile := range node.transferInfo.Receiving.AcceptedFiles { if acceptedFile.ID == fileID { // accepted @@ -442,7 +504,7 @@ func (node *Node) Start() { defer acceptedFile.Handler.Close() // remove this file from the pool - node.TransferInfo.AcceptedFiles = append(node.TransferInfo.AcceptedFiles[:index], node.TransferInfo.AcceptedFiles[index+1:]...) + node.transferInfo.Receiving.AcceptedFiles = append(node.transferInfo.Receiving.AcceptedFiles[:index], node.transferInfo.Receiving.AcceptedFiles[index+1:]...) // compare checksums realChecksum, err := checksum.GetPartialCheckSum(acceptedFile.Handler) @@ -461,7 +523,7 @@ func (node *Node) Start() { } } - // node.State.Stopped = true + // node.state.Stopped = true case protocol.HeaderEncryptionKey: // retrieve the key @@ -473,15 +535,15 @@ func (node *Node) Start() { encrKey := make([]byte, keySize) packetReader.Read(encrKey) - node.Net.EncryptionKey = encrKey + node.netInfo.EncryptionKey = encrKey case protocol.HeaderDone: - node.State.Stopped = true + node.state.Stopped = true case protocol.HeaderDisconnecting: - node.State.Stopped = true + node.state.Stopped = true - fmt.Printf("%s disconnected\n", node.Net.Conn.RemoteAddr()) + fmt.Printf("%s disconnected\n", node.netInfo.Conn.RemoteAddr()) } } diff --git a/src/node/transfer.go b/src/node/transfer.go index 0a07cc3..78ec8a1 100644 --- a/src/node/transfer.go +++ b/src/node/transfer.go @@ -24,7 +24,7 @@ func sendFilePacket(connection net.Conn, file *fsys.File, encrKey []byte) error return err } - // FILE~(idInBinary)(filenameLengthInBinary)(filename)(filesize)(checksumLengthInBinary)checksum + // FILE~(idInBinary)(filenameLengthInBinary)(filename)(filesize)(checksumLengthInBinary)(checksum) // send file packet with file description filePacket := protocol.Packet{ @@ -69,14 +69,22 @@ func sendFilePacket(connection net.Conn, file *fsys.File, encrKey []byte) error return err } + // we do not check for packet size because there is no way that it`ll exceed current + // maximum of 128 KiB return nil } -// sends a notification about the directory -func sendDirectoryPacket(connection net.Conn, dir *fsys.Directory) error { +// sends a notification about the directory. If encrkey != nil - encrypts +// packet`s body +func sendDirectoryPacket(connection net.Conn, dir *fsys.Directory, encrKey []byte) error { if connection == nil { return ErrorNotConnected } + + dirPacket := protocol.Packet{ + Header: protocol.HeaderDirectory, + } + // DIRECTORY~(dirname size in binary)(dirname)(dirsize)(checksumLengthInBinary)(checksum) dirPacketBuffer := new(bytes.Buffer) @@ -103,6 +111,24 @@ func sendDirectoryPacket(connection net.Conn, dir *fsys.Directory) error { } dirPacketBuffer.Write([]byte(dir.Checksum)) + dirPacket.Body = dirPacketBuffer.Bytes() + + if encrKey != nil { + // if the key is given - encrypt ready-to-go packet + err = dirPacket.EncryptBody(encrKey) + if err != nil { + return err + } + } + + // and send it + err = protocol.SendPacket(connection, dirPacket) + if err != nil { + return err + } + + // we do not check for packet size because there is no way that it`ll exceed current + // maximum of 128 KiB return nil }