Compare commits

...

16 Commits
2.1.2 ... main

Author SHA1 Message Date
Kasianov Nikolai Alekseevich 1d8f987c27 Removed old email from license notices; minor changes to the program output, help message 2 years ago
Gitea b30d0c3465 No more github 2 years ago
Gitea 6bb927a26e Being idependent from github 2 years ago
Unbewohnte c2bd2cb0cf Apparently my name is spelled differently in English ¯\_(ツ)_/¯ 3 years ago
Unbewohnte 2548fa68e7 Less syscalls to open files on the receiving end 3 years ago
Unbewohnte 8eb2f181fd Symlinks support ! 3 years ago
Unbewohnte 700811b179 [checksum] Micro optimisations; [node] Renamed nodes to receiving and sending; [fsys] ftu now DOES NOT die on 'permission denied's 3 years ago
Unbewohnte 21d16c590f Updated README: I can't be inspired by a thing I've never used, can I ?... Tehehe~ 3 years ago
Unbewohnte 4d12a15a58 [Node] Invalid transfer info printing is >>|FIXED|<< 3 years ago
Unbewohnte 382908c1e5 Updated copyright year 3 years ago
Unbewohnte e4c086b358 make release 3 years ago
Unbewohnte 0ac9cd2b71 v2.2.0; Fixed freezing on big amount of files; Improved transfer data printing; Introduction to some minor bugs with it as well, but that is not a severe problem anyway 3 years ago
Unbewohnte 75cb68dd30 [node] fixed one-file transfer logical issue 3 years ago
Unbewohnte 550c6e26ef [main] changed version 3 years ago
Unbewohnte 74464c9470 [main] verbose output flag changed to ?; [node] added 3 microsecond delay in sender node when sending file info packets 3 years ago
Unbewohnte a8e973645a [main] added a missing new line for verbose output usage message 3 years ago
  1. 34
      Makefile
  2. 13
      README.md
  3. 2
      src/addr/local.go
  4. 16
      src/checksum/checksum.go
  5. 2
      src/checksum/checksum_test.go
  6. 2
      src/encryption/decrypt.go
  7. 2
      src/encryption/encrypt.go
  8. 2
      src/encryption/encryption_test.go
  9. 2
      src/encryption/key.go
  10. 67
      src/fsys/dir.go
  11. 38
      src/fsys/dir_test.go
  12. 6
      src/fsys/file.go
  13. 2
      src/fsys/file_test.go
  14. 70
      src/fsys/symlink.go
  15. 42
      src/fsys/symlink_test.go
  16. 4
      src/go.mod
  17. 30
      src/main.go
  18. 393
      src/node/node.go
  19. 10
      src/node/options.go
  20. 2
      src/protocol/constants.go
  21. 25
      src/protocol/headers.go
  22. 4
      src/protocol/packet.go
  23. 4
      src/protocol/packetConstruct.go
  24. 4
      src/protocol/packetDecode.go
  25. 2
      src/protocol/protocol_test.go
  26. 2
      src/protocol/recv.go
  27. 60
      src/protocol/send.go
  28. 1
      src/testfiles/testdir/testsymlink2.txt
  29. 1
      src/testfiles/testsymlink.txt

34
Makefile

@ -10,31 +10,31 @@ INSTALLATION_SCRIPT := install.sh
all: all:
cd $(SRC_DIR) && go build && mv $(EXE_NAME) .. cd $(SRC_DIR) && go build && mv $(EXE_NAME) ..
pkgrelease: release:
rm -rf $(RELEASE_DIR) rm -rf $(RELEASE_DIR)
mkdir $(RELEASE_DIR) mkdir $(RELEASE_DIR)
mkdir $(RELEASE_DIR)/linux_amd64 mkdir $(RELEASE_DIR)/ftu_linux_amd64
mkdir $(RELEASE_DIR)/darwin_amd64 mkdir $(RELEASE_DIR)/ftu_darwin_amd64
mkdir $(RELEASE_DIR)/windows_amd64 mkdir $(RELEASE_DIR)/ftu_windows_amd64
cd $(SRC_DIR) && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ftu && mv ftu ../$(RELEASE_DIR)/linux_amd64 cd $(SRC_DIR) && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ftu && mv ftu ../$(RELEASE_DIR)/ftu_linux_amd64
cd $(SRC_DIR) && CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o ftu && mv ftu ../$(RELEASE_DIR)/darwin_amd64 cd $(SRC_DIR) && CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o ftu && mv ftu ../$(RELEASE_DIR)/ftu_darwin_amd64
cd $(SRC_DIR) && CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o ftu.exe && mv ftu.exe ../$(RELEASE_DIR)/windows_amd64 cd $(SRC_DIR) && CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o ftu.exe && mv ftu.exe ../$(RELEASE_DIR)/ftu_windows_amd64
cp $(LICENSE_FILE) $(RELEASE_DIR)/linux_amd64 cp $(LICENSE_FILE) $(RELEASE_DIR)/ftu_linux_amd64
cp $(INSTALLATION_SCRIPT) $(RELEASE_DIR)/linux_amd64 cp $(INSTALLATION_SCRIPT) $(RELEASE_DIR)/ftu_linux_amd64
cp $(LICENSE_FILE) $(RELEASE_DIR)/darwin_amd64 cp $(LICENSE_FILE) $(RELEASE_DIR)/ftu_darwin_amd64
cp $(LICENSE_FILE) $(RELEASE_DIR)/windows_amd64 cp $(LICENSE_FILE) $(RELEASE_DIR)/ftu_windows_amd64
cd $(RELEASE_DIR) && zip -r linux_amd64 linux_amd64/ cd $(RELEASE_DIR) && zip -r ftu_linux_amd64 ftu_linux_amd64/
cd $(RELEASE_DIR) && zip -r darwin_amd64 darwin_amd64/ cd $(RELEASE_DIR) && zip -r ftu_darwin_amd64 ftu_darwin_amd64/
cd $(RELEASE_DIR) && zip -r windows_amd64 windows_amd64/ cd $(RELEASE_DIR) && zip -r ftu_windows_amd64 ftu_windows_amd64/
rm -rf $(RELEASE_DIR)/linux_amd64 rm -rf $(RELEASE_DIR)/ftu_linux_amd64
rm -rf $(RELEASE_DIR)/darwin_amd64 rm -rf $(RELEASE_DIR)/ftu_darwin_amd64
rm -rf $(RELEASE_DIR)/windows_amd64 rm -rf $(RELEASE_DIR)/ftu_windows_amd64
race: race:
cd $(SRC_DIR) && go build -race && mv $(EXE_NAME) .. cd $(SRC_DIR) && go build -race && mv $(EXE_NAME) ..

13
README.md

