diff options
| -rw-r--r-- | cmd/mybittorrent/decoder.go | 140 | ||||
| -rw-r--r-- | cmd/mybittorrent/file.go | 74 | ||||
| -rw-r--r-- | cmd/mybittorrent/main.go | 22 | ||||
| -rw-r--r-- | go.mod | 2 | ||||
| -rw-r--r-- | go.sum | 2 |
5 files changed, 17 insertions, 223 deletions
diff --git a/cmd/mybittorrent/decoder.go b/cmd/mybittorrent/decoder.go deleted file mode 100644 index 641b042..0000000 --- a/cmd/mybittorrent/decoder.go +++ /dev/null @@ -1,140 +0,0 @@ -package main - -import ( - "fmt" - "strconv" - "strings" - "unicode" -) - -type Decoder struct { - raw string - decoderFuncs []DecoderFunc -} - -type DecoderFunc func(string) (any, int, error) - -func NewDecoder(raw string) *Decoder { - d := &Decoder{raw: raw} - return d.WithDecoderFunc(d.decodeSlice).WithDecoderFunc(d.decodeMap).WithDecoderFunc(d.decodeString).WithDecoderFunc(d.decodeInteger) -} - -func (d *Decoder) WithDecoderFunc(f DecoderFunc) *Decoder { - d.decoderFuncs = append(d.decoderFuncs, f) - return d -} - -func (d *Decoder) Decode() (any, error) { - for _, f := range d.decoderFuncs { - if r, l, err := f(d.raw); l > 0 || err != nil { - return r, nil - } - } - - return nil, fmt.Errorf("failed to decode value: %s", d.raw) -} - -func (d *Decoder) decodeString(chunk string) (any, int, error) { - if !unicode.IsDigit(rune(chunk[0])) { - return "", 0, nil - } - - var firstColonIndex int - - for i := 0; i < len(chunk); i++ { - if chunk[i] == ':' { - firstColonIndex = i - break - } - } - - lengthStr := chunk[:firstColonIndex] - - length, err := strconv.Atoi(lengthStr) - if err != nil { - return "", 0, err - } - - val := chunk[firstColonIndex+1 : firstColonIndex+1+length] - - return val, length + 1 + len(lengthStr), nil -} - -func (d *Decoder) decodeInteger(chunk string) (any, int, error) { - if rune(chunk[0]) != 'i' { - return 0, 0, nil - } - - num, err := strconv.Atoi(chunk[1:strings.IndexByte(chunk, 'e')]) - if err != nil { - return 0, 0, err - } - - return num, len(strconv.Itoa(num)) + 2, nil -} - -func (d *Decoder) decodeSlice(chunk string) (any, int, error) { - if rune(chunk[0]) != 'l' { - return nil, 0, nil - } - - if rune(chunk[1]) == 'e' { - return []any{}, 2, nil - } - - result := make([]any, 0, 32) - offset := 1 - - for rune(chunk[offset]) != 'e' { - for _, f := range d.decoderFuncs { - if v, l, err := f(chunk[offset:]); l > 0 { - if err != nil { - return nil, 0, err - } - result = append(result, v) - offset += l - break - } - } - } - - return result, offset + 1, nil -} - -func (d *Decoder) decodeMap(chunk string) (any, int, error) { - if rune(chunk[0]) != 'd' { - return nil, 0, nil - } - - if rune(chunk[1]) == 'e' { - return map[string]any{}, 2, nil - } - - result := make(map[string]any, 32) - offset := 1 - - for rune(chunk[offset]) != 'e' { - key, keyLen, err := d.decodeString(chunk[offset:]) - if err != nil { - return nil, 0, err - } - - if keyLen == 0 { - return nil, 0, fmt.Errorf("invalid bencoded map key length") - } - - for _, f := range d.decoderFuncs { - if v, l, err := f(chunk[offset+keyLen:]); l > 0 { - if err != nil { - return nil, 0, err - } - - result[key.(string)] = v - offset += keyLen + l - break - } - } - } - - return result, offset + 1, nil -} diff --git a/cmd/mybittorrent/file.go b/cmd/mybittorrent/file.go index 2302976..498ea3f 100644 --- a/cmd/mybittorrent/file.go +++ b/cmd/mybittorrent/file.go @@ -1,12 +1,6 @@ package main -import ( - "bytes" - "fmt" - "io" -) - -type File struct { +type Meta struct { Announce string `bencode:"announce"` Info FileInfo `bencode:"info"` } @@ -17,69 +11,3 @@ type FileInfo struct { PieceLength int `bencode:"piece length"` Pieces string `bencode:"pieces"` } - -func NewFile() *File { - return &File{} -} - -func (f *File) ReadFrom(r io.ReadCloser) error { - buf := new(bytes.Buffer) - - _, err := buf.ReadFrom(r) - if err != nil { - return err - } - - defer func(r io.ReadCloser) { - err := r.Close() - if err != nil { - fmt.Printf("failed to close: %v+\n", err) - } - }(r) - - if err != nil { - return err - } - - decoded, err := NewDecoder(buf.String()).Decode() - if err != nil { - return err - } - - content, ok := decoded.(map[string]any) - if !ok { - return fmt.Errorf("invalid contents") - } - - f.Announce, ok = content["announce"].(string) - if !ok { - return fmt.Errorf("invalid announce field") - } - - info, ok := content["info"].(map[string]any) - if !ok { - return fmt.Errorf("invalid info field") - } - - f.Info.Length, ok = info["length"].(int) - if !ok { - return fmt.Errorf("invalid info.length field") - } - - f.Info.Name, ok = info["name"].(string) - if !ok { - return fmt.Errorf("invalid info.name field") - } - - f.Info.PieceLength, ok = info["piece length"].(int) - if !ok { - return fmt.Errorf("invalid info.piece length field") - } - - f.Info.Pieces, ok = info["pieces"].(string) - if !ok { - return fmt.Errorf("invalid info.pieces field") - } - - return nil -} diff --git a/cmd/mybittorrent/main.go b/cmd/mybittorrent/main.go index 7905e01..d8014e7 100644 --- a/cmd/mybittorrent/main.go +++ b/cmd/mybittorrent/main.go @@ -1,10 +1,13 @@ package main import ( + "crypto/sha1" "encoding/json" "fmt" "os" - // bencode "github.com/jackpal/bencode-go" // Available if you need it! + "strings" + + bencode "github.com/jackpal/bencode-go" // Available if you need it! ) func main() { @@ -13,8 +16,7 @@ func main() { switch command { case "decode": bencodedValue := os.Args[2] - - decoded, err := NewDecoder(bencodedValue).Decode() + decoded, err := bencode.Decode(strings.NewReader(bencodedValue)) if err != nil { fmt.Println(err) return @@ -26,18 +28,20 @@ func main() { case "info": f, err := os.Open(os.Args[2]) if err != nil { - fmt.Println("Failed to open file: " + os.Args[2]) + fmt.Println("Failed to open file: ", os.Args[2]) } - tf := NewFile() + var meta Meta + if err = bencode.Unmarshal(f, &meta); err != nil { + panic(err) + } - err = tf.ReadFrom(f) - if err != nil { - fmt.Println("Failed to parse file: " + os.Args[2]) + sha := sha1.New() + if err = bencode.Marshal(sha, meta.Info); err != nil { panic(err) } - fmt.Printf("Tracker URL: %s\nLength: %d", tf.Announce, tf.Info.Length) + fmt.Printf("Tracker URL: %s\nLength: %d\nInfo Hash: %x", meta.Announce, meta.Info.Length, sha.Sum(nil)) default: fmt.Println("Unknown command: " + command) @@ -10,4 +10,4 @@ module github.com/codecrafters-io/bittorrent-starter-go go 1.22 -require github.com/jackpal/bencode-go v1.0.0 // indirect +require github.com/jackpal/bencode-go v1.0.2 // indirect @@ -1,2 +1,4 @@ github.com/jackpal/bencode-go v1.0.0 h1:lzbSPPqqSfWQnqVNe/BBY1NXdDpncArxShL10+fmFus= github.com/jackpal/bencode-go v1.0.0/go.mod h1:5FSBQ74yhCl5oQ+QxRPYzWMONFnxbL68/23eezsBI5c= +github.com/jackpal/bencode-go v1.0.2 h1:LcCNfZ344u0LpBPOZNjpCLps/wUOuN4r87Fy9+5yU8g= +github.com/jackpal/bencode-go v1.0.2/go.mod h1:6jI9mUjO3GQbZti3JizEfxTzRfWOM8oBBcwbwlTfceI= |