I’m stuck on Stage #JV8 - Download the whole file.
So I sucessfully downloaded the whole file on my local computer and got the test.txt. However, when i ran the test on codecrafters, I always got execution timeout when running the test. I want to know that is this because my program are too slow or there are some issues with the testing server. Thank you so much.
The file when download through sample.torrent is:
What Is a Hacker?
There is a community, a shared culture, of expert programmers and networking wizards that traces its history back through decades to the first time-sharing minicomputers and the earliest ARPAnet experiments. The members of this culture originated the term ‘hacker’. Hackers built the Internet. Hackers made the Unix operating system what it is today. Hackers make the World Wide Web work. If you are part of this culture, if you have contributed to it and other people in it know who you are and call you a hacker, you’re a hacker.
The hacker mind-set is not confined to this software-hacker culture. There are people who apply the hacker attitude to other things, like electronics or music — actually, you can find it at the highest levels of any science or art. Software hackers recognize these kindred spirits elsewhere and may call them ‘hackers’ too — and some claim that the hacker nature is really independent of the particular medium the hacker works in. But in the rest of this document we will focus on the skills and attitudes of software hackers, and the traditions of the shared culture that originated the term ‘hacker’…
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";
import crypto from "crypto";
import * as path from "path";
import { resolve } from "node:path";
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);
let pieceLength;
if (parsedFile.pieces.length - 1 === pieceIndex) {
pieceLength = parsedFile.lastPieceLength;
} else {
pieceLength = parsedFile.pieceLength;
}
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));
}
} else {
// console.log(data.length);
}
}
async function makeConnection(fileName, peer, outputPath, pieceIndex) {
return new Promise(async (resolve, reject) => {
const client = new net.Socket();
let hasCompleted = false;
const [peerIP, peerPort] = peer.split(":");
const handshake = await handshakePeers(fileName, peer);
const chunks = [];
const parsedFile = await parseTorrentFile(fileName);
const timeoutId = setTimeout(() => {
if (!hasCompleted) {
console.log(`Timeout for piece ${pieceIndex}, retrying...`);
client.destroy(); // Forcefully close the connection
reject(new Error("Download timed out"));
}
}, 4000);
client.connect(peerPort, peerIP, () => {
console.log("Connected");
client.write(handshake);
});
client.on("data", (data) => {
// console.log(data);
if (data.length === 74) {
// console.log("Hello")
const handshakeMessage = data.subarray(0, 68);
const firstByteOfHandshakeMessage = handshakeMessage.readUInt8(0);
if (firstByteOfHandshakeMessage === 19) {
console.log(
"Peer ID:",
handshakeMessage
.subarray(handshakeMessage.length - 20)
.toString("hex")
);
const peerMessage = data.subarray(68);
handlePeerMessages(
client,
peerMessage,
parsedFile,
pieceIndex,
chunks
);
}
} else
if (data.length === 68) {
console.log(
"Peer ID:",
data.subarray(data.length - 20).toString("hex")
);
if (!outputPath && !pieceIndex) {
client.end();
return;
}
} else {
handlePeerMessages(client, data, parsedFile, pieceIndex, chunks);
}
});
client.on("close", () => {
clearTimeout(timeoutId); // Clear timeout if the connection completes
// console.log(outputPath, pieceIndex);
if (outputPath && pieceIndex !== undefined) {
const downloadedData = Buffer.concat(chunks);
const hash = crypto
.createHash("sha1")
.update(downloadedData)
.digest("hex");
// console.log(hash);
// console.log(parsedFile.pieces);
if (parsedFile.pieces.includes(hash)) {
console.log("Download piece", pieceIndex, "successfully");
const outputDir = path.dirname(outputPath);
// console.log(">>> check outputDir", outputDir);
if (!file.existsSync(outputDir)) {
file.mkdirSync(outputDir, { recursive: true });
}
for (const chunk of chunks) {
file.appendFileSync(outputPath, chunk);
}
hasCompleted = true;
}
}
console.log("Connection closed");
resolve();
});
client.on("error", (err) => {
clearTimeout(timeoutId); // Clear timeout if an error occurs
console.error("Client error:", err);
reject(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 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];
// console.log({outputPath, fileName, pieceIndex})
await makeConnection(fileName, peerAddress, outputPath, pieceIndex);
} else if (command === "download") {
const outputPath = process.argv[4];
const fileName = process.argv[5];
const peers = await discoverPeers(fileName);
const peerAddress = peers[0];
const parsedFile = await parseTorrentFile(fileName);
// console.log(parsedFile);
const maxRetries = 3;
for (let i = 0; i < parsedFile.pieces.length; i++) {
let retries = 0;
let completed = false;
while (retries < maxRetries && !completed) {
try {
await makeConnection(fileName, peerAddress, outputPath, i);
completed = true;
} catch (error) {
retries++;
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}
}
} else {
throw new Error(`Unknown command ${command}`);
}
}
main();