// Creates a human-readable formatting of the bencoded data. public static string ToHuman(byte[] data) { var value = Bencoding.Decode(data); var result = new StringBuilder(); toHuman(value, result, 0, 2); return(result.ToString()); }
public static void SaveMetadata(byte[] bytes) { var info = Bencoding.DecodeDict(bytes); var name = info.GetBytes("name"); var metainfo = Bencoding.Dict(); metainfo.Set("info", info); var torrentFileData = Bencoding.Encode(metainfo); string[] pathStrings = { Directory.GetCurrentDirectory(), "..", "..", "our-state", "metadata", name.FromUTF8() + ".torrent" }; var path = Path.Combine(pathStrings); // if the file already exists, completely overwrite it. using (var stream = File.Create(path)) { stream.Write(torrentFileData, 0, torrentFileData.Length); } }
// For messages with msgType 0 (request) and 2 (reject) private static byte[] ConstructMessage(int ourExtCode, int msgType, int piece) { var messageDict = new Dictionary <byte[], object>(); messageDict.Set("msg_type", msgType); messageDict.Set("piece", piece); var encodedMsg = Bencoding.Encode(messageDict); var length = (encodedMsg.Length + 2).EncodeBytes(); var message = new byte[encodedMsg.Length + 6]; length.CopyTo(message, 0); message[4] = 20; message[5] = (byte)ourExtCode; encodedMsg.CopyTo(message, 6); return(message); }
// ourExtCode refers to the value we have associated with ut_metadata // theirExtCode refers to the value they have associated with ut_metadata public byte[] GetMetadata(NetworkStream stream, TcpClient connection, byte ourExtCode, byte theirExtCode, byte[] infohash) { if (!connection.Connected) { throw new Exception("Disconnected from peer after handshake."); } var currentPiece = 0; var info = new VerifiedBytes(infohash, (Int32)metadataLength, CommonPieceSizes.BEP9_METADATA); using (logger.BeginScope($"Metadata request for {infohash.ToHex()} from {connection.Client.RemoteEndPoint}")) { // Request the first piece. var initialRequest = ConstructRequestMessage(ourExtCode, 0); logger.LogInformation(LoggingEvents.METADATA_REQUEST, "Sending request for first metadata piece: " + initialRequest.ToHuman()); stream.Write(initialRequest); while (true) { Int32 theirLength = 0; // Read lengths until we get a non-zero (non-keepalive) length. while (theirLength == 0) { var theirPrefix = stream.ReadBytes(4); theirLength = theirPrefix.Decode32BitInteger(); if (theirLength == 0) { logger.LogInformation(LoggingEvents.PEER_PROTOCOL_MSG, "Got keepalive. Let's send our own!"); stream.Write(new byte[4]); } } logger.LogInformation(LoggingEvents.PEER_PROTOCOL_MSG, $"Got message with length {theirLength}."); var peerResponse = stream.ReadBytes(theirLength); var responseTypeId = peerResponse[0]; switch (responseTypeId) { case 20: logger.LogInformation(LoggingEvents.EXTENSION_MESSAGE, "It's an extension message! Hurrah!"); var extensionId = peerResponse[1]; if (extensionId == theirExtCode) { logger.LogInformation(LoggingEvents.METADATA_RESPONSE_INCOMING, "It's a metadata exchange message!"); var data = peerResponse.Slice(2); var dict = Bencoding.DecodeFirstDict(data, out Int64 dictSize); var postDict = data.Slice((Int32)dictSize); // This is the metadata itself -- a bencoded dictionary of utf8 strings if (dict.GetInt("piece") != currentPiece) { throw new Exception($"Expected piece {currentPiece}. Instead, received {dict.GetInt("piece")}"); } logger.LogInformation( LoggingEvents.METADATA_RESPONSE_INCOMING, $"Got BEP-9 {Bencoding.ToHuman(Bencoding.Encode(dict))} followed by {postDict.Length} bytes of data.\nStoring..."); info.ProvidePiece(currentPiece, postDict); currentPiece++; if (currentPiece == info.PieceCount) { byte[] combinedPieces; try { combinedPieces = info.Result.Result; } catch (BytesVerificationException ex) { logger.LogWarning(LoggingEvents.METADATA_FAILURE, "metadata verification failed!", ex); throw new MetadataException("Metadata verification failed -- try another peer."); } logger.LogInformation(LoggingEvents.DATA_STORAGE_ACTION, "metadata verified! saving..."); DataHandler.SaveMetadata(combinedPieces); logger.LogInformation(LoggingEvents.DATA_STORAGE_ACTION, "metadata saved."); return(combinedPieces); } var request = ConstructRequestMessage(ourExtCode, currentPiece); logger.LogInformation(LoggingEvents.METADATA_REQUEST, "Requesting the next piece of metadata..."); stream.Write(request); } else { logger.LogWarning(LoggingEvents.PEER_UNEXPECTED_RESPONSE, $"It's an unexpected message type, ID {extensionId}."); } break; case 0: logger.LogInformation(LoggingEvents.PEER_PROTOCOL_MSG, "It's a choke message! :("); break; case 1: logger.LogInformation(LoggingEvents.PEER_PROTOCOL_MSG, "It's an unchoke message! :D"); break; case 2: logger.LogInformation(LoggingEvents.PEER_PROTOCOL_MSG, "It's an interested message! <3"); break; case 4: logger.LogInformation(LoggingEvents.PEER_PROTOCOL_MSG, "It's a not interested message! </3"); break; default: logger.LogWarning(LoggingEvents.PEER_UNEXPECTED_RESPONSE, $"Unexpected message type {responseTypeId}; ignoring."); break; } } } }