losLab PDF Library lahko za enak vhod ustvari bajtno enak izhod PDF, ko kličete SetDeterministicDocumentID(1). Privzeto je polje /ID v sledi izpeljano iz zgoščene vrednosti MD5 sistemske ure, zato se dva zagona istega generatorja razlikujeta vsaj v teh bajtih. Deterministični način namesto tega izpelje /ID iz stabilnega semena, kar povrne ponovljivost gradenj
Simptom se običajno pokaže v CI, še preden ga kdo začne iskati. Predloga se ni spremenila, vhodni zapis se ni spremenil, pisave se niso spremenile, ustvarjeni PDF pa ob vsakem zagonu cevovoda vseeno vrne drugačen razpršilni niz. Predpomnilniki gradenj nikoli ne zadenejo. Vsebinsko naslovljena shramba kopiči svež objekt na vsako nočno gradnjo. Razlike na ravni bajtov se pokažejo pri datotekah, ki jih nihče ni spreminjal. Ko razliko izsledite do dejanskih bajtov, gre skoraj vedno za isto peščico šestnajstiških znakov v sledi datoteke
Čemu služi polje ID v sledi?
Sled /ID je oznaka identitete datoteke, ne kontrolna vsota vsebine. ISO 32000-1 §14.4 ga opredeljuje kot polje dveh bajtnih nizov: prvi element je trajni identifikator, dodeljen ob ustvarjanju dokumenta, ki naj bi preživel vsako poznejšo urejanje, drugi element pa je spremenljivi identifikator, ki ga zapisovalnik osveži ob vsaki spremembi datoteke. Skupaj sistemu omogočata odločitev, ali sta dve datoteki revizija enega dokumenta ali dva nepovezana dokumenta. §7.5.5 v praksi naredi vnos dejansko obvezen, saj mora sled nositi /ID, kadar koli nosi tudi /Encrypt
Specifikacija ne pove, kako vrednost izračunati. Priporočilo je zgoščena vrednost sestavin, kot so trenutni čas, pot datoteke, velikost datoteke in slovar informacij o dokumentu, sistemska ura pa je sestavina, ki rezultat naredi edinstven. To je natanko lastnost, ki jo želite za identiteto, in natanko lastnost, ki uniči ponovljivost, zato mora biti to izrecno stikalo, ne tiha sprememba obnašanja
Zakaj isti gradbeni proces vsakič ustvari drugačen PDF?
Ker je privzeti identifikator izpeljan iz trenutka ustvarjanja. Zgodovinsko je losLab PDF Library gradila nize /ID iz zgoščene vrednosti MD5 trenutnega časovnega žiga, zato dokument, ustvarjen dvakrat eno sekundo narazen, nosi dva različna trajna identifikatorja, tudi če je vsak drug bajt v datoteki enak. Posledice nižje po verigi so resnične: gradbeni sistem, ki artefakte ključi po razpršilu, nikoli ne more ponovno uporabiti koraka PDF, deduplicirajoča shramba objektov obdrži eno kopijo na gradnjo namesto ene kopije na dokument, pregledovalec, ki gleda binarno razliko, pa mora dokazati, da je edina sprememba šum, preden lahko zaupa preostanku razlike. Deterministično ustvarjanje /ID obstaja, da ta šum odstrani, v enakem duhu kot delo za stabilnost postavitve, opisano v opombah o tokovih objektov in tokovih navzkrižnih referenc
Preklop na ponovljiv identifikator
Deterministični način je privzeto izklopljen, na voljo za posamezen dokument, tako da se obstoječi izhod ne spremeni, dokler ga izrecno ne zahtevate. SetDeterministicDocumentID sprejme 0 ali 1 in vrne 1, ko je bila vrednost sprejeta, 0 za vse zunaj tega obsega; GetDeterministicDocumentID poroča o trenutnem stanju. SetDocumentIDSeed poda izrecen niz semena, ki prevlada nad vsem drugim, prazno seme pa povrne izpeljano seme. GetDocumentFileID po shranjevanju prebere /ID[0], tako da ga lahko zabeležite ali nad njim uveljavite trditev
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;
Osvežitev se zgodi ob shranjevanju, ne ob preklopu zastavice, zato omogočanje deterministicnega načina pozno v gradnji dokumenta še vedno začne veljati. To pomeni tudi, da spremenjeno seme doseže datoteko ob naslednjem polnem shranjevanju: nastavite seme A, shranite, nastavite seme B, shranite, in datoteki nosita različna identifikatorja, medtem ko obnovitev semena A obnovi izvirno vrednost. Izrecno seme je pravilna izbira vedno, kadar ima vaš dokument naraven stabilen ključ, kot je številka računa, revizija zapisa ali identifikator git commita, ker identifikator loči od naključnih metapodatkov
Od kod pride seme, ko ga ne podate sami?
Brez izrecnega semena losLab PDF Library izpelje eno seme iz stanja dokumenta, ki bi moralo biti nespremenljivo med enakimi ponovnimi generiranji: glave različice PDF, števila strani in vsakega vnosa v slovarju informacij o dokumentu. Vrednosti nizov in imen se vzamejo dobesedno, drugi tipi objektov prispevajo svojo serializirano obliko, celota pa se zgosti v nize /ID. Pomembna posledica je, da sta CreationDate in ModDate del slovarja informacij in torej po zasnovi del semena. Dva zagona si zaslužita enak identifikator le, kadar resnično proizvedeta enake metapodatke dokumenta
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');
Fiksiranje ModDate s ključem 8 opravlja dvojno vlogo, in to je del, ki uporabnike ujame nepripravljene. Deterministični /ID sam po sebi ne naredi datoteke bajtno enake, ker pot shranjevanja žigosa ModDate s trenutnim časom, razen če je klicatelj to vrednost nastavil izrecno. Nastavitev ključa 8 označi vrednost kot podano s strani klicatelja in zavre ta žig. Če želite ponovljivo datoteko, ne le ponovljiv identifikator, obravnavajte časovne žige metapodatkov kot vhode gradnje: izpeljite jih iz izvornega zapisa ali iz fiksne epohe, nikoli iz Now
Zakaj prepisovanje ID-ja pokvari šifriran PDF?
Ker /ID[0] v šifriranem dokumentu ni le metapodatek, temveč ključni material. ISO 32000-1 §7.6.3.3 Algoritem 2 vnese prvi element identifikatorja datoteke v izračun šifrirnega ključa za standardni varnostni upravitelj pri revizijah 2 do 4, skupaj z zapolnjenim geslom, vrednostjo /O in bitmi dovoljenj. Izpeljani ključ nato ustvari validacijski niz /U, ki ga bralnik preveri ob odpiranju, ključ datoteke pa se izpelje in predpomni, ko pokličete Encrypt ali ko se naloži šifriran dokument, oboje pa se zgodi pred shranjevanjem. Prepisovanje identifikatorja med shranjevanjem bi zato oddalo strukturno veljavno datoteko, katere preverjanje /U ob ponovnem odpiranju odpove: ne subtilna okvara, temveč dokument, ki ga nihče ne more odpreti, vključno z vami. Zato je deterministična osvežitev omejena na dokumente, ki ne nosijo stanja šifriranja, in zato šifriran dokument obdrži kakršen koli /ID že ima, ne glede na deterministični način, nastavitev pa na tej poti nima nobenega učinka. Sorodno obravnavo revizij in semantiko dovoljenj pokriva vodič po reviziji šifriranja in dovoljenj PDF. Upoštevajte tudi, da pot obnovitve šifriranja osveži le /ID[1], identifikator spremembe, natanko tako, kot to predvideva §14.4
Zakaj postopna shranjevanja obdržijo izvirni identifikator
Druga meja je dodajalni način. Postopna posodobitev pusti vsak prejšnji bajt datoteke nedotaknjen in za njim zapiše novo revizijo, trajnost /ID[0] po §14.4 pa je tisto, kar odjemalcu pove, da nova revizija pripada istemu dokumentu kot stara. Prepisovanje bi to povezavo prekinilo, si nasprotovalo z revizijami, ki že sedijo v datoteki, in poseglo v semantiko podpisov, saj podpis pokriva razpon bajtov določene revizije določenega dokumenta. losLab PDF Library zato osveži deterministični identifikator le pri polnih shranjevanjih in nikoli med dodajalnim načinom, kar ohranja jamstvo, opisano v članku o postopnih posodobitvah PDF in dodajanju v tok nedotaknjeno
Ena sama točka za ustvarjanje identifikatorja
Vse ustvarjanje /ID v losLab PDF Library zdaj teče skozi eno samo notranjo rutino, NewFileIDString, kar deterministično stikalo naredi zanesljivo namesto zaplate na eni poti kode. Ustvarjanje praznega dokumenta, leno ustvarjanje manjkajočega polja /ID na zahtevo in pot obnovitve prstnega odtisa šifriranja vsi kličejo to rutino, zato obstaja natanko eno mesto, kjer bi sistemska ura lahko spet pricurljala nazaj. To tudi pomeni, da so prihodnje različice, na primer identifikator, izpeljan iz vsebine, sprememba ene same funkcije, ne revizija celotnega serializatorja
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');
To primerjavo vključite v svoj testni nabor, preden se kjer koli drugje zanesete na ponovljiv izhod, saj glasno odpove v trenutku, ko kakšna nova funkcija znova vnese časovni žig. Ponovljivost je lastnost, ki sicer tiho razpada, ena sama trditev nad dvema shranjevanjema v pomnilniku pa stane skoraj nič, da jo poganjate ob vsaki gradnji
Tukaj prikazan API za deterministični identifikator je del losLab PDF Library za Delphi in C++Builder, skupaj s celotnim referenčnim gradivom za informacije o dokumentu, šifriranje in postopno shranjevanje