// Scrape scrapes using UDP format func (u UDPTracker) Scrape(files []data.FileRecord) []byte { // Iterate all files, grabbing their statistics stats := make([]udp.ScrapeStats, 0) for _, file := range files { stat := udp.ScrapeStats{} // Seeders stat.Seeders = uint32(file.Seeders()) // Completed stat.Completed = uint32(file.Completed()) // Leechers stat.Leechers = uint32(file.Leechers()) // Append to slice stats = append(stats[:], stat) } // Create UDP scrape response scrape := udp.ScrapeResponse{ Action: 2, TransID: u.TransID, FileStats: stats, } // Convert to UDP byte buffer buf, err := scrape.MarshalBinary() if err != nil { log.Println(err.Error()) return u.Error("Could not create UDP scrape response") } return buf }
// TestUDPTrackerScrape verifies that the UDP tracker scrape format is correct func TestUDPTrackerScrape(t *testing.T) { log.Println("TestUDPTrackerScrape()") // Generate mock data.FileRecord file := data.FileRecord{ InfoHash: "6465616462656566", Verified: true, } // Save mock file if !file.Save() { t.Fatalf("Failed to save mock file") } // Store file in slice files := make([]data.FileRecord, 0) files = append(files[:], file) // Create a UDP tracker, trigger a scrape tracker := UDPTracker{TransID: uint32(1234)} res := tracker.Scrape(files) // Decode response scrape := new(udp.ScrapeResponse) err := scrape.UnmarshalBinary(res) if err != nil { t.Fatalf("Failed to decode UDP scrape response") } log.Println(scrape) // Verify correct action if scrape.Action != 2 { t.Fatalf("Incorrect UDP action, expected 2") } // Encode response, verify same as before scrapeBuf, err := scrape.MarshalBinary() if err != nil { t.Fatalf("Failed to encode UDP scrape response") } if !bytes.Equal(res, scrapeBuf) { t.Fatalf("Byte slices are not identical") } // Delete mock file if !file.Delete() { t.Fatalf("Failed to delete mock file") } }
// Scrape scrapes using UDP format func (u UDPTracker) Scrape(files []data.FileRecord) []byte { // Buffered channel to receive UDP scrape stats structs resChan := make(chan *orderedScrape, len(files)) // Assign index to each file to preserve order during concurrent operations index := 0 // Iterate all files in parallel for _, f := range files { // Create orderedScrape, assign index to ensure correct return order during concurrent operations o := orderedScrape{ Index: index, File: udp.ScrapeStats{}, } index++ go func(f data.FileRecord, o *orderedScrape) { // Seeders count var err error seeders, err := f.Seeders() if err != nil { log.Println(err.Error()) } o.File.Seeders = uint32(seeders) // Completion count completed, err := f.Completed() if err != nil { log.Println(err.Error()) } o.File.Completed = uint32(completed) // Leechers count leechers, err := f.Leechers() if err != nil { log.Println(err.Error()) } o.File.Leechers = uint32(leechers) // Return results on channel resChan <- o }(f, &o) } // Fetch all results from channel received := 0 stats := make([]udp.ScrapeStats, len(files), len(files)) for o := range resChan { stats[o.Index] = o.File received++ // Once all file stats are received, break loop if received == len(files) { break } } // Close response channel close(resChan) // Create UDP scrape response scrape := udp.ScrapeResponse{ Action: 2, TransID: u.TransID, FileStats: stats, } // Convert to UDP byte buffer buf, err := scrape.MarshalBinary() if err != nil { log.Println(err.Error()) return u.Error(ErrScrapeFailure.Error()) } return buf }
// TestUDPRouter verifies that the main UDP router is working properly func TestUDPRouter(t *testing.T) { log.Println("TestUDPRouter()") // Load config config, err := common.LoadConfig() if err != nil { t.Fatalf("Could not load configuration: %s", err.Error()) } common.Static.Config = config // Generate mock data.FileRecord file := data.FileRecord{ InfoHash: "6465616462656566303030303030303030303030", Verified: true, } // Save mock file if err := file.Save(); err != nil { t.Fatalf("Failed to save mock file: %s", err.Error()) } // Fake UDP address addr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0") if err != nil { t.Fatalf("Failed to create fake UDP address") } // Connect packet with handshake connect := udp.Packet{udpInitID, 0, 1234} connectBuf, err := connect.MarshalBinary() if err != nil { t.Fatalf("Failed to create UDP connect packet") } // Perform connection handshake res, err := parseUDP(connectBuf, addr) if err != nil { errRes := new(udp.ErrorResponse) err2 := errRes.UnmarshalBinary(res) if err2 != nil { t.Fatalf(err.Error()) } log.Println("ERROR:", errRes.Error) t.Fatalf(err.Error()) } // Retrieve response, get new connection ID, which will be expected by router connRes := new(udp.ConnectResponse) err = connRes.UnmarshalBinary(res) if err != nil { t.Fatalf(err.Error()) } // Create announce request announce := udp.AnnounceRequest{ ConnID: connRes.ConnID, Action: 1, TransID: connRes.TransID, InfoHash: []byte("deadbeef000000000000"), PeerID: []byte("00001111222233334444"), Downloaded: 0, Left: 0, Uploaded: 0, IP: 0, Key: 1234, Port: 5000, } // Get announce bytes announceBuf, err := announce.MarshalBinary() if err != nil { t.Fatalf(err.Error()) } // Send announce to UDP router res, err = parseUDP(announceBuf, addr) if err != nil { errRes := new(udp.ErrorResponse) err2 := errRes.UnmarshalBinary(res) if err2 != nil { t.Fatalf(err.Error()) } log.Println("ERROR:", errRes.Error) t.Fatalf(err.Error()) } // Get UDP announce response announceRes := new(udp.AnnounceResponse) err = announceRes.UnmarshalBinary(res) if err != nil { errRes := new(udp.ErrorResponse) err2 := errRes.UnmarshalBinary(res) if err2 != nil { t.Fatalf(err.Error()) } log.Println("ERROR:", errRes.Error) t.Fatalf(err.Error()) } log.Println(announceRes) // Create scrape request scrape := udp.ScrapeRequest{ ConnID: connRes.ConnID, Action: 2, TransID: connRes.TransID, InfoHashes: [][]byte{[]byte("deadbeef000000000000")}, } // Get scrape bytes scrapeBuf, err := scrape.MarshalBinary() if err != nil { t.Fatalf(err.Error()) } // Send scrape to UDP router res, err = parseUDP(scrapeBuf, addr) if err != nil { errRes := new(udp.ErrorResponse) err2 := errRes.UnmarshalBinary(res) if err2 != nil { t.Fatalf(err.Error()) } log.Println("ERROR:", errRes.Error) t.Fatalf(err.Error()) } // Get UDP scrape response scrapeRes := new(udp.ScrapeResponse) err = scrapeRes.UnmarshalBinary(res) if err != nil { errRes := new(udp.ErrorResponse) err2 := errRes.UnmarshalBinary(res) if err2 != nil { t.Fatalf(err.Error()) } log.Println("ERROR:", errRes.Error) t.Fatalf(err.Error()) } log.Println(scrapeRes) // Delete mock file if err := file.Delete(); err != nil { t.Fatalf("Failed to delete mock file: %s", err.Error()) } }