losLab PDF Library voi tuottaa tavu tavulta identtisen PDF-tulosteen identtiselle syötteelle heti kun kutsut SetDeterministicDocumentID(1). Oletuksena loppuosan /ID-taulukko on MD5-tiiviste seinäkellon ajasta, joten kaksi ajoa samasta generaattorista eroavat vähintään noissa tavuissa. Deterministinen tila johtaa /ID:n sen sijaan vakaasta siemenluvusta, mikä palauttaa toistettavat käännökset
Oire ilmenee yleensä CI:ssä ennen kuin kukaan lähtee etsimään sitä. Malli ei ole muuttunut, syötetietue ei ole muuttunut, fontit eivät ole muuttuneet, ja silti generoitu PDF tiivistyy eri tavalla joka putkiajolla. Käännösvälimuistit eivät koskaan osu. Sisältöosoitteistettu tallennustila kerää tuoreen blobin joka yöllisestä käännöksestä. Tavutason regressioerot syttyvät tiedostoissa, joihin kukaan ei ole koskenut. Kun eron jäljittää todellisiin tavuihin, kyseessä on lähes aina sama kourallinen heksanumeroita tiedoston loppuosassa
Mihin loppuosan ID-taulukko on tarkoitettu
/ID loppuosassa on tiedoston identiteettimerkki, ei sisällön tarkistussumma. ISO 32000-1 §14.4 määrittelee sen kahden tavumerkkijonon taulukoksi: ensimmäinen alkio on pysyvä tunniste, joka annetaan asiakirjaa luotaessa ja jonka on tarkoitus säilyä jokaisen myöhemmän muokkauksen läpi, ja toinen alkio on muuttuva tunniste, jonka kirjoittaja päivittää joka kerta kun tiedostoa muokataan. Yhdessä ne antavat järjestelmälle mahdollisuuden päättää, ovatko kaksi tiedostoa yhden asiakirjan revisioita vai kaksi toisiinsa liittymätöntä asiakirjaa. §7.5.5 tekee kentästä käytännössä pakollisen, koska loppuosan on kannettava /ID aina, kun se kantaa myös /Encrypt:in
Spesifikaatio ei sano mitään siitä, miten arvo lasketaan. Suositus on tiiviste asioista kuten nykyinen aika, tiedostopolku, tiedostokoko ja asiakirjan tietosanakirja, ja seinäkellon aika on ainesosa, joka tekee tuloksesta ainutlaatuisen. Se on juuri se ominaisuus, jota identiteetin kannalta halutaan, ja juuri se ominaisuus, joka tuhoaa toistettavuuden, minkä vuoksi tämän täytyy olla nimenomainen kytkin eikä hiljainen käyttäytymismuutos
Miksi sama käännös tuottaa joka kerta erilaisen PDF:n?
Koska oletustunniste johdetaan generointihetkestä. Historiallisesti losLab PDF Library rakensi /ID-merkkijonot nykyisen aikaleiman MD5:stä, joten kahdesti sekunnin välein luotu asiakirja kantaa kaksi eri pysyvää tunnistetta, vaikka jokainen muu tavu tiedostossa olisi identtinen. Alavirran kustannus on todellinen: käännösjärjestelmä, joka avainnistaa artefaktit tiivisteellä, ei koskaan voi käyttää uudelleen PDF-vaihetta, deduplikoiva objektivarasto pitää yhden kopion per käännös yhden kopion per asiakirja sijaan, ja binaariero-katselmoija joutuu todistamaan, että ainoa muutos on kohinaa, ennen kuin voi luottaa muuhun eroon. Deterministinen /ID-generointi on olemassa poistaakseen tämän kohinan, samassa hengessä kuin asettelun vakautta käsittelevä työ artikkelissa objektivirrat ja ristiviittausvirrat
Siirtyminen toistettavaan tunnisteeseen
Deterministinen tila on liittymisvalintainen, asiakirjakohtainen ja oletuksena pois päältä, joten olemassa oleva tuloste pysyy muuttumattomana, kunnes sitä pyydetään. SetDeterministicDocumentID hyväksyy arvon 0 tai 1 ja palauttaa 1, kun arvo hyväksyttiin, 0 kaikelle alueen ulkopuoliselle; GetDeterministicDocumentID ilmoittaa nykyisen tilan. SetDocumentIDSeed antaa nimenomaisen siemenmerkkijonon, joka voittaa kaiken muun, ja tyhjän siemenen antaminen palauttaa johdetun siemenen. GetDocumentFileID lukee /ID[0]:n takaisin tallennuksen jälkeen, joten sen voi lokittaa tai varmentaa
var
Lib: TPDFlib;
FileID: WideString;
begin
Lib := TPDFlib.Create;
try
Lib.SetDeterministicDocumentID(1);
Lib.SetDocumentIDSeed('invoice-4471-rev3');
Lib.SetOrigin(1);
Lib.DrawText(100, 700, 'Invoice 4471');
Lib.SaveToFile('invoice.pdf');
FileID := Lib.GetDocumentFileID; // identical on every run
finally
Lib.Free;
end;
end;
Päivitys tapahtuu tallennushetkellä, ei silloin kun lippu käännetään, joten deterministisen tilan käyttöönotto myöhään asiakirjan rakentamisessa astuu silti voimaan. Se tarkoittaa myös, että muuttunut siemen saavuttaa tiedoston seuraavassa täydessä tallennuksessa: aseta siemen A, tallenna, aseta siemen B, tallenna, ja kaksi tiedostoa kantavat eri tunnisteita, kun taas siemenen A palauttaminen palauttaa alkuperäisen arvon. Nimenomainen siemen on oikea valinta aina, kun asiakirjalla on luonnollinen vakaa avain, kuten laskun numero, tietueen revisio tai git-committunniste, koska se irrottaa tunnisteen satunnaisesta metadatasta
Mistä siemen tulee, kun sitä ei anneta itse?
Ilman nimenomaista siementä losLab PDF Library johtaa yhden asiakirjatilasta, jonka pitäisi pysyä muuttumattomana identtisten uudelleengenerointien välillä: PDF-versio-otsikosta, sivumäärästä ja jokaisesta asiakirjan tietosanakirjan merkinnästä. Merkkijono- ja nimiarvot otetaan sellaisenaan, muut objektityypit antavat panoksensa serialisoidussa muodossaan, ja koko kokonaisuus tiivistetään /ID-merkkijonoiksi. Tärkeä seuraus on, että CreationDate ja ModDate ovat osa tietosanakirjaa ja siten tarkoituksella osa siementä. Kaksi ajoa ansaitsee saman tunnisteen vain, kun ne todella tuottavat saman asiakirjan metadatan
Lib.SetDeterministicDocumentID(1);
// No SetDocumentIDSeed: the seed is derived from document state,
// so the timestamps in the Info dictionary have to be pinned.
Lib.SetInformation(2, 'Quarterly Report'); // Title
Lib.SetInformation(5, 'reporting-service 4.2'); // Creator
Lib.SetInformation(7, 'D:20260101000000Z'); // CreationDate
Lib.SetInformation(8, 'D:20260101000000Z'); // ModDate
Lib.SaveToFile('report.pdf');
ModDate:n lukitseminen avaimella 8 hoitaa kaksi tehtävää, ja tämä on osa, joka yllättää ihmiset. Pelkkä deterministinen /ID ei tee tiedostosta tavu tavulta identtistä, koska tallennuspolku leimaa ModDate:n nykyisellä ajalla, ellei kutsuja ole asettanut sitä nimenomaisesti. Avaimen 8 asettaminen merkitsee arvon kutsujan antamaksi ja tukahduttaa tuon leiman. Jos haluat toistettavan tiedoston pelkän toistettavan tunnisteen sijaan, kohtele metadatan aikaleimoja käännöksen syötteinä: johda ne lähdetietueesta tai kiinteästä epookista, älä koskaan Now:sta
Miksi ID:n uudelleenkirjoittaminen rikkoo salatun PDF:n?
Koska /ID[0] ei ole salatussa asiakirjassa pelkkää metadataa, vaan avainmateriaalia. ISO 32000-1 §7.6.3.3 Algoritmi 2 syöttää tiedostotunnisteen ensimmäisen alkion salausavaimen laskentaan vakiotietoturvakäsittelijälle revisioissa 2–4, yhdessä täytetyn salasanan, /O-arvon ja käyttöoikeusbittien kanssa. Johdettu avain tuottaa sitten /U-vahvistusmerkkijonon, jonka lukija tarkistaa avattaessa, ja tiedostoavain johdetaan ja välimuistitetaan, kun kutsut Encrypt:iä tai kun salattu asiakirja ladataan, molemmat tapahtuvat ennen tallennusta. Tunnisteen uudelleenkirjoittaminen tallennuksen aikana tuottaisi siis rakenteellisesti kelvollisen tiedoston, jonka /U-tarkistus epäonnistuu uudelleenavattaessa: ei hienovarainen vioittuminen, vaan asiakirja, jota kukaan ei voi avata, mukaan lukien sinä. Siksi deterministinen päivitys on rajattu asiakirjoihin, jotka eivät kanna salaustilaa, ja siksi salattu asiakirja säilyttää minkä tahansa /ID:n, joka sillä jo oli, deterministinen tila tai ei, ja asetuksella ei yksinkertaisesti ole vaikutusta tuolla polulla. Siihen liittyvä revisiokäsittely ja käyttöoikeussemantiikka käsitellään artikkelissa PDF-salauksen ja käyttöoikeuksien auditointi. Huomaa myös, että salauksen palautuspolku päivittää vain /ID[1]:n, muutostunnisteen, täsmälleen niin kuin §14.4 tarkoittaa
Miksi asteittaiset tallennukset säilyttävät alkuperäisen tunnisteen
Toinen raja on liitostila. Asteittainen päivitys jättää jokaisen tiedoston aiemman tavun koskemattomaksi ja kirjoittaa uuden revision sen jälkeen, ja /ID[0]:n pysyvyys §14.4:n mukaan on juuri se, mikä kertoo kuluttajalle, että uusi revisio kuuluu samaan asiakirjaan kuin vanha. Sen uudelleenkirjoittaminen katkaisisi tuon yhteyden, olisi ristiriidassa tiedostossa jo olevien revisioiden kanssa ja häiritsisi allekirjoitussemantiikkaa, koska allekirjoitus kattaa tietyn asiakirjan tietyn revision tavualueen. losLab PDF Library päivittää siis deterministisen tunnisteen vain täysissä tallennuksissa eikä koskaan liitostilan aikana, mikä pitää artikkelissa PDF-asteittaiset päivitykset ja liittäminen virtaan kuvatun takuun ehjänä
Yksi solmukohta tunnisteen generoinnille
Kaikki /ID-generointi losLab PDF Libraryssä kulkee nyt yhden sisäisen rutiinin, NewFileIDString, kautta, mikä tekee deterministisestä kytkimestä luotettavan yhden koodipolun paikkauksen sijaan. Tyhjän asiakirjan luonti, puuttuvan /ID-taulukon laiska luonti tarpeen mukaan ja salauksen sormenjäljen palautuspolku kutsuvat kaikki sitä, joten on olemassa täsmälleen yksi paikka, josta seinäkellon aika voisi vuotaa takaisin. Se tarkoittaa myös, että tulevat variantit, kuten sisällöstä johdettu tunniste, ovat muutos yhteen funktioon koko serialisoijan auditoinnin sijaan
function BuildQuote(const Seed: WideString): AnsiString;
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
Lib.SetDeterministicDocumentID(1);
Lib.SetDocumentIDSeed(Seed);
Lib.SetInformation(7, 'D:20260101000000Z');
Lib.SetInformation(8, 'D:20260101000000Z');
Lib.SetOrigin(1);
Lib.DrawText(100, 700, 'Quote 8812');
Result := Lib.SaveToString;
finally
Lib.Free;
end;
end;
// Regression guard: two independent builds, one byte sequence.
if BuildQuote('quote-8812') = BuildQuote('quote-8812') then
WriteLn('reproducible')
else
WriteLn('nondeterminism leaked into the output');
Kytke tämä vertailu testisarjaasi ennen kuin luotat toistettavaan tulosteeseen missään muualla, koska se epäonnistuu äänekkäästi heti kun jokin uusi ominaisuus tuo aikaleiman takaisin. Toistettavuus on ominaisuus, joka muuten rappeutuu hiljaa, ja yksi väite kahden muistissa olevan tallennuksen yli maksaa lähes ei mitään ajettavaksi joka käännöksellä
Tässä esitetty deterministinen tunniste-API toimitetaan losLab PDF Libraryn mukana Delphille ja C++Builderille, yhdessä täyden asiakirjatieto-, salaus- ja asteittaistallennusviitteen kanssa