Compare commits

..

22 Commits
2.1.0 ... 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
Unbewohnte d0a0eaf780 [Make] output clean executable names 3 years ago
Unbewohnte 5aae65980d [Make] remove release folders after zipping 3 years ago
Unbewohnte 3a40920af5 Verbose output 3 years ago
Unbewohnte 502877c515 [main] changed version string; [make] pkgrelease for easy cross-building 3 years ago
Unbewohnte 09e3c7b960 [node] print transfer info. The output is not flooded anymore on receiver 3 years ago
Unbewohnte 1a302cb9fb [node] bugfixes? [make] cross compilation automatisation 3 years ago
  1. 33
      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. 443
      src/node/node.go
  19. 11
      src/node/options.go
  20. 2
      src/protocol/constants.go
  21. 17
      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

33
Makefile

@ -3,10 +3,39 @@
SRC_DIR := src/ SRC_DIR := src/
EXE_NAME := ftu EXE_NAME := ftu
INSTALLATION_DIR := /usr/local/bin/ INSTALLATION_DIR := /usr/local/bin/
RELEASE_DIR := release
LICENSE_FILE := COPYING
INSTALLATION_SCRIPT := install.sh
all: all:
cd $(SRC_DIR) && go build && mv $(EXE_NAME) .. cd $(SRC_DIR) && go build && mv $(EXE_NAME) ..
release:
rm -rf $(RELEASE_DIR)
mkdir $(RELEASE_DIR)
mkdir $(RELEASE_DIR)/ftu_linux_amd64
mkdir $(RELEASE_DIR)/ftu_darwin_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)/ftu_linux_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)/ftu_windows_amd64
cp $(LICENSE_FILE) $(RELEASE_DIR)/ftu_linux_amd64
cp $(INSTALLATION_SCRIPT) $(RELEASE_DIR)/ftu_linux_amd64
cp $(LICENSE_FILE) $(RELEASE_DIR)/ftu_darwin_amd64
cp $(LICENSE_FILE) $(RELEASE_DIR)/ftu_windows_amd64
cd $(RELEASE_DIR) && zip -r ftu_linux_amd64 ftu_linux_amd64/
cd $(RELEASE_DIR) && zip -r ftu_darwin_amd64 ftu_darwin_amd64/
cd $(RELEASE_DIR) && zip -r ftu_windows_amd64 ftu_windows_amd64/
rm -rf $(RELEASE_DIR)/ftu_linux_amd64
rm -rf $(RELEASE_DIR)/ftu_darwin_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) ..
@ -14,7 +43,7 @@ install: all
cp $(EXE_NAME) $(INSTALLATION_DIR) cp $(EXE_NAME) $(INSTALLATION_DIR)
test: test:
cd $(SRC_DIR) && go test ./... ; cd .. cd $(SRC_DIR) && go test ./...
clean: clean:
rm $(EXE_NAME) rm -rf $(EXE_NAME) $(RELEASE_DIR)

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.0" 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,6 +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("?", 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")
@ -51,14 +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("| -? [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")
@ -81,7 +83,6 @@ func init() {
fmt.Printf("| ftu -r -s /home/user/homework/\n") fmt.Printf("| ftu -r -s /home/user/homework/\n")
fmt.Printf("| creates a node that will send every file in the directory !RECUSRIVELY!\n\n\n") fmt.Printf("| creates a node that will send every file in the directory !RECUSRIVELY!\n\n\n")
} }
flag.Parse() flag.Parse()
@ -97,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)
} }
@ -118,13 +119,14 @@ func init() {
func main() { func main() {
nodeOptions := node.NodeOptions{ nodeOptions := node.NodeOptions{
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,
}, },
@ -132,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)
} }

