diff --git a/src/interfaces/ISolidFile.ts b/src/interfaces/ISolidFile.ts new file mode 100644 index 0000000..5416d20 --- /dev/null +++ b/src/interfaces/ISolidFile.ts @@ -0,0 +1,37 @@ +interface ISolidFile { + //TODO +} + +interface ISolidFileInfo { + /** List of files that are part of this torrent. */ + files: ISolidFile[]; + /** Length of one piece in bytes, e.g. 4194304 for 4 MB. */ + 'piece length': number; + /** Concatenated hashes of all pieces. */ + pieces: string; + /** Whether the torrent is private, always 0. */ + private: 0 | 1; + /** Whether the torrent is closed, always 1. */ + closed: 0 | 1; + /** Unique ID of this torrent, e.g. '85ff8715-d1ce-4320-805c-bea80b6dd03c' */ + uniqueid: string; +} + +interface ISolid { + /** Timestamp when this torrent was created, given in seconds since 1970. Can be read with `new Date(... * 1000)`. */ + 'creation date': number; + /** Internal tracker URL used to announce this torrent. */ + announce: string; + /** Title of this torrent in the format `${product}: ${from}to${to}` */ + title: string; + /** Unknown integer, always 16097. */ + networkgroupid: number; + /** The URL where the files from this torrent are stored, in the format `http://cdn-patch.swtor.com/patch/${product}/${product}_${from}to${to}/` */ + reliable: string; + /** Always '0' */ + 'reliable-id': string; + /** Contains further information about this torrent, including the list of files. */ + info: ISolidFileInfo; +} + +export default ISolid; diff --git a/src/ssn/getSolidpkg.ts b/src/ssn/getSolidpkg.ts index 8ea3c3f..3604ba8 100644 --- a/src/ssn/getSolidpkg.ts +++ b/src/ssn/getSolidpkg.ts @@ -1,9 +1,11 @@ import getUrlContents from '../getUrlContents'; import { Product } from '../interfaces/ISettings'; +import ISolid from '../interfaces/ISolidFile'; import verifyProductName from '../ssn/verifyProductName'; import bpParse from './bencodeParser'; import extractFile from './extractFile'; import readSsnFile from './readSsnFile'; +import verifySolidpkg from './verifySolidpkg'; export default async function getSolidpkg(product: Product, from: number, to: number): Promise { //Verify function arguments @@ -37,7 +39,15 @@ export default async function getSolidpkg(product: Product, from: number, to: nu //Extract metafile.solid file const solidFile = await extractFile(firstFile, [new DataView(ssnFile)]); - const solidContents = bpParse(new DataView(solidFile)).obj; + const solidContents = bpParse(new DataView(solidFile)).obj as ISolid; - return solidContents; + //Verify metafile.solid for correctness + verifySolidpkg(solidContents, { product, from, to }); + + return { + created: new Date(solidContents['creation date'] * 1000), + files: solidContents.info.files, + pieceLength: solidContents.info['piece length'], + //pieces: solidContents.info.pieces, + }; } diff --git a/src/ssn/verifySolidpkg.ts b/src/ssn/verifySolidpkg.ts new file mode 100644 index 0000000..d4f35b6 --- /dev/null +++ b/src/ssn/verifySolidpkg.ts @@ -0,0 +1,45 @@ +import ISolid from '../interfaces/ISolidFile'; + +/** Verifies metafile.solid for correctness. */ +export default function verifySolidpkg(file: ISolid, { product, from, to }: {product: string, from: number, to: number}) { + if (typeof file['creation date'] !== 'number') { + throw new Error(`Expected creation date to be a number but it was "${file['creation date']}".`); + } + if (file.announce !== 'http://Tracker22.fda5b1cf-eac6-402b-9ebf-17bc381314aa.automated.ssntracker.int:80/') { + throw new Error(`Expected announce URL but it was "${file.announce}".`); + } + if (file.title !== `${product}: ${from}to${to}`) { + throw new Error(`Expected title "${product}: ${from}to${to}" but it was "${file.title}".`); + } + if (file.networkgroupid !== 16097) { + throw new Error(`Expected networkgroupid 16097 but it was "${file.networkgroupid}".`); + } + if (file.reliable !== `http://cdn-patch.swtor.com/patch/${product}/${product}_${from}to${to}/`) { + throw new Error(`Expected reliable URL but it was "${file.reliable}".`); + } + if (file['reliable-id'] !== '0') { + throw new Error(`Expected reliable-id to be "0" but it was "${file['reliable-id']}".`); + } + + if (file.info === undefined) { + throw new Error(`Expected info field but it was missing.`); + } + + //TODO: read file.info.files + + if (file.info['piece length'] !== 4194304) { + throw new Error(`Expected piece length to be "4194304" but it was "${file.info['piece length']}".`); + } + if (typeof file.info.pieces !== 'string') { + throw new Error(`Expected pieces to be a string but it was "${file.info.pieces}".`); + } + if (file.info.private !== 0) { + throw new Error(`Expected file to not be private ("0") but it was "${file.info.private}".`); + } + if (file.info.closed !== 1) { + throw new Error(`Expected file to be closed ("1") but it was "${file.info.closed}".`); + } + if (typeof file.info.uniqueid !== 'string') { + throw new Error(`Expected uniqueid to be a string but it was "${file.info.uniqueid}".`); + } +}