I’m stuck on Stage #ND2.
I’m getting the response message with id 7 but the block in the payload seem to be note equal to 16kb (16384 bytes) when i receive the message, it just about 2000 to 4000 bytes one time even though they are not the last piece. Moreover, while getting the message id 7, I also receive some data which are not in the right format from the other peers .
And here’s a snippet of my code:
import process from "node:process";
import * as file from "fs";
import * as http from "http";
import parseTorrent from "parse-torrent";
import bencode from "bencode";
import axios from "axios";
import * as net from "net";
function decode(bencodedValue) {
return bencode.decode(bencodedValue, "utf8");
}
async function parseTorrentFile(fileName) {
const fileContent = file.readFileSync(fileName);
const decodedFileContent = await parseTorrent(fileContent);
return decodedFileContent;
}
async function discoverPeers(fileName) {
const fileContent = file.readFileSync(fileName);
const parsedFile = await parseTorrent(fileContent);
// console.log(parsedFile);
let trackerUrl = "";
if (parsedFile.announce.length > 0) {
trackerUrl = parsedFile.announce[0];
}
function urlEncodeInfoHash(infoHash) {
let result = "%" + infoHash.match(/.{1,2}/g).join("%");
return result;
}
let infoHash = parsedFile.infoHash;
infoHash = urlEncodeInfoHash(infoHash);
const peerId = "019583diuj491n520j21";
const port = 6881;
const uploaded = 0;
const downloaded = 0;
const left = parsedFile.length;
const compact = 1;
const params = new URLSearchParams();
params.append("peer_id", peerId);
params.append("port", port);
params.append("uploaded", uploaded);
params.append("downloaded", downloaded);
params.append("left", left);
params.append("compact", compact);
const newTrackerUrl = `${trackerUrl}?info_hash=${infoHash}&${params
.toString()
.toLowerCase()}`;
const res = await fetch(newTrackerUrl);
let response = Buffer.from(await res.arrayBuffer());
response = bencode.decode(response);
const peerList = response.peers;
const result = [];
for (let i = 0; i < peerList.length; i += 6) {
const firstParam = peerList[i];
const secondParam = peerList[i + 1];
const thirdParam = peerList[i + 2];
const fourthParam = peerList[i + 3];
const fifthParam = peerList[i + 4];
const sixthParam = peerList[i + 5];
const port = (fifthParam << 8) | sixthParam;
const address = `${firstParam}.${secondParam}.${thirdParam}.${fourthParam}:${port}`;
result.push(address);
}
return result;
}
async function handshakePeers(fileName, peer) {
let [peerIP, peerPort] = peer.split(":");
peerPort = parseInt(peerPort);
const parsedFile = await parseTorrentFile(fileName);
let protocolString = Buffer.from("BitTorrent protocol");
let peerId = Buffer.from("019583diuj491n520j21");
let protocolLength = 19;
let reserved = Buffer.alloc(8);
let infoHash = Buffer.from(parsedFile.infoHashBuffer);
protocolLength = Buffer.from([protocolLength]);
const handshake = Buffer.concat([
protocolLength,
protocolString,
reserved,
infoHash,
peerId,
]);
return handshake;
}
function handlePeerMessages(client, data, parsedFile, pieceIndex, chunks) {
const messagePrefixLength = data.readUInt32BE(0);
const messageID = data.readUInt8(4);
let payload = data.subarray(5, messagePrefixLength + 5);
// console.log(payload);
const pieceLength = parsedFile.pieceLength;
const lastPieceLength = parsedFile.lastPieceLength;
const sendInterestedMessage = () => {
const interestedMessage = Buffer.from([0, 0, 0, 1, 2]);
return interestedMessage;
};
const sendRequestMessage = (messageID) => {
console.log("Sending request message");
let blockSize = 2 ** 14;
let byteOffset;
//First time sending Request
if (messageID === 1) {
byteOffset = 0;
} else {
// console.log(">>> check payload length", payload.subarray(8).length);
byteOffset = payload.readUInt32BE(4) + payload.subarray(8).length;
// console.log(byteOffset);
}
if (byteOffset + blockSize >= pieceLength) {
blockSize = pieceLength - byteOffset;
}
const requestMessage = Buffer.alloc(17);
requestMessage.writeUInt32BE(13, 0);
requestMessage.writeUInt8(6, 4);
requestMessage.writeUInt32BE(pieceIndex, 5);
requestMessage.writeUInt32BE(byteOffset, 9);
requestMessage.writeUInt32BE(blockSize, 13);
// console.log(message);
return requestMessage;
};
if (messageID === 5) {
client.write(sendInterestedMessage());
} else if (messageID === 1) {
client.write(sendRequestMessage(1));
} else if (messageID === 7) {
const payloadBlockSize = payload.subarray(8).length;
console.log(">>>check payloadBlockSize", payloadBlockSize);
console.log("messageID 7 come here bro");
if (payloadBlockSize === 0) {
client.end();
} else {
const block = payload.subarray(8);
chunks.push(block);
client.write(sendRequestMessage(7));
}
}
}
async function makeConnection(fileName, peer, pieceIndex) {
const client = new net.Socket();
const chunks = [];
const [peerIP, peerPort] = peer.split(":");
const handshake = await handshakePeers(fileName, peer);
const parsedFile = await parseTorrentFile(fileName);
client.connect(peerPort, peerIP, () => {
console.log("Connected");
client.write(handshake);
});
client.on("data", (data) => {
//HandshakeMessage
console.log(data.length);
if (data.length === 68) {
console.log("Peer ID:", data.subarray(data.length - 20).toString("hex"));
} else {
console.log(data);
handlePeerMessages(client, data, parsedFile, pieceIndex, chunks);
}
});
client.on("close", () => {
console.log({chunks})
console.log("Connection closed");
});
client.on("error", (err) => {
console.error("Client error:", err);
});
}
async function main() {
// test = "d3:foo3:bar5:helloi52ee"
// console.log(decode(test));
const command = process.argv[2];
// You can use print statements as follows for debugging, they'll be visible when running tests.
// console.log("Logs from your program will appear here!");
// Uncomment this block to pass the first stage
if (command === "decode") {
const bencodedValue = process.argv[3];
console.log(JSON.stringify(decode(bencodedValue)));
} else if (command === "info") {
const fileName = process.argv[3];
const parsedTorrentFile = await parseTorrentFile(fileName);
let trackerUrl = "";
trackerUrl = parsedTorrentFile.announce[0];
const fileLength = parsedTorrentFile.length;
const infoHash = parsedTorrentFile.infoHash;
const pieceLength = parsedTorrentFile.pieceLength;
const pieces = parsedTorrentFile.pieces.join("\n");
const result = `Tracker URL: ${trackerUrl}\nLength: ${fileLength}\nInfo Hash: ${infoHash}\nPiece Length: ${pieceLength}\nPieceHashes:\n${pieces}`;
console.log(result);
} else if (command === "peers") {
const fileName = process.argv[3];
console.log((await discoverPeers(fileName)).join("\n"));
} else if (command === "handshake") {
const fileName = process.argv[3];
const peer = process.argv[4];
await makeConnection(fileName, peer);
} else if (command === "download_piece") {
const flag = process.argv[3];
const outputPath = process.argv[4];
const fileName = process.argv[5];
const pieceIndex = parseInt(process.argv[6]);
const peers = await discoverPeers(fileName);
//Took 1 peer
const peerAddress = peers[0];
await makeConnection(fileName, peerAddress, pieceIndex);
} else {
throw new Error(`Unknown command ${command}`);
}
}
main();