Skip to content

Commit da4870b

Browse files
committed
feat: implement chunked file transfer protocol
This change replaces the simple full-file transfer mechanism with a robust chunked protocol that supports: - Concurrent uploads/downloads with unique transfer IDs - 1MB chunk streaming to support large files - Transfer state management with acknowledgments - Round-robin scheduling for fair resource usage during concurrent transfers Signed-off-by: llogen <christoph.lange@blindspot.software>
1 parent f795b4c commit da4870b

11 files changed

Lines changed: 1616 additions & 727 deletions

File tree

cmds/dutctl/file_transfer.go

Lines changed: 418 additions & 0 deletions
Large diffs are not rendered by default.

cmds/dutctl/rpc.go

Lines changed: 16 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@ import (
1010
"errors"
1111
"fmt"
1212
"io"
13-
"io/fs"
1413
"log"
15-
"os"
1614
"strings"
1715

1816
"connectrpc.com/connect"
@@ -101,6 +99,8 @@ func (app *application) runRPC(device, command string, cmdArgs []string) error {
10199

102100
errChan := make(chan error, numWorkers)
103101

102+
ftManager := newClientFileTransferManager()
103+
104104
stream := app.rpcClient.Run(runCtx)
105105
req := &pb.RunRequest{
106106
Msg: &pb.RunRequest_Command{
@@ -175,51 +175,30 @@ func (app *application) runRPC(device, command string, cmdArgs []string) error {
175175
case *pb.Console_Stdin:
176176
log.Printf("Unexpected Console Stdin %q", string(consoleData.Stdin))
177177
}
178-
case *pb.RunResponse_FileRequest:
179-
path := msg.FileRequest.GetPath()
180-
log.Printf("File request for: %q\n", path)
178+
case *pb.RunResponse_FileTransferRequest:
179+
ftReq := msg.FileTransferRequest
181180

182-
content, err := os.ReadFile(path)
183-
if err != nil {
184-
errChan <- fmt.Errorf("reading requested file %q: %w", path, err)
181+
ftErr := ftManager.handleFileTransferRequest(ftReq, stream)
182+
if ftErr != nil {
183+
errChan <- ftErr
185184

186185
return
187186
}
188187

189-
err = stream.Send(&pb.RunRequest{
190-
Msg: &pb.RunRequest_File{
191-
File: &pb.File{
192-
Path: path,
193-
Content: content,
194-
},
195-
},
196-
})
197-
if err != nil {
198-
errChan <- fmt.Errorf("sending requested file %q: %w", path, err)
199-
200-
return
201-
}
188+
case *pb.RunResponse_FileChunk:
189+
chunk := msg.FileChunk
202190

203-
log.Printf("Sent file: %q\n", path)
204-
case *pb.RunResponse_File:
205-
path := msg.File.GetPath()
206-
content := msg.File.GetContent()
207-
208-
log.Printf("Received file: %q\n", path)
209-
210-
if len(content) == 0 {
211-
log.Println("Received empty file content")
212-
}
213-
214-
perm := 0600
215-
216-
err = os.WriteFile(path, content, fs.FileMode(perm))
217-
if err != nil {
218-
errChan <- fmt.Errorf("saving received file %q: %w", path, err)
191+
chunkErr := ftManager.handleFileChunk(chunk, stream)
192+
if chunkErr != nil {
193+
errChan <- chunkErr
219194

220195
return
221196
}
222197

198+
case *pb.RunResponse_FileTransferResponse:
199+
ftRes := msg.FileTransferResponse
200+
ftManager.handleFileTransferResponse(ftRes)
201+
223202
default:
224203
log.Printf("Unexpected message type %T", msg)
225204
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ require (
77
github.com/bougou/go-ipmi v0.7.8
88
github.com/go-playground/validator/v10 v10.30.1
99
github.com/google/go-cmp v0.7.0
10+
github.com/google/uuid v1.1.2
1011
github.com/stianeikeland/go-rpio/v4 v4.6.0
1112
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07
1213
golang.org/x/crypto v0.47.0
@@ -20,7 +21,6 @@ require (
2021
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
2122
github.com/go-playground/locales v0.14.1 // indirect
2223
github.com/go-playground/universal-translator v0.18.1 // indirect
23-
github.com/google/uuid v1.1.2 // indirect
2424
github.com/kr/pretty v0.3.0 // indirect
2525
github.com/kr/text v0.2.0 // indirect
2626
github.com/leodido/go-urn v1.4.0 // indirect

internal/dutagent/broker.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,14 @@ type Broker struct {
3030
func (b *Broker) init() {
3131
log.Print("Broker: Initializing")
3232

33-
b.session.printCh = make(chan string)
34-
b.session.stdinCh = make(chan []byte)
35-
b.session.stdoutCh = make(chan []byte)
36-
b.session.stderrCh = make(chan []byte)
37-
b.session.fileReqCh = make(chan string)
38-
b.session.fileCh = make(chan chan []byte)
33+
b.session.printCh = make(chan string, channelBufferSize)
34+
b.session.stdinCh = make(chan []byte, channelBufferSize)
35+
b.session.stdoutCh = make(chan []byte, channelBufferSize)
36+
b.session.stderrCh = make(chan []byte, channelBufferSize)
37+
b.session.activeUploads = make(map[string]*uploadState)
38+
b.session.activeDownloads = make(map[string]*downloadState)
39+
b.session.downloadOrder = make([]string, 0)
40+
b.session.downloadIndex = 0
3941

4042
// Buffer equals number of workers so error sends never block.
4143
b.errCh = make(chan error, numWorkers)

0 commit comments

Comments
 (0)