From 51a3085452aa5c9ec9b273080e96b4c5f89569bc Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Fri, 29 Aug 2025 14:14:05 +1000 Subject: [PATCH 01/20] Add CQCM staging server to platforms --- .../app/controllers/dfc_provider/platforms_controller.rb | 1 + engines/dfc_provider/app/services/api_user.rb | 1 + lib/open_food_network/feature_toggle.rb | 6 +++++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb b/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb index 7599fe8c06..5360780fd5 100644 --- a/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb +++ b/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb @@ -6,6 +6,7 @@ module DfcProvider # local ID => semantic ID PLATFORM_IDS = { 'cqcm-dev' => "https://api.proxy-dev.cqcm.startinblox.com/profile", + 'cqcm-stg' => "https://api.proxy-stg.cqcm.startinblox.com/profile", }.freeze prepend_before_action :move_authenticity_token diff --git a/engines/dfc_provider/app/services/api_user.rb b/engines/dfc_provider/app/services/api_user.rb index 85fe7461e1..ea054ce99c 100644 --- a/engines/dfc_provider/app/services/api_user.rb +++ b/engines/dfc_provider/app/services/api_user.rb @@ -4,6 +4,7 @@ class ApiUser CLIENT_MAP = { "https://waterlooregionfood.ca/portal/profile" => "cqcm-dev", + "https://api.proxy-stg.cqcm.startinblox.com/profile" => "cqcm-stg", }.freeze def self.from_client_id(client_id) diff --git a/lib/open_food_network/feature_toggle.rb b/lib/open_food_network/feature_toggle.rb index 7400c17ef6..9976f3e1fd 100644 --- a/lib/open_food_network/feature_toggle.rb +++ b/lib/open_food_network/feature_toggle.rb @@ -62,7 +62,11 @@ module OpenFoodNetwork Enable the inventory. DESC "cqcm-dev" => <<~DESC, - Show DFC Permissions interface to share data with CQCM dev platform. + Show DFC Permissions interface with development platform. + DESC + "cqcm-stg" => <<~DESC, + Show DFC Permissions interface to share data with CQCM staging platform. + After approval, enteprises should apppear on https://cqcm-map.startinblox.com/. DESC }.merge(conditional_features).freeze; From cb9edfaed860376e9083ad51ee5f5899dea75173 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 10 Sep 2025 14:58:21 +1000 Subject: [PATCH 02/20] Show DPM platforms enabled for user --- .../app/controllers/dfc_provider/platforms_controller.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb b/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb index 5360780fd5..dc35694fae 100644 --- a/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb +++ b/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb @@ -66,7 +66,9 @@ module DfcProvider end def available_platforms - PLATFORM_IDS.keys.select(&method(:feature?)) + PLATFORM_IDS.keys.select do |platform| + feature?(platform, current_user) + end end def platform(key) From 06c27d6aaff8267fdf54e6953c1b800f36b6a9b5 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Mon, 15 Sep 2025 13:33:10 +1000 Subject: [PATCH 03/20] Spec current publish of catalog of all enterprises --- .../spec/requests/catalog_items_spec.rb | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/engines/dfc_provider/spec/requests/catalog_items_spec.rb b/engines/dfc_provider/spec/requests/catalog_items_spec.rb index d037fa7faa..95de58a1ca 100644 --- a/engines/dfc_provider/spec/requests/catalog_items_spec.rb +++ b/engines/dfc_provider/spec/requests/catalog_items_spec.rb @@ -80,6 +80,25 @@ RSpec.describe "CatalogItems", swagger_doc: "dfc.yaml" do run_test! end + context "with a second enterprise" do + let(:enterprise_id) { 10_000 } + + before do + create( + :distributor_enterprise, + id: 10_001, owner: user, name: "Fred's Icecream", description: "Yum", + address: build(:address, id: 40_001), + ) + + pending "Fix publishing all enterprises!!!" + end + + run_test! do + expect(response.body).to include "Apple" + expect(response.body).not_to include "Icecream" + end + end + context "with default enterprise id" do let(:enterprise_id) { "default" } From 44d29e98e0ea816ba1b4f105fb2e50c58efb3701 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Mon, 15 Sep 2025 13:54:08 +1000 Subject: [PATCH 04/20] Fix publishing all enterprises when listing catalog --- .../controllers/dfc_provider/catalog_items_controller.rb | 8 +++----- engines/dfc_provider/spec/requests/catalog_items_spec.rb | 2 -- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/engines/dfc_provider/app/controllers/dfc_provider/catalog_items_controller.rb b/engines/dfc_provider/app/controllers/dfc_provider/catalog_items_controller.rb index 15705dc217..ca029b3072 100644 --- a/engines/dfc_provider/app/controllers/dfc_provider/catalog_items_controller.rb +++ b/engines/dfc_provider/app/controllers/dfc_provider/catalog_items_controller.rb @@ -9,13 +9,11 @@ module DfcProvider def index require_permission "ReadProducts" - enterprises = current_user.enterprises.map do |enterprise| - EnterpriseBuilder.enterprise(enterprise) - end - catalog_items = enterprises.flat_map(&:catalogItems) + enterprise = EnterpriseBuilder.enterprise(current_enterprise) + catalog_items = enterprise.catalogItems render json: DfcIo.export( - *enterprises, + enterprise, *catalog_items, *catalog_items.map(&:product), *catalog_items.map(&:product).flat_map(&:isVariantOf), diff --git a/engines/dfc_provider/spec/requests/catalog_items_spec.rb b/engines/dfc_provider/spec/requests/catalog_items_spec.rb index 95de58a1ca..1b69192310 100644 --- a/engines/dfc_provider/spec/requests/catalog_items_spec.rb +++ b/engines/dfc_provider/spec/requests/catalog_items_spec.rb @@ -89,8 +89,6 @@ RSpec.describe "CatalogItems", swagger_doc: "dfc.yaml" do id: 10_001, owner: user, name: "Fred's Icecream", description: "Yum", address: build(:address, id: 40_001), ) - - pending "Fix publishing all enterprises!!!" end run_test! do From 4d59343f6c2d29d8e73eb50ce02e0de615ffd519 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Mon, 15 Sep 2025 15:20:35 +1000 Subject: [PATCH 05/20] List enterprises on DFC API --- .../dfc_provider/enterprises_controller.rb | 17 +++- engines/dfc_provider/config/routes.rb | 2 +- .../spec/requests/enterprises_spec.rb | 77 +++++++++++++++++ swagger/dfc.yaml | 84 +++++++++++++++++++ 4 files changed, 178 insertions(+), 2 deletions(-) diff --git a/engines/dfc_provider/app/controllers/dfc_provider/enterprises_controller.rb b/engines/dfc_provider/app/controllers/dfc_provider/enterprises_controller.rb index 5575d13f14..00487b3018 100644 --- a/engines/dfc_provider/app/controllers/dfc_provider/enterprises_controller.rb +++ b/engines/dfc_provider/app/controllers/dfc_provider/enterprises_controller.rb @@ -3,7 +3,22 @@ # Controller used to provide the CatalogItem API for the DFC application module DfcProvider class EnterprisesController < DfcProvider::ApplicationController - before_action :check_enterprise + before_action :check_enterprise, except: :index + + def index + enterprises = current_user.enterprises.map do |enterprise| + EnterpriseBuilder.enterprise(enterprise) + end + + render json: DfcIo.export( + *enterprises, + *enterprises.map(&:mainContact), + *enterprises.flat_map(&:localizations), + *enterprises.flat_map(&:suppliedProducts), + *enterprises.flat_map(&:catalogItems), + *enterprises.flat_map(&:socialMedias), + ) + end def show enterprise = EnterpriseBuilder.enterprise(current_enterprise) diff --git a/engines/dfc_provider/config/routes.rb b/engines/dfc_provider/config/routes.rb index 6c7c487040..28870be6b2 100644 --- a/engines/dfc_provider/config/routes.rb +++ b/engines/dfc_provider/config/routes.rb @@ -2,7 +2,7 @@ DfcProvider::Engine.routes.draw do resources :addresses, only: [:show] - resources :enterprises, only: [:show] do + resources :enterprises, only: [:index, :show] do resources :catalog_items, only: [:index, :show, :update] resources :offers, only: [:show, :update] resources :platforms, only: [:index, :show, :update] diff --git a/engines/dfc_provider/spec/requests/enterprises_spec.rb b/engines/dfc_provider/spec/requests/enterprises_spec.rb index d5b1dc0a7a..fb67ec8666 100644 --- a/engines/dfc_provider/spec/requests/enterprises_spec.rb +++ b/engines/dfc_provider/spec/requests/enterprises_spec.rb @@ -18,6 +18,14 @@ RSpec.describe "Enterprises", swagger_doc: "dfc.yaml" do address: build(:address, id: 40_000, address1: "42 Doveton Street"), ) end + let!(:other_enterprise) do + create( + :distributor_enterprise, + id: 10_001, owner: user, abn: "123 457", name: "Fred's Icecream", + description: "We use our strawberries to make icecream.", + address: build(:address, id: 40_001, address1: "42 Doveton Street"), + ) + end let!(:enterprise_group) do create( :enterprise_group, @@ -46,6 +54,75 @@ RSpec.describe "Enterprises", swagger_doc: "dfc.yaml" do before { login_as user } + path "/api/dfc/enterprises" do + get "List enterprises" do + produces "application/json" + + response "200", "successful" do + context "as platform user" do + include_context "authenticated as platform" + + context "without permissions" do + run_test! do + expect(response.body).to eq "" + end + end + + context "with access to one enterprise" do + before do + DfcPermission.create!( + user:, enterprise_id: enterprise.id, + scope: "ReadEnterprise", grantee: "cqcm-dev", + ) + end + + run_test! do + expect(response.body).to include "Fred's Farm" + expect(response.body).not_to include "Fred's Icecream" + end + end + + context "with access to two enterprises" do + before do + DfcPermission.create!( + user:, enterprise_id: enterprise.id, + scope: "ReadEnterprise", grantee: "cqcm-dev", + ) + DfcPermission.create!( + user:, enterprise_id: other_enterprise.id, + scope: "ReadEnterprise", grantee: "cqcm-dev", + ) + end + + run_test! do + expect(response.body).to include "Fred's Farm" + expect(response.body).to include "Fred's Icecream" + end + end + end + + context "as user owning two enterprises" do + run_test! do + expect(response.body).to include "Fred's Farm" + expect(response.body).to include "Fred's Icecream" + + # Insert static value to keep documentation deterministic: + response.body.gsub!( + %r{active_storage/[0-9A-Za-z/=-]*/logo-white.png}, + "active_storage/url/logo-white.png", + ).gsub!( + %r{active_storage/[0-9A-Za-z/=-]*/logo.png}, + "active_storage/url/logo.png", + ).gsub!( + %r{active_storage/[0-9A-Za-z/=-]*/promo.png}, + "active_storage/url/promo.png", + ) + end + end + end + end + end + path "/api/dfc/enterprises/{id}" do get "Show enterprise" do parameter name: :id, in: :path, type: :string diff --git a/swagger/dfc.yaml b/swagger/dfc.yaml index a6a630b9f1..b0d0c0901b 100644 --- a/swagger/dfc.yaml +++ b/swagger/dfc.yaml @@ -407,6 +407,90 @@ paths: dfc-b:hasCity: Herndon dfc-b:hasCountry: Australia dfc-b:region: Victoria + "/api/dfc/enterprises": + get: + summary: List enterprises + tags: + - Enterprises + responses: + '200': + description: successful + content: + application/json: + examples: + test_example: + value: + "@context": https://www.datafoodconsortium.org + "@graph": + - "@id": http://test.host/api/dfc/enterprises/10001 + "@type": dfc-b:Enterprise + dfc-b:hasAddress: http://test.host/api/dfc/addresses/40001 + dfc-b:name: Fred's Icecream + dfc-b:hasDescription: We use our strawberries to make icecream. + dfc-b:VATnumber: 123 457 + dfc-b:hasMainContact: http://test.host/api/dfc/enterprises/10001#mainContact + ofn:long_description: "

Hello, world!

This is a paragraph.

" + - "@id": http://test.host/api/dfc/enterprises/10000 + "@type": dfc-b:Enterprise + dfc-b:hasAddress: http://test.host/api/dfc/addresses/40000 + dfc-b:hasPhoneNumber: 0404 444 000 200 + dfc-b:email: hello@example.org + dfc-b:websitePage: https://openfoodnetwork.org + dfc-b:hasSocialMedia: http://test.host/api/dfc/enterprises/10000/social_medias/facebook + dfc-b:logo: http://test.host/rails/active_storage/url/logo.png + dfc-b:name: Fred's Farm + dfc-b:hasDescription: This is an awesome enterprise + dfc-b:VATnumber: 123 456 + dfc-b:manages: http://test.host/api/dfc/enterprises/10000/catalog_items/10001 + dfc-b:supplies: http://test.host/api/dfc/enterprises/10000/supplied_products/10001 + dfc-b:hasMainContact: http://test.host/api/dfc/enterprises/10000#mainContact + ofn:long_description: "

Hello, world!

This is a paragraph.

" + ofn:contact_name: Fred Farmer + ofn:logo_url: http://test.host/rails/active_storage/url/logo.png + ofn:promo_image_url: http://test.host/rails/active_storage/url/promo.png + - "@id": http://test.host/api/dfc/enterprises/10001#mainContact + "@type": dfc-b:Person + - "@id": http://test.host/api/dfc/enterprises/10000#mainContact + "@type": dfc-b:Person + dfc-b:firstName: Fred + dfc-b:familyName: Farmer + - "@id": http://test.host/api/dfc/addresses/40001 + "@type": dfc-b:Address + dfc-b:hasStreet: 42 Doveton Street + dfc-b:hasPostalCode: '20170' + dfc-b:hasCity: Herndon + dfc-b:hasCountry: Australia + dfc-b:region: Victoria + - "@id": http://test.host/api/dfc/addresses/40000 + "@type": dfc-b:Address + dfc-b:hasStreet: 42 Doveton Street + dfc-b:hasPostalCode: '20170' + dfc-b:hasCity: Herndon + dfc-b:hasCountry: Australia + dfc-b:region: Victoria + - "@id": http://test.host/api/dfc/enterprises/10000/supplied_products/10001 + "@type": dfc-b:SuppliedProduct + dfc-b:name: Apple - 1g + dfc-b:description: Round + dfc-b:hasQuantity: + "@type": dfc-b:QuantitativeValue + dfc-b:hasUnit: dfc-m:Gram + dfc-b:value: 1.0 + dfc-b:image: http://test.host/rails/active_storage/url/logo-white.png + dfc-b:isVariantOf: http://test.host/api/dfc/product_groups/90000 + ofn:spree_product_id: 90000 + ofn:spree_product_uri: http://test.host/api/dfc/enterprises/10000?spree_product_id=90000 + ofn:image: http://test.host/rails/active_storage/url/logo-white.png + - "@id": http://test.host/api/dfc/enterprises/10000/catalog_items/10001 + "@type": dfc-b:CatalogItem + dfc-b:references: http://test.host/api/dfc/enterprises/10000/supplied_products/10001 + dfc-b:sku: APP + dfc-b:stockLimitation: 5 + dfc-b:offeredThrough: http://test.host/api/dfc/enterprises/10000/offers/10001 + - "@id": http://test.host/api/dfc/enterprises/10000/social_medias/facebook + "@type": dfc-b:SocialMedia + dfc-b:name: facebook + dfc-b:URL: https://facebook.com/user "/api/dfc/enterprises/{id}": get: summary: Show enterprise From df6e5536615aafd82726298fd8ee6dc746c6be61 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 17 Sep 2025 11:52:31 +1000 Subject: [PATCH 06/20] Add SuppliedProducts index endpoint --- .../supplied_products_controller.rb | 24 ++++++++- engines/dfc_provider/config/routes.rb | 1 + .../spec/requests/supplied_products_spec.rb | 53 ++++++++++++++++++- swagger/dfc.yaml | 46 ++++++++++++++++ 4 files changed, 122 insertions(+), 2 deletions(-) diff --git a/engines/dfc_provider/app/controllers/dfc_provider/supplied_products_controller.rb b/engines/dfc_provider/app/controllers/dfc_provider/supplied_products_controller.rb index 51857b80b4..9ccd6ebb66 100644 --- a/engines/dfc_provider/app/controllers/dfc_provider/supplied_products_controller.rb +++ b/engines/dfc_provider/app/controllers/dfc_provider/supplied_products_controller.rb @@ -4,11 +4,33 @@ # SuppliedProducts are products that are managed by an enterprise. module DfcProvider class SuppliedProductsController < DfcProvider::ApplicationController - before_action :check_enterprise + before_action :check_enterprise, except: :index rescue_from JSON::LD::JsonLdError::LoadingDocumentFailed, with: -> do head :bad_request end + def index + # WARNING! + # + # For DFC platforms accessing this with scoped permissions: + # We rely on the ReadEnterprise scope to list enterprises and + # assume that the ReadProducts scope has been granted as well. + # + # This will be correct for the first iteration of the DFC Permissions + # module but needs to be revised later. + enterprises = current_user.enterprises.map do |enterprise| + EnterpriseBuilder.enterprise(enterprise) + end + catalog_items = enterprises.flat_map(&:catalogItems) + + render json: DfcIo.export( + *catalog_items, + *catalog_items.map(&:product), + *catalog_items.map(&:product).flat_map(&:isVariantOf), + *catalog_items.flat_map(&:offers), + ) + end + def create supplied_product = import&.first diff --git a/engines/dfc_provider/config/routes.rb b/engines/dfc_provider/config/routes.rb index 28870be6b2..e92e9ca450 100644 --- a/engines/dfc_provider/config/routes.rb +++ b/engines/dfc_provider/config/routes.rb @@ -13,6 +13,7 @@ DfcProvider::Engine.routes.draw do resources :affiliated_by, only: [:create, :destroy], module: 'enterprise_groups' end resources :persons, only: [:show] + resources :supplied_products, only: [:index] resources :product_groups, only: [:show] resource :affiliate_sales_data, only: [:show] diff --git a/engines/dfc_provider/spec/requests/supplied_products_spec.rb b/engines/dfc_provider/spec/requests/supplied_products_spec.rb index a316154159..8f74da912c 100644 --- a/engines/dfc_provider/spec/requests/supplied_products_spec.rb +++ b/engines/dfc_provider/spec/requests/supplied_products_spec.rb @@ -14,7 +14,11 @@ RSpec.describe "SuppliedProducts", swagger_doc: "dfc.yaml" do ) } let(:variant) { - build(:base_variant, id: 10_001, unit_value: 1, primary_taxon: taxon, supplier: enterprise) + build( + :base_variant, + id: 10_001, sku: "BP", unit_value: 1, + primary_taxon: taxon, supplier: enterprise, + ) } let(:taxon) { build( @@ -34,6 +38,53 @@ RSpec.describe "SuppliedProducts", swagger_doc: "dfc.yaml" do before { login_as user } + path "/api/dfc/supplied_products" do + get "Index SuppliedProducts" do + produces "application/json" + + response "200", "success" do + context "as platform user" do + include_context "authenticated as platform" + + context "without permissions" do + run_test! do + expect(response.body).to eq "" + end + end + + context "with access to products" do + before do + DfcPermission.create!( + user:, enterprise_id: 10_000, + scope: "ReadEnterprise", grantee: "cqcm-dev", + ) + DfcPermission.create!( + user:, enterprise_id: 10_000, + scope: "ReadProducts", grantee: "cqcm-dev", + ) + end + + run_test! do + expect(response.body).to include "Pesto" + end + end + end + + context "as user owning two enterprises" do + run_test! do + expect(response.body).to include "Pesto" + + # Insert static value to keep documentation deterministic: + response.body.gsub!( + %r{active_storage/[0-9A-Za-z/=-]*/logo-white.png}, + "active_storage/url/logo-white.png", + ) + end + end + end + end + end + path "/api/dfc/enterprises/{enterprise_id}/supplied_products" do parameter name: :enterprise_id, in: :path, type: :string diff --git a/swagger/dfc.yaml b/swagger/dfc.yaml index b0d0c0901b..146edf8736 100644 --- a/swagger/dfc.yaml +++ b/swagger/dfc.yaml @@ -810,6 +810,52 @@ paths: dfc-b:URL: https://facebook.com/user '404': description: not found + "/api/dfc/supplied_products": + get: + summary: Index SuppliedProducts + tags: + - SuppliedProducts + responses: + '200': + description: success + content: + application/json: + examples: + test_example: + value: + "@context": https://www.datafoodconsortium.org + "@graph": + - "@id": http://test.host/api/dfc/enterprises/10000/catalog_items/10001 + "@type": dfc-b:CatalogItem + dfc-b:references: http://test.host/api/dfc/enterprises/10000/supplied_products/10001 + dfc-b:sku: BP + dfc-b:stockLimitation: 5 + dfc-b:offeredThrough: http://test.host/api/dfc/enterprises/10000/offers/10001 + - "@id": http://test.host/api/dfc/enterprises/10000/supplied_products/10001 + "@type": dfc-b:SuppliedProduct + dfc-b:name: Pesto - 1g + dfc-b:description: Basil Pesto + dfc-b:hasType: dfc-pt:processed-vegetable + dfc-b:hasQuantity: + "@type": dfc-b:QuantitativeValue + dfc-b:hasUnit: dfc-m:Gram + dfc-b:value: 1.0 + dfc-b:image: http://test.host/rails/active_storage/url/logo-white.png + dfc-b:isVariantOf: http://test.host/api/dfc/product_groups/90000 + ofn:spree_product_id: 90000 + ofn:spree_product_uri: http://test.host/api/dfc/enterprises/10000?spree_product_id=90000 + ofn:image: http://test.host/rails/active_storage/url/logo-white.png + - "@id": http://test.host/api/dfc/product_groups/90000 + "@type": dfc-b:SuppliedProduct + dfc-b:name: Pesto + dfc-b:hasVariant: http://test.host/api/dfc/enterprises/10000/supplied_products/10001 + - "@id": http://test.host/api/dfc/enterprises/10000/offers/10001 + "@type": dfc-b:Offer + dfc-b:hasPrice: + "@type": dfc-b:Price + dfc-b:value: 19.99 + dfc-b:hasUnit: dfc-m:AustralianDollar + dfc-b:stockLimitation: 5 "/api/dfc/enterprises/{enterprise_id}/supplied_products": parameters: - name: enterprise_id From c7efa43cdbb069fb15f99942aec43cb8caa3a533 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 17 Sep 2025 12:31:07 +1000 Subject: [PATCH 07/20] Add well-known config for SiB directory proxy --- public/.well-known/dfc | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 public/.well-known/dfc diff --git a/public/.well-known/dfc b/public/.well-known/dfc new file mode 100644 index 0000000000..c02097d7ec --- /dev/null +++ b/public/.well-known/dfc @@ -0,0 +1,4 @@ +{ + "https://github.com/datafoodconsortium/taxonomies/releases/latest/download/scopes.rdf#ReadEnterprise": "/api/dfc/enterprises/", + "https://github.com/datafoodconsortium/taxonomies/releases/latest/download/scopes.rdf#ReadProducts": "/api/dfc/supplied_products/", +} From ad78ef14efc394d0e24ffdb41ee0e0c0c2e22c9e Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 17 Sep 2025 15:52:03 +1000 Subject: [PATCH 08/20] Automate replacement of generated image URLs in Swagger doc --- .../spec/requests/enterprises_spec.rb | 24 ------------------- .../spec/requests/supplied_products_spec.rb | 16 ------------- spec/swagger_helper.rb | 6 +++++ 3 files changed, 6 insertions(+), 40 deletions(-) diff --git a/engines/dfc_provider/spec/requests/enterprises_spec.rb b/engines/dfc_provider/spec/requests/enterprises_spec.rb index fb67ec8666..059080ffc0 100644 --- a/engines/dfc_provider/spec/requests/enterprises_spec.rb +++ b/engines/dfc_provider/spec/requests/enterprises_spec.rb @@ -105,18 +105,6 @@ RSpec.describe "Enterprises", swagger_doc: "dfc.yaml" do run_test! do expect(response.body).to include "Fred's Farm" expect(response.body).to include "Fred's Icecream" - - # Insert static value to keep documentation deterministic: - response.body.gsub!( - %r{active_storage/[0-9A-Za-z/=-]*/logo-white.png}, - "active_storage/url/logo-white.png", - ).gsub!( - %r{active_storage/[0-9A-Za-z/=-]*/logo.png}, - "active_storage/url/logo.png", - ).gsub!( - %r{active_storage/[0-9A-Za-z/=-]*/promo.png}, - "active_storage/url/promo.png", - ) end end end @@ -168,18 +156,6 @@ RSpec.describe "Enterprises", swagger_doc: "dfc.yaml" do "dfc-b:affiliates" => "http://test.host/api/dfc/enterprise_groups/60000", "dfc-b:websitePage" => "https://openfoodnetwork.org", ) - - # Insert static value to keep documentation deterministic: - response.body.gsub!( - %r{active_storage/[0-9A-Za-z/=-]*/logo-white.png}, - "active_storage/url/logo-white.png", - ).gsub!( - %r{active_storage/[0-9A-Za-z/=-]*/logo.png}, - "active_storage/url/logo.png", - ).gsub!( - %r{active_storage/[0-9A-Za-z/=-]*/promo.png}, - "active_storage/url/promo.png", - ) end end end diff --git a/engines/dfc_provider/spec/requests/supplied_products_spec.rb b/engines/dfc_provider/spec/requests/supplied_products_spec.rb index 8f74da912c..efb9ad99c9 100644 --- a/engines/dfc_provider/spec/requests/supplied_products_spec.rb +++ b/engines/dfc_provider/spec/requests/supplied_products_spec.rb @@ -73,12 +73,6 @@ RSpec.describe "SuppliedProducts", swagger_doc: "dfc.yaml" do context "as user owning two enterprises" do run_test! do expect(response.body).to include "Pesto" - - # Insert static value to keep documentation deterministic: - response.body.gsub!( - %r{active_storage/[0-9A-Za-z/=-]*/logo-white.png}, - "active_storage/url/logo-white.png", - ) end end end @@ -218,10 +212,6 @@ RSpec.describe "SuppliedProducts", swagger_doc: "dfc.yaml" do "supplied_products/#{variant_id}", "supplied_products/10001" ) - .gsub!( - %r{active_storage/[0-9A-Za-z/=-]*/logo-white.png}, - "active_storage/url/logo-white.png", - ) end end end @@ -246,12 +236,6 @@ RSpec.describe "SuppliedProducts", swagger_doc: "dfc.yaml" do expect(json_response["ofn:spree_product_id"]).to eq 90_000 expect(json_response["dfc-b:hasType"]).to eq("dfc-pt:processed-vegetable") expect(json_response["ofn:image"]).to include("logo-white.png") - - # Insert static value to keep documentation deterministic: - response.body.gsub!( - %r{active_storage/[0-9A-Za-z/=-]*/logo-white.png}, - "active_storage/url/logo-white.png", - ) end end diff --git a/spec/swagger_helper.rb b/spec/swagger_helper.rb index 266cf47311..28cec7a963 100644 --- a/spec/swagger_helper.rb +++ b/spec/swagger_helper.rb @@ -80,6 +80,12 @@ RSpec.configure do |config| next if response&.body.blank? + # Replace random values from generated strings for a deterministic documentation. + response.body.gsub!( + %r{/rails/active_storage/[0-9A-Za-z/=-]*/([^/.]+).png}, + '/rails/active_storage/url/\1.png', + ) + # Include response as example in the documentation. example.metadata[:response][:content] ||= {} example.metadata[:response][:content].deep_merge!( From bf661159c62b4348d23222805d96050ae80b118a Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 17 Sep 2025 17:13:25 +1000 Subject: [PATCH 09/20] Notify data proxy of permission changes --- .../dfc_provider/platforms_controller.rb | 3 + .../app/services/proxy_notifier.rb | 67 +++++++++++ .../spec/requests/platforms_spec.rb | 6 + .../spec/services/proxy_notifier_spec.rb | 27 +++++ .../ProxyNotifier/notifies_the_proxy.yml | 110 ++++++++++++++++++ .../receives_an_access_token.yml | 52 +++++++++ .../admin/enterprises/dfc_permissions_spec.rb | 3 + 7 files changed, 268 insertions(+) create mode 100644 engines/dfc_provider/app/services/proxy_notifier.rb create mode 100644 engines/dfc_provider/spec/services/proxy_notifier_spec.rb create mode 100644 spec/fixtures/vcr_cassettes/ProxyNotifier/notifies_the_proxy.yml create mode 100644 spec/fixtures/vcr_cassettes/ProxyNotifier/receives_an_access_token.yml diff --git a/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb b/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb index dc35694fae..26ec1ebe3c 100644 --- a/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb +++ b/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb @@ -46,6 +46,9 @@ module DfcProvider grantee: key, ) end + + ProxyNotifier.new.refresh(PLATFORM_IDS[key]) + render json: platform(key) end diff --git a/engines/dfc_provider/app/services/proxy_notifier.rb b/engines/dfc_provider/app/services/proxy_notifier.rb new file mode 100644 index 0000000000..7da33718df --- /dev/null +++ b/engines/dfc_provider/app/services/proxy_notifier.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require "private_address_check" +require "private_address_check/tcpsocket_ext" + +# Call a webhook to notify a data proxy about changes in our data. +class ProxyNotifier + TOKEN_ENDPOINTS = { + 'https://api.proxy-dev.cqcm.startinblox.com/profile' => "https://kc.cqcm.startinblox.com/realms/startinblox/protocol/openid-connect/token", + 'https://api.proxy-stg.cqcm.startinblox.com/profile' => "https://kc.cqcm.startinblox.com/realms/startinblox/protocol/openid-connect/token", + 'https://api.proxy.cqcm.startinblox.com/profile' => "https://authentification.cqcm.coop/realms/cqcm/protocol/openid-connect/token", + + }.freeze + + def refresh(platform_url) + PrivateAddressCheck.only_public_connections do + notify_proxy(platform_url) + end + end + + def request_token(platform_url) + connection = Faraday.new( + request: { timeout: 5 }, + ) do |f| + f.request :url_encoded + f.response :json + f.response :raise_error + end + + url = TOKEN_ENDPOINTS[platform_url] + data = { + grant_type: "client_credentials", + client_id: ENV.fetch("OPENID_APP_ID", nil), + client_secret: ENV.fetch("OPENID_APP_SECRET", nil), + scope: "WriteEnterprise", + } + response = connection.post(url, data) + response.body["access_token"] + end + + def notify_proxy(platform_url) + token = request_token(platform_url) + data = { + eventType: "refresh", + enterpriseUrlid: DfcProvider::Engine.routes.url_helpers.enterprises_url, + scope: "ReadEnterprise", + } + + connection = Faraday.new( + request: { timeout: 10 }, + headers: { + 'Authorization' => "Bearer #{token}", + } + ) do |f| + f.request :json + f.response :json + f.response :raise_error + end + connection.post(webhook_url(platform_url), data) + end + + def webhook_url(platform_url) + URI.parse(platform_url).tap do |url| + url.path = "/djangoldp-dfc/webhook/" + end + end +end diff --git a/engines/dfc_provider/spec/requests/platforms_spec.rb b/engines/dfc_provider/spec/requests/platforms_spec.rb index bc5ea5e372..2273b3025d 100644 --- a/engines/dfc_provider/spec/requests/platforms_spec.rb +++ b/engines/dfc_provider/spec/requests/platforms_spec.rb @@ -93,6 +93,12 @@ RSpec.describe "Platforms", swagger_doc: "dfc.yaml" do example.metadata[:operation][:parameters].first[:schema][:example] end + before do + stub_request(:post, "https://kc.cqcm.startinblox.com/realms/startinblox/protocol/openid-connect/token") + .and_return(body: { access_token: "testtoken" }.to_json) + stub_request(:post, "https://api.proxy-dev.cqcm.startinblox.com/djangoldp-dfc/webhook/") + end + run_test! do expect(json_response["@id"]).to eq "https://api.proxy-dev.cqcm.startinblox.com/profile" end diff --git a/engines/dfc_provider/spec/services/proxy_notifier_spec.rb b/engines/dfc_provider/spec/services/proxy_notifier_spec.rb new file mode 100644 index 0000000000..dfda2b5d37 --- /dev/null +++ b/engines/dfc_provider/spec/services/proxy_notifier_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require_relative "../spec_helper" + +# These tests depend on valid OpenID Connect client credentials in your +# `.env.test.local` file. +# +# OPENID_APP_ID="..." +# OPENID_APP_SECRET="..." +RSpec.describe ProxyNotifier do + let(:platform_url) { "https://api.proxy-dev.cqcm.startinblox.com/profile" } + + it "receives an access token", :vcr do + token = subject.request_token(platform_url) + expect(token).to be_a String + expect(token.length).to be > 20 + end + + it "notifies the proxy", :vcr do + # The test server is not reachable by the notified server. + # If you don't have valid credentials, you'll get an unauthorized error. + # Correctly authenticated, the server fails to update its data. + expect { + subject.refresh(platform_url) + }.to raise_error Faraday::ServerError + end +end diff --git a/spec/fixtures/vcr_cassettes/ProxyNotifier/notifies_the_proxy.yml b/spec/fixtures/vcr_cassettes/ProxyNotifier/notifies_the_proxy.yml new file mode 100644 index 0000000000..9c24efd8cd --- /dev/null +++ b/spec/fixtures/vcr_cassettes/ProxyNotifier/notifies_the_proxy.yml @@ -0,0 +1,110 @@ +--- +http_interactions: +- request: + method: post + uri: https://kc.cqcm.startinblox.com/realms/startinblox/protocol/openid-connect/token + body: + encoding: UTF-8 + string: client_id=https%3A%2F%2Fstaging.openfoodnetwork.org.uk%2F&client_secret=&grant_type=client_credentials&scope=WriteEnterprise + headers: + User-Agent: + - Faraday v2.9.0 + Content-Type: + - application/x-www-form-urlencoded + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx/1.22.1 + Date: + - Wed, 24 Sep 2025 04:08:53 GMT + Content-Type: + - application/json + Content-Length: + - '1726' + Connection: + - keep-alive + Cache-Control: + - no-store + Pragma: + - no-cache + Referrer-Policy: + - no-referrer + Strict-Transport-Security: + - max-age=31536000; includeSubDomains + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Xss-Protection: + - 1; mode=block + body: + encoding: UTF-8 + string: '{"access_token":"","expires_in":300,"refresh_expires_in":0,"token_type":"Bearer","not-before-policy":0,"scope":"WriteEnterprise + profile email"}' + recorded_at: Wed, 24 Sep 2025 04:08:53 GMT +- request: + method: post + uri: https://api.proxy-dev.cqcm.startinblox.com/djangoldp-dfc/webhook/ + body: + encoding: UTF-8 + string: '{"eventType":"refresh","enterpriseUrlid":"http://test.host/api/dfc/enterprises","scope":"ReadEnterprise"}' + headers: + Authorization: + - "" + User-Agent: + - Faraday v2.9.0 + Content-Type: + - application/json + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 500 + message: Internal Server Error + headers: + Connection: + - keep-alive + Content-Length: + - '110452' + Content-Type: + - text/html; charset=utf-8 + Vary: + - Accept-Encoding, Cookie + Access-Control-Allow-Origin: + - None + Access-Control-Allow-Methods: + - GET,POST,PUT,PATCH,DELETE,OPTIONS,HEAD + Access-Control-Allow-Headers: + - authorization, Content-Type, if-match, accept, sentry-trace, DPoP, cache-control, + pragma, prefer, accept-model-fields, depth + Access-Control-Expose-Headers: + - Location, User + Access-Control-Allow-Credentials: + - 'true' + X-Frame-Options: + - DENY + X-Content-Type-Options: + - nosniff + Referrer-Policy: + - same-origin + Cross-Origin-Opener-Policy: + - same-origin + Via: + - 1.1 alproxy + Date: + - Wed, 24 Sep 2025 04:08:56 GMT + body: + encoding: ASCII-8BIT + string: !binary |- + PCFET0NUWVBFIGh0bWw+CjxodG1sIGxhbmc9ImVuIj4KPGhlYWQ+CiAgPG1ldGEgaHR0cC1lcXVpdj0iY29udGVudC10eXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7IGNoYXJzZXQ9dXRmLTgiPgogIDxtZXRhIG5hbWU9InJvYm90cyIgY29udGVudD0iTk9ORSxOT0FSQ0hJVkUiPgogIDx0aXRsZT5KU09ORGVjb2RlRXJyb3IKICAgICAgICAgIGF0IC9kamFuZ29sZHAtZGZjL3dlYmhvb2svPC90aXRsZT4KICA8c3R5bGUgdHlwZT0idGV4dC9jc3MiPgogICAgaHRtbCAqIHsgcGFkZGluZzowOyBtYXJnaW46MDsgfQogICAgYm9keSAqIHsgcGFkZGluZzoxMHB4IDIwcHg7IH0KICAgIGJvZHkgKiAqIHsgcGFkZGluZzowOyB9CiAgICBib2R5IHsgZm9udDpzbWFsbCBzYW5zLXNlcmlmOyBiYWNrZ3JvdW5kLWNvbG9yOiNmZmY7IGNvbG9yOiMwMDA7IH0KICAgIGJvZHk+ZGl2IHsgYm9yZGVyLWJvdHRvbToxcHggc29saWQgI2RkZDsgfQogICAgaDEgeyBmb250LXdlaWdodDpub3JtYWw7IH0KICAgIGgyIHsgbWFyZ2luLWJvdHRvbTouOGVtOyB9CiAgICBoMyB7IG1hcmdpbjoxZW0gMCAuNWVtIDA7IH0KICAgIGg0IHsgbWFyZ2luOjAgMCAuNWVtIDA7IGZvbnQtd2VpZ2h0OiBub3JtYWw7IH0KICAgIGNvZGUsIHByZSB7IGZvbnQtc2l6ZTogMTAwJTsgd2hpdGUtc3BhY2U6IHByZS13cmFwOyB3b3JkLWJyZWFrOiBicmVhay13b3JkOyB9CiAgICBzdW1tYXJ5IHsgY3Vyc29yOiBwb2ludGVyOyB9CiAgICB0YWJsZSB7IGJvcmRlcjoxcHggc29saWQgI2NjYzsgYm9yZGVyLWNvbGxhcHNlOiBjb2xsYXBzZTsgd2lkdGg6MTAwJTsgYmFja2dyb3VuZDp3aGl0ZTsgfQogICAgdGJvZHkgdGQsIHRib2R5IHRoIHsgdmVydGljYWwtYWxpZ246dG9wOyBwYWRkaW5nOjJweCAzcHg7IH0KICAgIHRoZWFkIHRoIHsKICAgICAgcGFkZGluZzoxcHggNnB4IDFweCAzcHg7IGJhY2tncm91bmQ6I2ZlZmVmZTsgdGV4dC1hbGlnbjpsZWZ0OwogICAgICBmb250LXdlaWdodDpub3JtYWw7IGZvbnQtc2l6ZToxMXB4OyBib3JkZXI6MXB4IHNvbGlkICNkZGQ7CiAgICB9CiAgICB0Ym9keSB0aCB7IHdpZHRoOjEyZW07IHRleHQtYWxpZ246cmlnaHQ7IGNvbG9yOiM2NjY7IHBhZGRpbmctcmlnaHQ6LjVlbTsgfQogICAgdGFibGUudmFycyB7IG1hcmdpbjo1cHggMTBweCAycHggNDBweDsgd2lkdGg6IGF1dG87IH0KICAgIHRhYmxlLnZhcnMgdGQsIHRhYmxlLnJlcSB0ZCB7IGZvbnQtZmFtaWx5Om1vbm9zcGFjZTsgfQogICAgdGFibGUgdGQuY29kZSB7IHdpZHRoOjEwMCU7IH0KICAgIHRhYmxlIHRkLmNvZGUgcHJlIHsgb3ZlcmZsb3c6aGlkZGVuOyB9CiAgICB0YWJsZS5zb3VyY2UgdGggeyBjb2xvcjojNjY2OyB9CiAgICB0YWJsZS5zb3VyY2UgdGQgeyBmb250LWZhbWlseTptb25vc3BhY2U7IHdoaXRlLXNwYWNlOnByZTsgYm9yZGVyLWJvdHRvbToxcHggc29saWQgI2VlZTsgfQogICAgdWwudHJhY2ViYWNrIHsgbGlzdC1zdHlsZS10eXBlOm5vbmU7IGNvbG9yOiAjMjIyOyB9CiAgICB1bC50cmFjZWJhY2sgbGkuY2F1c2UgeyB3b3JkLWJyZWFrOiBicmVhay13b3JkOyB9CiAgICB1bC50cmFjZWJhY2sgbGkuZnJhbWUgeyBwYWRkaW5nLWJvdHRvbToxZW07IGNvbG9yOiM0ZjRmNGY7IH0KICAgIHVsLnRyYWNlYmFjayBsaS51c2VyIHsgYmFja2dyb3VuZC1jb2xvcjojZTBlMGUwOyBjb2xvcjojMDAwIH0KICAgIGRpdi5jb250ZXh0IHsgcGFkZGluZzoxMHB4IDA7IG92ZXJmbG93OmhpZGRlbjsgfQogICAgZGl2LmNvbnRleHQgb2wgeyBwYWRkaW5nLWxlZnQ6MzBweDsgbWFyZ2luOjAgMTBweDsgbGlzdC1zdHlsZS1wb3NpdGlvbjogaW5zaWRlOyB9CiAgICBkaXYuY29udGV4dCBvbCBsaSB7IGZvbnQtZmFtaWx5Om1vbm9zcGFjZTsgd2hpdGUtc3BhY2U6cHJlOyBjb2xvcjojNzc3OyBjdXJzb3I6cG9pbnRlcjsgcGFkZGluZy1sZWZ0OiAycHg7IH0KICAgIGRpdi5jb250ZXh0IG9sIGxpIHByZSB7IGRpc3BsYXk6aW5saW5lOyB9CiAgICBkaXYuY29udGV4dCBvbC5jb250ZXh0LWxpbmUgbGkgeyBjb2xvcjojNDY0NjQ2OyBiYWNrZ3JvdW5kLWNvbG9yOiNkZmRmZGY7IHBhZGRpbmc6IDNweCAycHg7IH0KICAgIGRpdi5jb250ZXh0IG9sLmNvbnRleHQtbGluZSBsaSBzcGFuIHsgcG9zaXRpb246YWJzb2x1dGU7IHJpZ2h0OjMycHg7IH0KICAgIC51c2VyIGRpdi5jb250ZXh0IG9sLmNvbnRleHQtbGluZSBsaSB7IGJhY2tncm91bmQtY29sb3I6I2JiYjsgY29sb3I6IzAwMDsgfQogICAgLnVzZXIgZGl2LmNvbnRleHQgb2wgbGkgeyBjb2xvcjojNjY2OyB9CiAgICBkaXYuY29tbWFuZHMsIHN1bW1hcnkuY29tbWFuZHMgeyBtYXJnaW4tbGVmdDogNDBweDsgfQogICAgZGl2LmNvbW1hbmRzIGEsIHN1bW1hcnkuY29tbWFuZHMgeyBjb2xvcjojNTU1OyB0ZXh0LWRlY29yYXRpb246bm9uZTsgfQogICAgLnVzZXIgZGl2LmNvbW1hbmRzIGEgeyBjb2xvcjogYmxhY2s7IH0KICAgICNzdW1tYXJ5IHsgYmFja2dyb3VuZDogI2ZmYzsgfQogICAgI3N1bW1hcnkgaDIgeyBmb250LXdlaWdodDogbm9ybWFsOyBjb2xvcjogIzY2NjsgfQogICAgI2V4cGxhbmF0aW9uIHsgYmFja2dyb3VuZDojZWVlOyB9CiAgICAjdGVtcGxhdGUsICN0ZW1wbGF0ZS1ub3QtZXhpc3QgeyBiYWNrZ3JvdW5kOiNmNmY2ZjY7IH0KICAgICN0ZW1wbGF0ZS1ub3QtZXhpc3QgdWwgeyBtYXJnaW46IDAgMCAxMHB4IDIwcHg7IH0KICAgICN0ZW1wbGF0ZS1ub3QtZXhpc3QgLnBvc3Rtb3J0ZW0tc2VjdGlvbiB7IG1hcmdpbi1ib3R0b206IDNweDsgfQogICAgI3VuaWNvZGUtaGludCB7IGJhY2tncm91bmQ6I2VlZTsgfQogICAgI3RyYWNlYmFjayB7IGJhY2tncm91bmQ6I2VlZTsgfQogICAgI3JlcXVlc3RpbmZvIHsgYmFja2dyb3VuZDojZjZmNmY2OyBwYWRkaW5nLWxlZnQ6MTIwcHg7IH0KICAgICNzdW1tYXJ5IHRhYmxlIHsgYm9yZGVyOm5vbmU7IGJhY2tncm91bmQ6dHJhbnNwYXJlbnQ7IH0KICAgICNyZXF1ZXN0aW5mbyBoMiwgI3JlcXVlc3RpbmZvIGgzIHsgcG9zaXRpb246cmVsYXRpdmU7IG1hcmdpbi1sZWZ0Oi0xMDBweDsgfQogICAgI3JlcXVlc3RpbmZvIGgzIHsgbWFyZ2luLWJvdHRvbTotMWVtOyB9CiAgICAuZXJyb3IgeyBiYWNrZ3JvdW5kOiAjZmZjOyB9CiAgICAuc3BlY2lmaWMgeyBjb2xvcjojY2MzMzAwOyBmb250LXdlaWdodDpib2xkOyB9CiAgICBoMiBzcGFuLmNvbW1hbmRzIHsgZm9udC1zaXplOi43ZW07IGZvbnQtd2VpZ2h0Om5vcm1hbDsgfQogICAgc3Bhbi5jb21tYW5kcyBhOmxpbmsge2NvbG9yOiM1RTU2OTQ7fQogICAgcHJlLmV4Y2VwdGlvbl92YWx1ZSB7IGZvbnQtZmFtaWx5OiBzYW5zLXNlcmlmOyBjb2xvcjogIzU3NTc1NzsgZm9udC1zaXplOiAxLjVlbTsgbWFyZ2luOiAxMHB4IDAgMTBweCAwOyB9CiAgICAuYXBwZW5kLWJvdHRvbSB7IG1hcmdpbi1ib3R0b206IDEwcHg7IH0KICAgIC5mbmFtZSB7IHVzZXItc2VsZWN0OiBhbGw7IH0KICA8L3N0eWxlPgogIAogIDxzY3JpcHQ+CiAgICBmdW5jdGlvbiBoaWRlQWxsKGVsZW1zKSB7CiAgICAgIGZvciAodmFyIGUgPSAwOyBlIDwgZWxlbXMubGVuZ3RoOyBlKyspIHsKICAgICAgICBlbGVtc1tlXS5zdHlsZS5kaXNwbGF5ID0gJ25vbmUnOwogICAgICB9CiAgICB9CiAgICB3aW5kb3cub25sb2FkID0gZnVuY3Rpb24oKSB7CiAgICAgIGhpZGVBbGwoZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCgnb2wucHJlLWNvbnRleHQnKSk7CiAgICAgIGhpZGVBbGwoZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCgnb2wucG9zdC1jb250ZXh0JykpOwogICAgICBoaWRlQWxsKGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3JBbGwoJ2Rpdi5wYXN0ZWJpbicpKTsKICAgIH0KICAgIGZ1bmN0aW9uIHRvZ2dsZSgpIHsKICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCBhcmd1bWVudHMubGVuZ3RoOyBpKyspIHsKICAgICAgICB2YXIgZSA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKGFyZ3VtZW50c1tpXSk7CiAgICAgICAgaWYgKGUpIHsKICAgICAgICAgIGUuc3R5bGUuZGlzcGxheSA9IGUuc3R5bGUuZGlzcGxheSA9PSAnbm9uZScgPyAnYmxvY2snOiAnbm9uZSc7CiAgICAgICAgfQogICAgICB9CiAgICAgIHJldHVybiBmYWxzZTsKICAgIH0KICAgIGZ1bmN0aW9uIHN3aXRjaFBhc3RlYmluRnJpZW5kbHkobGluaykgewogICAgICBzMSA9ICJTd2l0Y2ggdG8gY29weS1hbmQtcGFzdGUgdmlldyI7CiAgICAgIHMyID0gIlN3aXRjaCBiYWNrIHRvIGludGVyYWN0aXZlIHZpZXciOwogICAgICBsaW5rLnRleHRDb250ZW50ID0gbGluay50ZXh0Q29udGVudC50cmltKCkgPT0gczEgPyBzMjogczE7CiAgICAgIHRvZ2dsZSgnYnJvd3NlclRyYWNlYmFjaycsICdwYXN0ZWJpblRyYWNlYmFjaycpOwogICAgICByZXR1cm4gZmFsc2U7CiAgICB9CiAgPC9zY3JpcHQ+CiAgCjwvaGVhZD4KPGJvZHk+CjxkaXYgaWQ9InN1bW1hcnkiPgogIDxoMT5KU09ORGVjb2RlRXJyb3IKICAgICAgIGF0IC9kamFuZ29sZHAtZGZjL3dlYmhvb2svPC9oMT4KICA8cHJlIGNsYXNzPSJleGNlcHRpb25fdmFsdWUiPkV4cGVjdGluZyB2YWx1ZTogbGluZSAxIGNvbHVtbiAxIChjaGFyIDApPC9wcmU+CiAgPHRhYmxlIGNsYXNzPSJtZXRhIj4KCiAgICA8dHI+CiAgICAgIDx0aD5SZXF1ZXN0IE1ldGhvZDo8L3RoPgogICAgICA8dGQ+UE9TVDwvdGQ+CiAgICA8L3RyPgogICAgPHRyPgogICAgICA8dGg+UmVxdWVzdCBVUkw6PC90aD4KICAgICAgPHRkPmh0dHBzOi8vYXBpLnByb3h5LWRldi5jcWNtLnN0YXJ0aW5ibG94LmNvbS9kamFuZ29sZHAtZGZjL3dlYmhvb2svPC90ZD4KICAgIDwvdHI+CgogICAgPHRyPgogICAgICA8dGg+RGphbmdvIFZlcnNpb246PC90aD4KICAgICAgPHRkPjQuMi4yMDwvdGQ+CiAgICA8L3RyPgoKICAgIDx0cj4KICAgICAgPHRoPkV4Y2VwdGlvbiBUeXBlOjwvdGg+CiAgICAgIDx0ZD5KU09ORGVjb2RlRXJyb3I8L3RkPgogICAgPC90cj4KCgogICAgPHRyPgogICAgICA8dGg+RXhjZXB0aW9uIFZhbHVlOjwvdGg+CiAgICAgIDx0ZD48cHJlPkV4cGVjdGluZyB2YWx1ZTogbGluZSAxIGNvbHVtbiAxIChjaGFyIDApPC9wcmU+PC90ZD4KICAgIDwvdHI+CgoKICAgIDx0cj4KICAgICAgPHRoPkV4Y2VwdGlvbiBMb2NhdGlvbjo8L3RoPgogICAgICA8dGQ+PHNwYW4gY2xhc3M9ImZuYW1lIj4vaG9tZS9jcWNtLXByb3h5LWRldi9zdGFydGluYmxveC92ZW52L2xpYi9weXRob24zLjExL3NpdGUtcGFja2FnZXMvcmVxdWVzdHMvbW9kZWxzLnB5PC9zcGFuPiwgbGluZSA5NzUsIGluIGpzb248L3RkPgogICAgPC90cj4KCgogICAgPHRyPgogICAgICA8dGg+UmFpc2VkIGR1cmluZzo8L3RoPgogICAgICA8dGQ+ZGF0YV9mb29kX2NvbnNvcnRpdW0udmlld3MuQ2FjaGVXZWJob29rVmlldzwvdGQ+CiAgICA8L3RyPgoKICAgIDx0cj4KICAgICAgPHRoPlB5dGhvbiBFeGVjdXRhYmxlOjwvdGg+CiAgICAgIDx0ZD4vdXNyL2Fsd2F5c2RhdGEvdXdzZ2kvMi4wLjI4L2Jpbi91d3NnaTwvdGQ+CiAgICA8L3RyPgogICAgPHRyPgogICAgICA8dGg+UHl0aG9uIFZlcnNpb246PC90aD4KICAgICAgPHRkPjMuMTEuMTM8L3RkPgogICAgPC90cj4KICAgIDx0cj4KICAgICAgPHRoPlB5dGhvbiBQYXRoOjwvdGg+CiAgICAgIDx0ZD48cHJlPlsmI3gyNzsuJiN4Mjc7LAogJiN4Mjc7JiN4Mjc7LAogJiN4Mjc7L3Vzci9hbHdheXNkYXRhL3B5dGhvbi8zLjExL2xpYi9weXRob24zMTEuemlwJiN4Mjc7LAogJiN4Mjc7L3Vzci9hbHdheXNkYXRhL3B5dGhvbi8zLjExL2xpYi9weXRob24zLjExJiN4Mjc7LAogJiN4Mjc7L3Vzci9hbHdheXNkYXRhL3B5dGhvbi8zLjExL2xpYi9weXRob24zLjExL2xpYi1keW5sb2FkJiN4Mjc7LAogJiN4Mjc7L2hvbWUvY3FjbS1wcm94eS1kZXYvc3RhcnRpbmJsb3gvdmVudi9saWIvcHl0aG9uMy4xMS9zaXRlLXBhY2thZ2VzJiN4Mjc7XTwvcHJlPjwvdGQ+CiAgICA8L3RyPgogICAgPHRyPgogICAgICA8dGg+U2VydmVyIHRpbWU6PC90aD4KICAgICAgPHRkPldlZCwgMjQgU2VwIDIwMjUgMDQ6MDg6NTYgKzAwMDA8L3RkPgogICAgPC90cj4KICA8L3RhYmxlPgo8L2Rpdj4KCgoKCjxkaXYgaWQ9InRyYWNlYmFjayI+CiAgPGgyPlRyYWNlYmFjayA8c3BhbiBjbGFzcz0iY29tbWFuZHMiPjxhIGhyZWY9IiMiIG9uY2xpY2s9InJldHVybiBzd2l0Y2hQYXN0ZWJpbkZyaWVuZGx5KHRoaXMpOyI+CiAgICBTd2l0Y2ggdG8gY29weS1hbmQtcGFzdGUgdmlldzwvYT48L3NwYW4+CiAgPC9oMj4KICA8ZGl2IGlkPSJicm93c2VyVHJhY2ViYWNrIj4KICAgIDx1bCBjbGFzcz0idHJhY2ViYWNrIj4KICAgICAgCiAgICAgICAgCiAgICAgICAgPGxpIGNsYXNzPSJmcmFtZSB1c2VyIj4KICAgICAgICAgIAogICAgICAgICAgICA8Y29kZSBjbGFzcz0iZm5hbWUiPi9ob21lL2NxY20tcHJveHktZGV2L3N0YXJ0aW5ibG94L3ZlbnYvbGliL3B5dGhvbjMuMTEvc2l0ZS1wYWNrYWdlcy9yZXF1ZXN0cy9tb2RlbHMucHk8L2NvZGU+LCBsaW5lIDk3MSwgaW4ganNvbgogICAgICAgICAgCgogICAgICAgICAgCiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbnRleHQiIGlkPSJjMTQwNTE1NzMwNDQ3MjMyIj4KICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgIDxvbCBzdGFydD0iOTY0IiBjbGFzcz0icHJlLWNvbnRleHQiIGlkPSJwcmUxNDA1MTU3MzA0NDcyMzIiPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDQ0NzIzMicsICdwb3N0MTQwNTE1NzMwNDQ3MjMyJykiPjxwcmU+ICAgICAgICAgICAgICAgICAgICAjIGFuZCB0aGUgc2VydmVyIGRpZG4mI3gyNzt0IGJvdGhlciB0byB0ZWxsIHVzIHdoYXQgY29kZWMgKndhcyo8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDQ0NzIzMicsICdwb3N0MTQwNTE1NzMwNDQ3MjMyJykiPjxwcmU+ICAgICAgICAgICAgICAgICAgICAjIHVzZWQuPC9wcmU+PC9saT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzA0NDcyMzInLCAncG9zdDE0MDUxNTczMDQ0NzIzMicpIj48cHJlPiAgICAgICAgICAgICAgICAgICAgcGFzczwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwNDQ3MjMyJywgJ3Bvc3QxNDA1MTU3MzA0NDcyMzInKSI+PHByZT4gICAgICAgICAgICAgICAgZXhjZXB0IEpTT05EZWNvZGVFcnJvciBhcyBlOjwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwNDQ3MjMyJywgJ3Bvc3QxNDA1MTU3MzA0NDcyMzInKSI+PHByZT4gICAgICAgICAgICAgICAgICAgIHJhaXNlIFJlcXVlc3RzSlNPTkRlY29kZUVycm9yKGUubXNnLCBlLmRvYywgZS5wb3MpPC9wcmU+PC9saT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzA0NDcyMzInLCAncG9zdDE0MDUxNTczMDQ0NzIzMicpIj48cHJlPjwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwNDQ3MjMyJywgJ3Bvc3QxNDA1MTU3MzA0NDcyMzInKSI+PHByZT4gICAgICAgIHRyeTo8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICA8L29sPgogICAgICAgICAgICAgIAogICAgICAgICAgICAgIDxvbCBzdGFydD0iOTcxIiBjbGFzcz0iY29udGV4dC1saW5lIj4KICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDQ0NzIzMicsICdwb3N0MTQwNTE1NzMwNDQ3MjMyJykiPjxwcmU+ICAgICAgICAgICAgcmV0dXJuIGNvbXBsZXhqc29uLmxvYWRzKHNlbGYudGV4dCwgKiprd2FyZ3MpCiAgICAgICAgICAgICAgICAgICAgICAgIF5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5ePC9wcmU+IDxzcGFuPuKApjwvc3Bhbj48L2xpPgogICAgICAgICAgICAgIDwvb2w+CiAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICA8b2wgc3RhcnQ9Jzk3MicgY2xhc3M9InBvc3QtY29udGV4dCIgaWQ9InBvc3QxNDA1MTU3MzA0NDcyMzIiPgogICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwNDQ3MjMyJywgJ3Bvc3QxNDA1MTU3MzA0NDcyMzInKSI+PHByZT4gICAgICAgIGV4Y2VwdCBKU09ORGVjb2RlRXJyb3IgYXMgZTo8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwNDQ3MjMyJywgJ3Bvc3QxNDA1MTU3MzA0NDcyMzInKSI+PHByZT4gICAgICAgICAgICAjIENhdGNoIEpTT04tcmVsYXRlZCBlcnJvcnMgYW5kIHJhaXNlIGFzIHJlcXVlc3RzLkpTT05EZWNvZGVFcnJvcjwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzA0NDcyMzInLCAncG9zdDE0MDUxNTczMDQ0NzIzMicpIj48cHJlPiAgICAgICAgICAgICMgVGhpcyBhbGlhc2VzIGpzb24uSlNPTkRlY29kZUVycm9yIGFuZCBzaW1wbGVqc29uLkpTT05EZWNvZGVFcnJvcjwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzA0NDcyMzInLCAncG9zdDE0MDUxNTczMDQ0NzIzMicpIj48cHJlPiAgICAgICAgICAgIHJhaXNlIFJlcXVlc3RzSlNPTkRlY29kZUVycm9yKGUubXNnLCBlLmRvYywgZS5wb3MpPC9wcmU+PC9saT4KICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDQ0NzIzMicsICdwb3N0MTQwNTE1NzMwNDQ3MjMyJykiPjxwcmU+PC9wcmU+PC9saT4KICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDQ0NzIzMicsICdwb3N0MTQwNTE1NzMwNDQ3MjMyJykiPjxwcmU+ICAgIEBwcm9wZXJ0eTwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgIDwvb2w+CiAgICAgICAgICAgICAgCiAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgCgogICAgICAgICAgCiAgICAgICAgICAgIAogICAgICAgICAgICAgIDxkZXRhaWxzPgogICAgICAgICAgICAgICAgPHN1bW1hcnkgY2xhc3M9ImNvbW1hbmRzIj5Mb2NhbCB2YXJzPC9zdW1tYXJ5PgogICAgICAgICAgICAKICAgICAgICAgICAgPHRhYmxlIGNsYXNzPSJ2YXJzIiBpZD0idjE0MDUxNTczMDQ0NzIzMiI+CiAgICAgICAgICAgICAgPHRoZWFkPgogICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICA8dGg+VmFyaWFibGU8L3RoPgogICAgICAgICAgICAgICAgICA8dGg+VmFsdWU8L3RoPgogICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICA8L3RoZWFkPgogICAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgPHRkPmt3YXJnczwvdGQ+CiAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJjb2RlIj48cHJlPnt9PC9wcmU+PC90ZD4KICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgPHRkPnNlbGY8L3RkPgogICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iY29kZSI+PHByZT4mbHQ7UmVzcG9uc2UgWzIwMF0mZ3Q7PC9wcmU+PC90ZD4KICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICAgIDwvdGFibGU+CiAgICAgICAgICAgIDwvZGV0YWlscz4KICAgICAgICAgIAogICAgICAgIDwvbGk+CiAgICAgIAogICAgICAgIAogICAgICAgIDxsaSBjbGFzcz0iZnJhbWUgdXNlciI+CiAgICAgICAgICAKICAgICAgICAgICAgPGNvZGUgY2xhc3M9ImZuYW1lIj4vdXNyL2Fsd2F5c2RhdGEvcHl0aG9uLzMuMTEvbGliL3B5dGhvbjMuMTEvanNvbi9fX2luaXRfXy5weTwvY29kZT4sIGxpbmUgMzQ2LCBpbiBsb2FkcwogICAgICAgICAgCgogICAgICAgICAgCiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbnRleHQiIGlkPSJjMTQwNTE1NzMwNDQ4OTYwIj4KICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgIDxvbCBzdGFydD0iMzM5IiBjbGFzcz0icHJlLWNvbnRleHQiIGlkPSJwcmUxNDA1MTU3MzA0NDg5NjAiPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDQ0ODk2MCcsICdwb3N0MTQwNTE1NzMwNDQ4OTYwJykiPjxwcmU+ICAgICAgICAgICAgcmFpc2UgVHlwZUVycm9yKGYmI3gyNzt0aGUgSlNPTiBvYmplY3QgbXVzdCBiZSBzdHIsIGJ5dGVzIG9yIGJ5dGVhcnJheSwgJiN4Mjc7PC9wcmU+PC9saT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzA0NDg5NjAnLCAncG9zdDE0MDUxNTczMDQ0ODk2MCcpIj48cHJlPiAgICAgICAgICAgICAgICAgICAgICAgICAgICBmJiN4Mjc7bm90IHtzLl9fY2xhc3NfXy5fX25hbWVfX30mI3gyNzspPC9wcmU+PC9saT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzA0NDg5NjAnLCAncG9zdDE0MDUxNTczMDQ0ODk2MCcpIj48cHJlPiAgICAgICAgcyA9IHMuZGVjb2RlKGRldGVjdF9lbmNvZGluZyhzKSwgJiN4Mjc7c3Vycm9nYXRlcGFzcyYjeDI3Oyk8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDQ0ODk2MCcsICdwb3N0MTQwNTE1NzMwNDQ4OTYwJykiPjxwcmU+PC9wcmU+PC9saT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzA0NDg5NjAnLCAncG9zdDE0MDUxNTczMDQ0ODk2MCcpIj48cHJlPiAgICBpZiAoY2xzIGlzIE5vbmUgYW5kIG9iamVjdF9ob29rIGlzIE5vbmUgYW5kPC9wcmU+PC9saT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzA0NDg5NjAnLCAncG9zdDE0MDUxNTczMDQ0ODk2MCcpIj48cHJlPiAgICAgICAgICAgIHBhcnNlX2ludCBpcyBOb25lIGFuZCBwYXJzZV9mbG9hdCBpcyBOb25lIGFuZDwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwNDQ4OTYwJywgJ3Bvc3QxNDA1MTU3MzA0NDg5NjAnKSI+PHByZT4gICAgICAgICAgICBwYXJzZV9jb25zdGFudCBpcyBOb25lIGFuZCBvYmplY3RfcGFpcnNfaG9vayBpcyBOb25lIGFuZCBub3Qga3cpOjwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgIDwvb2w+CiAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgPG9sIHN0YXJ0PSIzNDYiIGNsYXNzPSJjb250ZXh0LWxpbmUiPgogICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwNDQ4OTYwJywgJ3Bvc3QxNDA1MTU3MzA0NDg5NjAnKSI+PHByZT4gICAgICAgIHJldHVybiBfZGVmYXVsdF9kZWNvZGVyLmRlY29kZShzKQogICAgICAgICAgICAgICAgICAgIF5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5ePC9wcmU+IDxzcGFuPuKApjwvc3Bhbj48L2xpPgogICAgICAgICAgICAgIDwvb2w+CiAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICA8b2wgc3RhcnQ9JzM0NycgY2xhc3M9InBvc3QtY29udGV4dCIgaWQ9InBvc3QxNDA1MTU3MzA0NDg5NjAiPgogICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwNDQ4OTYwJywgJ3Bvc3QxNDA1MTU3MzA0NDg5NjAnKSI+PHByZT4gICAgaWYgY2xzIGlzIE5vbmU6PC9wcmU+PC9saT4KICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDQ0ODk2MCcsICdwb3N0MTQwNTE1NzMwNDQ4OTYwJykiPjxwcmU+ICAgICAgICBjbHMgPSBKU09ORGVjb2RlcjwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzA0NDg5NjAnLCAncG9zdDE0MDUxNTczMDQ0ODk2MCcpIj48cHJlPiAgICBpZiBvYmplY3RfaG9vayBpcyBub3QgTm9uZTo8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwNDQ4OTYwJywgJ3Bvc3QxNDA1MTU3MzA0NDg5NjAnKSI+PHByZT4gICAgICAgIGt3WyYjeDI3O29iamVjdF9ob29rJiN4Mjc7XSA9IG9iamVjdF9ob29rPC9wcmU+PC9saT4KICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDQ0ODk2MCcsICdwb3N0MTQwNTE1NzMwNDQ4OTYwJykiPjxwcmU+ICAgIGlmIG9iamVjdF9wYWlyc19ob29rIGlzIG5vdCBOb25lOjwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzA0NDg5NjAnLCAncG9zdDE0MDUxNTczMDQ0ODk2MCcpIj48cHJlPiAgICAgICAga3dbJiN4Mjc7b2JqZWN0X3BhaXJzX2hvb2smI3gyNztdID0gb2JqZWN0X3BhaXJzX2hvb2s8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICA8L29sPgogICAgICAgICAgICAgIAogICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgIAoKICAgICAgICAgIAogICAgICAgICAgICAKICAgICAgICAgICAgICA8ZGV0YWlscz4KICAgICAgICAgICAgICAgIDxzdW1tYXJ5IGNsYXNzPSJjb21tYW5kcyI+TG9jYWwgdmFyczwvc3VtbWFyeT4KICAgICAgICAgICAgCiAgICAgICAgICAgIDx0YWJsZSBjbGFzcz0idmFycyIgaWQ9InYxNDA1MTU3MzA0NDg5NjAiPgogICAgICAgICAgICAgIDx0aGVhZD4KICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgPHRoPlZhcmlhYmxlPC90aD4KICAgICAgICAgICAgICAgICAgPHRoPlZhbHVlPC90aD4KICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgPC90aGVhZD4KICAgICAgICAgICAgICA8dGJvZHk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgIDx0ZD5jbHM8L3RkPgogICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iY29kZSI+PHByZT5Ob25lPC9wcmU+PC90ZD4KICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgPHRkPmt3PC90ZD4KICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImNvZGUiPjxwcmU+e308L3ByZT48L3RkPgogICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICA8dGQ+b2JqZWN0X2hvb2s8L3RkPgogICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iY29kZSI+PHByZT5Ob25lPC9wcmU+PC90ZD4KICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgPHRkPm9iamVjdF9wYWlyc19ob29rPC90ZD4KICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImNvZGUiPjxwcmU+Tm9uZTwvcHJlPjwvdGQ+CiAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgIDx0ZD5wYXJzZV9jb25zdGFudDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJjb2RlIj48cHJlPk5vbmU8L3ByZT48L3RkPgogICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICA8dGQ+cGFyc2VfZmxvYXQ8L3RkPgogICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iY29kZSI+PHByZT5Ob25lPC9wcmU+PC90ZD4KICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgPHRkPnBhcnNlX2ludDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJjb2RlIj48cHJlPk5vbmU8L3ByZT48L3RkPgogICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICA8dGQ+czwvdGQ+CiAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJjb2RlIj48cHJlPigmcXVvdDsmbHQ7aHRtbCBuZy1jc3A9JiN4Mjc7bm8tdW5zYWZlLWV2YWwmI3gyNzsmZ3Q7XG4mcXVvdDsKICYjeDI3OyZsdDtoZWFkJmd0O1xuJiN4Mjc7CiAmcXVvdDsmbHQ7bWV0YSBjaGFyc2V0PSYjeDI3O3V0Zi04JiN4Mjc7Jmd0O1xuJnF1b3Q7CiAmcXVvdDsmbHQ7bWV0YSBjb250ZW50PSYjeDI3O3dpZHRoPWRldmljZS13aWR0aCxpbml0aWFsLXNjYWxlPTEuMCYjeDI3OyBuYW1lPSYjeDI3O3ZpZXdwb3J0JiN4Mjc7Jmd0O1xuJnF1b3Q7CiAmcXVvdDsmbHQ7bWV0YSBjb250ZW50PSYjeDI3O09wZW4gRm9vZCBOZXR3b3JrJiN4Mjc7IHByb3BlcnR5PSYjeDI3O29nOnRpdGxlJiN4Mjc7Jmd0O1xuJnF1b3Q7CiAmcXVvdDsmbHQ7bWV0YSBjb250ZW50PSYjeDI3O1RoZSBPcGVuIEZvb2QgTmV0d29yayBzb2Z0d2FyZSBwbGF0Zm9ybSBhbGxvd3MgZmFybWVycyB0byAmcXVvdDsKICYjeDI3O3NlbGwgcHJvZHVjZSBvbmxpbmUsIGF0IGEgcHJpY2UgdGhhdCB3b3JrcyBmb3IgdGhlbS4gSXQgaGFzIGJlZW4gYnVpbHQgJiN4Mjc7CiAmI3gyNztzcGVjaWZpY2FsbHkgZm9yIHNlbGxpbmcgZm9vZCBzbyBpdCBjYW4gaGFuZGxlIHRyaWNreSBtZWFzdXJlcyBvciBzdG9jayAmI3gyNzsKICYjeDI3O2xldmVscyB0aGF0IG9ubHkgZm9vZCBoYXMgLSBhIGRvemVuIGVnZ3MsIGEgYnVuY2ggb2YgcGFyc2xleSwgYSB3aG9sZSAmI3gyNzsKICZxdW90O2NoaWNrZW4gdGhhdCB2YXJpZXMgaW4gd2VpZ2h04oCmJiN4Mjc7IHByb3BlcnR5PSYjeDI3O29nOmRlc2NyaXB0aW9uJiN4Mjc7Jmd0O1xuJnF1b3Q7CiAmI3gyNzsmbHQ7bWV0YSAmI3gyNzsKICZxdW90O2NvbnRlbnQ9JiN4Mjc7PEhJRERFTi1PUEVOSURfQVBQX0lEPnJhaWxzL2FjdGl2ZV9zdG9yYWdlL2Jsb2JzL3JlZGlyZWN0L2V5SmZjbUZwYkhNaU9uc2liV1Z6YzJGblpTSTZJa0pCYUhCQk1URmpRV2M5UFNJc0ltVjRjQ0k2Ym5Wc2JDd2ljSFZ5SWpvaVlteHZZbDlwWkNKOWZRPT0tLTYxZDMyZGQzOGFkODk2YzFjNzEwNmM4ZDllMTFkOTc5MDdkZTdmOWYvb2ZuLXVrLnBuZyYjeDI3OyAmcXVvdDsKICZxdW90O3Byb3BlcnR5PSYjeDI3O29nOmltYWdlJiN4Mjc7Jmd0O1xuJnF1b3Q7CiAmcXVvdDsmbHQ7bWV0YSBjb250ZW50PSYjeDI3O25vaW5kZXgmI3gyNzsgbmFtZT0mI3gyNztyb2JvdHMmI3gyNzsmZ3Q7XG4mcXVvdDsKICYjeDI3OyZsdDt0aXRsZSZndDtXZWxjb21lIHRvIE9wZW4gRm9vZCBOZXR3b3JrJmx0Oy90aXRsZSZndDtcbiYjeDI3OwogJiN4Mjc7Jmx0O2xpbmsgcmVsPSZxdW90O2ljb24mcXVvdDsgdHlwZT0mcXVvdDtpbWFnZS94LWljb24mcXVvdDsgaHJlZj0mcXVvdDsvZmF2aWNvbi1zdGFnaW5nLmljbyZxdW90OyAvJmd0O1xuJiN4Mjc7CiAmI3gyNzsmbHQ7bGluayAmI3gyNzsKICZxdW90O2hyZWY9JiN4Mjc7aHR0cHM6Ly9mb250cy5nb29nbGVhcGlzLmNvbS9jc3M/ZmFtaWx5PVJvYm90bzo0MDAsMzAwaXRhbGljLDQwMGl0YWxpYywzMDAsNzAwLDcwMGl0YWxpY3xPc3dhbGQ6MzAwLDQwMCw3MDAmI3gyNzsgJnF1b3Q7CiAmcXVvdDtyZWw9JiN4Mjc7c3R5bGVzaGVldCYjeDI3OyB0eXBlPSYjeDI3O3RleHQvY3NzJiN4Mjc7Jmd0O1xuJnF1b3Q7CiAmcXVvdDsmbHQ7bGluayBhcz0mI3gyNztmb250JiN4Mjc7IGNyb3Nzb3JpZ2luPSYjeDI3O2Fub255bW91cyYjeDI3OyAmcXVvdDsKICZxdW90O2hyZWY9JiN4Mjc7L3BhY2tzL21lZGlhL2ZvbnRzL09GTi12Mi1jMjczMDM3YjNiYTNlZTgyNjRiYzY3ZTMxZWEzZDcwMS53b2ZmJiN4Mjc7ICZxdW90OwogJnF1b3Q7cmVsPSYjeDI3O3ByZWxvYWQmI3gyNzsmZ3Q7XG4mcXVvdDsKICYjeDI3OyZsdDtzY3JpcHQmZ3Q7XG4mI3gyNzsKICYjeDI3OyAgdmFyIF9tdG0gPSB3aW5kb3cuX210bSA9IHdpbmRvdy5fbXRtIHx8IFtdO1xuJiN4Mjc7CiAmcXVvdDsgIF9tdG0ucHVzaCh7JiN4Mjc7bXRtLnN0YXJ0VGltZSYjeDI3OzogKG5ldyBEYXRlKCkuZ2V0VGltZSgpKSwgJiN4Mjc7ZXZlbnQmI3gyNzs6ICZxdW90OwogJnF1b3Q7JiN4Mjc7bXRtLlN0YXJ0JiN4Mjc7fSk7XG4mcXVvdDsKICZxdW90OyAgdmFyIGQ9ZG9jdW1lbnQsIGc9ZC5jcmVhdGVFbGVtZW50KCYjeDI3O3NjcmlwdCYjeDI3OyksICZxdW90OwogJnF1b3Q7cz1kLmdldEVsZW1lbnRzQnlUYWdOYW1lKCYjeDI3O3NjcmlwdCYjeDI3OylbMF07XG4mcXVvdDsKICYjeDI3OyAgdmFyICYjeDI3OwogJiN4Mjc7dT0mcXVvdDtodHRwczovL2Nkbi5pbm5vY3JhZnQuY2xvdWQvb3BlbmZvb2RuZXR3b3JrLmlubm9jcmFmdC5jbG91ZC9jb250YWluZXJfN01BZ1d4aHIuanMmcXVvdDs7XG4mI3gyNzsKICZxdW90OyAgZy50eXBlPSYjeDI3O3RleHQvamF2YXNjcmlwdCYjeDI3OzsgZy5hc3luYz10cnVlOyBnLmRlZmVyPXRydWU7IGcuc3JjPXU7ICZxdW90OwogJiN4Mjc7cy5wYXJlbnROb2RlLmluc2VydEJlZm9yZShnLHMpO1xuJiN4Mjc7CiAmI3gyNzsmbHQ7L3NjcmlwdCZndDtcbiYjeDI3OwogJiN4Mjc7XG4mI3gyNzsKICYjeDI3OyZsdDtsaW5rIGhyZWZsYW5nPSZxdW90O2VuLWdiJnF1b3Q7ICYjeDI3OwogJiN4Mjc7aHJlZj0mcXVvdDs8SElEREVOLU9QRU5JRF9BUFBfSUQ+bG9jYWxlcy9lbl9HQiZxdW90OyZndDtcbiYjeDI3OwogJiN4Mjc7Jmx0O2xpbmsgaHJlZmxhbmc9JnF1b3Q7Y3kmcXVvdDsgJiN4Mjc7CiAmI3gyNztocmVmPSZxdW90OzxISURERU4tT1BFTklEX0FQUF9JRD5sb2NhbGVzL2N5JnF1b3Q7Jmd0O1xuJiN4Mjc7CiAmI3gyNzsmbHQ7bGluayBocmVmbGFuZz0mcXVvdDtlbiZxdW90OyAmI3gyNzsKICYjeDI3O2hyZWY9JnF1b3Q7PEhJRERFTi1PUEVOSURfQVBQX0lEPmxvY2FsZXMvZW4mcXVvdDsmZ3Q7XG4mI3gyNzsKICYjeDI3OyZsdDtsaW5rIHJlbD0mcXVvdDtzdHlsZXNoZWV0JnF1b3Q7IGhyZWY9JnF1b3Q7L3BhY2tzL2Nzcy9kYXJrc3dhcm0tNjU3ODc5MjEuY3NzJnF1b3Q7ICYjeDI3OwogJiN4Mjc7ZGF0YS10dXJiby10cmFjaz0mcXVvdDtyZWxvYWQmcXVvdDsgbWVkaWE9JnF1b3Q7c2NyZWVuJnF1b3Q7IC8mZ3Q7XG4mI3gyNzsKICYjeDI3OyZsdDtzY3JpcHQgc3JjPSZxdW90Oy9wYWNrcy9qcy9hcHBsaWNhdGlvbi1jNDQxMzNmMGQ2MTEwM2NlZjA1Zi5qcyZxdW90OyAmI3gyNzsKICYjeDI3O2RhdGEtdHVyYm8tdHJhY2s9JnF1b3Q7cmVsb2FkJnF1b3Q7Jmd0OyZsdDsvc2NyaXB0Jmd0O1xuJiN4Mjc7CiAmI3gyNztcbiYjeDI3OwogJnF1b3Q7Jmx0O3NjcmlwdCBzcmM9JiN4Mjc7Ly9kMnd5OGY3YTl1cnNubS5jbG91ZGZyb250Lm5ldC92Ny9idWdzbmFnLm1pbi5qcyYjeDI3OyZndDsmbHQ7L3NjcmlwdCZndDtcbiZxdW90OwogJiN4Mjc7Jmx0O3NjcmlwdCZndDtcbiYjeDI3OwogJiN4Mjc7ICBCdWdzbmFnLnN0YXJ0KHtcbiYjeDI3OwogJiN4Mjc7ICAgIGFwaUtleTogJnF1b3Q7ZjZjNGUyODVlY2Q3ZjU2ODM3OTNlOGQzZjRhM2RlNzcmcXVvdDssXG4mI3gyNzsKICYjeDI3OyAgICByZWxlYXNlU3RhZ2U6ICZxdW90O3N0YWdpbmcmcXVvdDtcbiYjeDI3OwogJiN4Mjc7ICB9KVxuJiN4Mjc7CiAmI3gyNzsmbHQ7L3NjcmlwdCZndDtcbiYjeDI3OwogJiN4Mjc7XG4mI3gyNzsKICZxdW90OyZsdDtzY3JpcHQgc3JjPSYjeDI3O2h0dHBzOi8vanMuc3RyaXBlLmNvbS92My8mI3gyNzsgdHlwZT0mI3gyNzt0ZXh0L2phdmFzY3JpcHQmI3gyNzsmZ3Q7Jmx0Oy9zY3JpcHQmZ3Q7XG4mcXVvdDsKICYjeDI3O1xuJiN4Mjc7CiAmI3gyNzsmbHQ7c2NyaXB0ICYjeDI3OwogJiN4Mjc7c3JjPSZxdW90Oy9hc3NldHMvZGFya3N3YXJtL2FsbC0xODMzMDM5ZWE4MWMwMjU0NjEzMDBmNTlkMmE2ZTNhYTVmNzBlNTlkZjZiNzc3ZmVlNGY2MTY4NTJjZWFkNTAwLmpzJnF1b3Q7ICYjeDI3OwogJiN4Mjc7ZGF0YS10dXJiby10cmFjaz0mcXVvdDtyZWxvYWQmcXVvdDsmZ3Q7Jmx0Oy9zY3JpcHQmZ3Q7XG4mI3gyNzsKICYjeDI3OyZsdDtzY3JpcHQgJiN4Mjc7CiAmI3gyNztzcmM9JnF1b3Q7L2Fzc2V0cy93ZWIvYWxsLTFkYTFjYzZmZTZhNTBjNjAwYTQ1NTJhMmNjMWE0NTI5NzYyM2UzZDUxN2IwZTk1MjE5ZWQ1MWNkN2Y3OTgzNDIuanMmcXVvdDsgJiN4Mjc7CiAmI3gyNztkYXRhLXR1cmJvLXRyYWNrPSZxdW90O3JlbG9hZCZxdW90OyZndDsmbHQ7L3NjcmlwdCZndDtcbiYjeDI3OwogJiN4Mjc7Jmx0O3NjcmlwdCZndDtcbiYjeDI3OwogJiN4Mjc7ICBJMThuLmRlZmF1bHRfbG9jYWxlID0gJnF1b3Q7ZW5fR0ImcXVvdDs7XG4mI3gyNzsKICYjeDI3OyAgSTE4bi5iYXNlX2xvY2FsZSA9ICZxdW90O2VuJnF1b3Q7O1xuJiN4Mjc7CiAmI3gyNzsgIEkxOG4ubG9jYWxlID0gJnF1b3Q7ZW5fR0ImcXVvdDs7XG4mI3gyNzsKICYjeDI3OyAgSTE4bi5mYWxsYmFja3MgPSB0cnVlO1xuJiN4Mjc7CiAmcXVvdDsgIG1vbWVudC5sb2NhbGUoW0kxOG4ubG9jYWxlLCAmI3gyNztlbiYjeDI3O10pO1xuJnF1b3Q7CiAmI3gyNzsmbHQ7L3NjcmlwdCZndDtcbiYjeDI3OwogJiN4Mjc7XG4mI3gyNzsKICYjeDI3OyZsdDttZXRhIG5hbWU9JnF1b3Q7Y3NyZi1wYXJhbSZxdW90OyBjb250ZW50PSZxdW90O2F1dGhlbnRpY2l0eV90b2tlbiZxdW90OyAvJmd0O1xuJiN4Mjc7CiAmI3gyNzsmbHQ7bWV0YSBuYW1lPSZxdW90O2NzcmYtdG9rZW4mcXVvdDsgJiN4Mjc7CiAmI3gyNztjb250ZW50PSZxdW90O2x4Vmc4OWFic2x0SUN0N1pQX1lNZHIzNURXeWtVb0hyeXVtSUZNN1RhamxPVWRzVVhaNlJYbHczYVFGbHdMM19iZmVKV2dEQzBMenJ0YlhRdTZNQmZ3JnF1b3Q7ICYjeDI3OwogJiN4Mjc7LyZndDtcbiYjeDI3OwogJnF1b3Q7Jmx0O21ldGEgY29udGVudD0mI3gyNztuby1jYWNoZSYjeDI3OyBuYW1lPSYjeDI3O3R1cmJvLWNhY2hlLWNvbnRyb2wmI3gyNzsmZ3Q7XG4mcXVvdDsKICYjeDI3OyZsdDttZXRhIG5hbWU9JnF1b3Q7YWN0aW9uLWNhYmxlLXVybCZxdW90OyBjb250ZW50PSZxdW90Oy9jYWJsZSZxdW90OyAvJmd0O1xuJiN4Mjc7CiAmI3gyNzsmbHQ7L2hlYWQmZ3Q7XG4mI3gyNzsKICZxdW90OyZsdDtib2R5IGJvZHktc2Nyb2xsPSYjeDI3O3RydWUmI3gyNzsgY2xhc3M9JiN4Mjc7b2ZmLWNhbnZhcyYjeDI3OyBkYXRhLXR1cmJvPSYjeDI3O2ZhbHNlJiN4Mjc7Jmd0O1xuJnF1b3Q7CiAmcXVvdDsmbHQ7ZGl2IGNsYXNzPSYjeDI3O29mZi1jYW52YXMtd3JhcCYjeDI3OyBvZmZjYW52YXMmZ3Q7XG4mcXVvdDsKICZxdW90OyZsdDtkaXYgY2xhc3M9JiN4Mjc7Zml4ZWQgb2ZmLWNhbnZhcy1maXhlZCYjeDI3OyZndDtcbiZxdW90OwogJnF1b3Q7Jmx0O2RpdiBuZy1jb250cm9sbGVyPSYjeDI3O0NhcnREcm9wZG93bkN0cmwmI3gyNzsmZ3Q7XG4mcXVvdDsKICZxdW90OyZsdDtuYXYgY2xhc3M9JiN4Mjc7dG9wLWJhciBzaG93LWZvci1sYXJnZS11cCYjeDI3OyZndDtcbiZxdW90OwogJnF1b3Q7Jmx0O3NlY3Rpb24gY2xhc3M9JiN4Mjc7dG9wLWJhci1zZWN0aW9uJiN4Mjc7Jmd0O1xuJnF1b3Q7CiAmcXVvdDsmbHQ7dWwgY2xhc3M9JiN4Mjc7bmF2LWxvZ28mI3gyNzsmZ3Q7XG4mcXVvdDsKICZxdW90OyZsdDtsaSBjbGFzcz0mI3gyNztvZm4tbG9nbyYjeDI3OyZndDtcbiZxdW90OwogJnF1b3Q7Jmx0O2EgaHJlZj0mI3gyNzsvJiN4Mjc7Jmd0O1xuJnF1b3Q7CiAmI3gyNzsmbHQ7aW1nICYjeDI3OwogJnF1b3Q7c3JjPSYjeDI3OzxISURERU4tT1BFTklEX0FQUF9JRD5yYWlscy9hY3RpdmVfc3RvcmFnZS9ibG9icy9yZWRpcmVjdC9leUpmY21GcGJITWlPbnNpYldWemMyRm5aU0k2SWtKQmFIQkJNVEZqUVdjOVBTSXNJbVY0Y0NJNmJuVnNiQ3dpY0hWeUlqb2lZbXh2WWw5cFpDSjlmUT09LS02MWQzMmRkMzhhZDg5NmMxYzcxMDZjOGQ5ZTExZDk3OTA3ZGU3ZjlmL29mbi11ay5wbmcmI3gyNzsmZ3Q7XG4mcXVvdDsKICYjeDI3OyZsdDsvYSZndDtcbiYjeDI3OwogJiN4Mjc7Jmx0Oy9saSZndDtcbiYjeDI3OwogJnF1b3Q7Jmx0O2xpIGNsYXNzPSYjeDI3O3Bvd2VyZWQtYnkmI3gyNzsmZ3Q7XG4mcXVvdDsKICZxdW90OyZsdDtpbWcgc3JjPSYjeDI3Oy9mYeKApiAmbHQ7dHJpbW1lZCAzMDEzNSBieXRlcyBzdHJpbmcmZ3Q7PC9wcmU+PC90ZD4KICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICAgIDwvdGFibGU+CiAgICAgICAgICAgIDwvZGV0YWlscz4KICAgICAgICAgIAogICAgICAgIDwvbGk+CiAgICAgIAogICAgICAgIAogICAgICAgIDxsaSBjbGFzcz0iZnJhbWUgdXNlciI+CiAgICAgICAgICAKICAgICAgICAgICAgPGNvZGUgY2xhc3M9ImZuYW1lIj4vdXNyL2Fsd2F5c2RhdGEvcHl0aG9uLzMuMTEvbGliL3B5dGhvbjMuMTEvanNvbi9kZWNvZGVyLnB5PC9jb2RlPiwgbGluZSAzMzcsIGluIGRlY29kZQogICAgICAgICAgCgogICAgICAgICAgCiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbnRleHQiIGlkPSJjMTQwNTE1NzMwNDQzNTIwIj4KICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgIDxvbCBzdGFydD0iMzMwIiBjbGFzcz0icHJlLWNvbnRleHQiIGlkPSJwcmUxNDA1MTU3MzA0NDM1MjAiPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDQ0MzUyMCcsICdwb3N0MTQwNTE1NzMwNDQzNTIwJykiPjxwcmU+PC9wcmU+PC9saT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzA0NDM1MjAnLCAncG9zdDE0MDUxNTczMDQ0MzUyMCcpIj48cHJlPjwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwNDQzNTIwJywgJ3Bvc3QxNDA1MTU3MzA0NDM1MjAnKSI+PHByZT4gICAgZGVmIGRlY29kZShzZWxmLCBzLCBfdz1XSElURVNQQUNFLm1hdGNoKTo8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDQ0MzUyMCcsICdwb3N0MTQwNTE1NzMwNDQzNTIwJykiPjxwcmU+ICAgICAgICAmcXVvdDsmcXVvdDsmcXVvdDtSZXR1cm4gdGhlIFB5dGhvbiByZXByZXNlbnRhdGlvbiBvZiBgYHNgYCAoYSBgYHN0cmBgIGluc3RhbmNlPC9wcmU+PC9saT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzA0NDM1MjAnLCAncG9zdDE0MDUxNTczMDQ0MzUyMCcpIj48cHJlPiAgICAgICAgY29udGFpbmluZyBhIEpTT04gZG9jdW1lbnQpLjwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwNDQzNTIwJywgJ3Bvc3QxNDA1MTU3MzA0NDM1MjAnKSI+PHByZT48L3ByZT48L2xpPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDQ0MzUyMCcsICdwb3N0MTQwNTE1NzMwNDQzNTIwJykiPjxwcmU+ICAgICAgICAmcXVvdDsmcXVvdDsmcXVvdDs8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICA8L29sPgogICAgICAgICAgICAgIAogICAgICAgICAgICAgIDxvbCBzdGFydD0iMzM3IiBjbGFzcz0iY29udGV4dC1saW5lIj4KICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDQ0MzUyMCcsICdwb3N0MTQwNTE1NzMwNDQzNTIwJykiPjxwcmU+ICAgICAgICBvYmosIGVuZCA9IHNlbGYucmF3X2RlY29kZShzLCBpZHg9X3cocywgMCkuZW5kKCkpCiAgICAgICAgICAgICAgICAgICAgICAgIF5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5ePC9wcmU+IDxzcGFuPuKApjwvc3Bhbj48L2xpPgogICAgICAgICAgICAgIDwvb2w+CiAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICA8b2wgc3RhcnQ9JzMzOCcgY2xhc3M9InBvc3QtY29udGV4dCIgaWQ9InBvc3QxNDA1MTU3MzA0NDM1MjAiPgogICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwNDQzNTIwJywgJ3Bvc3QxNDA1MTU3MzA0NDM1MjAnKSI+PHByZT4gICAgICAgIGVuZCA9IF93KHMsIGVuZCkuZW5kKCk8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwNDQzNTIwJywgJ3Bvc3QxNDA1MTU3MzA0NDM1MjAnKSI+PHByZT4gICAgICAgIGlmIGVuZCAhPSBsZW4ocyk6PC9wcmU+PC9saT4KICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDQ0MzUyMCcsICdwb3N0MTQwNTE1NzMwNDQzNTIwJykiPjxwcmU+ICAgICAgICAgICAgcmFpc2UgSlNPTkRlY29kZUVycm9yKCZxdW90O0V4dHJhIGRhdGEmcXVvdDssIHMsIGVuZCk8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwNDQzNTIwJywgJ3Bvc3QxNDA1MTU3MzA0NDM1MjAnKSI+PHByZT4gICAgICAgIHJldHVybiBvYmo8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwNDQzNTIwJywgJ3Bvc3QxNDA1MTU3MzA0NDM1MjAnKSI+PHByZT48L3ByZT48L2xpPgogICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwNDQzNTIwJywgJ3Bvc3QxNDA1MTU3MzA0NDM1MjAnKSI+PHByZT4gICAgZGVmIHJhd19kZWNvZGUoc2VsZiwgcywgaWR4PTApOjwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgIDwvb2w+CiAgICAgICAgICAgICAgCiAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgCgogICAgICAgICAgCiAgICAgICAgICAgIAogICAgICAgICAgICAgIDxkZXRhaWxzPgogICAgICAgICAgICAgICAgPHN1bW1hcnkgY2xhc3M9ImNvbW1hbmRzIj5Mb2NhbCB2YXJzPC9zdW1tYXJ5PgogICAgICAgICAgICAKICAgICAgICAgICAgPHRhYmxlIGNsYXNzPSJ2YXJzIiBpZD0idjE0MDUxNTczMDQ0MzUyMCI+CiAgICAgICAgICAgICAgPHRoZWFkPgogICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICA8dGg+VmFyaWFibGU8L3RoPgogICAgICAgICAgICAgICAgICA8dGg+VmFsdWU8L3RoPgogICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICA8L3RoZWFkPgogICAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgPHRkPl93PC90ZD4KICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImNvZGUiPjxwcmU+Jmx0O2J1aWx0LWluIG1ldGhvZCBtYXRjaCBvZiByZS5QYXR0ZXJuIG9iamVjdCBhdCAweDdmY2M2NzJhZWRjMCZndDs8L3ByZT48L3RkPgogICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICA8dGQ+czwvdGQ+CiAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJjb2RlIj48cHJlPigmcXVvdDsmbHQ7aHRtbCBuZy1jc3A9JiN4Mjc7bm8tdW5zYWZlLWV2YWwmI3gyNzsmZ3Q7XG4mcXVvdDsKICYjeDI3OyZsdDtoZWFkJmd0O1xuJiN4Mjc7CiAmcXVvdDsmbHQ7bWV0YSBjaGFyc2V0PSYjeDI3O3V0Zi04JiN4Mjc7Jmd0O1xuJnF1b3Q7CiAmcXVvdDsmbHQ7bWV0YSBjb250ZW50PSYjeDI3O3dpZHRoPWRldmljZS13aWR0aCxpbml0aWFsLXNjYWxlPTEuMCYjeDI3OyBuYW1lPSYjeDI3O3ZpZXdwb3J0JiN4Mjc7Jmd0O1xuJnF1b3Q7CiAmcXVvdDsmbHQ7bWV0YSBjb250ZW50PSYjeDI3O09wZW4gRm9vZCBOZXR3b3JrJiN4Mjc7IHByb3BlcnR5PSYjeDI3O29nOnRpdGxlJiN4Mjc7Jmd0O1xuJnF1b3Q7CiAmcXVvdDsmbHQ7bWV0YSBjb250ZW50PSYjeDI3O1RoZSBPcGVuIEZvb2QgTmV0d29yayBzb2Z0d2FyZSBwbGF0Zm9ybSBhbGxvd3MgZmFybWVycyB0byAmcXVvdDsKICYjeDI3O3NlbGwgcHJvZHVjZSBvbmxpbmUsIGF0IGEgcHJpY2UgdGhhdCB3b3JrcyBmb3IgdGhlbS4gSXQgaGFzIGJlZW4gYnVpbHQgJiN4Mjc7CiAmI3gyNztzcGVjaWZpY2FsbHkgZm9yIHNlbGxpbmcgZm9vZCBzbyBpdCBjYW4gaGFuZGxlIHRyaWNreSBtZWFzdXJlcyBvciBzdG9jayAmI3gyNzsKICYjeDI3O2xldmVscyB0aGF0IG9ubHkgZm9vZCBoYXMgLSBhIGRvemVuIGVnZ3MsIGEgYnVuY2ggb2YgcGFyc2xleSwgYSB3aG9sZSAmI3gyNzsKICZxdW90O2NoaWNrZW4gdGhhdCB2YXJpZXMgaW4gd2VpZ2h04oCmJiN4Mjc7IHByb3BlcnR5PSYjeDI3O29nOmRlc2NyaXB0aW9uJiN4Mjc7Jmd0O1xuJnF1b3Q7CiAmI3gyNzsmbHQ7bWV0YSAmI3gyNzsKICZxdW90O2NvbnRlbnQ9JiN4Mjc7PEhJRERFTi1PUEVOSURfQVBQX0lEPnJhaWxzL2FjdGl2ZV9zdG9yYWdlL2Jsb2JzL3JlZGlyZWN0L2V5SmZjbUZwYkhNaU9uc2liV1Z6YzJGblpTSTZJa0pCYUhCQk1URmpRV2M5UFNJc0ltVjRjQ0k2Ym5Wc2JDd2ljSFZ5SWpvaVlteHZZbDlwWkNKOWZRPT0tLTYxZDMyZGQzOGFkODk2YzFjNzEwNmM4ZDllMTFkOTc5MDdkZTdmOWYvb2ZuLXVrLnBuZyYjeDI3OyAmcXVvdDsKICZxdW90O3Byb3BlcnR5PSYjeDI3O29nOmltYWdlJiN4Mjc7Jmd0O1xuJnF1b3Q7CiAmcXVvdDsmbHQ7bWV0YSBjb250ZW50PSYjeDI3O25vaW5kZXgmI3gyNzsgbmFtZT0mI3gyNztyb2JvdHMmI3gyNzsmZ3Q7XG4mcXVvdDsKICYjeDI3OyZsdDt0aXRsZSZndDtXZWxjb21lIHRvIE9wZW4gRm9vZCBOZXR3b3JrJmx0Oy90aXRsZSZndDtcbiYjeDI3OwogJiN4Mjc7Jmx0O2xpbmsgcmVsPSZxdW90O2ljb24mcXVvdDsgdHlwZT0mcXVvdDtpbWFnZS94LWljb24mcXVvdDsgaHJlZj0mcXVvdDsvZmF2aWNvbi1zdGFnaW5nLmljbyZxdW90OyAvJmd0O1xuJiN4Mjc7CiAmI3gyNzsmbHQ7bGluayAmI3gyNzsKICZxdW90O2hyZWY9JiN4Mjc7aHR0cHM6Ly9mb250cy5nb29nbGVhcGlzLmNvbS9jc3M/ZmFtaWx5PVJvYm90bzo0MDAsMzAwaXRhbGljLDQwMGl0YWxpYywzMDAsNzAwLDcwMGl0YWxpY3xPc3dhbGQ6MzAwLDQwMCw3MDAmI3gyNzsgJnF1b3Q7CiAmcXVvdDtyZWw9JiN4Mjc7c3R5bGVzaGVldCYjeDI3OyB0eXBlPSYjeDI3O3RleHQvY3NzJiN4Mjc7Jmd0O1xuJnF1b3Q7CiAmcXVvdDsmbHQ7bGluayBhcz0mI3gyNztmb250JiN4Mjc7IGNyb3Nzb3JpZ2luPSYjeDI3O2Fub255bW91cyYjeDI3OyAmcXVvdDsKICZxdW90O2hyZWY9JiN4Mjc7L3BhY2tzL21lZGlhL2ZvbnRzL09GTi12Mi1jMjczMDM3YjNiYTNlZTgyNjRiYzY3ZTMxZWEzZDcwMS53b2ZmJiN4Mjc7ICZxdW90OwogJnF1b3Q7cmVsPSYjeDI3O3ByZWxvYWQmI3gyNzsmZ3Q7XG4mcXVvdDsKICYjeDI3OyZsdDtzY3JpcHQmZ3Q7XG4mI3gyNzsKICYjeDI3OyAgdmFyIF9tdG0gPSB3aW5kb3cuX210bSA9IHdpbmRvdy5fbXRtIHx8IFtdO1xuJiN4Mjc7CiAmcXVvdDsgIF9tdG0ucHVzaCh7JiN4Mjc7bXRtLnN0YXJ0VGltZSYjeDI3OzogKG5ldyBEYXRlKCkuZ2V0VGltZSgpKSwgJiN4Mjc7ZXZlbnQmI3gyNzs6ICZxdW90OwogJnF1b3Q7JiN4Mjc7bXRtLlN0YXJ0JiN4Mjc7fSk7XG4mcXVvdDsKICZxdW90OyAgdmFyIGQ9ZG9jdW1lbnQsIGc9ZC5jcmVhdGVFbGVtZW50KCYjeDI3O3NjcmlwdCYjeDI3OyksICZxdW90OwogJnF1b3Q7cz1kLmdldEVsZW1lbnRzQnlUYWdOYW1lKCYjeDI3O3NjcmlwdCYjeDI3OylbMF07XG4mcXVvdDsKICYjeDI3OyAgdmFyICYjeDI3OwogJiN4Mjc7dT0mcXVvdDtodHRwczovL2Nkbi5pbm5vY3JhZnQuY2xvdWQvb3BlbmZvb2RuZXR3b3JrLmlubm9jcmFmdC5jbG91ZC9jb250YWluZXJfN01BZ1d4aHIuanMmcXVvdDs7XG4mI3gyNzsKICZxdW90OyAgZy50eXBlPSYjeDI3O3RleHQvamF2YXNjcmlwdCYjeDI3OzsgZy5hc3luYz10cnVlOyBnLmRlZmVyPXRydWU7IGcuc3JjPXU7ICZxdW90OwogJiN4Mjc7cy5wYXJlbnROb2RlLmluc2VydEJlZm9yZShnLHMpO1xuJiN4Mjc7CiAmI3gyNzsmbHQ7L3NjcmlwdCZndDtcbiYjeDI3OwogJiN4Mjc7XG4mI3gyNzsKICYjeDI3OyZsdDtsaW5rIGhyZWZsYW5nPSZxdW90O2VuLWdiJnF1b3Q7ICYjeDI3OwogJiN4Mjc7aHJlZj0mcXVvdDs8SElEREVOLU9QRU5JRF9BUFBfSUQ+bG9jYWxlcy9lbl9HQiZxdW90OyZndDtcbiYjeDI3OwogJiN4Mjc7Jmx0O2xpbmsgaHJlZmxhbmc9JnF1b3Q7Y3kmcXVvdDsgJiN4Mjc7CiAmI3gyNztocmVmPSZxdW90OzxISURERU4tT1BFTklEX0FQUF9JRD5sb2NhbGVzL2N5JnF1b3Q7Jmd0O1xuJiN4Mjc7CiAmI3gyNzsmbHQ7bGluayBocmVmbGFuZz0mcXVvdDtlbiZxdW90OyAmI3gyNzsKICYjeDI3O2hyZWY9JnF1b3Q7PEhJRERFTi1PUEVOSURfQVBQX0lEPmxvY2FsZXMvZW4mcXVvdDsmZ3Q7XG4mI3gyNzsKICYjeDI3OyZsdDtsaW5rIHJlbD0mcXVvdDtzdHlsZXNoZWV0JnF1b3Q7IGhyZWY9JnF1b3Q7L3BhY2tzL2Nzcy9kYXJrc3dhcm0tNjU3ODc5MjEuY3NzJnF1b3Q7ICYjeDI3OwogJiN4Mjc7ZGF0YS10dXJiby10cmFjaz0mcXVvdDtyZWxvYWQmcXVvdDsgbWVkaWE9JnF1b3Q7c2NyZWVuJnF1b3Q7IC8mZ3Q7XG4mI3gyNzsKICYjeDI3OyZsdDtzY3JpcHQgc3JjPSZxdW90Oy9wYWNrcy9qcy9hcHBsaWNhdGlvbi1jNDQxMzNmMGQ2MTEwM2NlZjA1Zi5qcyZxdW90OyAmI3gyNzsKICYjeDI3O2RhdGEtdHVyYm8tdHJhY2s9JnF1b3Q7cmVsb2FkJnF1b3Q7Jmd0OyZsdDsvc2NyaXB0Jmd0O1xuJiN4Mjc7CiAmI3gyNztcbiYjeDI3OwogJnF1b3Q7Jmx0O3NjcmlwdCBzcmM9JiN4Mjc7Ly9kMnd5OGY3YTl1cnNubS5jbG91ZGZyb250Lm5ldC92Ny9idWdzbmFnLm1pbi5qcyYjeDI3OyZndDsmbHQ7L3NjcmlwdCZndDtcbiZxdW90OwogJiN4Mjc7Jmx0O3NjcmlwdCZndDtcbiYjeDI3OwogJiN4Mjc7ICBCdWdzbmFnLnN0YXJ0KHtcbiYjeDI3OwogJiN4Mjc7ICAgIGFwaUtleTogJnF1b3Q7ZjZjNGUyODVlY2Q3ZjU2ODM3OTNlOGQzZjRhM2RlNzcmcXVvdDssXG4mI3gyNzsKICYjeDI3OyAgICByZWxlYXNlU3RhZ2U6ICZxdW90O3N0YWdpbmcmcXVvdDtcbiYjeDI3OwogJiN4Mjc7ICB9KVxuJiN4Mjc7CiAmI3gyNzsmbHQ7L3NjcmlwdCZndDtcbiYjeDI3OwogJiN4Mjc7XG4mI3gyNzsKICZxdW90OyZsdDtzY3JpcHQgc3JjPSYjeDI3O2h0dHBzOi8vanMuc3RyaXBlLmNvbS92My8mI3gyNzsgdHlwZT0mI3gyNzt0ZXh0L2phdmFzY3JpcHQmI3gyNzsmZ3Q7Jmx0Oy9zY3JpcHQmZ3Q7XG4mcXVvdDsKICYjeDI3O1xuJiN4Mjc7CiAmI3gyNzsmbHQ7c2NyaXB0ICYjeDI3OwogJiN4Mjc7c3JjPSZxdW90Oy9hc3NldHMvZGFya3N3YXJtL2FsbC0xODMzMDM5ZWE4MWMwMjU0NjEzMDBmNTlkMmE2ZTNhYTVmNzBlNTlkZjZiNzc3ZmVlNGY2MTY4NTJjZWFkNTAwLmpzJnF1b3Q7ICYjeDI3OwogJiN4Mjc7ZGF0YS10dXJiby10cmFjaz0mcXVvdDtyZWxvYWQmcXVvdDsmZ3Q7Jmx0Oy9zY3JpcHQmZ3Q7XG4mI3gyNzsKICYjeDI3OyZsdDtzY3JpcHQgJiN4Mjc7CiAmI3gyNztzcmM9JnF1b3Q7L2Fzc2V0cy93ZWIvYWxsLTFkYTFjYzZmZTZhNTBjNjAwYTQ1NTJhMmNjMWE0NTI5NzYyM2UzZDUxN2IwZTk1MjE5ZWQ1MWNkN2Y3OTgzNDIuanMmcXVvdDsgJiN4Mjc7CiAmI3gyNztkYXRhLXR1cmJvLXRyYWNrPSZxdW90O3JlbG9hZCZxdW90OyZndDsmbHQ7L3NjcmlwdCZndDtcbiYjeDI3OwogJiN4Mjc7Jmx0O3NjcmlwdCZndDtcbiYjeDI3OwogJiN4Mjc7ICBJMThuLmRlZmF1bHRfbG9jYWxlID0gJnF1b3Q7ZW5fR0ImcXVvdDs7XG4mI3gyNzsKICYjeDI3OyAgSTE4bi5iYXNlX2xvY2FsZSA9ICZxdW90O2VuJnF1b3Q7O1xuJiN4Mjc7CiAmI3gyNzsgIEkxOG4ubG9jYWxlID0gJnF1b3Q7ZW5fR0ImcXVvdDs7XG4mI3gyNzsKICYjeDI3OyAgSTE4bi5mYWxsYmFja3MgPSB0cnVlO1xuJiN4Mjc7CiAmcXVvdDsgIG1vbWVudC5sb2NhbGUoW0kxOG4ubG9jYWxlLCAmI3gyNztlbiYjeDI3O10pO1xuJnF1b3Q7CiAmI3gyNzsmbHQ7L3NjcmlwdCZndDtcbiYjeDI3OwogJiN4Mjc7XG4mI3gyNzsKICYjeDI3OyZsdDttZXRhIG5hbWU9JnF1b3Q7Y3NyZi1wYXJhbSZxdW90OyBjb250ZW50PSZxdW90O2F1dGhlbnRpY2l0eV90b2tlbiZxdW90OyAvJmd0O1xuJiN4Mjc7CiAmI3gyNzsmbHQ7bWV0YSBuYW1lPSZxdW90O2NzcmYtdG9rZW4mcXVvdDsgJiN4Mjc7CiAmI3gyNztjb250ZW50PSZxdW90O2x4Vmc4OWFic2x0SUN0N1pQX1lNZHIzNURXeWtVb0hyeXVtSUZNN1RhamxPVWRzVVhaNlJYbHczYVFGbHdMM19iZmVKV2dEQzBMenJ0YlhRdTZNQmZ3JnF1b3Q7ICYjeDI3OwogJiN4Mjc7LyZndDtcbiYjeDI3OwogJnF1b3Q7Jmx0O21ldGEgY29udGVudD0mI3gyNztuby1jYWNoZSYjeDI3OyBuYW1lPSYjeDI3O3R1cmJvLWNhY2hlLWNvbnRyb2wmI3gyNzsmZ3Q7XG4mcXVvdDsKICYjeDI3OyZsdDttZXRhIG5hbWU9JnF1b3Q7YWN0aW9uLWNhYmxlLXVybCZxdW90OyBjb250ZW50PSZxdW90Oy9jYWJsZSZxdW90OyAvJmd0O1xuJiN4Mjc7CiAmI3gyNzsmbHQ7L2hlYWQmZ3Q7XG4mI3gyNzsKICZxdW90OyZsdDtib2R5IGJvZHktc2Nyb2xsPSYjeDI3O3RydWUmI3gyNzsgY2xhc3M9JiN4Mjc7b2ZmLWNhbnZhcyYjeDI3OyBkYXRhLXR1cmJvPSYjeDI3O2ZhbHNlJiN4Mjc7Jmd0O1xuJnF1b3Q7CiAmcXVvdDsmbHQ7ZGl2IGNsYXNzPSYjeDI3O29mZi1jYW52YXMtd3JhcCYjeDI3OyBvZmZjYW52YXMmZ3Q7XG4mcXVvdDsKICZxdW90OyZsdDtkaXYgY2xhc3M9JiN4Mjc7Zml4ZWQgb2ZmLWNhbnZhcy1maXhlZCYjeDI3OyZndDtcbiZxdW90OwogJnF1b3Q7Jmx0O2RpdiBuZy1jb250cm9sbGVyPSYjeDI3O0NhcnREcm9wZG93bkN0cmwmI3gyNzsmZ3Q7XG4mcXVvdDsKICZxdW90OyZsdDtuYXYgY2xhc3M9JiN4Mjc7dG9wLWJhciBzaG93LWZvci1sYXJnZS11cCYjeDI3OyZndDtcbiZxdW90OwogJnF1b3Q7Jmx0O3NlY3Rpb24gY2xhc3M9JiN4Mjc7dG9wLWJhci1zZWN0aW9uJiN4Mjc7Jmd0O1xuJnF1b3Q7CiAmcXVvdDsmbHQ7dWwgY2xhc3M9JiN4Mjc7bmF2LWxvZ28mI3gyNzsmZ3Q7XG4mcXVvdDsKICZxdW90OyZsdDtsaSBjbGFzcz0mI3gyNztvZm4tbG9nbyYjeDI3OyZndDtcbiZxdW90OwogJnF1b3Q7Jmx0O2EgaHJlZj0mI3gyNzsvJiN4Mjc7Jmd0O1xuJnF1b3Q7CiAmI3gyNzsmbHQ7aW1nICYjeDI3OwogJnF1b3Q7c3JjPSYjeDI3OzxISURERU4tT1BFTklEX0FQUF9JRD5yYWlscy9hY3RpdmVfc3RvcmFnZS9ibG9icy9yZWRpcmVjdC9leUpmY21GcGJITWlPbnNpYldWemMyRm5aU0k2SWtKQmFIQkJNVEZqUVdjOVBTSXNJbVY0Y0NJNmJuVnNiQ3dpY0hWeUlqb2lZbXh2WWw5cFpDSjlmUT09LS02MWQzMmRkMzhhZDg5NmMxYzcxMDZjOGQ5ZTExZDk3OTA3ZGU3ZjlmL29mbi11ay5wbmcmI3gyNzsmZ3Q7XG4mcXVvdDsKICYjeDI3OyZsdDsvYSZndDtcbiYjeDI3OwogJiN4Mjc7Jmx0Oy9saSZndDtcbiYjeDI3OwogJnF1b3Q7Jmx0O2xpIGNsYXNzPSYjeDI3O3Bvd2VyZWQtYnkmI3gyNzsmZ3Q7XG4mcXVvdDsKICZxdW90OyZsdDtpbWcgc3JjPSYjeDI3Oy9mYeKApiAmbHQ7dHJpbW1lZCAzMDEzNSBieXRlcyBzdHJpbmcmZ3Q7PC9wcmU+PC90ZD4KICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgPHRkPnNlbGY8L3RkPgogICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iY29kZSI+PHByZT4mbHQ7anNvbi5kZWNvZGVyLkpTT05EZWNvZGVyIG9iamVjdCBhdCAweDdmY2M2NzJjZGY1MCZndDs8L3ByZT48L3RkPgogICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgPC90Ym9keT4KICAgICAgICAgICAgPC90YWJsZT4KICAgICAgICAgICAgPC9kZXRhaWxzPgogICAgICAgICAgCiAgICAgICAgPC9saT4KICAgICAgCiAgICAgICAgCiAgICAgICAgPGxpIGNsYXNzPSJmcmFtZSB1c2VyIj4KICAgICAgICAgIAogICAgICAgICAgICA8Y29kZSBjbGFzcz0iZm5hbWUiPi91c3IvYWx3YXlzZGF0YS9weXRob24vMy4xMS9saWIvcHl0aG9uMy4xMS9qc29uL2RlY29kZXIucHk8L2NvZGU+LCBsaW5lIDM1NSwgaW4gcmF3X2RlY29kZQogICAgICAgICAgCgogICAgICAgICAgCiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbnRleHQiIGlkPSJjMTQwNTE1NzI4NDc0ODgwIj4KICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgIDxvbCBzdGFydD0iMzQ4IiBjbGFzcz0icHJlLWNvbnRleHQiIGlkPSJwcmUxNDA1MTU3Mjg0NzQ4ODAiPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTcyODQ3NDg4MCcsICdwb3N0MTQwNTE1NzI4NDc0ODgwJykiPjxwcmU+ICAgICAgICBUaGlzIGNhbiBiZSB1c2VkIHRvIGRlY29kZSBhIEpTT04gZG9jdW1lbnQgZnJvbSBhIHN0cmluZyB0aGF0IG1heTwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzI4NDc0ODgwJywgJ3Bvc3QxNDA1MTU3Mjg0NzQ4ODAnKSI+PHByZT4gICAgICAgIGhhdmUgZXh0cmFuZW91cyBkYXRhIGF0IHRoZSBlbmQuPC9wcmU+PC9saT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3Mjg0NzQ4ODAnLCAncG9zdDE0MDUxNTcyODQ3NDg4MCcpIj48cHJlPjwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzI4NDc0ODgwJywgJ3Bvc3QxNDA1MTU3Mjg0NzQ4ODAnKSI+PHByZT4gICAgICAgICZxdW90OyZxdW90OyZxdW90OzwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzI4NDc0ODgwJywgJ3Bvc3QxNDA1MTU3Mjg0NzQ4ODAnKSI+PHByZT4gICAgICAgIHRyeTo8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTcyODQ3NDg4MCcsICdwb3N0MTQwNTE1NzI4NDc0ODgwJykiPjxwcmU+ICAgICAgICAgICAgb2JqLCBlbmQgPSBzZWxmLnNjYW5fb25jZShzLCBpZHgpPC9wcmU+PC9saT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3Mjg0NzQ4ODAnLCAncG9zdDE0MDUxNTcyODQ3NDg4MCcpIj48cHJlPiAgICAgICAgZXhjZXB0IFN0b3BJdGVyYXRpb24gYXMgZXJyOjwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgIDwvb2w+CiAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgPG9sIHN0YXJ0PSIzNTUiIGNsYXNzPSJjb250ZXh0LWxpbmUiPgogICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzI4NDc0ODgwJywgJ3Bvc3QxNDA1MTU3Mjg0NzQ4ODAnKSI+PHByZT4gICAgICAgICAgICByYWlzZSBKU09ORGVjb2RlRXJyb3IoJnF1b3Q7RXhwZWN0aW5nIHZhbHVlJnF1b3Q7LCBzLCBlcnIudmFsdWUpIGZyb20gTm9uZQogICAgICAgICAgICAgICAgIF5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl48L3ByZT4gPHNwYW4+4oCmPC9zcGFuPjwvbGk+CiAgICAgICAgICAgICAgPC9vbD4KICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgIDxvbCBzdGFydD0nMzU2JyBjbGFzcz0icG9zdC1jb250ZXh0IiBpZD0icG9zdDE0MDUxNTcyODQ3NDg4MCI+CiAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3Mjg0NzQ4ODAnLCAncG9zdDE0MDUxNTcyODQ3NDg4MCcpIj48cHJlPiAgICAgICAgcmV0dXJuIG9iaiwgZW5kPC9wcmU+PC9saT4KICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgPC9vbD4KICAgICAgICAgICAgICAKICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAKCiAgICAgICAgICAKICAgICAgICAgICAgCiAgICAgICAgICAgICAgPGRldGFpbHM+CiAgICAgICAgICAgICAgICA8c3VtbWFyeSBjbGFzcz0iY29tbWFuZHMiPkxvY2FsIHZhcnM8L3N1bW1hcnk+CiAgICAgICAgICAgIAogICAgICAgICAgICA8dGFibGUgY2xhc3M9InZhcnMiIGlkPSJ2MTQwNTE1NzI4NDc0ODgwIj4KICAgICAgICAgICAgICA8dGhlYWQ+CiAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgIDx0aD5WYXJpYWJsZTwvdGg+CiAgICAgICAgICAgICAgICAgIDx0aD5WYWx1ZTwvdGg+CiAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgIDwvdGhlYWQ+CiAgICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICA8dGQ+aWR4PC90ZD4KICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImNvZGUiPjxwcmU+MDwvcHJlPjwvdGQ+CiAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgIDx0ZD5zPC90ZD4KICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImNvZGUiPjxwcmU+KCZxdW90OyZsdDtodG1sIG5nLWNzcD0mI3gyNztuby11bnNhZmUtZXZhbCYjeDI3OyZndDtcbiZxdW90OwogJiN4Mjc7Jmx0O2hlYWQmZ3Q7XG4mI3gyNzsKICZxdW90OyZsdDttZXRhIGNoYXJzZXQ9JiN4Mjc7dXRmLTgmI3gyNzsmZ3Q7XG4mcXVvdDsKICZxdW90OyZsdDttZXRhIGNvbnRlbnQ9JiN4Mjc7d2lkdGg9ZGV2aWNlLXdpZHRoLGluaXRpYWwtc2NhbGU9MS4wJiN4Mjc7IG5hbWU9JiN4Mjc7dmlld3BvcnQmI3gyNzsmZ3Q7XG4mcXVvdDsKICZxdW90OyZsdDttZXRhIGNvbnRlbnQ9JiN4Mjc7T3BlbiBGb29kIE5ldHdvcmsmI3gyNzsgcHJvcGVydHk9JiN4Mjc7b2c6dGl0bGUmI3gyNzsmZ3Q7XG4mcXVvdDsKICZxdW90OyZsdDttZXRhIGNvbnRlbnQ9JiN4Mjc7VGhlIE9wZW4gRm9vZCBOZXR3b3JrIHNvZnR3YXJlIHBsYXRmb3JtIGFsbG93cyBmYXJtZXJzIHRvICZxdW90OwogJiN4Mjc7c2VsbCBwcm9kdWNlIG9ubGluZSwgYXQgYSBwcmljZSB0aGF0IHdvcmtzIGZvciB0aGVtLiBJdCBoYXMgYmVlbiBidWlsdCAmI3gyNzsKICYjeDI3O3NwZWNpZmljYWxseSBmb3Igc2VsbGluZyBmb29kIHNvIGl0IGNhbiBoYW5kbGUgdHJpY2t5IG1lYXN1cmVzIG9yIHN0b2NrICYjeDI3OwogJiN4Mjc7bGV2ZWxzIHRoYXQgb25seSBmb29kIGhhcyAtIGEgZG96ZW4gZWdncywgYSBidW5jaCBvZiBwYXJzbGV5LCBhIHdob2xlICYjeDI3OwogJnF1b3Q7Y2hpY2tlbiB0aGF0IHZhcmllcyBpbiB3ZWlnaHTigKYmI3gyNzsgcHJvcGVydHk9JiN4Mjc7b2c6ZGVzY3JpcHRpb24mI3gyNzsmZ3Q7XG4mcXVvdDsKICYjeDI3OyZsdDttZXRhICYjeDI3OwogJnF1b3Q7Y29udGVudD0mI3gyNzs8SElEREVOLU9QRU5JRF9BUFBfSUQ+cmFpbHMvYWN0aXZlX3N0b3JhZ2UvYmxvYnMvcmVkaXJlY3QvZXlKZmNtRnBiSE1pT25zaWJXVnpjMkZuWlNJNklrSkJhSEJCTVRGalFXYzlQU0lzSW1WNGNDSTZiblZzYkN3aWNIVnlJam9pWW14dllsOXBaQ0o5ZlE9PS0tNjFkMzJkZDM4YWQ4OTZjMWM3MTA2YzhkOWUxMWQ5NzkwN2RlN2Y5Zi9vZm4tdWsucG5nJiN4Mjc7ICZxdW90OwogJnF1b3Q7cHJvcGVydHk9JiN4Mjc7b2c6aW1hZ2UmI3gyNzsmZ3Q7XG4mcXVvdDsKICZxdW90OyZsdDttZXRhIGNvbnRlbnQ9JiN4Mjc7bm9pbmRleCYjeDI3OyBuYW1lPSYjeDI3O3JvYm90cyYjeDI3OyZndDtcbiZxdW90OwogJiN4Mjc7Jmx0O3RpdGxlJmd0O1dlbGNvbWUgdG8gT3BlbiBGb29kIE5ldHdvcmsmbHQ7L3RpdGxlJmd0O1xuJiN4Mjc7CiAmI3gyNzsmbHQ7bGluayByZWw9JnF1b3Q7aWNvbiZxdW90OyB0eXBlPSZxdW90O2ltYWdlL3gtaWNvbiZxdW90OyBocmVmPSZxdW90Oy9mYXZpY29uLXN0YWdpbmcuaWNvJnF1b3Q7IC8mZ3Q7XG4mI3gyNzsKICYjeDI3OyZsdDtsaW5rICYjeDI3OwogJnF1b3Q7aHJlZj0mI3gyNztodHRwczovL2ZvbnRzLmdvb2dsZWFwaXMuY29tL2Nzcz9mYW1pbHk9Um9ib3RvOjQwMCwzMDBpdGFsaWMsNDAwaXRhbGljLDMwMCw3MDAsNzAwaXRhbGljfE9zd2FsZDozMDAsNDAwLDcwMCYjeDI3OyAmcXVvdDsKICZxdW90O3JlbD0mI3gyNztzdHlsZXNoZWV0JiN4Mjc7IHR5cGU9JiN4Mjc7dGV4dC9jc3MmI3gyNzsmZ3Q7XG4mcXVvdDsKICZxdW90OyZsdDtsaW5rIGFzPSYjeDI3O2ZvbnQmI3gyNzsgY3Jvc3NvcmlnaW49JiN4Mjc7YW5vbnltb3VzJiN4Mjc7ICZxdW90OwogJnF1b3Q7aHJlZj0mI3gyNzsvcGFja3MvbWVkaWEvZm9udHMvT0ZOLXYyLWMyNzMwMzdiM2JhM2VlODI2NGJjNjdlMzFlYTNkNzAxLndvZmYmI3gyNzsgJnF1b3Q7CiAmcXVvdDtyZWw9JiN4Mjc7cHJlbG9hZCYjeDI3OyZndDtcbiZxdW90OwogJiN4Mjc7Jmx0O3NjcmlwdCZndDtcbiYjeDI3OwogJiN4Mjc7ICB2YXIgX210bSA9IHdpbmRvdy5fbXRtID0gd2luZG93Ll9tdG0gfHwgW107XG4mI3gyNzsKICZxdW90OyAgX210bS5wdXNoKHsmI3gyNzttdG0uc3RhcnRUaW1lJiN4Mjc7OiAobmV3IERhdGUoKS5nZXRUaW1lKCkpLCAmI3gyNztldmVudCYjeDI3OzogJnF1b3Q7CiAmcXVvdDsmI3gyNzttdG0uU3RhcnQmI3gyNzt9KTtcbiZxdW90OwogJnF1b3Q7ICB2YXIgZD1kb2N1bWVudCwgZz1kLmNyZWF0ZUVsZW1lbnQoJiN4Mjc7c2NyaXB0JiN4Mjc7KSwgJnF1b3Q7CiAmcXVvdDtzPWQuZ2V0RWxlbWVudHNCeVRhZ05hbWUoJiN4Mjc7c2NyaXB0JiN4Mjc7KVswXTtcbiZxdW90OwogJiN4Mjc7ICB2YXIgJiN4Mjc7CiAmI3gyNzt1PSZxdW90O2h0dHBzOi8vY2RuLmlubm9jcmFmdC5jbG91ZC9vcGVuZm9vZG5ldHdvcmsuaW5ub2NyYWZ0LmNsb3VkL2NvbnRhaW5lcl83TUFnV3hoci5qcyZxdW90OztcbiYjeDI3OwogJnF1b3Q7ICBnLnR5cGU9JiN4Mjc7dGV4dC9qYXZhc2NyaXB0JiN4Mjc7OyBnLmFzeW5jPXRydWU7IGcuZGVmZXI9dHJ1ZTsgZy5zcmM9dTsgJnF1b3Q7CiAmI3gyNztzLnBhcmVudE5vZGUuaW5zZXJ0QmVmb3JlKGcscyk7XG4mI3gyNzsKICYjeDI3OyZsdDsvc2NyaXB0Jmd0O1xuJiN4Mjc7CiAmI3gyNztcbiYjeDI3OwogJiN4Mjc7Jmx0O2xpbmsgaHJlZmxhbmc9JnF1b3Q7ZW4tZ2ImcXVvdDsgJiN4Mjc7CiAmI3gyNztocmVmPSZxdW90OzxISURERU4tT1BFTklEX0FQUF9JRD5sb2NhbGVzL2VuX0dCJnF1b3Q7Jmd0O1xuJiN4Mjc7CiAmI3gyNzsmbHQ7bGluayBocmVmbGFuZz0mcXVvdDtjeSZxdW90OyAmI3gyNzsKICYjeDI3O2hyZWY9JnF1b3Q7PEhJRERFTi1PUEVOSURfQVBQX0lEPmxvY2FsZXMvY3kmcXVvdDsmZ3Q7XG4mI3gyNzsKICYjeDI3OyZsdDtsaW5rIGhyZWZsYW5nPSZxdW90O2VuJnF1b3Q7ICYjeDI3OwogJiN4Mjc7aHJlZj0mcXVvdDs8SElEREVOLU9QRU5JRF9BUFBfSUQ+bG9jYWxlcy9lbiZxdW90OyZndDtcbiYjeDI3OwogJiN4Mjc7Jmx0O2xpbmsgcmVsPSZxdW90O3N0eWxlc2hlZXQmcXVvdDsgaHJlZj0mcXVvdDsvcGFja3MvY3NzL2Rhcmtzd2FybS02NTc4NzkyMS5jc3MmcXVvdDsgJiN4Mjc7CiAmI3gyNztkYXRhLXR1cmJvLXRyYWNrPSZxdW90O3JlbG9hZCZxdW90OyBtZWRpYT0mcXVvdDtzY3JlZW4mcXVvdDsgLyZndDtcbiYjeDI3OwogJiN4Mjc7Jmx0O3NjcmlwdCBzcmM9JnF1b3Q7L3BhY2tzL2pzL2FwcGxpY2F0aW9uLWM0NDEzM2YwZDYxMTAzY2VmMDVmLmpzJnF1b3Q7ICYjeDI3OwogJiN4Mjc7ZGF0YS10dXJiby10cmFjaz0mcXVvdDtyZWxvYWQmcXVvdDsmZ3Q7Jmx0Oy9zY3JpcHQmZ3Q7XG4mI3gyNzsKICYjeDI3O1xuJiN4Mjc7CiAmcXVvdDsmbHQ7c2NyaXB0IHNyYz0mI3gyNzsvL2Qyd3k4ZjdhOXVyc25tLmNsb3VkZnJvbnQubmV0L3Y3L2J1Z3NuYWcubWluLmpzJiN4Mjc7Jmd0OyZsdDsvc2NyaXB0Jmd0O1xuJnF1b3Q7CiAmI3gyNzsmbHQ7c2NyaXB0Jmd0O1xuJiN4Mjc7CiAmI3gyNzsgIEJ1Z3NuYWcuc3RhcnQoe1xuJiN4Mjc7CiAmI3gyNzsgICAgYXBpS2V5OiAmcXVvdDtmNmM0ZTI4NWVjZDdmNTY4Mzc5M2U4ZDNmNGEzZGU3NyZxdW90OyxcbiYjeDI3OwogJiN4Mjc7ICAgIHJlbGVhc2VTdGFnZTogJnF1b3Q7c3RhZ2luZyZxdW90O1xuJiN4Mjc7CiAmI3gyNzsgIH0pXG4mI3gyNzsKICYjeDI3OyZsdDsvc2NyaXB0Jmd0O1xuJiN4Mjc7CiAmI3gyNztcbiYjeDI3OwogJnF1b3Q7Jmx0O3NjcmlwdCBzcmM9JiN4Mjc7aHR0cHM6Ly9qcy5zdHJpcGUuY29tL3YzLyYjeDI3OyB0eXBlPSYjeDI3O3RleHQvamF2YXNjcmlwdCYjeDI3OyZndDsmbHQ7L3NjcmlwdCZndDtcbiZxdW90OwogJiN4Mjc7XG4mI3gyNzsKICYjeDI3OyZsdDtzY3JpcHQgJiN4Mjc7CiAmI3gyNztzcmM9JnF1b3Q7L2Fzc2V0cy9kYXJrc3dhcm0vYWxsLTE4MzMwMzllYTgxYzAyNTQ2MTMwMGY1OWQyYTZlM2FhNWY3MGU1OWRmNmI3NzdmZWU0ZjYxNjg1MmNlYWQ1MDAuanMmcXVvdDsgJiN4Mjc7CiAmI3gyNztkYXRhLXR1cmJvLXRyYWNrPSZxdW90O3JlbG9hZCZxdW90OyZndDsmbHQ7L3NjcmlwdCZndDtcbiYjeDI3OwogJiN4Mjc7Jmx0O3NjcmlwdCAmI3gyNzsKICYjeDI3O3NyYz0mcXVvdDsvYXNzZXRzL3dlYi9hbGwtMWRhMWNjNmZlNmE1MGM2MDBhNDU1MmEyY2MxYTQ1Mjk3NjIzZTNkNTE3YjBlOTUyMTllZDUxY2Q3Zjc5ODM0Mi5qcyZxdW90OyAmI3gyNzsKICYjeDI3O2RhdGEtdHVyYm8tdHJhY2s9JnF1b3Q7cmVsb2FkJnF1b3Q7Jmd0OyZsdDsvc2NyaXB0Jmd0O1xuJiN4Mjc7CiAmI3gyNzsmbHQ7c2NyaXB0Jmd0O1xuJiN4Mjc7CiAmI3gyNzsgIEkxOG4uZGVmYXVsdF9sb2NhbGUgPSAmcXVvdDtlbl9HQiZxdW90OztcbiYjeDI3OwogJiN4Mjc7ICBJMThuLmJhc2VfbG9jYWxlID0gJnF1b3Q7ZW4mcXVvdDs7XG4mI3gyNzsKICYjeDI3OyAgSTE4bi5sb2NhbGUgPSAmcXVvdDtlbl9HQiZxdW90OztcbiYjeDI3OwogJiN4Mjc7ICBJMThuLmZhbGxiYWNrcyA9IHRydWU7XG4mI3gyNzsKICZxdW90OyAgbW9tZW50LmxvY2FsZShbSTE4bi5sb2NhbGUsICYjeDI3O2VuJiN4Mjc7XSk7XG4mcXVvdDsKICYjeDI3OyZsdDsvc2NyaXB0Jmd0O1xuJiN4Mjc7CiAmI3gyNztcbiYjeDI3OwogJiN4Mjc7Jmx0O21ldGEgbmFtZT0mcXVvdDtjc3JmLXBhcmFtJnF1b3Q7IGNvbnRlbnQ9JnF1b3Q7YXV0aGVudGljaXR5X3Rva2VuJnF1b3Q7IC8mZ3Q7XG4mI3gyNzsKICYjeDI3OyZsdDttZXRhIG5hbWU9JnF1b3Q7Y3NyZi10b2tlbiZxdW90OyAmI3gyNzsKICYjeDI3O2NvbnRlbnQ9JnF1b3Q7bHhWZzg5YWJzbHRJQ3Q3WlBfWU1kcjM1RFd5a1VvSHJ5dW1JRk03VGFqbE9VZHNVWFo2UlhsdzNhUUZsd0wzX2JmZUpXZ0RDMEx6cnRiWFF1Nk1CZncmcXVvdDsgJiN4Mjc7CiAmI3gyNzsvJmd0O1xuJiN4Mjc7CiAmcXVvdDsmbHQ7bWV0YSBjb250ZW50PSYjeDI3O25vLWNhY2hlJiN4Mjc7IG5hbWU9JiN4Mjc7dHVyYm8tY2FjaGUtY29udHJvbCYjeDI3OyZndDtcbiZxdW90OwogJiN4Mjc7Jmx0O21ldGEgbmFtZT0mcXVvdDthY3Rpb24tY2FibGUtdXJsJnF1b3Q7IGNvbnRlbnQ9JnF1b3Q7L2NhYmxlJnF1b3Q7IC8mZ3Q7XG4mI3gyNzsKICYjeDI3OyZsdDsvaGVhZCZndDtcbiYjeDI3OwogJnF1b3Q7Jmx0O2JvZHkgYm9keS1zY3JvbGw9JiN4Mjc7dHJ1ZSYjeDI3OyBjbGFzcz0mI3gyNztvZmYtY2FudmFzJiN4Mjc7IGRhdGEtdHVyYm89JiN4Mjc7ZmFsc2UmI3gyNzsmZ3Q7XG4mcXVvdDsKICZxdW90OyZsdDtkaXYgY2xhc3M9JiN4Mjc7b2ZmLWNhbnZhcy13cmFwJiN4Mjc7IG9mZmNhbnZhcyZndDtcbiZxdW90OwogJnF1b3Q7Jmx0O2RpdiBjbGFzcz0mI3gyNztmaXhlZCBvZmYtY2FudmFzLWZpeGVkJiN4Mjc7Jmd0O1xuJnF1b3Q7CiAmcXVvdDsmbHQ7ZGl2IG5nLWNvbnRyb2xsZXI9JiN4Mjc7Q2FydERyb3Bkb3duQ3RybCYjeDI3OyZndDtcbiZxdW90OwogJnF1b3Q7Jmx0O25hdiBjbGFzcz0mI3gyNzt0b3AtYmFyIHNob3ctZm9yLWxhcmdlLXVwJiN4Mjc7Jmd0O1xuJnF1b3Q7CiAmcXVvdDsmbHQ7c2VjdGlvbiBjbGFzcz0mI3gyNzt0b3AtYmFyLXNlY3Rpb24mI3gyNzsmZ3Q7XG4mcXVvdDsKICZxdW90OyZsdDt1bCBjbGFzcz0mI3gyNztuYXYtbG9nbyYjeDI3OyZndDtcbiZxdW90OwogJnF1b3Q7Jmx0O2xpIGNsYXNzPSYjeDI3O29mbi1sb2dvJiN4Mjc7Jmd0O1xuJnF1b3Q7CiAmcXVvdDsmbHQ7YSBocmVmPSYjeDI3Oy8mI3gyNzsmZ3Q7XG4mcXVvdDsKICYjeDI3OyZsdDtpbWcgJiN4Mjc7CiAmcXVvdDtzcmM9JiN4Mjc7PEhJRERFTi1PUEVOSURfQVBQX0lEPnJhaWxzL2FjdGl2ZV9zdG9yYWdlL2Jsb2JzL3JlZGlyZWN0L2V5SmZjbUZwYkhNaU9uc2liV1Z6YzJGblpTSTZJa0pCYUhCQk1URmpRV2M5UFNJc0ltVjRjQ0k2Ym5Wc2JDd2ljSFZ5SWpvaVlteHZZbDlwWkNKOWZRPT0tLTYxZDMyZGQzOGFkODk2YzFjNzEwNmM4ZDllMTFkOTc5MDdkZTdmOWYvb2ZuLXVrLnBuZyYjeDI3OyZndDtcbiZxdW90OwogJiN4Mjc7Jmx0Oy9hJmd0O1xuJiN4Mjc7CiAmI3gyNzsmbHQ7L2xpJmd0O1xuJiN4Mjc7CiAmcXVvdDsmbHQ7bGkgY2xhc3M9JiN4Mjc7cG93ZXJlZC1ieSYjeDI3OyZndDtcbiZxdW90OwogJnF1b3Q7Jmx0O2ltZyBzcmM9JiN4Mjc7L2Zh4oCmICZsdDt0cmltbWVkIDMwMTM1IGJ5dGVzIHN0cmluZyZndDs8L3ByZT48L3RkPgogICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICA8dGQ+c2VsZjwvdGQ+CiAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJjb2RlIj48cHJlPiZsdDtqc29uLmRlY29kZXIuSlNPTkRlY29kZXIgb2JqZWN0IGF0IDB4N2ZjYzY3MmNkZjUwJmd0OzwvcHJlPjwvdGQ+CiAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICA8L3RhYmxlPgogICAgICAgICAgICA8L2RldGFpbHM+CiAgICAgICAgICAKICAgICAgICA8L2xpPgogICAgICAKICAgICAgICAKICAgICAgICAgIDxsaSBjbGFzcz0iY2F1c2UiPjxoMz4KICAgICAgICAgIAogICAgICAgICAgICBEdXJpbmcgaGFuZGxpbmcgb2YgdGhlIGFib3ZlIGV4Y2VwdGlvbiAoRXhwZWN0aW5nIHZhbHVlOiBsaW5lIDEgY29sdW1uIDEgKGNoYXIgMCkpLCBhbm90aGVyIGV4Y2VwdGlvbiBvY2N1cnJlZDoKICAgICAgICAgIAogICAgICAgIDwvaDM+PC9saT4KICAgICAgICAKICAgICAgICA8bGkgY2xhc3M9ImZyYW1lIGRqYW5nbyI+CiAgICAgICAgICAKICAgICAgICAgICAgPGNvZGUgY2xhc3M9ImZuYW1lIj4vaG9tZS9jcWNtLXByb3h5LWRldi9zdGFydGluYmxveC92ZW52L2xpYi9weXRob24zLjExL3NpdGUtcGFja2FnZXMvZGphbmdvL2NvcmUvaGFuZGxlcnMvZXhjZXB0aW9uLnB5PC9jb2RlPiwgbGluZSA1NSwgaW4gaW5uZXIKICAgICAgICAgIAoKICAgICAgICAgIAogICAgICAgICAgICA8ZGl2IGNsYXNzPSJjb250ZXh0IiBpZD0iYzE0MDUxNTczMDM4MzI5NiI+CiAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICA8b2wgc3RhcnQ9IjQ4IiBjbGFzcz0icHJlLWNvbnRleHQiIGlkPSJwcmUxNDA1MTU3MzAzODMyOTYiPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4MzI5NicsICdwb3N0MTQwNTE1NzMwMzgzMjk2JykiPjxwcmU+PC9wcmU+PC9saT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzODMyOTYnLCAncG9zdDE0MDUxNTczMDM4MzI5NicpIj48cHJlPiAgICAgICAgcmV0dXJuIGlubmVyPC9wcmU+PC9saT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzODMyOTYnLCAncG9zdDE0MDUxNTczMDM4MzI5NicpIj48cHJlPiAgICBlbHNlOjwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzgzMjk2JywgJ3Bvc3QxNDA1MTU3MzAzODMyOTYnKSI+PHByZT48L3ByZT48L2xpPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4MzI5NicsICdwb3N0MTQwNTE1NzMwMzgzMjk2JykiPjxwcmU+ICAgICAgICBAd3JhcHMoZ2V0X3Jlc3BvbnNlKTwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzgzMjk2JywgJ3Bvc3QxNDA1MTU3MzAzODMyOTYnKSI+PHByZT4gICAgICAgIGRlZiBpbm5lcihyZXF1ZXN0KTo8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4MzI5NicsICdwb3N0MTQwNTE1NzMwMzgzMjk2JykiPjxwcmU+ICAgICAgICAgICAgdHJ5OjwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgIDwvb2w+CiAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgPG9sIHN0YXJ0PSI1NSIgY2xhc3M9ImNvbnRleHQtbGluZSI+CiAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzODMyOTYnLCAncG9zdDE0MDUxNTczMDM4MzI5NicpIj48cHJlPiAgICAgICAgICAgICAgICByZXNwb25zZSA9IGdldF9yZXNwb25zZShyZXF1ZXN0KQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXl5eXl5eXl5eXl5eXl5eXl5eXl5ePC9wcmU+IDxzcGFuPuKApjwvc3Bhbj48L2xpPgogICAgICAgICAgICAgIDwvb2w+CiAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICA8b2wgc3RhcnQ9JzU2JyBjbGFzcz0icG9zdC1jb250ZXh0IiBpZD0icG9zdDE0MDUxNTczMDM4MzI5NiI+CiAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzODMyOTYnLCAncG9zdDE0MDUxNTczMDM4MzI5NicpIj48cHJlPiAgICAgICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZXhjOjwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzODMyOTYnLCAncG9zdDE0MDUxNTczMDM4MzI5NicpIj48cHJlPiAgICAgICAgICAgICAgICByZXNwb25zZSA9IHJlc3BvbnNlX2Zvcl9leGNlcHRpb24ocmVxdWVzdCwgZXhjKTwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzODMyOTYnLCAncG9zdDE0MDUxNTczMDM4MzI5NicpIj48cHJlPiAgICAgICAgICAgIHJldHVybiByZXNwb25zZTwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzODMyOTYnLCAncG9zdDE0MDUxNTczMDM4MzI5NicpIj48cHJlPjwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzODMyOTYnLCAncG9zdDE0MDUxNTczMDM4MzI5NicpIj48cHJlPiAgICAgICAgcmV0dXJuIGlubmVyPC9wcmU+PC9saT4KICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4MzI5NicsICdwb3N0MTQwNTE1NzMwMzgzMjk2JykiPjxwcmU+PC9wcmU+PC9saT4KICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgPC9vbD4KICAgICAgICAgICAgICAKICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAKCiAgICAgICAgICAKICAgICAgICAgICAgCiAgICAgICAgICAgICAgPGRldGFpbHM+CiAgICAgICAgICAgICAgICA8c3VtbWFyeSBjbGFzcz0iY29tbWFuZHMiPkxvY2FsIHZhcnM8L3N1bW1hcnk+CiAgICAgICAgICAgIAogICAgICAgICAgICA8dGFibGUgY2xhc3M9InZhcnMiIGlkPSJ2MTQwNTE1NzMwMzgzMjk2Ij4KICAgICAgICAgICAgICA8dGhlYWQ+CiAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgIDx0aD5WYXJpYWJsZTwvdGg+CiAgICAgICAgICAgICAgICAgIDx0aD5WYWx1ZTwvdGg+CiAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgIDwvdGhlYWQ+CiAgICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICA8dGQ+ZXhjPC90ZD4KICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImNvZGUiPjxwcmU+SlNPTkRlY29kZUVycm9yKCYjeDI3O0V4cGVjdGluZyB2YWx1ZTogbGluZSAxIGNvbHVtbiAxIChjaGFyIDApJiN4Mjc7KTwvcHJlPjwvdGQ+CiAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgIDx0ZD5nZXRfcmVzcG9uc2U8L3RkPgogICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iY29kZSI+PHByZT4mbHQ7Ym91bmQgbWV0aG9kIEJhc2VIYW5kbGVyLl9nZXRfcmVzcG9uc2Ugb2YgJmx0O2RqYW5nby5jb3JlLmhhbmRsZXJzLndzZ2kuV1NHSUhhbmRsZXIgb2JqZWN0IGF0IDB4N2ZjYzY1MmJjZGQwJmd0OyZndDs8L3ByZT48L3RkPgogICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICA8dGQ+cmVxdWVzdDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJjb2RlIj48cHJlPiZsdDtXU0dJUmVxdWVzdDogUE9TVCAmI3gyNzsvZGphbmdvbGRwLWRmYy93ZWJob29rLyYjeDI3OyZndDs8L3ByZT48L3RkPgogICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgPC90Ym9keT4KICAgICAgICAgICAgPC90YWJsZT4KICAgICAgICAgICAgPC9kZXRhaWxzPgogICAgICAgICAgCiAgICAgICAgPC9saT4KICAgICAgCiAgICAgICAgCiAgICAgICAgPGxpIGNsYXNzPSJmcmFtZSBkamFuZ28iPgogICAgICAgICAgCiAgICAgICAgICAgIDxjb2RlIGNsYXNzPSJmbmFtZSI+L2hvbWUvY3FjbS1wcm94eS1kZXYvc3RhcnRpbmJsb3gvdmVudi9saWIvcHl0aG9uMy4xMS9zaXRlLXBhY2thZ2VzL2RqYW5nby9jb3JlL2hhbmRsZXJzL2Jhc2UucHk8L2NvZGU+LCBsaW5lIDE5NywgaW4gX2dldF9yZXNwb25zZQogICAgICAgICAgCgogICAgICAgICAgCiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbnRleHQiIGlkPSJjMTQwNTE1NzMwMzgyNTI4Ij4KICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgIDxvbCBzdGFydD0iMTkwIiBjbGFzcz0icHJlLWNvbnRleHQiIGlkPSJwcmUxNDA1MTU3MzAzODI1MjgiPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4MjUyOCcsICdwb3N0MTQwNTE1NzMwMzgyNTI4JykiPjxwcmU+PC9wcmU+PC9saT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzODI1MjgnLCAncG9zdDE0MDUxNTczMDM4MjUyOCcpIj48cHJlPiAgICAgICAgaWYgcmVzcG9uc2UgaXMgTm9uZTo8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4MjUyOCcsICdwb3N0MTQwNTE1NzMwMzgyNTI4JykiPjxwcmU+ICAgICAgICAgICAgd3JhcHBlZF9jYWxsYmFjayA9IHNlbGYubWFrZV92aWV3X2F0b21pYyhjYWxsYmFjayk8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4MjUyOCcsICdwb3N0MTQwNTE1NzMwMzgyNTI4JykiPjxwcmU+ICAgICAgICAgICAgIyBJZiBpdCBpcyBhbiBhc3luY2hyb25vdXMgdmlldywgcnVuIGl0IGluIGEgc3VidGhyZWFkLjwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzgyNTI4JywgJ3Bvc3QxNDA1MTU3MzAzODI1MjgnKSI+PHByZT4gICAgICAgICAgICBpZiBpc2Nvcm91dGluZWZ1bmN0aW9uKHdyYXBwZWRfY2FsbGJhY2spOjwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzgyNTI4JywgJ3Bvc3QxNDA1MTU3MzAzODI1MjgnKSI+PHByZT4gICAgICAgICAgICAgICAgd3JhcHBlZF9jYWxsYmFjayA9IGFzeW5jX3RvX3N5bmMod3JhcHBlZF9jYWxsYmFjayk8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4MjUyOCcsICdwb3N0MTQwNTE1NzMwMzgyNTI4JykiPjxwcmU+ICAgICAgICAgICAgdHJ5OjwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgIDwvb2w+CiAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgPG9sIHN0YXJ0PSIxOTciIGNsYXNzPSJjb250ZXh0LWxpbmUiPgogICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzgyNTI4JywgJ3Bvc3QxNDA1MTU3MzAzODI1MjgnKSI+PHByZT4gICAgICAgICAgICAgICAgcmVzcG9uc2UgPSB3cmFwcGVkX2NhbGxiYWNrKHJlcXVlc3QsICpjYWxsYmFja19hcmdzLCAqKmNhbGxiYWNrX2t3YXJncykKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBeXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl48L3ByZT4gPHNwYW4+4oCmPC9zcGFuPjwvbGk+CiAgICAgICAgICAgICAgPC9vbD4KICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgIDxvbCBzdGFydD0nMTk4JyBjbGFzcz0icG9zdC1jb250ZXh0IiBpZD0icG9zdDE0MDUxNTczMDM4MjUyOCI+CiAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzODI1MjgnLCAncG9zdDE0MDUxNTczMDM4MjUyOCcpIj48cHJlPiAgICAgICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZTo8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzgyNTI4JywgJ3Bvc3QxNDA1MTU3MzAzODI1MjgnKSI+PHByZT4gICAgICAgICAgICAgICAgcmVzcG9uc2UgPSBzZWxmLnByb2Nlc3NfZXhjZXB0aW9uX2J5X21pZGRsZXdhcmUoZSwgcmVxdWVzdCk8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzgyNTI4JywgJ3Bvc3QxNDA1MTU3MzAzODI1MjgnKSI+PHByZT4gICAgICAgICAgICAgICAgaWYgcmVzcG9uc2UgaXMgTm9uZTo8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzgyNTI4JywgJ3Bvc3QxNDA1MTU3MzAzODI1MjgnKSI+PHByZT4gICAgICAgICAgICAgICAgICAgIHJhaXNlPC9wcmU+PC9saT4KICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4MjUyOCcsICdwb3N0MTQwNTE1NzMwMzgyNTI4JykiPjxwcmU+PC9wcmU+PC9saT4KICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4MjUyOCcsICdwb3N0MTQwNTE1NzMwMzgyNTI4JykiPjxwcmU+ICAgICAgICAjIENvbXBsYWluIGlmIHRoZSB2aWV3IHJldHVybmVkIE5vbmUgKGEgY29tbW9uIGVycm9yKS48L3ByZT48L2xpPgogICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICA8L29sPgogICAgICAgICAgICAgIAogICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgIAoKICAgICAgICAgIAogICAgICAgICAgICAKICAgICAgICAgICAgICA8ZGV0YWlscz4KICAgICAgICAgICAgICAgIDxzdW1tYXJ5IGNsYXNzPSJjb21tYW5kcyI+TG9jYWwgdmFyczwvc3VtbWFyeT4KICAgICAgICAgICAgCiAgICAgICAgICAgIDx0YWJsZSBjbGFzcz0idmFycyIgaWQ9InYxNDA1MTU3MzAzODI1MjgiPgogICAgICAgICAgICAgIDx0aGVhZD4KICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgPHRoPlZhcmlhYmxlPC90aD4KICAgICAgICAgICAgICAgICAgPHRoPlZhbHVlPC90aD4KICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgPC90aGVhZD4KICAgICAgICAgICAgICA8dGJvZHk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgIDx0ZD5jYWxsYmFjazwvdGQ+CiAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJjb2RlIj48cHJlPiZsdDtmdW5jdGlvbiBWaWV3LmFzX3ZpZXcuJmx0O2xvY2FscyZndDsudmlldyBhdCAweDdmY2M1ZWNhNDQwMCZndDs8L3ByZT48L3RkPgogICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICA8dGQ+Y2FsbGJhY2tfYXJnczwvdGQ+CiAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJjb2RlIj48cHJlPigpPC9wcmU+PC90ZD4KICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgPHRkPmNhbGxiYWNrX2t3YXJnczwvdGQ+CiAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJjb2RlIj48cHJlPnt9PC9wcmU+PC90ZD4KICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgPHRkPm1pZGRsZXdhcmVfbWV0aG9kPC90ZD4KICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImNvZGUiPjxwcmU+Jmx0O2JvdW5kIG1ldGhvZCBDc3JmVmlld01pZGRsZXdhcmUucHJvY2Vzc192aWV3IG9mICZsdDtDc3JmVmlld01pZGRsZXdhcmUgZ2V0X3Jlc3BvbnNlPWNvbnZlcnRfZXhjZXB0aW9uX3RvX3Jlc3BvbnNlLiZsdDtsb2NhbHMmZ3Q7LmlubmVyJmd0OyZndDs8L3ByZT48L3RkPgogICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICA8dGQ+cmVxdWVzdDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJjb2RlIj48cHJlPiZsdDtXU0dJUmVxdWVzdDogUE9TVCAmI3gyNzsvZGphbmdvbGRwLWRmYy93ZWJob29rLyYjeDI3OyZndDs8L3ByZT48L3RkPgogICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICA8dGQ+cmVzcG9uc2U8L3RkPgogICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iY29kZSI+PHByZT5Ob25lPC9wcmU+PC90ZD4KICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgPHRkPnNlbGY8L3RkPgogICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iY29kZSI+PHByZT4mbHQ7ZGphbmdvLmNvcmUuaGFuZGxlcnMud3NnaS5XU0dJSGFuZGxlciBvYmplY3QgYXQgMHg3ZmNjNjUyYmNkZDAmZ3Q7PC9wcmU+PC90ZD4KICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgPHRkPndyYXBwZWRfY2FsbGJhY2s8L3RkPgogICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iY29kZSI+PHByZT4mbHQ7ZnVuY3Rpb24gVmlldy5hc192aWV3LiZsdDtsb2NhbHMmZ3Q7LnZpZXcgYXQgMHg3ZmNjNWVjYTQ0MDAmZ3Q7PC9wcmU+PC90ZD4KICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICAgIDwvdGFibGU+CiAgICAgICAgICAgIDwvZGV0YWlscz4KICAgICAgICAgIAogICAgICAgIDwvbGk+CiAgICAgIAogICAgICAgIAogICAgICAgIDxsaSBjbGFzcz0iZnJhbWUgZGphbmdvIj4KICAgICAgICAgIAogICAgICAgICAgICA8Y29kZSBjbGFzcz0iZm5hbWUiPi9ob21lL2NxY20tcHJveHktZGV2L3N0YXJ0aW5ibG94L3ZlbnYvbGliL3B5dGhvbjMuMTEvc2l0ZS1wYWNrYWdlcy9kamFuZ28vdmlld3MvZGVjb3JhdG9ycy9jc3JmLnB5PC9jb2RlPiwgbGluZSA1NiwgaW4gd3JhcHBlcl92aWV3CiAgICAgICAgICAKCiAgICAgICAgICAKICAgICAgICAgICAgPGRpdiBjbGFzcz0iY29udGV4dCIgaWQ9ImMxNDA1MTU3MzAzODMzNjAiPgogICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgPG9sIHN0YXJ0PSI0OSIgY2xhc3M9InByZS1jb250ZXh0IiBpZD0icHJlMTQwNTE1NzMwMzgzMzYwIj4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzODMzNjAnLCAncG9zdDE0MDUxNTczMDM4MzM2MCcpIj48cHJlPmRlZiBjc3JmX2V4ZW1wdCh2aWV3X2Z1bmMpOjwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzgzMzYwJywgJ3Bvc3QxNDA1MTU3MzAzODMzNjAnKSI+PHByZT4gICAgJnF1b3Q7JnF1b3Q7JnF1b3Q7TWFyayBhIHZpZXcgZnVuY3Rpb24gYXMgYmVpbmcgZXhlbXB0IGZyb20gdGhlIENTUkYgdmlldyBwcm90ZWN0aW9uLiZxdW90OyZxdW90OyZxdW90OzwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzgzMzYwJywgJ3Bvc3QxNDA1MTU3MzAzODMzNjAnKSI+PHByZT48L3ByZT48L2xpPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4MzM2MCcsICdwb3N0MTQwNTE1NzMwMzgzMzYwJykiPjxwcmU+ICAgICMgdmlld19mdW5jLmNzcmZfZXhlbXB0ID0gVHJ1ZSB3b3VsZCBhbHNvIHdvcmssIGJ1dCBkZWNvcmF0b3JzIGFyZSBuaWNlcjwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzgzMzYwJywgJ3Bvc3QxNDA1MTU3MzAzODMzNjAnKSI+PHByZT4gICAgIyBpZiB0aGV5IGRvbiYjeDI3O3QgaGF2ZSBzaWRlIGVmZmVjdHMsIHNvIHJldHVybiBhIG5ldyBmdW5jdGlvbi48L3ByZT48L2xpPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4MzM2MCcsICdwb3N0MTQwNTE1NzMwMzgzMzYwJykiPjxwcmU+ICAgIEB3cmFwcyh2aWV3X2Z1bmMpPC9wcmU+PC9saT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzODMzNjAnLCAncG9zdDE0MDUxNTczMDM4MzM2MCcpIj48cHJlPiAgICBkZWYgd3JhcHBlcl92aWV3KCphcmdzLCAqKmt3YXJncyk6PC9wcmU+PC9saT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgPC9vbD4KICAgICAgICAgICAgICAKICAgICAgICAgICAgICA8b2wgc3RhcnQ9IjU2IiBjbGFzcz0iY29udGV4dC1saW5lIj4KICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4MzM2MCcsICdwb3N0MTQwNTE1NzMwMzgzMzYwJykiPjxwcmU+ICAgICAgICByZXR1cm4gdmlld19mdW5jKCphcmdzLCAqKmt3YXJncykKICAgICAgICAgICAgICAgICAgIF5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5ePC9wcmU+IDxzcGFuPuKApjwvc3Bhbj48L2xpPgogICAgICAgICAgICAgIDwvb2w+CiAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICA8b2wgc3RhcnQ9JzU3JyBjbGFzcz0icG9zdC1jb250ZXh0IiBpZD0icG9zdDE0MDUxNTczMDM4MzM2MCI+CiAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzODMzNjAnLCAncG9zdDE0MDUxNTczMDM4MzM2MCcpIj48cHJlPjwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzODMzNjAnLCAncG9zdDE0MDUxNTczMDM4MzM2MCcpIj48cHJlPiAgICB3cmFwcGVyX3ZpZXcuY3NyZl9leGVtcHQgPSBUcnVlPC9wcmU+PC9saT4KICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4MzM2MCcsICdwb3N0MTQwNTE1NzMwMzgzMzYwJykiPjxwcmU+ICAgIHJldHVybiB3cmFwcGVyX3ZpZXc8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICA8L29sPgogICAgICAgICAgICAgIAogICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgIAoKICAgICAgICAgIAogICAgICAgICAgICAKICAgICAgICAgICAgICA8ZGV0YWlscz4KICAgICAgICAgICAgICAgIDxzdW1tYXJ5IGNsYXNzPSJjb21tYW5kcyI+TG9jYWwgdmFyczwvc3VtbWFyeT4KICAgICAgICAgICAgCiAgICAgICAgICAgIDx0YWJsZSBjbGFzcz0idmFycyIgaWQ9InYxNDA1MTU3MzAzODMzNjAiPgogICAgICAgICAgICAgIDx0aGVhZD4KICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgPHRoPlZhcmlhYmxlPC90aD4KICAgICAgICAgICAgICAgICAgPHRoPlZhbHVlPC90aD4KICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgPC90aGVhZD4KICAgICAgICAgICAgICA8dGJvZHk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgIDx0ZD5hcmdzPC90ZD4KICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImNvZGUiPjxwcmU+KCZsdDtXU0dJUmVxdWVzdDogUE9TVCAmI3gyNzsvZGphbmdvbGRwLWRmYy93ZWJob29rLyYjeDI3OyZndDssKTwvcHJlPjwvdGQ+CiAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgIDx0ZD5rd2FyZ3M8L3RkPgogICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iY29kZSI+PHByZT57fTwvcHJlPjwvdGQ+CiAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgIDx0ZD52aWV3X2Z1bmM8L3RkPgogICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iY29kZSI+PHByZT4mbHQ7ZnVuY3Rpb24gVmlldy5hc192aWV3LiZsdDtsb2NhbHMmZ3Q7LnZpZXcgYXQgMHg3ZmNjNWVjYTQzNjAmZ3Q7PC9wcmU+PC90ZD4KICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICAgIDwvdGFibGU+CiAgICAgICAgICAgIDwvZGV0YWlscz4KICAgICAgICAgIAogICAgICAgIDwvbGk+CiAgICAgIAogICAgICAgIAogICAgICAgIDxsaSBjbGFzcz0iZnJhbWUgZGphbmdvIj4KICAgICAgICAgIAogICAgICAgICAgICA8Y29kZSBjbGFzcz0iZm5hbWUiPi9ob21lL2NxY20tcHJveHktZGV2L3N0YXJ0aW5ibG94L3ZlbnYvbGliL3B5dGhvbjMuMTEvc2l0ZS1wYWNrYWdlcy9kamFuZ28vdmlld3MvZ2VuZXJpYy9iYXNlLnB5PC9jb2RlPiwgbGluZSAxMDQsIGluIHZpZXcKICAgICAgICAgIAoKICAgICAgICAgIAogICAgICAgICAgICA8ZGl2IGNsYXNzPSJjb250ZXh0IiBpZD0iYzE0MDUxNTczMDM3NzI4MCI+CiAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICA8b2wgc3RhcnQ9Ijk3IiBjbGFzcz0icHJlLWNvbnRleHQiIGlkPSJwcmUxNDA1MTU3MzAzNzcyODAiPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM3NzI4MCcsICdwb3N0MTQwNTE1NzMwMzc3MjgwJykiPjxwcmU+ICAgICAgICAgICAgc2VsZiA9IGNscygqKmluaXRrd2FyZ3MpPC9wcmU+PC9saT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzNzcyODAnLCAncG9zdDE0MDUxNTczMDM3NzI4MCcpIj48cHJlPiAgICAgICAgICAgIHNlbGYuc2V0dXAocmVxdWVzdCwgKmFyZ3MsICoqa3dhcmdzKTwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzc3MjgwJywgJ3Bvc3QxNDA1MTU3MzAzNzcyODAnKSI+PHByZT4gICAgICAgICAgICBpZiBub3QgaGFzYXR0cihzZWxmLCAmcXVvdDtyZXF1ZXN0JnF1b3Q7KTo8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM3NzI4MCcsICdwb3N0MTQwNTE1NzMwMzc3MjgwJykiPjxwcmU+ICAgICAgICAgICAgICAgIHJhaXNlIEF0dHJpYnV0ZUVycm9yKDwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzc3MjgwJywgJ3Bvc3QxNDA1MTU3MzAzNzcyODAnKSI+PHByZT4gICAgICAgICAgICAgICAgICAgICZxdW90OyVzIGluc3RhbmNlIGhhcyBubyAmI3gyNztyZXF1ZXN0JiN4Mjc7IGF0dHJpYnV0ZS4gRGlkIHlvdSBvdmVycmlkZSAmcXVvdDs8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM3NzI4MCcsICdwb3N0MTQwNTE1NzMwMzc3MjgwJykiPjxwcmU+ICAgICAgICAgICAgICAgICAgICAmcXVvdDtzZXR1cCgpIGFuZCBmb3JnZXQgdG8gY2FsbCBzdXBlcigpPyZxdW90OyAlIGNscy5fX25hbWVfXzwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzc3MjgwJywgJ3Bvc3QxNDA1MTU3MzAzNzcyODAnKSI+PHByZT4gICAgICAgICAgICAgICAgKTwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgIDwvb2w+CiAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgPG9sIHN0YXJ0PSIxMDQiIGNsYXNzPSJjb250ZXh0LWxpbmUiPgogICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzc3MjgwJywgJ3Bvc3QxNDA1MTU3MzAzNzcyODAnKSI+PHByZT4gICAgICAgICAgICByZXR1cm4gc2VsZi5kaXNwYXRjaChyZXF1ZXN0LCAqYXJncywgKiprd2FyZ3MpCiAgICAgICAgICAgICAgICAgICAgICAgIF5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXjwvcHJlPiA8c3Bhbj7igKY8L3NwYW4+PC9saT4KICAgICAgICAgICAgICA8L29sPgogICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgPG9sIHN0YXJ0PScxMDUnIGNsYXNzPSJwb3N0LWNvbnRleHQiIGlkPSJwb3N0MTQwNTE1NzMwMzc3MjgwIj4KICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM3NzI4MCcsICdwb3N0MTQwNTE1NzMwMzc3MjgwJykiPjxwcmU+PC9wcmU+PC9saT4KICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM3NzI4MCcsICdwb3N0MTQwNTE1NzMwMzc3MjgwJykiPjxwcmU+ICAgICAgICB2aWV3LnZpZXdfY2xhc3MgPSBjbHM8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzc3MjgwJywgJ3Bvc3QxNDA1MTU3MzAzNzcyODAnKSI+PHByZT4gICAgICAgIHZpZXcudmlld19pbml0a3dhcmdzID0gaW5pdGt3YXJnczwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzNzcyODAnLCAncG9zdDE0MDUxNTczMDM3NzI4MCcpIj48cHJlPjwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzNzcyODAnLCAncG9zdDE0MDUxNTczMDM3NzI4MCcpIj48cHJlPiAgICAgICAgIyBfX25hbWVfXyBhbmQgX19xdWFsbmFtZV9fIGFyZSBpbnRlbnRpb25hbGx5IGxlZnQgdW5jaGFuZ2VkIGFzPC9wcmU+PC9saT4KICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM3NzI4MCcsICdwb3N0MTQwNTE1NzMwMzc3MjgwJykiPjxwcmU+ICAgICAgICAjIHZpZXdfY2xhc3Mgc2hvdWxkIGJlIHVzZWQgdG8gcm9idXN0bHkgZGV0ZXJtaW5lIHRoZSBuYW1lIG9mIHRoZSB2aWV3PC9wcmU+PC9saT4KICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgPC9vbD4KICAgICAgICAgICAgICAKICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAKCiAgICAgICAgICAKICAgICAgICAgICAgCiAgICAgICAgICAgICAgPGRldGFpbHM+CiAgICAgICAgICAgICAgICA8c3VtbWFyeSBjbGFzcz0iY29tbWFuZHMiPkxvY2FsIHZhcnM8L3N1bW1hcnk+CiAgICAgICAgICAgIAogICAgICAgICAgICA8dGFibGUgY2xhc3M9InZhcnMiIGlkPSJ2MTQwNTE1NzMwMzc3MjgwIj4KICAgICAgICAgICAgICA8dGhlYWQ+CiAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgIDx0aD5WYXJpYWJsZTwvdGg+CiAgICAgICAgICAgICAgICAgIDx0aD5WYWx1ZTwvdGg+CiAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgIDwvdGhlYWQ+CiAgICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICA8dGQ+YXJnczwvdGQ+CiAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJjb2RlIj48cHJlPigpPC9wcmU+PC90ZD4KICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgPHRkPmNsczwvdGQ+CiAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJjb2RlIj48cHJlPiZsdDtjbGFzcyAmI3gyNztkYXRhX2Zvb2RfY29uc29ydGl1bS52aWV3cy5DYWNoZVdlYmhvb2tWaWV3JiN4Mjc7Jmd0OzwvcHJlPjwvdGQ+CiAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgIDx0ZD5pbml0a3dhcmdzPC90ZD4KICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImNvZGUiPjxwcmU+e308L3ByZT48L3RkPgogICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICA8dGQ+a3dhcmdzPC90ZD4KICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImNvZGUiPjxwcmU+e308L3ByZT48L3RkPgogICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICA8dGQ+cmVxdWVzdDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJjb2RlIj48cHJlPiZsdDtXU0dJUmVxdWVzdDogUE9TVCAmI3gyNzsvZGphbmdvbGRwLWRmYy93ZWJob29rLyYjeDI3OyZndDs8L3ByZT48L3RkPgogICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICA8dGQ+c2VsZjwvdGQ+CiAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJjb2RlIj48cHJlPiZsdDtkYXRhX2Zvb2RfY29uc29ydGl1bS52aWV3cy5DYWNoZVdlYmhvb2tWaWV3IG9iamVjdCBhdCAweDdmY2M1ZTMxYzkxMCZndDs8L3ByZT48L3RkPgogICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgPC90Ym9keT4KICAgICAgICAgICAgPC90YWJsZT4KICAgICAgICAgICAgPC9kZXRhaWxzPgogICAgICAgICAgCiAgICAgICAgPC9saT4KICAgICAgCiAgICAgICAgCiAgICAgICAgPGxpIGNsYXNzPSJmcmFtZSB1c2VyIj4KICAgICAgICAgIAogICAgICAgICAgICA8Y29kZSBjbGFzcz0iZm5hbWUiPi9ob21lL2NxY20tcHJveHktZGV2L3N0YXJ0aW5ibG94L3ZlbnYvbGliL3B5dGhvbjMuMTEvc2l0ZS1wYWNrYWdlcy9yZXN0X2ZyYW1ld29yay92aWV3cy5weTwvY29kZT4sIGxpbmUgNTA5LCBpbiBkaXNwYXRjaAogICAgICAgICAgCgogICAgICAgICAgCiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbnRleHQiIGlkPSJjMTQwNTE1NzMwMzgyMDE2Ij4KICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgIDxvbCBzdGFydD0iNTAyIiBjbGFzcz0icHJlLWNvbnRleHQiIGlkPSJwcmUxNDA1MTU3MzAzODIwMTYiPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4MjAxNicsICdwb3N0MTQwNTE1NzMwMzgyMDE2JykiPjxwcmU+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlbGYuaHR0cF9tZXRob2Rfbm90X2FsbG93ZWQpPC9wcmU+PC9saT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzODIwMTYnLCAncG9zdDE0MDUxNTczMDM4MjAxNicpIj48cHJlPiAgICAgICAgICAgIGVsc2U6PC9wcmU+PC9saT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzODIwMTYnLCAncG9zdDE0MDUxNTczMDM4MjAxNicpIj48cHJlPiAgICAgICAgICAgICAgICBoYW5kbGVyID0gc2VsZi5odHRwX21ldGhvZF9ub3RfYWxsb3dlZDwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzgyMDE2JywgJ3Bvc3QxNDA1MTU3MzAzODIwMTYnKSI+PHByZT48L3ByZT48L2xpPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4MjAxNicsICdwb3N0MTQwNTE1NzMwMzgyMDE2JykiPjxwcmU+ICAgICAgICAgICAgcmVzcG9uc2UgPSBoYW5kbGVyKHJlcXVlc3QsICphcmdzLCAqKmt3YXJncyk8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4MjAxNicsICdwb3N0MTQwNTE1NzMwMzgyMDE2JykiPjxwcmU+PC9wcmU+PC9saT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzODIwMTYnLCAncG9zdDE0MDUxNTczMDM4MjAxNicpIj48cHJlPiAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBleGM6PC9wcmU+PC9saT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgPC9vbD4KICAgICAgICAgICAgICAKICAgICAgICAgICAgICA8b2wgc3RhcnQ9IjUwOSIgY2xhc3M9ImNvbnRleHQtbGluZSI+CiAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzODIwMTYnLCAncG9zdDE0MDUxNTczMDM4MjAxNicpIj48cHJlPiAgICAgICAgICAgIHJlc3BvbnNlID0gc2VsZi5oYW5kbGVfZXhjZXB0aW9uKGV4YykKICAgICAgICAgICAgICAgICAgICAgICAgICAgIF5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5ePC9wcmU+IDxzcGFuPuKApjwvc3Bhbj48L2xpPgogICAgICAgICAgICAgIDwvb2w+CiAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICA8b2wgc3RhcnQ9JzUxMCcgY2xhc3M9InBvc3QtY29udGV4dCIgaWQ9InBvc3QxNDA1MTU3MzAzODIwMTYiPgogICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzgyMDE2JywgJ3Bvc3QxNDA1MTU3MzAzODIwMTYnKSI+PHByZT48L3ByZT48L2xpPgogICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzgyMDE2JywgJ3Bvc3QxNDA1MTU3MzAzODIwMTYnKSI+PHByZT4gICAgICAgIHNlbGYucmVzcG9uc2UgPSBzZWxmLmZpbmFsaXplX3Jlc3BvbnNlKHJlcXVlc3QsIHJlc3BvbnNlLCAqYXJncywgKiprd2FyZ3MpPC9wcmU+PC9saT4KICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4MjAxNicsICdwb3N0MTQwNTE1NzMwMzgyMDE2JykiPjxwcmU+ICAgICAgICByZXR1cm4gc2VsZi5yZXNwb25zZTwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzODIwMTYnLCAncG9zdDE0MDUxNTczMDM4MjAxNicpIj48cHJlPjwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzODIwMTYnLCAncG9zdDE0MDUxNTczMDM4MjAxNicpIj48cHJlPiAgICBkZWYgb3B0aW9ucyhzZWxmLCByZXF1ZXN0LCAqYXJncywgKiprd2FyZ3MpOjwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzODIwMTYnLCAncG9zdDE0MDUxNTczMDM4MjAxNicpIj48cHJlPiAgICAgICAgJnF1b3Q7JnF1b3Q7JnF1b3Q7PC9wcmU+PC9saT4KICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgPC9vbD4KICAgICAgICAgICAgICAKICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAKCiAgICAgICAgICAKICAgICAgICAgICAgCiAgICAgICAgICAgICAgPGRldGFpbHM+CiAgICAgICAgICAgICAgICA8c3VtbWFyeSBjbGFzcz0iY29tbWFuZHMiPkxvY2FsIHZhcnM8L3N1bW1hcnk+CiAgICAgICAgICAgIAogICAgICAgICAgICA8dGFibGUgY2xhc3M9InZhcnMiIGlkPSJ2MTQwNTE1NzMwMzgyMDE2Ij4KICAgICAgICAgICAgICA8dGhlYWQ+CiAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgIDx0aD5WYXJpYWJsZTwvdGg+CiAgICAgICAgICAgICAgICAgIDx0aD5WYWx1ZTwvdGg+CiAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgIDwvdGhlYWQ+CiAgICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICA8dGQ+YXJnczwvdGQ+CiAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJjb2RlIj48cHJlPigpPC9wcmU+PC90ZD4KICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgPHRkPmhhbmRsZXI8L3RkPgogICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iY29kZSI+PHByZT4mbHQ7Ym91bmQgbWV0aG9kIENhY2hlV2ViaG9va1ZpZXcucG9zdCBvZiAmbHQ7ZGF0YV9mb29kX2NvbnNvcnRpdW0udmlld3MuQ2FjaGVXZWJob29rVmlldyBvYmplY3QgYXQgMHg3ZmNjNWUzMWM5MTAmZ3Q7Jmd0OzwvcHJlPjwvdGQ+CiAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgIDx0ZD5rd2FyZ3M8L3RkPgogICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iY29kZSI+PHByZT57fTwvcHJlPjwvdGQ+CiAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgIDx0ZD5yZXF1ZXN0PC90ZD4KICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImNvZGUiPjxwcmU+Jmx0O3Jlc3RfZnJhbWV3b3JrLnJlcXVlc3QuUmVxdWVzdDogUE9TVCAmI3gyNzsvZGphbmdvbGRwLWRmYy93ZWJob29rLyYjeDI3OyZndDs8L3ByZT48L3RkPgogICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICA8dGQ+c2VsZjwvdGQ+CiAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJjb2RlIj48cHJlPiZsdDtkYXRhX2Zvb2RfY29uc29ydGl1bS52aWV3cy5DYWNoZVdlYmhvb2tWaWV3IG9iamVjdCBhdCAweDdmY2M1ZTMxYzkxMCZndDs8L3ByZT48L3RkPgogICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgPC90Ym9keT4KICAgICAgICAgICAgPC90YWJsZT4KICAgICAgICAgICAgPC9kZXRhaWxzPgogICAgICAgICAgCiAgICAgICAgPC9saT4KICAgICAgCiAgICAgICAgCiAgICAgICAgPGxpIGNsYXNzPSJmcmFtZSB1c2VyIj4KICAgICAgICAgIAogICAgICAgICAgICA8Y29kZSBjbGFzcz0iZm5hbWUiPi9ob21lL2NxY20tcHJveHktZGV2L3N0YXJ0aW5ibG94L3ZlbnYvbGliL3B5dGhvbjMuMTEvc2l0ZS1wYWNrYWdlcy9yZXN0X2ZyYW1ld29yay92aWV3cy5weTwvY29kZT4sIGxpbmUgNDY5LCBpbiBoYW5kbGVfZXhjZXB0aW9uCiAgICAgICAgICAKCiAgICAgICAgICAKICAgICAgICAgICAgPGRpdiBjbGFzcz0iY29udGV4dCIgaWQ9ImMxNDA1MTU3MzAzODM4MDgiPgogICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgPG9sIHN0YXJ0PSI0NjIiIGNsYXNzPSJwcmUtY29udGV4dCIgaWQ9InByZTE0MDUxNTczMDM4MzgwOCI+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzgzODA4JywgJ3Bvc3QxNDA1MTU3MzAzODM4MDgnKSI+PHByZT48L3ByZT48L2xpPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4MzgwOCcsICdwb3N0MTQwNTE1NzMwMzgzODA4JykiPjxwcmU+ICAgICAgICBleGNlcHRpb25faGFuZGxlciA9IHNlbGYuZ2V0X2V4Y2VwdGlvbl9oYW5kbGVyKCk8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4MzgwOCcsICdwb3N0MTQwNTE1NzMwMzgzODA4JykiPjxwcmU+PC9wcmU+PC9saT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzODM4MDgnLCAncG9zdDE0MDUxNTczMDM4MzgwOCcpIj48cHJlPiAgICAgICAgY29udGV4dCA9IHNlbGYuZ2V0X2V4Y2VwdGlvbl9oYW5kbGVyX2NvbnRleHQoKTwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzgzODA4JywgJ3Bvc3QxNDA1MTU3MzAzODM4MDgnKSI+PHByZT4gICAgICAgIHJlc3BvbnNlID0gZXhjZXB0aW9uX2hhbmRsZXIoZXhjLCBjb250ZXh0KTwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzgzODA4JywgJ3Bvc3QxNDA1MTU3MzAzODM4MDgnKSI+PHByZT48L3ByZT48L2xpPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4MzgwOCcsICdwb3N0MTQwNTE1NzMwMzgzODA4JykiPjxwcmU+ICAgICAgICBpZiByZXNwb25zZSBpcyBOb25lOjwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgIDwvb2w+CiAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgPG9sIHN0YXJ0PSI0NjkiIGNsYXNzPSJjb250ZXh0LWxpbmUiPgogICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzgzODA4JywgJ3Bvc3QxNDA1MTU3MzAzODM4MDgnKSI+PHByZT4gICAgICAgICAgICBzZWxmLnJhaXNlX3VuY2F1Z2h0X2V4Y2VwdGlvbihleGMpCiAgICAgICAgICAgICAgICAgXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXjwvcHJlPiA8c3Bhbj7igKY8L3NwYW4+PC9saT4KICAgICAgICAgICAgICA8L29sPgogICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgPG9sIHN0YXJ0PSc0NzAnIGNsYXNzPSJwb3N0LWNvbnRleHQiIGlkPSJwb3N0MTQwNTE1NzMwMzgzODA4Ij4KICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4MzgwOCcsICdwb3N0MTQwNTE1NzMwMzgzODA4JykiPjxwcmU+PC9wcmU+PC9saT4KICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4MzgwOCcsICdwb3N0MTQwNTE1NzMwMzgzODA4JykiPjxwcmU+ICAgICAgICByZXNwb25zZS5leGNlcHRpb24gPSBUcnVlPC9wcmU+PC9saT4KICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4MzgwOCcsICdwb3N0MTQwNTE1NzMwMzgzODA4JykiPjxwcmU+ICAgICAgICByZXR1cm4gcmVzcG9uc2U8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzgzODA4JywgJ3Bvc3QxNDA1MTU3MzAzODM4MDgnKSI+PHByZT48L3ByZT48L2xpPgogICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzgzODA4JywgJ3Bvc3QxNDA1MTU3MzAzODM4MDgnKSI+PHByZT4gICAgZGVmIHJhaXNlX3VuY2F1Z2h0X2V4Y2VwdGlvbihzZWxmLCBleGMpOjwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzODM4MDgnLCAncG9zdDE0MDUxNTczMDM4MzgwOCcpIj48cHJlPiAgICAgICAgaWYgc2V0dGluZ3MuREVCVUc6PC9wcmU+PC9saT4KICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgPC9vbD4KICAgICAgICAgICAgICAKICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAKCiAgICAgICAgICAKICAgICAgICAgICAgCiAgICAgICAgICAgICAgPGRldGFpbHM+CiAgICAgICAgICAgICAgICA8c3VtbWFyeSBjbGFzcz0iY29tbWFuZHMiPkxvY2FsIHZhcnM8L3N1bW1hcnk+CiAgICAgICAgICAgIAogICAgICAgICAgICA8dGFibGUgY2xhc3M9InZhcnMiIGlkPSJ2MTQwNTE1NzMwMzgzODA4Ij4KICAgICAgICAgICAgICA8dGhlYWQ+CiAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgIDx0aD5WYXJpYWJsZTwvdGg+CiAgICAgICAgICAgICAgICAgIDx0aD5WYWx1ZTwvdGg+CiAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgIDwvdGhlYWQ+CiAgICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICA8dGQ+Y29udGV4dDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJjb2RlIj48cHJlPnsmI3gyNzthcmdzJiN4Mjc7OiAoKSwKICYjeDI3O2t3YXJncyYjeDI3Ozoge30sCiAmI3gyNztyZXF1ZXN0JiN4Mjc7OiAmbHQ7cmVzdF9mcmFtZXdvcmsucmVxdWVzdC5SZXF1ZXN0OiBQT1NUICYjeDI3Oy9kamFuZ29sZHAtZGZjL3dlYmhvb2svJiN4Mjc7Jmd0OywKICYjeDI3O3ZpZXcmI3gyNzs6ICZsdDtkYXRhX2Zvb2RfY29uc29ydGl1bS52aWV3cy5DYWNoZVdlYmhvb2tWaWV3IG9iamVjdCBhdCAweDdmY2M1ZTMxYzkxMCZndDt9PC9wcmU+PC90ZD4KICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgPHRkPmV4YzwvdGQ+CiAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJjb2RlIj48cHJlPkpTT05EZWNvZGVFcnJvcigmI3gyNztFeHBlY3RpbmcgdmFsdWU6IGxpbmUgMSBjb2x1bW4gMSAoY2hhciAwKSYjeDI3Oyk8L3ByZT48L3RkPgogICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICA8dGQ+ZXhjZXB0aW9uX2hhbmRsZXI8L3RkPgogICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iY29kZSI+PHByZT4mbHQ7ZnVuY3Rpb24gZXhjZXB0aW9uX2hhbmRsZXIgYXQgMHg3ZmNjNjUzNjRjMjAmZ3Q7PC9wcmU+PC90ZD4KICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgPHRkPnJlc3BvbnNlPC90ZD4KICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImNvZGUiPjxwcmU+Tm9uZTwvcHJlPjwvdGQ+CiAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgIDx0ZD5zZWxmPC90ZD4KICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImNvZGUiPjxwcmU+Jmx0O2RhdGFfZm9vZF9jb25zb3J0aXVtLnZpZXdzLkNhY2hlV2ViaG9va1ZpZXcgb2JqZWN0IGF0IDB4N2ZjYzVlMzFjOTEwJmd0OzwvcHJlPjwvdGQ+CiAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICA8L3RhYmxlPgogICAgICAgICAgICA8L2RldGFpbHM+CiAgICAgICAgICAKICAgICAgICA8L2xpPgogICAgICAKICAgICAgICAKICAgICAgICA8bGkgY2xhc3M9ImZyYW1lIHVzZXIiPgogICAgICAgICAgCiAgICAgICAgICAgIDxjb2RlIGNsYXNzPSJmbmFtZSI+L2hvbWUvY3FjbS1wcm94eS1kZXYvc3RhcnRpbmJsb3gvdmVudi9saWIvcHl0aG9uMy4xMS9zaXRlLXBhY2thZ2VzL3Jlc3RfZnJhbWV3b3JrL3ZpZXdzLnB5PC9jb2RlPiwgbGluZSA0ODAsIGluIHJhaXNlX3VuY2F1Z2h0X2V4Y2VwdGlvbgogICAgICAgICAgCgogICAgICAgICAgCiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbnRleHQiIGlkPSJjMTQwNTE1NzMwMzc2MTI4Ij4KICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgIDxvbCBzdGFydD0iNDczIiBjbGFzcz0icHJlLWNvbnRleHQiIGlkPSJwcmUxNDA1MTU3MzAzNzYxMjgiPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM3NjEyOCcsICdwb3N0MTQwNTE1NzMwMzc2MTI4JykiPjxwcmU+PC9wcmU+PC9saT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzNzYxMjgnLCAncG9zdDE0MDUxNTczMDM3NjEyOCcpIj48cHJlPiAgICBkZWYgcmFpc2VfdW5jYXVnaHRfZXhjZXB0aW9uKHNlbGYsIGV4Yyk6PC9wcmU+PC9saT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzNzYxMjgnLCAncG9zdDE0MDUxNTczMDM3NjEyOCcpIj48cHJlPiAgICAgICAgaWYgc2V0dGluZ3MuREVCVUc6PC9wcmU+PC9saT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzNzYxMjgnLCAncG9zdDE0MDUxNTczMDM3NjEyOCcpIj48cHJlPiAgICAgICAgICAgIHJlcXVlc3QgPSBzZWxmLnJlcXVlc3Q8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM3NjEyOCcsICdwb3N0MTQwNTE1NzMwMzc2MTI4JykiPjxwcmU+ICAgICAgICAgICAgcmVuZGVyZXJfZm9ybWF0ID0gZ2V0YXR0cihyZXF1ZXN0LmFjY2VwdGVkX3JlbmRlcmVyLCAmI3gyNztmb3JtYXQmI3gyNzspPC9wcmU+PC9saT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzNzYxMjgnLCAncG9zdDE0MDUxNTczMDM3NjEyOCcpIj48cHJlPiAgICAgICAgICAgIHVzZV9wbGFpbnRleHRfdHJhY2ViYWNrID0gcmVuZGVyZXJfZm9ybWF0IG5vdCBpbiAoJiN4Mjc7aHRtbCYjeDI3OywgJiN4Mjc7YXBpJiN4Mjc7LCAmI3gyNzthZG1pbiYjeDI3Oyk8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM3NjEyOCcsICdwb3N0MTQwNTE1NzMwMzc2MTI4JykiPjxwcmU+ICAgICAgICAgICAgcmVxdWVzdC5mb3JjZV9wbGFpbnRleHRfZXJyb3JzKHVzZV9wbGFpbnRleHRfdHJhY2ViYWNrKTwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgIDwvb2w+CiAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgPG9sIHN0YXJ0PSI0ODAiIGNsYXNzPSJjb250ZXh0LWxpbmUiPgogICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzc2MTI4JywgJ3Bvc3QxNDA1MTU3MzAzNzYxMjgnKSI+PHByZT4gICAgICAgIHJhaXNlIGV4YwogICAgICAgICAgICAgXl5eXl5eXl5ePC9wcmU+IDxzcGFuPuKApjwvc3Bhbj48L2xpPgogICAgICAgICAgICAgIDwvb2w+CiAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICA8b2wgc3RhcnQ9JzQ4MScgY2xhc3M9InBvc3QtY29udGV4dCIgaWQ9InBvc3QxNDA1MTU3MzAzNzYxMjgiPgogICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzc2MTI4JywgJ3Bvc3QxNDA1MTU3MzAzNzYxMjgnKSI+PHByZT48L3ByZT48L2xpPgogICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzc2MTI4JywgJ3Bvc3QxNDA1MTU3MzAzNzYxMjgnKSI+PHByZT4gICAgIyBOb3RlOiBWaWV3cyBhcmUgbWFkZSBDU1JGIGV4ZW1wdCBmcm9tIHdpdGhpbiBgYXNfdmlld2AgYXMgdG8gcHJldmVudDwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzNzYxMjgnLCAncG9zdDE0MDUxNTczMDM3NjEyOCcpIj48cHJlPiAgICAjIGFjY2lkZW50YWwgcmVtb3ZhbCBvZiB0aGlzIGV4ZW1wdGlvbiBpbiBjYXNlcyB3aGVyZSBgZGlzcGF0Y2hgIG5lZWRzIHRvPC9wcmU+PC9saT4KICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM3NjEyOCcsICdwb3N0MTQwNTE1NzMwMzc2MTI4JykiPjxwcmU+ICAgICMgYmUgb3ZlcnJpZGRlbi48L3ByZT48L2xpPgogICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzc2MTI4JywgJ3Bvc3QxNDA1MTU3MzAzNzYxMjgnKSI+PHByZT4gICAgZGVmIGRpc3BhdGNoKHNlbGYsIHJlcXVlc3QsICphcmdzLCAqKmt3YXJncyk6PC9wcmU+PC9saT4KICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM3NjEyOCcsICdwb3N0MTQwNTE1NzMwMzc2MTI4JykiPjxwcmU+ICAgICAgICAmcXVvdDsmcXVvdDsmcXVvdDs8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICA8L29sPgogICAgICAgICAgICAgIAogICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgIAoKICAgICAgICAgIAogICAgICAgICAgICAKICAgICAgICAgICAgICA8ZGV0YWlscz4KICAgICAgICAgICAgICAgIDxzdW1tYXJ5IGNsYXNzPSJjb21tYW5kcyI+TG9jYWwgdmFyczwvc3VtbWFyeT4KICAgICAgICAgICAgCiAgICAgICAgICAgIDx0YWJsZSBjbGFzcz0idmFycyIgaWQ9InYxNDA1MTU3MzAzNzYxMjgiPgogICAgICAgICAgICAgIDx0aGVhZD4KICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgPHRoPlZhcmlhYmxlPC90aD4KICAgICAgICAgICAgICAgICAgPHRoPlZhbHVlPC90aD4KICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgPC90aGVhZD4KICAgICAgICAgICAgICA8dGJvZHk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgIDx0ZD5leGM8L3RkPgogICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iY29kZSI+PHByZT5KU09ORGVjb2RlRXJyb3IoJiN4Mjc7RXhwZWN0aW5nIHZhbHVlOiBsaW5lIDEgY29sdW1uIDEgKGNoYXIgMCkmI3gyNzspPC9wcmU+PC90ZD4KICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgPHRkPnJlbmRlcmVyX2Zvcm1hdDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJjb2RlIj48cHJlPiYjeDI3O2pzb24mI3gyNzs8L3ByZT48L3RkPgogICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICA8dGQ+cmVxdWVzdDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJjb2RlIj48cHJlPiZsdDtyZXN0X2ZyYW1ld29yay5yZXF1ZXN0LlJlcXVlc3Q6IFBPU1QgJiN4Mjc7L2RqYW5nb2xkcC1kZmMvd2ViaG9vay8mI3gyNzsmZ3Q7PC9wcmU+PC90ZD4KICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgPHRkPnNlbGY8L3RkPgogICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iY29kZSI+PHByZT4mbHQ7ZGF0YV9mb29kX2NvbnNvcnRpdW0udmlld3MuQ2FjaGVXZWJob29rVmlldyBvYmplY3QgYXQgMHg3ZmNjNWUzMWM5MTAmZ3Q7PC9wcmU+PC90ZD4KICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgPHRkPnVzZV9wbGFpbnRleHRfdHJhY2ViYWNrPC90ZD4KICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImNvZGUiPjxwcmU+VHJ1ZTwvcHJlPjwvdGQ+CiAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICA8L3RhYmxlPgogICAgICAgICAgICA8L2RldGFpbHM+CiAgICAgICAgICAKICAgICAgICA8L2xpPgogICAgICAKICAgICAgICAKICAgICAgICA8bGkgY2xhc3M9ImZyYW1lIHVzZXIiPgogICAgICAgICAgCiAgICAgICAgICAgIDxjb2RlIGNsYXNzPSJmbmFtZSI+L2hvbWUvY3FjbS1wcm94eS1kZXYvc3RhcnRpbmJsb3gvdmVudi9saWIvcHl0aG9uMy4xMS9zaXRlLXBhY2thZ2VzL3Jlc3RfZnJhbWV3b3JrL3ZpZXdzLnB5PC9jb2RlPiwgbGluZSA1MDYsIGluIGRpc3BhdGNoCiAgICAgICAgICAKCiAgICAgICAgICAKICAgICAgICAgICAgPGRpdiBjbGFzcz0iY29udGV4dCIgaWQ9ImMxNDA1MTU3MzAzODI4NDgiPgogICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgPG9sIHN0YXJ0PSI0OTkiIGNsYXNzPSJwcmUtY29udGV4dCIgaWQ9InByZTE0MDUxNTczMDM4Mjg0OCI+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzgyODQ4JywgJ3Bvc3QxNDA1MTU3MzAzODI4NDgnKSI+PHByZT4gICAgICAgICAgICAjIEdldCB0aGUgYXBwcm9wcmlhdGUgaGFuZGxlciBtZXRob2Q8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4Mjg0OCcsICdwb3N0MTQwNTE1NzMwMzgyODQ4JykiPjxwcmU+ICAgICAgICAgICAgaWYgcmVxdWVzdC5tZXRob2QubG93ZXIoKSBpbiBzZWxmLmh0dHBfbWV0aG9kX25hbWVzOjwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzgyODQ4JywgJ3Bvc3QxNDA1MTU3MzAzODI4NDgnKSI+PHByZT4gICAgICAgICAgICAgICAgaGFuZGxlciA9IGdldGF0dHIoc2VsZiwgcmVxdWVzdC5tZXRob2QubG93ZXIoKSw8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4Mjg0OCcsICdwb3N0MTQwNTE1NzMwMzgyODQ4JykiPjxwcmU+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlbGYuaHR0cF9tZXRob2Rfbm90X2FsbG93ZWQpPC9wcmU+PC9saT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzODI4NDgnLCAncG9zdDE0MDUxNTczMDM4Mjg0OCcpIj48cHJlPiAgICAgICAgICAgIGVsc2U6PC9wcmU+PC9saT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzODI4NDgnLCAncG9zdDE0MDUxNTczMDM4Mjg0OCcpIj48cHJlPiAgICAgICAgICAgICAgICBoYW5kbGVyID0gc2VsZi5odHRwX21ldGhvZF9ub3RfYWxsb3dlZDwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzgyODQ4JywgJ3Bvc3QxNDA1MTU3MzAzODI4NDgnKSI+PHByZT48L3ByZT48L2xpPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICA8L29sPgogICAgICAgICAgICAgIAogICAgICAgICAgICAgIDxvbCBzdGFydD0iNTA2IiBjbGFzcz0iY29udGV4dC1saW5lIj4KICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4Mjg0OCcsICdwb3N0MTQwNTE1NzMwMzgyODQ4JykiPjxwcmU+ICAgICAgICAgICAgcmVzcG9uc2UgPSBoYW5kbGVyKHJlcXVlc3QsICphcmdzLCAqKmt3YXJncykKICAgICAgICAgICAgICAgICAgICAgICAgICAgIF5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXjwvcHJlPiA8c3Bhbj7igKY8L3NwYW4+PC9saT4KICAgICAgICAgICAgICA8L29sPgogICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgPG9sIHN0YXJ0PSc1MDcnIGNsYXNzPSJwb3N0LWNvbnRleHQiIGlkPSJwb3N0MTQwNTE1NzMwMzgyODQ4Ij4KICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4Mjg0OCcsICdwb3N0MTQwNTE1NzMwMzgyODQ4JykiPjxwcmU+PC9wcmU+PC9saT4KICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4Mjg0OCcsICdwb3N0MTQwNTE1NzMwMzgyODQ4JykiPjxwcmU+ICAgICAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGV4Yzo8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzgyODQ4JywgJ3Bvc3QxNDA1MTU3MzAzODI4NDgnKSI+PHByZT4gICAgICAgICAgICByZXNwb25zZSA9IHNlbGYuaGFuZGxlX2V4Y2VwdGlvbihleGMpPC9wcmU+PC9saT4KICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4Mjg0OCcsICdwb3N0MTQwNTE1NzMwMzgyODQ4JykiPjxwcmU+PC9wcmU+PC9saT4KICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4Mjg0OCcsICdwb3N0MTQwNTE1NzMwMzgyODQ4JykiPjxwcmU+ICAgICAgICBzZWxmLnJlc3BvbnNlID0gc2VsZi5maW5hbGl6ZV9yZXNwb25zZShyZXF1ZXN0LCByZXNwb25zZSwgKmFyZ3MsICoqa3dhcmdzKTwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzODI4NDgnLCAncG9zdDE0MDUxNTczMDM4Mjg0OCcpIj48cHJlPiAgICAgICAgcmV0dXJuIHNlbGYucmVzcG9uc2U8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICA8L29sPgogICAgICAgICAgICAgIAogICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgIAoKICAgICAgICAgIAogICAgICAgICAgICAKICAgICAgICAgICAgICA8ZGV0YWlscz4KICAgICAgICAgICAgICAgIDxzdW1tYXJ5IGNsYXNzPSJjb21tYW5kcyI+TG9jYWwgdmFyczwvc3VtbWFyeT4KICAgICAgICAgICAgCiAgICAgICAgICAgIDx0YWJsZSBjbGFzcz0idmFycyIgaWQ9InYxNDA1MTU3MzAzODI4NDgiPgogICAgICAgICAgICAgIDx0aGVhZD4KICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgPHRoPlZhcmlhYmxlPC90aD4KICAgICAgICAgICAgICAgICAgPHRoPlZhbHVlPC90aD4KICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgPC90aGVhZD4KICAgICAgICAgICAgICA8dGJvZHk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgIDx0ZD5hcmdzPC90ZD4KICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImNvZGUiPjxwcmU+KCk8L3ByZT48L3RkPgogICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICA8dGQ+aGFuZGxlcjwvdGQ+CiAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJjb2RlIj48cHJlPiZsdDtib3VuZCBtZXRob2QgQ2FjaGVXZWJob29rVmlldy5wb3N0IG9mICZsdDtkYXRhX2Zvb2RfY29uc29ydGl1bS52aWV3cy5DYWNoZVdlYmhvb2tWaWV3IG9iamVjdCBhdCAweDdmY2M1ZTMxYzkxMCZndDsmZ3Q7PC9wcmU+PC90ZD4KICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgPHRkPmt3YXJnczwvdGQ+CiAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJjb2RlIj48cHJlPnt9PC9wcmU+PC90ZD4KICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgPHRkPnJlcXVlc3Q8L3RkPgogICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iY29kZSI+PHByZT4mbHQ7cmVzdF9mcmFtZXdvcmsucmVxdWVzdC5SZXF1ZXN0OiBQT1NUICYjeDI3Oy9kamFuZ29sZHAtZGZjL3dlYmhvb2svJiN4Mjc7Jmd0OzwvcHJlPjwvdGQ+CiAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgIDx0ZD5zZWxmPC90ZD4KICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImNvZGUiPjxwcmU+Jmx0O2RhdGFfZm9vZF9jb25zb3J0aXVtLnZpZXdzLkNhY2hlV2ViaG9va1ZpZXcgb2JqZWN0IGF0IDB4N2ZjYzVlMzFjOTEwJmd0OzwvcHJlPjwvdGQ+CiAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICA8L3RhYmxlPgogICAgICAgICAgICA8L2RldGFpbHM+CiAgICAgICAgICAKICAgICAgICA8L2xpPgogICAgICAKICAgICAgICAKICAgICAgICA8bGkgY2xhc3M9ImZyYW1lIHVzZXIiPgogICAgICAgICAgCiAgICAgICAgICAgIDxjb2RlIGNsYXNzPSJmbmFtZSI+L2hvbWUvY3FjbS1wcm94eS1kZXYvc3RhcnRpbmJsb3gvc2lic2VydmVyL2RhdGFfZm9vZF9jb25zb3J0aXVtL3ZpZXdzLnB5PC9jb2RlPiwgbGluZSA5MCwgaW4gcG9zdAogICAgICAgICAgCgogICAgICAgICAgCiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbnRleHQiIGlkPSJjMTQwNTE1NzMwMzgyOTc2Ij4KICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgIDxvbCBzdGFydD0iODMiIGNsYXNzPSJwcmUtY29udGV4dCIgaWQ9InByZTE0MDUxNTczMDM4Mjk3NiI+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzgyOTc2JywgJ3Bvc3QxNDA1MTU3MzAzODI5NzYnKSI+PHByZT4gICAgICAgICAgICAgICAgICAgIHJldHVybiBSZXNwb25zZSg8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4Mjk3NicsICdwb3N0MTQwNTE1NzMwMzgyOTc2JykiPjxwcmU+ICAgICAgICAgICAgICAgICAgICAgICAgezwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzgyOTc2JywgJ3Bvc3QxNDA1MTU3MzAzODI5NzYnKSI+PHByZT4gICAgICAgICAgICAgICAgICAgICAgICAgICAgJnF1b3Q7ZXJyb3ImcXVvdDs6ICZxdW90O09iamVjdHMgc2hvdWxkIGJlIHNlcmlhbGlzZWQgd2l0aCBvbmx5IEBpZCBhbmQgQHR5cGUmcXVvdDs8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4Mjk3NicsICdwb3N0MTQwNTE1NzMwMzgyOTc2JykiPjxwcmU+ICAgICAgICAgICAgICAgICAgICAgICAgfSw8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4Mjk3NicsICdwb3N0MTQwNTE1NzMwMzgyOTc2JykiPjxwcmU+ICAgICAgICAgICAgICAgICAgICAgICAgc3RhdHVzPTQwMCw8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4Mjk3NicsICdwb3N0MTQwNTE1NzMwMzgyOTc2JykiPjxwcmU+ICAgICAgICAgICAgICAgICAgICApPC9wcmU+PC9saT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzODI5NzYnLCAncG9zdDE0MDUxNTczMDM4Mjk3NicpIj48cHJlPjwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgIDwvb2w+CiAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgPG9sIHN0YXJ0PSI5MCIgY2xhc3M9ImNvbnRleHQtbGluZSI+CiAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzODI5NzYnLCAncG9zdDE0MDUxNTczMDM4Mjk3NicpIj48cHJlPiAgICAgICAgc2VsZi5wcm9jZXNzKHJlcXVlc3QsIGRhdGEpCiAgICAgICAgICAgIF5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXjwvcHJlPiA8c3Bhbj7igKY8L3NwYW4+PC9saT4KICAgICAgICAgICAgICA8L29sPgogICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgPG9sIHN0YXJ0PSc5MScgY2xhc3M9InBvc3QtY29udGV4dCIgaWQ9InBvc3QxNDA1MTU3MzAzODI5NzYiPgogICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzgyOTc2JywgJ3Bvc3QxNDA1MTU3MzAzODI5NzYnKSI+PHByZT4gICAgICAgIHJldHVybiBSZXNwb25zZSh7fSwgc3RhdHVzPTIwMCk8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzgyOTc2JywgJ3Bvc3QxNDA1MTU3MzAzODI5NzYnKSI+PHByZT48L3ByZT48L2xpPgogICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzgyOTc2JywgJ3Bvc3QxNDA1MTU3MzAzODI5NzYnKSI+PHByZT48L3ByZT48L2xpPgogICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzgyOTc2JywgJ3Bvc3QxNDA1MTU3MzAzODI5NzYnKSI+PHByZT5jbGFzcyBFbnRlcnByaXNlSW1wb3J0VmlldyhCYXNlQ1NWSW1wb3J0Vmlldyk6PC9wcmU+PC9saT4KICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4Mjk3NicsICdwb3N0MTQwNTE1NzMwMzgyOTc2JykiPjxwcmU+ICAgIGRlZiBnZXRfZm9ybV9jbGFzcyhzZWxmLCAqYXJncywgKiprd2FyZ3MpOjwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzODI5NzYnLCAncG9zdDE0MDUxNTczMDM4Mjk3NicpIj48cHJlPiAgICAgICAgcmV0dXJuIEVudGVycHJpc2VJbXBvcnRGb3JtKCphcmdzLCAqKmt3YXJncyk8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICA8L29sPgogICAgICAgICAgICAgIAogICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgIAoKICAgICAgICAgIAogICAgICAgICAgICAKICAgICAgICAgICAgICA8ZGV0YWlscz4KICAgICAgICAgICAgICAgIDxzdW1tYXJ5IGNsYXNzPSJjb21tYW5kcyI+TG9jYWwgdmFyczwvc3VtbWFyeT4KICAgICAgICAgICAgCiAgICAgICAgICAgIDx0YWJsZSBjbGFzcz0idmFycyIgaWQ9InYxNDA1MTU3MzAzODI5NzYiPgogICAgICAgICAgICAgIDx0aGVhZD4KICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgPHRoPlZhcmlhYmxlPC90aD4KICAgICAgICAgICAgICAgICAgPHRoPlZhbHVlPC90aD4KICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgPC90aGVhZD4KICAgICAgICAgICAgICA8dGJvZHk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgIDx0ZD5hcmdzPC90ZD4KICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImNvZGUiPjxwcmU+KCk8L3ByZT48L3RkPgogICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICA8dGQ+ZGF0YTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJjb2RlIj48cHJlPnsmI3gyNztlbnRlcnByaXNlVXJsaWQmI3gyNzs6ICYjeDI3O2h0dHA6Ly90ZXN0Lmhvc3QvYXBpL2RmYy9lbnRlcnByaXNlcyYjeDI3OywKICYjeDI3O2V2ZW50VHlwZSYjeDI3OzogJiN4Mjc7cmVmcmVzaCYjeDI3OywKICYjeDI3O3Njb3BlJiN4Mjc7OiAmI3gyNztSZWFkRW50ZXJwcmlzZSYjeDI3O308L3ByZT48L3RkPgogICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICA8dGQ+a3dhcmdzPC90ZD4KICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImNvZGUiPjxwcmU+e308L3ByZT48L3RkPgogICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICA8dGQ+cmVxdWVzdDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJjb2RlIj48cHJlPiZsdDtyZXN0X2ZyYW1ld29yay5yZXF1ZXN0LlJlcXVlc3Q6IFBPU1QgJiN4Mjc7L2RqYW5nb2xkcC1kZmMvd2ViaG9vay8mI3gyNzsmZ3Q7PC9wcmU+PC90ZD4KICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgPHRkPnNlbGY8L3RkPgogICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iY29kZSI+PHByZT4mbHQ7ZGF0YV9mb29kX2NvbnNvcnRpdW0udmlld3MuQ2FjaGVXZWJob29rVmlldyBvYmplY3QgYXQgMHg3ZmNjNWUzMWM5MTAmZ3Q7PC9wcmU+PC90ZD4KICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICAgIDwvdGFibGU+CiAgICAgICAgICAgIDwvZGV0YWlscz4KICAgICAgICAgIAogICAgICAgIDwvbGk+CiAgICAgIAogICAgICAgIAogICAgICAgIDxsaSBjbGFzcz0iZnJhbWUgdXNlciI+CiAgICAgICAgICAKICAgICAgICAgICAgPGNvZGUgY2xhc3M9ImZuYW1lIj4vaG9tZS9jcWNtLXByb3h5LWRldi9zdGFydGluYmxveC9zaWJzZXJ2ZXIvZGF0YV9mb29kX2NvbnNvcnRpdW0vdmlld3MucHk8L2NvZGU+LCBsaW5lIDQ2LCBpbiBwcm9jZXNzCiAgICAgICAgICAKCiAgICAgICAgICAKICAgICAgICAgICAgPGRpdiBjbGFzcz0iY29udGV4dCIgaWQ9ImMxNDA1MTU3MzAzODE3NjAiPgogICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgPG9sIHN0YXJ0PSIzOSIgY2xhc3M9InByZS1jb250ZXh0IiBpZD0icHJlMTQwNTE1NzMwMzgxNzYwIj4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzODE3NjAnLCAncG9zdDE0MDUxNTczMDM4MTc2MCcpIj48cHJlPiAgICBkZWYgcHJvY2VzcyhzZWxmLCByZXF1ZXN0LCBkYXRhKTo8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4MTc2MCcsICdwb3N0MTQwNTE1NzMwMzgxNzYwJykiPjxwcmU+ICAgICAgICBpZiBkYXRhWyZxdW90O2V2ZW50VHlwZSZxdW90O10gPT0gV2ViaG9va0V2ZW50VHlwZS5VUERBVEU6PC9wcmU+PC9saT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzODE3NjAnLCAncG9zdDE0MDUxNTczMDM4MTc2MCcpIj48cHJlPiAgICAgICAgICAgICMgUGFyc2UgYW5kIGltcG9ydCB0aGUgZ3JhcGguPC9wcmU+PC9saT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzODE3NjAnLCAncG9zdDE0MDUxNTczMDM4MTc2MCcpIj48cHJlPiAgICAgICAgICAgICMgVE9ETzogdHJpZ2dlciBvcHRpb25hbCBiZWhhdmlvdXIgaW4gdGhlIHBhcnNlciB0byBmYWlsIGxvdWRseS48L3ByZT48L2xpPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4MTc2MCcsICdwb3N0MTQwNTE1NzMwMzgxNzYwJykiPjxwcmU+ICAgICAgICAgICAgUHJveHlSZWZyZXNoUGFyc2VyKGRhdGFbJnF1b3Q7QGlkJnF1b3Q7XSkucGFyc2UoZGF0YSk8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4MTc2MCcsICdwb3N0MTQwNTE1NzMwMzgxNzYwJykiPjxwcmU+ICAgICAgICBlbGlmIGRhdGFbJnF1b3Q7ZXZlbnRUeXBlJnF1b3Q7XSA9PSBXZWJob29rRXZlbnRUeXBlLlJFRlJFU0g6PC9wcmU+PC9saT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzODE3NjAnLCAncG9zdDE0MDUxNTczMDM4MTc2MCcpIj48cHJlPiAgICAgICAgICAgIGhvc3QgPSB1cmxwYXJzZShyZXF1ZXN0LnBsYXRmb3JtX3VybGlkKTwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgIDwvb2w+CiAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgPG9sIHN0YXJ0PSI0NiIgY2xhc3M9ImNvbnRleHQtbGluZSI+CiAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzODE3NjAnLCAncG9zdDE0MDUxNTczMDM4MTc2MCcpIj48cHJlPiAgICAgICAgICAgIFJlc291cmNlU2VydmVyQ2xpZW50KGYmcXVvdDt7aG9zdC5zY2hlbWV9Oi8ve2hvc3QubmV0bG9jfS8mcXVvdDspLnJlcXVlc3Rfc2NvcGUoCiAgICAgICAgICAgICAgICBePC9wcmU+IDxzcGFuPuKApjwvc3Bhbj48L2xpPgogICAgICAgICAgICAgIDwvb2w+CiAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICA8b2wgc3RhcnQ9JzQ3JyBjbGFzcz0icG9zdC1jb250ZXh0IiBpZD0icG9zdDE0MDUxNTczMDM4MTc2MCI+CiAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzODE3NjAnLCAncG9zdDE0MDUxNTczMDM4MTc2MCcpIj48cHJlPiAgICAgICAgICAgICAgICBkYXRhWyZxdW90O3Njb3BlJnF1b3Q7XTwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzODE3NjAnLCAncG9zdDE0MDUxNTczMDM4MTc2MCcpIj48cHJlPiAgICAgICAgICAgICk8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzgxNzYwJywgJ3Bvc3QxNDA1MTU3MzAzODE3NjAnKSI+PHByZT4gICAgICAgIGVsaWYgZGF0YVsmcXVvdDtldmVudFR5cGUmcXVvdDtdID09IFdlYmhvb2tFdmVudFR5cGUuUkVWT0tFOjwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzODE3NjAnLCAncG9zdDE0MDUxNTczMDM4MTc2MCcpIj48cHJlPiAgICAgICAgICAgIGZvciBvYmogaW4gZGF0YVsmcXVvdDtvYmplY3RzJnF1b3Q7XTo8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzgxNzYwJywgJ3Bvc3QxNDA1MTU3MzAzODE3NjAnKSI+PHByZT4gICAgICAgICAgICAgICAgTW9kZWwuZ2V0X3N1YmNsYXNzX3dpdGhfcmRmX3R5cGUob2JqWyZxdW90O0B0eXBlJnF1b3Q7XSkub2JqZWN0cy5maWx0ZXIoPC9wcmU+PC9saT4KICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4MTc2MCcsICdwb3N0MTQwNTE1NzMwMzgxNzYwJykiPjxwcmU+ICAgICAgICAgICAgICAgICAgICBwcm94eV9vZj1vYmpbJnF1b3Q7QGlkJnF1b3Q7XTwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgIDwvb2w+CiAgICAgICAgICAgICAgCiAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgCgogICAgICAgICAgCiAgICAgICAgICAgIAogICAgICAgICAgICAgIDxkZXRhaWxzPgogICAgICAgICAgICAgICAgPHN1bW1hcnkgY2xhc3M9ImNvbW1hbmRzIj5Mb2NhbCB2YXJzPC9zdW1tYXJ5PgogICAgICAgICAgICAKICAgICAgICAgICAgPHRhYmxlIGNsYXNzPSJ2YXJzIiBpZD0idjE0MDUxNTczMDM4MTc2MCI+CiAgICAgICAgICAgICAgPHRoZWFkPgogICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICA8dGg+VmFyaWFibGU8L3RoPgogICAgICAgICAgICAgICAgICA8dGg+VmFsdWU8L3RoPgogICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICA8L3RoZWFkPgogICAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgPHRkPmRhdGE8L3RkPgogICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iY29kZSI+PHByZT57JiN4Mjc7ZW50ZXJwcmlzZVVybGlkJiN4Mjc7OiAmI3gyNztodHRwOi8vdGVzdC5ob3N0L2FwaS9kZmMvZW50ZXJwcmlzZXMmI3gyNzssCiAmI3gyNztldmVudFR5cGUmI3gyNzs6ICYjeDI3O3JlZnJlc2gmI3gyNzssCiAmI3gyNztzY29wZSYjeDI3OzogJiN4Mjc7UmVhZEVudGVycHJpc2UmI3gyNzt9PC9wcmU+PC90ZD4KICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgPHRkPmhvc3Q8L3RkPgogICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iY29kZSI+PHByZT5QYXJzZVJlc3VsdChzY2hlbWU9JiN4Mjc7aHR0cHMmI3gyNzssIG5ldGxvYz0mI3gyNztzdGFnaW5nLm9wZW5mb29kbmV0d29yay5vcmcudWsmI3gyNzssIHBhdGg9JiN4Mjc7LyYjeDI3OywgcGFyYW1zPSYjeDI3OyYjeDI3OywgcXVlcnk9JiN4Mjc7JiN4Mjc7LCBmcmFnbWVudD0mI3gyNzsmI3gyNzspPC9wcmU+PC90ZD4KICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgPHRkPnJlcXVlc3Q8L3RkPgogICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iY29kZSI+PHByZT4mbHQ7cmVzdF9mcmFtZXdvcmsucmVxdWVzdC5SZXF1ZXN0OiBQT1NUICYjeDI3Oy9kamFuZ29sZHAtZGZjL3dlYmhvb2svJiN4Mjc7Jmd0OzwvcHJlPjwvdGQ+CiAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgIDx0ZD5zZWxmPC90ZD4KICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImNvZGUiPjxwcmU+Jmx0O2RhdGFfZm9vZF9jb25zb3J0aXVtLnZpZXdzLkNhY2hlV2ViaG9va1ZpZXcgb2JqZWN0IGF0IDB4N2ZjYzVlMzFjOTEwJmd0OzwvcHJlPjwvdGQ+CiAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICA8L3RhYmxlPgogICAgICAgICAgICA8L2RldGFpbHM+CiAgICAgICAgICAKICAgICAgICA8L2xpPgogICAgICAKICAgICAgICAKICAgICAgICA8bGkgY2xhc3M9ImZyYW1lIHVzZXIiPgogICAgICAgICAgCiAgICAgICAgICAgIDxjb2RlIGNsYXNzPSJmbmFtZSI+L2hvbWUvY3FjbS1wcm94eS1kZXYvc3RhcnRpbmJsb3gvc2lic2VydmVyL2RhdGFfZm9vZF9jb25zb3J0aXVtL3Byb3h5L3Jlc291cmNlLnB5PC9jb2RlPiwgbGluZSAzMjIsIGluIHJlcXVlc3Rfc2NvcGUKICAgICAgICAgIAoKICAgICAgICAgIAogICAgICAgICAgICA8ZGl2IGNsYXNzPSJjb250ZXh0IiBpZD0iYzE0MDUxNTczMDM4MzYxNiI+CiAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICA8b2wgc3RhcnQ9IjMxNSIgY2xhc3M9InByZS1jb250ZXh0IiBpZD0icHJlMTQwNTE1NzMwMzgzNjE2Ij4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzODM2MTYnLCAncG9zdDE0MDUxNTczMDM4MzYxNicpIj48cHJlPjwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzgzNjE2JywgJ3Bvc3QxNDA1MTU3MzAzODM2MTYnKSI+PHByZT4gICAgICAgIDpyYWlzZXMgS2V5Y2xvYWtBdXRoZW50aWNhdGlvbkV4Y2VwdGlvbjogaWYgYXV0aGVudGljYXRpb24gd2l0aCBLZXljbG9hayBpcyB1bnN1Y2Nlc3NmdWw8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4MzYxNicsICdwb3N0MTQwNTE1NzMwMzgzNjE2JykiPjxwcmU+ICAgICAgICA6cmFpc2VzIFJlcXVlc3RFeGNlcHRpb246IGlmIGRhdGFzZXJ2ZXIgcmVxdWVzdCBpcyB1bnN1Y2Nlc3NmdWw8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4MzYxNicsICdwb3N0MTQwNTE1NzMwMzgzNjE2JykiPjxwcmU+ICAgICAgICAmcXVvdDsmcXVvdDsmcXVvdDs8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4MzYxNicsICdwb3N0MTQwNTE1NzMwMzgzNjE2JykiPjxwcmU+ICAgICAgICAjIEVhY2ggc2NvcGUgaGFzIGFuIGFzc29jaWF0ZWQgZW5kcG9pbnQuPC9wcmU+PC9saT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzODM2MTYnLCAncG9zdDE0MDUxNTczMDM4MzYxNicpIj48cHJlPiAgICAgICAgZW5kcG9pbnQgPSBmJnF1b3Q7e3NlbGYuZGF0YXNlcnZlcl91cmx9e3NlbGYuc2NvcGVfY29uZmlnW3Njb3BlXX0mcXVvdDs8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDM4MzYxNicsICdwb3N0MTQwNTE1NzMwMzgzNjE2JykiPjxwcmU+ICAgICAgICBwYXJzZXIgPSBQcm94eVJlZnJlc2hQYXJzZXIoZW5kcG9pbnQpPC9wcmU+PC9saT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgPC9vbD4KICAgICAgICAgICAgICAKICAgICAgICAgICAgICA8b2wgc3RhcnQ9IjMyMiIgY2xhc3M9ImNvbnRleHQtbGluZSI+CiAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzAzODM2MTYnLCAncG9zdDE0MDUxNTczMDM4MzYxNicpIj48cHJlPiAgICAgICAgc2VsZi5fcmVxdWVzdF9hbmRfcHJvY2Vzc19zY29wZV9hdF9lbmRwb2ludChwYXJzZXIsIHNjb3BlLCBlbmRwb2ludCkKICAgICAgICAgICAgIF5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5ePC9wcmU+IDxzcGFuPuKApjwvc3Bhbj48L2xpPgogICAgICAgICAgICAgIDwvb2w+CiAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICA8b2wgc3RhcnQ9JzMyMycgY2xhc3M9InBvc3QtY29udGV4dCIgaWQ9InBvc3QxNDA1MTU3MzAzODM2MTYiPgogICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwMzgzNjE2JywgJ3Bvc3QxNDA1MTU3MzAzODM2MTYnKSI+PHByZT4gICAgICAgIHBhcnNlci5jbGVhbl91cCgpPC9wcmU+PC9saT4KICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgPC9vbD4KICAgICAgICAgICAgICAKICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAKCiAgICAgICAgICAKICAgICAgICAgICAgCiAgICAgICAgICAgICAgPGRldGFpbHM+CiAgICAgICAgICAgICAgICA8c3VtbWFyeSBjbGFzcz0iY29tbWFuZHMiPkxvY2FsIHZhcnM8L3N1bW1hcnk+CiAgICAgICAgICAgIAogICAgICAgICAgICA8dGFibGUgY2xhc3M9InZhcnMiIGlkPSJ2MTQwNTE1NzMwMzgzNjE2Ij4KICAgICAgICAgICAgICA8dGhlYWQ+CiAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgIDx0aD5WYXJpYWJsZTwvdGg+CiAgICAgICAgICAgICAgICAgIDx0aD5WYWx1ZTwvdGg+CiAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgIDwvdGhlYWQ+CiAgICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICA8dGQ+ZW5kcG9pbnQ8L3RkPgogICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iY29kZSI+PHByZT4mI3gyNzs8SElEREVOLU9QRU5JRF9BUFBfSUQ+ZW50ZXJwcmlzZXMvJiN4Mjc7PC9wcmU+PC90ZD4KICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgPHRkPnBhcnNlcjwvdGQ+CiAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJjb2RlIj48cHJlPiZsdDtkYXRhX2Zvb2RfY29uc29ydGl1bS5wcm94eS5yZXNvdXJjZS5Qcm94eVJlZnJlc2hQYXJzZXIgb2JqZWN0IGF0IDB4N2ZjYzVlMzJjOTkwJmd0OzwvcHJlPjwvdGQ+CiAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgIDx0ZD5zY29wZTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJjb2RlIj48cHJlPiYjeDI3O1JlYWRFbnRlcnByaXNlJiN4Mjc7PC9wcmU+PC90ZD4KICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgPHRkPnNlbGY8L3RkPgogICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iY29kZSI+PHByZT4mbHQ7ZGF0YV9mb29kX2NvbnNvcnRpdW0ucHJveHkucmVzb3VyY2UuUmVzb3VyY2VTZXJ2ZXJDbGllbnQgb2JqZWN0IGF0IDB4N2ZjYzVlMzFkZjkwJmd0OzwvcHJlPjwvdGQ+CiAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICA8L3RhYmxlPgogICAgICAgICAgICA8L2RldGFpbHM+CiAgICAgICAgICAKICAgICAgICA8L2xpPgogICAgICAKICAgICAgICAKICAgICAgICA8bGkgY2xhc3M9ImZyYW1lIHVzZXIiPgogICAgICAgICAgCiAgICAgICAgICAgIDxjb2RlIGNsYXNzPSJmbmFtZSI+L2hvbWUvY3FjbS1wcm94eS1kZXYvc3RhcnRpbmJsb3gvc2lic2VydmVyL2RhdGFfZm9vZF9jb25zb3J0aXVtL3Byb3h5L3Jlc291cmNlLnB5PC9jb2RlPiwgbGluZSAzMDMsIGluIF9yZXF1ZXN0X2FuZF9wcm9jZXNzX3Njb3BlX2F0X2VuZHBvaW50CiAgICAgICAgICAKCiAgICAgICAgICAKICAgICAgICAgICAgPGRpdiBjbGFzcz0iY29udGV4dCIgaWQ9ImMxNDA1MTU3MzA0NDk2NjQiPgogICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgPG9sIHN0YXJ0PSIyOTYiIGNsYXNzPSJwcmUtY29udGV4dCIgaWQ9InByZTE0MDUxNTczMDQ0OTY2NCI+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwNDQ5NjY0JywgJ3Bvc3QxNDA1MTU3MzA0NDk2NjQnKSI+PHByZT4gICAgICAgIFJlcXVlc3RzIGFuIGFjY2VzcyB0b2tlbiBmcm9tIEtleWNsb2FrIGZvciBhIGdpdmVuIHNjb3BlLDwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwNDQ5NjY0JywgJ3Bvc3QxNDA1MTU3MzA0NDk2NjQnKSI+PHByZT4gICAgICAgIGFuZCB0aGVuIHJlY3Vyc2l2ZWx5IHJlcXVlc3RzIGZyb20gdGhlIGRhdGFzZXJ2ZXIgdGhlIGFzc29jaWF0ZWQgZW5kcG9pbnQsPC9wcmU+PC9saT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzA0NDk2NjQnLCAncG9zdDE0MDUxNTczMDQ0OTY2NCcpIj48cHJlPiAgICAgICAgc2NyYXBpbmcgYWxsIGF2YWlsYWJsZSBkYXRhIHVudGlsIGNvbXBsZXRlLjwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwNDQ5NjY0JywgJ3Bvc3QxNDA1MTU3MzA0NDk2NjQnKSI+PHByZT4gICAgICAgICZxdW90OyZxdW90OyZxdW90OzwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwNDQ5NjY0JywgJ3Bvc3QxNDA1MTU3MzA0NDk2NjQnKSI+PHByZT4gICAgICAgIGhlYWRlcnMgPSBzZWxmLl9nZXRfYXV0aF9oZWFkZXJzX3dpdGhfdG9rZW5fZm9yX3Njb3BlKHNjb3BlKTwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwNDQ5NjY0JywgJ3Bvc3QxNDA1MTU3MzA0NDk2NjQnKSI+PHByZT4gICAgICAgIHJlc3BvbnNlID0gcmVxdWVzdHMuZ2V0KGVuZHBvaW50LCBoZWFkZXJzPWhlYWRlcnMpPC9wcmU+PC9saT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzA0NDk2NjQnLCAncG9zdDE0MDUxNTczMDQ0OTY2NCcpIj48cHJlPiAgICAgICAgcmVzcG9uc2UucmFpc2VfZm9yX3N0YXR1cygpPC9wcmU+PC9saT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgPC9vbD4KICAgICAgICAgICAgICAKICAgICAgICAgICAgICA8b2wgc3RhcnQ9IjMwMyIgY2xhc3M9ImNvbnRleHQtbGluZSI+CiAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzA0NDk2NjQnLCAncG9zdDE0MDUxNTczMDQ0OTY2NCcpIj48cHJlPiAgICAgICAgZGF0YSA9IHJlc3BvbnNlLmpzb24oKQogICAgICAgICAgICAgICAgICAgIF5eXl5eXl5eXl5eXl5eXjwvcHJlPiA8c3Bhbj7igKY8L3NwYW4+PC9saT4KICAgICAgICAgICAgICA8L29sPgogICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgPG9sIHN0YXJ0PSczMDQnIGNsYXNzPSJwb3N0LWNvbnRleHQiIGlkPSJwb3N0MTQwNTE1NzMwNDQ5NjY0Ij4KICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDQ0OTY2NCcsICdwb3N0MTQwNTE1NzMwNDQ5NjY0JykiPjxwcmU+PC9wcmU+PC9saT4KICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDQ0OTY2NCcsICdwb3N0MTQwNTE1NzMwNDQ5NjY0JykiPjxwcmU+ICAgICAgICAjIFBhcnNlIHRoZSByZXR1cm5lZCBncmFwaCwgcmVzb2x2ZSBhbmQgaW1wb3J0IHRvIHRoZSByZWxldmFudCBtb2RlbHMuPC9wcmU+PC9saT4KICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDQ0OTY2NCcsICdwb3N0MTQwNTE1NzMwNDQ5NjY0JykiPjxwcmU+ICAgICAgICBwYXJzZXIucGFyc2UoZGF0YSk8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwNDQ5NjY0JywgJ3Bvc3QxNDA1MTU3MzA0NDk2NjQnKSI+PHByZT48L3ByZT48L2xpPgogICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwNDQ5NjY0JywgJ3Bvc3QxNDA1MTU3MzA0NDk2NjQnKSI+PHByZT4gICAgICAgICMgSWYgdGhlcmUgaXMgbW9yZSBkYXRhLCBjb250aW51ZS48L3ByZT48L2xpPgogICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwNDQ5NjY0JywgJ3Bvc3QxNDA1MTU3MzA0NDk2NjQnKSI+PHByZT4gICAgICAgIGlmICZxdW90O25leHQmcXVvdDsgaW4gZGF0YSBhbmQgZGF0YVsmcXVvdDtuZXh0JnF1b3Q7XSBpcyBub3QgTm9uZTo8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICA8L29sPgogICAgICAgICAgICAgIAogICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgIAoKICAgICAgICAgIAogICAgICAgICAgICAKICAgICAgICAgICAgICA8ZGV0YWlscz4KICAgICAgICAgICAgICAgIDxzdW1tYXJ5IGNsYXNzPSJjb21tYW5kcyI+TG9jYWwgdmFyczwvc3VtbWFyeT4KICAgICAgICAgICAgCiAgICAgICAgICAgIDx0YWJsZSBjbGFzcz0idmFycyIgaWQ9InYxNDA1MTU3MzA0NDk2NjQiPgogICAgICAgICAgICAgIDx0aGVhZD4KICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgPHRoPlZhcmlhYmxlPC90aD4KICAgICAgICAgICAgICAgICAgPHRoPlZhbHVlPC90aD4KICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgPC90aGVhZD4KICAgICAgICAgICAgICA8dGJvZHk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgIDx0ZD5lbmRwb2ludDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJjb2RlIj48cHJlPiYjeDI3OzxISURERU4tT1BFTklEX0FQUF9JRD5lbnRlcnByaXNlcy8mI3gyNzs8L3ByZT48L3RkPgogICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICA8dGQ+aGVhZGVyczwvdGQ+CiAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJjb2RlIj48cHJlPnsmI3gyNztBdXRob3JpemF0aW9uJiN4Mjc7OiAmI3gyNztCZWFyZXIgJiN4Mjc7CiAgICAgICAgICAgICAgICAgICYjeDI3O2V5SmhiR2NpT2lKU1V6STFOaUlzSW5SNWNDSWdPaUFpU2xkVUlpd2lhMmxrSWlBNklDSlJSbEpFUzFkYVEyaHpNbGxRWW5ScVlsOXlRVXR3UXpOek1GbzBUMkZDTUV0RlEwNTZObmx4V0hRMEluMC5leUpsZUhBaU9qRTNOVGcyT0RjeU16WXNJbWxoZENJNk1UYzFPRFk0Tmprek5pd2lhblJwSWpvaU9UVTVNbVkxTWpJdE16bGxZUzAwWm1FMkxUaGlZek10WkRWaFpEWmtNMk0wWWpSaUlpd2lhWE56SWpvaWFIUjBjSE02THk5cll5NWpjV050TG5OMFlYSjBhVzVpYkc5NExtTnZiUzl5WldGc2JYTXZjM1JoY25ScGJtSnNiM2dpTENKaGRXUWlPaUpoWTJOdmRXNTBJaXdpYzNWaUlqb2lOV1psTTJNeU5tTXRNamN6TmkwME9HRTBMV0kyWTJZdFlUbGxNMkpqWm1Oa1pqQXdJaXdpZEhsd0lqb2lRbVZoY21WeUlpd2lZWHB3SWpvaWFIUjBjSE02THk5aGNHa3VjSEp2ZUhrdFpHVjJMbU54WTIwdWMzUmhjblJwYm1Kc2IzZ3VZMjl0TDNCeWIyWnBiR1VpTENKeVpXRnNiVjloWTJObGMzTWlPbnNpY205c1pYTWlPbHNpYjJabWJHbHVaVjloWTJObGMzTWlMQ0oxYldGZllYVjBhRzl5YVhwaGRHbHZiaUlzSW1SbFptRjFiSFF0Y205c1pYTXRjM1JoY25ScGJtSnNiM2dpWFgwc0luSmxjMjkxY21ObFgyRmpZMlZ6Y3lJNmV5SmhZMk52ZFc1MElqcDdJbkp2YkdWeklqcGJJbTFoYm1GblpTMWhZMk52ZFc1MElpd2liV0Z1WVdkbExXRmpZMjkxYm5RdGJHbHVhM01pTENKMmFXVjNMWEJ5YjJacGJHVWlYWDE5TENKelkyOXdaU0k2SWxKbFlXUkZiblJsY25CeWFYTmxJaXdpWTJ4cFpXNTBTRzl6ZENJNklqRTNNaTR4T0M0d0xqRWlMQ0pqYkdsbGJuUkJaR1J5WlhOeklqb2lNVGN5TGpFNExqQXVNU0lzSW1Oc2FXVnVkRjlwWkNJNkltaDBkSEJ6T2k4dllYQnBMbkJ5YjNoNUxXUmxkaTVqY1dOdExuTjBZWEowYVc1aWJHOTRMbU52YlM5d2NtOW1hV3hsSW4wLkU1OEtXZFdCMTlVdkRVVHpFeXBPRnJxVUczNmNyQkQ1bmZDZmZVdnBxd2hhcGt6ZVNiR0QwZTF2b2JMN2hxaWdYZlFna2VSNWVIRVJ6RGJqVndpTVVsUzd2eFRSSnltX1plc0tYUmFzeUpqeGc3SG1WcVpBemlGdmpsenV2OGdndEdqdUxZY2JDd2dxRW5fWU1xd2FMNlZVYXRVLXpOLTBVWjZ3T2toZHlGdGdxQTNQLXMzZzZ6bU9zMEs4Y2VUSEE4Ul9hRFlOSlgwcmZNVGsyR3oxZ0YzSV9GeGNQVmd3RlFXM0RqMVh1cjh2WW1jOV9DbmlmdmFWdzR2MzVmV3BDbjRyWGpNTXpuRW9XcHVRZno4NGZ2QUU1dVNMTUVXWG1tWWdNNUlxaUtjY2VDdTdxSmlKa0VvQ3JzT3NJVTAxdTdEZVF6VzhwUnZ1Vm5JQThTUmZjUSYjeDI3O308L3ByZT48L3RkPgogICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICA8dGQ+cGFyc2VyPC90ZD4KICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImNvZGUiPjxwcmU+Jmx0O2RhdGFfZm9vZF9jb25zb3J0aXVtLnByb3h5LnJlc291cmNlLlByb3h5UmVmcmVzaFBhcnNlciBvYmplY3QgYXQgMHg3ZmNjNWUzMmM5OTAmZ3Q7PC9wcmU+PC90ZD4KICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgPHRkPnJlc3BvbnNlPC90ZD4KICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImNvZGUiPjxwcmU+Jmx0O1Jlc3BvbnNlIFsyMDBdJmd0OzwvcHJlPjwvdGQ+CiAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgIDx0ZD5zY29wZTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJjb2RlIj48cHJlPiYjeDI3O1JlYWRFbnRlcnByaXNlJiN4Mjc7PC9wcmU+PC90ZD4KICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgPHRkPnNlbGY8L3RkPgogICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iY29kZSI+PHByZT4mbHQ7ZGF0YV9mb29kX2NvbnNvcnRpdW0ucHJveHkucmVzb3VyY2UuUmVzb3VyY2VTZXJ2ZXJDbGllbnQgb2JqZWN0IGF0IDB4N2ZjYzVlMzFkZjkwJmd0OzwvcHJlPjwvdGQ+CiAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICA8L3RhYmxlPgogICAgICAgICAgICA8L2RldGFpbHM+CiAgICAgICAgICAKICAgICAgICA8L2xpPgogICAgICAKICAgICAgICAKICAgICAgICA8bGkgY2xhc3M9ImZyYW1lIHVzZXIiPgogICAgICAgICAgCiAgICAgICAgICAgIDxjb2RlIGNsYXNzPSJmbmFtZSI+L2hvbWUvY3FjbS1wcm94eS1kZXYvc3RhcnRpbmJsb3gvdmVudi9saWIvcHl0aG9uMy4xMS9zaXRlLXBhY2thZ2VzL3JlcXVlc3RzL21vZGVscy5weTwvY29kZT4sIGxpbmUgOTc1LCBpbiBqc29uCiAgICAgICAgICAKCiAgICAgICAgICAKICAgICAgICAgICAgPGRpdiBjbGFzcz0iY29udGV4dCIgaWQ9ImMxNDA1MTU3MzA0NDYzMzYiPgogICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgPG9sIHN0YXJ0PSI5NjgiIGNsYXNzPSJwcmUtY29udGV4dCIgaWQ9InByZTE0MDUxNTczMDQ0NjMzNiI+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwNDQ2MzM2JywgJ3Bvc3QxNDA1MTU3MzA0NDYzMzYnKSI+PHByZT4gICAgICAgICAgICAgICAgICAgIHJhaXNlIFJlcXVlc3RzSlNPTkRlY29kZUVycm9yKGUubXNnLCBlLmRvYywgZS5wb3MpPC9wcmU+PC9saT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzA0NDYzMzYnLCAncG9zdDE0MDUxNTczMDQ0NjMzNicpIj48cHJlPjwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwNDQ2MzM2JywgJ3Bvc3QxNDA1MTU3MzA0NDYzMzYnKSI+PHByZT4gICAgICAgIHRyeTo8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDQ0NjMzNicsICdwb3N0MTQwNTE1NzMwNDQ2MzM2JykiPjxwcmU+ICAgICAgICAgICAgcmV0dXJuIGNvbXBsZXhqc29uLmxvYWRzKHNlbGYudGV4dCwgKiprd2FyZ3MpPC9wcmU+PC9saT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzA0NDYzMzYnLCAncG9zdDE0MDUxNTczMDQ0NjMzNicpIj48cHJlPiAgICAgICAgZXhjZXB0IEpTT05EZWNvZGVFcnJvciBhcyBlOjwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwNDQ2MzM2JywgJ3Bvc3QxNDA1MTU3MzA0NDYzMzYnKSI+PHByZT4gICAgICAgICAgICAjIENhdGNoIEpTT04tcmVsYXRlZCBlcnJvcnMgYW5kIHJhaXNlIGFzIHJlcXVlc3RzLkpTT05EZWNvZGVFcnJvcjwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgPGxpIG9uY2xpY2s9InRvZ2dsZSgncHJlMTQwNTE1NzMwNDQ2MzM2JywgJ3Bvc3QxNDA1MTU3MzA0NDYzMzYnKSI+PHByZT4gICAgICAgICAgICAjIFRoaXMgYWxpYXNlcyBqc29uLkpTT05EZWNvZGVFcnJvciBhbmQgc2ltcGxlanNvbi5KU09ORGVjb2RlRXJyb3I8L3ByZT48L2xpPgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICA8L29sPgogICAgICAgICAgICAgIAogICAgICAgICAgICAgIDxvbCBzdGFydD0iOTc1IiBjbGFzcz0iY29udGV4dC1saW5lIj4KICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDQ0NjMzNicsICdwb3N0MTQwNTE1NzMwNDQ2MzM2JykiPjxwcmU+ICAgICAgICAgICAgcmFpc2UgUmVxdWVzdHNKU09ORGVjb2RlRXJyb3IoZS5tc2csIGUuZG9jLCBlLnBvcykKICAgICAgICAgICAgICAgICBeXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXjwvcHJlPiA8c3Bhbj7igKY8L3NwYW4+PC9saT4KICAgICAgICAgICAgICA8L29sPgogICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgPG9sIHN0YXJ0PSc5NzYnIGNsYXNzPSJwb3N0LWNvbnRleHQiIGlkPSJwb3N0MTQwNTE1NzMwNDQ2MzM2Ij4KICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDQ0NjMzNicsICdwb3N0MTQwNTE1NzMwNDQ2MzM2JykiPjxwcmU+PC9wcmU+PC9saT4KICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDQ0NjMzNicsICdwb3N0MTQwNTE1NzMwNDQ2MzM2JykiPjxwcmU+ICAgIEBwcm9wZXJ0eTwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzA0NDYzMzYnLCAncG9zdDE0MDUxNTczMDQ0NjMzNicpIj48cHJlPiAgICBkZWYgbGlua3Moc2VsZik6PC9wcmU+PC9saT4KICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIDxsaSBvbmNsaWNrPSJ0b2dnbGUoJ3ByZTE0MDUxNTczMDQ0NjMzNicsICdwb3N0MTQwNTE1NzMwNDQ2MzM2JykiPjxwcmU+ICAgICAgICAmcXVvdDsmcXVvdDsmcXVvdDtSZXR1cm5zIHRoZSBwYXJzZWQgaGVhZGVyIGxpbmtzIG9mIHRoZSByZXNwb25zZSwgaWYgYW55LiZxdW90OyZxdW90OyZxdW90OzwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzA0NDYzMzYnLCAncG9zdDE0MDUxNTczMDQ0NjMzNicpIj48cHJlPjwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8bGkgb25jbGljaz0idG9nZ2xlKCdwcmUxNDA1MTU3MzA0NDYzMzYnLCAncG9zdDE0MDUxNTczMDQ0NjMzNicpIj48cHJlPiAgICAgICAgaGVhZGVyID0gc2VsZi5oZWFkZXJzLmdldCgmcXVvdDtsaW5rJnF1b3Q7KTwvcHJlPjwvbGk+CiAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgIDwvb2w+CiAgICAgICAgICAgICAgCiAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgCgogICAgICAgICAgCiAgICAgICAgICAgIAogICAgICAgICAgICAgIDxkZXRhaWxzPgogICAgICAgICAgICAgICAgPHN1bW1hcnkgY2xhc3M9ImNvbW1hbmRzIj5Mb2NhbCB2YXJzPC9zdW1tYXJ5PgogICAgICAgICAgICAKICAgICAgICAgICAgPHRhYmxlIGNsYXNzPSJ2YXJzIiBpZD0idjE0MDUxNTczMDQ0NjMzNiI+CiAgICAgICAgICAgICAgPHRoZWFkPgogICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICA8dGg+VmFyaWFibGU8L3RoPgogICAgICAgICAgICAgICAgICA8dGg+VmFsdWU8L3RoPgogICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICA8L3RoZWFkPgogICAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgPHRkPmt3YXJnczwvdGQ+CiAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJjb2RlIj48cHJlPnt9PC9wcmU+PC90ZD4KICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgPHRkPnNlbGY8L3RkPgogICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iY29kZSI+PHByZT4mbHQ7UmVzcG9uc2UgWzIwMF0mZ3Q7PC9wcmU+PC90ZD4KICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICAgIDwvdGFibGU+CiAgICAgICAgICAgIDwvZGV0YWlscz4KICAgICAgICAgIAogICAgICAgIDwvbGk+CiAgICAgIAogICAgPC91bD4KICA8L2Rpdj4KCiAgPGZvcm0gYWN0aW9uPSJodHRwczovL2RwYXN0ZS5jb20vIiBuYW1lPSJwYXN0ZWZvcm0iIGlkPSJwYXN0ZWZvcm0iIG1ldGhvZD0icG9zdCI+CiAgPGRpdiBpZD0icGFzdGViaW5UcmFjZWJhY2siIGNsYXNzPSJwYXN0ZWJpbiI+CiAgICA8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJsYW5ndWFnZSIgdmFsdWU9IlB5dGhvbkNvbnNvbGUiPgogICAgPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0idGl0bGUiCiAgICAgIHZhbHVlPSJKU09ORGVjb2RlRXJyb3IgYXQgL2RqYW5nb2xkcC1kZmMvd2ViaG9vay8iPgogICAgPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0ic291cmNlIiB2YWx1ZT0iRGphbmdvIERwYXN0ZSBBZ2VudCI+CiAgICA8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJwb3N0ZXIiIHZhbHVlPSJEamFuZ28iPgogICAgPHRleHRhcmVhIG5hbWU9ImNvbnRlbnQiIGlkPSJ0cmFjZWJhY2tfYXJlYSIgY29scz0iMTQwIiByb3dzPSIyNSI+CkVudmlyb25tZW50OgoKClJlcXVlc3QgTWV0aG9kOiBQT1NUClJlcXVlc3QgVVJMOiBodHRwczovL2FwaS5wcm94eS1kZXYuY3FjbS5zdGFydGluYmxveC5jb20vZGphbmdvbGRwLWRmYy93ZWJob29rLwoKRGphbmdvIFZlcnNpb246IDQuMi4yMApQeXRob24gVmVyc2lvbjogMy4xMS4xMwpJbnN0YWxsZWQgQXBwbGljYXRpb25zOgpbJiN4Mjc7ZGphbmdvbGRwX2FjY291bnQmI3gyNzssCiAmI3gyNztkYXRhX2Zvb2RfY29uc29ydGl1bSYjeDI3OywKICYjeDI3O2RqYW5nb2xkcF9jc3YmI3gyNzssCiAmI3gyNztvaWRjX3Byb3ZpZGVyJiN4Mjc7LAogJiN4Mjc7ZGphbmdvLmNvbnRyaWIuYWRtaW4mI3gyNzssCiAmI3gyNztkamFuZ28uY29udHJpYi5hdXRoJiN4Mjc7LAogJiN4Mjc7ZGphbmdvLmNvbnRyaWIuY29udGVudHR5cGVzJiN4Mjc7LAogJiN4Mjc7ZGphbmdvLmNvbnRyaWIuc2Vzc2lvbnMmI3gyNzssCiAmI3gyNztkamFuZ28uY29udHJpYi5tZXNzYWdlcyYjeDI3OywKICYjeDI3O2RqYW5nby5jb250cmliLnN0YXRpY2ZpbGVzJiN4Mjc7LAogJiN4Mjc7ZGphbmdvbGRwJiN4Mjc7LAogJiN4Mjc7Z3VhcmRpYW4mI3gyNztdCkluc3RhbGxlZCBNaWRkbGV3YXJlOgomI3gyNzsmI3gyNzsKCgoKVHJhY2ViYWNrIChtb3N0IHJlY2VudCBjYWxsIGxhc3QpOgogIEZpbGUgIi9ob21lL2NxY20tcHJveHktZGV2L3N0YXJ0aW5ibG94L3ZlbnYvbGliL3B5dGhvbjMuMTEvc2l0ZS1wYWNrYWdlcy9yZXF1ZXN0cy9tb2RlbHMucHkiLCBsaW5lIDk3MSwgaW4ganNvbgogICAgcmV0dXJuIGNvbXBsZXhqc29uLmxvYWRzKHNlbGYudGV4dCwgKiprd2FyZ3MpCiAgICAgICAgICAgXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl4KICBGaWxlICIvdXNyL2Fsd2F5c2RhdGEvcHl0aG9uLzMuMTEvbGliL3B5dGhvbjMuMTEvanNvbi9fX2luaXRfXy5weSIsIGxpbmUgMzQ2LCBpbiBsb2FkcwogICAgcmV0dXJuIF9kZWZhdWx0X2RlY29kZXIuZGVjb2RlKHMpCiAgICAgICAgICAgXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl4KICBGaWxlICIvdXNyL2Fsd2F5c2RhdGEvcHl0aG9uLzMuMTEvbGliL3B5dGhvbjMuMTEvanNvbi9kZWNvZGVyLnB5IiwgbGluZSAzMzcsIGluIGRlY29kZQogICAgb2JqLCBlbmQgPSBzZWxmLnJhd19kZWNvZGUocywgaWR4PV93KHMsIDApLmVuZCgpKQogICAgICAgICAgICAgICBeXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXgogIEZpbGUgIi91c3IvYWx3YXlzZGF0YS9weXRob24vMy4xMS9saWIvcHl0aG9uMy4xMS9qc29uL2RlY29kZXIucHkiLCBsaW5lIDM1NSwgaW4gcmF3X2RlY29kZQogICAgcmFpc2UgSlNPTkRlY29kZUVycm9yKCZxdW90O0V4cGVjdGluZyB2YWx1ZSZxdW90OywgcywgZXJyLnZhbHVlKSBmcm9tIE5vbmUKICAgIF5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl4KCkR1cmluZyBoYW5kbGluZyBvZiB0aGUgYWJvdmUgZXhjZXB0aW9uIChFeHBlY3RpbmcgdmFsdWU6IGxpbmUgMSBjb2x1bW4gMSAoY2hhciAwKSksIGFub3RoZXIgZXhjZXB0aW9uIG9jY3VycmVkOgogIEZpbGUgIi9ob21lL2NxY20tcHJveHktZGV2L3N0YXJ0aW5ibG94L3ZlbnYvbGliL3B5dGhvbjMuMTEvc2l0ZS1wYWNrYWdlcy9kamFuZ28vY29yZS9oYW5kbGVycy9leGNlcHRpb24ucHkiLCBsaW5lIDU1LCBpbiBpbm5lcgogICAgcmVzcG9uc2UgPSBnZXRfcmVzcG9uc2UocmVxdWVzdCkKICAgICAgICAgICAgICAgXl5eXl5eXl5eXl5eXl5eXl5eXl5eCiAgRmlsZSAiL2hvbWUvY3FjbS1wcm94eS1kZXYvc3RhcnRpbmJsb3gvdmVudi9saWIvcHl0aG9uMy4xMS9zaXRlLXBhY2thZ2VzL2RqYW5nby9jb3JlL2hhbmRsZXJzL2Jhc2UucHkiLCBsaW5lIDE5NywgaW4gX2dldF9yZXNwb25zZQogICAgcmVzcG9uc2UgPSB3cmFwcGVkX2NhbGxiYWNrKHJlcXVlc3QsICpjYWxsYmFja19hcmdzLCAqKmNhbGxiYWNrX2t3YXJncykKICAgICAgICAgICAgICAgXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eCiAgRmlsZSAiL2hvbWUvY3FjbS1wcm94eS1kZXYvc3RhcnRpbmJsb3gvdmVudi9saWIvcHl0aG9uMy4xMS9zaXRlLXBhY2thZ2VzL2RqYW5nby92aWV3cy9kZWNvcmF0b3JzL2NzcmYucHkiLCBsaW5lIDU2LCBpbiB3cmFwcGVyX3ZpZXcKICAgIHJldHVybiB2aWV3X2Z1bmMoKmFyZ3MsICoqa3dhcmdzKQogICAgICAgICAgIF5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eCiAgRmlsZSAiL2hvbWUvY3FjbS1wcm94eS1kZXYvc3RhcnRpbmJsb3gvdmVudi9saWIvcHl0aG9uMy4xMS9zaXRlLXBhY2thZ2VzL2RqYW5nby92aWV3cy9nZW5lcmljL2Jhc2UucHkiLCBsaW5lIDEwNCwgaW4gdmlldwogICAgcmV0dXJuIHNlbGYuZGlzcGF0Y2gocmVxdWVzdCwgKmFyZ3MsICoqa3dhcmdzKQogICAgICAgICAgIF5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXgogIEZpbGUgIi9ob21lL2NxY20tcHJveHktZGV2L3N0YXJ0aW5ibG94L3ZlbnYvbGliL3B5dGhvbjMuMTEvc2l0ZS1wYWNrYWdlcy9yZXN0X2ZyYW1ld29yay92aWV3cy5weSIsIGxpbmUgNTA5LCBpbiBkaXNwYXRjaAogICAgcmVzcG9uc2UgPSBzZWxmLmhhbmRsZV9leGNlcHRpb24oZXhjKQogICAgICAgICAgICAgICBeXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXgogIEZpbGUgIi9ob21lL2NxY20tcHJveHktZGV2L3N0YXJ0aW5ibG94L3ZlbnYvbGliL3B5dGhvbjMuMTEvc2l0ZS1wYWNrYWdlcy9yZXN0X2ZyYW1ld29yay92aWV3cy5weSIsIGxpbmUgNDY5LCBpbiBoYW5kbGVfZXhjZXB0aW9uCiAgICBzZWxmLnJhaXNlX3VuY2F1Z2h0X2V4Y2VwdGlvbihleGMpCiAgICBeXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eCiAgRmlsZSAiL2hvbWUvY3FjbS1wcm94eS1kZXYvc3RhcnRpbmJsb3gvdmVudi9saWIvcHl0aG9uMy4xMS9zaXRlLXBhY2thZ2VzL3Jlc3RfZnJhbWV3b3JrL3ZpZXdzLnB5IiwgbGluZSA0ODAsIGluIHJhaXNlX3VuY2F1Z2h0X2V4Y2VwdGlvbgogICAgcmFpc2UgZXhjCiAgICBeXl5eXl5eXl4KICBGaWxlICIvaG9tZS9jcWNtLXByb3h5LWRldi9zdGFydGluYmxveC92ZW52L2xpYi9weXRob24zLjExL3NpdGUtcGFja2FnZXMvcmVzdF9mcmFtZXdvcmsvdmlld3MucHkiLCBsaW5lIDUwNiwgaW4gZGlzcGF0Y2gKICAgIHJlc3BvbnNlID0gaGFuZGxlcihyZXF1ZXN0LCAqYXJncywgKiprd2FyZ3MpCiAgICAgICAgICAgICAgIF5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXgogIEZpbGUgIi9ob21lL2NxY20tcHJveHktZGV2L3N0YXJ0aW5ibG94L3NpYnNlcnZlci9kYXRhX2Zvb2RfY29uc29ydGl1bS92aWV3cy5weSIsIGxpbmUgOTAsIGluIHBvc3QKICAgIHNlbGYucHJvY2VzcyhyZXF1ZXN0LCBkYXRhKQogICAgXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eCiAgRmlsZSAiL2hvbWUvY3FjbS1wcm94eS1kZXYvc3RhcnRpbmJsb3gvc2lic2VydmVyL2RhdGFfZm9vZF9jb25zb3J0aXVtL3ZpZXdzLnB5IiwgbGluZSA0NiwgaW4gcHJvY2VzcwogICAgUmVzb3VyY2VTZXJ2ZXJDbGllbnQoZiZxdW90O3tob3N0LnNjaGVtZX06Ly97aG9zdC5uZXRsb2N9LyZxdW90OykucmVxdWVzdF9zY29wZSgKICAgIF4KICBGaWxlICIvaG9tZS9jcWNtLXByb3h5LWRldi9zdGFydGluYmxveC9zaWJzZXJ2ZXIvZGF0YV9mb29kX2NvbnNvcnRpdW0vcHJveHkvcmVzb3VyY2UucHkiLCBsaW5lIDMyMiwgaW4gcmVxdWVzdF9zY29wZQogICAgc2VsZi5fcmVxdWVzdF9hbmRfcHJvY2Vzc19zY29wZV9hdF9lbmRwb2ludChwYXJzZXIsIHNjb3BlLCBlbmRwb2ludCkKICAgIF5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eCiAgRmlsZSAiL2hvbWUvY3FjbS1wcm94eS1kZXYvc3RhcnRpbmJsb3gvc2lic2VydmVyL2RhdGFfZm9vZF9jb25zb3J0aXVtL3Byb3h5L3Jlc291cmNlLnB5IiwgbGluZSAzMDMsIGluIF9yZXF1ZXN0X2FuZF9wcm9jZXNzX3Njb3BlX2F0X2VuZHBvaW50CiAgICBkYXRhID0gcmVzcG9uc2UuanNvbigpCiAgICAgICAgICAgXl5eXl5eXl5eXl5eXl5eCiAgRmlsZSAiL2hvbWUvY3FjbS1wcm94eS1kZXYvc3RhcnRpbmJsb3gvdmVudi9saWIvcHl0aG9uMy4xMS9zaXRlLXBhY2thZ2VzL3JlcXVlc3RzL21vZGVscy5weSIsIGxpbmUgOTc1LCBpbiBqc29uCiAgICByYWlzZSBSZXF1ZXN0c0pTT05EZWNvZGVFcnJvcihlLm1zZywgZS5kb2MsIGUucG9zKQogICAgXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl4KCkV4Y2VwdGlvbiBUeXBlOiBKU09ORGVjb2RlRXJyb3IgYXQgL2RqYW5nb2xkcC1kZmMvd2ViaG9vay8KRXhjZXB0aW9uIFZhbHVlOiBFeHBlY3RpbmcgdmFsdWU6IGxpbmUgMSBjb2x1bW4gMSAoY2hhciAwKQo8L3RleHRhcmVhPgogIDxicj48YnI+CiAgPGlucHV0IHR5cGU9InN1Ym1pdCIgdmFsdWU9IlNoYXJlIHRoaXMgdHJhY2ViYWNrIG9uIGEgcHVibGljIHdlYnNpdGUiPgogIDwvZGl2Pgo8L2Zvcm0+Cgo8L2Rpdj4KCgo8ZGl2IGlkPSJyZXF1ZXN0aW5mbyI+CiAgPGgyPlJlcXVlc3QgaW5mb3JtYXRpb248L2gyPgoKCiAgCiAgICA8aDMgaWQ9InVzZXItaW5mbyI+VVNFUjwvaDM+CiAgICA8cD5Bbm9ueW1vdXNVc2VyPC9wPgogIAoKICA8aDMgaWQ9ImdldC1pbmZvIj5HRVQ8L2gzPgogIAogICAgPHA+Tm8gR0VUIGRhdGE8L3A+CiAgCgogIDxoMyBpZD0icG9zdC1pbmZvIj5QT1NUPC9oMz4KICAKICAgIDxwPk5vIFBPU1QgZGF0YTwvcD4KICAKCiAgPGgzIGlkPSJmaWxlcy1pbmZvIj5GSUxFUzwvaDM+CiAgCiAgICA8cD5ObyBGSUxFUyBkYXRhPC9wPgogIAoKICA8aDMgaWQ9ImNvb2tpZS1pbmZvIj5DT09LSUVTPC9oMz4KICAKICAgIDxwPk5vIGNvb2tpZSBkYXRhPC9wPgogIAoKICA8aDMgaWQ9Im1ldGEtaW5mbyI+TUVUQTwvaDM+CiAgPHRhYmxlIGNsYXNzPSJyZXEiPgogICAgPHRoZWFkPgogICAgICA8dHI+CiAgICAgICAgPHRoPlZhcmlhYmxlPC90aD4KICAgICAgICA8dGg+VmFsdWU8L3RoPgogICAgICA8L3RyPgogICAgPC90aGVhZD4KICAgIDx0Ym9keT4KICAgICAgCiAgICAgICAgPHRyPgogICAgICAgICAgPHRkPkNPTlRFTlRfTEVOR1RIPC90ZD4KICAgICAgICAgIDx0ZCBjbGFzcz0iY29kZSI+PHByZT4mI3gyNzsxMDUmI3gyNzs8L3ByZT48L3RkPgogICAgICAgIDwvdHI+CiAgICAgIAogICAgICAgIDx0cj4KICAgICAgICAgIDx0ZD5DT05URU5UX1RZUEU8L3RkPgogICAgICAgICAgPHRkIGNsYXNzPSJjb2RlIj48cHJlPiYjeDI3O2FwcGxpY2F0aW9uL2pzb24mI3gyNzs8L3ByZT48L3RkPgogICAgICAgIDwvdHI+CiAgICAgIAogICAgICAgIDx0cj4KICAgICAgICAgIDx0ZD5IVFRQX0FDQ0VQVDwvdGQ+CiAgICAgICAgICA8dGQgY2xhc3M9ImNvZGUiPjxwcmU+JiN4Mjc7Ki8qJiN4Mjc7PC9wcmU+PC90ZD4KICAgICAgICA8L3RyPgogICAgICAKICAgICAgICA8dHI+CiAgICAgICAgICA8dGQ+SFRUUF9BQ0NFUFRfRU5DT0RJTkc8L3RkPgogICAgICAgICAgPHRkIGNsYXNzPSJjb2RlIj48cHJlPiYjeDI3O2d6aXA7cT0xLjAsZGVmbGF0ZTtxPTAuNixpZGVudGl0eTtxPTAuMyYjeDI3OzwvcHJlPjwvdGQ+CiAgICAgICAgPC90cj4KICAgICAgCiAgICAgICAgPHRyPgogICAgICAgICAgPHRkPkhUVFBfQVVUSE9SSVpBVElPTjwvdGQ+CiAgICAgICAgICA8dGQgY2xhc3M9ImNvZGUiPjxwcmU+KCYjeDI3O0JlYXJlciAmI3gyNzsKICYjeDI3O2V5SmhiR2NpT2lKU1V6STFOaUlzSW5SNWNDSWdPaUFpU2xkVUlpd2lhMmxrSWlBNklDSlJSbEpFUzFkYVEyaHpNbGxRWW5ScVlsOXlRVXR3UXpOek1GbzBUMkZDTUV0RlEwNTZObmx4V0hRMEluMC5leUpsZUhBaU9qRTNOVGcyT0RjeU16TXNJbWxoZENJNk1UYzFPRFk0Tmprek15d2lhblJwSWpvaVpUZGpNVGxrTURRdE9EaGhOQzAwTnpjeExXSTRORGN0TVRaaFptTmlNakl3TmpNMElpd2lhWE56SWpvaWFIUjBjSE02THk5cll5NWpjV050TG5OMFlYSjBhVzVpYkc5NExtTnZiUzl5WldGc2JYTXZjM1JoY25ScGJtSnNiM2dpTENKaGRXUWlPaUpoWTJOdmRXNTBJaXdpYzNWaUlqb2lNVGc0WWpnNU1qVXRZbVprT0MwME1UbGlMVGhsTlRVdE9XWmlOVFppT0RBd05qUTVJaXdpZEhsd0lqb2lRbVZoY21WeUlpd2lZWHB3SWpvaWFIUjBjSE02THk5emRHRm5hVzVuTG05d1pXNW1iMjlrYm1WMGQyOXlheTV2Y21jdWRXc3ZJaXdpWVdOeUlqb2lNU0lzSW1Gc2JHOTNaV1F0YjNKcFoybHVjeUk2V3lKb2RIUndjem92TDNOMFlXZHBibWN1YjNCbGJtWnZiMlJ1WlhSM2IzSnJMbTl5Wnk1MWF5OGlYU3dpY21WaGJHMWZZV05qWlhOeklqcDdJbkp2YkdWeklqcGJJbTltWm14cGJtVmZZV05qWlhOeklpd2lkVzFoWDJGMWRHaHZjbWw2WVhScGIyNGlMQ0prWldaaGRXeDBMWEp2YkdWekxYTjBZWEowYVc1aWJHOTRJbDE5TENKeVpYTnZkWEpqWlY5aFkyTmxjM01pT25zaVlXTmpiM1Z1ZENJNmV5SnliMnhsY3lJNld5SnRZVzVoWjJVdFlXTmpiM1Z1ZENJc0ltMWhibUZuWlMxaFkyTnZkVzUwTFd4cGJtdHpJaXdpZG1sbGR5MXdjbTltYVd4bElsMTlMQ0pvZEhSd2N6b3ZMM04wWVdkcGJtY3ViM0JsYm1admIyUnVaWFIzYjNKckxtOXlaeTUxYXk4aU9uc2ljbTlzWlhNaU9sc2lkVzFoWDNCeWIzUmxZM1JwYjI0aVhYMTlMQ0p6WTI5d1pTSTZJbGR5YVhSbFJXNTBaWEp3Y21selpTQndjbTltYVd4bElHVnRZV2xzSWl3aVpXMWhhV3hmZG1WeWFXWnBaV1FpT21aaGJITmxMQ0pqYkdsbGJuUkliM04wSWpvaU1UY3lMakU0TGpBdU1TSXNJbkJ5WldabGNuSmxaRjkxYzJWeWJtRnRaU0k2SW5ObGNuWnBZMlV0WVdOamIzVnVkQzFvZEhSd2N6b3ZMM04wWVdkcGJtY3ViM0JsYm1admIyUnVaWFIzYjNKckxtOXlaeTUxYXk4aUxDSmpiR2xsYm5SQlpHUnlaWE56SWpvaU1UY3lMakU0TGpBdU1TSXNJbU5zYVdWdWRGOXBaQ0k2SW1oMGRIQnpPaTh2YzNSaFoybHVaeTV2Y0dWdVptOXZaRzVsZEhkdmNtc3ViM0puTG5Wckx5SjkuQ0dyQVRZcDlPMkJvTGpSNWlFSEZZdDRRNnBhSEtBS2luNFFKTi1wZ21CcXltZWxuWE9qX1ZmanZSTjhQTXlPMHNLNzU0M0JfSTdLZmR3SzlvZWtoSE5GSHpKTkgxdnlSRkNqME05TzcyOXB4NWEwaFBpcnM3a3JONnREZm5yV0tYM2pQazA5ekFrZktSaEhQRnZ2NU1MUXZzaHNIM1lBSzZUNFhQbVVmbklEQ0pBVWJRbDdjb2NwejFyNHBTTmR5NHhNNm9CTm56c3NOdGFybjdOWEJlTFpOS2FicmJxUHkxR3dYNUlEbTcxYmwyX3o0bWdHZWhVd1BIZFozVzBZYjYzTy1ibHdXWHdTSVdoM2VHX1FONGQ5b00xMUI3RjJQOEQ3YjZ6eDJRRXd5eVkzc280ZHhVM0lueXlDNy1HVDdZNG1NbjE1dFhoN3JfejdEUmxWdUhBJiN4Mjc7KTwvcHJlPjwvdGQ+CiAgICAgICAgPC90cj4KICAgICAgCiAgICAgICAgPHRyPgogICAgICAgICAgPHRkPkhUVFBfSE9TVDwvdGQ+CiAgICAgICAgICA8dGQgY2xhc3M9ImNvZGUiPjxwcmU+JiN4Mjc7YXBpLnByb3h5LWRldi5jcWNtLnN0YXJ0aW5ibG94LmNvbSYjeDI3OzwvcHJlPjwvdGQ+CiAgICAgICAgPC90cj4KICAgICAgCiAgICAgICAgPHRyPgogICAgICAgICAgPHRkPkhUVFBfVVNFUl9BR0VOVDwvdGQ+CiAgICAgICAgICA8dGQgY2xhc3M9ImNvZGUiPjxwcmU+JiN4Mjc7RmFyYWRheSB2Mi45LjAmI3gyNzs8L3ByZT48L3RkPgogICAgICAgIDwvdHI+CiAgICAgIAogICAgICAgIDx0cj4KICAgICAgICAgIDx0ZD5IVFRQX1ZJQTwvdGQ+CiAgICAgICAgICA8dGQgY2xhc3M9ImNvZGUiPjxwcmU+JiN4Mjc7MS4xIGFscHJveHkmI3gyNzs8L3ByZT48L3RkPgogICAgICAgIDwvdHI+CiAgICAgIAogICAgICAgIDx0cj4KICAgICAgICAgIDx0ZD5IVFRQX1hfRk9SV0FSREVEX1BST1RPPC90ZD4KICAgICAgICAgIDx0ZCBjbGFzcz0iY29kZSI+PHByZT4mI3gyNztodHRwcyYjeDI3OzwvcHJlPjwvdGQ+CiAgICAgICAgPC90cj4KICAgICAgCiAgICAgICAgPHRyPgogICAgICAgICAgPHRkPkhUVFBfWF9SRUFMX0lQPC90ZD4KICAgICAgICAgIDx0ZCBjbGFzcz0iY29kZSI+PHByZT4mI3gyNzsxODIuMjM5LjE5NC4xMTgmI3gyNzs8L3ByZT48L3RkPgogICAgICAgIDwvdHI+CiAgICAgIAogICAgICAgIDx0cj4KICAgICAgICAgIDx0ZD5QQVRIX0lORk88L3RkPgogICAgICAgICAgPHRkIGNsYXNzPSJjb2RlIj48cHJlPiYjeDI3Oy9kamFuZ29sZHAtZGZjL3dlYmhvb2svJiN4Mjc7PC9wcmU+PC90ZD4KICAgICAgICA8L3RyPgogICAgICAKICAgICAgICA8dHI+CiAgICAgICAgICA8dGQ+UVVFUllfU1RSSU5HPC90ZD4KICAgICAgICAgIDx0ZCBjbGFzcz0iY29kZSI+PHByZT4mI3gyNzsmI3gyNzs8L3ByZT48L3RkPgogICAgICAgIDwvdHI+CiAgICAgIAogICAgICAgIDx0cj4KICAgICAgICAgIDx0ZD5SRU1PVEVfQUREUjwvdGQ+CiAgICAgICAgICA8dGQgY2xhc3M9ImNvZGUiPjxwcmU+JiN4Mjc7MTgyLjIzOS4xOTQuMTE4JiN4Mjc7PC9wcmU+PC90ZD4KICAgICAgICA8L3RyPgogICAgICAKICAgICAgICA8dHI+CiAgICAgICAgICA8dGQ+UkVRVUVTVF9NRVRIT0Q8L3RkPgogICAgICAgICAgPHRkIGNsYXNzPSJjb2RlIj48cHJlPiYjeDI3O1BPU1QmI3gyNzs8L3ByZT48L3RkPgogICAgICAgIDwvdHI+CiAgICAgIAogICAgICAgIDx0cj4KICAgICAgICAgIDx0ZD5SRVFVRVNUX1VSSTwvdGQ+CiAgICAgICAgICA8dGQgY2xhc3M9ImNvZGUiPjxwcmU+JiN4Mjc7L2RqYW5nb2xkcC1kZmMvd2ViaG9vay8mI3gyNzs8L3ByZT48L3RkPgogICAgICAgIDwvdHI+CiAgICAgIAogICAgICAgIDx0cj4KICAgICAgICAgIDx0ZD5TQ1JJUFRfTkFNRTwvdGQ+CiAgICAgICAgICA8dGQgY2xhc3M9ImNvZGUiPjxwcmU+JiN4Mjc7JiN4Mjc7PC9wcmU+PC90ZD4KICAgICAgICA8L3RyPgogICAgICAKICAgICAgICA8dHI+CiAgICAgICAgICA8dGQ+U0VSVkVSX05BTUU8L3RkPgogICAgICAgICAgPHRkIGNsYXNzPSJjb2RlIj48cHJlPiYjeDI3O2FzdHJhbCYjeDI3OzwvcHJlPjwvdGQ+CiAgICAgICAgPC90cj4KICAgICAgCiAgICAgICAgPHRyPgogICAgICAgICAgPHRkPlNFUlZFUl9QT1JUPC90ZD4KICAgICAgICAgIDx0ZCBjbGFzcz0iY29kZSI+PHByZT4mI3gyNzs4MTAwJiN4Mjc7PC9wcmU+PC90ZD4KICAgICAgICA8L3RyPgogICAgICAKICAgICAgICA8dHI+CiAgICAgICAgICA8dGQ+U0VSVkVSX1BST1RPQ09MPC90ZD4KICAgICAgICAgIDx0ZCBjbGFzcz0iY29kZSI+PHByZT4mI3gyNztIVFRQLzEuMSYjeDI3OzwvcHJlPjwvdGQ+CiAgICAgICAgPC90cj4KICAgICAgCiAgICAgICAgPHRyPgogICAgICAgICAgPHRkPnV3c2dpLm5vZGU8L3RkPgogICAgICAgICAgPHRkIGNsYXNzPSJjb2RlIj48cHJlPmImI3gyNzthc3RyYWwmI3gyNzs8L3ByZT48L3RkPgogICAgICAgIDwvdHI+CiAgICAgIAogICAgICAgIDx0cj4KICAgICAgICAgIDx0ZD51d3NnaS52ZXJzaW9uPC90ZD4KICAgICAgICAgIDx0ZCBjbGFzcz0iY29kZSI+PHByZT5iJiN4Mjc7Mi4wLjI4JiN4Mjc7PC9wcmU+PC90ZD4KICAgICAgICA8L3RyPgogICAgICAKICAgICAgICA8dHI+CiAgICAgICAgICA8dGQ+d3NnaS5lcnJvcnM8L3RkPgogICAgICAgICAgPHRkIGNsYXNzPSJjb2RlIj48cHJlPiZsdDtfaW8uVGV4dElPV3JhcHBlciBuYW1lPTIgbW9kZT0mI3gyNzt3JiN4Mjc7IGVuY29kaW5nPSYjeDI3O1VURi04JiN4Mjc7Jmd0OzwvcHJlPjwvdGQ+CiAgICAgICAgPC90cj4KICAgICAgCiAgICAgICAgPHRyPgogICAgICAgICAgPHRkPndzZ2kuaW5wdXQ8L3RkPgogICAgICAgICAgPHRkIGNsYXNzPSJjb2RlIj48cHJlPiZsdDt1d3NnaS5fSW5wdXQgb2JqZWN0IGF0IDB4N2ZjYzVlYzc1MjMwJmd0OzwvcHJlPjwvdGQ+CiAgICAgICAgPC90cj4KICAgICAgCiAgICAgICAgPHRyPgogICAgICAgICAgPHRkPndzZ2kubXVsdGlwcm9jZXNzPC90ZD4KICAgICAgICAgIDx0ZCBjbGFzcz0iY29kZSI+PHByZT5UcnVlPC9wcmU+PC90ZD4KICAgICAgICA8L3RyPgogICAgICAKICAgICAgICA8dHI+CiAgICAgICAgICA8dGQ+d3NnaS5tdWx0aXRocmVhZDwvdGQ+CiAgICAgICAgICA8dGQgY2xhc3M9ImNvZGUiPjxwcmU+RmFsc2U8L3ByZT48L3RkPgogICAgICAgIDwvdHI+CiAgICAgIAogICAgICAgIDx0cj4KICAgICAgICAgIDx0ZD53c2dpLnJ1bl9vbmNlPC90ZD4KICAgICAgICAgIDx0ZCBjbGFzcz0iY29kZSI+PHByZT5GYWxzZTwvcHJlPjwvdGQ+CiAgICAgICAgPC90cj4KICAgICAgCiAgICAgICAgPHRyPgogICAgICAgICAgPHRkPndzZ2kudXJsX3NjaGVtZTwvdGQ+CiAgICAgICAgICA8dGQgY2xhc3M9ImNvZGUiPjxwcmU+JiN4Mjc7aHR0cHMmI3gyNzs8L3ByZT48L3RkPgogICAgICAgIDwvdHI+CiAgICAgIAogICAgICAgIDx0cj4KICAgICAgICAgIDx0ZD53c2dpLnZlcnNpb248L3RkPgogICAgICAgICAgPHRkIGNsYXNzPSJjb2RlIj48cHJlPigxLCAwKTwvcHJlPjwvdGQ+CiAgICAgICAgPC90cj4KICAgICAgCiAgICA8L3Rib2R5PgogIDwvdGFibGU+CgoKICA8aDMgaWQ9InNldHRpbmdzLWluZm8iPlNldHRpbmdzPC9oMz4KICA8aDQ+VXNpbmcgc2V0dGluZ3MgbW9kdWxlIDxjb2RlPjwvY29kZT48L2g0PgogIDx0YWJsZSBjbGFzcz0icmVxIj4KICAgIDx0aGVhZD4KICAgICAgPHRyPgogICAgICAgIDx0aD5TZXR0aW5nPC90aD4KICAgICAgICA8dGg+VmFsdWU8L3RoPgogICAgICA8L3RyPgogICAgPC90aGVhZD4KICAgIDx0Ym9keT4KICAgICAgCiAgICAgICAgPHRyPgogICAgICAgICAgPHRkPkRKQU5HT0xEUF9QQUNLQUdFUzwvdGQ+CiAgICAgICAgICA8dGQgY2xhc3M9ImNvZGUiPjxwcmU+WyYjeDI3O2RqYW5nb2xkcF9hY2NvdW50JiN4Mjc7LCAmI3gyNztkYXRhX2Zvb2RfY29uc29ydGl1bSYjeDI3OywgJiN4Mjc7ZGphbmdvbGRwX2NzdiYjeDI3O108L3ByZT48L3RkPgogICAgICAgIDwvdHI+CiAgICAgIAogICAgICAgIDx0cj4KICAgICAgICAgIDx0ZD5JTlNUQUxMRURfQVBQUzwvdGQ+CiAgICAgICAgICA8dGQgY2xhc3M9ImNvZGUiPjxwcmU+WyYjeDI3O2RqYW5nb2xkcF9hY2NvdW50JiN4Mjc7LAogJiN4Mjc7ZGF0YV9mb29kX2NvbnNvcnRpdW0mI3gyNzssCiAmI3gyNztkamFuZ29sZHBfY3N2JiN4Mjc7LAogJiN4Mjc7b2lkY19wcm92aWRlciYjeDI3OywKICYjeDI3O2RqYW5nby5jb250cmliLmFkbWluJiN4Mjc7LAogJiN4Mjc7ZGphbmdvLmNvbnRyaWIuYXV0aCYjeDI3OywKICYjeDI3O2RqYW5nby5jb250cmliLmNvbnRlbnR0eXBlcyYjeDI3OywKICYjeDI3O2RqYW5nby5jb250cmliLnNlc3Npb25zJiN4Mjc7LAogJiN4Mjc7ZGphbmdvLmNvbnRyaWIubWVzc2FnZXMmI3gyNzssCiAmI3gyNztkamFuZ28uY29udHJpYi5zdGF0aWNmaWxlcyYjeDI3OywKICYjeDI3O2RqYW5nb2xkcCYjeDI3OywKICYjeDI3O2d1YXJkaWFuJiN4Mjc7XTwvcHJlPjwvdGQ+CiAgICAgICAgPC90cj4KICAgICAgCiAgICA8L3Rib2R5PgogIDwvdGFibGU+Cgo8L2Rpdj4KCiAgPGRpdiBpZD0iZXhwbGFuYXRpb24iPgogICAgPHA+CiAgICAgIFlvdeKAmXJlIHNlZWluZyB0aGlzIGVycm9yIGJlY2F1c2UgeW91IGhhdmUgPGNvZGU+REVCVUcgPSBUcnVlPC9jb2RlPiBpbiB5b3VyCiAgICAgIERqYW5nbyBzZXR0aW5ncyBmaWxlLiBDaGFuZ2UgdGhhdCB0byA8Y29kZT5GYWxzZTwvY29kZT4sIGFuZCBEamFuZ28gd2lsbAogICAgICBkaXNwbGF5IGEgc3RhbmRhcmQgcGFnZSBnZW5lcmF0ZWQgYnkgdGhlIGhhbmRsZXIgZm9yIHRoaXMgc3RhdHVzIGNvZGUuCiAgICA8L3A+CiAgPC9kaXY+Cgo8L2JvZHk+CjwvaHRtbD4K + recorded_at: Wed, 24 Sep 2025 04:08:57 GMT +recorded_with: VCR 6.2.0 diff --git a/spec/fixtures/vcr_cassettes/ProxyNotifier/receives_an_access_token.yml b/spec/fixtures/vcr_cassettes/ProxyNotifier/receives_an_access_token.yml new file mode 100644 index 0000000000..156785445e --- /dev/null +++ b/spec/fixtures/vcr_cassettes/ProxyNotifier/receives_an_access_token.yml @@ -0,0 +1,52 @@ +--- +http_interactions: +- request: + method: post + uri: https://kc.cqcm.startinblox.com/realms/startinblox/protocol/openid-connect/token + body: + encoding: UTF-8 + string: client_id=https%3A%2F%2Fstaging.openfoodnetwork.org.uk%2F&client_secret=&grant_type=client_credentials&scope=WriteEnterprise + headers: + User-Agent: + - Faraday v2.9.0 + Content-Type: + - application/x-www-form-urlencoded + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx/1.22.1 + Date: + - Fri, 19 Sep 2025 06:09:58 GMT + Content-Type: + - application/json + Content-Length: + - '1726' + Connection: + - keep-alive + Cache-Control: + - no-store + Pragma: + - no-cache + Referrer-Policy: + - no-referrer + Strict-Transport-Security: + - max-age=31536000; includeSubDomains + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Xss-Protection: + - 1; mode=block + body: + encoding: UTF-8 + string: '{"access_token":"","expires_in":300,"refresh_expires_in":0,"token_type":"Bearer","not-before-policy":0,"scope":"WriteEnterprise + profile email"}' + recorded_at: Fri, 19 Sep 2025 06:09:58 GMT +recorded_with: VCR 6.2.0 diff --git a/spec/system/admin/enterprises/dfc_permissions_spec.rb b/spec/system/admin/enterprises/dfc_permissions_spec.rb index 5b7adcd716..7c86f6ed0f 100644 --- a/spec/system/admin/enterprises/dfc_permissions_spec.rb +++ b/spec/system/admin/enterprises/dfc_permissions_spec.rb @@ -11,6 +11,9 @@ RSpec.describe "DFC Permissions", feature: "cqcm-dev", vcr: true do before do login_as enterprise.owner + + # Disable data proxy webhook which can't reach our test server. + allow_any_instance_of(ProxyNotifier).to receive(:refresh) end it "is not visible when no platform is enabled" do From 2780ae78f78e659dee43884da8fec069f0fb14dc Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 24 Sep 2025 15:41:15 +1000 Subject: [PATCH 10/20] Add CQCM production servers --- .../app/controllers/dfc_provider/platforms_controller.rb | 1 + engines/dfc_provider/app/services/api_user.rb | 1 + engines/dfc_provider/app/services/authorization_control.rb | 7 +++++++ engines/dfc_provider/app/services/proxy_notifier.rb | 2 +- lib/open_food_network/feature_toggle.rb | 4 +++- 5 files changed, 13 insertions(+), 2 deletions(-) diff --git a/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb b/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb index 26ec1ebe3c..770cf07bfc 100644 --- a/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb +++ b/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb @@ -7,6 +7,7 @@ module DfcProvider PLATFORM_IDS = { 'cqcm-dev' => "https://api.proxy-dev.cqcm.startinblox.com/profile", 'cqcm-stg' => "https://api.proxy-stg.cqcm.startinblox.com/profile", + 'cqcm' => "https://carte.cqcm.coop/profile", }.freeze prepend_before_action :move_authenticity_token diff --git a/engines/dfc_provider/app/services/api_user.rb b/engines/dfc_provider/app/services/api_user.rb index ea054ce99c..987f328c2e 100644 --- a/engines/dfc_provider/app/services/api_user.rb +++ b/engines/dfc_provider/app/services/api_user.rb @@ -5,6 +5,7 @@ class ApiUser CLIENT_MAP = { "https://waterlooregionfood.ca/portal/profile" => "cqcm-dev", "https://api.proxy-stg.cqcm.startinblox.com/profile" => "cqcm-stg", + "https://carte.cqcm.coop/profile" => "cqcm", }.freeze def self.from_client_id(client_id) diff --git a/engines/dfc_provider/app/services/authorization_control.rb b/engines/dfc_provider/app/services/authorization_control.rb index ffcef33982..582edcff0d 100644 --- a/engines/dfc_provider/app/services/authorization_control.rb +++ b/engines/dfc_provider/app/services/authorization_control.rb @@ -18,6 +18,13 @@ class AuthorizationControl MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqtvdb3BdHoLnNeMLaWd7nugPwdRAJJpdSySTtttEQY2/v1Q3byJ/kReSNGrUNkPVkOeDN3milgN5Apz+sNCwbtzOCulyFMmvuIOZFBqz5tcgwjZinSwpGBXpn6ehXyCET2LlcfLYAPA9axtaNg9wBLIHoxIPWpa2LcZstogyZY/yKUZXQTDqM5B5TyUkPN89xHFdq8SQuXPasbpYl7mGhZHkTDHiKZ9VK7K5tqsEZTD9dCuTGMKsthbOrlDnc9bAJ3PyKLRdib21Y1GGlTozo4Y/1q448E/DFp5rVC6jG6JFnsEnP0WVn+6qz7yxI7IfUU2YSAGgtGYaQkWtEfED0QIDAQAB -----END PUBLIC KEY----- KEY + + # Copied from: https:///authentification.cqcm.coop/realms/cqcm + "https:///authentification.cqcm.coop/realms/cqcm" => <<~KEY, + -----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhz7dK3xQAWL+u++E/64T1OHEvnFrZRLzgCmw0leib3JL/XbaE4Jbd3fs2+zc3+dCwvCuLEKKO9Hc9wg79ifjtMKFfZDE1Ba+qhw7J9tYnu7TBtaxKuWUCdtwuultEdW+NFndaUvhD/TdyjDkRiO98mgvUbm2A3q/zyDmoUpR2IEfevkMSz8MnxUo1bDTJIyoYoKwnbToI1E9RVx2uYsYKk24Pfd+r6oTbi7TxA6Ia4EiREFki2gNIAdp66IqF0Gxyd+nGlkIbQGrW+9xynU4ar3ZNq/P8EZFdO57AdEvC3ZAzpTvOVcQ0cQ4XbRSYWQHyZ8jnjggpeddTGSqVlgx1wIDAQAB + -----END PUBLIC KEY----- + KEY }.freeze def self.public_key(token) diff --git a/engines/dfc_provider/app/services/proxy_notifier.rb b/engines/dfc_provider/app/services/proxy_notifier.rb index 7da33718df..0e4b73b4bd 100644 --- a/engines/dfc_provider/app/services/proxy_notifier.rb +++ b/engines/dfc_provider/app/services/proxy_notifier.rb @@ -8,7 +8,7 @@ class ProxyNotifier TOKEN_ENDPOINTS = { 'https://api.proxy-dev.cqcm.startinblox.com/profile' => "https://kc.cqcm.startinblox.com/realms/startinblox/protocol/openid-connect/token", 'https://api.proxy-stg.cqcm.startinblox.com/profile' => "https://kc.cqcm.startinblox.com/realms/startinblox/protocol/openid-connect/token", - 'https://api.proxy.cqcm.startinblox.com/profile' => "https://authentification.cqcm.coop/realms/cqcm/protocol/openid-connect/token", + 'https://carte.cqcm.coop/profile' => "https://authentification.cqcm.coop/realms/cqcm/protocol/openid-connect/token", }.freeze diff --git a/lib/open_food_network/feature_toggle.rb b/lib/open_food_network/feature_toggle.rb index 9976f3e1fd..3c5b5ed9d0 100644 --- a/lib/open_food_network/feature_toggle.rb +++ b/lib/open_food_network/feature_toggle.rb @@ -66,7 +66,9 @@ module OpenFoodNetwork DESC "cqcm-stg" => <<~DESC, Show DFC Permissions interface to share data with CQCM staging platform. - After approval, enteprises should apppear on https://cqcm-map.startinblox.com/. + DESC + "cqcm" => <<~DESC, + Show DFC Permissions interface to share data with CQCM. DESC }.merge(conditional_features).freeze; From 91ad63d1ed85ce74b0fd5f7fc64820a7b7460546 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 24 Sep 2025 15:51:19 +1000 Subject: [PATCH 11/20] Use test token as source of truth for validity --- .../shared_contexts/authenticated_as_platform.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/engines/dfc_provider/spec/support/shared_contexts/authenticated_as_platform.rb b/engines/dfc_provider/spec/support/shared_contexts/authenticated_as_platform.rb index ea60bed0c4..d6f9e09a17 100644 --- a/engines/dfc_provider/spec/support/shared_contexts/authenticated_as_platform.rb +++ b/engines/dfc_provider/spec/support/shared_contexts/authenticated_as_platform.rb @@ -3,12 +3,18 @@ # Authenticate via Authoriztion token RSpec.shared_context "authenticated as platform" do let(:Authorization) { - "Bearer #{file_fixture('startinblox_access_token.jwt').read}" + "Bearer #{access_token}" + } + let(:access_token) { + file_fixture("startinblox_access_token.jwt").read } before do + payload = JWT.decode(access_token, nil, false, { algorithm: "RS256" }).first + issued_at = Time.zone.at(payload["iat"]) + # Once upon a time when the access token hadn't expired yet... - travel_to(Date.parse("2025-06-13")) + travel_to(issued_at) # Reset any login via session cookie. login_as nil From 1028d42e35035fc00e904759473417d8f1cac2f2 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 24 Sep 2025 15:54:20 +1000 Subject: [PATCH 12/20] Update test token for new dev client id --- engines/dfc_provider/app/services/api_user.rb | 2 +- spec/fixtures/files/startinblox_access_token.jwt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/engines/dfc_provider/app/services/api_user.rb b/engines/dfc_provider/app/services/api_user.rb index 987f328c2e..434216c07b 100644 --- a/engines/dfc_provider/app/services/api_user.rb +++ b/engines/dfc_provider/app/services/api_user.rb @@ -3,7 +3,7 @@ # Authorised user or client using the API class ApiUser CLIENT_MAP = { - "https://waterlooregionfood.ca/portal/profile" => "cqcm-dev", + "https://api.proxy-dev.cqcm.startinblox.com/profile" => "cqcm-dev", "https://api.proxy-stg.cqcm.startinblox.com/profile" => "cqcm-stg", "https://carte.cqcm.coop/profile" => "cqcm", }.freeze diff --git a/spec/fixtures/files/startinblox_access_token.jwt b/spec/fixtures/files/startinblox_access_token.jwt index ddca0faafd..48b81b90ac 100644 --- a/spec/fixtures/files/startinblox_access_token.jwt +++ b/spec/fixtures/files/startinblox_access_token.jwt @@ -1 +1 @@ -eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJRRlJES1daQ2hzMllQYnRqYl9yQUtwQzNzMFo0T2FCMEtFQ056NnlxWHQ0In0.eyJleHAiOjE3NDk3ODk3MDcsImlhdCI6MTc0OTc4OTQwNywianRpIjoiOWE4ODU4NDAtODhjNy00OTliLWIyOGUtMmE5ZmViM2EyNmU0IiwiaXNzIjoiaHR0cHM6Ly9rYy5jcWNtLnN0YXJ0aW5ibG94LmNvbS9yZWFsbXMvc3RhcnRpbmJsb3giLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiNWZlM2MyNmMtMjczNi00OGE0LWI2Y2YtYTllM2JjZmNkZjAwIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiaHR0cHM6Ly93YXRlcmxvb3JlZ2lvbmZvb2QuY2EvcG9ydGFsL3Byb2ZpbGUiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiIsImRlZmF1bHQtcm9sZXMtc3RhcnRpbmJsb3giXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6IlJlYWRFbnRlcnByaXNlIFJlYWRQcm9kdWN0cyIsImNsaWVudEhvc3QiOiIxNzIuMTguMC4xIiwiY2xpZW50QWRkcmVzcyI6IjE3Mi4xOC4wLjEiLCJjbGllbnRfaWQiOiJodHRwczovL3dhdGVybG9vcmVnaW9uZm9vZC5jYS9wb3J0YWwvcHJvZmlsZSJ9.Ln7wY0_ptRAza7M8w3yXU02TvluH028uaoJ5VHiN9-PnakokzHve7SCuSd1hvVikYAivWFIBRP97vwfpb_DW-d9Afk_XcQqcA0L36ynUIZ69X5uQ2zakEW0kB6pwqd8AL8tlWVUg2PixBXJ6daJcgWNF7RlKXg6wgy4JYL_VxD3VJjST911-z4_TMuQ2OC-3SJNwNv3BspSmUXm7F6y8xGFN7wuCPjU90WIiZ_vxTbVdM0zNtBM0uMJFeFv2_ZzoJIIiNHYLWtD3LrKcXePLSejpo-DPVWR_lGdDdM7BmzOHPKZ9KMaV-oa3lYNYC5shhJOpoB3vHngtdYdv8jq7Cg +eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJRRlJES1daQ2hzMllQYnRqYl9yQUtwQzNzMFo0T2FCMEtFQ056NnlxWHQ0In0.eyJleHAiOjE3NTg2ODY1OTUsImlhdCI6MTc1ODY4NjI5NSwianRpIjoiMjhjZmZkOGItNWNlNi00ZjgxLWFiYjUtMjY0NTg4MjhhM2E3IiwiaXNzIjoiaHR0cHM6Ly9rYy5jcWNtLnN0YXJ0aW5ibG94LmNvbS9yZWFsbXMvc3RhcnRpbmJsb3giLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiNWZlM2MyNmMtMjczNi00OGE0LWI2Y2YtYTllM2JjZmNkZjAwIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiaHR0cHM6Ly9hcGkucHJveHktZGV2LmNxY20uc3RhcnRpbmJsb3guY29tL3Byb2ZpbGUiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiIsImRlZmF1bHQtcm9sZXMtc3RhcnRpbmJsb3giXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6IlJlYWRFbnRlcnByaXNlIiwiY2xpZW50SG9zdCI6IjE3Mi4xOC4wLjEiLCJjbGllbnRBZGRyZXNzIjoiMTcyLjE4LjAuMSIsImNsaWVudF9pZCI6Imh0dHBzOi8vYXBpLnByb3h5LWRldi5jcWNtLnN0YXJ0aW5ibG94LmNvbS9wcm9maWxlIn0.DH0o4GJxKJumSbuLwdk3wz0DdwUvBM9NH6E07lkP3s1iJ23bMIE_p4gsL44RQHjB05nZWDXYrwyIJK8vLlrd8oRcZCzHgBHMQ1_-8G_JFb6s8IW_q7ROZqPwm7Wknt5fSiE7Tf3NXR2Xr6afm4f8BAcQDd2i7LjIGHomEt0pG8Q3HWzSpXJ9scJ_9enXRZTd02JLOnargKdpK9VPfGO8HjxDMip_W-aGKQ89-3XF-q3ZjC-rOxK7ZzOEbT-YE_M3nrfVeX9BnwX38vAk97UKhsLGFtupsSD3aoS6bZb2Axv3cn6e0IJ3G2iPXy36WSc_WVnhRUt8H5E7YDeHJpzTZg From f2f0d954c66f8373cb699d60cc51ac574cf65f3d Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 24 Sep 2025 16:47:50 +1000 Subject: [PATCH 13/20] Move source of truth of platforms into one place The first test tokens had an inconsistent client_id and I had to introduce multiple mappings to get the right config. But that has been harmonised and we can put the config in one place. --- app/helpers/admin/enterprises_helper.rb | 2 +- .../dfc_provider/platforms_controller.rb | 14 +++------- engines/dfc_provider/app/services/api_user.rb | 26 ++++++++++++++++--- .../app/services/proxy_notifier.rb | 24 +++++++---------- .../spec/services/proxy_notifier_spec.rb | 6 ++--- 5 files changed, 38 insertions(+), 34 deletions(-) diff --git a/app/helpers/admin/enterprises_helper.rb b/app/helpers/admin/enterprises_helper.rb index a82d96dd4a..f315f8acb4 100644 --- a/app/helpers/admin/enterprises_helper.rb +++ b/app/helpers/admin/enterprises_helper.rb @@ -50,7 +50,7 @@ module Admin end def dfc_platforms_available? - DfcProvider::PlatformsController::PLATFORM_IDS.keys.any? do |id| + ApiUser::PLATFORMS.keys.any? do |id| feature?(id, spree_current_user) end end diff --git a/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb b/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb index 770cf07bfc..56ab267274 100644 --- a/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb +++ b/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb @@ -2,14 +2,6 @@ module DfcProvider class PlatformsController < DfcProvider::ApplicationController - # List of platform identifiers. - # local ID => semantic ID - PLATFORM_IDS = { - 'cqcm-dev' => "https://api.proxy-dev.cqcm.startinblox.com/profile", - 'cqcm-stg' => "https://api.proxy-stg.cqcm.startinblox.com/profile", - 'cqcm' => "https://carte.cqcm.coop/profile", - }.freeze - prepend_before_action :move_authenticity_token before_action :check_enterprise @@ -48,7 +40,7 @@ module DfcProvider ) end - ProxyNotifier.new.refresh(PLATFORM_IDS[key]) + ProxyNotifier.new.refresh(key) render json: platform(key) end @@ -70,7 +62,7 @@ module DfcProvider end def available_platforms - PLATFORM_IDS.keys.select do |platform| + ApiUser::PLATFORMS.keys.select do |platform| feature?(platform, current_user) end end @@ -78,7 +70,7 @@ module DfcProvider def platform(key) { '@type': "dfc-t:Platform", - '@id': PLATFORM_IDS[key], + '@id': ApiUser.platform_url(key), localId: key, 'dfc-t:hasAssignedScopes': { '@type': "rdf:List", diff --git a/engines/dfc_provider/app/services/api_user.rb b/engines/dfc_provider/app/services/api_user.rb index 434216c07b..580d2458e3 100644 --- a/engines/dfc_provider/app/services/api_user.rb +++ b/engines/dfc_provider/app/services/api_user.rb @@ -2,11 +2,29 @@ # Authorised user or client using the API class ApiUser - CLIENT_MAP = { - "https://api.proxy-dev.cqcm.startinblox.com/profile" => "cqcm-dev", - "https://api.proxy-stg.cqcm.startinblox.com/profile" => "cqcm-stg", - "https://carte.cqcm.coop/profile" => "cqcm", + PLATFORMS = { + 'cqcm-dev' => { + id: "https://api.proxy-dev.cqcm.startinblox.com/profile", + tokens: "https://kc.cqcm.startinblox.com/realms/startinblox/protocol/openid-connect/token", + }, + 'cqcm-stg' => { + id: "https://api.proxy-stg.cqcm.startinblox.com/profile", + tokens: "https://kc.cqcm.startinblox.com/realms/startinblox/protocol/openid-connect/token", + }, + 'cqcm' => { + id: "https://carte.cqcm.coop/profile", + tokens: "https://authentification.cqcm.coop/realms/cqcm/protocol/openid-connect/token", + }, }.freeze + CLIENT_MAP = PLATFORMS.keys.index_by { |key| PLATFORMS.dig(key, :id) }.freeze + + def self.platform_url(platform) + PLATFORMS.dig(platform, :id) + end + + def self.token_endpoint(platform) + PLATFORMS.dig(platform, :tokens) + end def self.from_client_id(client_id) id = CLIENT_MAP[client_id] diff --git a/engines/dfc_provider/app/services/proxy_notifier.rb b/engines/dfc_provider/app/services/proxy_notifier.rb index 0e4b73b4bd..fe91f78279 100644 --- a/engines/dfc_provider/app/services/proxy_notifier.rb +++ b/engines/dfc_provider/app/services/proxy_notifier.rb @@ -5,20 +5,13 @@ require "private_address_check/tcpsocket_ext" # Call a webhook to notify a data proxy about changes in our data. class ProxyNotifier - TOKEN_ENDPOINTS = { - 'https://api.proxy-dev.cqcm.startinblox.com/profile' => "https://kc.cqcm.startinblox.com/realms/startinblox/protocol/openid-connect/token", - 'https://api.proxy-stg.cqcm.startinblox.com/profile' => "https://kc.cqcm.startinblox.com/realms/startinblox/protocol/openid-connect/token", - 'https://carte.cqcm.coop/profile' => "https://authentification.cqcm.coop/realms/cqcm/protocol/openid-connect/token", - - }.freeze - - def refresh(platform_url) + def refresh(platform) PrivateAddressCheck.only_public_connections do - notify_proxy(platform_url) + notify_proxy(platform) end end - def request_token(platform_url) + def request_token(platform) connection = Faraday.new( request: { timeout: 5 }, ) do |f| @@ -27,7 +20,7 @@ class ProxyNotifier f.response :raise_error end - url = TOKEN_ENDPOINTS[platform_url] + url = ApiUser.token_endpoint(platform) data = { grant_type: "client_credentials", client_id: ENV.fetch("OPENID_APP_ID", nil), @@ -38,8 +31,8 @@ class ProxyNotifier response.body["access_token"] end - def notify_proxy(platform_url) - token = request_token(platform_url) + def notify_proxy(platform) + token = request_token(platform) data = { eventType: "refresh", enterpriseUrlid: DfcProvider::Engine.routes.url_helpers.enterprises_url, @@ -56,10 +49,11 @@ class ProxyNotifier f.response :json f.response :raise_error end - connection.post(webhook_url(platform_url), data) + connection.post(webhook_url(platform), data) end - def webhook_url(platform_url) + def webhook_url(platform) + platform_url = ApiUser.platform_url(platform) URI.parse(platform_url).tap do |url| url.path = "/djangoldp-dfc/webhook/" end diff --git a/engines/dfc_provider/spec/services/proxy_notifier_spec.rb b/engines/dfc_provider/spec/services/proxy_notifier_spec.rb index dfda2b5d37..2cf9cfd316 100644 --- a/engines/dfc_provider/spec/services/proxy_notifier_spec.rb +++ b/engines/dfc_provider/spec/services/proxy_notifier_spec.rb @@ -8,10 +8,10 @@ require_relative "../spec_helper" # OPENID_APP_ID="..." # OPENID_APP_SECRET="..." RSpec.describe ProxyNotifier do - let(:platform_url) { "https://api.proxy-dev.cqcm.startinblox.com/profile" } + let(:platform) { "cqcm-dev" } it "receives an access token", :vcr do - token = subject.request_token(platform_url) + token = subject.request_token(platform) expect(token).to be_a String expect(token.length).to be > 20 end @@ -21,7 +21,7 @@ RSpec.describe ProxyNotifier do # If you don't have valid credentials, you'll get an unauthorized error. # Correctly authenticated, the server fails to update its data. expect { - subject.refresh(platform_url) + subject.refresh(platform) }.to raise_error Faraday::ServerError end end From 404c07a590fe5d3652bcec435daea1b120b424f6 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Mon, 29 Sep 2025 16:17:56 +1000 Subject: [PATCH 14/20] Spec DFC endpoint configuration It looks like puma finds the file only under `/.well-known/dfc` and not `/.well-known/dfc/` with a slash in staging environment while it works here in dev and test. And in any case, just placing the file in `public/` doesn't produce the right content type. --- spec/requests/well_known_spec.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 spec/requests/well_known_spec.rb diff --git a/spec/requests/well_known_spec.rb b/spec/requests/well_known_spec.rb new file mode 100644 index 0000000000..d9fad20c50 --- /dev/null +++ b/spec/requests/well_known_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe "/.well-known/" do + describe "dfc/" do + it "publishes our endpoints" do + get "/.well-known/dfc/" + + expect(response).to have_http_status :ok + expect(response.content_type).to eq "text/plain" # Should be JSON! + expect(response.body).to include "ReadEnterprise" + end + end +end From 9460d17417e1ad8d5c582c106be7c43f94586a21 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Mon, 29 Sep 2025 16:31:46 +1000 Subject: [PATCH 15/20] Publish DFC endpoints as JSON --- app/controllers/well_known_controller.rb | 13 +++++++++++++ config/routes.rb | 3 +++ public/.well-known/dfc | 4 ---- spec/requests/well_known_spec.rb | 3 ++- 4 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 app/controllers/well_known_controller.rb delete mode 100644 public/.well-known/dfc diff --git a/app/controllers/well_known_controller.rb b/app/controllers/well_known_controller.rb new file mode 100644 index 0000000000..e6999ae1e6 --- /dev/null +++ b/app/controllers/well_known_controller.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class WellKnownController < ApplicationController + layout nil + + def dfc + base = "https://github.com/datafoodconsortium/taxonomies/releases/latest/download/scopes.rdf#" + render json: { + "#{base}ReadEnterprise" => "/api/dfc/enterprises/", + "#{base}ReadProducts" => "/api/dfc/supplied_products/", + } + end +end diff --git a/config/routes.rb b/config/routes.rb index 7537dd4615..0946d21ab7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -29,6 +29,9 @@ Openfoodnetwork::Application.routes.draw do get '/orders/:id/token/:token' => 'spree/orders#show', :as => :token_order get '/payments/:id/authorize' => 'payments#redirect_to_authorize', as: "authorize_payment" + # Well known paths + get "/.well-known/dfc/", to: "well_known#dfc" + resource :cart, controller: "cart" do post :populate end diff --git a/public/.well-known/dfc b/public/.well-known/dfc deleted file mode 100644 index c02097d7ec..0000000000 --- a/public/.well-known/dfc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "https://github.com/datafoodconsortium/taxonomies/releases/latest/download/scopes.rdf#ReadEnterprise": "/api/dfc/enterprises/", - "https://github.com/datafoodconsortium/taxonomies/releases/latest/download/scopes.rdf#ReadProducts": "/api/dfc/supplied_products/", -} diff --git a/spec/requests/well_known_spec.rb b/spec/requests/well_known_spec.rb index d9fad20c50..9f6f423958 100644 --- a/spec/requests/well_known_spec.rb +++ b/spec/requests/well_known_spec.rb @@ -8,8 +8,9 @@ RSpec.describe "/.well-known/" do get "/.well-known/dfc/" expect(response).to have_http_status :ok - expect(response.content_type).to eq "text/plain" # Should be JSON! expect(response.body).to include "ReadEnterprise" + expect(response.content_type).to eq "application/json; charset=utf-8" + expect(response.parsed_body.count).to eq 2 end end end From 2761cee5e671094a674c53c61a735f2ad662a7af Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Fri, 3 Oct 2025 15:11:07 +1000 Subject: [PATCH 16/20] Publish coordinates of addresses --- engines/dfc_provider/app/services/address_builder.rb | 4 +++- engines/dfc_provider/spec/requests/enterprises_spec.rb | 9 ++++++++- .../dfc_provider/spec/services/address_builder_spec.rb | 6 ++++++ swagger/dfc.yaml | 4 ++++ 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/engines/dfc_provider/app/services/address_builder.rb b/engines/dfc_provider/app/services/address_builder.rb index 1c7b3618d3..5f20a0e67c 100644 --- a/engines/dfc_provider/app/services/address_builder.rb +++ b/engines/dfc_provider/app/services/address_builder.rb @@ -8,7 +8,9 @@ class AddressBuilder < DfcBuilder postalCode: address.zipcode, city: address.city, country: address.country.name, - region: address.state.name + region: address.state.name, + latitude: address.latitude, + longitude: address.longitude, ) end end diff --git a/engines/dfc_provider/spec/requests/enterprises_spec.rb b/engines/dfc_provider/spec/requests/enterprises_spec.rb index 059080ffc0..858fd84789 100644 --- a/engines/dfc_provider/spec/requests/enterprises_spec.rb +++ b/engines/dfc_provider/spec/requests/enterprises_spec.rb @@ -15,9 +15,16 @@ RSpec.describe "Enterprises", swagger_doc: "dfc.yaml" do email_address: "hello@example.org", phone: "0404 444 000 200", website: "https://openfoodnetwork.org", - address: build(:address, id: 40_000, address1: "42 Doveton Street"), + address:, ) end + let(:address) { + build( + :address, + id: 40_000, address1: "42 Doveton Street", + latitude: -25.345376, longitude: 131.0312006, + ) + } let!(:other_enterprise) do create( :distributor_enterprise, diff --git a/engines/dfc_provider/spec/services/address_builder_spec.rb b/engines/dfc_provider/spec/services/address_builder_spec.rb index 89020de3e7..da9830b323 100644 --- a/engines/dfc_provider/spec/services/address_builder_spec.rb +++ b/engines/dfc_provider/spec/services/address_builder_spec.rb @@ -8,6 +8,7 @@ RSpec.describe AddressBuilder do build( :address, id: 1, address1: "Paradise 15", zipcode: "0001", city: "Goosnargh", + latitude: -25.345376, longitude: 131.0312006, state: build(:state, name: "Victoria") ) } @@ -38,5 +39,10 @@ RSpec.describe AddressBuilder do it "assigns a region" do expect(result.region).to eq "Victoria" end + + it "assigns coordinates" do + expect(result.latitude).to eq(-25.345376) + expect(result.longitude).to eq 131.0312006 + end end end diff --git a/swagger/dfc.yaml b/swagger/dfc.yaml index 146edf8736..74a4fd113b 100644 --- a/swagger/dfc.yaml +++ b/swagger/dfc.yaml @@ -467,6 +467,8 @@ paths: dfc-b:hasPostalCode: '20170' dfc-b:hasCity: Herndon dfc-b:hasCountry: Australia + dfc-b:latitude: -25.345376 + dfc-b:longitude: 131.0312006 dfc-b:region: Victoria - "@id": http://test.host/api/dfc/enterprises/10000/supplied_products/10001 "@type": dfc-b:SuppliedProduct @@ -541,6 +543,8 @@ paths: dfc-b:hasPostalCode: '20170' dfc-b:hasCity: Herndon dfc-b:hasCountry: Australia + dfc-b:latitude: -25.345376 + dfc-b:longitude: 131.0312006 dfc-b:region: Victoria - "@id": http://test.host/api/dfc/enterprises/10000/supplied_products/10001 "@type": dfc-b:SuppliedProduct From 86774b3e4ea21f58a950b020ea5ec9b9cd1515b1 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Mon, 6 Oct 2025 15:55:25 +1100 Subject: [PATCH 17/20] Tell data proxy the enterprise to update --- .../app/controllers/dfc_provider/platforms_controller.rb | 4 +++- engines/dfc_provider/app/services/proxy_notifier.rb | 8 ++++---- engines/dfc_provider/spec/services/proxy_notifier_spec.rb | 3 ++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb b/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb index 56ab267274..4663359c93 100644 --- a/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb +++ b/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb @@ -40,7 +40,9 @@ module DfcProvider ) end - ProxyNotifier.new.refresh(key) + urls = DfcProvider::Engine.routes.url_helpers + enterprise_url = urls.enterprise_url(current_enterprise.id) + ProxyNotifier.new.refresh(key, enterprise_url) render json: platform(key) end diff --git a/engines/dfc_provider/app/services/proxy_notifier.rb b/engines/dfc_provider/app/services/proxy_notifier.rb index fe91f78279..908398f897 100644 --- a/engines/dfc_provider/app/services/proxy_notifier.rb +++ b/engines/dfc_provider/app/services/proxy_notifier.rb @@ -5,9 +5,9 @@ require "private_address_check/tcpsocket_ext" # Call a webhook to notify a data proxy about changes in our data. class ProxyNotifier - def refresh(platform) + def refresh(platform, enterprise_url) PrivateAddressCheck.only_public_connections do - notify_proxy(platform) + notify_proxy(platform, enterprise_url) end end @@ -31,11 +31,11 @@ class ProxyNotifier response.body["access_token"] end - def notify_proxy(platform) + def notify_proxy(platform, enterprise_url) token = request_token(platform) data = { eventType: "refresh", - enterpriseUrlid: DfcProvider::Engine.routes.url_helpers.enterprises_url, + enterpriseUrlid: enterprise_url, scope: "ReadEnterprise", } diff --git a/engines/dfc_provider/spec/services/proxy_notifier_spec.rb b/engines/dfc_provider/spec/services/proxy_notifier_spec.rb index 2cf9cfd316..f3a8b0e5ba 100644 --- a/engines/dfc_provider/spec/services/proxy_notifier_spec.rb +++ b/engines/dfc_provider/spec/services/proxy_notifier_spec.rb @@ -9,6 +9,7 @@ require_relative "../spec_helper" # OPENID_APP_SECRET="..." RSpec.describe ProxyNotifier do let(:platform) { "cqcm-dev" } + let(:enterprise_url) { "http://ofn.example.net/api/dfc/enterprises/10000" } it "receives an access token", :vcr do token = subject.request_token(platform) @@ -21,7 +22,7 @@ RSpec.describe ProxyNotifier do # If you don't have valid credentials, you'll get an unauthorized error. # Correctly authenticated, the server fails to update its data. expect { - subject.refresh(platform) + subject.refresh(platform, enterprise_url) }.to raise_error Faraday::ServerError end end From 591a279927a2aac025c6e2b5a419057404150e5d Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Mon, 6 Oct 2025 16:03:40 +1100 Subject: [PATCH 18/20] DRY controller --- .../dfc_provider/platforms_controller.rb | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb b/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb index 4663359c93..d9ecea036b 100644 --- a/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb +++ b/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb @@ -24,20 +24,10 @@ module DfcProvider scopes_to_delete = current_scopes - requested_scopes scopes_to_create = requested_scopes - current_scopes - DfcPermission.where( - user: current_user, - enterprise: current_enterprise, - scope: scopes_to_delete, - grantee: key, - ).delete_all + dfc_permissions(key).where(scope: scopes_to_delete).delete_all scopes_to_create.each do |scope| - DfcPermission.create!( - user: current_user, - enterprise: current_enterprise, - scope:, - grantee: key, - ) + dfc_permissions(key).create!(scope:) end urls = DfcProvider::Engine.routes.url_helpers @@ -91,11 +81,15 @@ module DfcProvider end def granted_scopes(platform_id) + dfc_permissions(platform_id).pluck(:scope) + end + + def dfc_permissions(platform_id) DfcPermission.where( user: current_user, enterprise: current_enterprise, grantee: platform_id, - ).pluck(:scope) + ) end # The DFC Permission Module is sending tokens in the Authorization header. From c6a34cfe341ea847fab4d595b3f27faeecc55a72 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 8 Oct 2025 16:28:45 +1100 Subject: [PATCH 19/20] Move catalog_item builder into the right module --- .../dfc_provider/catalog_items_controller.rb | 2 +- .../app/services/catalog_item_builder.rb | 15 +++++++++++++++ engines/dfc_provider/app/services/dfc_builder.rb | 15 --------------- .../app/services/enterprise_builder.rb | 2 +- .../spec/services/catalog_item_builder_spec.rb | 6 +++--- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/engines/dfc_provider/app/controllers/dfc_provider/catalog_items_controller.rb b/engines/dfc_provider/app/controllers/dfc_provider/catalog_items_controller.rb index ca029b3072..1ff786d09a 100644 --- a/engines/dfc_provider/app/controllers/dfc_provider/catalog_items_controller.rb +++ b/engines/dfc_provider/app/controllers/dfc_provider/catalog_items_controller.rb @@ -22,7 +22,7 @@ module DfcProvider end def show - catalog_item = DfcBuilder.catalog_item(variant) + catalog_item = CatalogItemBuilder.catalog_item(variant) offers = catalog_item.offers render json: DfcIo.export(catalog_item, *offers) end diff --git a/engines/dfc_provider/app/services/catalog_item_builder.rb b/engines/dfc_provider/app/services/catalog_item_builder.rb index 4a0c28005c..9d719169f4 100644 --- a/engines/dfc_provider/app/services/catalog_item_builder.rb +++ b/engines/dfc_provider/app/services/catalog_item_builder.rb @@ -1,6 +1,21 @@ # frozen_string_literal: true class CatalogItemBuilder < DfcBuilder + def self.catalog_item(variant) + id = urls.enterprise_catalog_item_url( + enterprise_id: variant.supplier_id, + id: variant.id, + ) + product = SuppliedProductBuilder.supplied_product(variant) + + DataFoodConsortium::Connector::CatalogItem.new( + id, product:, + sku: variant.sku, + stockLimitation: stock_limitation(variant), + offers: [OfferBuilder.build(variant)], + ) + end + def self.apply_stock(item, variant) limit = item&.stockLimitation diff --git a/engines/dfc_provider/app/services/dfc_builder.rb b/engines/dfc_provider/app/services/dfc_builder.rb index 4f55e301fb..f91cc78af0 100644 --- a/engines/dfc_provider/app/services/dfc_builder.rb +++ b/engines/dfc_provider/app/services/dfc_builder.rb @@ -1,21 +1,6 @@ # frozen_string_literal: true class DfcBuilder - def self.catalog_item(variant) - id = urls.enterprise_catalog_item_url( - enterprise_id: variant.supplier_id, - id: variant.id, - ) - product = SuppliedProductBuilder.supplied_product(variant) - - DataFoodConsortium::Connector::CatalogItem.new( - id, product:, - sku: variant.sku, - stockLimitation: stock_limitation(variant), - offers: [OfferBuilder.build(variant)], - ) - end - # The DFC sees "empty" stock as unlimited. # http://static.datafoodconsortium.org/conception/DFC%20-%20Business%20rules.pdf def self.stock_limitation(variant) diff --git a/engines/dfc_provider/app/services/enterprise_builder.rb b/engines/dfc_provider/app/services/enterprise_builder.rb index 6b5c933c57..8f37b478d5 100644 --- a/engines/dfc_provider/app/services/enterprise_builder.rb +++ b/engines/dfc_provider/app/services/enterprise_builder.rb @@ -6,7 +6,7 @@ class EnterpriseBuilder < DfcBuilder # in the DFC standard. variants = enterprise.supplied_variants.to_a - catalog_items = variants.map(&method(:catalog_item)) + catalog_items = variants.map(&CatalogItemBuilder.method(:catalog_item)) supplied_products = catalog_items.map(&:product) address = AddressBuilder.address(enterprise.address) diff --git a/engines/dfc_provider/spec/services/catalog_item_builder_spec.rb b/engines/dfc_provider/spec/services/catalog_item_builder_spec.rb index 15052e034d..f42e752292 100644 --- a/engines/dfc_provider/spec/services/catalog_item_builder_spec.rb +++ b/engines/dfc_provider/spec/services/catalog_item_builder_spec.rb @@ -2,7 +2,7 @@ require_relative "../spec_helper" -RSpec.describe DfcBuilder do +RSpec.describe CatalogItemBuilder do let(:variant) { build(:variant) } describe ".catalog_item" do @@ -10,7 +10,7 @@ RSpec.describe DfcBuilder do variant.id = 5 variant.supplier_id = 7 - item = DfcBuilder.catalog_item(variant) + item = CatalogItemBuilder.catalog_item(variant) expect(item.semanticId).to eq( "http://test.host/api/dfc/enterprises/7/catalog_items/5" @@ -21,7 +21,7 @@ RSpec.describe DfcBuilder do variant.id = 5 variant.supplier_id = 7 - item = DfcBuilder.catalog_item(variant) + item = CatalogItemBuilder.catalog_item(variant) expect(item.product.semanticId).to eq( "http://test.host/api/dfc/enterprises/7/supplied_products/5" From b2da57b4963a96762d0bb20428f3c523622be97d Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Fri, 10 Oct 2025 16:18:00 +1100 Subject: [PATCH 20/20] Publish supplier of catalog item --- .rubocop_todo.yml | 32 +++++++++++++++---- .../app/services/catalog_item_builder.rb | 4 ++- engines/dfc_provider/lib/dfc_provider.rb | 1 + .../lib/dfc_provider/catalog_item.rb | 16 ++++++++++ .../services/catalog_item_builder_spec.rb | 15 ++++++--- 5 files changed, 57 insertions(+), 11 deletions(-) create mode 100644 engines/dfc_provider/lib/dfc_provider/catalog_item.rb diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 2ced80c5ae..8403c4cdb6 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -47,7 +47,7 @@ Metrics/BlockNesting: Exclude: - 'app/models/spree/payment/processing.rb' -# Offense count: 47 +# Offense count: 48 # Configuration parameters: CountComments, Max, CountAsOne. Metrics/ClassLength: Exclude: @@ -88,6 +88,7 @@ Metrics/ClassLength: - 'app/services/cart_service.rb' - 'app/services/order_cycles/form_service.rb' - 'app/services/orders/sync_service.rb' + - 'app/services/permissions/order.rb' - 'app/services/sets/product_set.rb' - 'engines/order_management/app/services/order_management/order/updater.rb' - 'lib/open_food_network/enterprise_fee_calculator.rb' @@ -98,7 +99,6 @@ Metrics/ClassLength: - 'lib/reporting/reports/enterprise_fee_summary/enterprise_fees_with_tax_report_by_producer.rb' - 'lib/reporting/reports/enterprise_fee_summary/scope.rb' - 'lib/reporting/reports/xero_invoices/base.rb' - - 'app/services/permissions/order.rb' # Offense count: 30 # Configuration parameters: AllowedMethods, AllowedPatterns, Max. @@ -129,14 +129,13 @@ Metrics/CyclomaticComplexity: - 'lib/spree/localized_number.rb' - 'spec/models/product_importer_spec.rb' -# Offense count: 23 +# Offense count: 22 # Configuration parameters: CountComments, Max, CountAsOne, AllowedMethods, AllowedPatterns. Metrics/MethodLength: Exclude: - 'app/controllers/admin/enterprises_controller.rb' - 'app/controllers/payment_gateways/paypal_controller.rb' - 'app/controllers/spree/orders_controller.rb' - - 'app/helpers/spree/admin/navigation_helper.rb' - 'app/models/spree/ability.rb' - 'app/models/spree/gateway/pay_pal_express.rb' - 'app/models/spree/order/checkout.rb' @@ -149,7 +148,7 @@ Metrics/MethodLength: - 'lib/spree/localized_number.rb' - 'lib/tasks/sample_data/product_factory.rb' -# Offense count: 47 +# Offense count: 10 # Configuration parameters: CountComments, Max, CountAsOne. Metrics/ModuleLength: Exclude: @@ -174,7 +173,7 @@ Metrics/ParameterLists: - 'spec/support/controller_requests_helper.rb' - 'spec/system/admin/reports_spec.rb' -# Offense count: 3 +# Offense count: 4 # Configuration parameters: AllowedMethods, AllowedPatterns, Max. Metrics/PerceivedComplexity: Exclude: @@ -182,6 +181,27 @@ Metrics/PerceivedComplexity: - 'app/models/spree/ability.rb' - 'app/models/spree/order/checkout.rb' +# Offense count: 1 +# Configuration parameters: EnforcedStyle, AllowedPatterns. +# SupportedStyles: snake_case, camelCase +Naming/MethodName: + Exclude: + - 'engines/dfc_provider/lib/dfc_provider/catalog_item.rb' + +# Offense count: 1 +# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. +# AllowedNames: as, at, by, cc, db, id, if, in, io, ip, of, on, os, pp, to +Naming/MethodParameterName: + Exclude: + - 'engines/dfc_provider/lib/dfc_provider/catalog_item.rb' + +# Offense count: 3 +# Configuration parameters: EnforcedStyle, AllowedIdentifiers, AllowedPatterns. +# SupportedStyles: snake_case, camelCase +Naming/VariableName: + Exclude: + - 'engines/dfc_provider/lib/dfc_provider/catalog_item.rb' + # Offense count: 1 # Configuration parameters: TransactionMethods. Rails/TransactionExitStatement: diff --git a/engines/dfc_provider/app/services/catalog_item_builder.rb b/engines/dfc_provider/app/services/catalog_item_builder.rb index 9d719169f4..32cb06e284 100644 --- a/engines/dfc_provider/app/services/catalog_item_builder.rb +++ b/engines/dfc_provider/app/services/catalog_item_builder.rb @@ -6,13 +6,15 @@ class CatalogItemBuilder < DfcBuilder enterprise_id: variant.supplier_id, id: variant.id, ) + supplier_url = urls.enterprise_url(variant.supplier_id) product = SuppliedProductBuilder.supplied_product(variant) - DataFoodConsortium::Connector::CatalogItem.new( + DfcProvider::CatalogItem.new( id, product:, sku: variant.sku, stockLimitation: stock_limitation(variant), offers: [OfferBuilder.build(variant)], + managedBy: supplier_url, ) end diff --git a/engines/dfc_provider/lib/dfc_provider.rb b/engines/dfc_provider/lib/dfc_provider.rb index b58f456953..59935871e9 100644 --- a/engines/dfc_provider/lib/dfc_provider.rb +++ b/engines/dfc_provider/lib/dfc_provider.rb @@ -9,6 +9,7 @@ require "dfc_provider/engine" # Custom data types require "dfc_provider/supplied_product" require "dfc_provider/address" +require "dfc_provider/catalog_item" require "dfc_provider/coordination" # 🙈 Monkey-patch a better inspector for semantic objects diff --git a/engines/dfc_provider/lib/dfc_provider/catalog_item.rb b/engines/dfc_provider/lib/dfc_provider/catalog_item.rb new file mode 100644 index 0000000000..a7a88fef96 --- /dev/null +++ b/engines/dfc_provider/lib/dfc_provider/catalog_item.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +# Temporary solution. +module DfcProvider + class CatalogItem < DataFoodConsortium::Connector::CatalogItem + attr_accessor :managedBy + + def initialize(semantic_id, managedBy: "", **properties) + super(semantic_id, **properties) + @managedBy = managedBy + + registerSemanticProperty("dfc-b:managedBy", &method("managedBy")) + .valueSetter = method("managedBy=") + end + end +end diff --git a/engines/dfc_provider/spec/services/catalog_item_builder_spec.rb b/engines/dfc_provider/spec/services/catalog_item_builder_spec.rb index f42e752292..2ee234616b 100644 --- a/engines/dfc_provider/spec/services/catalog_item_builder_spec.rb +++ b/engines/dfc_provider/spec/services/catalog_item_builder_spec.rb @@ -6,10 +6,12 @@ RSpec.describe CatalogItemBuilder do let(:variant) { build(:variant) } describe ".catalog_item" do - it "assigns a semantic id" do + before do variant.id = 5 variant.supplier_id = 7 + end + it "assigns a semantic id" do item = CatalogItemBuilder.catalog_item(variant) expect(item.semanticId).to eq( @@ -18,15 +20,20 @@ RSpec.describe CatalogItemBuilder do end it "refers to a supplied product" do - variant.id = 5 - variant.supplier_id = 7 - item = CatalogItemBuilder.catalog_item(variant) expect(item.product.semanticId).to eq( "http://test.host/api/dfc/enterprises/7/supplied_products/5" ) end + + it "refers to the supplier" do + item = CatalogItemBuilder.catalog_item(variant) + + expect(item.managedBy).to eq( + "http://test.host/api/dfc/enterprises/7" + ) + end end describe ".apply_stock" do