443
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,21 +32,15 @@ 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 netInfoInfo struct { type netInfo struct {
ConnAddr string // address to connect to. Does not include port ConnAddr string // address to connect to. Does not include port
Conn net.Conn // the core TCP connection of the node. Self-explanatory Conn net.Conn // the core TCP connection of the node. Self-explanatory
Port uint // a port to connect to/listen on Port uint // a port to connect to/listen on
@ -59,15 +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
// CurrentFileIndex uint64 // an index of a file with a specific ID that is currently being transported 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
@ -78,11 +79,12 @@ type transferInfo struct {
// Sender and receiver in one type ! // Sender and receiver in one type !
type Node struct { type Node struct {
verboseOutput bool
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
netInfo *netInfoInfo stopped bool // the way to exit the mainloop in case of an external error or a successful end of a transfer
state *nodeInnerstates netInfo *netInfo
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
} }
@ -119,28 +121,30 @@ func NewNode(options *NodeOptions) (*Node, error) {
} }
node := Node{ node := Node{
verboseOutput: options.VerboseOutput,
mutex: &sync.Mutex{}, mutex: &sync.Mutex{},
packetPipe: make(chan *protocol.Packet, 100), packetPipe: make(chan *protocol.Packet, 100),
isSending: options.IsSending, isSending: options.IsSending,
netInfo: &netInfoInfo{ 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,
}, },
}, },
} }
@ -153,14 +157,14 @@ func (node *Node) connect() error {
node.netInfo.Port = 7270 node.netInfo.Port = 7270
} }
fmt.Printf("Connecting to %s:%d...\n", node.netInfo.ConnAddr, node.netInfo.Port) fmt.Printf("\nConnecting to %s:%d...", node.netInfo.ConnAddr, node.netInfo.Port)
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", node.netInfo.ConnAddr, node.netInfo.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 { if err != nil {
return err return err
} }
fmt.Printf("Connected\n") fmt.Printf("\nConnected")
node.netInfo.Conn = conn node.netInfo.Conn = conn
@ -183,7 +187,7 @@ func (node *Node) disconnect() error {
return err return err
} }
node.state.Stopped = true node.stopped = true
} }
return nil return nil
@ -202,17 +206,38 @@ func (node *Node) waitForConnection() error {
return err return err
} }
fmt.Printf("New connection from %s\n", connection.RemoteAddr().String()) fmt.Printf("\nNew connection from %s", connection.RemoteAddr().String())
node.netInfo.Conn = connection node.netInfo.Conn = connection
return nil return nil
} }
// Starts the node in either sending or receiving state and performs the transfer // Prints information about the transfer after defined delay
func (node *Node) Start() { func (node *Node) printTransferInfo(delay time.Duration) error {
time.Sleep(delay)
switch node.isSending { switch node.isSending {
case true: case true:
if !node.transferInfo.Sending.AllowedToTransfer {
// do not print if the transfer has not been accepted yet
break
}
fmt.Printf("\r| (%.2f/%.2f MB)",
float32(node.transferInfo.Sending.SentBytes)/1024/1024,
float32(node.transferInfo.Sending.TotalTransferSize)/1024/1024,
)
case false:
fmt.Printf("\r| (%.2f/%.2f MB)",
float32(node.transferInfo.Receiving.ReceivedBytes)/1024/1024,
float32(node.transferInfo.Receiving.TotalDownloadSize)/1024/1024,
)
}
return nil
}
func (node *Node) send() {
// SENDER NODE // SENDER NODE
localIP, err := addr.GetLocal() localIP, err := addr.GetLocal()
@ -221,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("Sending \"%s\" (%.3f %s) locally on %s:%d\n", 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("Sending \"%s\" (%.3f %s) locally on %s:%d\n", 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)
} }
@ -267,7 +296,7 @@ func (node *Node) Start() {
// generate and send encryption key // generate and send encryption key
encrKey := encryption.Generate32AESkey() encrKey := encryption.Generate32AESkey()
node.netInfo.EncryptionKey = encrKey node.netInfo.EncryptionKey = encrKey
fmt.Printf("Generated encryption key: %s\n", encrKey) fmt.Printf("\nGenerated encryption key: %s\n", encrKey)
err = protocol.SendEncryptionKey(node.netInfo.Conn, encrKey) err = protocol.SendEncryptionKey(node.netInfo.Conn, encrKey)
if err != nil { if err != nil {
@ -278,20 +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")
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("The 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
@ -311,89 +345,47 @@ 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("Transfer allowed. Sending...\n")
// 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)
}
}
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)
}
} }
fmt.Printf("\n")
case protocol.HeaderReject: case protocol.HeaderReject:
node.state.Stopped = true node.stopped = true
fmt.Printf("\nTransfer rejected. Disconnecting...")
fmt.Printf("Transfer rejected. Disconnecting...\n")
case protocol.HeaderDisconnecting: case protocol.HeaderDisconnecting:
node.state.Stopped = true node.stopped = true
fmt.Printf("%s disconnected\n", node.netInfo.Conn.RemoteAddr()) fmt.Printf("\n%s disconnected", node.netInfo.Conn.RemoteAddr())
case protocol.HeaderAlreadyHave: case protocol.HeaderAlreadyHave:
// the other node already has a file with such ID. // the other node already has a file with such ID.
@ -408,30 +400,75 @@ func (node *Node) Start() {
node.transferInfo.Sending.FilesToSend = append(node.transferInfo.Sending.FilesToSend[:index], node.transferInfo.Sending.FilesToSend[index+1:]...) node.transferInfo.Sending.FilesToSend = append(node.transferInfo.Sending.FilesToSend[:index], node.transferInfo.Sending.FilesToSend[index+1:]...)
node.transferInfo.Sending.CurrentFileID++ node.transferInfo.Sending.CurrentFileID++
node.transferInfo.Sending.SentBytes += fileToSend.Size
node.transferInfo.Sending.InTransfer = false
if node.verboseOutput {
fmt.Printf("\n[File] receiver already has \"%s\"", fileToSend.Name)
}
} }
} }
} }
// 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("Transfer ended successfully\n") node.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)
}
}
node.state.Stopped = true 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
@ -443,10 +480,16 @@ 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
if node.verboseOutput {
fmt.Printf("\n[File] fully sent \"%s\" -- %d bytes", node.transferInfo.Sending.FilesToSend[currentFileIndex].Name, node.transferInfo.Sending.FilesToSend[currentFileIndex].Size)
}
fileIDBuff := new(bytes.Buffer) fileIDBuff := new(bytes.Buffer)
err = binary.Write(fileIDBuff, binary.BigEndian, node.transferInfo.Sending.FilesToSend[currentFileIndex].ID) err = binary.Write(fileIDBuff, binary.BigEndian, node.transferInfo.Sending.FilesToSend[currentFileIndex].ID)
if err != nil { if err != nil {
@ -467,31 +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("An error occured while sending a piece of \"%s\": %s\n", 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("Could not connect to %s:%d\n", 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)
} }
@ -501,18 +546,23 @@ 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 {
fmt.Printf("\n")
node.disconnect() node.disconnect()
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("The connection has been closed unexpectedly\n") fmt.Printf("\nConnection has been closed unexpectedly\n")
os.Exit(-1) os.Exit(-1)
} }
@ -536,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 {
@ -544,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 {
@ -568,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("[ERROR] could not create a directory\n") 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)
@ -586,15 +641,6 @@ func (node *Node) Start() {
panic(err) panic(err)
} }
// notify the node that we`re ready to transportation. No need
// for encryption because the body is nil
err = protocol.SendPacket(node.netInfo.Conn, protocol.Packet{
Header: protocol.HeaderReady,
})
if err != nil {
panic(err)
}
} else { } else {
// no // no
@ -608,20 +654,23 @@ 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)
} }
if 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 // does not have a parent dir
file.Path = filepath.Join(node.transferInfo.Receiving.DownloadsPath, file.Name) file.Path = filepath.Join(node.transferInfo.Receiving.DownloadsPath, file.Name)
} else { } else {
@ -641,17 +690,18 @@ func (node *Node) Start() {
// check if it is the exact file // check if it is the exact file
existingFileHandler, err := os.Open(file.Path) existingFileHandler, err := os.Open(file.Path)
if err != nil { if err != nil {
os.Remove(file.Path) panic(err)
} }
existingFileChecksum, _ := checksum.GetPartialCheckSum(existingFileHandler) existingFileChecksum, err := checksum.GetPartialCheckSum(existingFileHandler)
if err != nil {
panic(err)
}
if existingFileChecksum == file.Checksum { if existingFileChecksum == file.Checksum {
// it`s the exact same file. No need to receive it again // it`s the exact same file. No need to receive it again
// notify the other node // notify the other node
fmt.Printf("Already have \"%s\". Skipping...\n\n", file.Name)
alreadyHavePacketBodyBuffer := new(bytes.Buffer) alreadyHavePacketBodyBuffer := new(bytes.Buffer)
binary.Write(alreadyHavePacketBodyBuffer, binary.BigEndian, file.ID) binary.Write(alreadyHavePacketBodyBuffer, binary.BigEndian, file.ID)
@ -660,18 +710,36 @@ func (node *Node) Start() {
Body: alreadyHavePacketBodyBuffer.Bytes(), Body: alreadyHavePacketBodyBuffer.Bytes(),
} }
if node.netInfo.EncryptionKey != nil {
encryptedBody, err := encryption.Encrypt(node.netInfo.EncryptionKey, alreadyHavePacket.Body) encryptedBody, err := encryption.Encrypt(node.netInfo.EncryptionKey, alreadyHavePacket.Body)
if err != nil { if err != nil {
panic(err) panic(err)
} }
alreadyHavePacket.Body = encryptedBody alreadyHavePacket.Body = encryptedBody
}
protocol.SendPacket(node.netInfo.Conn, alreadyHavePacket) protocol.SendPacket(node.netInfo.Conn, alreadyHavePacket)
node.transferInfo.Receiving.ReceivedBytes += file.Size
if node.verboseOutput {
fmt.Printf("\n[File] already have \"%s\"", file.Name)
}
} 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()
@ -681,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:
@ -699,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()
@ -712,26 +788,17 @@ 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)
}
} }
} }
// notify the other node that this one is ready readyPacket := protocol.Packet{
err = protocol.SendPacket(node.netInfo.Conn, protocol.Packet{
Header: protocol.HeaderReady, Header: protocol.HeaderReady,
})
if err != nil {
panic(err)
} }
protocol.SendPacket(node.netInfo.Conn, readyPacket)
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
@ -744,10 +811,16 @@ func (node *Node) Start() {
if acceptedFile.ID == fileID { if acceptedFile.ID == fileID {
// accepted // accepted
if node.verboseOutput {
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:]...)
@ -758,13 +831,14 @@ func (node *Node) Start() {
panic(err) panic(err)
} }
fmt.Printf("\n| Checking hashes for file \"%s\"\n", acceptedFile.Name)
if realChecksum != acceptedFile.Checksum { if realChecksum != acceptedFile.Checksum {
fmt.Printf("| %s --- %s file is corrupted\n", realChecksum, acceptedFile.Checksum) if node.verboseOutput {
fmt.Printf("\n[ERROR] \"%s\" is corrupted", acceptedFile.Name)
}
acceptedFile.Close() acceptedFile.Close()
break break
} else { } else {
fmt.Printf("| %s --- %s\n", realChecksum, acceptedFile.Checksum)
acceptedFile.Close() acceptedFile.Close()
break break
} }
@ -790,21 +864,64 @@ func (node *Node) Start() {
node.netInfo.EncryptionKey = encrKey node.netInfo.EncryptionKey = encrKey
fmt.Printf("Got an encryption key: %s\n", 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("%s disconnected\n", node.netInfo.Conn.RemoteAddr()) fmt.Printf("\n%s disconnected", node.netInfo.Conn.RemoteAddr())
} }
} }
}
// 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()
} }
} }

11
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
} }
@ -34,6 +34,7 @@ type ClientSideNodeOptions struct {
type NodeOptions struct { type NodeOptions struct {
IsSending bool IsSending bool
WorkingPort uint WorkingPort uint
ServerSide *ServerSideNodeOptions VerboseOutput bool
ClientSide *ClientSideNodeOptions SenderSide *SenderNodeOptions
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

17
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,8 +55,8 @@ 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. The sender is not allowed to "spam" FILEBYTES // FILEBYTES or FILE packet. The sender is not allowed to "spam" FILEBYTES or FILE
// packets without the permission of receiver. // packets without the permission (packet with this header) from receiver.
// ie: READY!~ // ie: READY!~
const HeaderReady Header = "READY" const HeaderReady Header = "READY"
@ -114,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