package meta import ( "bytes" "crypto/sha1" "errors" "fmt" "os" "path" "time" ) var ErrNotImplemented = errors.New("not implemented") type File struct { Length int `bencode:"length"` MD5Sum []byte `bencode:"md5sum,optional"` Path []string `bencode:"path"` } func (f File) Name() string { return path.Join(f.Path...) } type Piece struct { Length int CheckSum [sha1.Size]byte } type Info struct { Files []File `bencode:"files"` Length int `bencode:"length"` MD5Sum []byte `bencode:"md5sum,optional"` Name string `bencode:"name"` PieceLength int `bencode:"piece length"` Pieces []byte `bencode:"pieces"` Private bool `bencode:"private"` } func (i Info) TotalLength() int { if i.Length > 0 { return i.Length } var l int for _, f := range i.Files { l += f.Length } return l } func (i Info) GetPieces() []Piece { length := i.TotalLength() n := length / i.PieceLength last := length % i.PieceLength if last > 0 { n++ } p := make([]Piece, n) for k := 0; k < n; k++ { p[k].Length = i.PieceLength if k+1 == n { p[k].Length = length } off := k * sha1.Size copy(p[k].CheckSum[:], i.Pieces[off:off+sha1.Size]) } return p } func (i Info) NPieces() int { return len(i.Pieces) / sha1.Size } func (i Info) Check(b []byte, n int) bool { sum := sha1.Sum(b) off := n * sha1.Size return bytes.Equal(i.Pieces[off:off+sha1.Size], sum[:]) } func (i Info) CheckSum(n int) []byte { off := n * sha1.Size return i.Pieces[off : off+sha1.Size] } func (i Info) CheckSums() [][]byte { cs := make([][]byte, i.NPieces()) for n := 0; n < i.NPieces(); n++ { cs[n] = i.CheckSum(n) } return cs } func (i Info) FullPath(n int) (string, error) { if i.isSingleFile() { return i.Name, nil } if n >= len(i.Files) || n < 0 { return "", errors.New("out of range") } return path.Join(i.Name, i.Files[n].Name()), nil } func (i Info) Offset(piece int) int { return i.PieceLength * piece } func (i Info) FindFile(off int) (int, int) { for n, l := range i.Files { if l.Length > off { return n, off } off -= l.Length } return 0, off } /* * 0 1 2 3 4 5 6 7 #piece * |----|----|----|----|----|----|----|--| pieces * |-|----|--|----|----------|-|----|----| files * 0 1 2 3 4 5 6 7 #file */ func (i Info) WriteAt(b []byte, off int64) (n int, err error) { return 0, ErrNotImplemented } // ReadAt ... func (i Info) ReadAt(b []byte, off int64) (n int, err error) { if i.isSingleFile() { fd, err := os.OpenFile(i.Name, os.O_RDWR|os.O_CREATE, 0644) if err != nil { return 0, err } defer fd.Close() return fd.ReadAt(b, int64(i.PieceLength)*off) } else { n, off := i.FindFile(int(off)) start := off end := i.PieceLength for k := n; k < len(i.Files); k++ { f := i.Files[n] p, err := i.FullPath(k) if err != nil { return 0, err } fd, err := os.OpenFile(p, os.O_RDWR|os.O_CREATE, 0644) if err != nil { return 0, err } defer fd.Close() if end > f.Length { end = f.Length } fd.ReadAt(b[start:end], int64(off)) start = end end = i.PieceLength off += f.Length if start == end { return cap(b), nil } } } return 0, ErrNotImplemented } /* func (i Info) WriteAt(data []byte, chunk, offcet int) error { return nil } func (i Info) ReadAt(chunk, offset, length int) ([]byte, error) { if length == 0 { length = i.PieceLength offset = 0 } data := make([]byte, length) start := chunk * i.PieceLength var n, off int for n = range i.Files { off += i.Files[n].Length if off > start { break } } // ... return data, nil } */ func (i Info) isSingleFile() bool { return len(i.Files) == 0 } // Last returns size of last truncated piece func (i Info) Last() int { return i.TotalLength() % i.PieceLength } // Full returns count of full pieces func (i Info) Full() int { return i.TotalLength() / i.PieceLength } func (i Info) String() string { var s string for n, f := range i.Files { p, err := i.FullPath(n) if err != nil { panic(err) } s += fmt.Sprintf(" %s (%d)\n", p, f.Length) } s += fmt.Sprintf("%s (%d) ", i.Name, i.TotalLength()) s += fmt.Sprintf("%d × %d + %d\n", i.Full(), i.PieceLength, i.Last()) return s } type Torrent struct { Announce string `bencode:"announce"` AnnounceList [][]string `bencode:"announce-list,optional"` Comment string `bencode:"comment,optional"` CreatedBy string `bencode:"created by,optional"` CreationDate time.Time `bencode:"creation date,optional"` Encoding string `bencode:"encoding,optional"` HTTPSeeds []string `bencode:"httpseeds,optional"` Info Info `bencode:"info"` URLList string `bencode:"url-list,optional"` InfoHash []byte `bencode:"-"` } func (t Torrent) String() string { s := fmt.Sprintf("announce: %s\n", t.Announce) s += fmt.Sprintf("comment: %s\n", t.Comment) s += fmt.Sprintf("date: %s\n", t.CreationDate) s += fmt.Sprintf("%v", t.Info) s += fmt.Sprintf("info hash: %x", t.InfoHash) return s } /* metainfo file.: OpenBSD_songs_ogg-2016-03-25-0127.torrent info hash.....: 1aa5af9f6f533961a65169bdeeb801e611724d32 directory name: OpenBSD_songs_ogg files.........: 38 OpenBSD_songs_ogg/song30.ogg (2636244) 2.51 MB OpenBSD_songs_ogg/song31.ogg (2381018) 2.27 MB OpenBSD_songs_ogg/song32.ogg (2253880) 2.15 MB OpenBSD_songs_ogg/song33.ogg (3238559) 3.09 MB OpenBSD_songs_ogg/song34.ogg (5062603) 4.83 MB OpenBSD_songs_ogg/song35.ogg (7191264) 6.86 MB OpenBSD_songs_ogg/song36.ogg (5197630) 4.96 MB OpenBSD_songs_ogg/song37.ogg (13937255) 13.29 MB OpenBSD_songs_ogg/song38.ogg (5860681) 5.59 MB OpenBSD_songs_ogg/song38b.ogg (5759105) 5.49 MB OpenBSD_songs_ogg/song39.ogg (5954997) 5.68 MB OpenBSD_songs_ogg/song40.ogg (3610740) 3.44 MB OpenBSD_songs_ogg/song41.ogg (8382781) 7.99 MB OpenBSD_songs_ogg/song42.ogg (6713047) 6.40 MB OpenBSD_songs_ogg/song43.ogg (6834505) 6.52 MB OpenBSD_songs_ogg/song44.ogg (4628329) 4.41 MB OpenBSD_songs_ogg/song45.ogg (4737475) 4.52 MB OpenBSD_songs_ogg/song46.ogg (3766867) 3.59 MB OpenBSD_songs_ogg/song47.ogg (6586251) 6.28 MB OpenBSD_songs_ogg/song48.ogg (3143847) 3.00 MB OpenBSD_songs_ogg/song49.ogg (6006834) 5.73 MB OpenBSD_songs_ogg/song50.ogg (4184951) 3.99 MB OpenBSD_songs_ogg/song51.ogg (4216946) 4.02 MB OpenBSD_songs_ogg/song52.ogg (4283099) 4.08 MB OpenBSD_songs_ogg/song53.ogg (4635216) 4.42 MB OpenBSD_songs_ogg/song54.ogg (3195265) 3.05 MB OpenBSD_songs_ogg/song55.ogg (6160151) 5.87 MB OpenBSD_songs_ogg/song56.ogg (5471902) 5.22 MB OpenBSD_songs_ogg/song57.ogg (4057801) 3.87 MB OpenBSD_songs_ogg/song58a.ogg (3252520) 3.10 MB OpenBSD_songs_ogg/song58b.ogg (4376163) 4.17 MB OpenBSD_songs_ogg/song58c.ogg (3595317) 3.43 MB OpenBSD_songs_ogg/song58d.ogg (7013091) 6.69 MB OpenBSD_songs_ogg/song59a.ogg (5766764) 5.50 MB OpenBSD_songs_ogg/song59b.ogg (5317415) 5.07 MB OpenBSD_songs_ogg/songsh.ogg (4890381) 4.66 MB OpenBSD_songs_ogg/songsi.ogg (5694670) 5.43 MB OpenBSD_songs_ogg/songty.ogg (6045645) 5.77 MB archive size..: 196041209 (747 * 262144 + 219641) 186.96 MB announce url..: http://OpenBSD.somedomain.net:6969/announce creation date.: Fri Mar 25 02:27:56 2016 created by....: mktorrent 1.0 comment.......: ogg files from OpenBSD/songs Created by andrew fresh (andrew@afresh1.com) http://OpenBSD.somedomain.net/ */ /* metainfo file.: OpenBSD_5.9_amd64_install59.iso-2016-03-29-0449.torrent info hash.....: e840038dea1998c39614dcd28594501df02bd32d file name.....: OpenBSD_5.9_amd64_install59.iso file size.....: 233662464 (891 * 262144 + 92160) 222.84 MB announce url..: http://OpenBSD.somedomain.net:6969/announce creation date.: Tue Mar 29 06:49:18 2016 created by....: mktorrent 1.0 comment.......: OpenBSD/5.9/amd64/install59.iso Created by andrew fresh (andrew@afresh1.com) http://OpenBSD.somedomain.net/ */