@ -16,7 +16,7 @@ In order to transfer one file on one computer to another - they need to establis
In order to establish a connection - there needs to be a 1) sender (server) (the owner of the file), waiting for connections, and a 2) receiver (client), who will try to connect to a sender (server). If the requirements are met - client will connect to server and the packet exchange will begin. In order to establish a connection - there needs to be a 1) sender (server) (the owner of the file), waiting for connections, and a 2) receiver (client), who will try to connect to a sender (server). If the requirements are met - client will connect to server and the packet exchange will begin.
The server and the client needs to communicate with packets according to certain rules, given by a [protocol](https://github.com/Unbewohnte/ftu/tree/main/src/protocol). The server and the client needs to communicate with packets according to certain rules, given by a [protocol](http://unbewohnte.xyz:3000/Unbewohnte/ftu/src/branch/main/src/protocol).
The packet has its header and body. They are divided into several groups of use by headers, this way we can specify what kind of data is stored inside packet`s body and react accordingly. The packet has its header and body. They are divided into several groups of use by headers, this way we can specify what kind of data is stored inside packet`s body and react accordingly.
@ -28,13 +28,13 @@ Thus, with a connection and a way of communication, the sender will send some pa
## ● Installation ## ● Installation
### ● From release (Pre-compiled) ### ● From release (Pre-compiled)
- Proceed to [releases page](https://github.com/Unbewohnte/ftu/releases) - Proceed to [releases page](http://unbewohnte.xyz:3000/Unbewohnte/ftu/releases)
- Choose a version/architecture you have and download an archive - Choose a version/architecture you have and download an archive
- Unpack an archive - Unpack an archive
- If on GNU/Linux - run `chmod +x install.sh && sudo ./install.sh` - If on GNU/Linux - run `chmod +x install.sh && sudo ./install.sh`
### ● From source (Compile it yourself) (You need [Go](https://golang.org/dl/) and [git](https://git-scm.com/) to be installed on your machine) ### ● From source (Compile it yourself) (You need [Go](https://golang.org/dl/) and [git](https://git-scm.com/) to be installed on your machine)
- `git clone https://github.com/Unbewohnte/ftu.git` - `git clone http://unbewohnte.xyz:3000/Unbewohnte/ftu`
- `cd` into the folder - `cd` into the folder
- If on GNU/Linux - run `make && sudo make install` or `make && chmod +x install.sh && sudo ./install` - If on GNU/Linux - run `make && sudo make install` or `make && chmod +x install.sh && sudo ./install`
- else - cd into src/ folder and simply run `go build`; after that you`re free to put the binary wherever you desire - else - cd into src/ folder and simply run `go build`; after that you`re free to put the binary wherever you desire
@ -46,7 +46,7 @@ Now you have ftu installed !
## ● Usage ## ● Usage
`ftu -h` - to print a usage message `ftu -h` - to print a usage message
`ftu [FLAGS]` `ftu [FLAGs]`
### ● FLAGs ### ● FLAGs
- -p [uint] for port - -p [uint] for port
@ -54,6 +54,7 @@ Now you have ftu installed !
- -a [ip_address|domain_name] address to connect to (cannot be used with -s) - -a [ip_address|domain_name] address to connect to (cannot be used with -s)
- -d [path_to_directory] where the files will be downloaded to (cannot be used with -s) - -d [path_to_directory] where the files will be downloaded to (cannot be used with -s)
- -s [path_to_file|directory] to send it (cannot be used with -a) - -s [path_to_file|directory] to send it (cannot be used with -a)
- -? [true|false] to turn on|off verbose output
- -v print version text - -v print version text
- -l print license - -l print license
@ -82,9 +83,5 @@ creates a node that will send every file in the directory !RECUSRIVELY!
--- ---
## ● Inspired by [croc](https://github.com/schollz/croc)
---
## ● License ## ● License
GPLv3 license GPLv3 license

2
src/addr/local.go

@ -1,6 +1,6 @@
/* /*
ftu - file transferring utility. ftu - file transferring utility.
Copyright (C) 2021 Kasyanov Nikolay Alexeevich (Unbewohnte (https://unbewohnte.xyz/)) Copyright (C) 2021,2022 Kasyanov Nikolay Alexeyevich (Unbewohnte)
This file is a part of ftu This file is a part of ftu

16
src/checksum/checksum.go

@ -1,6 +1,6 @@
/* /*
ftu - file transferring utility. ftu - file transferring utility.
Copyright (C) 2021 Kasyanov Nikolay Alexeevich (Unbewohnte (https://unbewohnte.xyz/)) Copyright (C) 2021,2022 Kasyanov Nikolay Alexeyevich (Unbewohnte)
This file is a part of ftu This file is a part of ftu
@ -21,6 +21,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
package checksum package checksum
import ( import (
"bytes"
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"io" "io"
@ -36,8 +37,8 @@ import (
// GetPartialCheckSum is default method used to get a file checksum by sender and receiver // GetPartialCheckSum is default method used to get a file checksum by sender and receiver
func GetPartialCheckSum(file *os.File) (string, error) { func GetPartialCheckSum(file *os.File) (string, error) {
// "capturing" CHUNKSIZE bytes and then skipping STEP bytes before the next chunk until the last one // "capturing" CHUNKSIZE bytes and then skipping STEP bytes before the next chunk until the last one
const CHUNKS uint = 100 const CHUNKS uint = 50
const CHUNKSIZE uint = 100 const CHUNKSIZE uint = 50
const STEP uint = 250 const STEP uint = 250
fileStats, err := file.Stat() fileStats, err := file.Stat()
@ -48,7 +49,7 @@ func GetPartialCheckSum(file *os.File) (string, error) {
fileSize := fileStats.Size() fileSize := fileStats.Size()
if fileSize < int64(CHUNKS*CHUNKSIZE+STEP*(CHUNKS-1)) { if fileSize < int64(CHUNKS*CHUNKSIZE+STEP*(CHUNKS-1)) {
// file is too small to chop it in chunks, so just doing full checksum // file is too small to chop it in chunks, so just get the full checksum
checksum, err := getFullCheckSum(file) checksum, err := getFullCheckSum(file)
if err != nil { if err != nil {
@ -62,19 +63,20 @@ func GetPartialCheckSum(file *os.File) (string, error) {
return "", err return "", err
} }
var capturedChunks string // var capturedChunks string
var capturedChunks bytes.Buffer
var read uint64 = 0 var read uint64 = 0
for i := 0; uint(i) < CHUNKS; i++ { for i := 0; uint(i) < CHUNKS; i++ {
buffer := make([]byte, CHUNKSIZE) buffer := make([]byte, CHUNKSIZE)
r, _ := file.ReadAt(buffer, int64(read)) r, _ := file.ReadAt(buffer, int64(read))
capturedChunks += string(buffer) capturedChunks.Write(buffer)
read += uint64(r) read += uint64(r)
read += uint64(STEP) read += uint64(STEP)
} }
checksumBytes := sha256.Sum256([]byte(capturedChunks)) checksumBytes := sha256.Sum256(capturedChunks.Bytes())
checksum := hex.EncodeToString(checksumBytes[:]) checksum := hex.EncodeToString(checksumBytes[:])
return checksum, nil return checksum, nil

2
src/checksum/checksum_test.go

@ -1,6 +1,6 @@
/* /*
ftu - file transferring utility. ftu - file transferring utility.
Copyright (C) 2021 Kasyanov Nikolay Alexeevich (Unbewohnte (https://unbewohnte.xyz/)) Copyright (C) 2021,2022 Kasyanov Nikolay Alexeyevich (Unbewohnte)
This file is a part of ftu This file is a part of ftu

2
src/encryption/decrypt.go

@ -1,6 +1,6 @@
/* /*
ftu - file transferring utility. ftu - file transferring utility.
Copyright (C) 2021 Kasyanov Nikolay Alexeevich (Unbewohnte (https://unbewohnte.xyz/)) Copyright (C) 2021,2022 Kasyanov Nikolay Alexeyevich (Unbewohnte)
This file is a part of ftu This file is a part of ftu

2
src/encryption/encrypt.go

@ -1,6 +1,6 @@
/* /*
ftu - file transferring utility. ftu - file transferring utility.
Copyright (C) 2021 Kasyanov Nikolay Alexeevich (Unbewohnte (https://unbewohnte.xyz/)) Copyright (C) 2021,2022 Kasyanov Nikolay Alexeyevich (Unbewohnte)
This file is a part of ftu This file is a part of ftu

2
src/encryption/encryption_test.go

@ -1,6 +1,6 @@
/* /*
ftu - file transferring utility. ftu - file transferring utility.
Copyright (C) 2021 Kasyanov Nikolay Alexeevich (Unbewohnte (https://unbewohnte.xyz/)) Copyright (C) 2021,2022 Kasyanov Nikolay Alexeyevich (Unbewohnte)
This file is a part of ftu This file is a part of ftu

2
src/encryption/key.go

@ -1,6 +1,6 @@
/* /*
ftu - file transferring utility. ftu - file transferring utility.
Copyright (C) 2021 Kasyanov Nikolay Alexeevich (Unbewohnte (https://unbewohnte.xyz/)) Copyright (C) 2021,2022 Kasyanov Nikolay Alexeyevich (Unbewohnte)
This file is a part of ftu This file is a part of ftu

67
src/fsys/dir.go

@ -1,6 +1,6 @@
/* /*
ftu - file transferring utility. ftu - file transferring utility.
Copyright (C) 2021 Kasyanov Nikolay Alexeevich (Unbewohnte (https://unbewohnte.xyz/)) Copyright (C) 2021,2022 Kasyanov Nikolay Alexeyevich (Unbewohnte)
This file is a part of ftu This file is a part of ftu
@ -22,6 +22,7 @@ package fsys
import ( import (
"fmt" "fmt"
"io/fs"
"os" "os"
"path/filepath" "path/filepath"
) )
@ -32,6 +33,7 @@ type Directory struct {
Path string Path string
Size uint64 Size uint64
RelativeParentPath string // Relative path to the directory, where the highest point in the hierarchy is the upmost parent dir. Set manually RelativeParentPath string // Relative path to the directory, where the highest point in the hierarchy is the upmost parent dir. Set manually
Symlinks []*Symlink
Files []*File Files []*File
Directories []*Directory Directories []*Directory
} }
@ -63,12 +65,13 @@ func GetDir(path string, recursive bool) (*Directory, error) {
// loop through each entry in the directory // loop through each entry in the directory
entries, err := os.ReadDir(absPath) entries, err := os.ReadDir(absPath)
if err != nil { if err != nil && err != fs.ErrPermission {
return nil, err return nil, err
} }
var innerDirs []*Directory var innerDirs []*Directory
var innerFiles []*File var innerFiles []*File
var innerSymlinks []*Symlink
for _, entry := range entries { for _, entry := range entries {
entryInfo, err := entry.Info() entryInfo, err := entry.Info()
if err != nil { if err != nil {
@ -92,11 +95,30 @@ func GetDir(path string, recursive bool) (*Directory, error) {
// if not - skip the directory and only work with the files // if not - skip the directory and only work with the files
} else { } else {
// not a directory
switch entryInfo.Mode()&os.ModeSymlink != 0 {
case true:
// it is a symlink
innerSymlinkPath := filepath.Join(absPath, entryInfo.Name())
symlink, err := GetSymlink(innerSymlinkPath, false)
if err != nil {
// skip this symlink
continue
}
innerSymlinks = append(innerSymlinks, symlink)
case false:
// it is a usual file
innerFilePath := filepath.Join(absPath, entryInfo.Name()) innerFilePath := filepath.Join(absPath, entryInfo.Name())
innerFile, err := GetFile(innerFilePath) innerFile, err := GetFile(innerFilePath)
if err != nil { if err != nil {
return nil, err // skip this file
continue
} }
directory.Size += innerFile.Size directory.Size += innerFile.Size
@ -104,9 +126,11 @@ func GetDir(path string, recursive bool) (*Directory, error) {
innerFiles = append(innerFiles, innerFile) innerFiles = append(innerFiles, innerFile)
} }
} }
}
directory.Directories = innerDirs directory.Directories = innerDirs
directory.Files = innerFiles directory.Files = innerFiles
directory.Symlinks = innerSymlinks
return &directory, nil return &directory, nil
} }
@ -132,7 +156,27 @@ func (dir *Directory) GetAllFiles(recursive bool) []*File {
return files return files
} }
// Sets `RelativeParentPath` relative to the given base path. // Returns every symlink in that directory
func (dir *Directory) GetAllSymlinks(recursive bool) []*Symlink {
var symlinks []*Symlink = dir.Symlinks
if recursive {
if len(dir.Directories) == 0 {
return symlinks
}
for _, innerDir := range dir.Directories {
innerSymlinks := innerDir.GetAllSymlinks(recursive)
symlinks = append(symlinks, innerSymlinks...)
}
} else {
symlinks = dir.Symlinks
}
return symlinks
}
// Sets `RelativeParentPath` relative to the given base path for files and `Path`, `TargetPath` for symlinks so the
// file with such path: // file with such path:
// /home/user/directory/somefile.txt // /home/user/directory/somefile.txt
// had a relative path like that: // had a relative path like that:
@ -148,5 +192,20 @@ func (dir *Directory) SetRelativePaths(base string, recursive bool) error {
file.RelativeParentPath = relPath file.RelativeParentPath = relPath
} }
for _, symlink := range dir.GetAllSymlinks(recursive) {
symRelPath, err := filepath.Rel(base, symlink.Path)
if err != nil {
return err
}
symlink.Path = symRelPath
symRelTargetPath, err := filepath.Rel(base, symlink.TargetPath)
if err != nil {
return err
}
symlink.TargetPath = symRelTargetPath
}
return nil return nil
} }

38
src/fsys/dir_test.go

@ -1,6 +1,6 @@
/* /*
ftu - file transferring utility. ftu - file transferring utility.
Copyright (C) 2021 Kasyanov Nikolay Alexeevich (Unbewohnte (https://unbewohnte.xyz/)) Copyright (C) 2021,2022 Kasyanov Nikolay Alexeyevich (Unbewohnte)
This file is a part of ftu This file is a part of ftu
@ -21,6 +21,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
package fsys package fsys
import ( import (
"os"
"path/filepath"
"testing" "testing"
) )
@ -79,25 +81,23 @@ func Test_GetFiles(t *testing.T) {
} }
// func Test_SetRelativePaths(t *testing.T) { func Test_GetSymlinks(t *testing.T) {
// dirpath := "../testfiles/" dirpath := "../testfiles/"
// dir, err := GetDir(dirpath, true) os.Symlink(filepath.Join(dirpath, "testfile.txt"), filepath.Join(dirpath, "testsymlink.txt"))
// if err != nil { os.Symlink(filepath.Join(dirpath, "testdir", "testfile2.txt"), filepath.Join(dirpath, "testdir", "testsymlink2.txt"))
// t.Fatalf("%s", err)
// }
// absDirPath, err := filepath.Abs(dirpath) dir, err := GetDir(dirpath, true)
// if err != nil { if err != nil {
// t.Fatalf("%s", err) t.Fatalf("%s", err)
// } }
// err = dir.SetRelativePaths(absDirPath, true) // recursive
// if err != nil { symlinks := dir.GetAllSymlinks(true)
// t.Fatalf("%s", err)
// } symlinkCount := 2
// for count, file := range dir.GetAllFiles(true) { if len(symlinks) != symlinkCount {
// t.Errorf("[%d] %v\n", count, file.RelativeParentPath) t.Fatalf("expected to get %d symlinks; got %d\n", symlinkCount, len(symlinks))
// } }
// } }

6
src/fsys/file.go

@ -1,6 +1,6 @@
/* /*
ftu - file transferring utility. ftu - file transferring utility.
Copyright (C) 2021 Kasyanov Nikolay Alexeevich (Unbewohnte (https://unbewohnte.xyz/)) Copyright (C) 2021,2022 Kasyanov Nikolay Alexeyevich (Unbewohnte)
This file is a part of ftu This file is a part of ftu
@ -25,7 +25,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"github.com/Unbewohnte/ftu/checksum" "unbewohnte/ftu/checksum"
) )
// A struct that represents the necessary file information for transportation through node // A struct that represents the necessary file information for transportation through node
@ -45,7 +45,7 @@ var ErrorNotFile error = fmt.Errorf("not a file")
// Get general information about a file with the // Get general information about a file with the
// future ability to open it. // future ability to open it.
// NOTE that Handler field is nil BY DEFAULT until you // NOTE that Handler field is nil BY DEFAULT until you
// manually call a (file *File) Open() function to open it ! // manually call (file *File).Open() to open it !
func GetFile(path string) (*File, error) { func GetFile(path string) (*File, error) {
absPath, err := filepath.Abs(path) absPath, err := filepath.Abs(path)
if err != nil { if err != nil {

2
src/fsys/file_test.go

@ -1,6 +1,6 @@
/* /*
ftu - file transferring utility. ftu - file transferring utility.
Copyright (C) 2021 Kasyanov Nikolay Alexeevich (Unbewohnte (https://unbewohnte.xyz/)) Copyright (C) 2021,2022 Kasyanov Nikolay Alexeyevich (Unbewohnte)
This file is a part of ftu This file is a part of ftu

70
src/fsys/symlink.go

@ -0,0 +1,70 @@
/*
ftu - file transferring utility.
Copyright (C) 2021,2022 Kasyanov Nikolay Alexeyevich (Unbewohnte)
This file is a part of ftu
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package fsys
import (
"fmt"
"os"
)
type Symlink struct {
TargetPath string
Path string
}
// Checks whether path is referring to a symlink or not
func IsSymlink(path string) (bool, error) {
stats, err := os.Lstat(path)
if err != nil {
return false, err
}
isSymlink := stats.Mode()&os.ModeSymlink != 0
return isSymlink, nil
}
var ErrorNotSymlink error = fmt.Errorf("not a symlink")
// get necessary information about a symlink in a filesystem. If check is false -
// does not check if path REALLY refers to a symlink
func GetSymlink(path string, check bool) (*Symlink, error) {
if check {
isSymlink, err := IsSymlink(path)
if err != nil {
return nil, err
}
if !isSymlink {
return nil, ErrorNotSymlink
}
}
target, err := os.Readlink(path)
if err != nil {
return nil, err
}
symlink := Symlink{
TargetPath: target,
Path: path,
}
return &symlink, nil
}

42
src/fsys/symlink_test.go

@ -0,0 +1,42 @@
/*
ftu - file transferring utility.
Copyright (C) 2021,2022 Kasyanov Nikolay Alexeyevich (Unbewohnte)
This file is a part of ftu
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package fsys
import (
"os"
"path/filepath"
"testing"
)
func Test_IsSymlink(t *testing.T) {
dirpath := "../testfiles/"
symlinkPath := filepath.Join(dirpath, "testsymlink.txt")
os.Symlink(filepath.Join(dirpath, "testfile.txt"), symlinkPath)
isSymlink, err := IsSymlink(symlinkPath)
if err != nil {
t.Fatalf("%s\n", err)
}
if !isSymlink {
t.Fatalf("%s expected to be a symlink\n", symlinkPath)
}
}

4
src/go.mod

@ -1,3 +1,3 @@
module github.com/Unbewohnte/ftu module unbewohnte/ftu
go 1.17 go 1.18

30
src/main.go

@ -1,6 +1,6 @@
/* /*
ftu - file transferring utility. ftu - file transferring utility.
Copyright (C) 2021 Kasyanov Nikolay Alexeevich (Unbewohnte (https://unbewohnte.xyz/)) Copyright (C) 2021,2022 Kasyanov Nikolay Alexeyevich (Unbewohnte (https://unbewohnte.xyz/))
This file is a part of ftu This file is a part of ftu
@ -26,13 +26,13 @@ import (
"fmt" "fmt"
"os" "os"
"github.com/Unbewohnte/ftu/node" "unbewohnte/ftu/node"
) )
var ( var (
VERSION string = "v2.1.2" VERSION string = "v2.3.3"
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) versionInformation string = fmt.Sprintf("ftu %s\nfile transferring utility\n\nCopyright (C) 2021,2022 Kasyanov Nikolay Alexeyevich (Unbewohnte)\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)
//go:embed COPYING //go:embed COPYING
licenseInformation string licenseInformation string
@ -43,7 +43,7 @@ var (
ADDRESS *string = flag.String("a", "", "Specifies an address to connect to") ADDRESS *string = flag.String("a", "", "Specifies an address to connect to")
DOWNLOADS_DIR *string = flag.String("d", ".", "Downloads folder") DOWNLOADS_DIR *string = flag.String("d", ".", "Downloads folder")
SEND *string = flag.String("s", "", "Specify a file|directory to send") SEND *string = flag.String("s", "", "Specify a file|directory to send")
VERBOSE *bool = flag.Bool("vp", false, "Turn on/off verbose output") VERBOSE *bool = flag.Bool("?", false, "Turn on/off verbose output")
PRINT_VERSION *bool = flag.Bool("v", false, "Print version information") PRINT_VERSION *bool = flag.Bool("v", false, "Print version information")
PRINT_LICENSE *bool = flag.Bool("l", false, "Print license information") PRINT_LICENSE *bool = flag.Bool("l", false, "Print license information")
@ -52,15 +52,15 @@ var (
func init() { func init() {
flag.Usage = func() { flag.Usage = func() {
fmt.Printf("ftu -[FLAG]...\n\n") fmt.Printf("ftu -[FLAGs]\n\n")
fmt.Printf("[FLAGs]\n\n") fmt.Printf("[FLAGs]\n\n")
fmt.Printf("| -p [Uinteger_here] for port\n") fmt.Printf("| -p [integer] for port\n")
fmt.Printf("| -r [true|false] for recursive sending of a directory\n") fmt.Printf("| -r [true|false] send recursively or not\n")
fmt.Printf("| -a [ip_address|domain_name] address to connect to (cannot be used with -s)\n") fmt.Printf("| -a [ip_address|domain_name] address to connect to (cannot be used with -s)\n")
fmt.Printf("| -d [path_to_directory] where the files will be downloaded to (cannot be used with -s)\n") fmt.Printf("| -d [path_to_directory] where the files will be downloaded to (cannot be used with -s)\n")
fmt.Printf("| -s [path_to_file|directory] to send it (cannot be used with -a)\n") fmt.Printf("| -s [path_to_file|directory] send it (cannot be used with -a)\n")
fmt.Printf("| -vp [true|false] to turn on|off verbose output") fmt.Printf("| -? [true|false] turn on|off verbose output\n")
fmt.Printf("| -l print license information\n") fmt.Printf("| -l print license information\n")
fmt.Printf("| -v print version information\n\n\n") fmt.Printf("| -v print version information\n\n\n")
@ -98,12 +98,12 @@ func init() {
// validate flags // validate flags
if *SEND == "" && *ADDRESS == "" { if *SEND == "" && *ADDRESS == "" {
fmt.Printf("Neither sending nor receiving flag was specified. Run ftu -h for help\n") fmt.Printf("[ERROR] Neither sending nor receiving flag was specified. Run ftu -h for help\n")
os.Exit(-1) os.Exit(-1)
} }
if *SEND != "" && *ADDRESS != "" { if *SEND != "" && *ADDRESS != "" {
fmt.Printf("Can`t send and receive at the same time. Specify only -s or -a\n") fmt.Printf("[ERROR] Can't send and receive at the same time. Specify either -s or -a\n")
os.Exit(-1) os.Exit(-1)
} }
@ -122,11 +122,11 @@ func main() {
VerboseOutput: *VERBOSE, VerboseOutput: *VERBOSE,
IsSending: isSending, IsSending: isSending,
WorkingPort: *PORT, WorkingPort: *PORT,
ServerSide: &node.ServerSideNodeOptions{ SenderSide: &node.SenderNodeOptions{
ServingPath: *SEND, ServingPath: *SEND,
Recursive: *RECUSRIVE, Recursive: *RECUSRIVE,
}, },
ClientSide: &node.ClientSideNodeOptions{ ReceiverSide: &node.ReceiverNodeOptions{
ConnectionAddr: *ADDRESS, ConnectionAddr: *ADDRESS,
DownloadsFolderPath: *DOWNLOADS_DIR, DownloadsFolderPath: *DOWNLOADS_DIR,
}, },
@ -134,7 +134,7 @@ func main() {
node, err := node.NewNode(&nodeOptions) node, err := node.NewNode(&nodeOptions)
if err != nil { if err != nil {
fmt.Printf("Error constructing a new node: %s\n", err) fmt.Printf("[ERROR] Error constructing a new node: %s\n", err)
os.Exit(-1) os.Exit(-1)
} }

393
src/node/node.go

@ -1,6 +1,6 @@
/* /*
ftu - file transferring utility. ftu - file transferring utility.
Copyright (C) 2021 Kasyanov Nikolay Alexeevich (Unbewohnte (https://unbewohnte.xyz/)) Copyright (C) 2021,2022 Kasyanov Nikolay Alexeyevich (Unbewohnte)
This file is a part of ftu This file is a part of ftu
@ -32,19 +32,13 @@ import (
"fmt" "fmt"
"github.com/Unbewohnte/ftu/addr" "unbewohnte/ftu/addr"
"github.com/Unbewohnte/ftu/checksum" "unbewohnte/ftu/checksum"
"github.com/Unbewohnte/ftu/encryption" "unbewohnte/ftu/encryption"
"github.com/Unbewohnte/ftu/fsys" "unbewohnte/ftu/fsys"
"github.com/Unbewohnte/ftu/protocol" "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 // netInfowork specific settings
type netInfo struct { type netInfo struct {
ConnAddr string // address to connect to. Does not include port ConnAddr string // address to connect to. Does not include port
@ -59,14 +53,22 @@ type sending struct {
IsDirectory bool // is ServingPath a directory IsDirectory bool // is ServingPath a directory
Recursive bool // recursively send directory Recursive bool // recursively send directory
CanSendBytes bool // is the other node ready to receive another piece 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 FilesToSend []*fsys.File
SymlinksToSend []*fsys.Symlink
CurrentFileID uint64 // an id of a file that is currently being transported 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
CurrentSymlinkIndex uint64 // current index of a symlink that is
} }
// Receiving-side node information // Receiving-side node information
type receiving struct { type receiving struct {
AcceptedFiles []*fsys.File // files that`ve been accepted to be received AcceptedFiles []*fsys.File // files that`ve been accepted to be received
DownloadsPath string // where to download 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 // Both sending-side and receiving-side information
@ -81,8 +83,8 @@ type Node struct {
mutex *sync.Mutex mutex *sync.Mutex
packetPipe chan *protocol.Packet // a way to receive incoming packets from another goroutine packetPipe chan *protocol.Packet // a way to receive incoming packets from another goroutine
isSending bool // sending or a receiving node 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 netInfo *netInfo
state *nodeInnerstates
transferInfo *transferInfo transferInfo *transferInfo
} }
@ -91,7 +93,7 @@ func NewNode(options *NodeOptions) (*Node, error) {
var isDir bool var isDir bool
if options.IsSending { if options.IsSending {
// sending node preparation // sending node preparation
sendingPathStats, err := os.Stat(options.ServerSide.ServingPath) sendingPathStats, err := os.Stat(options.SenderSide.ServingPath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -106,12 +108,12 @@ func NewNode(options *NodeOptions) (*Node, error) {
} else { } else {
// receiving node preparation // receiving node preparation
var err error var err error
options.ClientSide.DownloadsFolderPath, err = filepath.Abs(options.ClientSide.DownloadsFolderPath) options.ReceiverSide.DownloadsFolderPath, err = filepath.Abs(options.ReceiverSide.DownloadsFolderPath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = os.MkdirAll(options.ClientSide.DownloadsFolderPath, os.ModePerm) err = os.MkdirAll(options.ReceiverSide.DownloadsFolderPath, os.ModePerm)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -125,23 +127,24 @@ func NewNode(options *NodeOptions) (*Node, error) {
isSending: options.IsSending, isSending: options.IsSending,
netInfo: &netInfo{ netInfo: &netInfo{
Port: options.WorkingPort, Port: options.WorkingPort,
ConnAddr: options.ClientSide.ConnectionAddr, ConnAddr: options.ReceiverSide.ConnectionAddr,
EncryptionKey: nil, EncryptionKey: nil,
Conn: nil, Conn: nil,
}, },
state: &nodeInnerstates{ stopped: false,
AllowedToTransfer: false,
Stopped: false,
},
transferInfo: &transferInfo{ transferInfo: &transferInfo{
Sending: &sending{ Sending: &sending{
ServingPath: options.ServerSide.ServingPath, ServingPath: options.SenderSide.ServingPath,
Recursive: options.ServerSide.Recursive, Recursive: options.SenderSide.Recursive,
IsDirectory: isDir, IsDirectory: isDir,
TotalTransferSize: 0,
SentBytes: 0,
}, },
Receiving: &receiving{ Receiving: &receiving{
AcceptedFiles: nil, AcceptedFiles: nil,
DownloadsPath: options.ClientSide.DownloadsFolderPath, DownloadsPath: options.ReceiverSide.DownloadsFolderPath,
ReceivedBytes: 0,
TotalDownloadSize: 0,
}, },
}, },
} }
@ -184,7 +187,7 @@ func (node *Node) disconnect() error {
return err return err
} }
node.state.Stopped = true node.stopped = true
} }
return nil return nil
@ -216,24 +219,25 @@ func (node *Node) printTransferInfo(delay time.Duration) error {
switch node.isSending { switch node.isSending {
case true: case true:
if !node.state.AllowedToTransfer { if !node.transferInfo.Sending.AllowedToTransfer {
// do not print if the transfer has not been accepted yet
break 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: case false:
if len(node.transferInfo.Receiving.AcceptedFiles) <= 0 { fmt.Printf("\r| (%.2f/%.2f MB)",
break float32(node.transferInfo.Receiving.ReceivedBytes)/1024/1024,
} float32(node.transferInfo.Receiving.TotalDownloadSize)/1024/1024,
fmt.Printf("\r| file(s) left to receive: %4d", len(node.transferInfo.Receiving.AcceptedFiles)) )
} }
return nil return nil
} }
// Starts the node in either sending or receiving state and performs the transfer func (node *Node) send() {
func (node *Node) Start() {
switch node.isSending {
case true:
// SENDER NODE // SENDER NODE
localIP, err := addr.GetLocal() localIP, err := addr.GetLocal()
@ -242,40 +246,44 @@ func (node *Node) Start() {
} }
// retrieve information about the file|directory // retrieve information about the file|directory
var fileToSend *fsys.File var FILETOSEND *fsys.File
var dirToSend *fsys.Directory var DIRTOSEND *fsys.Directory
switch node.transferInfo.Sending.IsDirectory { switch node.transferInfo.Sending.IsDirectory {
case true: case true:
dirToSend, err = fsys.GetDir(node.transferInfo.Sending.ServingPath, node.transferInfo.Sending.Recursive) DIRTOSEND, err = fsys.GetDir(node.transferInfo.Sending.ServingPath, node.transferInfo.Sending.Recursive)
if err != nil { if err != nil {
panic(err) panic(err)
} }
case false: case false:
fileToSend, err = fsys.GetFile(node.transferInfo.Sending.ServingPath) FILETOSEND, err = fsys.GetFile(node.transferInfo.Sending.ServingPath)
if err != nil { if err != nil {
panic(err) panic(err)
} }
} }
if dirToSend != nil { if DIRTOSEND != nil {
size := float32(dirToSend.Size) / 1024 / 1024 node.transferInfo.Sending.TotalTransferSize = DIRTOSEND.Size
displaySize := float32(DIRTOSEND.Size) / 1024 / 1024
sizeLevel := "MiB" sizeLevel := "MiB"
if size >= 1024 { if displaySize >= 1024 {
// GiB // GiB
size = size / 1024 displaySize = displaySize / 1024
sizeLevel = "GiB" sizeLevel = "GiB"
} }
fmt.Printf("\nSending \"%s\" (%.3f %s) locally on %s:%d", dirToSend.Name, size, sizeLevel, localIP, node.netInfo.Port) fmt.Printf("\nSending \"%s\" (%.3f %s) locally on %s:%d and remotely (if configured)", DIRTOSEND.Name, displaySize, sizeLevel, localIP, node.netInfo.Port)
} else { } else {
size := float32(fileToSend.Size) / 1024 / 1024 node.transferInfo.Sending.TotalTransferSize = FILETOSEND.Size
displaySize := float32(FILETOSEND.Size) / 1024 / 1024
sizeLevel := "MiB" sizeLevel := "MiB"
if size >= 1024 { if displaySize >= 1024 {
// GiB // GiB
size = size / 1024 displaySize = displaySize / 1024
sizeLevel = "GiB" sizeLevel = "GiB"
} }
fmt.Printf("\nSending \"%s\" (%.3f %s) locally on %s:%d and remotely", fileToSend.Name, size, sizeLevel, localIP, node.netInfo.Port) fmt.Printf("\nSending \"%s\" (%.3f %s) locally on %s:%d and remotely (if configured)", FILETOSEND.Name, displaySize, sizeLevel, localIP, node.netInfo.Port)
} }
@ -299,21 +307,25 @@ func (node *Node) Start() {
go protocol.ReceivePackets(node.netInfo.Conn, node.packetPipe) go protocol.ReceivePackets(node.netInfo.Conn, node.packetPipe)
// send info about file/directory // send info about file/directory
go protocol.SendTransferOffer(node.netInfo.Conn, fileToSend, dirToSend, node.netInfo.EncryptionKey) go protocol.SendTransferOffer(node.netInfo.Conn, FILETOSEND, DIRTOSEND, node.netInfo.EncryptionKey)
// mainloop // mainloop
for { for {
if node.state.Stopped { if node.stopped {
fmt.Printf("\n") fmt.Printf("\n")
node.disconnect() node.disconnect()
break break
} }
if !node.verboseOutput {
go node.printTransferInfo(time.Second)
}
// receive incoming packets and decrypt them if necessary // receive incoming packets and decrypt them if necessary
incomingPacket, ok := <-node.packetPipe incomingPacket, ok := <-node.packetPipe
if !ok { if !ok {
fmt.Printf("\nThe connection has been closed unexpectedly\n") fmt.Printf("\nThe connection has been closed unexpectedly\n")
os.Exit(-1.) os.Exit(-1)
} }
// if encryption key is set - decrypt packet on the spot // if encryption key is set - decrypt packet on the spot
@ -333,107 +345,46 @@ func (node *Node) Start() {
case protocol.HeaderAccept: case protocol.HeaderAccept:
// the receiving node has accepted the transfer // the receiving node has accepted the transfer
node.state.AllowedToTransfer = true node.transferInfo.Sending.AllowedToTransfer = true
fmt.Printf("\nTransfer allowed. Sending...")
// notify it about all the files that are going to be sent // prepare files to send
switch node.transferInfo.Sending.IsDirectory { switch node.transferInfo.Sending.IsDirectory {
case true: case true:
// send file packets for the files in the directory // send file packets for the files in the directory
err = dirToSend.SetRelativePaths(dirToSend.Path, node.transferInfo.Sending.Recursive) err = DIRTOSEND.SetRelativePaths(DIRTOSEND.Path, node.transferInfo.Sending.Recursive)
if err != nil { if err != nil {
panic(err) panic(err)
} }
filesToSend := dirToSend.GetAllFiles(node.transferInfo.Sending.Recursive) filesToSend := DIRTOSEND.GetAllFiles(node.transferInfo.Sending.Recursive)
symlinksToSend := DIRTOSEND.GetAllSymlinks(node.transferInfo.Sending.Recursive)
node.transferInfo.Sending.SymlinksToSend = symlinksToSend
// notify the other node about all the files that are going to be sent
for counter, file := range filesToSend { for counter, file := range filesToSend {
// assign ID and add it to the node sendlist // assign ID and add it to the node sendlist
file.ID = uint64(counter) file.ID = uint64(counter)
node.transferInfo.Sending.FilesToSend = append(node.transferInfo.Sending.FilesToSend, file) node.transferInfo.Sending.FilesToSend = append(node.transferInfo.Sending.FilesToSend, file)
}
// set current file id to the first file // set current file id to the first file
node.transferInfo.Sending.CurrentFileID = 0 node.transferInfo.Sending.CurrentFileID = 0
filePacket, err := protocol.CreateFilePacket(file)
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
}
err = protocol.SendPacket(node.netInfo.Conn, *filePacket)
if err != nil {
panic(err)
}
if node.verboseOutput {
fmt.Printf("\n[File] Sent filepacket for \"%s\"", file.Name)
}
}
filesInfoDonePacket := protocol.Packet{
Header: protocol.HeaderFilesInfoDone,
}
protocol.SendPacket(node.netInfo.Conn, filesInfoDonePacket)
if node.verboseOutput {
fmt.Printf("\n[File] Done sending filepackets")
}
case false: case false:
// send a filepacket of a single file FILETOSEND.ID = 0
fileToSend.ID = 0 node.transferInfo.Sending.FilesToSend = append(node.transferInfo.Sending.FilesToSend, FILETOSEND)
node.transferInfo.Sending.FilesToSend = append(node.transferInfo.Sending.FilesToSend, fileToSend)
// set current file index to the first and only file // set current file index to the first and only file
node.transferInfo.Sending.CurrentFileID = 0 node.transferInfo.Sending.CurrentFileID = 0
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
}
err = protocol.SendPacket(node.netInfo.Conn, *filePacket)
if err != nil {
panic(err)
}
if node.verboseOutput {
fmt.Printf("\n[File] Sent filepacket for \"%s\"", fileToSend.Name)
}
if node.verboseOutput {
fmt.Printf("\n[File] Done sending filepackets")
}
} }
fmt.Printf("\n")
case protocol.HeaderReject: case protocol.HeaderReject:
node.state.Stopped = true node.stopped = true
fmt.Printf("\nTransfer rejected. Disconnecting...") fmt.Printf("\nTransfer rejected. Disconnecting...")
case protocol.HeaderDisconnecting: case protocol.HeaderDisconnecting:
node.state.Stopped = true node.stopped = true
fmt.Printf("\n%s disconnected", node.netInfo.Conn.RemoteAddr()) fmt.Printf("\n%s disconnected", node.netInfo.Conn.RemoteAddr())
case protocol.HeaderAlreadyHave: case protocol.HeaderAlreadyHave:
@ -450,35 +401,74 @@ func (node *Node) Start() {
node.transferInfo.Sending.CurrentFileID++ node.transferInfo.Sending.CurrentFileID++
node.transferInfo.Sending.SentBytes += fileToSend.Size
node.transferInfo.Sending.InTransfer = false
if node.verboseOutput { if node.verboseOutput {
fmt.Printf("\n[File] receiver already has \"%s\"", fileToSend.Name) fmt.Printf("\n[File] receiver already has \"%s\"", fileToSend.Name)
} }
} }
} }
}
if !node.verboseOutput {
go node.printTransferInfo(time.Second)
} }
// Transfer section // Transfer section
if len(node.transferInfo.Sending.FilesToSend) == 0 { // if all files have been sent -> send symlinks
if len(node.transferInfo.Sending.FilesToSend) == 0 && node.transferInfo.Sending.CurrentSymlinkIndex < uint64(len(node.transferInfo.Sending.SymlinksToSend)) {
protocol.SendSymlink(node.transferInfo.Sending.SymlinksToSend[node.transferInfo.Sending.CurrentSymlinkIndex], node.netInfo.Conn, encrKey)
node.transferInfo.Sending.CurrentSymlinkIndex++
continue
}
if len(node.transferInfo.Sending.FilesToSend) == 0 && node.transferInfo.Sending.CurrentSymlinkIndex == uint64(len(node.transferInfo.Sending.SymlinksToSend)) {
// if there`s nothing else to send - create and send DONE packet // if there`s nothing else to send - create and send DONE packet
protocol.SendPacket(node.netInfo.Conn, protocol.Packet{ protocol.SendPacket(node.netInfo.Conn, protocol.Packet{
Header: protocol.HeaderDone, Header: protocol.HeaderDone,
}) })
fmt.Printf("\nTransfer ended successfully") node.stopped = true
node.state.Stopped = true
continue
}
if node.transferInfo.Sending.AllowedToTransfer && !node.transferInfo.Sending.InTransfer {
// notify the node about the next file to be sent
// 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.netInfo.EncryptionKey != nil {
err = fpacket.EncryptBody(node.netInfo.EncryptionKey)
if err != nil {
panic(err)
}
}
err = protocol.SendPacket(node.netInfo.Conn, *fpacket)
if err != nil {
panic(err)
}
// initiate the transfer for this file on the next iteration
node.transferInfo.Sending.InTransfer = true
continue continue
} }
// if allowed to transfer and the other node is ready to receive packets - send one piece // if allowed to transfer and the other node is ready to receive packets - send one piece
// and wait for it to be ready again // and wait for it to be ready again
if node.state.AllowedToTransfer && node.transferInfo.Sending.CanSendBytes { if node.transferInfo.Sending.AllowedToTransfer && node.transferInfo.Sending.CanSendBytes && node.transferInfo.Sending.InTransfer {
// sending a piece of a single file // sending a piece of a single file
// determine an index of a file with current ID // determine an index of a file with current ID
@ -490,7 +480,8 @@ func (node *Node) Start() {
} }
} }
err = protocol.SendPiece(node.transferInfo.Sending.FilesToSend[currentFileIndex], node.netInfo.Conn, node.netInfo.EncryptionKey) sentBytes, err := protocol.SendPiece(node.transferInfo.Sending.FilesToSend[currentFileIndex], node.netInfo.Conn, node.netInfo.EncryptionKey)
node.transferInfo.Sending.SentBytes += sentBytes
switch err { switch err {
case protocol.ErrorSentAll: case protocol.ErrorSentAll:
// the file has been sent fully // the file has been sent fully
@ -519,30 +510,33 @@ func (node *Node) Start() {
protocol.SendPacket(node.netInfo.Conn, endFilePacket) protocol.SendPacket(node.netInfo.Conn, endFilePacket)
// remove this file from the queue
node.transferInfo.Sending.FilesToSend = append(node.transferInfo.Sending.FilesToSend[:currentFileIndex], node.transferInfo.Sending.FilesToSend[currentFileIndex+1:]...) node.transferInfo.Sending.FilesToSend = append(node.transferInfo.Sending.FilesToSend[:currentFileIndex], node.transferInfo.Sending.FilesToSend[currentFileIndex+1:]...)
// start sending the next file // set counter to the next file ID
node.transferInfo.Sending.CurrentFileID++ node.transferInfo.Sending.CurrentFileID++
node.transferInfo.Sending.InTransfer = false
case nil: case nil:
node.transferInfo.Sending.CanSendBytes = false node.transferInfo.Sending.CanSendBytes = false
default: default:
node.state.Stopped = true node.stopped = true
fmt.Printf("\nAn error occured while sending a piece of \"%s\": %s", node.transferInfo.Sending.FilesToSend[currentFileIndex].Name, err) fmt.Printf("\n[ERROR] An error occured while sending a piece of \"%s\": %s", node.transferInfo.Sending.FilesToSend[currentFileIndex].Name, err)
panic(err) panic(err)
} }
} }
} }
}
case false: func (node *Node) receive() {
// RECEIVER NODE // RECEIVER NODE
// connect to the sending node // connect to the sending node
err := node.connect() err := node.connect()
if err != nil { if err != nil {
fmt.Printf("\nCould not connect to %s:%d", node.netInfo.ConnAddr, node.netInfo.Port) fmt.Printf("\n[ERROR] Could not connect to %s:%d\n", node.netInfo.ConnAddr, node.netInfo.Port)
os.Exit(-1) os.Exit(-1)
} }
@ -552,7 +546,7 @@ func (node *Node) Start() {
// mainloop // mainloop
for { for {
node.mutex.Lock() node.mutex.Lock()
stopped := node.state.Stopped stopped := node.stopped
node.mutex.Unlock() node.mutex.Unlock()
if stopped { if stopped {
@ -561,10 +555,14 @@ func (node *Node) Start() {
break break
} }
if !node.verboseOutput && node.transferInfo.Receiving.ReceivedBytes != 0 {
go node.printTransferInfo(time.Second)
}
// receive incoming packets and decrypt them if necessary // receive incoming packets and decrypt them if necessary
incomingPacket, ok := <-node.packetPipe incomingPacket, ok := <-node.packetPipe
if !ok { if !ok {
fmt.Printf("\nThe connection has been closed unexpectedly\n") fmt.Printf("\nConnection has been closed unexpectedly\n")
os.Exit(-1) os.Exit(-1)
} }
@ -588,6 +586,8 @@ func (node *Node) Start() {
} }
if file != nil { if file != nil {
node.transferInfo.Receiving.TotalDownloadSize = file.Size
size := float32(file.Size) / 1024 / 1024 size := float32(file.Size) / 1024 / 1024
sizeLevel := "MiB" sizeLevel := "MiB"
if size >= 1024 { if size >= 1024 {
@ -596,7 +596,10 @@ func (node *Node) Start() {
sizeLevel = "GiB" sizeLevel = "GiB"
} }
fmt.Printf("\n| Filename: %s\n| Size: %.3f %s\n| Checksum: %s\n", file.Name, size, sizeLevel, file.Checksum) fmt.Printf("\n| Filename: %s\n| Size: %.3f %s\n| Checksum: %s\n", file.Name, size, sizeLevel, file.Checksum)
} else if dir != nil { } else if dir != nil {
node.transferInfo.Receiving.TotalDownloadSize = dir.Size
size := float32(dir.Size) / 1024 / 1024 size := float32(dir.Size) / 1024 / 1024
sizeLevel := "MiB" sizeLevel := "MiB"
if size >= 1024 { if size >= 1024 {
@ -620,7 +623,7 @@ func (node *Node) Start() {
err = os.MkdirAll(filepath.Join(node.transferInfo.Receiving.DownloadsPath, dir.Name), os.ModePerm) err = os.MkdirAll(filepath.Join(node.transferInfo.Receiving.DownloadsPath, dir.Name), os.ModePerm)
if err != nil { if err != nil {
// well, just download all files in the default downloads folder then // well, just download all files in the default downloads folder then
fmt.Printf("\n[ERROR] could not create a directory") fmt.Printf("\n[ERROR] could not create a directory, downloading directly to the specified location")
} else { } else {
// also download everything in a newly created directory // also download everything in a newly created directory
node.transferInfo.Receiving.DownloadsPath = filepath.Join(node.transferInfo.Receiving.DownloadsPath, dir.Name) node.transferInfo.Receiving.DownloadsPath = filepath.Join(node.transferInfo.Receiving.DownloadsPath, dir.Name)
@ -651,14 +654,13 @@ func (node *Node) Start() {
} }
node.mutex.Lock() node.mutex.Lock()
node.state.Stopped = true node.stopped = true
node.mutex.Unlock() node.mutex.Unlock()
} }
}() }()
case protocol.HeaderFile: case protocol.HeaderFile:
// add file to the accepted files; // add file to the accepted files;
file, err := protocol.DecodeFilePacket(incomingPacket) file, err := protocol.DecodeFilePacket(incomingPacket)
if err != nil { if err != nil {
panic(err) panic(err)
@ -718,6 +720,8 @@ func (node *Node) Start() {
protocol.SendPacket(node.netInfo.Conn, alreadyHavePacket) protocol.SendPacket(node.netInfo.Conn, alreadyHavePacket)
node.transferInfo.Receiving.ReceivedBytes += file.Size
if node.verboseOutput { if node.verboseOutput {
fmt.Printf("\n[File] already have \"%s\"", file.Name) fmt.Printf("\n[File] already have \"%s\"", file.Name)
} }
@ -725,6 +729,17 @@ func (node *Node) Start() {
} else { } else {
// not the same file. Remove it and await new bytes // not the same file. Remove it and await new bytes
os.Remove(file.Path) os.Remove(file.Path)
node.mutex.Lock()
node.transferInfo.Receiving.AcceptedFiles = append(node.transferInfo.Receiving.AcceptedFiles, file)
node.mutex.Unlock()
err = protocol.SendPacket(node.netInfo.Conn, protocol.Packet{
Header: protocol.HeaderReady,
})
if err != nil {
panic(err)
}
} }
existingFileHandler.Close() existingFileHandler.Close()
@ -734,6 +749,13 @@ func (node *Node) Start() {
node.mutex.Lock() node.mutex.Lock()
node.transferInfo.Receiving.AcceptedFiles = append(node.transferInfo.Receiving.AcceptedFiles, file) node.transferInfo.Receiving.AcceptedFiles = append(node.transferInfo.Receiving.AcceptedFiles, file)
node.mutex.Unlock() node.mutex.Unlock()
err = protocol.SendPacket(node.netInfo.Conn, protocol.Packet{
Header: protocol.HeaderReady,
})
if err != nil {
panic(err)
}
} }
case protocol.HeaderFileBytes: case protocol.HeaderFileBytes:
@ -752,11 +774,12 @@ func (node *Node) Start() {
// accepted // accepted
// append provided bytes to the file // append provided bytes to the file
if acceptedFile.Handler == nil {
err = acceptedFile.Open() err = acceptedFile.Open()
if err != nil { if err != nil {
panic(err) panic(err)
} }
}
fileBytes := fileBytesBuffer.Bytes() fileBytes := fileBytesBuffer.Bytes()
@ -765,11 +788,7 @@ func (node *Node) Start() {
panic(err) panic(err)
} }
acceptedFile.SentBytes += uint64(wrote) acceptedFile.SentBytes += uint64(wrote)
node.transferInfo.Receiving.ReceivedBytes += uint64(wrote)
err = acceptedFile.Close()
if err != nil {
panic(err)
}
} }
} }
@ -778,21 +797,8 @@ func (node *Node) Start() {
} }
protocol.SendPacket(node.netInfo.Conn, readyPacket) protocol.SendPacket(node.netInfo.Conn, readyPacket)
case protocol.HeaderFilesInfoDone:
// have all information about the files
// 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)
}
case protocol.HeaderEndfile: case protocol.HeaderEndfile:
// one of the files has been received completely, // one of the files has been received completely
// compare checksums and check if it is the last
// file in the transfer
fileIDReader := bytes.NewReader(incomingPacket.Body) fileIDReader := bytes.NewReader(incomingPacket.Body)
var fileID uint64 var fileID uint64
@ -809,10 +815,12 @@ func (node *Node) Start() {
fmt.Printf("\n[File] fully received \"%s\" -- %d bytes", acceptedFile.Name, acceptedFile.Size) fmt.Printf("\n[File] fully received \"%s\" -- %d bytes", acceptedFile.Name, acceptedFile.Size)
} }
if acceptedFile.Handler == nil {
err = acceptedFile.Open() err = acceptedFile.Open()
if err != nil { if err != nil {
panic(err) panic(err)
} }
}
// remove this file from the pool // remove this file from the pool
node.transferInfo.Receiving.AcceptedFiles = append(node.transferInfo.Receiving.AcceptedFiles[:index], node.transferInfo.Receiving.AcceptedFiles[index+1:]...) node.transferInfo.Receiving.AcceptedFiles = append(node.transferInfo.Receiving.AcceptedFiles[:index], node.transferInfo.Receiving.AcceptedFiles[index+1:]...)
@ -824,7 +832,10 @@ func (node *Node) Start() {
} }
if realChecksum != acceptedFile.Checksum { if realChecksum != acceptedFile.Checksum {
fmt.Printf("\n| \"%s\" is corrupted", acceptedFile.Name) if node.verboseOutput {
fmt.Printf("\n[ERROR] \"%s\" is corrupted", acceptedFile.Name)
}
acceptedFile.Close() acceptedFile.Close()
break break
} else { } else {
@ -855,22 +866,62 @@ func (node *Node) Start() {
fmt.Printf("\nGot an encryption key: %s", encrKey) fmt.Printf("\nGot an encryption key: %s", encrKey)
case protocol.HeaderSymlink:
// SYMLINK~(string size in binary)(location in the filesystem)(string size in binary)(location of a target)
packetReader := bytes.NewReader(incomingPacket.Body)
// extract the location of the symlink
var locationSize uint64
binary.Read(packetReader, binary.BigEndian, &locationSize)
symlinkLocationBytes := make([]byte, locationSize)
packetReader.Read(symlinkLocationBytes)
// extract the target of a symlink
var targetSize uint64
binary.Read(packetReader, binary.BigEndian, &targetSize)
symlinkTargetLocationBytes := make([]byte, targetSize)
packetReader.Read(symlinkTargetLocationBytes)
symlinkLocation := string(symlinkLocationBytes)
symlinkTargetLocation := string(symlinkTargetLocationBytes)
// create a symlink
// should be already downloaded
symlinkDir := filepath.Join(node.transferInfo.Receiving.DownloadsPath, filepath.Dir(symlinkLocation))
os.MkdirAll(symlinkDir, os.ModePerm)
os.Symlink(
filepath.Join(node.transferInfo.Receiving.DownloadsPath, symlinkTargetLocation),
filepath.Join(node.transferInfo.Receiving.DownloadsPath, symlinkLocation))
protocol.SendPacket(node.netInfo.Conn, protocol.Packet{
Header: protocol.HeaderReady,
})
case protocol.HeaderDone: case protocol.HeaderDone:
node.mutex.Lock() node.mutex.Lock()
node.state.Stopped = true node.stopped = true
node.mutex.Unlock() node.mutex.Unlock()
case protocol.HeaderDisconnecting: case protocol.HeaderDisconnecting:
node.mutex.Lock() node.mutex.Lock()
node.state.Stopped = true node.stopped = true
node.mutex.Unlock() 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)
} }
} }
// 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()
} }
} }

10
src/node/options.go

@ -1,6 +1,6 @@
/* /*
ftu - file transferring utility. ftu - file transferring utility.
Copyright (C) 2021 Kasyanov Nikolay Alexeevich (Unbewohnte (https://unbewohnte.xyz/)) Copyright (C) 2021,2022 Kasyanov Nikolay Alexeyevich (Unbewohnte)
This file is a part of ftu This file is a part of ftu
@ -20,12 +20,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
package node package node
type ServerSideNodeOptions struct { type SenderNodeOptions struct {
ServingPath string ServingPath string
Recursive bool Recursive bool
} }
type ClientSideNodeOptions struct { type ReceiverNodeOptions struct {
ConnectionAddr string ConnectionAddr string
DownloadsFolderPath string DownloadsFolderPath string
} }
@ -35,6 +35,6 @@ type NodeOptions struct {
IsSending bool IsSending bool
WorkingPort uint WorkingPort uint
VerboseOutput bool VerboseOutput bool
ServerSide *ServerSideNodeOptions SenderSide *SenderNodeOptions
ClientSide *ClientSideNodeOptions ReceiverSide *ReceiverNodeOptions
} }

2
src/protocol/constants.go

@ -1,6 +1,6 @@
/* /*
ftu - file transferring utility. ftu - file transferring utility.
Copyright (C) 2021 Kasyanov Nikolay Alexeevich (Unbewohnte (https://unbewohnte.xyz/)) Copyright (C) 2021,2022 Kasyanov Nikolay Alexeyevich (Unbewohnte)
This file is a part of ftu This file is a part of ftu

25
src/protocol/headers.go

@ -1,6 +1,6 @@
/* /*
ftu - file transferring utility. ftu - file transferring utility.
Copyright (C) 2021 Kasyanov Nikolay Alexeevich (Unbewohnte (https://unbewohnte.xyz/)) Copyright (C) 2021,2022 Kasyanov Nikolay Alexeyevich (Unbewohnte)
This file is a part of ftu This file is a part of ftu
@ -29,7 +29,7 @@ type Header string
//// and (size) is 8 bytes long big-endian binary encoded uint64 //// and (size) is 8 bytes long big-endian binary encoded uint64
// ENCRKEY. // 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. // by sender. Body contains a size of a key and the key itself.
// ie: ENCRKEY~(size)(encryption key) // ie: ENCRKEY~(size)(encryption key)
const HeaderEncryptionKey Header = "ENCRKEY" const HeaderEncryptionKey Header = "ENCRKEY"
@ -41,7 +41,7 @@ const HeaderReject Header = "REJECT"
// ACCEPT. // ACCEPT.
// The opposite of the previous REJECT. Sent by receiver when // 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~ // ie: ACCEPT~
const HeaderAccept Header = "ACCEPT" const HeaderAccept Header = "ACCEPT"
@ -55,19 +55,11 @@ const HeaderDone Header = "DONE"
// READY. // READY.
// Sent by receiver when it has read and processed the last // 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 // FILEBYTES or FILE packet. The sender is not allowed to "spam" FILEBYTES or FILE
// receive bytes (FILESINFODONE). The sender is not allowed to "spam" FILEBYTES // packets without the permission (packet with this header) from receiver.
// packets without the permission of receiver.
// ie: READY!~ // ie: READY!~
const HeaderReady Header = "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!. // BYE!.
// Packet with this header can be sent both by receiver and sender. // 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 // It`s used when the sender or the receiver are going to disconnect
@ -122,3 +114,10 @@ const HeaderDirectory Header = "DIRECTORY"
// Body must contain a file ID. // Body must contain a file ID.
// ie: ALREADYHAVE~(file ID in binary) // ie: ALREADYHAVE~(file ID in binary)
const HeaderAlreadyHave Header = "ALREADYHAVE" const HeaderAlreadyHave Header = "ALREADYHAVE"
// SYMLINK
// Sent by sender AFTER ALL FILES has been sent already. Indicates that there
// is a symlink in some place that points to some other already received file.
// Body must contain information where the symlink is and the target file.
// ie: SYMLINK~(string size in binary)(location in the filesystem)(string size in binary)(location of a target)
const HeaderSymlink Header = "SYMLINK"

4
src/protocol/packet.go

@ -1,6 +1,6 @@
/* /*
ftu - file transferring utility. ftu - file transferring utility.
Copyright (C) 2021 Kasyanov Nikolay Alexeevich (Unbewohnte (https://unbewohnte.xyz/)) Copyright (C) 2021,2022 Kasyanov Nikolay Alexeyevich (Unbewohnte)
This file is a part of ftu This file is a part of ftu
@ -31,7 +31,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/Unbewohnte/ftu/encryption" "unbewohnte/ftu/encryption"
) )
// Internal representation of packet before|after the transportation // Internal representation of packet before|after the transportation

4
src/protocol/packetConstruct.go

@ -1,6 +1,6 @@
/* /*
ftu - file transferring utility. ftu - file transferring utility.
Copyright (C) 2021 Kasyanov Nikolay Alexeevich (Unbewohnte (https://unbewohnte.xyz/)) Copyright (C) 2021,2022 Kasyanov Nikolay Alexeyevich (Unbewohnte)
This file is a part of ftu This file is a part of ftu
@ -25,7 +25,7 @@ import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"github.com/Unbewohnte/ftu/fsys" "unbewohnte/ftu/fsys"
) )
// constructs a ready to send FILE packet // constructs a ready to send FILE packet

4
src/protocol/packetDecode.go

@ -1,6 +1,6 @@
/* /*
ftu - file transferring utility. ftu - file transferring utility.
Copyright (C) 2021 Kasyanov Nikolay Alexeevich (Unbewohnte (https://unbewohnte.xyz/)) Copyright (C) 2021,2022 Kasyanov Nikolay Alexeyevich (Unbewohnte)
This file is a part of ftu This file is a part of ftu
@ -26,7 +26,7 @@ import (
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"github.com/Unbewohnte/ftu/fsys" "unbewohnte/ftu/fsys"
) )
var ErrorWrongPacket error = fmt.Errorf("wrong type of packet header") var ErrorWrongPacket error = fmt.Errorf("wrong type of packet header")

2
src/protocol/protocol_test.go

@ -1,6 +1,6 @@
/* /*
ftu - file transferring utility. ftu - file transferring utility.
Copyright (C) 2021 Kasyanov Nikolay Alexeevich (Unbewohnte (https://unbewohnte.xyz/)) Copyright (C) 2021,2022 Kasyanov Nikolay Alexeyevich (Unbewohnte)
This file is a part of ftu This file is a part of ftu

2
src/protocol/recv.go

@ -1,6 +1,6 @@
/* /*
ftu - file transferring utility. ftu - file transferring utility.
Copyright (C) 2021 Kasyanov Nikolay Alexeevich (Unbewohnte (https://unbewohnte.xyz/)) Copyright (C) 2021,2022 Kasyanov Nikolay Alexeyevich (Unbewohnte)
This file is a part of ftu This file is a part of ftu

60
src/protocol/send.go

@ -1,6 +1,6 @@
/* /*
ftu - file transferring utility. ftu - file transferring utility.
Copyright (C) 2021 Kasyanov Nikolay Alexeevich (Unbewohnte (https://unbewohnte.xyz/)) Copyright (C) 2021,2022 Kasyanov Nikolay Alexeyevich (Unbewohnte)
This file is a part of ftu This file is a part of ftu
@ -28,8 +28,8 @@ import (
"io" "io"
"net" "net"
"github.com/Unbewohnte/ftu/encryption" "unbewohnte/ftu/encryption"
"github.com/Unbewohnte/ftu/fsys" "unbewohnte/ftu/fsys"
) )
// Sends given packet to connection. // Sends given packet to connection.
@ -137,13 +137,15 @@ func SendTransferOffer(connection net.Conn, file *fsys.File, dir *fsys.Directory
var ErrorSentAll error = fmt.Errorf("sent the whole file") 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 // another piece util the file has been fully sent. If encrKey is not nil - encrypts each packet with
// this key // this key. Returns amount of filebytes written to the connection
func SendPiece(file *fsys.File, connection net.Conn, encrKey []byte) error { func SendPiece(file *fsys.File, connection net.Conn, encrKey []byte) (uint64, error) {
var sentBytes uint64 = 0
err := file.Open() err := file.Open()
if err != nil { if err != nil {
return err return sentBytes, err
} }
defer file.Close() defer file.Close()
@ -152,7 +154,7 @@ func SendPiece(file *fsys.File, connection net.Conn, encrKey []byte) error {
} }
if file.Size == file.SentBytes { if file.Size == file.SentBytes {
return ErrorSentAll return sentBytes, ErrorSentAll
} }
fileBytesPacket := Packet{ fileBytesPacket := Packet{
@ -164,7 +166,7 @@ func SendPiece(file *fsys.File, connection net.Conn, encrKey []byte) error {
// write file ID first // write file ID first
err = binary.Write(packetBodyBuff, binary.BigEndian, file.ID) err = binary.Write(packetBodyBuff, binary.BigEndian, file.ID)
if err != nil { if err != nil {
return err return sentBytes, err
} }
// fill the remaining space of packet with the contents of a file // fill the remaining space of packet with the contents of a file
@ -172,7 +174,7 @@ func SendPiece(file *fsys.File, connection net.Conn, encrKey []byte) error {
if encrKey != nil { if encrKey != nil {
// account for padding // account for padding
canSendBytes -= 32 canSendBytes -= 48
} }
if (file.Size - file.SentBytes) < canSendBytes { if (file.Size - file.SentBytes) < canSendBytes {
@ -183,9 +185,10 @@ func SendPiece(file *fsys.File, connection net.Conn, encrKey []byte) error {
read, err := file.Handler.ReadAt(fileBytes, int64(file.SentBytes)) read, err := file.Handler.ReadAt(fileBytes, int64(file.SentBytes))
if err != nil { if err != nil {
return err return sentBytes, err
} }
file.SentBytes += uint64(read) file.SentBytes += uint64(read)
sentBytes += uint64(canSendBytes)
packetBodyBuff.Write(fileBytes) packetBodyBuff.Write(fileBytes)
@ -194,12 +197,45 @@ func SendPiece(file *fsys.File, connection net.Conn, encrKey []byte) error {
if encrKey != nil { if encrKey != nil {
err = fileBytesPacket.EncryptBody(encrKey) err = fileBytesPacket.EncryptBody(encrKey)
if err != nil { if err != nil {
return err return sentBytes, err
} }
} }
// send it to the other side // send it to the other side
err = SendPacket(connection, fileBytesPacket) err = SendPacket(connection, fileBytesPacket)
if err != nil {
return 0, err
}
return sentBytes, nil
}
// Sends a symlink to the other side. If encrKey is not nil - encrypts the packet with this key
func SendSymlink(symlink *fsys.Symlink, connection net.Conn, encrKey []byte) error {
symlinkPacket := Packet{
Header: HeaderSymlink,
}
symlinkPacketBodyBuff := new(bytes.Buffer)
// SYMLINK~(string size in binary)(location in the filesystem)(string size in binary)(location of a target)
binary.Write(symlinkPacketBodyBuff, binary.BigEndian, uint64(len(symlink.Path)))
symlinkPacketBodyBuff.Write([]byte(symlink.Path))
binary.Write(symlinkPacketBodyBuff, binary.BigEndian, uint64(len(symlink.TargetPath)))
symlinkPacketBodyBuff.Write([]byte(symlink.TargetPath))
symlinkPacket.Body = symlinkPacketBodyBuff.Bytes()
if encrKey != nil {
err := symlinkPacket.EncryptBody(encrKey)
if err != nil {
return err
}
}
err := SendPacket(connection, symlinkPacket)
if err != nil { if err != nil {
return err return err
} }

1
src/testfiles/testdir/testsymlink2.txt

@ -0,0 +1 @@
../testfiles/testdir/testfile2.txt

1
src/testfiles/testsymlink.txt

@ -0,0 +1 @@
../testfiles/testfile.txt
Loading…
Cancel
Save