Stuck on stage Recieve Metadata #ZH1

I’m stuck on Stage #ZH1.

I keep getting EOF error,after sending the request message

Here are my logs:

[tester::#ZH1] Running tests for Stage #ZH1 (Magnet Links - Receive metadata)
[tester::#ZH1] Running ./your_bittorrent.sh magnet_info "magnet:?xt=urn:btih:c5fb9894bdaba464811b088d806bdd611ba490af&dn=magnet3.gif&tr=http%3A%2F%2F127.0.0.1:41085%2Fannounce"
[tester::#ZH1] Tracker started on address 127.0.0.1:41085...
[tester::#ZH1] 
[tester::#ZH1] Peer listening on address: 127.0.0.1:46715
[your_program] Logs from your program will appear here!
[your_program] Tracker URL: http://127.0.0.1:41085/announce
[your_program] Info Hash: c5fb9894bdaba464811b088d806bdd611ba490af
[tester::#ZH1] Waiting to receive handshake message
[tester::#ZH1] Received handshake: [infohash: c5fb9894bdaba464811b088d806bdd611ba490af, peer_id: 746774777672786b626a6d73706d6976716e736a]
[tester::#ZH1] 
[tester::#ZH1] Sending back handshake with peer_id: dcc78ef2fe5e2978ff6b759669788f15089402aa
[tester::#ZH1] Sending bitfield message
[tester::#ZH1] Sending extension handshake
[tester::#ZH1] Waiting to receive extension handshake message
[your_program] Peer ID: dcc78ef2fe5e2978ff6b759669788f15089402aa
[tester::#ZH1] Received extension handshake with payload: d1:md11:ut_metadatai20eee
[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] Received bitfield message
[your_program] Sending extension handshake...
[your_program] Peer Metadata Extension ID: 202
[your_program] Sending request message...
[your_program] Error reading message length: EOF
[tester::#ZH1] ✓ Tracker URL is correct.
[tester::#ZH1] Expected stdout to contain "Length: 629944", got: "Tracker URL: http://127.0.0.1:41085/announce\nInfo Hash: c5fb9894bdaba464811b088d806bdd611ba490af\nPeer ID: dcc78ef2fe5e2978ff6b759669788f15089402aa\nReceived bitfield message\nSending extension handshake...\nPeer Metadata Extension ID: 202\nSending request message...\nError reading message length: EOF\n"
[tester::#ZH1] Test failed

And here’s a snippet of my code:

package magnet

import (
	"bytes"
	"encoding/binary"
	"encoding/hex"
	"fmt"
	"github.com/codecrafters-io/bittorrent-starter-go/cmd/mybittorrent/peers"
	"github.com/codecrafters-io/bittorrent-starter-go/cmd/mybittorrent/tcp"
	"github.com/jackpal/bencode-go"
	"io"
	"net"
	"net/url"
	"regexp"
)

type extensionMsg struct {
	M map[string]interface{} `bencode:"m"`
}
type requestMsgPayload struct {
	Msg_type int `bencode:"msg_type"`
	Piece    int `bencode:"piece"`
}
type dataMsgPayload struct {
	Msg_type   int `bencode:"msg_type"`
	Piece      int `bencode:"piece"`
	Total_size int `bencode:"total_size"`
}

func ParseMagnetLinks(magnetLink string) (string, string) {
	infoHashPattern := `xt=urn:btih:([a-fA-F0-9]{40}|[a-zA-Z2-7]{32})`
	trackerPattern := `tr=([^&]+)`
	trackerURL := ""

	reTracker := regexp.MustCompile(trackerPattern)
	trackerURLs := reTracker.FindAllStringSubmatch(magnetLink, -1)
	for _, match := range trackerURLs {
		decodedURL, err := url.QueryUnescape(match[1])
		if err != nil {
			fmt.Println(err)
			return "", ""
		}
		trackerURL = decodedURL
		fmt.Println("Tracker URL:", trackerURL)
	}

	reInfoHash := regexp.MustCompile(infoHashPattern)
	infoHash := reInfoHash.FindStringSubmatch(magnetLink)
	if len(infoHash) > 1 {
		fmt.Println("Info Hash:", infoHash[1])
	}
	return trackerURL, infoHash[1]
}

func MagnetHandshake(magnetLink string) {
	trackerURL, infoHash := ParseMagnetLinks(magnetLink)
	byteInfoHash, _ := hex.DecodeString(infoHash)
	var infoHashArray [20]byte
	copy(infoHashArray[:], byteInfoHash)

	peerList, err := peers.FetchPeersFromTracker(trackerURL, infoHashArray, nil)
	if err != nil || len(peerList) == 0 {
		fmt.Println("Error fetching peers or no peers available:", err)
		return
	}
	peerTCPAddr, err := net.ResolveTCPAddr("tcp", peerList[0])
	if err != nil {
		fmt.Println("Error resolving TCP address:", err)
		return
	}

	tcpConn, err := net.DialTCP("tcp", nil, peerTCPAddr)
	if err != nil {
		fmt.Println("Error establishing TCP connection:", err)
		return
	}
	defer tcpConn.Close()
	peerID := tcp.CompleteHandshake(tcpConn, infoHashArray)
	fmt.Println("Peer ID:", peerID)
	sendExtensionHandshake(tcpConn)
}

func sendExtensionHandshake(tcpConn *net.TCPConn) {
	var peerMetaDataExtensionID int
	requestMsgSent := false

	for {
		messageLength := make([]byte, 4)
		_, err := io.ReadFull(tcpConn, messageLength)
		if err != nil {
			fmt.Println("Error reading message length:", err)
			return
		}
		length := binary.BigEndian.Uint32(messageLength)
		if length == 0 {
			fmt.Println("Keep alive message received")
			return
		}

		messageID := make([]byte, 1)
		_, err = io.ReadFull(tcpConn, messageID)
		if err != nil {
			fmt.Println("Error reading message ID:", err)
			return
		}

		id := uint8(messageID[0])
		switch id {
		case 5:
			fmt.Println("Received bitfield message")
			payload := make([]byte, length-1)
			_, err := io.ReadFull(tcpConn, payload)
			if err != nil {
				fmt.Println("Error reading bitfield payload:", err)
				return
			}

			bencodedDict := map[string]interface{}{
				"m": map[string]uint8{"ut_metadata": 20},
			}
			var bencodedDictBytesBuffer bytes.Buffer
			err = bencode.Marshal(&bencodedDictBytesBuffer, bencodedDict)
			if err != nil {
				fmt.Println("Error encoding bencoded dictionary:", err)
				return
			}

			extensionPayload := bencodedDictBytesBuffer.Bytes()
			messageLen := len(extensionPayload) + 2
			extensionHandshake := make([]byte, 4+messageLen)
			binary.BigEndian.PutUint32(extensionHandshake[0:4], uint32(messageLen))
			extensionHandshake[4] = 20
			extensionHandshake[5] = 0
			copy(extensionHandshake[6:], extensionPayload)
			fmt.Println("Sending extension handshake...")
			_, err = tcpConn.Write(extensionHandshake)
			if err != nil {
				fmt.Println("Error sending extension handshake:", err)
			}

		case 20:
			payload := make([]byte, length-1)
			_, err = io.ReadFull(tcpConn, payload)
			if err != nil {
				fmt.Println("Error reading extension message payload:", err)
				return
			}

			extensionMsgID := payload[0]
			dict := payload[1:]
			buf := bytes.NewReader(dict)

			if extensionMsgID == 0 {
				extensionMsg := extensionMsg{}
				err := bencode.Unmarshal(buf, &extensionMsg)
				if err != nil {
					fmt.Println("Error unmarshaling extension message:", err)
					return
				}

				if metadataExtID, ok := extensionMsg.M["ut_metadata"].(int); ok {
					peerMetaDataExtensionID = metadataExtID
					fmt.Println("Peer Metadata Extension ID:", peerMetaDataExtensionID)

					requestMsgPayload := requestMsgPayload{
						Msg_type: 0,
						Piece:    0,
					}
					var requestMsgPayloadBuf bytes.Buffer
					err = bencode.Marshal(&requestMsgPayloadBuf, requestMsgPayload)
					if err != nil {
						fmt.Println("Error marshaling request payload:", err)
						return
					}

					payloadLength := len(requestMsgPayloadBuf.Bytes()) + 2
					requestMsg := make([]byte, 4+payloadLength)
					binary.BigEndian.PutUint32(requestMsg[:4], uint32(payloadLength))
					requestMsg[4] = 20
					requestMsg[5] = uint8(peerMetaDataExtensionID)
					copy(requestMsg[6:], requestMsgPayloadBuf.Bytes())
					fmt.Println("Sending request message...")
					_, err = tcpConn.Write(requestMsg)
					if err != nil {
						fmt.Println("Error sending request message:", err)
						return
					}
					requestMsgSent = true
				} else {
					fmt.Println("Could not extract metadata extension ID")
					return
				}
			} else if extensionMsgID == uint8(peerMetaDataExtensionID) && requestMsgSent {
				dataMsgPayload := dataMsgPayload{}
				err := bencode.Unmarshal(buf, &dataMsgPayload)
				if err != nil {
					fmt.Println("Error unmarshaling data message:", err)
					return
				}
				fmt.Println("Metadata response received:")
				fmt.Println("Msg Type:", dataMsgPayload.Msg_type)
				fmt.Println("Piece:", dataMsgPayload.Piece)
				fmt.Println("Total Size:", dataMsgPayload.Total_size)
				
				// Break the loop after receiving the metadata response
				// break
			}
		}
	}
}

Hi @TusharAbhinav, I’ll take a look at your code a few hours later.

1 Like

@TusharAbhinav It seems this comparison is not correct:

The extensionMsgID in the data response is actually your own metadata extension ID:

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