首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >去为Storj做作业面试挑战

去为Storj做作业面试挑战
EN

Code Review用户
提问于 2020-02-04 12:25:09
回答 1查看 1.1K关注 0票数 1

去年12月,我接受了Storj的采访,但在我的代码挑战之后失败了。Storj很好地支付了我完成挑战所需的时间,但拒绝给出任何反馈,说明我提交的材料在哪里或为什么达不到他们的标准。

这个代码挑战的回购可以在这里找到:https://github.com/Samyoul/storj-file-sender

问题摘要指出:

假设您有两台笔记本(A和B)和一台服务器。笔记本电脑在不同的房子里,每台都在防火墙后面,所以笔记本电脑A不能和笔记本电脑B说话,反之亦然。服务器位于某个数据中心,两台笔记本电脑都可以与其对话。笔记本电脑A的用户想要发送一个文件给笔记本电脑B的用户。用户正在打电话,这样他们就可以交换信息,而不是文件本身。您需要编写三个程序:

  • 发送者-这个程序将运行在笔记本电脑A上。
  • 接收器-这个程序将运行在笔记本电脑B上。
  • 继电器-这个程序将运行在服务器上,这两台笔记本电脑都可以到达。

我的发件人代码:

代码语言:javascript
复制
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
}

我的中继代码:

代码语言:javascript
复制
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
}

我的接收器代码:

代码语言:javascript
复制
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,但这是我编写的应用程序的核心。我还有什么能做得更好的?谢谢。

EN

回答 1

Code Review用户

发布于 2020-02-05 10:00:42

因为OP代码使用的是糟糕的抽象,所以应该产生的代码评审需要从头开始编写一个全新的解决方案,说明应该按照OP选择的路径完成的工作。

这是一项很大的工作,可能最终代码甚至没有展示针对OP代码某些特定缺陷的具体解决方案,因为新的实现可能从本质上不会受到这个或那个缺陷的影响。

下面是关于OP代码的一些评论,然后是另一种方法建议

  • sm变量的使用似乎很活跃,您应该使用go run参数对代码进行-race处理。
  • 中继代码管理对洪水的影响较弱。它们缺乏包含超时时间的最大计数限制和生存期系统。
  • 发送/接收操作可能很弱,无法窃取代码。
  • 标题是一个映射字符串,我将使用一个定义良好的结构
  • 这是奇怪的hdr := *new([]byte),只需写hdr := []byte{}
  • 测试并不是测试大部分用例,当前的api不允许它。

脏代码,但功能强大,userA与userB共享文件。第三方rdv点是由tor网络提供的,用户必须同时交换EP和凭证。

代码语言:javascript
复制
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双向连接,无需开箱即用,尽管某些企业级网络环境可能会出现问题。

我不知道这是否能通过测试(.)

票数 1
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/236641

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档