#ZH1 Golang metadata EOF

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

Hi @SamSyntax, the bug is here:

extMessageID[0] represents your own ID, while utMetadataID represents the other’s ID. As such, they are not meant to match.

1 Like

Wow, that was so simple. Thanks mate!

1 Like

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.