package metainfo import ( "crypto/sha1" "fmt" "os" "storrent/bencode" ) type File struct { Info Info InfoHash [20]byte Size int64 } type Info struct { Name string PieceSize int64 Pieces [][20]byte Size int64 Files []Entry } type Entry struct { Size int64 Offset int64 Path []string } type Segment struct { File int Offset int64 Size int64 } func (i *Info) Segments(off, size int64) []Segment { var segs []Segment for idx, f := range i.Files { end := f.Offset + f.Size if off >= end { continue } n := min(size, end-off) segs = append(segs, Segment{File: idx, Offset: off - f.Offset, Size: n}) off += n size -= n if size == 0 { break } } return segs } func Parse(path string) (*File, error) { data, err := os.ReadFile(path) if err != nil { return nil, err } return ParseBytes(data) } func ParseBytes(data []byte) (*File, error) { v, _, err := bencode.Decode(data) if err != nil { return nil, err } d, ok := v.(map[string]any) if !ok { return nil, fmt.Errorf("invalid torrent dict") } f := &File{} raw, err := findInfoBytes(data) if err != nil { return nil, err } f.InfoHash = sha1.Sum(raw) dict, ok := d["info"].(map[string]any) if !ok { return nil, fmt.Errorf("invalid info dict") } f.Info, err = parseInfo(dict) if err != nil { return nil, err } if f.Info.Size > 0 { f.Size = f.Info.Size } else { for _, e := range f.Info.Files { f.Size += e.Size } } return f, nil } func findInfoBytes(data []byte) ([]byte, error) { if len(data) == 0 || data[0] != 'd' { return nil, fmt.Errorf("not a dict") } i := 1 for i < len(data) && data[i] != 'e' { k, n, err := bencode.DecodeString(data[i:]) if err != nil { return nil, err } i += n if k == "info" { _, n, err := bencode.Decode(data[i:]) if err != nil { return nil, err } return data[i : i+n], nil } _, n, err = bencode.Decode(data[i:]) if err != nil { return nil, err } i += n } return nil, fmt.Errorf("info not found") } func parseInfo(d map[string]any) (Info, error) { var info Info name, ok := d["name"].(string) if !ok { return info, fmt.Errorf("invalid name") } info.Name = name pl, ok := d["piece length"].(int64) if !ok { return info, fmt.Errorf("invalid piece length") } info.PieceSize = pl ps, ok := d["pieces"].(string) if !ok || len(ps)%20 != 0 { return info, fmt.Errorf("invalid pieces") } npieces := len(ps) / 20 info.Pieces = make([][20]byte, npieces) for i := range npieces { copy(info.Pieces[i][:], ps[i*20:(i+1)*20]) } if n, ok := d["length"].(int64); ok { info.Size = n return info, nil } fs, ok := d["files"].([]any) if !ok { return info, fmt.Errorf("invalid files") } off := int64(0) for _, f := range fs { fd, ok := f.(map[string]any) if !ok { return info, fmt.Errorf("invalid file entry") } n, ok := fd["length"].(int64) if !ok { return info, fmt.Errorf("invalid file length") } list, ok := fd["path"].([]any) if !ok { return info, fmt.Errorf("invalid file path") } var path []string for _, p := range list { s, ok := p.(string) if !ok { return info, fmt.Errorf("invalid path element") } path = append(path, s) } info.Files = append(info.Files, Entry{Size: n, Offset: off, Path: path}) off += n } return info, nil }