去年12月,我接受了Storj的采访,但在我的代码挑战之后失败了。Storj很好地支付了我完成挑战所需的时间,但拒绝给出任何反馈,说明我提交的材料在哪里或为什么达不到他们的标准。
这个代码挑战的回购可以在这里找到:https://github.com/Samyoul/storj-file-sender
问题摘要指出:
假设您有两台笔记本(A和B)和一台服务器。笔记本电脑在不同的房子里,每台都在防火墙后面,所以笔记本电脑A不能和笔记本电脑B说话,反之亦然。服务器位于某个数据中心,两台笔记本电脑都可以与其对话。笔记本电脑A的用户想要发送一个文件给笔记本电脑B的用户。用户正在打电话,这样他们就可以交换信息,而不是文件本身。您需要编写三个程序:
我的发件人代码:
package main
import (
"encoding/binary"
"errors"
"io"
"log"
"net"
"os"
"github.com/Samyoul/storj-file-sender/common"
"github.com/Samyoul/storj-file-sender/sender/codegen"
)
func main() {
// get arguments
args := os.Args
err := validateArgs(args)
if err != nil {
log.Fatalf("error - validating arguments : %s", err)
}
// checksum file
h, err := common.HashFile(args[2])
if err != nil {
log.Fatalf("error - checksumming file %s : %s", args[2], err)
}
// generate secret code
// Use the int64 encoded checksum of the file as part of the random seed
code := codegen.Make(int64(binary.BigEndian.Uint64(h.Sum(nil))))
// display secret code
println(code)
// open connection with relay
conn, err := net.Dial("tcp", args[1])
if err != nil {
log.Fatalf("error - making a connection : %s", err)
}
defer conn.Close()
// Set write buffer size
err = conn.(*net.TCPConn).SetWriteBuffer(common.BufferLimit)
if err != nil {
log.Fatalf("error - setting write buffer : %s", err)
}
// Write and send header on connection, send checksum and filename with header
hdr := common.MakeRequestHeaderSend(args[2], code, h.Sum(nil))
conn.Write(hdr)
// Open file hold ready to transfer
f, err := os.Open(args[2])
if err != nil {
log.Fatal(err)
}
defer f.Close()
}
func validateArgs(args []string) error {
if len(args) != 3 {
return errors.New(
"invalid number of arguments.\n" +
"expected : ./sender <relay-host>:<relay-port> <file-to-send>\n" +
"example : ./sender localhost:9021 corgis.mp4")
}
return nil
}我的中继代码:
package main
import (
"errors"
"fmt"
"io"
"log"
"net"
"os"
"sync"
"github.com/Samyoul/storj-file-sender/common"
)
type stream struct {
checksum []byte
filename []byte
sendConn chan net.Conn
wg sync.WaitGroup
}
func (s *stream) Close() {
close(s.sendConn)
}
type streamMap map[string]*stream
func main() {
// get init argument
args := os.Args
err := validateArgs(args)
if err != nil {
log.Fatalf("error - validating arguments : %s", err)
}
// open TCP server
l, err := net.Listen("tcp", args[1])
if err != nil {
log.Fatalf("error - starting tcp server : %s", err)
}
defer l.Close()
// Create streams map
sm := streamMap{}
// wait for connections
for {
conn, err := l.Accept()
if err != nil {
log.Println(err)
return // return don't exit because you don't want to kill your whole server over a single fail connection
}
// serve connections with successful connection
go handle(&sm, conn)
}
}
func handle(sm *streamMap, conn net.Conn) {
defer conn.Close()
// Get the connection header
// Added header so that I connection parameters can be exchanged between client and server
hdr, err := common.GetRequestHeader(conn)
if err != nil {
log.Printf("error - getting request header - %s", err)
return
}
// determine the type of connection coming in.
// switch logic to give handler for each the send and receive requests
switch string(hdr["Type"]) {
case common.HeaderSend:
err = send(sm, conn, hdr)
if err != nil {
log.Printf("error - processing send request - %s", err)
return
}
break
case common.HeaderReceive:
err = receive(sm, conn, hdr)
if err != nil {
log.Printf("error - processing receive request - %s", err)
return
}
break
default:
log.Println(hdr)
}
}
func validateArgs(args []string) error {
if len(args) != 2 {
return errors.New(
"invalid number of arguments.\n" +
"expected : ./relay :<port>\n" +
"example : ./relay :9021")
}
return nil
}
func send(sm *streamMap, conn net.Conn, hdr common.Header) error {
s := &stream{}
(*sm)[string(hdr["Code"])] = s
s.filename = hdr["Filename"]
s.checksum = hdr["Checksum"]
s.sendConn = make(chan net.Conn)
s.wg = sync.WaitGroup{}
defer s.Close()
err := conn.(*net.TCPConn).SetReadBuffer(common.BufferLimit)
if err != nil {
return err
}
s.wg.Add(1)
s.sendConn <- conn
s.wg.Wait()
delete(*sm, string(hdr["Code"]))
return nil
}
func receive(sm *streamMap, conn net.Conn, hdr common.Header) error {
// Check the stream exists in the stream map
s, ok := (*sm)[string(hdr["Code"])]
if !ok {
return errors.New(fmt.Sprintf("unrecognised secret code '%s'", hdr["Code"]))
}
rh := common.MakeResponseHeaderReceive(s.filename, s.checksum)
_, err := conn.Write(rh)
if err != nil {
return err
}
_, err = io.Copy(conn, <-s.sendConn)
if err != nil {
return err
}
s.wg.Done()
return nil
}我的接收器代码:
package main
import (
"bytes"
"errors"
"io"
"log"
"net"
"os"
"github.com/Samyoul/storj-file-sender/common"
)
func main() {
// get arguments
args := os.Args
err := validateArgs(args)
if err != nil {
log.Fatalf("error - validating arguments : %s", err)
}
// open connection with relay
conn, err := net.Dial("tcp", args[1])
if err != nil {
log.Fatalf("error - making a connection : %s", err)
}
defer conn.Close()
// make receive request to relay
reqH := common.MakeRequestHeaderReceive(args[2])
conn.Write(reqH)
// get receive response header from relay with checksum and filename
resH, err := common.GetResponseHeader(conn)
if err != nil {
log.Fatalf("error - reading response header : %s", err)
}
// start to receive data stream
fn := args[3] + string(resH["Filename"])
f, err := os.Create(fn)
if err != nil {
log.Fatalf("error - creating file : %s", err)
}
defer f.Close()
// write data to file
_, err = io.Copy(f, conn)
if err != nil {
log.Fatalf("error - creating file : %s", err)
}
// check file complete with checksum comparison.
h, err := common.HashFile(fn)
if err != nil {
log.Fatalf("error - checksumming file %s : %s", fn, err)
}
if bytes.Compare(h.Sum(nil), resH["Checksum"]) != 0 {
log.Fatalf("error - checksum does not match")
}
}
func validateArgs(args []string) error {
if len(args) != 4 {
return errors.New(
"invalid number of arguments.\n" +
"expected : ./receiver <relay-host>:<relay-port> <secret-code> <output-directory>\n" +
"example : ./receiver localhost:9021 this-is-a-secret-code out/")
}
return nil
}还有一些其他文件用于处理公共功能和随机代码gen,但这是我编写的应用程序的核心。我还有什么能做得更好的?谢谢。
发布于 2020-02-05 10:00:42
因为OP代码使用的是糟糕的抽象,所以应该产生的代码评审需要从头开始编写一个全新的解决方案,说明应该按照OP选择的路径完成的工作。
这是一项很大的工作,可能最终代码甚至没有展示针对OP代码某些特定缺陷的具体解决方案,因为新的实现可能从本质上不会受到这个或那个缺陷的影响。
下面是关于OP代码的一些评论,然后是另一种方法建议
go run参数对代码进行-race处理。hdr := *new([]byte),只需写hdr := []byte{}脏代码,但功能强大,userA与userB共享文件。第三方rdv点是由tor网络提供的,用户必须同时交换EP和凭证。
package main
import (
"context"
"crypto"
"crypto/ed25519"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"flag"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"os/signal"
"time"
"github.com/99designs/basicauth-go"
"github.com/clementauger/tor-prebuilt/embedded"
"github.com/cretz/bine/tor"
)
func main() {
var privateKey crypto.PrivateKey
if _, err := os.Stat("onion.pk"); os.IsNotExist(err) {
_, privateKey, err = ed25519.GenerateKey(rand.Reader)
if err != nil {
log.Fatal(err)
}
x509Encoded, err := x509.MarshalPKCS8PrivateKey(privateKey)
if err != nil {
log.Fatal(err)
}
pemEncoded := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: x509Encoded})
ioutil.WriteFile("onion.pk", pemEncoded, os.ModePerm)
} else {
d, _ := ioutil.ReadFile("onion.pk")
block, _ := pem.Decode(d)
x509Encoded := block.Bytes
privateKey, err = x509.ParsePKCS8PrivateKey(x509Encoded)
if err != nil {
log.Fatal(err)
}
}
d, err := ioutil.TempDir("", "")
if err != nil {
log.Fatal(err)
}
// Start tor with default config (can set start conf's DebugWriter to os.Stdout for debug logs)
fmt.Println("Starting and registering onion service, please wait a couple of minutes...")
t, err := tor.Start(nil, &tor.StartConf{TempDataDirBase: d, ProcessCreator: embedded.NewCreator(), NoHush: true})
if err != nil {
log.Panicf("Unable to start Tor: %v", err)
}
defer t.Close()
// Wait at most a few minutes to publish the service
listenCtx, listenCancel := context.WithTimeout(context.Background(), 3*time.Minute)
defer listenCancel()
// Create a v3 onion service to listen on any port but show as 80
onion, err := t.Listen(listenCtx, &tor.ListenConf{Key: privateKey, Version3: true, RemotePorts: []int{80}})
if err != nil {
log.Panicf("Unable to create onion service: %v", err)
}
defer onion.Close()
fmt.Printf("Open Tor browser and navigate to http://%v.onion\n", onion.ID)
// fmt.Println("Press enter to exit")
// Serve the current folder from HTTP
errCh := make(chan error, 1)
go func() {
errCh <- mainn(onion)
}()
// End when enter is pressed
// go func() {
// if _, err := fmt.Scanln(); err == nil {
// errCh <- nil
// }
// }()
c := make(chan os.Signal, 1)
signal.Notify(c)
select {
case err = <-errCh:
log.Panicf("Failed serving: %v", err)
case s := <-c:
fmt.Println("Got signal:", s)
}
}
func mainn(onion *tor.OnionService) error {
var dir string
var user string
var pwd string
flag.StringVar(&dir, "d", ".", "directory path to serve")
flag.StringVar(&user, "u", "user", "username")
flag.StringVar(&pwd, "p", "pass", "password")
flag.Parse()
users := map[string][]string{}
users[user] = []string{pwd}
middleware := basicauth.New(onion.ID, users)
h := middleware(http.FileServer(http.Dir(dir)))
return http.Serve(onion, h)
}由于底层实现是使用http库,所以可以使用各种中间件来提高安全性或其他方面。
使用tor,您有一个零配置的JIT双向连接,无需开箱即用,尽管某些企业级网络环境可能会出现问题。
我不知道这是否能通过测试(.)
https://codereview.stackexchange.com/questions/236641
复制相似问题