diff --git a/specs/arch.excalidraw b/specs/arch.excalidraw index 8008274..f282369 100644 --- a/specs/arch.excalidraw +++ b/specs/arch.excalidraw @@ -6,9 +6,9 @@ { "id": "A3BjC6kJe019Pes-xjr_L", "type": "rectangle", - "x": 307.66668701171875, - "y": 719.3333740234375, - "width": 321.66668701171875, + "x": 229.66685485839844, + "y": 510.6665344238281, + "width": 142.66674804687503, "height": 104, "angle": 0, "strokeColor": "#1e1e1e", @@ -25,8 +25,8 @@ "type": 3 }, "seed": 1572721102, - "version": 56, - "versionNonce": 1402545874, + "version": 473, + "versionNonce": 372173867, "isDeleted": false, "boundElements": [ { @@ -34,17 +34,17 @@ "id": "KnkPQWiZzaALgJ-eehAOa" } ], - "updated": 1770174471076, + "updated": 1770713803521, "link": null, "locked": false }, { "id": "KnkPQWiZzaALgJ-eehAOa", "type": "text", - "x": 406.6700668334961, - "y": 746.3333740234375, - "width": 123.65992736816406, - "height": 50, + "x": 252.53026580810547, + "y": 525.1665344238281, + "width": 96.93992614746094, + "height": 75, "angle": 0, "strokeColor": "#1e1e1e", "backgroundColor": "transparent", @@ -58,22 +58,2251 @@ "index": "a0V", "roundness": null, "seed": 1287469326, - "version": 49, - "versionNonce": 630772558, + "version": 525, + "versionNonce": 359781899, "isDeleted": false, - "boundElements": null, - "updated": 1770174490368, + "boundElements": [], + "updated": 1770715837116, "link": null, "locked": false, - "text": "lib\n(data model)", + "text": "sync pp\n(rrdp :\nsnapshot)", "fontSize": 20, "fontFamily": 5, "textAlign": "center", "verticalAlign": "middle", "containerId": "A3BjC6kJe019Pes-xjr_L", - "originalText": "lib\n(data model)", + "originalText": "sync pp\n(rrdp : snapshot)", "autoResize": true, "lineHeight": 1.25 + }, + { + "id": "OEUoSQXsoNAlW1DKIAxfz", + "type": "rectangle", + "x": 450.8333435058594, + "y": 733, + "width": 273.0000305175781, + "height": 73.3333740234375, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a1", + "roundness": null, + "seed": 467204645, + "version": 134, + "versionNonce": 180665995, + "isDeleted": false, + "boundElements": [ + { + "id": "kuy3HgzouffiV8HGPVFXs", + "type": "arrow" + } + ], + "updated": 1770713136745, + "link": null, + "locked": false + }, + { + "id": "He3YhF-5V2DNcvisVxcR-", + "type": "rectangle", + "x": 462.5000305175781, + "y": 740.3334045410156, + "width": 34, + "height": 57.33331298828125, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a2", + "roundness": null, + "seed": 377649675, + "version": 61, + "versionNonce": 1723253093, + "isDeleted": false, + "boundElements": null, + "updated": 1770712989659, + "link": null, + "locked": false + }, + { + "id": "429lDIK7sZZVrobXBCCOM", + "type": "rectangle", + "x": 677.5, + "y": 740.3333740234375, + "width": 34, + "height": 57.33331298828125, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a3", + "roundness": null, + "seed": 381193739, + "version": 131, + "versionNonce": 1416506635, + "isDeleted": false, + "boundElements": [ + { + "id": "JIgaMBDeOw0e_QXCpZVtJ", + "type": "arrow" + } + ], + "updated": 1770713188483, + "link": null, + "locked": false + }, + { + "id": "1oL0AZ6pawivSjBm5AkQ7", + "type": "text", + "x": 565.1666870117188, + "y": 755.0000305175781, + "width": 27.399978637695312, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a4", + "roundness": null, + "seed": 301396005, + "version": 50, + "versionNonce": 935935979, + "isDeleted": false, + "boundElements": null, + "updated": 1770713011072, + "link": null, + "locked": false, + "text": ".....", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": ".....", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "4LB5PqQJnm4ueuWOwSbwc", + "type": "text", + "x": 507.8333740234375, + "y": 829.6667175292969, + "width": 186.4598388671875, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a5", + "roundness": null, + "seed": 1180991787, + "version": 64, + "versionNonce": 664751051, + "isDeleted": false, + "boundElements": null, + "updated": 1770713034384, + "link": null, + "locked": false, + "text": "CA Instance Queue", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "CA Instance Queue", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "TFaVjSDfJfUcVXyChBCXv", + "type": "text", + "x": 188.50003051757812, + "y": 763.0000305175781, + "width": 38.319976806640625, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a7", + "roundness": null, + "seed": 1650142597, + "version": 5, + "versionNonce": 1052482507, + "isDeleted": false, + "boundElements": null, + "updated": 1770713114585, + "link": null, + "locked": false, + "text": "TAL", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "TAL", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "idOFy_iWEKNagEh1MY6sO", + "type": "text", + "x": 315.3400115966797, + "y": 760.5000305175781, + "width": 27.459991455078125, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a8", + "roundness": null, + "seed": 1543888459, + "version": 74, + "versionNonce": 1318869893, + "isDeleted": false, + "boundElements": [], + "updated": 1770713131151, + "link": null, + "locked": false, + "text": "TA", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "TA", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "kuy3HgzouffiV8HGPVFXs", + "type": "arrow", + "x": 371.16668701171875, + "y": 769.6667175292969, + "width": 64, + "height": 0.66668701171875, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a9", + "roundness": { + "type": 2 + }, + "seed": 839188037, + "version": 31, + "versionNonce": 291819, + "isDeleted": false, + "boundElements": null, + "updated": 1770713136745, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 64, + 0.66668701171875 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": { + "elementId": "OEUoSQXsoNAlW1DKIAxfz", + "focus": -0.05912097242545651, + "gap": 15.666656494140625 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "vZUF8ByLdBuAz9XP5QeuX", + "type": "arrow", + "x": 245.1666259765625, + "y": 770.3333435058594, + "width": 49.333343505859375, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aA", + "roundness": { + "type": 2 + }, + "seed": 2018039755, + "version": 42, + "versionNonce": 812442347, + "isDeleted": false, + "boundElements": null, + "updated": 1770713145632, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 49.333343505859375, + 0 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "C3kbyNY6B_sroFAwxCwsC", + "type": "rectangle", + "x": 788.833251953125, + "y": 682.3333129882812, + "width": 34, + "height": 57.33331298828125, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aB", + "roundness": null, + "seed": 943491077, + "version": 195, + "versionNonce": 1619895909, + "isDeleted": false, + "boundElements": [ + { + "id": "cktmBgzDHQVp__ThQSYj7", + "type": "arrow" + }, + { + "id": "t6L4IloEw2niv4WL5K7nm", + "type": "arrow" + } + ], + "updated": 1770713896198, + "link": null, + "locked": false + }, + { + "id": "Szfqf3ja4il1wMaTv_uj7", + "type": "text", + "x": 787.27001953125, + "y": 755.1666564941406, + "width": 119.69989013671875, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aC", + "roundness": null, + "seed": 235369867, + "version": 156, + "versionNonce": 804417579, + "isDeleted": false, + "boundElements": [], + "updated": 1770713191158, + "link": null, + "locked": false, + "text": "CA Instance", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "CA Instance", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "JIgaMBDeOw0e_QXCpZVtJ", + "type": "arrow", + "x": 725.833251953125, + "y": 772.3333435058594, + "width": 46, + "height": 52.66668701171875, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aD", + "roundness": { + "type": 2 + }, + "seed": 971931179, + "version": 116, + "versionNonce": 182150661, + "isDeleted": false, + "boundElements": [], + "updated": 1770713189266, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 46, + -52.66668701171875 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "429lDIK7sZZVrobXBCCOM", + "focus": 0.8146120463303697, + "gap": 14.333251953125 + }, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "kLzcK45_-wwExQkPC-3H2", + "type": "rectangle", + "x": 200.49993896484375, + "y": 478.99993896484375, + "width": 859.3333129882814, + "height": 151.33331298828125, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aF", + "roundness": null, + "seed": 202597093, + "version": 289, + "versionNonce": 1742973061, + "isDeleted": false, + "boundElements": [ + { + "id": "t6L4IloEw2niv4WL5K7nm", + "type": "arrow" + }, + { + "id": "YAnz1mQQJg1wxRipPpmKi", + "type": "arrow" + }, + { + "id": "a48Sp0St07ikfp4HPkCQB", + "type": "arrow" + }, + { + "id": "Pt3BLxaIlOtwd7YRMQn5v", + "type": "arrow" + }, + { + "id": "vQalvc46G5OWeDrkewoJH", + "type": "arrow" + }, + { + "id": "7YXvUCaxevFO0Iezcy6IC", + "type": "arrow" + } + ], + "updated": 1770715551581, + "link": null, + "locked": false + }, + { + "id": "z1PCZEC6PlKPNyMaVt07U", + "type": "text", + "x": 579.1681678945889, + "y": 636.6551279590612, + "width": 182.1998291015625, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aG", + "roundness": null, + "seed": 1565035915, + "version": 377, + "versionNonce": 448729029, + "isDeleted": false, + "boundElements": [], + "updated": 1770714961779, + "link": null, + "locked": false, + "text": "Processing Pipeline", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Processing Pipeline", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "v1_0G9KNH85btQTB3ih87", + "type": "rectangle", + "x": 395.16656494140625, + "y": 506.99993896484375, + "width": 142.66674804687503, + "height": 104, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aH", + "roundness": { + "type": 3 + }, + "seed": 160797035, + "version": 517, + "versionNonce": 2048833739, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "xxfRjmFsMWLM9GPtxrzcX" + }, + { + "id": "4KlrFqoYod1GoHYtp3QB4", + "type": "arrow" + } + ], + "updated": 1770715127136, + "link": null, + "locked": false + }, + { + "id": "xxfRjmFsMWLM9GPtxrzcX", + "type": "text", + "x": 425.10997772216797, + "y": 533.9999389648438, + "width": 82.77992248535156, + "height": 50, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aI", + "roundness": null, + "seed": 1331047435, + "version": 593, + "versionNonce": 996077899, + "isDeleted": false, + "boundElements": [], + "updated": 1770713803521, + "link": null, + "locked": false, + "text": "process\nmanifest", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "v1_0G9KNH85btQTB3ih87", + "originalText": "process\nmanifest", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "OF9Zme4Y72rBJkDJv58Rb", + "type": "rectangle", + "x": 566.4999389648438, + "y": 503.6666259765625, + "width": 142.66674804687503, + "height": 104, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aJ", + "roundness": { + "type": 3 + }, + "seed": 1754257611, + "version": 562, + "versionNonce": 1688768491, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "RMfTmdKtVF2zNIveJYUAC" + } + ], + "updated": 1770713803521, + "link": null, + "locked": false + }, + { + "id": "RMfTmdKtVF2zNIveJYUAC", + "type": "text", + "x": 600.9733428955078, + "y": 530.6666259765625, + "width": 73.71994018554688, + "height": 50, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aK", + "roundness": null, + "seed": 441485163, + "version": 660, + "versionNonce": 2089114251, + "isDeleted": false, + "boundElements": [], + "updated": 1770713803521, + "link": null, + "locked": false, + "text": "extract\nobjects", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "OF9Zme4Y72rBJkDJv58Rb", + "originalText": "extract\nobjects", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "umGo7PbCuL0F4zhqtdX1_", + "type": "rectangle", + "x": 732.4998779296875, + "y": 503.6666259765625, + "width": 142.66674804687503, + "height": 104, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aL", + "roundness": { + "type": 3 + }, + "seed": 1275946373, + "version": 622, + "versionNonce": 531616043, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "b7w7bIGqgN85Z8iYT0Dcj" + } + ], + "updated": 1770713803521, + "link": null, + "locked": false + }, + { + "id": "b7w7bIGqgN85Z8iYT0Dcj", + "type": "text", + "x": 751.0832977294922, + "y": 530.6666259765625, + "width": 105.49990844726562, + "height": 50, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aM", + "roundness": null, + "seed": 917531877, + "version": 776, + "versionNonce": 1871868875, + "isDeleted": false, + "boundElements": [], + "updated": 1770713803521, + "link": null, + "locked": false, + "text": "discover\nchildren CA", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "umGo7PbCuL0F4zhqtdX1_", + "originalText": "discover\nchildren CA", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "im7H13hqf0_1s6jd5E0a4", + "type": "rectangle", + "x": 901.833251953125, + "y": 505.0000305175781, + "width": 142.66674804687503, + "height": 104, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aN", + "roundness": { + "type": 3 + }, + "seed": 1772745611, + "version": 662, + "versionNonce": 1406108427, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "h29w2So5ml3xekBVdbgaz" + } + ], + "updated": 1770713812236, + "link": null, + "locked": false + }, + { + "id": "h29w2So5ml3xekBVdbgaz", + "type": "text", + "x": 947.9066467285156, + "y": 532.0000305175781, + "width": 50.51995849609375, + "height": 50, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aO", + "roundness": null, + "seed": 249121323, + "version": 835, + "versionNonce": 1554385099, + "isDeleted": false, + "boundElements": [], + "updated": 1770713822622, + "link": null, + "locked": false, + "text": "build\naudit", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "im7H13hqf0_1s6jd5E0a4", + "originalText": "build\naudit", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "t6L4IloEw2niv4WL5K7nm", + "type": "arrow", + "x": 772.9289455258842, + "y": 688.035284189194, + "width": 649.4359483818903, + "height": 129.18418959695612, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aQ", + "roundness": { + "type": 2 + }, + "seed": 913179205, + "version": 232, + "versionNonce": 2000429477, + "isDeleted": false, + "boundElements": [], + "updated": 1770713900954, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -606.3745786685331, + -3.515241767764792 + ], + [ + -649.4359483818903, + -108.09289990413765 + ], + [ + -601.9805465730485, + -129.18418959695612 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "C3kbyNY6B_sroFAwxCwsC", + "focus": 0.7917180455874194, + "gap": 15.90430642724084 + }, + "endBinding": { + "elementId": "kLzcK45_-wwExQkPC-3H2", + "focus": 0.7497759659582492, + "gap": 29.551540012008104 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "843HQCqnmHcT3PYPB0Els", + "type": "ellipse", + "x": 1148.1782083580185, + "y": 356.7261813981366, + "width": 124.79015750147141, + "height": 92.27443263452369, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aW", + "roundness": null, + "seed": 1422139211, + "version": 61, + "versionNonce": 587213925, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "zl-h4gpMTiazFye5YqQb9" + }, + { + "id": "YAnz1mQQJg1wxRipPpmKi", + "type": "arrow" + }, + { + "id": "W76Psz4HIaspsJifBTHpa", + "type": "arrow" + } + ], + "updated": 1770715650828, + "link": null, + "locked": false + }, + { + "id": "zl-h4gpMTiazFye5YqQb9", + "type": "text", + "x": 1183.9733233426903, + "y": 390.23945919239196, + "width": 52.9599609375, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aX", + "roundness": null, + "seed": 708083883, + "version": 8, + "versionNonce": 742717515, + "isDeleted": false, + "boundElements": null, + "updated": 1770714823157, + "link": null, + "locked": false, + "text": "VRPS", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "843HQCqnmHcT3PYPB0Els", + "originalText": "VRPS", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "rtPxnJZ_KJKqnYDczuitY", + "type": "ellipse", + "x": 1156.0874822212681, + "y": 480.19815340802825, + "width": 124.79015750147141, + "height": 92.27443263452369, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aY", + "roundness": null, + "seed": 308230187, + "version": 109, + "versionNonce": 946646309, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "N9Dxb1Tcm5JN5-pNz4qZ2" + }, + { + "id": "a48Sp0St07ikfp4HPkCQB", + "type": "arrow" + }, + { + "id": "o0LBiXrYpF6thqL7BFYKo", + "type": "arrow" + } + ], + "updated": 1770715655497, + "link": null, + "locked": false + }, + { + "id": "N9Dxb1Tcm5JN5-pNz4qZ2", + "type": "text", + "x": 1186.622602699104, + "y": 513.7114312022836, + "width": 63.479949951171875, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aZ", + "roundness": null, + "seed": 1170012875, + "version": 62, + "versionNonce": 416444485, + "isDeleted": false, + "boundElements": [], + "updated": 1770714833646, + "link": null, + "locked": false, + "text": "ASPAS", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "rtPxnJZ_KJKqnYDczuitY", + "originalText": "ASPAS", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "YAnz1mQQJg1wxRipPpmKi", + "type": "arrow", + "x": 1085.7831698357254, + "y": 495.57718528533957, + "width": 54.48580488748621, + "height": 69.42552205782272, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aa", + "roundness": { + "type": 2 + }, + "seed": 533521157, + "version": 32, + "versionNonce": 1900454917, + "isDeleted": false, + "boundElements": [], + "updated": 1770714840090, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 54.48580488748621, + -69.42552205782272 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "kLzcK45_-wwExQkPC-3H2", + "focus": 0.8368105037552906, + "gap": 25.949917882600403 + }, + "endBinding": { + "elementId": "843HQCqnmHcT3PYPB0Els", + "focus": 0.5276404713432764, + "gap": 13.81162576334855 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "a48Sp0St07ikfp4HPkCQB", + "type": "arrow", + "x": 1089.2984116034902, + "y": 524.5777086129651, + "width": 50.97056311972142, + "height": 0.8788305561624838, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "ac", + "roundness": { + "type": 2 + }, + "seed": 202843845, + "version": 39, + "versionNonce": 225090283, + "isDeleted": false, + "boundElements": [], + "updated": 1770714844780, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 50.97056311972142, + -0.8788305561624838 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "kLzcK45_-wwExQkPC-3H2", + "focus": -0.26689844330457513, + "gap": 29.465159650365194 + }, + "endBinding": { + "elementId": "rtPxnJZ_KJKqnYDczuitY", + "focus": 0.0844056092074887, + "gap": 15.888046773141621 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "DK8JWu-LB_JZjLeitPkpQ", + "type": "diamond", + "x": 406.02847240618723, + "y": 322.0134203692252, + "width": 178.39717206123768, + "height": 108.09285967569502, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "af", + "roundness": null, + "seed": 80098635, + "version": 199, + "versionNonce": 479805963, + "isDeleted": false, + "boundElements": [ + { + "id": "4KlrFqoYod1GoHYtp3QB4", + "type": "arrow" + } + ], + "updated": 1770715127136, + "link": null, + "locked": false + }, + { + "id": "_dnjAEiKy2tQflIZ68Y75", + "type": "diamond", + "x": 181.05480954915538, + "y": 323.77108148155014, + "width": 211.7915665705777, + "height": 108.09285967569502, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "ag", + "roundness": null, + "seed": 1496227493, + "version": 208, + "versionNonce": 1803201637, + "isDeleted": false, + "boundElements": [ + { + "id": "Pt3BLxaIlOtwd7YRMQn5v", + "type": "arrow" + } + ], + "updated": 1770715120138, + "link": null, + "locked": false + }, + { + "id": "eFHZwprSnx_MmU5qpFi_b", + "type": "text", + "x": 237.21178614013763, + "y": 238.17249994796077, + "width": 112.7799072265625, + "height": 75, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 90, + "groupIds": [], + "frameId": null, + "index": "ah", + "roundness": null, + "seed": 384823051, + "version": 323, + "versionNonce": 1467795045, + "isDeleted": false, + "boundElements": null, + "updated": 1770715873720, + "link": null, + "locked": false, + "text": "Rocksdb\nRAW \nfile storage", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Rocksdb\nRAW \nfile storage", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "N_CnzkNMno_NcalbX69HZ", + "type": "text", + "x": 459.02706095157197, + "y": 234.60973935983566, + "width": 81.51992797851562, + "height": 75, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "ai", + "roundness": null, + "seed": 1487292645, + "version": 421, + "versionNonce": 1520370981, + "isDeleted": false, + "boundElements": [], + "updated": 1770715856943, + "link": null, + "locked": false, + "text": "Rocksdb\nCached \npp pack", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Rocksdb\nCached \npp pack", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "FKPacTBkdYfEDyoTeVKYK", + "type": "text", + "x": 216.91768466165425, + "y": 366.2200973276109, + "width": 149.5598602294922, + "height": 50, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aj", + "roundness": null, + "seed": 798575717, + "version": 43, + "versionNonce": 1640069157, + "isDeleted": false, + "boundElements": [], + "updated": 1770715754840, + "link": null, + "locked": false, + "text": "roa/mft/crl/cer\n...", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "roa/mft/crl/cer\n...", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "0VLNDqkI9xTDkpa9klFZE", + "type": "text", + "x": 425.3382465511269, + "y": 362.8778343775037, + "width": 138.01988220214844, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "al", + "roundness": null, + "seed": 658771717, + "version": 42, + "versionNonce": 589361675, + "isDeleted": false, + "boundElements": null, + "updated": 1770715099668, + "link": null, + "locked": false, + "text": "pp1/pp2/pp3...", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "pp1/pp2/pp3...", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "Pt3BLxaIlOtwd7YRMQn5v", + "type": "arrow", + "x": 288.70815337566285, + "y": 500.8500077085442, + "width": 0, + "height": 61.51624819457322, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "ao", + "roundness": null, + "seed": 671800651, + "version": 37, + "versionNonce": 1454988453, + "isDeleted": false, + "boundElements": [], + "updated": 1770715120859, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + -61.51624819457322 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "kLzcK45_-wwExQkPC-3H2", + "focus": -0.7947054697458888, + "gap": 21.850068743700433 + }, + "endBinding": { + "elementId": "_dnjAEiKy2tQflIZ68Y75", + "focus": -0.0325194568168833, + "gap": 7.650603380853567 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "4KlrFqoYod1GoHYtp3QB4", + "type": "arrow", + "x": 498.7422197476858, + "y": 492.94077407373726, + "width": 0.8788305561624838, + "height": 55.364635443648694, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aq", + "roundness": null, + "seed": 1936471909, + "version": 31, + "versionNonce": 328269259, + "isDeleted": false, + "boundElements": [], + "updated": 1770715127802, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.8788305561624838, + -55.364635443648694 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "v1_0G9KNH85btQTB3ih87", + "focus": 0.43229228206783604, + "gap": 14.05916489110649 + }, + "endBinding": { + "elementId": "DK8JWu-LB_JZjLeitPkpQ", + "focus": -0.09936772335567325, + "gap": 8.288872253399507 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "U4xi117P4_jufOhTzVO-f", + "type": "ellipse", + "x": 1159.6027239890327, + "y": 605.8669805518919, + "width": 124.79015750147141, + "height": 92.27443263452369, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "as", + "roundness": null, + "seed": 495749285, + "version": 158, + "versionNonce": 230478603, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "IwuR5g2qVAXRdW_qLao0g" + }, + { + "id": "vQalvc46G5OWeDrkewoJH", + "type": "arrow" + }, + { + "id": "2G3GCq_dSEQyV1-jEXeqc", + "type": "arrow" + } + ], + "updated": 1770715305141, + "link": null, + "locked": false + }, + { + "id": "IwuR5g2qVAXRdW_qLao0g", + "type": "text", + "x": 1185.8778499600326, + "y": 626.8802583461472, + "width": 71.99993896484375, + "height": 50, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "at", + "roundness": null, + "seed": 1318988805, + "version": 129, + "versionNonce": 1027538277, + "isDeleted": false, + "boundElements": [], + "updated": 1770715285634, + "link": null, + "locked": false, + "text": "children\nCA", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "U4xi117P4_jufOhTzVO-f", + "originalText": "children\nCA", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "vQalvc46G5OWeDrkewoJH", + "type": "arrow", + "x": 1086.6619199350027, + "y": 574.6695216334091, + "width": 57.12229655597366, + "height": 49.212982464281595, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "au", + "roundness": null, + "seed": 1091621, + "version": 57, + "versionNonce": 429104389, + "isDeleted": false, + "boundElements": [], + "updated": 1770715294531, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 57.12229655597366, + 49.212982464281595 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "kLzcK45_-wwExQkPC-3H2", + "focus": -0.8372608711722136, + "gap": 26.828667981877743 + }, + "endBinding": { + "elementId": "U4xi117P4_jufOhTzVO-f", + "focus": -0.3930431622123068, + "gap": 23.065911483134556 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "2G3GCq_dSEQyV1-jEXeqc", + "type": "arrow", + "x": 1214.0884886480765, + "y": 718.7933077153741, + "width": 803.2266392680979, + "height": 166.97273688710857, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aw", + "roundness": { + "type": 2 + }, + "seed": 1118350091, + "version": 124, + "versionNonce": 1191218219, + "isDeleted": false, + "boundElements": [], + "updated": 1770715306172, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -140.6086247710855, + 166.09390633094608 + ], + [ + -803.2266392680979, + 166.97273688710857 + ], + [ + -783.8929604020511, + 85.24398932743668 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "U4xi117P4_jufOhTzVO-f", + "focus": -0.479352215629856, + "gap": 20.949548539847708 + }, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "kX3rMNwDurF6i699O_SE8", + "type": "text", + "x": 110.67323112589077, + "y": 157.70055147613942, + "width": 336.02783203125, + "height": 35, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "ay", + "roundness": null, + "seed": 1264632357, + "version": 105, + "versionNonce": 933795019, + "isDeleted": false, + "boundElements": null, + "updated": 1770715475754, + "link": null, + "locked": false, + "text": "V1. Sequential processing", + "fontSize": 28, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "V1. Sequential processing", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "iMvryJksIX4kiotrrzNMi", + "type": "ellipse", + "x": 1143.9240010952778, + "y": 248.03048232085865, + "width": 124.79015750147141, + "height": 92.27443263452369, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "az", + "roundness": null, + "seed": 1848530763, + "version": 90, + "versionNonce": 1837481259, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "QOdrTAXBCmW3tHgLkvKXm" + }, + { + "id": "7YXvUCaxevFO0Iezcy6IC", + "type": "arrow" + }, + { + "id": "oUjYcLN0PDXh5SqSaNAWx", + "type": "arrow" + } + ], + "updated": 1770715646534, + "link": null, + "locked": false + }, + { + "id": "QOdrTAXBCmW3tHgLkvKXm", + "type": "text", + "x": 1175.5091246248714, + "y": 281.543760115114, + "width": 61.37994384765625, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b00", + "roundness": null, + "seed": 345716203, + "version": 43, + "versionNonce": 1180190219, + "isDeleted": false, + "boundElements": [], + "updated": 1770715541086, + "link": null, + "locked": false, + "text": "audits", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "iMvryJksIX4kiotrrzNMi", + "originalText": "audits", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "7YXvUCaxevFO0Iezcy6IC", + "type": "arrow", + "x": 1071.170836964614, + "y": 458.23867826807736, + "width": 69.41464522805859, + "height": 132.51886816265744, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b02", + "roundness": { + "type": 2 + }, + "seed": 1262289355, + "version": 39, + "versionNonce": 402592645, + "isDeleted": false, + "boundElements": [], + "updated": 1770715552130, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 69.41464522805859, + -132.51886816265744 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "kLzcK45_-wwExQkPC-3H2", + "focus": 0.8320755854741445, + "gap": 23.655248458044902 + }, + "endBinding": { + "elementId": "iMvryJksIX4kiotrrzNMi", + "focus": 0.5684502667918746, + "gap": 14.443376509558341 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "fjx7X_gy8MWBBc13kSEj2", + "type": "ellipse", + "x": 1334.0385113175626, + "y": 356.33343728288344, + "width": 124.79015750147141, + "height": 92.27443263452369, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b04", + "roundness": null, + "seed": 524119141, + "version": 137, + "versionNonce": 2089948965, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "ppT0eZXbt_-txmfFdXSiJ" + }, + { + "id": "W76Psz4HIaspsJifBTHpa", + "type": "arrow" + } + ], + "updated": 1770715650828, + "link": null, + "locked": false + }, + { + "id": "ppT0eZXbt_-txmfFdXSiJ", + "type": "text", + "x": 1369.8336263022343, + "y": 377.3467150771388, + "width": 52.9599609375, + "height": 50, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b05", + "roundness": null, + "seed": 2013042629, + "version": 90, + "versionNonce": 1208732619, + "isDeleted": false, + "boundElements": [], + "updated": 1770715630147, + "link": null, + "locked": false, + "text": "all\nVRPS", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "fjx7X_gy8MWBBc13kSEj2", + "originalText": "all\nVRPS", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "vU2SoMKFYnRv7qQ8ougXL", + "type": "ellipse", + "x": 1341.9477851808124, + "y": 479.80540929277515, + "width": 124.79015750147141, + "height": 92.27443263452369, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b06", + "roundness": null, + "seed": 2055165733, + "version": 185, + "versionNonce": 333801445, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "oDt_WC0CBa0qsC6aku2og" + }, + { + "id": "o0LBiXrYpF6thqL7BFYKo", + "type": "arrow" + } + ], + "updated": 1770715655497, + "link": null, + "locked": false + }, + { + "id": "oDt_WC0CBa0qsC6aku2og", + "type": "text", + "x": 1372.4829056586482, + "y": 500.8186870870305, + "width": 63.479949951171875, + "height": 50, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b07", + "roundness": null, + "seed": 1362307717, + "version": 143, + "versionNonce": 1855620523, + "isDeleted": false, + "boundElements": [], + "updated": 1770715641267, + "link": null, + "locked": false, + "text": "all\nASPAS", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "vU2SoMKFYnRv7qQ8ougXL", + "originalText": "all\nASPAS", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "_-RUXNYuXXTiB3Q--94P9", + "type": "ellipse", + "x": 1329.7843040548223, + "y": 247.6377382056055, + "width": 124.79015750147141, + "height": 92.27443263452369, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b08", + "roundness": null, + "seed": 2094371301, + "version": 166, + "versionNonce": 647147115, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "TyqsGlPV27EPQNidoSqTU" + }, + { + "id": "oUjYcLN0PDXh5SqSaNAWx", + "type": "arrow" + } + ], + "updated": 1770715646534, + "link": null, + "locked": false + }, + { + "id": "TyqsGlPV27EPQNidoSqTU", + "type": "text", + "x": 1361.369427584416, + "y": 268.6510159998609, + "width": 61.37994384765625, + "height": 50, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b09", + "roundness": null, + "seed": 175377733, + "version": 135, + "versionNonce": 274912075, + "isDeleted": false, + "boundElements": [], + "updated": 1770715637300, + "link": null, + "locked": false, + "text": "all\naudits", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "_-RUXNYuXXTiB3Q--94P9", + "originalText": "all\naudits", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "oUjYcLN0PDXh5SqSaNAWx", + "type": "arrow", + "x": 1275.7337250740727, + "y": 294.1676986381206, + "width": 42.595350480853995, + "height": 1.0517691453775342, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b0A", + "roundness": { + "type": 2 + }, + "seed": 644830955, + "version": 19, + "versionNonce": 1674239947, + "isDeleted": false, + "boundElements": null, + "updated": 1770715646534, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 42.595350480853995, + 1.0517691453775342 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "iMvryJksIX4kiotrrzNMi", + "focus": -0.03594946064605954, + "gap": 7.0195664773233375 + }, + "endBinding": { + "elementId": "_-RUXNYuXXTiB3Q--94P9", + "focus": -0.06854401054159467, + "gap": 11.478115061936531 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "W76Psz4HIaspsJifBTHpa", + "type": "arrow", + "x": 1280.992378222155, + "y": 402.49664677231607, + "width": 43.64711962623164, + "height": 1.0517691453775342, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b0B", + "roundness": { + "type": 2 + }, + "seed": 528764843, + "version": 26, + "versionNonce": 1969706949, + "isDeleted": false, + "boundElements": null, + "updated": 1770715650828, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 43.64711962623164, + -1.0517691453775342 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "843HQCqnmHcT3PYPB0Els", + "focus": 0.02792031962434366, + "gap": 8.025608282862457 + }, + "endBinding": { + "elementId": "fjx7X_gy8MWBBc13kSEj2", + "focus": 0.05784554647968262, + "gap": 9.411101585368083 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "o0LBiXrYpF6thqL7BFYKo", + "type": "arrow", + "x": 1289.4062425169673, + "y": 518.1876900559462, + "width": 49.957541919691494, + "height": 1.0516728559748572, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b0C", + "roundness": { + "type": 2 + }, + "seed": 691556837, + "version": 27, + "versionNonce": 1392885893, + "isDeleted": false, + "boundElements": null, + "updated": 1770715655497, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 49.957541919691494, + -1.0516728559748572 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "rtPxnJZ_KJKqnYDczuitY", + "focus": -0.1402432291576511, + "gap": 9.29755273930887 + }, + "endBinding": { + "elementId": "vU2SoMKFYnRv7qQ8ougXL", + "focus": 0.21442257431549638, + "gap": 3.6200174208301923 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false } ], "appState": { diff --git a/src/policy.rs b/src/policy.rs index 6c99cc4..db7c756 100644 --- a/src/policy.rs +++ b/src/policy.rs @@ -16,13 +16,13 @@ impl Default for SyncPreference { #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum CaFailedFetchPolicy { - UseVerifiedCache, + UseFetchCachePp, StopAllOutput, } impl Default for CaFailedFetchPolicy { fn default() -> Self { - Self::UseVerifiedCache + Self::UseFetchCachePp } } diff --git a/src/storage.rs b/src/storage.rs index bfa7a15..7aac158 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -9,15 +9,15 @@ use sha2::Digest; use std::collections::HashSet; const CF_RAW_OBJECTS: &str = "raw_objects"; -const CF_VERIFIED_PUBLICATION_POINTS: &str = "verified_publication_points"; +const CF_FETCH_CACHE_PP: &str = "fetch_cache_pp"; const CF_RRDP_STATE: &str = "rrdp_state"; #[derive(Clone, Debug, PartialEq, Eq)] -pub struct VerifiedKey(String); +pub struct FetchCachePpKey(String); -impl VerifiedKey { +impl FetchCachePpKey { pub fn from_manifest_rsync_uri(manifest_rsync_uri: &str) -> Self { - Self(format!("verified:{manifest_rsync_uri}")) + Self(format!("fetch_cache_pp:{manifest_rsync_uri}")) } pub fn as_str(&self) -> &str { @@ -33,7 +33,7 @@ pub enum StorageError { #[error("missing column family: {0}")] MissingColumnFamily(&'static str), - #[error("verified publication point pack error: {0}")] + #[error("fetch_cache_pp pack error: {0}")] Pack(#[from] PackDecodeError), } @@ -44,7 +44,7 @@ pub struct RocksStore { } pub mod pack { - pub use super::{PackDecodeError, PackFile, PackTime, VerifiedPublicationPointPack}; + pub use super::{FetchCachePpPack, PackDecodeError, PackFile, PackTime}; } impl RocksStore { @@ -61,7 +61,7 @@ impl RocksStore { let cfs = vec![ ColumnFamilyDescriptor::new(CF_RAW_OBJECTS, Options::default()), - ColumnFamilyDescriptor::new(CF_VERIFIED_PUBLICATION_POINTS, Options::default()), + ColumnFamilyDescriptor::new(CF_FETCH_CACHE_PP, Options::default()), ColumnFamilyDescriptor::new(CF_RRDP_STATE, Options::default()), ]; @@ -102,16 +102,19 @@ impl RocksStore { Ok(()) } - pub fn put_verified(&self, key: &VerifiedKey, bytes: &[u8]) -> StorageResult<()> { - let cf = self.cf(CF_VERIFIED_PUBLICATION_POINTS)?; + pub fn put_fetch_cache_pp(&self, key: &FetchCachePpKey, bytes: &[u8]) -> StorageResult<()> { + let cf = self.cf(CF_FETCH_CACHE_PP)?; self.db .put_cf(cf, key.as_str().as_bytes(), bytes) .map_err(|e| StorageError::RocksDb(e.to_string()))?; Ok(()) } - pub fn get_verified(&self, key: &VerifiedKey) -> StorageResult>> { - let cf = self.cf(CF_VERIFIED_PUBLICATION_POINTS)?; + pub fn get_fetch_cache_pp( + &self, + key: &FetchCachePpKey, + ) -> StorageResult>> { + let cf = self.cf(CF_FETCH_CACHE_PP)?; let v = self .db .get_cf(cf, key.as_str().as_bytes()) @@ -172,10 +175,10 @@ impl RocksStore { } #[allow(dead_code)] - pub fn verified_iter_all<'a>( + pub fn fetch_cache_pp_iter_all<'a>( &'a self, ) -> StorageResult, Box<[u8]>)> + 'a> { - let cf = self.cf(CF_VERIFIED_PUBLICATION_POINTS)?; + let cf = self.cf(CF_FETCH_CACHE_PP)?; let mode = IteratorMode::Start; Ok(self.db.iterator_cf(cf, mode).filter_map(|res| res.ok())) } @@ -213,12 +216,17 @@ fn enable_blobdb_if_supported(opts: &mut Options) { } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct VerifiedPublicationPointPack { +pub struct FetchCachePpPack { pub format_version: u32, pub manifest_rsync_uri: String, pub publication_point_rsync_uri: String, + /// Manifest manifestNumber value (RFC 9286 §4.2.1). + /// + /// Minimal big-endian bytes. For zero, this is `[0]`. + pub manifest_number_be: Vec, + pub this_update: PackTime, pub next_update: PackTime, @@ -233,7 +241,7 @@ pub struct VerifiedPublicationPointPack { pub files: Vec, } -impl VerifiedPublicationPointPack { +impl FetchCachePpPack { pub const FORMAT_VERSION_V1: u32 = 1; pub fn encode(&self) -> StorageResult> { @@ -258,6 +266,23 @@ impl VerifiedPublicationPointPack { if self.publication_point_rsync_uri.is_empty() { return Err(PackDecodeError::MissingField("publication_point_rsync_uri").into()); } + if self.manifest_number_be.is_empty() { + return Err(PackDecodeError::MissingField("manifest_number_be").into()); + } + if self.manifest_number_be.len() > 20 { + return Err(PackDecodeError::InvalidField { + field: "manifest_number_be", + detail: "must be at most 20 octets (RFC 9286 §4.2.1)", + } + .into()); + } + if self.manifest_number_be.len() > 1 && self.manifest_number_be[0] == 0 { + return Err(PackDecodeError::InvalidField { + field: "manifest_number_be", + detail: "must be minimal big-endian (no leading zeros)", + } + .into()); + } self.this_update .parse() @@ -354,10 +379,10 @@ impl PackTime { #[derive(Debug, thiserror::Error)] pub enum PackDecodeError { - #[error("encode verified publication point pack failed: {0}")] + #[error("encode fetch_cache_pp pack failed: {0}")] Encode(String), - #[error("decode verified publication point pack failed: {0}")] + #[error("decode fetch_cache_pp pack failed: {0}")] Decode(String), #[error("unsupported pack format_version: {0}")] @@ -366,16 +391,22 @@ pub enum PackDecodeError { #[error("missing required field: {0}")] MissingField(&'static str), - #[error("missing manifest_bytes in verified pack")] + #[error("invalid field {field}: {detail}")] + InvalidField { + field: &'static str, + detail: &'static str, + }, + + #[error("missing manifest_bytes in fetch_cache_pp pack")] MissingManifestBytes, - #[error("duplicate file rsync uri in verified pack: {0}")] + #[error("duplicate file rsync uri in fetch_cache_pp pack: {0}")] DuplicateFileRsyncUri(String), - #[error("empty file bytes in verified pack: {0}")] + #[error("empty file bytes in fetch_cache_pp pack: {0}")] EmptyFileBytes(String), - #[error("file hash mismatch in verified pack: {rsync_uri}")] + #[error("file hash mismatch in fetch_cache_pp pack: {rsync_uri}")] FileHashMismatch { rsync_uri: String }, #[error("invalid time field {field}: {detail}")] diff --git a/src/validation/manifest.rs b/src/validation/manifest.rs index 3415092..ff832e3 100644 --- a/src/validation/manifest.rs +++ b/src/validation/manifest.rs @@ -2,19 +2,22 @@ use crate::data_model::manifest::{ManifestDecodeError, ManifestObject, ManifestV use crate::data_model::signed_object::SignedObjectVerifyError; use crate::policy::{CaFailedFetchPolicy, Policy}; use crate::report::{RfcRef, Warning}; -use crate::storage::{RocksStore, StorageError, VerifiedKey, VerifiedPublicationPointPack}; +use crate::storage::{FetchCachePpKey, FetchCachePpPack, RocksStore, StorageError}; +use crate::validation::cert_path::{CertPathError, validate_ee_cert_path}; use sha2::Digest; +use std::cmp::Ordering; +use std::collections::HashMap; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum PublicationPointSource { Fresh, - VerifiedCache, + FetchCachePp, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct PublicationPointResult { pub source: PublicationPointSource, - pub pack: VerifiedPublicationPointPack, + pub pack: FetchCachePpPack, pub warnings: Vec, } @@ -38,6 +41,26 @@ pub enum ManifestFreshError { )] Signature(#[from] SignedObjectVerifyError), + #[error( + "manifest embedded EE certificate path validation failed: {0} (RFC 6488 §3; RFC 9286 §6.2; RFC 9286 §6.6)" + )] + EeCertPath(#[from] CertPathError), + + #[error( + "manifest embedded EE certificate CRLDistributionPoints missing (cannot validate EE certificate) (RFC 6487 §4.8.6; RFC 6488 §3; RFC 9286 §6.2; RFC 9286 §6.6)" + )] + EeCrlDpMissing, + + #[error( + "publication point contains no CRL files (cannot validate manifest EE certificate) (RFC 9286 §7; RFC 6487 §4.8.6; RFC 6488 §3; RFC 9286 §6.2; RFC 9286 §6.6)" + )] + NoCrlFiles, + + #[error( + "CRL referenced by manifest embedded EE certificate CRLDistributionPoints not found at publication point: {0} (RFC 6487 §4.8.6; RFC 9286 §4.2.1; RFC 9286 §6.2; RFC 9286 §6.6)" + )] + EeCrlNotFound(String), + #[error( "manifest is not valid at validation_time: this_update={this_update_rfc3339_utc} next_update={next_update_rfc3339_utc} validation_time={validation_time_rfc3339_utc} (RFC 9286 §6.3; RFC 9286 §6.6)" )] @@ -47,6 +70,27 @@ pub enum ManifestFreshError { validation_time_rfc3339_utc: String, }, + #[error( + "manifest must reside at the same publication point as id-ad-caRepository: manifest={manifest_rsync_uri} publication_point={publication_point_rsync_uri} (RFC 9286 §6.1; RFC 9286 §6.6)" + )] + ManifestOutsidePublicationPoint { + manifest_rsync_uri: String, + publication_point_rsync_uri: String, + }, + + #[error( + "manifestNumber not higher than previously validated manifest: old={old_hex} new={new_hex} (RFC 9286 §4.2.1; RFC 9286 §6.6)" + )] + ManifestNumberNotIncreasing { old_hex: String, new_hex: String }, + + #[error( + "thisUpdate not more recent than previously validated manifest: old={old_rfc3339_utc} new={new_rfc3339_utc} (RFC 9286 §4.2.1; RFC 9286 §6.6)" + )] + ThisUpdateNotIncreasing { + old_rfc3339_utc: String, + new_rfc3339_utc: String, + }, + #[error( "manifest referenced file missing in raw_objects: {rsync_uri} (RFC 9286 §6.4; RFC 9286 §6.6)" )] @@ -58,14 +102,24 @@ pub enum ManifestFreshError { #[derive(Debug, thiserror::Error)] pub enum ManifestCachedError { - #[error("verified cache entry missing: {0} (RFC 9286 §6.6)")] - MissingVerifiedCache(String), + #[error("fetch_cache_pp entry missing: {0} (RFC 9286 §6.6)")] + MissingFetchCachePp(String), - #[error("verified cache pack invalid: {0}")] + #[error("fetch_cache_pp pack invalid: {0}")] InvalidPack(#[from] StorageError), #[error("cached manifest revalidation failed: {0}")] CachedManifestFresh(#[from] ManifestFreshError), + + #[error( + "cached fetch_cache_pp missing file referenced by manifest: {rsync_uri} (RFC 9286 §6.4; RFC 9286 §6.6)" + )] + CachedMissingFile { rsync_uri: String }, + + #[error( + "cached fetch_cache_pp file hash mismatch: {rsync_uri} (RFC 9286 §6.5; RFC 9286 §6.6)" + )] + CachedHashMismatch { rsync_uri: String }, } #[derive(Debug, thiserror::Error)] @@ -74,7 +128,7 @@ pub enum ManifestProcessError { StopAllOutput(#[from] ManifestFreshError), #[error( - "manifest processing failed and no usable verified cache is available: fresh={fresh}; cached={cached}" + "manifest processing failed and no usable fetch_cache_pp is available: fresh={fresh}; cached={cached}" )] NoUsableCache { fresh: ManifestFreshError, @@ -90,20 +144,24 @@ pub fn process_manifest_publication_point( policy: &Policy, manifest_rsync_uri: &str, publication_point_rsync_uri: &str, + issuer_ca_der: &[u8], + issuer_ca_rsync_uri: Option<&str>, validation_time: time::OffsetDateTime, ) -> Result { let fresh = try_build_fresh_pack( store, manifest_rsync_uri, publication_point_rsync_uri, + issuer_ca_der, + issuer_ca_rsync_uri, validation_time, ); match fresh { Ok(pack) => { - let key = VerifiedKey::from_manifest_rsync_uri(manifest_rsync_uri); + let key = FetchCachePpKey::from_manifest_rsync_uri(manifest_rsync_uri); let bytes = pack.encode()?; - store.put_verified(&key, &bytes)?; + store.put_fetch_cache_pp(&key, &bytes)?; Ok(PublicationPointResult { source: PublicationPointSource::Fresh, pack, @@ -114,7 +172,7 @@ pub fn process_manifest_publication_point( CaFailedFetchPolicy::StopAllOutput => { Err(ManifestProcessError::StopAllOutput(fresh_err)) } - CaFailedFetchPolicy::UseVerifiedCache => { + CaFailedFetchPolicy::UseFetchCachePp => { let mut warnings = vec![ Warning::new(format!("manifest failed fetch: {fresh_err}")) .with_rfc_refs(&[RfcRef("RFC 9286 §6.6")]) @@ -125,16 +183,18 @@ pub fn process_manifest_publication_point( store, manifest_rsync_uri, publication_point_rsync_uri, + issuer_ca_der, + issuer_ca_rsync_uri, validation_time, ) { Ok(pack) => { warnings.push( - Warning::new("using verified cache for publication point") + Warning::new("using fetch_cache_pp for publication point") .with_rfc_refs(&[RfcRef("RFC 9286 §6.6")]) .with_context(manifest_rsync_uri), ); Ok(PublicationPointResult { - source: PublicationPointSource::VerifiedCache, + source: PublicationPointSource::FetchCachePp, pack, warnings, }) @@ -153,13 +213,15 @@ fn load_and_revalidate_cached_pack( store: &RocksStore, manifest_rsync_uri: &str, publication_point_rsync_uri: &str, + issuer_ca_der: &[u8], + issuer_ca_rsync_uri: Option<&str>, validation_time: time::OffsetDateTime, -) -> Result { - let key = VerifiedKey::from_manifest_rsync_uri(manifest_rsync_uri); +) -> Result { + let key = FetchCachePpKey::from_manifest_rsync_uri(manifest_rsync_uri); let bytes = store - .get_verified(&key)? - .ok_or_else(|| ManifestCachedError::MissingVerifiedCache(key.as_str().to_string()))?; - let pack = VerifiedPublicationPointPack::decode(&bytes)?; + .get_fetch_cache_pp(&key)? + .ok_or_else(|| ManifestCachedError::MissingFetchCachePp(key.as_str().to_string()))?; + let pack = FetchCachePpPack::decode(&bytes)?; if pack.manifest_rsync_uri != manifest_rsync_uri { return Err(ManifestCachedError::InvalidPack(StorageError::RocksDb( @@ -172,15 +234,61 @@ fn load_and_revalidate_cached_pack( ))); } - revalidate_pack_with_current_time(&pack, validation_time).map_err(ManifestCachedError::from)?; + revalidate_cached_pack_with_current_time( + &pack, + issuer_ca_der, + issuer_ca_rsync_uri, + validation_time, + )?; Ok(pack) } -fn revalidate_pack_with_current_time( - pack: &VerifiedPublicationPointPack, +fn revalidate_cached_pack_with_current_time( + pack: &FetchCachePpPack, + issuer_ca_der: &[u8], + issuer_ca_rsync_uri: Option<&str>, validation_time: time::OffsetDateTime, -) -> Result<(), ManifestFreshError> { - let manifest = ManifestObject::decode_der(&pack.manifest_bytes)?; +) -> Result<(), ManifestCachedError> { + // First, re-validate the cached manifest itself with the current time. + let manifest = decode_and_validate_manifest_with_current_time(&pack.manifest_bytes, validation_time) + .map_err(ManifestCachedError::from)?; + + // Then, re-bind the manifest fileList to the cached pack contents, as per RFC 9286 §6.4-§6.5. + let by_uri: HashMap<&str, &crate::storage::PackFile> = pack + .files + .iter() + .map(|f| (f.rsync_uri.as_str(), f)) + .collect(); + + for entry in &manifest.manifest.files { + let rsync_uri = + join_rsync_dir_and_file(&pack.publication_point_rsync_uri, &entry.file_name); + let Some(file) = by_uri.get(rsync_uri.as_str()) else { + return Err(ManifestCachedError::CachedMissingFile { rsync_uri }); + }; + if file.sha256.as_slice() != entry.hash_bytes.as_slice() { + return Err(ManifestCachedError::CachedHashMismatch { rsync_uri }); + } + } + + // Finally, validate the manifest's embedded EE certificate path against the issuer CA + CRL. + // This enforces cert validity + CRL validity at `validation_time` for cached packs. + validate_manifest_embedded_ee_cert_path( + &manifest, + &pack.files, + issuer_ca_der, + issuer_ca_rsync_uri, + validation_time, + ) + .map_err(ManifestCachedError::from)?; + Ok(()) +} + +fn decode_and_validate_manifest_with_current_time( + manifest_bytes: &[u8], + validation_time: time::OffsetDateTime, +) -> Result { + let manifest = ManifestObject::decode_der(manifest_bytes)?; manifest.validate_embedded_ee_cert()?; manifest.signed_object.verify()?; @@ -207,15 +315,24 @@ fn revalidate_pack_with_current_time( }); } - Ok(()) + Ok(manifest) } fn try_build_fresh_pack( store: &RocksStore, manifest_rsync_uri: &str, publication_point_rsync_uri: &str, + issuer_ca_der: &[u8], + issuer_ca_rsync_uri: Option<&str>, validation_time: time::OffsetDateTime, -) -> Result { +) -> Result { + if !rsync_uri_is_under_publication_point(manifest_rsync_uri, publication_point_rsync_uri) { + return Err(ManifestFreshError::ManifestOutsidePublicationPoint { + manifest_rsync_uri: manifest_rsync_uri.to_string(), + publication_point_rsync_uri: publication_point_rsync_uri.to_string(), + }); + } + let manifest_bytes = store .get_raw(manifest_rsync_uri) .map_err(|e| ManifestFreshError::MissingManifest { @@ -225,31 +342,50 @@ fn try_build_fresh_pack( manifest_rsync_uri: manifest_rsync_uri.to_string(), })?; - let manifest = ManifestObject::decode_der(&manifest_bytes)?; - manifest.validate_embedded_ee_cert()?; - manifest.signed_object.verify()?; + let manifest = decode_and_validate_manifest_with_current_time(&manifest_bytes, validation_time)?; - let this_update = manifest - .manifest - .this_update - .to_offset(time::UtcOffset::UTC); - let next_update = manifest - .manifest - .next_update - .to_offset(time::UtcOffset::UTC); + let this_update = manifest.manifest.this_update.to_offset(time::UtcOffset::UTC); + let next_update = manifest.manifest.next_update.to_offset(time::UtcOffset::UTC); let now = validation_time.to_offset(time::UtcOffset::UTC); - if now < this_update || now > next_update { - return Err(ManifestFreshError::StaleOrEarly { - this_update_rfc3339_utc: this_update - .format(&time::format_description::well_known::Rfc3339) - .expect("format thisUpdate"), - next_update_rfc3339_utc: next_update - .format(&time::format_description::well_known::Rfc3339) - .expect("format nextUpdate"), - validation_time_rfc3339_utc: now - .format(&time::format_description::well_known::Rfc3339) - .expect("format validation_time"), - }); + + // RFC 9286 §4.2.1: replay/rollback detection for manifestNumber and thisUpdate. + // + // If a purported "new" manifest contains a manifestNumber equal to or lower than previously + // validated manifests, or a thisUpdate less recent than previously validated manifests, + // this is treated as a failed fetch and processing continues via the cached objects path (§6.6). + let key = FetchCachePpKey::from_manifest_rsync_uri(manifest_rsync_uri); + if let Some(old_bytes) = store.get_fetch_cache_pp(&key).ok().flatten() { + if let Ok(old_pack) = FetchCachePpPack::decode(&old_bytes) { + if old_pack.manifest_rsync_uri == manifest_rsync_uri + && old_pack.publication_point_rsync_uri == publication_point_rsync_uri + { + let new_num = manifest.manifest.manifest_number.bytes_be.as_slice(); + let old_num = old_pack.manifest_number_be.as_slice(); + if cmp_minimal_be_unsigned(new_num, old_num) != Ordering::Greater { + return Err(ManifestFreshError::ManifestNumberNotIncreasing { + old_hex: hex::encode_upper(old_num), + new_hex: hex::encode_upper(new_num), + }); + } + + let old_this_update = old_pack + .this_update + .parse() + .expect("pack internal validation ensures this_update parses"); + if this_update <= old_this_update { + use time::format_description::well_known::Rfc3339; + return Err(ManifestFreshError::ThisUpdateNotIncreasing { + old_rfc3339_utc: old_this_update + .to_offset(time::UtcOffset::UTC) + .format(&Rfc3339) + .expect("format old thisUpdate"), + new_rfc3339_utc: this_update + .format(&Rfc3339) + .expect("format new thisUpdate"), + }); + } + } + } } let mut files = Vec::with_capacity(manifest.manifest.files.len()); @@ -274,10 +410,21 @@ fn try_build_fresh_pack( )); } - Ok(VerifiedPublicationPointPack { - format_version: VerifiedPublicationPointPack::FORMAT_VERSION_V1, + // RFC 6488 §3: manifest (signed object) validity includes a valid EE cert path. + // We validate this after §6.4/§6.5 so the issuer CRL can be selected from the publication point. + validate_manifest_embedded_ee_cert_path( + &manifest, + &files, + issuer_ca_der, + issuer_ca_rsync_uri, + validation_time, + )?; + + Ok(FetchCachePpPack { + format_version: FetchCachePpPack::FORMAT_VERSION_V1, manifest_rsync_uri: manifest_rsync_uri.to_string(), publication_point_rsync_uri: publication_point_rsync_uri.to_string(), + manifest_number_be: manifest.manifest.manifest_number.bytes_be.clone(), this_update: crate::storage::PackTime::from_utc_offset_datetime(this_update), next_update: crate::storage::PackTime::from_utc_offset_datetime(next_update), verified_at: crate::storage::PackTime::from_utc_offset_datetime(now), @@ -286,6 +433,14 @@ fn try_build_fresh_pack( }) } +fn cmp_minimal_be_unsigned(a: &[u8], b: &[u8]) -> Ordering { + // Compare two minimal big-endian byte strings as unsigned integers. + // (Leading zeros are not expected; callers store minimal big-endian.) + a.len() + .cmp(&b.len()) + .then_with(|| a.cmp(b)) +} + fn join_rsync_dir_and_file(base: &str, file_name: &str) -> String { if base.ends_with('/') { format!("{base}{file_name}") @@ -293,3 +448,64 @@ fn join_rsync_dir_and_file(base: &str, file_name: &str) -> String { format!("{base}/{file_name}") } } + +fn rsync_uri_is_under_publication_point(uri: &str, publication_point_rsync_uri: &str) -> bool { + let pp = if publication_point_rsync_uri.ends_with('/') { + publication_point_rsync_uri.to_string() + } else { + format!("{publication_point_rsync_uri}/") + }; + uri.starts_with(&pp) +} + +fn validate_manifest_embedded_ee_cert_path( + manifest: &ManifestObject, + files: &[crate::storage::PackFile], + issuer_ca_der: &[u8], + issuer_ca_rsync_uri: Option<&str>, + validation_time: time::OffsetDateTime, +) -> Result<(), ManifestFreshError> { + let ee = &manifest.signed_object.signed_data.certificates[0]; + let ee_der = ee.raw_der.as_slice(); + + let crl_files = files + .iter() + .filter(|f| f.rsync_uri.ends_with(".crl")) + .collect::>(); + if crl_files.is_empty() { + return Err(ManifestFreshError::NoCrlFiles); + } + + let Some(crldp_uris) = ee + .resource_cert + .tbs + .extensions + .crl_distribution_points_uris + .as_ref() + else { + return Err(ManifestFreshError::EeCrlDpMissing); + }; + + for u in crldp_uris { + let s = u.as_str(); + if let Some(f) = crl_files.iter().find(|f| f.rsync_uri == s) { + let _validated = validate_ee_cert_path( + ee_der, + issuer_ca_der, + f.bytes.as_slice(), + issuer_ca_rsync_uri, + Some(f.rsync_uri.as_str()), + validation_time, + )?; + return Ok(()); + } + } + + Err(ManifestFreshError::EeCrlNotFound( + crldp_uris + .iter() + .map(|u| u.as_str()) + .collect::>() + .join(", "), + )) +} diff --git a/src/validation/objects.rs b/src/validation/objects.rs index 4c83d21..1dfe2c8 100644 --- a/src/validation/objects.rs +++ b/src/validation/objects.rs @@ -9,7 +9,7 @@ use crate::data_model::roa::{IpPrefix, RoaAfi, RoaDecodeError, RoaObject, RoaVal use crate::data_model::signed_object::SignedObjectVerifyError; use crate::policy::{Policy, SignedObjectFailurePolicy}; use crate::report::{RfcRef, Warning}; -use crate::storage::{PackFile, VerifiedPublicationPointPack}; +use crate::storage::{FetchCachePpPack, PackFile}; use crate::validation::cert_path::{CertPathError, validate_ee_cert_path}; const RFC_NONE: &[RfcRef] = &[]; @@ -58,10 +58,10 @@ pub struct ObjectsStats { pub publication_point_dropped: bool, } -/// Process objects from a verified publication point pack using a known issuer CA certificate +/// Process objects from a fetch_cache_pp publication point pack using a known issuer CA certificate /// and its effective resources (resolved via the resource-path, RFC 6487 §7.2). -pub fn process_verified_publication_point_pack_for_issuer( - pack: &VerifiedPublicationPointPack, +pub fn process_fetch_cache_pp_pack_for_issuer( + pack: &FetchCachePpPack, policy: &Policy, issuer_ca_der: &[u8], issuer_ca_rsync_uri: Option<&str>, @@ -85,7 +85,7 @@ pub fn process_verified_publication_point_pack_for_issuer( // Enforce that `manifest_bytes` is actually a manifest object. let _manifest = - ManifestObject::decode_der(&pack.manifest_bytes).expect("verified pack manifest decodes"); + ManifestObject::decode_der(&pack.manifest_bytes).expect("fetch_cache_pp manifest decodes"); let crl_files = pack .files @@ -99,7 +99,7 @@ pub fn process_verified_publication_point_pack_for_issuer( if crl_files.is_empty() && (stats.roa_total > 0 || stats.aspa_total > 0) { stats.publication_point_dropped = true; warnings.push( - Warning::new("dropping publication point: no CRL files in verified pack") + Warning::new("dropping publication point: no CRL files in fetch_cache_pp") .with_rfc_refs(&[RfcRef("RFC 6487 §4.8.6"), RfcRef("RFC 9286 §7")]) .with_context(&pack.manifest_rsync_uri), ); @@ -110,7 +110,9 @@ pub fn process_verified_publication_point_pack_for_issuer( sha256_hex: sha256_hex_from_32(&f.sha256), kind: AuditObjectKind::Roa, result: AuditObjectResult::Skipped, - detail: Some("skipped due to missing CRL files in verified pack".to_string()), + detail: Some( + "skipped due to missing CRL files in fetch_cache_pp".to_string(), + ), }); } else if f.rsync_uri.ends_with(".asa") { audit.push(ObjectAuditEntry { @@ -118,7 +120,9 @@ pub fn process_verified_publication_point_pack_for_issuer( sha256_hex: sha256_hex_from_32(&f.sha256), kind: AuditObjectKind::Aspa, result: AuditObjectResult::Skipped, - detail: Some("skipped due to missing CRL files in verified pack".to_string()), + detail: Some( + "skipped due to missing CRL files in fetch_cache_pp".to_string(), + ), }); } } @@ -357,12 +361,12 @@ enum ObjectValidateError { MissingCrlDpUris, #[error( - "no CRL available in verified pack (cannot validate certificates) (RFC 9286 §7; RFC 6487 §4.8.6)" + "no CRL available in fetch_cache_pp (cannot validate certificates) (RFC 9286 §7; RFC 6487 §4.8.6)" )] MissingCrlInPack, #[error( - "CRL referenced by CRLDistributionPoints not found in verified pack: {0} (RFC 6487 §4.8.6; RFC 9286 §4.2.1)" + "CRL referenced by CRLDistributionPoints not found in fetch_cache_pp: {0} (RFC 6487 §4.8.6; RFC 9286 §4.2.1)" )] CrlNotFound(String), diff --git a/src/validation/run.rs b/src/validation/run.rs index 4a4150d..c038b8c 100644 --- a/src/validation/run.rs +++ b/src/validation/run.rs @@ -1,12 +1,12 @@ use crate::data_model::rc::{AsResourceSet, IpResourceSet}; use crate::fetch::rsync::RsyncFetcher; use crate::policy::Policy; -use crate::storage::{RocksStore, VerifiedKey}; +use crate::storage::{FetchCachePpKey, RocksStore}; use crate::sync::repo::{RepoSyncResult, sync_publication_point}; use crate::sync::rrdp::Fetcher as HttpFetcher; use crate::validation::manifest::{PublicationPointResult, process_manifest_publication_point}; use crate::validation::objects::{ - ObjectsOutput, process_verified_publication_point_pack_for_issuer, + ObjectsOutput, process_fetch_cache_pp_pack_for_issuer, }; #[derive(Clone, Debug, PartialEq, Eq)] @@ -29,8 +29,8 @@ pub enum RunError { /// /// This orchestrates: /// 1) repo sync (RRDP or rsync fallback) into `raw_objects` -/// 2) manifest RP processing into a verified pack (`verified:`) -/// 3) signed object processing (ROA/ASPA) from the verified pack +/// 2) manifest RP processing into a fetch_cache_pp pack (`fetch_cache_pp:`) +/// 3) signed object processing (ROA/ASPA) from the fetch_cache_pp pack pub fn run_publication_point_once( store: &RocksStore, policy: &Policy, @@ -60,10 +60,12 @@ pub fn run_publication_point_once( policy, manifest_rsync_uri, publication_point_rsync_uri, + issuer_ca_der, + issuer_ca_rsync_uri, validation_time, )?; - let objects = process_verified_publication_point_pack_for_issuer( + let objects = process_fetch_cache_pp_pack_for_issuer( &publication_point.pack, policy, issuer_ca_der, @@ -80,10 +82,10 @@ pub fn run_publication_point_once( }) } -pub fn verified_pack_exists(store: &RocksStore, manifest_rsync_uri: &str) -> Result { - let key = VerifiedKey::from_manifest_rsync_uri(manifest_rsync_uri); +pub fn fetch_cache_pp_exists(store: &RocksStore, manifest_rsync_uri: &str) -> Result { + let key = FetchCachePpKey::from_manifest_rsync_uri(manifest_rsync_uri); store - .get_verified(&key) + .get_fetch_cache_pp(&key) .map(|v| v.is_some()) .map_err(|e| e.to_string()) } diff --git a/src/validation/tree.rs b/src/validation/tree.rs index 70af84d..a106442 100644 --- a/src/validation/tree.rs +++ b/src/validation/tree.rs @@ -2,7 +2,7 @@ use crate::audit::DiscoveredFrom; use crate::audit::PublicationPointAudit; use crate::data_model::rc::{AsResourceSet, IpResourceSet}; use crate::report::{RfcRef, Warning}; -use crate::storage::VerifiedPublicationPointPack; +use crate::storage::FetchCachePpPack; use crate::validation::manifest::PublicationPointSource; use crate::validation::objects::{AspaAttestation, ObjectsOutput, Vrp}; @@ -54,14 +54,14 @@ impl CaInstanceHandle { #[derive(Clone, Debug, PartialEq, Eq)] pub struct PublicationPointRunResult { pub source: PublicationPointSource, - pub pack: VerifiedPublicationPointPack, + pub pack: FetchCachePpPack, pub warnings: Vec, pub objects: ObjectsOutput, pub audit: PublicationPointAudit, /// Candidate child CA instances discovered from this publication point. /// /// RFC 9286 §6.6 restriction is enforced by the tree engine: if this - /// publication point used verified cache due to failed fetch, children MUST NOT + /// publication point used fetch_cache_pp due to failed fetch, children MUST NOT /// be enqueued/processed in this run. pub discovered_children: Vec, } diff --git a/src/validation/tree_runner.rs b/src/validation/tree_runner.rs index 7d06cd6..386fd7e 100644 --- a/src/validation/tree_runner.rs +++ b/src/validation/tree_runner.rs @@ -11,7 +11,7 @@ use crate::sync::rrdp::Fetcher; use crate::validation::ca_instance::ca_instance_uris_from_ca_certificate; use crate::validation::ca_path::{CaPathError, validate_subordinate_ca_cert}; use crate::validation::manifest::{PublicationPointSource, process_manifest_publication_point}; -use crate::validation::objects::process_verified_publication_point_pack_for_issuer; +use crate::validation::objects::process_fetch_cache_pp_pack_for_issuer; use crate::validation::tree::{ CaInstanceHandle, DiscoveredChildCaInstance, PublicationPointRunResult, PublicationPointRunner, }; @@ -53,6 +53,8 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> { self.policy, &ca.manifest_rsync_uri, &ca.publication_point_rsync_uri, + &ca.ca_certificate_der, + ca.ca_certificate_rsync_uri.as_deref(), self.validation_time, ) { Ok(v) => v, @@ -61,7 +63,7 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> { warnings.extend(pp.warnings.clone()); - let objects = process_verified_publication_point_pack_for_issuer( + let objects = process_fetch_cache_pp_pack_for_issuer( &pp.pack, self.policy, &ca.ca_certificate_der, @@ -107,7 +109,7 @@ struct ChildDiscoveryOutput { fn discover_children_from_fresh_pack_with_audit( issuer: &CaInstanceHandle, - pack: &crate::storage::VerifiedPublicationPointPack, + pack: &crate::storage::FetchCachePpPack, validation_time: time::OffsetDateTime, ) -> Result { let issuer_ca_der = issuer.ca_certificate_der.as_slice(); @@ -219,7 +221,7 @@ fn discover_children_from_fresh_pack_with_audit( fn select_issuer_crl_from_pack<'a>( child_cert_der: &[u8], - pack: &'a crate::storage::VerifiedPublicationPointPack, + pack: &'a crate::storage::FetchCachePpPack, ) -> Result<(&'a str, &'a [u8]), String> { let child = crate::data_model::rc::ResourceCertificate::decode_der(child_cert_der) .map_err(|e| format!("child certificate decode failed: {e}"))?; @@ -237,7 +239,7 @@ fn select_issuer_crl_from_pack<'a>( } Err(format!( - "CRL referenced by child certificate CRLDistributionPoints not found in verified pack: {} (RFC 6487 §4.8.6; RFC 9286 §4.2.1)", + "CRL referenced by child certificate CRLDistributionPoints not found in fetch_cache_pp: {} (RFC 6487 §4.8.6; RFC 9286 §4.2.1)", crldp_uris .iter() .map(|u| u.as_str()) @@ -359,7 +361,7 @@ fn build_publication_point_audit( rrdp_notification_uri: ca.rrdp_notification_uri.clone(), source: match pp.source { PublicationPointSource::Fresh => "fresh".to_string(), - PublicationPointSource::VerifiedCache => "verified_cache".to_string(), + PublicationPointSource::FetchCachePp => "fetch_cache_pp".to_string(), }, this_update_rfc3339_utc: pp.pack.this_update.rfc3339_utc.clone(), next_update_rfc3339_utc: pp.pack.next_update.rfc3339_utc.clone(), @@ -374,7 +376,7 @@ mod tests { use super::*; use crate::data_model::rc::ResourceCertificate; use crate::fetch::rsync::LocalDirRsyncFetcher; - use crate::storage::{PackFile, PackTime, VerifiedPublicationPointPack}; + use crate::storage::{FetchCachePpPack, PackFile, PackTime}; use crate::sync::rrdp::Fetcher; use crate::validation::tree::PublicationPointRunner; @@ -573,12 +575,13 @@ authorityKeyIdentifier = keyid:always } } - fn dummy_pack_with_files(files: Vec) -> VerifiedPublicationPointPack { + fn dummy_pack_with_files(files: Vec) -> FetchCachePpPack { let now = time::OffsetDateTime::now_utc(); - VerifiedPublicationPointPack { - format_version: VerifiedPublicationPointPack::FORMAT_VERSION_V1, + FetchCachePpPack { + format_version: FetchCachePpPack::FORMAT_VERSION_V1, manifest_rsync_uri: "rsync://example.test/repo/issuer/issuer.mft".to_string(), publication_point_rsync_uri: "rsync://example.test/repo/issuer/".to_string(), + manifest_number_be: vec![1], this_update: PackTime::from_utc_offset_datetime(now), next_update: PackTime::from_utc_offset_datetime(now + time::Duration::hours(1)), verified_at: PackTime::from_utc_offset_datetime(now), diff --git a/tests/benchmark/selected/large-01.mft b/tests/benchmark/selected/large-01.mft new file mode 100644 index 0000000..24a4802 Binary files /dev/null and b/tests/benchmark/selected/large-01.mft differ diff --git a/tests/benchmark/selected/large-02.mft b/tests/benchmark/selected/large-02.mft new file mode 100644 index 0000000..f8989bb Binary files /dev/null and b/tests/benchmark/selected/large-02.mft differ diff --git a/tests/benchmark/selected/medium-01.mft b/tests/benchmark/selected/medium-01.mft new file mode 100644 index 0000000..822ba1d Binary files /dev/null and b/tests/benchmark/selected/medium-01.mft differ diff --git a/tests/benchmark/selected/medium-02.mft b/tests/benchmark/selected/medium-02.mft new file mode 100644 index 0000000..20b2ad1 Binary files /dev/null and b/tests/benchmark/selected/medium-02.mft differ diff --git a/tests/benchmark/selected/small-01.mft b/tests/benchmark/selected/small-01.mft new file mode 100644 index 0000000..45a23c6 Binary files /dev/null and b/tests/benchmark/selected/small-01.mft differ diff --git a/tests/benchmark/selected/small-02.mft b/tests/benchmark/selected/small-02.mft new file mode 100644 index 0000000..ea59290 Binary files /dev/null and b/tests/benchmark/selected/small-02.mft differ diff --git a/tests/benchmark/selected/sources.tsv b/tests/benchmark/selected/sources.tsv new file mode 100644 index 0000000..d16bbdd --- /dev/null +++ b/tests/benchmark/selected/sources.tsv @@ -0,0 +1,8 @@ +small-01 3160 1875 stored_point_container bench/full/cache/stored/rrdp/rrdp.twnic.tw/9e5ef8f1eb47c1bd46d2c578ebefa6a774fe140dbfbfeff1a018f1ebfc1de14b/rsync/rpkica.twnic.tw/rpki/TWNICCA/ATT/VRjTaiHoQb_taL2Agyl1Nd1wiwU.mft +small-02 3160 1875 stored_point_container bench/full/cache/stored/rrdp/rrdp.twnic.tw/9e5ef8f1eb47c1bd46d2c578ebefa6a774fe140dbfbfeff1a018f1ebfc1de14b/rsync/rpkica.twnic.tw/rpki/TWNICCA/BOT/Kn9vIkC0LSu8Df78l2yrIJHEfsc.mft +medium-01 16384 2681 stored_point_container bench/full/cache/stored/rrdp/rrdp.arin.net/e2c8cb4b372d841061fc231dcdc28a679c244f4f630d41cfe01c8c3aaa3a36f7/rsync/rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/69fd0156-bb1f-48b6-bf32-c9492286f195/a32aa006-ef22-4905-8c82-2651c19859b9/a32aa006-ef22-4905-8c82-2651c19859b9.mft +medium-02 16384 2681 stored_point_container bench/full/cache/stored/rrdp/rrdp.arin.net/e2c8cb4b372d841061fc231dcdc28a679c244f4f630d41cfe01c8c3aaa3a36f7/rsync/rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/6ead073c-b9f0-4523-b39e-a3c7dab97c25/dfc6770a-8775-4ba8-9d91-68facba79b33/dfc6770a-8775-4ba8-9d91-68facba79b33.mft +large-01 262869 2250 stored_point_container bench/full/cache/stored/rrdp/rpki-rrdp.us-east-2.amazonaws.com/c7cf30a5673053f1484f06ac72410809310e150ad73a8a7471cc95aad83b1e07/rsync/rpki-rsync.us-east-2.amazonaws.com/volume/16f1ffee-7461-4674-bb05-fddefa9a02c6/JmLOFOkF4Y68t1IvkrNoS8SGW00.mft +large-02 264331 10476 stored_point_container bench/full/cache/stored/rrdp/rrdp.ripe.net/2315d99a99627f34bc597569abc7c177ad45108a37f173f3d06dbabd64962f3c/rsync/rpki.ripe.net/repository/DEFAULT/fa/bc92e6-c8ee-48f0-ae7f-36ccb5a06195/1/tDgLm4wHBFftVLxF0S3d0kTgbVI.mft +xlarge-01 4522145 144968 stored_point_container bench/full/cache/stored/rrdp/rrdp.arin.net/e2c8cb4b372d841061fc231dcdc28a679c244f4f630d41cfe01c8c3aaa3a36f7/rsync/rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/fde169ed-d0d2-4165-8308-df2597e343f8/c2070b9e-789d-4f95-bca5-30e065e9a31d/c2070b9e-789d-4f95-bca5-30e065e9a31d.mft +xlarge-02 4615094 150261 stored_point_container bench/full/cache/stored/rrdp/rrdp.arin.net/e2c8cb4b372d841061fc231dcdc28a679c244f4f630d41cfe01c8c3aaa3a36f7/rsync/rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/f60c9f32-a87c-4339-a2f3-6299a3b02e29/b2f0a061-78dd-4d61-988a-266b192d9caa/b2f0a061-78dd-4d61-988a-266b192d9caa.mft diff --git a/tests/benchmark/selected/xlarge-01.mft b/tests/benchmark/selected/xlarge-01.mft new file mode 100644 index 0000000..2db917f Binary files /dev/null and b/tests/benchmark/selected/xlarge-01.mft differ diff --git a/tests/benchmark/selected/xlarge-02.mft b/tests/benchmark/selected/xlarge-02.mft new file mode 100644 index 0000000..ecebffd Binary files /dev/null and b/tests/benchmark/selected/xlarge-02.mft differ diff --git a/tests/test_apnic_stats_live_stage2.rs b/tests/test_apnic_stats_live_stage2.rs index 0f19bee..93de2b0 100644 --- a/tests/test_apnic_stats_live_stage2.rs +++ b/tests/test_apnic_stats_live_stage2.rs @@ -63,7 +63,7 @@ impl LiveStats { rpki::validation::manifest::PublicationPointSource::Fresh => { self.publication_points_fresh += 1 } - rpki::validation::manifest::PublicationPointSource::VerifiedCache => { + rpki::validation::manifest::PublicationPointSource::FetchCachePp => { self.publication_points_cached += 1 } } @@ -205,9 +205,9 @@ fn apnic_tree_full_stats_serial() { } } - let verified_total = store - .verified_iter_all() - .expect("verified_iter_all") + let fetch_cache_pp_total = store + .fetch_cache_pp_iter_all() + .expect("fetch_cache_pp_iter_all") .count(); println!("APNIC Stage2 full-tree serial stats"); @@ -238,6 +238,7 @@ fn apnic_tree_full_stats_serial() { stats.pack_file_uris_unique.len() ); println!("pack_uris_by_ext_total={:?}", stats.pack_uris_by_ext_total); + println!("fetch_cache_pp_total={fetch_cache_pp_total}"); println!(); println!( "crl_total={} crl_decode_ok={}", @@ -264,7 +265,7 @@ fn apnic_tree_full_stats_serial() { "rocksdb_raw_objects_total={} raw_by_ext={:?}", raw_total, raw_by_ext ); - println!("rocksdb_verified_packs_total={}", verified_total); + println!("rocksdb_fetch_cache_pp_total={fetch_cache_pp_total}"); // Loose sanity assertions (avoid flakiness due to repository churn). // diff --git a/tests/test_deterministic_semantics_m4.rs b/tests/test_deterministic_semantics_m4.rs index e9c342a..9863277 100644 --- a/tests/test_deterministic_semantics_m4.rs +++ b/tests/test_deterministic_semantics_m4.rs @@ -1,8 +1,8 @@ use rpki::audit::PublicationPointAudit; use rpki::policy::{Policy, SignedObjectFailurePolicy}; -use rpki::storage::{PackFile, PackTime, VerifiedPublicationPointPack}; +use rpki::storage::{FetchCachePpPack, PackFile, PackTime}; use rpki::validation::manifest::PublicationPointSource; -use rpki::validation::objects::process_verified_publication_point_pack_for_issuer; +use rpki::validation::objects::process_fetch_cache_pp_pack_for_issuer; use rpki::validation::tree::{ CaInstanceHandle, PublicationPointRunResult, PublicationPointRunner, TreeRunConfig, run_tree_serial_audit, @@ -13,14 +13,15 @@ fn fixture_bytes(path: &str) -> Vec { .unwrap_or_else(|e| panic!("read fixture {path}: {e}")) } -fn dummy_pack(files: Vec) -> VerifiedPublicationPointPack { +fn dummy_pack(files: Vec) -> FetchCachePpPack { let now = time::OffsetDateTime::now_utc(); let manifest_rsync_uri = "rsync://rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft"; - VerifiedPublicationPointPack { - format_version: VerifiedPublicationPointPack::FORMAT_VERSION_V1, + FetchCachePpPack { + format_version: FetchCachePpPack::FORMAT_VERSION_V1, manifest_rsync_uri: manifest_rsync_uri.to_string(), publication_point_rsync_uri: "rsync://rpki.cernet.net/repo/cernet/0/".to_string(), + manifest_number_be: vec![1], this_update: PackTime::from_utc_offset_datetime(now), next_update: PackTime::from_utc_offset_datetime(now + time::Duration::hours(1)), verified_at: PackTime::from_utc_offset_datetime(now), @@ -33,7 +34,7 @@ fn dummy_pack(files: Vec) -> VerifiedPublicationPointPack { struct SinglePackRunner { policy: Policy, - pack: VerifiedPublicationPointPack, + pack: FetchCachePpPack, } impl PublicationPointRunner for SinglePackRunner { @@ -41,7 +42,7 @@ impl PublicationPointRunner for SinglePackRunner { &self, ca: &CaInstanceHandle, ) -> Result { - let objects = process_verified_publication_point_pack_for_issuer( + let objects = process_fetch_cache_pp_pack_for_issuer( &self.pack, &self.policy, &ca.ca_certificate_der, diff --git a/tests/test_fetch_cache_pp_revalidation_m3.rs b/tests/test_fetch_cache_pp_revalidation_m3.rs new file mode 100644 index 0000000..3d1a90a --- /dev/null +++ b/tests/test_fetch_cache_pp_revalidation_m3.rs @@ -0,0 +1,208 @@ +use std::path::Path; + +use rpki::data_model::manifest::ManifestObject; +use rpki::policy::{CaFailedFetchPolicy, Policy}; +use rpki::storage::{FetchCachePpKey, FetchCachePpPack, RocksStore}; +use rpki::validation::manifest::process_manifest_publication_point; + +fn issuer_ca_fixture() -> Vec { + std::fs::read( + "tests/fixtures/repository/rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer", + ) + .expect("read issuer ca fixture") +} + +fn issuer_ca_rsync_uri() -> &'static str { + "rsync://rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer" +} + +fn fixture_to_rsync_uri(path: &Path) -> String { + let rel = path + .strip_prefix("tests/fixtures/repository") + .expect("path under tests/fixtures/repository"); + let mut it = rel.components(); + let host = it + .next() + .expect("host component") + .as_os_str() + .to_string_lossy(); + let rest = it.as_path().to_string_lossy(); + format!("rsync://{host}/{rest}") +} + +fn fixture_dir_to_rsync_uri(dir: &Path) -> String { + let mut s = fixture_to_rsync_uri(dir); + if !s.ends_with('/') { + s.push('/'); + } + s +} + +fn load_cernet_manifest_fixture() -> (std::path::PathBuf, Vec, ManifestObject) { + let manifest_path = Path::new( + "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft", + ) + .to_path_buf(); + let bytes = std::fs::read(&manifest_path).expect("read manifest fixture"); + let obj = ManifestObject::decode_der(&bytes).expect("decode manifest fixture"); + (manifest_path, bytes, obj) +} + +fn store_raw_publication_point_files( + store: &RocksStore, + manifest_path: &Path, + manifest_rsync_uri: &str, + manifest_bytes: &[u8], + manifest: &ManifestObject, + publication_point_rsync_uri: &str, +) { + store + .put_raw(manifest_rsync_uri, manifest_bytes) + .expect("store manifest raw"); + for entry in &manifest.manifest.files { + let file_path = manifest_path.parent().unwrap().join(&entry.file_name); + let bytes = std::fs::read(&file_path) + .unwrap_or_else(|_| panic!("read fixture file referenced by manifest: {file_path:?}")); + let rsync_uri = format!("{publication_point_rsync_uri}{}", entry.file_name); + store.put_raw(&rsync_uri, &bytes).expect("store file raw"); + } +} + +#[test] +fn cached_pack_revalidation_rejects_missing_file_referenced_by_manifest() { + let (manifest_path, manifest_bytes, manifest) = load_cernet_manifest_fixture(); + let validation_time = manifest.manifest.this_update + time::Duration::seconds(1); + + let manifest_rsync_uri = fixture_to_rsync_uri(&manifest_path); + let publication_point_rsync_uri = fixture_dir_to_rsync_uri(manifest_path.parent().unwrap()); + + let temp = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(temp.path()).expect("open rocksdb"); + store_raw_publication_point_files( + &store, + &manifest_path, + &manifest_rsync_uri, + &manifest_bytes, + &manifest, + &publication_point_rsync_uri, + ); + + let mut policy = Policy::default(); + policy.ca_failed_fetch_policy = CaFailedFetchPolicy::UseFetchCachePp; + let issuer_ca_der = issuer_ca_fixture(); + + let _fresh = process_manifest_publication_point( + &store, + &policy, + &manifest_rsync_uri, + &publication_point_rsync_uri, + &issuer_ca_der, + Some(issuer_ca_rsync_uri()), + validation_time, + ) + .expect("fresh run stores fetch_cache_pp"); + + let key = FetchCachePpKey::from_manifest_rsync_uri(&manifest_rsync_uri); + let cached_bytes = store + .get_fetch_cache_pp(&key) + .expect("get fetch_cache_pp") + .expect("fetch_cache_pp exists"); + let mut pack = FetchCachePpPack::decode(&cached_bytes).expect("decode pack"); + + // Remove one file from the pack: pack stays internally consistent, but no longer satisfies + // RFC 9286 §6.4 when revalidated against the manifest fileList. + pack.files.pop().expect("non-empty pack"); + let bytes = pack.encode().expect("encode pack"); + store + .put_fetch_cache_pp(&key, &bytes) + .expect("overwrite fetch_cache_pp"); + + // Force cache path: remove raw manifest so fresh processing fails at §6.2. + store + .delete_raw(&manifest_rsync_uri) + .expect("delete raw manifest"); + + let err = process_manifest_publication_point( + &store, + &policy, + &manifest_rsync_uri, + &publication_point_rsync_uri, + &issuer_ca_der, + Some(issuer_ca_rsync_uri()), + validation_time, + ) + .expect_err("cache pack missing file must be rejected"); + let msg = err.to_string(); + assert!(msg.contains("cached fetch_cache_pp missing file"), "{msg}"); + assert!(msg.contains("RFC 9286 §6.4"), "{msg}"); +} + +#[test] +fn cached_pack_revalidation_rejects_hash_mismatch_against_manifest_filelist() { + let (manifest_path, manifest_bytes, manifest) = load_cernet_manifest_fixture(); + let validation_time = manifest.manifest.this_update + time::Duration::seconds(1); + + let manifest_rsync_uri = fixture_to_rsync_uri(&manifest_path); + let publication_point_rsync_uri = fixture_dir_to_rsync_uri(manifest_path.parent().unwrap()); + + let temp = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(temp.path()).expect("open rocksdb"); + store_raw_publication_point_files( + &store, + &manifest_path, + &manifest_rsync_uri, + &manifest_bytes, + &manifest, + &publication_point_rsync_uri, + ); + + let mut policy = Policy::default(); + policy.ca_failed_fetch_policy = CaFailedFetchPolicy::UseFetchCachePp; + let issuer_ca_der = issuer_ca_fixture(); + + let _fresh = process_manifest_publication_point( + &store, + &policy, + &manifest_rsync_uri, + &publication_point_rsync_uri, + &issuer_ca_der, + Some(issuer_ca_rsync_uri()), + validation_time, + ) + .expect("fresh run stores fetch_cache_pp"); + + let key = FetchCachePpKey::from_manifest_rsync_uri(&manifest_rsync_uri); + let cached_bytes = store + .get_fetch_cache_pp(&key) + .expect("get fetch_cache_pp") + .expect("fetch_cache_pp exists"); + let mut pack = FetchCachePpPack::decode(&cached_bytes).expect("decode pack"); + + // Mutate one file but keep pack internally consistent by recomputing its sha256 field. + let victim = pack.files.first_mut().expect("non-empty pack"); + victim.bytes[0] ^= 0xFF; + victim.sha256 = victim.compute_sha256(); + let bytes = pack.encode().expect("encode pack"); + store + .put_fetch_cache_pp(&key, &bytes) + .expect("overwrite fetch_cache_pp"); + + // Force cache path. + store + .delete_raw(&manifest_rsync_uri) + .expect("delete raw manifest"); + + let err = process_manifest_publication_point( + &store, + &policy, + &manifest_rsync_uri, + &publication_point_rsync_uri, + &issuer_ca_der, + Some(issuer_ca_rsync_uri()), + validation_time, + ) + .expect_err("cache pack hash mismatch must be rejected"); + let msg = err.to_string(); + assert!(msg.contains("cached fetch_cache_pp file hash mismatch"), "{msg}"); + assert!(msg.contains("RFC 9286 §6.5"), "{msg}"); +} diff --git a/tests/test_manifest_cache_errors_more.rs b/tests/test_manifest_cache_errors_more.rs index 1421e33..a22960a 100644 --- a/tests/test_manifest_cache_errors_more.rs +++ b/tests/test_manifest_cache_errors_more.rs @@ -2,9 +2,20 @@ use std::path::Path; use rpki::data_model::manifest::ManifestObject; use rpki::policy::{CaFailedFetchPolicy, Policy}; -use rpki::storage::{RocksStore, VerifiedKey, VerifiedPublicationPointPack}; +use rpki::storage::{FetchCachePpKey, FetchCachePpPack, RocksStore}; use rpki::validation::manifest::process_manifest_publication_point; +fn issuer_ca_fixture() -> Vec { + std::fs::read( + "tests/fixtures/repository/rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer", + ) + .expect("read issuer ca fixture") +} + +fn issuer_ca_rsync_uri() -> &'static str { + "rsync://rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer" +} + fn fixture_to_rsync_uri(path: &Path) -> String { let rel = path .strip_prefix("tests/fixtures/repository") @@ -33,18 +44,21 @@ fn cache_is_not_used_when_missing_and_fresh_manifest_is_missing() { let store = RocksStore::open(temp.path()).expect("open rocksdb"); let mut policy = Policy::default(); - policy.ca_failed_fetch_policy = CaFailedFetchPolicy::UseVerifiedCache; + policy.ca_failed_fetch_policy = CaFailedFetchPolicy::UseFetchCachePp; + let issuer_ca_der = issuer_ca_fixture(); let err = process_manifest_publication_point( &store, &policy, "rsync://example.net/repo/manifest.mft", "rsync://example.net/repo/", + &issuer_ca_der, + Some(issuer_ca_rsync_uri()), time::OffsetDateTime::from_unix_timestamp(0).unwrap(), ) - .expect_err("no raw and no verified cache should fail"); + .expect_err("no raw and no fetch_cache_pp should fail"); - assert!(err.to_string().contains("verified cache entry missing")); + assert!(err.to_string().contains("fetch_cache_pp entry missing")); } #[test] @@ -75,27 +89,30 @@ fn cache_pack_publication_point_mismatch_is_rejected() { } let policy = Policy::default(); + let issuer_ca_der = issuer_ca_fixture(); let _ = process_manifest_publication_point( &store, &policy, &manifest_rsync_uri, &publication_point_rsync_uri, + &issuer_ca_der, + Some(issuer_ca_rsync_uri()), validation_time, ) - .expect("first run stores verified pack"); + .expect("first run stores fetch_cache_pp pack"); // Corrupt the cached pack by changing the publication point. - let key = VerifiedKey::from_manifest_rsync_uri(&manifest_rsync_uri); + let key = FetchCachePpKey::from_manifest_rsync_uri(&manifest_rsync_uri); let bytes = store - .get_verified(&key) - .expect("get verified") - .expect("verified exists"); - let mut pack = VerifiedPublicationPointPack::decode(&bytes).expect("decode pack"); + .get_fetch_cache_pp(&key) + .expect("get fetch_cache_pp") + .expect("fetch_cache_pp exists"); + let mut pack = FetchCachePpPack::decode(&bytes).expect("decode pack"); pack.publication_point_rsync_uri = "rsync://evil.invalid/repo/".to_string(); let bytes = pack.encode().expect("re-encode pack"); store - .put_verified(&key, &bytes) - .expect("overwrite verified"); + .put_fetch_cache_pp(&key, &bytes) + .expect("overwrite fetch_cache_pp"); // Remove raw manifest to force cache path. store @@ -107,6 +124,8 @@ fn cache_pack_publication_point_mismatch_is_rejected() { &policy, &manifest_rsync_uri, &publication_point_rsync_uri, + &issuer_ca_der, + Some(issuer_ca_rsync_uri()), validation_time, ) .expect_err("cache pack mismatch should fail"); diff --git a/tests/test_manifest_processor_m4.rs b/tests/test_manifest_processor_m4.rs index 3872837..fb06d15 100644 --- a/tests/test_manifest_processor_m4.rs +++ b/tests/test_manifest_processor_m4.rs @@ -2,9 +2,20 @@ use std::path::Path; use rpki::data_model::manifest::ManifestObject; use rpki::policy::{CaFailedFetchPolicy, Policy}; -use rpki::storage::{RocksStore, VerifiedKey, VerifiedPublicationPointPack}; +use rpki::storage::{FetchCachePpKey, FetchCachePpPack, RocksStore}; use rpki::validation::manifest::{PublicationPointSource, process_manifest_publication_point}; +fn issuer_ca_fixture() -> Vec { + std::fs::read( + "tests/fixtures/repository/rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer", + ) + .expect("read issuer ca fixture") +} + +fn issuer_ca_rsync_uri() -> &'static str { + "rsync://rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer" +} + fn fixture_to_rsync_uri(path: &Path) -> String { let rel = path .strip_prefix("tests/fixtures/repository") @@ -28,7 +39,7 @@ fn fixture_dir_to_rsync_uri(dir: &Path) -> String { } #[test] -fn manifest_success_writes_verified_pack() { +fn manifest_success_writes_fetch_cache_pp_pack() { let manifest_path = Path::new( "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft", ); @@ -55,23 +66,26 @@ fn manifest_success_writes_verified_pack() { } let policy = Policy::default(); + let issuer_ca_der = issuer_ca_fixture(); let out = process_manifest_publication_point( &store, &policy, &manifest_rsync_uri, &publication_point_rsync_uri, + &issuer_ca_der, + Some(issuer_ca_rsync_uri()), validation_time, ) .expect("process manifest publication point"); assert_eq!(out.source, PublicationPointSource::Fresh); assert!(out.warnings.is_empty()); - let key = VerifiedKey::from_manifest_rsync_uri(&manifest_rsync_uri); + let key = FetchCachePpKey::from_manifest_rsync_uri(&manifest_rsync_uri); let stored = store - .get_verified(&key) - .expect("get verified") - .expect("verified pack exists"); - let decoded = VerifiedPublicationPointPack::decode(&stored).expect("decode stored pack"); + .get_fetch_cache_pp(&key) + .expect("get fetch_cache_pp") + .expect("fetch_cache_pp pack exists"); + let decoded = FetchCachePpPack::decode(&stored).expect("decode stored pack"); assert_eq!(decoded.manifest_rsync_uri, manifest_rsync_uri); assert_eq!( decoded.publication_point_rsync_uri, @@ -80,7 +94,7 @@ fn manifest_success_writes_verified_pack() { } #[test] -fn manifest_hash_mismatch_falls_back_to_verified_cache_when_enabled() { +fn manifest_hash_mismatch_falls_back_to_fetch_cache_pp_when_enabled() { let manifest_path = Path::new( "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft", ); @@ -106,22 +120,25 @@ fn manifest_hash_mismatch_falls_back_to_verified_cache_when_enabled() { } let policy = Policy::default(); + let issuer_ca_der = issuer_ca_fixture(); let first = process_manifest_publication_point( &store, &policy, &manifest_rsync_uri, &publication_point_rsync_uri, + &issuer_ca_der, + Some(issuer_ca_rsync_uri()), validation_time, ) - .expect("first run stores verified pack"); + .expect("first run stores fetch_cache_pp pack"); assert_eq!(first.source, PublicationPointSource::Fresh); - let key = VerifiedKey::from_manifest_rsync_uri(&manifest_rsync_uri); + let key = FetchCachePpKey::from_manifest_rsync_uri(&manifest_rsync_uri); let cached_bytes = store - .get_verified(&key) - .expect("get verified") - .expect("verified pack exists"); - let cached_pack = VerifiedPublicationPointPack::decode(&cached_bytes).expect("decode cached"); + .get_fetch_cache_pp(&key) + .expect("get fetch_cache_pp") + .expect("fetch_cache_pp pack exists"); + let cached_pack = FetchCachePpPack::decode(&cached_bytes).expect("decode cached"); let victim = manifest .manifest @@ -141,10 +158,12 @@ fn manifest_hash_mismatch_falls_back_to_verified_cache_when_enabled() { &policy, &manifest_rsync_uri, &publication_point_rsync_uri, + &issuer_ca_der, + Some(issuer_ca_rsync_uri()), validation_time, ) - .expect("second run falls back to verified cache"); - assert_eq!(second.source, PublicationPointSource::VerifiedCache); + .expect("second run falls back to fetch_cache_pp"); + assert_eq!(second.source, PublicationPointSource::FetchCachePp); assert!(!second.warnings.is_empty()); assert_eq!(second.pack, cached_pack); } @@ -176,15 +195,18 @@ fn manifest_failed_fetch_stop_all_output() { } let mut policy = Policy::default(); - policy.ca_failed_fetch_policy = CaFailedFetchPolicy::UseVerifiedCache; + policy.ca_failed_fetch_policy = CaFailedFetchPolicy::UseFetchCachePp; + let issuer_ca_der = issuer_ca_fixture(); let _ = process_manifest_publication_point( &store, &policy, &manifest_rsync_uri, &publication_point_rsync_uri, + &issuer_ca_der, + Some(issuer_ca_rsync_uri()), validation_time, ) - .expect("first run stores verified pack"); + .expect("first run stores fetch_cache_pp pack"); let victim = manifest .manifest @@ -205,9 +227,11 @@ fn manifest_failed_fetch_stop_all_output() { &policy, &manifest_rsync_uri, &publication_point_rsync_uri, + &issuer_ca_der, + Some(issuer_ca_rsync_uri()), validation_time, ) - .expect_err("stop_all_output should not use verified cache"); + .expect_err("stop_all_output should not use fetch_cache_pp"); let msg = err.to_string(); assert!(msg.contains("cache use is disabled")); } @@ -240,14 +264,17 @@ fn manifest_fallback_pack_is_revalidated_and_rejected_if_stale() { } let policy = Policy::default(); + let issuer_ca_der = issuer_ca_fixture(); let _ = process_manifest_publication_point( &store, &policy, &manifest_rsync_uri, &publication_point_rsync_uri, + &issuer_ca_der, + Some(issuer_ca_rsync_uri()), ok_time, ) - .expect("first run stores verified pack"); + .expect("first run stores fetch_cache_pp pack"); store .delete_raw(&manifest_rsync_uri) @@ -258,9 +285,75 @@ fn manifest_fallback_pack_is_revalidated_and_rejected_if_stale() { &policy, &manifest_rsync_uri, &publication_point_rsync_uri, + &issuer_ca_der, + Some(issuer_ca_rsync_uri()), stale_time, ) - .expect_err("stale validation_time must reject verified cache pack"); + .expect_err("stale validation_time must reject fetch_cache_pp pack"); let msg = err.to_string(); assert!(msg.contains("not valid at validation_time")); } + +#[test] +fn manifest_replay_is_treated_as_failed_fetch_and_uses_fetch_cache_pp() { + let manifest_path = Path::new( + "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft", + ); + let manifest_bytes = std::fs::read(manifest_path).expect("read manifest fixture"); + let manifest = ManifestObject::decode_der(&manifest_bytes).expect("decode manifest fixture"); + + let t1 = manifest.manifest.this_update + time::Duration::seconds(1); + let t2 = manifest.manifest.this_update + time::Duration::seconds(2); + + let manifest_rsync_uri = fixture_to_rsync_uri(manifest_path); + let publication_point_rsync_uri = fixture_dir_to_rsync_uri(manifest_path.parent().unwrap()); + + let temp = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(temp.path()).expect("open rocksdb"); + + store + .put_raw(&manifest_rsync_uri, &manifest_bytes) + .expect("store manifest"); + for entry in &manifest.manifest.files { + let file_path = manifest_path.parent().unwrap().join(&entry.file_name); + let bytes = std::fs::read(&file_path) + .unwrap_or_else(|_| panic!("read fixture file referenced by manifest: {file_path:?}")); + let rsync_uri = format!("{publication_point_rsync_uri}{}", entry.file_name); + store.put_raw(&rsync_uri, &bytes).expect("store file"); + } + + let policy = Policy::default(); + let issuer_ca_der = issuer_ca_fixture(); + + let first = process_manifest_publication_point( + &store, + &policy, + &manifest_rsync_uri, + &publication_point_rsync_uri, + &issuer_ca_der, + Some(issuer_ca_rsync_uri()), + t1, + ) + .expect("first run builds and stores fetch_cache_pp pack"); + assert_eq!(first.source, PublicationPointSource::Fresh); + + let second = process_manifest_publication_point( + &store, + &policy, + &manifest_rsync_uri, + &publication_point_rsync_uri, + &issuer_ca_der, + Some(issuer_ca_rsync_uri()), + t2, + ) + .expect("second run should treat replay as failed fetch and use cache"); + assert_eq!(second.source, PublicationPointSource::FetchCachePp); + assert_eq!(second.pack, first.pack); + assert!( + second + .warnings + .iter() + .any(|w| w.message.contains("manifestNumber not higher")), + "expected warning mentioning manifestNumber monotonicity" + ); +} diff --git a/tests/test_manifest_rfc9286_section6_1.rs b/tests/test_manifest_rfc9286_section6_1.rs new file mode 100644 index 0000000..9bbaefc --- /dev/null +++ b/tests/test_manifest_rfc9286_section6_1.rs @@ -0,0 +1,77 @@ +use std::path::Path; + +use rpki::data_model::manifest::ManifestObject; +use rpki::policy::{CaFailedFetchPolicy, Policy}; +use rpki::storage::{FetchCachePpKey, RocksStore}; +use rpki::validation::manifest::process_manifest_publication_point; + +fn issuer_ca_fixture() -> Vec { + std::fs::read( + "tests/fixtures/repository/rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer", + ) + .expect("read issuer ca fixture") +} + +fn issuer_ca_rsync_uri() -> &'static str { + "rsync://rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer" +} + +#[test] +fn manifest_outside_publication_point_is_failed_fetch_rfc9286_section6_1() { + let fixture_manifest_path = Path::new( + "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft", + ); + let fixture_dir = fixture_manifest_path.parent().expect("fixture dir"); + + let manifest_bytes = std::fs::read(fixture_manifest_path).expect("read manifest fixture"); + let manifest = ManifestObject::decode_der(&manifest_bytes).expect("decode manifest fixture"); + let validation_time = manifest.manifest.this_update + time::Duration::seconds(1); + + // Intentionally mismatch: manifest is NOT under the publication point URI. + let manifest_rsync_uri = "rsync://example.test/a/manifest.mft"; + let publication_point_rsync_uri = "rsync://example.test/b/"; + + let temp = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(temp.path()).expect("open rocksdb"); + + // Store the manifest at its rsync URI. + store + .put_raw(manifest_rsync_uri, &manifest_bytes) + .expect("store manifest raw"); + + // Store all referenced files under the (different) publication point so that §6.4/§6.5 + // would otherwise succeed if §6.1 was not enforced. + for entry in &manifest.manifest.files { + let file_path = fixture_dir.join(&entry.file_name); + let bytes = std::fs::read(&file_path) + .unwrap_or_else(|_| panic!("read fixture file referenced by manifest: {file_path:?}")); + let rsync_uri = format!("{publication_point_rsync_uri}{}", entry.file_name); + store.put_raw(&rsync_uri, &bytes).expect("store file raw"); + } + + let mut policy = Policy::default(); + policy.ca_failed_fetch_policy = CaFailedFetchPolicy::StopAllOutput; + let issuer_ca_der = issuer_ca_fixture(); + + let err = process_manifest_publication_point( + &store, + &policy, + manifest_rsync_uri, + publication_point_rsync_uri, + &issuer_ca_der, + Some(issuer_ca_rsync_uri()), + validation_time, + ) + .expect_err("§6.1 mismatch must be treated as failed fetch"); + let msg = err.to_string(); + assert!(msg.contains("RFC 9286 §6.1"), "{msg}"); + + let key = FetchCachePpKey::from_manifest_rsync_uri(manifest_rsync_uri); + assert!( + store + .get_fetch_cache_pp(&key) + .expect("get fetch_cache_pp") + .is_none(), + "must not write fetch_cache_pp on failed fetch" + ); +} diff --git a/tests/test_objects_errors_more.rs b/tests/test_objects_errors_more.rs index 0fff655..73bfe87 100644 --- a/tests/test_objects_errors_more.rs +++ b/tests/test_objects_errors_more.rs @@ -6,7 +6,7 @@ use rpki::data_model::rc::ResourceCertificate; use rpki::policy::{Policy, SignedObjectFailurePolicy}; use rpki::storage::{PackFile, RocksStore}; use rpki::validation::manifest::process_manifest_publication_point; -use rpki::validation::objects::process_verified_publication_point_pack_for_issuer; +use rpki::validation::objects::process_fetch_cache_pp_pack_for_issuer; fn fixture_to_rsync_uri(path: &Path) -> String { let rel = path @@ -31,7 +31,7 @@ fn fixture_dir_to_rsync_uri(dir: &Path) -> String { } fn build_cernet_pack_and_validation_time() -> ( - rpki::storage::VerifiedPublicationPointPack, + rpki::storage::FetchCachePpPack, time::OffsetDateTime, Vec, ResourceCertificate, @@ -59,20 +59,22 @@ fn build_cernet_pack_and_validation_time() -> ( store.put_raw(&rsync_uri, &bytes).expect("store file"); } + let issuer_ca_der = std::fs::read( + "tests/fixtures/repository/rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer", + ) + .expect("read issuer CA cert fixture"); + let policy = Policy::default(); let out = process_manifest_publication_point( &store, &policy, &manifest_rsync_uri, &publication_point_rsync_uri, + &issuer_ca_der, + Some("rsync://rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer"), manifest.manifest.this_update + time::Duration::seconds(1), ) .expect("process manifest publication point"); - - let issuer_ca_der = std::fs::read( - "tests/fixtures/repository/rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer", - ) - .expect("read issuer CA cert fixture"); let issuer_ca = ResourceCertificate::decode_der(&issuer_ca_der).expect("decode issuer CA cert"); let crl_file = out @@ -104,7 +106,7 @@ fn missing_crl_causes_roas_to_be_dropped_under_drop_object_policy() { let mut policy = Policy::default(); policy.signed_object_failure_policy = SignedObjectFailurePolicy::DropObject; - let out = process_verified_publication_point_pack_for_issuer( + let out = process_fetch_cache_pp_pack_for_issuer( &pack, &policy, &issuer_ca_der, @@ -129,7 +131,7 @@ fn wrong_issuer_ca_cert_causes_roas_to_be_dropped_under_drop_object_policy() { // Use an unrelated trust anchor certificate as the issuer to force EE cert path validation to fail. let wrong_issuer_ca_der = std::fs::read("tests/fixtures/ta/arin-ta.cer").expect("read wrong issuer ca"); - let out = process_verified_publication_point_pack_for_issuer( + let out = process_fetch_cache_pp_pack_for_issuer( &pack, &policy, &wrong_issuer_ca_der, @@ -156,7 +158,7 @@ fn invalid_aspa_object_is_reported_as_warning_under_drop_object_policy() { let mut policy = Policy::default(); policy.signed_object_failure_policy = SignedObjectFailurePolicy::DropObject; - let out = process_verified_publication_point_pack_for_issuer( + let out = process_fetch_cache_pp_pack_for_issuer( &pack, &policy, &issuer_ca_der, diff --git a/tests/test_objects_policy_m8.rs b/tests/test_objects_policy_m8.rs index ff6fd97..3128611 100644 --- a/tests/test_objects_policy_m8.rs +++ b/tests/test_objects_policy_m8.rs @@ -6,7 +6,7 @@ use rpki::data_model::rc::ResourceCertificate; use rpki::policy::{Policy, SignedObjectFailurePolicy}; use rpki::storage::{PackFile, RocksStore}; use rpki::validation::manifest::process_manifest_publication_point; -use rpki::validation::objects::process_verified_publication_point_pack_for_issuer; +use rpki::validation::objects::process_fetch_cache_pp_pack_for_issuer; fn fixture_to_rsync_uri(path: &Path) -> String { let rel = path @@ -31,7 +31,7 @@ fn fixture_dir_to_rsync_uri(dir: &Path) -> String { } fn build_cernet_pack_and_validation_time() -> ( - rpki::storage::VerifiedPublicationPointPack, + rpki::storage::FetchCachePpPack, time::OffsetDateTime, Vec, ResourceCertificate, @@ -59,20 +59,22 @@ fn build_cernet_pack_and_validation_time() -> ( store.put_raw(&rsync_uri, &bytes).expect("store file"); } + let issuer_ca_der = std::fs::read( + "tests/fixtures/repository/rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer", + ) + .expect("read issuer CA cert fixture"); + let policy = Policy::default(); let out = process_manifest_publication_point( &store, &policy, &manifest_rsync_uri, &publication_point_rsync_uri, + &issuer_ca_der, + Some("rsync://rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer"), manifest.manifest.this_update + time::Duration::seconds(1), ) .expect("process manifest publication point"); - - let issuer_ca_der = std::fs::read( - "tests/fixtures/repository/rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer", - ) - .expect("read issuer CA cert fixture"); let issuer_ca = ResourceCertificate::decode_der(&issuer_ca_der).expect("decode issuer CA cert"); let crl_file = out @@ -127,7 +129,7 @@ fn drop_object_policy_drops_only_failing_object() { let mut policy = Policy::default(); policy.signed_object_failure_policy = SignedObjectFailurePolicy::DropObject; - let out = process_verified_publication_point_pack_for_issuer( + let out = process_fetch_cache_pp_pack_for_issuer( &pack, &policy, &issuer_ca_der, @@ -169,7 +171,7 @@ fn drop_publication_point_policy_drops_the_publication_point() { let mut policy = Policy::default(); policy.signed_object_failure_policy = SignedObjectFailurePolicy::DropPublicationPoint; - let out = process_verified_publication_point_pack_for_issuer( + let out = process_fetch_cache_pp_pack_for_issuer( &pack, &policy, &issuer_ca_der, diff --git a/tests/test_objects_process_pack_for_issuer.rs b/tests/test_objects_process_pack_for_issuer.rs index b86da52..bcb100b 100644 --- a/tests/test_objects_process_pack_for_issuer.rs +++ b/tests/test_objects_process_pack_for_issuer.rs @@ -1,10 +1,10 @@ use rpki::fetch::rsync::LocalDirRsyncFetcher; use rpki::policy::{Policy, SignedObjectFailurePolicy, SyncPreference}; -use rpki::storage::{PackFile, PackTime, RocksStore, VerifiedPublicationPointPack}; +use rpki::storage::{FetchCachePpPack, PackFile, PackTime, RocksStore}; use rpki::sync::repo::sync_publication_point; use rpki::sync::rrdp::Fetcher; use rpki::validation::manifest::process_manifest_publication_point; -use rpki::validation::objects::process_verified_publication_point_pack_for_issuer; +use rpki::validation::objects::process_fetch_cache_pp_pack_for_issuer; struct NoopHttpFetcher; impl Fetcher for NoopHttpFetcher { @@ -56,12 +56,13 @@ fn minimal_pack( manifest_bytes: Vec, files: Vec, validation_time: time::OffsetDateTime, -) -> VerifiedPublicationPointPack { +) -> FetchCachePpPack { // Keep times consistent enough to pass internal pack validation. - VerifiedPublicationPointPack { - format_version: VerifiedPublicationPointPack::FORMAT_VERSION_V1, + FetchCachePpPack { + format_version: FetchCachePpPack::FORMAT_VERSION_V1, manifest_rsync_uri: manifest_rsync_uri.to_string(), publication_point_rsync_uri: publication_point_rsync_uri.to_string(), + manifest_number_be: vec![1], this_update: PackTime::from_utc_offset_datetime(validation_time), next_update: PackTime::from_utc_offset_datetime(validation_time + time::Duration::hours(1)), verified_at: PackTime::from_utc_offset_datetime(validation_time), @@ -70,12 +71,12 @@ fn minimal_pack( } } -fn build_verified_pack_from_local_rsync_fixture( +fn build_fetch_cache_pp_from_local_rsync_fixture( dir: &std::path::Path, rsync_base_uri: &str, manifest_rsync_uri: &str, validation_time: time::OffsetDateTime, -) -> rpki::storage::VerifiedPublicationPointPack { +) -> rpki::storage::FetchCachePpPack { let store_dir = tempfile::tempdir().expect("store dir"); let store = RocksStore::open(store_dir.path()).expect("open rocksdb"); let policy = Policy { @@ -98,6 +99,8 @@ fn build_verified_pack_from_local_rsync_fixture( &policy, manifest_rsync_uri, rsync_base_uri, + issuer_ca_fixture().as_slice(), + Some(issuer_ca_rsync_uri()), validation_time, ) .expect("process manifest"); @@ -111,7 +114,7 @@ fn process_pack_for_issuer_extracts_vrps_from_real_cernet_fixture() { let manifest_rsync_uri = format!("{rsync_base_uri}{manifest_file}"); let validation_time = validation_time_from_manifest_fixture(&dir, &manifest_file); - let pack = build_verified_pack_from_local_rsync_fixture( + let pack = build_fetch_cache_pp_from_local_rsync_fixture( &dir, &rsync_base_uri, &manifest_rsync_uri, @@ -123,7 +126,7 @@ fn process_pack_for_issuer_extracts_vrps_from_real_cernet_fixture() { .expect("decode issuer ca"); let policy = Policy::default(); - let out = process_verified_publication_point_pack_for_issuer( + let out = process_fetch_cache_pp_pack_for_issuer( &pack, &policy, &issuer_ca_der, @@ -147,7 +150,7 @@ fn signed_object_failure_policy_drop_object_drops_only_bad_object() { let manifest_rsync_uri = format!("{rsync_base_uri}{manifest_file}"); let validation_time = validation_time_from_manifest_fixture(&dir, &manifest_file); - let mut pack = build_verified_pack_from_local_rsync_fixture( + let mut pack = build_fetch_cache_pp_from_local_rsync_fixture( &dir, &rsync_base_uri, &manifest_rsync_uri, @@ -177,7 +180,7 @@ fn signed_object_failure_policy_drop_object_drops_only_bad_object() { signed_object_failure_policy: SignedObjectFailurePolicy::DropObject, ..Policy::default() }; - let out = process_verified_publication_point_pack_for_issuer( + let out = process_fetch_cache_pp_pack_for_issuer( &pack, &policy, &issuer_ca_der, @@ -209,7 +212,7 @@ fn signed_object_failure_policy_drop_publication_point_drops_all_output() { let manifest_rsync_uri = format!("{rsync_base_uri}{manifest_file}"); let validation_time = validation_time_from_manifest_fixture(&dir, &manifest_file); - let mut pack = build_verified_pack_from_local_rsync_fixture( + let mut pack = build_fetch_cache_pp_from_local_rsync_fixture( &dir, &rsync_base_uri, &manifest_rsync_uri, @@ -239,7 +242,7 @@ fn signed_object_failure_policy_drop_publication_point_drops_all_output() { signed_object_failure_policy: SignedObjectFailurePolicy::DropPublicationPoint, ..Policy::default() }; - let out = process_verified_publication_point_pack_for_issuer( + let out = process_fetch_cache_pp_pack_for_issuer( &pack, &policy, &issuer_ca_der, @@ -293,7 +296,7 @@ fn process_pack_for_issuer_without_crl_drops_publication_point() { ); let policy = Policy::default(); - let out = process_verified_publication_point_pack_for_issuer( + let out = process_fetch_cache_pp_pack_for_issuer( &pack, &policy, &[], @@ -335,7 +338,7 @@ fn process_pack_for_issuer_handles_invalid_aspa_bytes() { signed_object_failure_policy: SignedObjectFailurePolicy::DropObject, ..Policy::default() }; - let out = process_verified_publication_point_pack_for_issuer( + let out = process_fetch_cache_pp_pack_for_issuer( &pack, &policy, &[], @@ -377,7 +380,7 @@ fn process_pack_for_issuer_drop_publication_point_on_invalid_aspa_bytes() { signed_object_failure_policy: SignedObjectFailurePolicy::DropPublicationPoint, ..Policy::default() }; - let out = process_verified_publication_point_pack_for_issuer( + let out = process_fetch_cache_pp_pack_for_issuer( &pack, &policy, &[], diff --git a/tests/test_objects_processing_coverage_m18.rs b/tests/test_objects_processing_coverage_m18.rs index 3354c76..d2845e2 100644 --- a/tests/test_objects_processing_coverage_m18.rs +++ b/tests/test_objects_processing_coverage_m18.rs @@ -1,18 +1,19 @@ use rpki::policy::{Policy, SignedObjectFailurePolicy}; -use rpki::storage::{PackFile, PackTime, VerifiedPublicationPointPack}; -use rpki::validation::objects::process_verified_publication_point_pack_for_issuer; +use rpki::storage::{FetchCachePpPack, PackFile, PackTime}; +use rpki::validation::objects::process_fetch_cache_pp_pack_for_issuer; fn fixture_bytes(path: &str) -> Vec { std::fs::read(std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(path)) .unwrap_or_else(|e| panic!("read fixture {path}: {e}")) } -fn dummy_pack(manifest_bytes: Vec, files: Vec) -> VerifiedPublicationPointPack { +fn dummy_pack(manifest_bytes: Vec, files: Vec) -> FetchCachePpPack { let now = time::OffsetDateTime::now_utc(); - VerifiedPublicationPointPack { - format_version: VerifiedPublicationPointPack::FORMAT_VERSION_V1, + FetchCachePpPack { + format_version: FetchCachePpPack::FORMAT_VERSION_V1, manifest_rsync_uri: "rsync://example.test/repo/pp/manifest.mft".to_string(), publication_point_rsync_uri: "rsync://example.test/repo/pp/".to_string(), + manifest_number_be: vec![1], this_update: PackTime::from_utc_offset_datetime(now), next_update: PackTime::from_utc_offset_datetime(now + time::Duration::hours(1)), verified_at: PackTime::from_utc_offset_datetime(now), @@ -59,7 +60,7 @@ fn process_pack_drop_object_on_wrong_issuer_ca_for_roa() { }; let wrong_issuer_ca_der = fixture_bytes("tests/fixtures/ta/arin-ta.cer"); - let out = process_verified_publication_point_pack_for_issuer( + let out = process_fetch_cache_pp_pack_for_issuer( &pack, &policy, &wrong_issuer_ca_der, @@ -121,7 +122,7 @@ fn process_pack_drop_publication_point_on_wrong_issuer_ca_for_roa_skips_rest() { }; let wrong_issuer_ca_der = fixture_bytes("tests/fixtures/ta/arin-ta.cer"); - let out = process_verified_publication_point_pack_for_issuer( + let out = process_fetch_cache_pp_pack_for_issuer( &pack, &policy, &wrong_issuer_ca_der, @@ -170,7 +171,7 @@ fn process_pack_drop_object_on_wrong_issuer_ca_for_aspa() { }; let wrong_issuer_ca_der = fixture_bytes("tests/fixtures/ta/arin-ta.cer"); - let out = process_verified_publication_point_pack_for_issuer( + let out = process_fetch_cache_pp_pack_for_issuer( &pack, &policy, &wrong_issuer_ca_der, @@ -225,7 +226,7 @@ fn process_pack_drop_publication_point_on_wrong_issuer_ca_for_aspa_skips_rest() }; let wrong_issuer_ca_der = fixture_bytes("tests/fixtures/ta/arin-ta.cer"); - let out = process_verified_publication_point_pack_for_issuer( + let out = process_fetch_cache_pp_pack_for_issuer( &pack, &policy, &wrong_issuer_ca_der, @@ -261,7 +262,7 @@ fn process_pack_for_issuer_marks_objects_skipped_when_missing_issuer_crl() { ..Policy::default() }; - let out = process_verified_publication_point_pack_for_issuer( + let out = process_fetch_cache_pp_pack_for_issuer( &pack, &policy, &[0x01, 0x02, 0x03], @@ -303,7 +304,7 @@ fn process_pack_for_issuer_drop_object_records_errors_and_continues() { ..Policy::default() }; - let out = process_verified_publication_point_pack_for_issuer( + let out = process_fetch_cache_pp_pack_for_issuer( &pack, &policy, &[0x01, 0x02, 0x03], @@ -344,7 +345,7 @@ fn process_pack_for_issuer_drop_publication_point_records_skips_for_rest() { ..Policy::default() }; - let out = process_verified_publication_point_pack_for_issuer( + let out = process_fetch_cache_pp_pack_for_issuer( &pack, &policy, &[0x01, 0x02, 0x03], @@ -394,7 +395,7 @@ fn process_pack_for_issuer_selects_crl_by_ee_crldp_uri_roa() { ..Policy::default() }; - let out = process_verified_publication_point_pack_for_issuer( + let out = process_fetch_cache_pp_pack_for_issuer( &pack, &policy, &[0x01, 0x02, 0x03], @@ -435,7 +436,7 @@ fn process_pack_for_issuer_rejects_roa_when_crldp_crl_missing() { ..Policy::default() }; - let out = process_verified_publication_point_pack_for_issuer( + let out = process_fetch_cache_pp_pack_for_issuer( &pack, &policy, &[0x01, 0x02, 0x03], @@ -490,7 +491,7 @@ fn process_pack_for_issuer_selects_crl_by_ee_crldp_uri_aspa() { ..Policy::default() }; - let out = process_verified_publication_point_pack_for_issuer( + let out = process_fetch_cache_pp_pack_for_issuer( &pack, &policy, &[0x01, 0x02, 0x03], diff --git a/tests/test_policy.rs b/tests/test_policy.rs index 9ebfdc8..30ca0ca 100644 --- a/tests/test_policy.rs +++ b/tests/test_policy.rs @@ -6,7 +6,7 @@ fn policy_defaults_are_correct() { assert_eq!(p.sync_preference, SyncPreference::RrdpThenRsync); assert_eq!( p.ca_failed_fetch_policy, - CaFailedFetchPolicy::UseVerifiedCache + CaFailedFetchPolicy::UseFetchCachePp ); assert_eq!( p.signed_object_failure_policy, diff --git a/tests/test_run_m9.rs b/tests/test_run_m9.rs index 373128f..d1d3ca7 100644 --- a/tests/test_run_m9.rs +++ b/tests/test_run_m9.rs @@ -7,7 +7,7 @@ use rpki::fetch::rsync::LocalDirRsyncFetcher; use rpki::policy::{Policy, SyncPreference}; use rpki::storage::RocksStore; use rpki::sync::rrdp::Fetcher; -use rpki::validation::run::{run_publication_point_once, verified_pack_exists}; +use rpki::validation::run::{fetch_cache_pp_exists, run_publication_point_once}; fn fixture_to_rsync_uri(path: &Path) -> String { let rel = path @@ -40,7 +40,7 @@ impl Fetcher for NeverHttpFetcher { } #[test] -fn e2e_offline_uses_rsync_then_writes_verified_pack_then_outputs_vrps() { +fn e2e_offline_uses_rsync_then_writes_fetch_cache_pp_then_outputs_vrps() { let fixture_dir = Path::new("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0"); let rsync_base_uri = "rsync://rpki.cernet.net/repo/cernet/0/"; let manifest_path = fixture_dir.join("05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft"); @@ -105,7 +105,7 @@ fn e2e_offline_uses_rsync_then_writes_verified_pack_then_outputs_vrps() { ) .expect("run publication point once"); - assert!(verified_pack_exists(&store, &manifest_rsync_uri).expect("exists check")); + assert!(fetch_cache_pp_exists(&store, &manifest_rsync_uri).expect("exists check")); assert_eq!(out.repo_sync.objects_written, expected_files); assert!( diff --git a/tests/test_storage_iter_all.rs b/tests/test_storage_iter_all.rs index 0303936..7fe8a0b 100644 --- a/tests/test_storage_iter_all.rs +++ b/tests/test_storage_iter_all.rs @@ -1,7 +1,7 @@ -use rpki::storage::{RocksStore, VerifiedKey}; +use rpki::storage::{FetchCachePpKey, RocksStore}; #[test] -fn storage_iter_all_lists_raw_and_verified_entries() { +fn storage_iter_all_lists_raw_and_fetch_cache_pp_entries() { let temp = tempfile::tempdir().expect("tempdir"); let store = RocksStore::open(temp.path()).expect("open rocksdb"); @@ -12,8 +12,10 @@ fn storage_iter_all_lists_raw_and_verified_entries() { .put_raw("rsync://example.test/repo/b.roa", b"b") .expect("put_raw b"); - let key = VerifiedKey::from_manifest_rsync_uri("rsync://example.test/repo/m.mft"); - store.put_verified(&key, b"x").expect("put_verified"); + let key = FetchCachePpKey::from_manifest_rsync_uri("rsync://example.test/repo/m.mft"); + store + .put_fetch_cache_pp(&key, b"x") + .expect("put_fetch_cache_pp"); let raw_keys = store .raw_iter_all() @@ -24,10 +26,10 @@ fn storage_iter_all_lists_raw_and_verified_entries() { assert!(raw_keys.contains(&"rsync://example.test/repo/a.cer".to_string())); assert!(raw_keys.contains(&"rsync://example.test/repo/b.roa".to_string())); - let verified_keys = store - .verified_iter_all() - .expect("verified_iter_all") + let keys = store + .fetch_cache_pp_iter_all() + .expect("fetch_cache_pp_iter_all") .map(|(k, _v)| String::from_utf8(k.to_vec()).expect("utf8 key")) .collect::>(); - assert_eq!(verified_keys, vec![key.as_str().to_string()]); + assert_eq!(keys, vec![key.as_str().to_string()]); } diff --git a/tests/test_storage_misc_coverage_more.rs b/tests/test_storage_misc_coverage_more.rs index 441c982..d0cb7bf 100644 --- a/tests/test_storage_misc_coverage_more.rs +++ b/tests/test_storage_misc_coverage_more.rs @@ -1,6 +1,6 @@ use rocksdb::WriteBatch; -use rpki::storage::{RocksStore, VerifiedKey}; +use rpki::storage::{FetchCachePpKey, RocksStore}; #[test] fn storage_delete_rrdp_state_works() { @@ -58,9 +58,12 @@ fn storage_raw_iter_prefix_filters_by_prefix() { } #[test] -fn storage_verified_key_format_is_stable() { - let k = VerifiedKey::from_manifest_rsync_uri("rsync://example.net/repo/manifest.mft"); - assert_eq!(k.as_str(), "verified:rsync://example.net/repo/manifest.mft"); +fn storage_fetch_cache_pp_key_format_is_stable() { + let k = FetchCachePpKey::from_manifest_rsync_uri("rsync://example.net/repo/manifest.mft"); + assert_eq!( + k.as_str(), + "fetch_cache_pp:rsync://example.net/repo/manifest.mft" + ); } #[test] diff --git a/tests/test_storage_rocksdb.rs b/tests/test_storage_rocksdb.rs index 22cb9cb..2c8e384 100644 --- a/tests/test_storage_rocksdb.rs +++ b/tests/test_storage_rocksdb.rs @@ -1,6 +1,6 @@ use std::path::Path; -use rpki::storage::{RocksStore, VerifiedKey}; +use rpki::storage::{FetchCachePpKey, RocksStore}; #[test] fn storage_opens_and_creates_column_families() { @@ -25,22 +25,22 @@ fn raw_objects_roundtrip_by_rsync_uri() { } #[test] -fn verified_pack_roundtrip_by_manifest_uri() { +fn fetch_cache_pp_roundtrip_by_manifest_uri() { let dir = tempfile::tempdir().expect("tempdir"); let store = RocksStore::open(dir.path()).expect("open rocksdb"); let manifest_uri = "rsync://example.invalid/repo/manifest.mft"; - let verified_key = VerifiedKey::from_manifest_rsync_uri(manifest_uri); + let key = FetchCachePpKey::from_manifest_rsync_uri(manifest_uri); assert_eq!( - verified_key.as_str(), - "verified:rsync://example.invalid/repo/manifest.mft" + key.as_str(), + "fetch_cache_pp:rsync://example.invalid/repo/manifest.mft" ); let bytes = b"pack"; store - .put_verified(&verified_key, bytes) - .expect("put verified"); - let got = store.get_verified(&verified_key).expect("get verified"); + .put_fetch_cache_pp(&key, bytes) + .expect("put fetch_cache_pp"); + let got = store.get_fetch_cache_pp(&key).expect("get fetch_cache_pp"); assert_eq!(got.as_deref(), Some(bytes.as_slice())); } diff --git a/tests/test_tree_failure_handling.rs b/tests/test_tree_failure_handling.rs index dd2d7a3..f01e2dd 100644 --- a/tests/test_tree_failure_handling.rs +++ b/tests/test_tree_failure_handling.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use rpki::audit::{DiscoveredFrom, PublicationPointAudit}; use rpki::report::Warning; -use rpki::storage::{PackTime, VerifiedPublicationPointPack}; +use rpki::storage::{FetchCachePpPack, PackTime}; use rpki::validation::manifest::PublicationPointSource; use rpki::validation::objects::{ObjectsOutput, ObjectsStats}; use rpki::validation::tree::{ @@ -10,11 +10,12 @@ use rpki::validation::tree::{ TreeRunConfig, run_tree_serial, }; -fn empty_pack(manifest_uri: &str, pp_uri: &str) -> VerifiedPublicationPointPack { - VerifiedPublicationPointPack { - format_version: VerifiedPublicationPointPack::FORMAT_VERSION_V1, +fn empty_pack(manifest_uri: &str, pp_uri: &str) -> FetchCachePpPack { + FetchCachePpPack { + format_version: FetchCachePpPack::FORMAT_VERSION_V1, publication_point_rsync_uri: pp_uri.to_string(), manifest_rsync_uri: manifest_uri.to_string(), + manifest_number_be: vec![1], this_update: PackTime { rfc3339_utc: "2026-01-01T00:00:00Z".to_string(), }, diff --git a/tests/test_tree_traversal_m14.rs b/tests/test_tree_traversal_m14.rs index 91ec597..d336f19 100644 --- a/tests/test_tree_traversal_m14.rs +++ b/tests/test_tree_traversal_m14.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use rpki::audit::{DiscoveredFrom, PublicationPointAudit}; use rpki::report::Warning; -use rpki::storage::{PackFile, PackTime, VerifiedPublicationPointPack}; +use rpki::storage::{FetchCachePpPack, PackFile, PackTime}; use rpki::validation::manifest::PublicationPointSource; use rpki::validation::objects::{ObjectsOutput, ObjectsStats}; use rpki::validation::tree::{ @@ -43,11 +43,12 @@ impl PublicationPointRunner for MockRunner { } } -fn empty_pack(manifest_uri: &str, pp_uri: &str) -> VerifiedPublicationPointPack { - VerifiedPublicationPointPack { +fn empty_pack(manifest_uri: &str, pp_uri: &str) -> FetchCachePpPack { + FetchCachePpPack { format_version: 1, publication_point_rsync_uri: pp_uri.to_string(), manifest_rsync_uri: manifest_uri.to_string(), + manifest_number_be: vec![1], this_update: PackTime { rfc3339_utc: "2026-01-01T00:00:00Z".to_string(), }, @@ -129,7 +130,7 @@ fn tree_enqueues_children_only_for_fresh_publication_points() { .with( child1_manifest, PublicationPointRunResult { - source: PublicationPointSource::VerifiedCache, + source: PublicationPointSource::FetchCachePp, pack: empty_pack(child1_manifest, "rsync://example.test/repo/child1/"), warnings: vec![Warning::new("child1 warning")], objects: ObjectsOutput { diff --git a/tests/test_verified_pack.rs b/tests/test_verified_pack.rs index fa67f98..ae7419d 100644 --- a/tests/test_verified_pack.rs +++ b/tests/test_verified_pack.rs @@ -1,6 +1,6 @@ -use rpki::storage::{PackFile, PackTime, VerifiedPublicationPointPack}; +use rpki::storage::{FetchCachePpPack, PackFile, PackTime}; -fn sample_pack() -> VerifiedPublicationPointPack { +fn sample_pack() -> FetchCachePpPack { let this_update = PackTime::from_utc_offset_datetime(time::OffsetDateTime::from_unix_timestamp(0).unwrap()); let next_update = PackTime::from_utc_offset_datetime( @@ -18,10 +18,11 @@ fn sample_pack() -> VerifiedPublicationPointPack { b"cer-bytes".to_vec(), ); - VerifiedPublicationPointPack { - format_version: VerifiedPublicationPointPack::FORMAT_VERSION_V1, + FetchCachePpPack { + format_version: FetchCachePpPack::FORMAT_VERSION_V1, manifest_rsync_uri: "rsync://example.net/repo/CA/manifest.mft".to_string(), publication_point_rsync_uri: "rsync://example.net/repo/CA/".to_string(), + manifest_number_be: vec![1], this_update, next_update, verified_at, @@ -34,7 +35,7 @@ fn sample_pack() -> VerifiedPublicationPointPack { fn pack_encode_decode_roundtrip() { let pack = sample_pack(); let bytes = pack.encode().expect("encode"); - let decoded = VerifiedPublicationPointPack::decode(&bytes).expect("decode"); + let decoded = FetchCachePpPack::decode(&bytes).expect("decode"); assert_eq!(decoded, pack); } @@ -43,7 +44,7 @@ fn pack_rejects_missing_manifest() { let mut pack = sample_pack(); pack.manifest_bytes.clear(); let bytes = pack.encode().expect("encode"); - assert!(VerifiedPublicationPointPack::decode(&bytes).is_err()); + assert!(FetchCachePpPack::decode(&bytes).is_err()); } #[test] @@ -53,14 +54,14 @@ fn pack_rejects_duplicate_rsync_uri_entries() { PackFile::from_bytes_compute_sha256("rsync://example.net/repo/CA/1.crl", b"other".to_vec()); pack.files.push(dup); let bytes = pack.encode().expect("encode"); - assert!(VerifiedPublicationPointPack::decode(&bytes).is_err()); + assert!(FetchCachePpPack::decode(&bytes).is_err()); } #[test] fn pack_includes_this_update_next_update() { let pack = sample_pack(); let bytes = pack.encode().expect("encode"); - let decoded = VerifiedPublicationPointPack::decode(&bytes).expect("decode"); + let decoded = FetchCachePpPack::decode(&bytes).expect("decode"); let this_update = decoded.this_update.parse().expect("parse this_update"); let next_update = decoded.next_update.parse().expect("parse next_update"); diff --git a/tests/test_verified_pack_decode_errors_more.rs b/tests/test_verified_pack_decode_errors_more.rs index 58665b0..398e439 100644 --- a/tests/test_verified_pack_decode_errors_more.rs +++ b/tests/test_verified_pack_decode_errors_more.rs @@ -1,6 +1,6 @@ -use rpki::storage::{PackFile, PackTime, VerifiedPublicationPointPack}; +use rpki::storage::{FetchCachePpPack, PackFile, PackTime}; -fn base_pack() -> VerifiedPublicationPointPack { +fn base_pack() -> FetchCachePpPack { let this_update = PackTime::from_utc_offset_datetime(time::OffsetDateTime::from_unix_timestamp(0).unwrap()); let next_update = PackTime::from_utc_offset_datetime( @@ -12,10 +12,11 @@ fn base_pack() -> VerifiedPublicationPointPack { let file = PackFile::from_bytes_compute_sha256("rsync://example.net/repo/obj.cer", b"x".to_vec()); - VerifiedPublicationPointPack { - format_version: VerifiedPublicationPointPack::FORMAT_VERSION_V1, + FetchCachePpPack { + format_version: FetchCachePpPack::FORMAT_VERSION_V1, manifest_rsync_uri: "rsync://example.net/repo/manifest.mft".to_string(), publication_point_rsync_uri: "rsync://example.net/repo/".to_string(), + manifest_number_be: vec![1], this_update, next_update, verified_at, @@ -30,7 +31,7 @@ fn pack_rejects_unsupported_format_version() { pack.format_version = 999; let bytes = pack.encode().expect("encode"); assert!( - VerifiedPublicationPointPack::decode(&bytes) + FetchCachePpPack::decode(&bytes) .unwrap_err() .to_string() .contains("unsupported pack format_version") @@ -42,7 +43,7 @@ fn pack_rejects_missing_manifest_rsync_uri() { let mut pack = base_pack(); pack.manifest_rsync_uri.clear(); let bytes = pack.encode().expect("encode"); - assert!(VerifiedPublicationPointPack::decode(&bytes).is_err()); + assert!(FetchCachePpPack::decode(&bytes).is_err()); } #[test] @@ -50,7 +51,34 @@ fn pack_rejects_missing_publication_point_rsync_uri() { let mut pack = base_pack(); pack.publication_point_rsync_uri.clear(); let bytes = pack.encode().expect("encode"); - assert!(VerifiedPublicationPointPack::decode(&bytes).is_err()); + assert!(FetchCachePpPack::decode(&bytes).is_err()); +} + +#[test] +fn pack_rejects_missing_manifest_number() { + let mut pack = base_pack(); + pack.manifest_number_be.clear(); + let bytes = pack.encode().expect("encode"); + let err = FetchCachePpPack::decode(&bytes).unwrap_err(); + assert!(err.to_string().contains("missing required field")); +} + +#[test] +fn pack_rejects_manifest_number_too_long() { + let mut pack = base_pack(); + pack.manifest_number_be = vec![1u8; 21]; + let bytes = pack.encode().expect("encode"); + let err = FetchCachePpPack::decode(&bytes).unwrap_err(); + assert!(err.to_string().contains("at most 20 octets")); +} + +#[test] +fn pack_rejects_manifest_number_with_leading_zeros() { + let mut pack = base_pack(); + pack.manifest_number_be = vec![0u8, 1u8]; + let bytes = pack.encode().expect("encode"); + let err = FetchCachePpPack::decode(&bytes).unwrap_err(); + assert!(err.to_string().contains("leading zeros")); } #[test] @@ -60,7 +88,7 @@ fn pack_rejects_invalid_time_fields() { rfc3339_utc: "not-a-time".to_string(), }; let bytes = pack.encode().expect("encode"); - assert!(VerifiedPublicationPointPack::decode(&bytes).is_err()); + assert!(FetchCachePpPack::decode(&bytes).is_err()); } #[test] @@ -74,7 +102,7 @@ fn pack_rejects_empty_file_bytes() { sha, )]; let bytes = pack.encode().expect("encode"); - assert!(VerifiedPublicationPointPack::decode(&bytes).is_err()); + assert!(FetchCachePpPack::decode(&bytes).is_err()); } #[test] @@ -86,7 +114,7 @@ fn pack_rejects_file_hash_mismatch() { [0u8; 32], )]; let bytes = pack.encode().expect("encode"); - let err = VerifiedPublicationPointPack::decode(&bytes).unwrap_err(); + let err = FetchCachePpPack::decode(&bytes).unwrap_err(); assert!(err.to_string().contains("file hash mismatch")); } @@ -96,6 +124,6 @@ fn pack_rejects_missing_file_rsync_uri() { let file = PackFile::from_bytes_compute_sha256("", b"x".to_vec()); pack.files = vec![file]; let bytes = pack.encode().expect("encode"); - let err = VerifiedPublicationPointPack::decode(&bytes).unwrap_err(); + let err = FetchCachePpPack::decode(&bytes).unwrap_err(); assert!(err.to_string().contains("missing required field")); }