I’m stuck on Stage #ZH1.
I’ve successfully performed a handshake, sent metadata request and received correct metadata when using test links provided in the module, but once I run cli test I get the below error message:
[tester::#ZH1] Peer listening on address: 127.0.0.1:37195
[your_program] Info Hash: ad42ce8109f54c99613ce38f9b4d87e70f24a165
[your_program] Tracker URL: http://127.0.0.1:33145/announce
[your_program] Attempting tracker: http://127.0.0.1:33145/announce
[your_program] DecodedTEst: map[complete:1 incomplete:0 interval:60 min interval:60 peers:K]
[your_program] Decoded Data Type: map[string]interface {}
[your_program] Decoded Data: map[complete:1 incomplete:0 interval:60 min interval:60 peers:K]
[your_program] Received 'peers' as string: K
[your_program] Parsed Peers (String): [127.0.0.1:37195]
[your_program] Connected to peer: 127.0.0.1:37195
[tester::#ZH1] Waiting to receive handshake message
[tester::#ZH1] Received handshake: [infohash: ad42ce8109f54c99613ce38f9b4d87e70f24a165, peer_id: 2d474f303030312d48576a5a4c786e3158347968]
[tester::#ZH1]
[tester::#ZH1] Sending back handshake with peer_id: e0345d36a0d59307329c949ae118646cb2da5d50
[tester::#ZH1] Sending bitfield message
[tester::#ZH1] Sending extension handshake
[tester::#ZH1] Waiting to receive extension handshake message
[tester::#ZH1] Received extension handshake with payload: d1:md11:ut_metadatai1eee
[tester::#ZH1] Checking metadata extension id received
[tester::#ZH1] Waiting to receive metadata request
[tester::#ZH1] Received payload: }d8:msg_typei0e5:piecei0ee
[tester::#ZH1] Sending metadata response
[your_program] Peer ID: e0345d36a0d59307329c949ae118646cb2da5d50
[your_program] Peer supports extensions: true
[your_program] 0
[your_program] Received 'bitfield' message. Ignoring...
[your_program] 125
[your_program] Peer supports ut_metadata with ID: 125
[your_program] Metadata request sent successfully.
[your_program] 177
[your_program] Message Length: 177
[your_program] Extension Message ID: 1
[your_program] Ignoring unrelated extended message...
[your_program] 1681406573
[your_program] Message Length: 1681406573
[your_program] Ignoring non-extended message (Length: 1681406573)...
[your_program] Error: failed to receive metadata: EOF
[tester::#ZH1] ✓ Tracker URL is correct.
And here’s a snippet of my code:
func sendMetadataMessage(conn net.Conn, utMetadataID int) error {
payload := map[string]interface{}{
"msg_type": 0, // Request message
"piece": 0, // Request the first piece
}
bencodedPayload, err := bencode.EncodeBytes(payload)
if err != nil {
return fmt.Errorf("failed to encode metadata request payload: %v", err)
}
message := append([]byte{byte(utMetadataID)}, bencodedPayload...)
lengthPrefix := make([]byte, 4)
binary.BigEndian.PutUint32(lengthPrefix, uint32(len(message)+1))
fullMsg := append(lengthPrefix, msgExtended)
fullMsg = append(fullMsg, message...)
_, err = conn.Write(fullMsg)
if err != nil {
return fmt.Errorf("failed to send metadata request: %v", err)
}
fmt.Println("Metadata request sent successfully.")
return nil
}
func receiveMetadata(conn net.Conn, utMetadataID int) (map[string]interface{}, map[string]interface{}, error) {
invalidCount := 0
maxInvalidMessages := 10
for {
// Read length prefix
lengthBytes := make([]byte, 4)
_, err := io.ReadFull(conn, lengthBytes)
if err != nil {
return nil, nil, fmt.Errorf("Failed to read length prefix: %v", err)
}
messageLength := binary.BigEndian.Uint32(lengthBytes)
fmt.Println(messageLength)
if messageLength == 0 {
fmt.Println("Keep alive message received")
return nil, nil, nil
}
fmt.Printf("Message Length: %d\n", messageLength)
// Read message ID
messageID := make([]byte, 1)
_, err = io.ReadFull(conn, messageID)
if err != nil {
fmt.Printf("Message ID: %d\n", messageID[0])
return nil, nil, fmt.Errorf("Failed to read message ID: %v", err)
}
id := uint8(messageID[0])
if id != msgExtended {
fmt.Printf("Ignoring non-extended message (Length: %d)...\n", messageLength)
_, err := io.CopyN(io.Discard, conn, int64(messageLength-1)) // Discard the remaining bytes of this message
if err != nil {
return nil, nil, err
}
invalidCount++
if invalidCount > maxInvalidMessages {
return nil, nil, fmt.Errorf("Too many invalid messages received")
}
continue
}
invalidCount = 0
// Read extension message ID
extMessageID := make([]byte, 1)
_, err = io.ReadFull(conn, extMessageID)
if err != nil {
return nil, nil, fmt.Errorf("Failed to read extension message ID: %v", err)
}
fmt.Printf("Extension Message ID: %d\n", extMessageID[0])
if extMessageID[0] != byte(utMetadataID) {
fmt.Println("Ignoring unrelated extended message...")
continue
}
// Read payload
payloadLength := int(messageLength) - 2
payload := make([]byte, payloadLength)
_, err = io.ReadFull(conn, payload)
if err != nil {
return nil, nil, fmt.Errorf("Failed to read payload: %v", err)
}
fmt.Printf("Payload: %x\n", payload)
dictEnd := bytes.Index(payload, []byte("ee")) + 2
if dictEnd < 2 || dictEnd > len(payload) {
return nil, nil, fmt.Errorf("Failed to locate end of bencoded dictionary")
}
var payloadDict map[string]interface{}
err = bencode.DecodeBytes(payload[:dictEnd], &payloadDict)
if err != nil {
return nil, nil, fmt.Errorf("Failed to decode payload dictionary: %v", err)
}
var metadata map[string]interface{}
err = bencode.DecodeBytes(payload[dictEnd:], &metadata)
if err != nil {
return nil, nil, fmt.Errorf("Failed to decode metadata: %v", err)
}
return payloadDict, metadata, nil
}
}
func MagnetInfo(link string) error {
magnetLink, err := ParseMagnetLink(link)
if err != nil {
return err
}
peerID := generatePeerID()
peers := magnetLink.getPeers()
if len(peers) == 0 {
return fmt.Errorf("no peers found")
}
conn, err := net.Dial("tcp", peers[0])
if err != nil {
return err
}
defer conn.Close()
fmt.Printf("Connected to peer: %s\n", peers[0])
peerSupportsExtensions, err := performHandshake(conn, magnetLink.InfoHashBytes, peerID)
if err != nil {
return fmt.Errorf("handshake with peer failed: %v", err)
}
if !peerSupportsExtensions {
return fmt.Errorf("peer does not support extensions")
}
err = sendExtensionHandshake(conn)
if err != nil {
return fmt.Errorf("failed to send extension handshake: %v", err)
}
extensions, err := receiveExtensionHandshake(conn)
if err != nil {
return fmt.Errorf("failed to receive extension handshake: %v", err)
}
utMetadataID, exists := extensions["ut_metadata"]
if !exists {
return fmt.Errorf("peer does not support ut_metadata extension")
}
fmt.Printf("Peer supports ut_metadata with ID: %d\n", utMetadataID)
err = sendMetadataMessage(conn, utMetadataID)
if err != nil {
return fmt.Errorf("failed to send metadata request: %v", err)
}
_, metadataInfo, err := receiveMetadata(conn, utMetadataID)
if err != nil {
return fmt.Errorf("failed to receive metadata: %v", err)
}
pieces, err := extractTorrentInfo(metadataInfo)
if err != nil {
return fmt.Errorf("failed to extract metadata: %v", err)
}
fmt.Println("Tracker URL:", magnetLink.Trackers[0])
fmt.Println("Length:", metadataInfo["length"])
fmt.Println("Info Hash:", magnetLink.InfoHashHex)
fmt.Println("Piece Length:", metadataInfo["piece length"])
fmt.Println("Piece Hashes:")
for _, piece := range pieces {
fmt.Println(piece)
}
return nil
}
func decodePieces(pieces string) ([]string, error) {
pieceLength := 20 // Each SHA-1 hash is 20 bytes
if len(pieces)%pieceLength != 0 {
return nil, fmt.Errorf("invalid pieces length: not a multiple of 20 bytes")
}
var pieceHashes []string
for i := 0; i < len(pieces); i += pieceLength {
pieceHash := pieces[i : i+pieceLength]
pieceHashes = append(pieceHashes, hex.EncodeToString([]byte(pieceHash)))
}
return pieceHashes, nil
}
func extractTorrentInfo(metadata map[string]interface{}) ([]string, error) {
// Extract the name
name, ok := metadata["name"].(string)
if !ok {
return nil, fmt.Errorf("missing or invalid 'name' in metadata")
}
fmt.Printf("Torrent Name: %s\n", name)
// Extract piece length
pieceLength, ok := metadata["piece length"].(int64)
if !ok {
return nil, fmt.Errorf("missing or invalid 'piece length' in metadata")
}
fmt.Printf("Piece Length: %d bytes\n", pieceLength)
if length, ok := metadata["length"].(int64); ok {
fmt.Printf("Total Length: %d bytes\n", length)
}
pieces, ok := metadata["pieces"].(string)
if !ok {
return nil, fmt.Errorf("missing or invalid 'pieces' in metadata")
}
pieceHashes, err := decodePieces(pieces)
if err != nil {
return nil, fmt.Errorf("failed to decode pieces: %v", err)
}
fmt.Printf("Number of Pieces: %d\n", len(pieceHashes))
var hashes []string
fmt.Println("Piece Hashes:")
for i, hash := range pieceHashes {
fmt.Printf("- Piece %d: %s\n", i, hash)
hashes = append(hashes, hash)
}
return hashes, nil
}
I’ve been struggling with it for a couple hours and still can’t figure this out. As mentioned above, test links work fine:
./your_bittorrent.sh magnet_info "magnet:?xt=urn:btih:3f994a835e090238873498636b98a3e78d1c34ca&dn=magnet2.gif&tr=http%3A%2F%2Fbittorrent-test-tracker.codecrafters.io%2Fannounce"
Info Hash: 3f994a835e090238873498636b98a3e78d1c34ca
Tracker URL: http://bittorrent-test-tracker.codecrafters.io/announce
Attempting tracker: http://bittorrent-test-tracker.codecrafters.io/announce
DecodedTEst: map[complete:3 incomplete:0 interval:60 min interval:60 peers:#Ɉ;IG6\]
Decoded Data Type: map[string]interface {}
Decoded Data: map[complete:3 incomplete:0 interval:60 min interval:60 peers:#Ɉ;IG6\]
Received 'peers' as string: #Ɉ;IG6\
Parsed Peers (String): [165.232.35.139:51592 139.59.184.255:51529 167.71.143.54:51548]
Connected to peer: 165.232.35.139:51592
Peer ID: 2d524e302e302e302d877ea56d136944b61d3663
Peer supports extensions: true
0
Received 'bitfield' message. Ignoring...
1
Peer supports ut_metadata with ID: 1
Metadata request sent successfully.
135
Message Length: 135
Extension Message ID: 1
Payload: 64383a6d73675f74797065693165353a706965636569306531303a746f74616c5f73697a65693931656564363a6c656e67746869373937353265343a6e616d6531313a6d61676e6574322e67696631323a7069656365206c656e6774686932363231343465363a70696563657332303ad78a7f55ddd89fef477bc49d938bc7e4d94094f165
Torrent Name: magnet2.gif
Piece Length: 262144 bytes
Total Length: 79752 bytes
Number of Pieces: 1
Piece Hashes:
- Piece 0: d78a7f55ddd89fef477bc49d938bc7e4d94094f1
Tracker URL: http://bittorrent-test-tracker.codecrafters.io/announce
Length: 79752
Info Hash: 3f994a835e090238873498636b98a3e78d1c34ca
Piece Length: 262144
Piece Hashes:
d78a7f55ddd89fef477bc49d938bc7e4d94094f1