Clinical Practice Guidelines
1.0.0 - ci-build International flag

Clinical Practice Guidelines, published by HL7 International - Clinical Decision Support WG. This is not an authorized publication; it is the continuous build for version 1.0.0). This version is based on the current content of https://github.com/HL7/cqf-recommendations/ and changes regularly. See the Directory of published versions

: Opioid CDS R4 Common Logic - XML Representation

Active as of 2018-03-25

Raw xml | Download



<Library xmlns="http://hl7.org/fhir">
  <id value="OpioidCDSR4Common"/>
  <text>
    <status value="generated"/>
    <div xmlns="http://www.w3.org/1999/xhtml"><h2>Participants</h2><table class="grid"><tr><td>Author</td><td>Kensaku Kawamoto, MD, PhD, MHS</td></tr><tr><td>Author</td><td>Bryn Rhodes</td></tr><tr><td>Author</td><td>Floyd Eisenberg, MD, MPH</td></tr><tr><td>Author</td><td>Robert McClure, MD, MPH</td></tr></table><h2>Related Artifacts</h2><table class="grid"><tr><td>documentation</td><td>CDC guideline for prescribing opioids for chronic pain</td><td>https://www.cdc.gov/mmwr/volumes/65/rr/rr6501e1.htm?CDC_AA_refVal=https%3A%2F%2Fwww.cdc.gov%2Fmmwr%2Fvolumes%2F65%2Frr%2Frr6501e1er.htm</td></tr></table><h2>Contents</h2><p><code>text/cql</code></p><pre><code class="language-sql">library OpioidCDSR4Common version '0.1.0'

using FHIR version '4.0.0'

include FHIRHelpers version '4.0.0' called FHIRHelpers
include OMTKLogic version '0.0.0' called OMTKLogic

codesystem &quot;SNOMED&quot;: 'http://snomed.info/sct'
codesystem &quot;Medication Request Category Codes&quot;: 'http://terminology.hl7.org/CodeSystem/medicationrequest-category'

valueset &quot;Active Condition&quot;: 'http://hl7.org/fhir/uv/cpg/ValueSet/cpg-active-condition'
valueset &quot;Benzodiazepines&quot;: 'http://fhir.org/guides/cdc/opioid-cds/ValueSet/benzodiazepines'
// TODO: Fix this name
valueset &quot;End of Life Conditions&quot;: 'http://fhir.org/guides/cdc/opioid-cds/ValueSet/end-of-life-conditions'
// Harvested from VSAC - OID: 2.16.840.1.113762.1.4.1108.15
// NOTE: This harvest note is incorrect, none of the following 3 value sets contain any of the codes in the above referenced valueset
// Rob will construct an appropriate hospice value set aligned with current eCQM program usage and we will use that when available
valueset &quot;Hospice Disposition&quot;: 'http://fhir.org/guides/cdc/opioid-cds/ValueSet/hospice-disposition'
valueset &quot;Hospice Finding Codes&quot;: 'http://fhir.org/guides/cdc/opioid-cds/ValueSet/hospice-finding'
valueset &quot;Hospice Procedure Codes&quot;: 'http://fhir.org/guides/cdc/opioid-cds/ValueSet/hospice-procedure'
valueset &quot;Illicit Drug Screening&quot;: 'http://fhir.org/guides/cdc/opioid-cds/ValueSet/illicit-drug-urine-screening'
// Harvested from VSAC - OID: 2.16.840.1.113883.3.526.3.1259
valueset &quot;Limited Life Expectancy Conditions&quot;: 'http://fhir.org/guides/cdc/opioid-cds/ValueSet/limited-life-expectancy-conditions'
valueset &quot;Long Acting Opioids&quot;: 'http://fhir.org/guides/cdc/opioid-cds/ValueSet/long-acting-opioids'
valueset &quot;Naloxone&quot;: 'http://fhir.org/guides/cdc/opioid-cds/ValueSet/naloxone'
valueset &quot;Risk Assessment&quot;: 'http://fhir.org/guides/cdc/opioid-cds/ValueSet/opioid-abuse-assessment'
valueset &quot;Opioid Drug Screening&quot;: 'http://fhir.org/guides/cdc/opioid-cds/ValueSet/opioid-urine-screening'
valueset &quot;Ambulatory Abuse Potential Opioids&quot;: 'http://fhir.org/guides/cdc/opioid-cds/ValueSet/opioids-abused-in-ambulatory-care'
valueset &quot;Substance Abuse&quot;: 'http://fhir.org/guides/cdc/opioid-cds/ValueSet/substance-abuse'

// TODO: Turn this into a valueset
code &quot;Referral to Hospice&quot;: '306205009' from &quot;SNOMED&quot;
// TODO: Turn this into a valueset
code &quot;Outpatient&quot;: 'outpatient' from &quot;Medication Request Category Codes&quot;

// TODO: Capture process decisions for long-term opioid use
define IsForChronicPain: true

define &quot;Active Ambulatory Opioid Rx&quot;:
  [MedicationRequest: &quot;Ambulatory Abuse Potential Opioids&quot;] Rx
    where Rx.status = 'active'
      and ToCodes(Rx.category.coding) contains &quot;Outpatient&quot;

define &quot;Active Ambulatory Benzodiazepine Rx&quot;:
  [MedicationRequest: &quot;Benzodiazepines&quot;] Rx
    where Rx.status = 'active'
      and ToCodes(Rx.category.coding) contains &quot;Outpatient&quot;

define &quot;Active Ambulatory Naloxone Rx&quot;:
  [MedicationRequest: &quot;Naloxone&quot;] Rx
    where Rx.status = 'active'
      and ToCodes(Rx.category.coding) contains &quot;Outpatient&quot;

define &quot;Ambulatory Opioid Rx&quot;:
  [MedicationRequest: &quot;Ambulatory Abuse Potential Opioids&quot;] Rx
      where ToCodes(Rx.category.coding) contains &quot;Outpatient&quot;

define &quot;End of Life Assessment&quot;:
  // 1. Conditions indicating end of life or with limited life expectancy
  exists (
    (
      [Condition: &quot;End of Life Conditions&quot;] C
        where C.clinicalStatus in &quot;Active Condition&quot;
    )
    union
    (
      [Condition: code in &quot;Limited Life Expectancy Conditions&quot;] C
        where C.clinicalStatus in &quot;Active Condition&quot;
    )
  )
  // 2. Admitted/referred/discharged to hospice care
  or exists (
    (
      [ServiceRequest: code in &quot;Referral to Hospice&quot;] RR
        where RR.status in { 'active', 'completed' }
    )
    union
    (
      [ServiceRequest: code in &quot;Hospice Procedure Codes&quot;] P
        where P.status in { 'in-progress', 'completed' }
    )
    union
    (
      [ServiceRequest: code in &quot;Hospice Procedure Codes&quot;] E
        where E.status in { 'planned', 'arrived', 'in-progress', 'finished', 'onleave' }
    )
    union
    (
      [Observation: code in &quot;Hospice Finding Codes&quot;] O
        where not (O.status in { 'unknown', 'entered-in-error', 'cancelled' })
    )
    union
    (
      [Encounter] E
        where
          (
            if E.hospitalization.dischargeDisposition.coding is null
                or not exists (E.hospitalization.dischargeDisposition.coding)
              then false
            else E.hospitalization.dischargeDisposition in &quot;Hospice Disposition&quot;
          )
          and E.status in { 'planned', 'arrived', 'in-progress', 'finished', 'onleave' }
    )
  )

define function Prescriptions(Orders List&lt;MedicationRequest&gt;):
  Orders O
    let
      // NOTE: Assuming medication is specified as a CodeableConcept with a single RxNorm code
      rxNormCode: ToCode(O.medication.coding[0]),
      medicationName: OMTKLogic.GetMedicationName(rxNormCode),
      // NOTE: Assuming a single dosage instruction element
      dosageInstruction: O.dosageInstruction[0],
      repeat: dosageInstruction.timing.repeat,
      frequency: Coalesce(repeat.frequencyMax.value, repeat.frequency.value),
      period: System.Quantity { value: repeat.period.value, unit: repeat.periodUnit.value },
      doseDescription:
        Coalesce(
          // There should be a conversion from FHIR.SimpleQuantity to System.Quantity
          if dosageInstruction.doseAndRate[0].dose is FHIR.Range
            then ToString(ToQuantity(dosageInstruction.doseAndRate[0].dose.low))
                          + '-' + ToString(ToQuantity(dosageInstruction.doseAndRate[0].dose.high))
                          + dosageInstruction.doseAndRate[0].dose.high.unit.value
            else ToString(ToQuantity(dosageInstruction.doseAndRate[0].dose)),
            ''
        ),
      frequencyDescription:
        ToString(dosageInstruction.timing.repeat.frequency.value) +
          Coalesce(
            '-' + ToString(dosageInstruction.timing.repeat.frequencyMax.value),
            ''
          )
    return {
      rxNormCode: rxNormCode,
      isDraft: O.status.value = 'draft',
      // NOTE: Assuming asNeeded is expressed as a boolean
      isPRN: dosageInstruction.asNeeded.value,
      prescription:
        if dosageInstruction.text is not null then
          medicationName + ' ' + dosageInstruction.text.value
        else
          // TODO: Shouldn't need the .value here on asNeededBoolean
          medicationName + ' ' + doseDescription + ' q' + frequencyDescription + (if dosageInstruction.asNeeded.value then ' PRN' else ''),
      // TODO: Shouldn't need the ToQuantity here...
      dose: if dosageInstruction.doseAndRate[0].dose is FHIR.Range
            then ToQuantity(dosageInstruction.doseAndRate[0].dose.high)
            else ToQuantity(dosageInstruction.doseAndRate[0].dose),
      dosesPerDay: Coalesce(OMTKLogic.ToDaily(frequency, period), 1.0)
    }

define function MME(prescriptions List&lt;MedicationRequest&gt;):
  (Prescriptions(prescriptions)) P
    let mme: SingletonFrom(OMTKLogic.CalculateMMEs({ { rxNormCode: P.rxNormCode, doseQuantity: P.dose, dosesPerDay: P.dosesPerDay } }))
    return {
      rxNormCode: P.rxNormCode,
      isDraft: P.isDraft,
      isPRN: P.isPRN,
      prescription: P.prescription,
      dailyDose: mme.dailyDoseDescription,
      conversionFactor: mme.conversionFactor,
      mme: mme.mme
    }

define function TotalMME(prescriptions List&lt;MedicationRequest&gt;):
  System.Quantity {
    value: Sum((MME(prescriptions)) M return M.mme.value),
    unit: 'mg/d'
  }

define function ProbableDaysInRange(Orders List&lt;MedicationRequest&gt;, daysPast Integer, numDaysInDaysPast Integer):
  Orders orders
    let
      frequency: orders.dosageInstruction[0].timing.repeat.frequency.value,
      period: orders.dosageInstruction[0].timing.repeat.period.value,
      periodDays: GetPeriodDays(orders.dosageInstruction[0].timing.repeat.periodUnit.value),
      dosesPerDay:
        if (frequency / (period * periodDays)) &gt;= 1.0
        then 1.0
        else frequency / (period * periodDays),
      repeat: orders.dispenseRequest.numberOfRepeatsAllowed.value,
      supplyDuration: GetDurationInDays(orders.dispenseRequest.expectedSupplyDuration),
      validityPeriod: days between orders.dispenseRequest.validityPeriod.&quot;start&quot;.value and Today(),
      endDifference:
        if orders.dispenseRequest.validityPeriod.&quot;end&quot;.value &lt; Today()
        then days between orders.dispenseRequest.validityPeriod.&quot;end&quot;.value and Today()
        else 0
    return
      if (repeat * supplyDuration) &lt; numDaysInDaysPast then false
      else
        (dosesPerDay * ((repeat * supplyDuration) / validityPeriod) * (daysPast - endDifference)) &gt;= numDaysInDaysPast

define function GetPeriodDays(value System.String):
  case
    when value = 'a' then 365.0
    when value = 'mo' then 30.0
    when value = 'h' then 1.0/24.0
    when value = 'min' then 1.0/24.0*60.0
    when value = 's' then 1.0/24.0*60.0*60.0
    when value = 'ms' then 1.0/24.0*60.0*60.0*1000.0
    else 1.0
  end

  define function GetDurationInDays(value FHIR.Duration):
    case
      when StartsWith(value.unit.value, 'a') then value.value.value * 365.0
      when StartsWith(value.unit.value, 'mo') then value.value.value * 30.0
      when StartsWith(value.unit.value, 'wk') then value.value.value * 7.0
      when StartsWith(value.unit.value, 'd') then value.value.value
      when StartsWith(value.unit.value, 'h') then value.value.value / 24.0
      when StartsWith(value.unit.value, 'min') then value.value.value / 60.0 / 24.0
      when StartsWith(value.unit.value, 's') then value.value.value / 60.0 / 60.0 / 24.0
      when StartsWith(value.unit.value, 'ms') then value.value.value / 60.0 / 60.0 / 24.0 / 1000.0
      else Message(1000, true, 'Undefined', 'Error', 'Unsupported duration unit')
    end

define function GetIngredient(rxNormCode Code):
  OMTKLogic.GetIngredients(rxNormCode).ingredientName

define function GetIngredients(rxNormCodes List&lt;Code&gt;):
  rxNormCodes rnc return GetIngredient(rnc)

define function GetMedicationNames(medications List&lt;MedicationRequest&gt;):
  medications M
    return OMTKLogic.GetIngredients(ToRxNormCode(M.medication.coding)).rxNormCode.display

/*
*  Conversion Functions
*/

define function ToCode(coding FHIR.Coding):
  System.Code {
    code: coding.code.value,
    system: coding.system.value,
    version: coding.version.value,
    display: coding.display.value
  }

define function ToCodes(coding List&lt;FHIR.Coding&gt;):
  coding c return ToCode(c)

define function ToRxNormCode(coding List&lt;FHIR.Coding&gt;):
  singleton from (
    coding C where C.system = 'http://www.nlm.nih.gov/research/umls/rxnorm'
  )

define function ToQuantity(quantity FHIR.Quantity):
  System.Quantity { value: quantity.value.value, unit: quantity.unit.value }
</code></pre><p><code>No Content</code> (<code>application/elm+xml</code>)</p></div>
  </text>
  <url
       value="http://fhir.org/guides/cdc/opioid-cds/Library/OpioidCDSR4Common"/>
  <version value="1.0.0"/>
  <name value="OpioidCDSR4Common"/>
  <title value="Opioid CDS R4 Common Logic"/>
  <status value="active"/>
  <experimental value="true"/>
  <type>
    <coding>
      <system value="http://terminology.hl7.org/CodeSystem/library-type"/>
      <code value="logic-library"/>
      <display value="Logic Library"/>
    </coding>
  </type>
  <date value="2018-03-25T13:49:09-06:00"/>
  <publisher value="HL7 International - Clinical Decision Support WG"/>
  <contact>
    <name value="HL7 International - Clinical Decision Support WG"/>
    <telecom>
      <system value="url"/>
      <value value="http://www.hl7.org/Special/committees/dss/index.cfm"/>
    </telecom>
  </contact>
  <description
               value="Common Opioid Decision Support Logic for use in implementing CDC Opioid Prescribing Guidelines."/>
  <useContext>
    <code>
      <system
              value="http://terminology.hl7.org/CodeSystem/usage-context-type"/>
      <code value="focus"/>
      <display value="Clinical Focus"/>
    </code>
    <valueCodeableConcept>
      <coding>
        <system value="http://snomed.info/sct"/>
        <code value="182888003"/>
        <display value="Medication requested (situation)"/>
      </coding>
    </valueCodeableConcept>
  </useContext>
  <useContext>
    <code>
      <system
              value="http://terminology.hl7.org/CodeSystem/usage-context-type"/>
      <code value="focus"/>
      <display value="Clinical Focus"/>
    </code>
    <valueCodeableConcept>
      <coding>
        <system value="http://snomed.info/sct"/>
        <code value="82423001"/>
        <display value="Chronic pain (finding)"/>
      </coding>
    </valueCodeableConcept>
  </useContext>
  <jurisdiction>
    <coding>
      <system value="http://unstats.un.org/unsd/methods/m49/m49.htm"/>
      <code value="001"/>
      <display value="World"/>
    </coding>
  </jurisdiction>
  <purpose
           value="This library contains common logic across recommendations including MME calculations, conversions, and looking up codes in valuesets."/>
  <usage
         value="This library is used for decision support for opioid guideline recommendations when applying PlanDefinitions."/>
  <copyright value="© CDC 2016+."/>
  <topic>
    <text value="Opioid Prescribing"/>
  </topic>
  <author>
    <name value="Kensaku Kawamoto, MD, PhD, MHS"/>
  </author>
  <author>
    <name value="Bryn Rhodes"/>
  </author>
  <author>
    <name value="Floyd Eisenberg, MD, MPH"/>
  </author>
  <author>
    <name value="Robert McClure, MD, MPH"/>
  </author>
  <relatedArtifact>
    <type value="documentation"/>
    <display value="CDC guideline for prescribing opioids for chronic pain"/>
    <url
         value="https://www.cdc.gov/mmwr/volumes/65/rr/rr6501e1.htm?CDC_AA_refVal=https%3A%2F%2Fwww.cdc.gov%2Fmmwr%2Fvolumes%2F65%2Frr%2Frr6501e1er.htm"/>
    <document>
      <url
           value="https://www.cdc.gov/mmwr/volumes/65/rr/rr6501e1.htm?CDC_AA_refVal=https%3A%2F%2Fwww.cdc.gov%2Fmmwr%2Fvolumes%2F65%2Frr%2Frr6501e1er.htm"/>
    </document>
  </relatedArtifact>
  <content>
    <contentType value="text/cql"/>
    <data
          value="bGlicmFyeSBPcGlvaWRDRFNSNENvbW1vbiB2ZXJzaW9uICcwLjEuMCcKCnVzaW5nIEZISVIgdmVyc2lvbiAnNC4wLjAnCgppbmNsdWRlIEZISVJIZWxwZXJzIHZlcnNpb24gJzQuMC4wJyBjYWxsZWQgRkhJUkhlbHBlcnMKaW5jbHVkZSBPTVRLTG9naWMgdmVyc2lvbiAnMC4wLjAnIGNhbGxlZCBPTVRLTG9naWMKCmNvZGVzeXN0ZW0gIlNOT01FRCI6ICdodHRwOi8vc25vbWVkLmluZm8vc2N0Jwpjb2Rlc3lzdGVtICJNZWRpY2F0aW9uIFJlcXVlc3QgQ2F0ZWdvcnkgQ29kZXMiOiAnaHR0cDovL3Rlcm1pbm9sb2d5LmhsNy5vcmcvQ29kZVN5c3RlbS9tZWRpY2F0aW9ucmVxdWVzdC1jYXRlZ29yeScKCnZhbHVlc2V0ICJBY3RpdmUgQ29uZGl0aW9uIjogJ2h0dHA6Ly9obDcub3JnL2ZoaXIvdXYvY3BnL1ZhbHVlU2V0L2NwZy1hY3RpdmUtY29uZGl0aW9uJwp2YWx1ZXNldCAiQmVuem9kaWF6ZXBpbmVzIjogJ2h0dHA6Ly9maGlyLm9yZy9ndWlkZXMvY2RjL29waW9pZC1jZHMvVmFsdWVTZXQvYmVuem9kaWF6ZXBpbmVzJwovLyBUT0RPOiBGaXggdGhpcyBuYW1lCnZhbHVlc2V0ICJFbmQgb2YgTGlmZSBDb25kaXRpb25zIjogJ2h0dHA6Ly9maGlyLm9yZy9ndWlkZXMvY2RjL29waW9pZC1jZHMvVmFsdWVTZXQvZW5kLW9mLWxpZmUtY29uZGl0aW9ucycKLy8gSGFydmVzdGVkIGZyb20gVlNBQyAtIE9JRDogMi4xNi44NDAuMS4xMTM3NjIuMS40LjExMDguMTUKLy8gTk9URTogVGhpcyBoYXJ2ZXN0IG5vdGUgaXMgaW5jb3JyZWN0LCBub25lIG9mIHRoZSBmb2xsb3dpbmcgMyB2YWx1ZSBzZXRzIGNvbnRhaW4gYW55IG9mIHRoZSBjb2RlcyBpbiB0aGUgYWJvdmUgcmVmZXJlbmNlZCB2YWx1ZXNldAovLyBSb2Igd2lsbCBjb25zdHJ1Y3QgYW4gYXBwcm9wcmlhdGUgaG9zcGljZSB2YWx1ZSBzZXQgYWxpZ25lZCB3aXRoIGN1cnJlbnQgZUNRTSBwcm9ncmFtIHVzYWdlIGFuZCB3ZSB3aWxsIHVzZSB0aGF0IHdoZW4gYXZhaWxhYmxlCnZhbHVlc2V0ICJIb3NwaWNlIERpc3Bvc2l0aW9uIjogJ2h0dHA6Ly9maGlyLm9yZy9ndWlkZXMvY2RjL29waW9pZC1jZHMvVmFsdWVTZXQvaG9zcGljZS1kaXNwb3NpdGlvbicKdmFsdWVzZXQgIkhvc3BpY2UgRmluZGluZyBDb2RlcyI6ICdodHRwOi8vZmhpci5vcmcvZ3VpZGVzL2NkYy9vcGlvaWQtY2RzL1ZhbHVlU2V0L2hvc3BpY2UtZmluZGluZycKdmFsdWVzZXQgIkhvc3BpY2UgUHJvY2VkdXJlIENvZGVzIjogJ2h0dHA6Ly9maGlyLm9yZy9ndWlkZXMvY2RjL29waW9pZC1jZHMvVmFsdWVTZXQvaG9zcGljZS1wcm9jZWR1cmUnCnZhbHVlc2V0ICJJbGxpY2l0IERydWcgU2NyZWVuaW5nIjogJ2h0dHA6Ly9maGlyLm9yZy9ndWlkZXMvY2RjL29waW9pZC1jZHMvVmFsdWVTZXQvaWxsaWNpdC1kcnVnLXVyaW5lLXNjcmVlbmluZycKLy8gSGFydmVzdGVkIGZyb20gVlNBQyAtIE9JRDogMi4xNi44NDAuMS4xMTM4ODMuMy41MjYuMy4xMjU5CnZhbHVlc2V0ICJMaW1pdGVkIExpZmUgRXhwZWN0YW5jeSBDb25kaXRpb25zIjogJ2h0dHA6Ly9maGlyLm9yZy9ndWlkZXMvY2RjL29waW9pZC1jZHMvVmFsdWVTZXQvbGltaXRlZC1saWZlLWV4cGVjdGFuY3ktY29uZGl0aW9ucycKdmFsdWVzZXQgIkxvbmcgQWN0aW5nIE9waW9pZHMiOiAnaHR0cDovL2ZoaXIub3JnL2d1aWRlcy9jZGMvb3Bpb2lkLWNkcy9WYWx1ZVNldC9sb25nLWFjdGluZy1vcGlvaWRzJwp2YWx1ZXNldCAiTmFsb3hvbmUiOiAnaHR0cDovL2ZoaXIub3JnL2d1aWRlcy9jZGMvb3Bpb2lkLWNkcy9WYWx1ZVNldC9uYWxveG9uZScKdmFsdWVzZXQgIlJpc2sgQXNzZXNzbWVudCI6ICdodHRwOi8vZmhpci5vcmcvZ3VpZGVzL2NkYy9vcGlvaWQtY2RzL1ZhbHVlU2V0L29waW9pZC1hYnVzZS1hc3Nlc3NtZW50Jwp2YWx1ZXNldCAiT3Bpb2lkIERydWcgU2NyZWVuaW5nIjogJ2h0dHA6Ly9maGlyLm9yZy9ndWlkZXMvY2RjL29waW9pZC1jZHMvVmFsdWVTZXQvb3Bpb2lkLXVyaW5lLXNjcmVlbmluZycKdmFsdWVzZXQgIkFtYnVsYXRvcnkgQWJ1c2UgUG90ZW50aWFsIE9waW9pZHMiOiAnaHR0cDovL2ZoaXIub3JnL2d1aWRlcy9jZGMvb3Bpb2lkLWNkcy9WYWx1ZVNldC9vcGlvaWRzLWFidXNlZC1pbi1hbWJ1bGF0b3J5LWNhcmUnCnZhbHVlc2V0ICJTdWJzdGFuY2UgQWJ1c2UiOiAnaHR0cDovL2ZoaXIub3JnL2d1aWRlcy9jZGMvb3Bpb2lkLWNkcy9WYWx1ZVNldC9zdWJzdGFuY2UtYWJ1c2UnCgovLyBUT0RPOiBUdXJuIHRoaXMgaW50byBhIHZhbHVlc2V0CmNvZGUgIlJlZmVycmFsIHRvIEhvc3BpY2UiOiAnMzA2MjA1MDA5JyBmcm9tICJTTk9NRUQiCi8vIFRPRE86IFR1cm4gdGhpcyBpbnRvIGEgdmFsdWVzZXQKY29kZSAiT3V0cGF0aWVudCI6ICdvdXRwYXRpZW50JyBmcm9tICJNZWRpY2F0aW9uIFJlcXVlc3QgQ2F0ZWdvcnkgQ29kZXMiCgovLyBUT0RPOiBDYXB0dXJlIHByb2Nlc3MgZGVjaXNpb25zIGZvciBsb25nLXRlcm0gb3Bpb2lkIHVzZQpkZWZpbmUgSXNGb3JDaHJvbmljUGFpbjogdHJ1ZQoKZGVmaW5lICJBY3RpdmUgQW1idWxhdG9yeSBPcGlvaWQgUngiOgogIFtNZWRpY2F0aW9uUmVxdWVzdDogIkFtYnVsYXRvcnkgQWJ1c2UgUG90ZW50aWFsIE9waW9pZHMiXSBSeAogICAgd2hlcmUgUnguc3RhdHVzID0gJ2FjdGl2ZScKICAgICAgYW5kIFRvQ29kZXMoUnguY2F0ZWdvcnkuY29kaW5nKSBjb250YWlucyAiT3V0cGF0aWVudCIKCmRlZmluZSAiQWN0aXZlIEFtYnVsYXRvcnkgQmVuem9kaWF6ZXBpbmUgUngiOgogIFtNZWRpY2F0aW9uUmVxdWVzdDogIkJlbnpvZGlhemVwaW5lcyJdIFJ4CiAgICB3aGVyZSBSeC5zdGF0dXMgPSAnYWN0aXZlJwogICAgICBhbmQgVG9Db2RlcyhSeC5jYXRlZ29yeS5jb2RpbmcpIGNvbnRhaW5zICJPdXRwYXRpZW50IgoKZGVmaW5lICJBY3RpdmUgQW1idWxhdG9yeSBOYWxveG9uZSBSeCI6CiAgW01lZGljYXRpb25SZXF1ZXN0OiAiTmFsb3hvbmUiXSBSeAogICAgd2hlcmUgUnguc3RhdHVzID0gJ2FjdGl2ZScKICAgICAgYW5kIFRvQ29kZXMoUnguY2F0ZWdvcnkuY29kaW5nKSBjb250YWlucyAiT3V0cGF0aWVudCIKCmRlZmluZSAiQW1idWxhdG9yeSBPcGlvaWQgUngiOgogIFtNZWRpY2F0aW9uUmVxdWVzdDogIkFtYnVsYXRvcnkgQWJ1c2UgUG90ZW50aWFsIE9waW9pZHMiXSBSeAogICAgICB3aGVyZSBUb0NvZGVzKFJ4LmNhdGVnb3J5LmNvZGluZykgY29udGFpbnMgIk91dHBhdGllbnQiCgpkZWZpbmUgIkVuZCBvZiBMaWZlIEFzc2Vzc21lbnQiOgogIC8vIDEuIENvbmRpdGlvbnMgaW5kaWNhdGluZyBlbmQgb2YgbGlmZSBvciB3aXRoIGxpbWl0ZWQgbGlmZSBleHBlY3RhbmN5CiAgZXhpc3RzICgKICAgICgKICAgICAgW0NvbmRpdGlvbjogIkVuZCBvZiBMaWZlIENvbmRpdGlvbnMiXSBDCiAgICAgICAgd2hlcmUgQy5jbGluaWNhbFN0YXR1cyBpbiAiQWN0aXZlIENvbmRpdGlvbiIKICAgICkKICAgIHVuaW9uCiAgICAoCiAgICAgIFtDb25kaXRpb246IGNvZGUgaW4gIkxpbWl0ZWQgTGlmZSBFeHBlY3RhbmN5IENvbmRpdGlvbnMiXSBDCiAgICAgICAgd2hlcmUgQy5jbGluaWNhbFN0YXR1cyBpbiAiQWN0aXZlIENvbmRpdGlvbiIKICAgICkKICApCiAgLy8gMi4gQWRtaXR0ZWQvcmVmZXJyZWQvZGlzY2hhcmdlZCB0byBob3NwaWNlIGNhcmUKICBvciBleGlzdHMgKAogICAgKAogICAgICBbU2VydmljZVJlcXVlc3Q6IGNvZGUgaW4gIlJlZmVycmFsIHRvIEhvc3BpY2UiXSBSUgogICAgICAgIHdoZXJlIFJSLnN0YXR1cyBpbiB7ICdhY3RpdmUnLCAnY29tcGxldGVkJyB9CiAgICApCiAgICB1bmlvbgogICAgKAogICAgICBbU2VydmljZVJlcXVlc3Q6IGNvZGUgaW4gIkhvc3BpY2UgUHJvY2VkdXJlIENvZGVzIl0gUAogICAgICAgIHdoZXJlIFAuc3RhdHVzIGluIHsgJ2luLXByb2dyZXNzJywgJ2NvbXBsZXRlZCcgfQogICAgKQogICAgdW5pb24KICAgICgKICAgICAgW1NlcnZpY2VSZXF1ZXN0OiBjb2RlIGluICJIb3NwaWNlIFByb2NlZHVyZSBDb2RlcyJdIEUKICAgICAgICB3aGVyZSBFLnN0YXR1cyBpbiB7ICdwbGFubmVkJywgJ2Fycml2ZWQnLCAnaW4tcHJvZ3Jlc3MnLCAnZmluaXNoZWQnLCAnb25sZWF2ZScgfQogICAgKQogICAgdW5pb24KICAgICgKICAgICAgW09ic2VydmF0aW9uOiBjb2RlIGluICJIb3NwaWNlIEZpbmRpbmcgQ29kZXMiXSBPCiAgICAgICAgd2hlcmUgbm90IChPLnN0YXR1cyBpbiB7ICd1bmtub3duJywgJ2VudGVyZWQtaW4tZXJyb3InLCAnY2FuY2VsbGVkJyB9KQogICAgKQogICAgdW5pb24KICAgICgKICAgICAgW0VuY291bnRlcl0gRQogICAgICAgIHdoZXJlCiAgICAgICAgICAoCiAgICAgICAgICAgIGlmIEUuaG9zcGl0YWxpemF0aW9uLmRpc2NoYXJnZURpc3Bvc2l0aW9uLmNvZGluZyBpcyBudWxsCiAgICAgICAgICAgICAgICBvciBub3QgZXhpc3RzIChFLmhvc3BpdGFsaXphdGlvbi5kaXNjaGFyZ2VEaXNwb3NpdGlvbi5jb2RpbmcpCiAgICAgICAgICAgICAgdGhlbiBmYWxzZQogICAgICAgICAgICBlbHNlIEUuaG9zcGl0YWxpemF0aW9uLmRpc2NoYXJnZURpc3Bvc2l0aW9uIGluICJIb3NwaWNlIERpc3Bvc2l0aW9uIgogICAgICAgICAgKQogICAgICAgICAgYW5kIEUuc3RhdHVzIGluIHsgJ3BsYW5uZWQnLCAnYXJyaXZlZCcsICdpbi1wcm9ncmVzcycsICdmaW5pc2hlZCcsICdvbmxlYXZlJyB9CiAgICApCiAgKQoKZGVmaW5lIGZ1bmN0aW9uIFByZXNjcmlwdGlvbnMoT3JkZXJzIExpc3Q8TWVkaWNhdGlvblJlcXVlc3Q+KToKICBPcmRlcnMgTwogICAgbGV0CiAgICAgIC8vIE5PVEU6IEFzc3VtaW5nIG1lZGljYXRpb24gaXMgc3BlY2lmaWVkIGFzIGEgQ29kZWFibGVDb25jZXB0IHdpdGggYSBzaW5nbGUgUnhOb3JtIGNvZGUKICAgICAgcnhOb3JtQ29kZTogVG9Db2RlKE8ubWVkaWNhdGlvbi5jb2RpbmdbMF0pLAogICAgICBtZWRpY2F0aW9uTmFtZTogT01US0xvZ2ljLkdldE1lZGljYXRpb25OYW1lKHJ4Tm9ybUNvZGUpLAogICAgICAvLyBOT1RFOiBBc3N1bWluZyBhIHNpbmdsZSBkb3NhZ2UgaW5zdHJ1Y3Rpb24gZWxlbWVudAogICAgICBkb3NhZ2VJbnN0cnVjdGlvbjogTy5kb3NhZ2VJbnN0cnVjdGlvblswXSwKICAgICAgcmVwZWF0OiBkb3NhZ2VJbnN0cnVjdGlvbi50aW1pbmcucmVwZWF0LAogICAgICBmcmVxdWVuY3k6IENvYWxlc2NlKHJlcGVhdC5mcmVxdWVuY3lNYXgudmFsdWUsIHJlcGVhdC5mcmVxdWVuY3kudmFsdWUpLAogICAgICBwZXJpb2Q6IFN5c3RlbS5RdWFudGl0eSB7IHZhbHVlOiByZXBlYXQucGVyaW9kLnZhbHVlLCB1bml0OiByZXBlYXQucGVyaW9kVW5pdC52YWx1ZSB9LAogICAgICBkb3NlRGVzY3JpcHRpb246CiAgICAgICAgQ29hbGVzY2UoCiAgICAgICAgICAvLyBUaGVyZSBzaG91bGQgYmUgYSBjb252ZXJzaW9uIGZyb20gRkhJUi5TaW1wbGVRdWFudGl0eSB0byBTeXN0ZW0uUXVhbnRpdHkKICAgICAgICAgIGlmIGRvc2FnZUluc3RydWN0aW9uLmRvc2VBbmRSYXRlWzBdLmRvc2UgaXMgRkhJUi5SYW5nZQogICAgICAgICAgICB0aGVuIFRvU3RyaW5nKFRvUXVhbnRpdHkoZG9zYWdlSW5zdHJ1Y3Rpb24uZG9zZUFuZFJhdGVbMF0uZG9zZS5sb3cpKQogICAgICAgICAgICAgICAgICAgICAgICAgICsgJy0nICsgVG9TdHJpbmcoVG9RdWFudGl0eShkb3NhZ2VJbnN0cnVjdGlvbi5kb3NlQW5kUmF0ZVswXS5kb3NlLmhpZ2gpKQogICAgICAgICAgICAgICAgICAgICAgICAgICsgZG9zYWdlSW5zdHJ1Y3Rpb24uZG9zZUFuZFJhdGVbMF0uZG9zZS5oaWdoLnVuaXQudmFsdWUKICAgICAgICAgICAgZWxzZSBUb1N0cmluZyhUb1F1YW50aXR5KGRvc2FnZUluc3RydWN0aW9uLmRvc2VBbmRSYXRlWzBdLmRvc2UpKSwKICAgICAgICAgICAgJycKICAgICAgICApLAogICAgICBmcmVxdWVuY3lEZXNjcmlwdGlvbjoKICAgICAgICBUb1N0cmluZyhkb3NhZ2VJbnN0cnVjdGlvbi50aW1pbmcucmVwZWF0LmZyZXF1ZW5jeS52YWx1ZSkgKwogICAgICAgICAgQ29hbGVzY2UoCiAgICAgICAgICAgICctJyArIFRvU3RyaW5nKGRvc2FnZUluc3RydWN0aW9uLnRpbWluZy5yZXBlYXQuZnJlcXVlbmN5TWF4LnZhbHVlKSwKICAgICAgICAgICAgJycKICAgICAgICAgICkKICAgIHJldHVybiB7CiAgICAgIHJ4Tm9ybUNvZGU6IHJ4Tm9ybUNvZGUsCiAgICAgIGlzRHJhZnQ6IE8uc3RhdHVzLnZhbHVlID0gJ2RyYWZ0JywKICAgICAgLy8gTk9URTogQXNzdW1pbmcgYXNOZWVkZWQgaXMgZXhwcmVzc2VkIGFzIGEgYm9vbGVhbgogICAgICBpc1BSTjogZG9zYWdlSW5zdHJ1Y3Rpb24uYXNOZWVkZWQudmFsdWUsCiAgICAgIHByZXNjcmlwdGlvbjoKICAgICAgICBpZiBkb3NhZ2VJbnN0cnVjdGlvbi50ZXh0IGlzIG5vdCBudWxsIHRoZW4KICAgICAgICAgIG1lZGljYXRpb25OYW1lICsgJyAnICsgZG9zYWdlSW5zdHJ1Y3Rpb24udGV4dC52YWx1ZQogICAgICAgIGVsc2UKICAgICAgICAgIC8vIFRPRE86IFNob3VsZG4ndCBuZWVkIHRoZSAudmFsdWUgaGVyZSBvbiBhc05lZWRlZEJvb2xlYW4KICAgICAgICAgIG1lZGljYXRpb25OYW1lICsgJyAnICsgZG9zZURlc2NyaXB0aW9uICsgJyBxJyArIGZyZXF1ZW5jeURlc2NyaXB0aW9uICsgKGlmIGRvc2FnZUluc3RydWN0aW9uLmFzTmVlZGVkLnZhbHVlIHRoZW4gJyBQUk4nIGVsc2UgJycpLAogICAgICAvLyBUT0RPOiBTaG91bGRuJ3QgbmVlZCB0aGUgVG9RdWFudGl0eSBoZXJlLi4uCiAgICAgIGRvc2U6IGlmIGRvc2FnZUluc3RydWN0aW9uLmRvc2VBbmRSYXRlWzBdLmRvc2UgaXMgRkhJUi5SYW5nZQogICAgICAgICAgICB0aGVuIFRvUXVhbnRpdHkoZG9zYWdlSW5zdHJ1Y3Rpb24uZG9zZUFuZFJhdGVbMF0uZG9zZS5oaWdoKQogICAgICAgICAgICBlbHNlIFRvUXVhbnRpdHkoZG9zYWdlSW5zdHJ1Y3Rpb24uZG9zZUFuZFJhdGVbMF0uZG9zZSksCiAgICAgIGRvc2VzUGVyRGF5OiBDb2FsZXNjZShPTVRLTG9naWMuVG9EYWlseShmcmVxdWVuY3ksIHBlcmlvZCksIDEuMCkKICAgIH0KCmRlZmluZSBmdW5jdGlvbiBNTUUocHJlc2NyaXB0aW9ucyBMaXN0PE1lZGljYXRpb25SZXF1ZXN0Pik6CiAgKFByZXNjcmlwdGlvbnMocHJlc2NyaXB0aW9ucykpIFAKICAgIGxldCBtbWU6IFNpbmdsZXRvbkZyb20oT01US0xvZ2ljLkNhbGN1bGF0ZU1NRXMoeyB7IHJ4Tm9ybUNvZGU6IFAucnhOb3JtQ29kZSwgZG9zZVF1YW50aXR5OiBQLmRvc2UsIGRvc2VzUGVyRGF5OiBQLmRvc2VzUGVyRGF5IH0gfSkpCiAgICByZXR1cm4gewogICAgICByeE5vcm1Db2RlOiBQLnJ4Tm9ybUNvZGUsCiAgICAgIGlzRHJhZnQ6IFAuaXNEcmFmdCwKICAgICAgaXNQUk46IFAuaXNQUk4sCiAgICAgIHByZXNjcmlwdGlvbjogUC5wcmVzY3JpcHRpb24sCiAgICAgIGRhaWx5RG9zZTogbW1lLmRhaWx5RG9zZURlc2NyaXB0aW9uLAogICAgICBjb252ZXJzaW9uRmFjdG9yOiBtbWUuY29udmVyc2lvbkZhY3RvciwKICAgICAgbW1lOiBtbWUubW1lCiAgICB9CgpkZWZpbmUgZnVuY3Rpb24gVG90YWxNTUUocHJlc2NyaXB0aW9ucyBMaXN0PE1lZGljYXRpb25SZXF1ZXN0Pik6CiAgU3lzdGVtLlF1YW50aXR5IHsKICAgIHZhbHVlOiBTdW0oKE1NRShwcmVzY3JpcHRpb25zKSkgTSByZXR1cm4gTS5tbWUudmFsdWUpLAogICAgdW5pdDogJ21nL2QnCiAgfQoKZGVmaW5lIGZ1bmN0aW9uIFByb2JhYmxlRGF5c0luUmFuZ2UoT3JkZXJzIExpc3Q8TWVkaWNhdGlvblJlcXVlc3Q+LCBkYXlzUGFzdCBJbnRlZ2VyLCBudW1EYXlzSW5EYXlzUGFzdCBJbnRlZ2VyKToKICBPcmRlcnMgb3JkZXJzCiAgICBsZXQKICAgICAgZnJlcXVlbmN5OiBvcmRlcnMuZG9zYWdlSW5zdHJ1Y3Rpb25bMF0udGltaW5nLnJlcGVhdC5mcmVxdWVuY3kudmFsdWUsCiAgICAgIHBlcmlvZDogb3JkZXJzLmRvc2FnZUluc3RydWN0aW9uWzBdLnRpbWluZy5yZXBlYXQucGVyaW9kLnZhbHVlLAogICAgICBwZXJpb2REYXlzOiBHZXRQZXJpb2REYXlzKG9yZGVycy5kb3NhZ2VJbnN0cnVjdGlvblswXS50aW1pbmcucmVwZWF0LnBlcmlvZFVuaXQudmFsdWUpLAogICAgICBkb3Nlc1BlckRheToKICAgICAgICBpZiAoZnJlcXVlbmN5IC8gKHBlcmlvZCAqIHBlcmlvZERheXMpKSA+PSAxLjAKICAgICAgICB0aGVuIDEuMAogICAgICAgIGVsc2UgZnJlcXVlbmN5IC8gKHBlcmlvZCAqIHBlcmlvZERheXMpLAogICAgICByZXBlYXQ6IG9yZGVycy5kaXNwZW5zZVJlcXVlc3QubnVtYmVyT2ZSZXBlYXRzQWxsb3dlZC52YWx1ZSwKICAgICAgc3VwcGx5RHVyYXRpb246IEdldER1cmF0aW9uSW5EYXlzKG9yZGVycy5kaXNwZW5zZVJlcXVlc3QuZXhwZWN0ZWRTdXBwbHlEdXJhdGlvbiksCiAgICAgIHZhbGlkaXR5UGVyaW9kOiBkYXlzIGJldHdlZW4gb3JkZXJzLmRpc3BlbnNlUmVxdWVzdC52YWxpZGl0eVBlcmlvZC4ic3RhcnQiLnZhbHVlIGFuZCBUb2RheSgpLAogICAgICBlbmREaWZmZXJlbmNlOgogICAgICAgIGlmIG9yZGVycy5kaXNwZW5zZVJlcXVlc3QudmFsaWRpdHlQZXJpb2QuImVuZCIudmFsdWUgPCBUb2RheSgpCiAgICAgICAgdGhlbiBkYXlzIGJldHdlZW4gb3JkZXJzLmRpc3BlbnNlUmVxdWVzdC52YWxpZGl0eVBlcmlvZC4iZW5kIi52YWx1ZSBhbmQgVG9kYXkoKQogICAgICAgIGVsc2UgMAogICAgcmV0dXJuCiAgICAgIGlmIChyZXBlYXQgKiBzdXBwbHlEdXJhdGlvbikgPCBudW1EYXlzSW5EYXlzUGFzdCB0aGVuIGZhbHNlCiAgICAgIGVsc2UKICAgICAgICAoZG9zZXNQZXJEYXkgKiAoKHJlcGVhdCAqIHN1cHBseUR1cmF0aW9uKSAvIHZhbGlkaXR5UGVyaW9kKSAqIChkYXlzUGFzdCAtIGVuZERpZmZlcmVuY2UpKSA+PSBudW1EYXlzSW5EYXlzUGFzdAoKZGVmaW5lIGZ1bmN0aW9uIEdldFBlcmlvZERheXModmFsdWUgU3lzdGVtLlN0cmluZyk6CiAgY2FzZQogICAgd2hlbiB2YWx1ZSA9ICdhJyB0aGVuIDM2NS4wCiAgICB3aGVuIHZhbHVlID0gJ21vJyB0aGVuIDMwLjAKICAgIHdoZW4gdmFsdWUgPSAnaCcgdGhlbiAxLjAvMjQuMAogICAgd2hlbiB2YWx1ZSA9ICdtaW4nIHRoZW4gMS4wLzI0LjAqNjAuMAogICAgd2hlbiB2YWx1ZSA9ICdzJyB0aGVuIDEuMC8yNC4wKjYwLjAqNjAuMAogICAgd2hlbiB2YWx1ZSA9ICdtcycgdGhlbiAxLjAvMjQuMCo2MC4wKjYwLjAqMTAwMC4wCiAgICBlbHNlIDEuMAogIGVuZAoKICBkZWZpbmUgZnVuY3Rpb24gR2V0RHVyYXRpb25JbkRheXModmFsdWUgRkhJUi5EdXJhdGlvbik6CiAgICBjYXNlCiAgICAgIHdoZW4gU3RhcnRzV2l0aCh2YWx1ZS51bml0LnZhbHVlLCAnYScpIHRoZW4gdmFsdWUudmFsdWUudmFsdWUgKiAzNjUuMAogICAgICB3aGVuIFN0YXJ0c1dpdGgodmFsdWUudW5pdC52YWx1ZSwgJ21vJykgdGhlbiB2YWx1ZS52YWx1ZS52YWx1ZSAqIDMwLjAKICAgICAgd2hlbiBTdGFydHNXaXRoKHZhbHVlLnVuaXQudmFsdWUsICd3aycpIHRoZW4gdmFsdWUudmFsdWUudmFsdWUgKiA3LjAKICAgICAgd2hlbiBTdGFydHNXaXRoKHZhbHVlLnVuaXQudmFsdWUsICdkJykgdGhlbiB2YWx1ZS52YWx1ZS52YWx1ZQogICAgICB3aGVuIFN0YXJ0c1dpdGgodmFsdWUudW5pdC52YWx1ZSwgJ2gnKSB0aGVuIHZhbHVlLnZhbHVlLnZhbHVlIC8gMjQuMAogICAgICB3aGVuIFN0YXJ0c1dpdGgodmFsdWUudW5pdC52YWx1ZSwgJ21pbicpIHRoZW4gdmFsdWUudmFsdWUudmFsdWUgLyA2MC4wIC8gMjQuMAogICAgICB3aGVuIFN0YXJ0c1dpdGgodmFsdWUudW5pdC52YWx1ZSwgJ3MnKSB0aGVuIHZhbHVlLnZhbHVlLnZhbHVlIC8gNjAuMCAvIDYwLjAgLyAyNC4wCiAgICAgIHdoZW4gU3RhcnRzV2l0aCh2YWx1ZS51bml0LnZhbHVlLCAnbXMnKSB0aGVuIHZhbHVlLnZhbHVlLnZhbHVlIC8gNjAuMCAvIDYwLjAgLyAyNC4wIC8gMTAwMC4wCiAgICAgIGVsc2UgTWVzc2FnZSgxMDAwLCB0cnVlLCAnVW5kZWZpbmVkJywgJ0Vycm9yJywgJ1Vuc3VwcG9ydGVkIGR1cmF0aW9uIHVuaXQnKQogICAgZW5kCgpkZWZpbmUgZnVuY3Rpb24gR2V0SW5ncmVkaWVudChyeE5vcm1Db2RlIENvZGUpOgogIE9NVEtMb2dpYy5HZXRJbmdyZWRpZW50cyhyeE5vcm1Db2RlKS5pbmdyZWRpZW50TmFtZQoKZGVmaW5lIGZ1bmN0aW9uIEdldEluZ3JlZGllbnRzKHJ4Tm9ybUNvZGVzIExpc3Q8Q29kZT4pOgogIHJ4Tm9ybUNvZGVzIHJuYyByZXR1cm4gR2V0SW5ncmVkaWVudChybmMpCgpkZWZpbmUgZnVuY3Rpb24gR2V0TWVkaWNhdGlvbk5hbWVzKG1lZGljYXRpb25zIExpc3Q8TWVkaWNhdGlvblJlcXVlc3Q+KToKICBtZWRpY2F0aW9ucyBNCiAgICByZXR1cm4gT01US0xvZ2ljLkdldEluZ3JlZGllbnRzKFRvUnhOb3JtQ29kZShNLm1lZGljYXRpb24uY29kaW5nKSkucnhOb3JtQ29kZS5kaXNwbGF5CgovKgoqICBDb252ZXJzaW9uIEZ1bmN0aW9ucwoqLwoKZGVmaW5lIGZ1bmN0aW9uIFRvQ29kZShjb2RpbmcgRkhJUi5Db2RpbmcpOgogIFN5c3RlbS5Db2RlIHsKICAgIGNvZGU6IGNvZGluZy5jb2RlLnZhbHVlLAogICAgc3lzdGVtOiBjb2Rpbmcuc3lzdGVtLnZhbHVlLAogICAgdmVyc2lvbjogY29kaW5nLnZlcnNpb24udmFsdWUsCiAgICBkaXNwbGF5OiBjb2RpbmcuZGlzcGxheS52YWx1ZQogIH0KCmRlZmluZSBmdW5jdGlvbiBUb0NvZGVzKGNvZGluZyBMaXN0PEZISVIuQ29kaW5nPik6CiAgY29kaW5nIGMgcmV0dXJuIFRvQ29kZShjKQoKZGVmaW5lIGZ1bmN0aW9uIFRvUnhOb3JtQ29kZShjb2RpbmcgTGlzdDxGSElSLkNvZGluZz4pOgogIHNpbmdsZXRvbiBmcm9tICgKICAgIGNvZGluZyBDIHdoZXJlIEMuc3lzdGVtID0gJ2h0dHA6Ly93d3cubmxtLm5paC5nb3YvcmVzZWFyY2gvdW1scy9yeG5vcm0nCiAgKQoKZGVmaW5lIGZ1bmN0aW9uIFRvUXVhbnRpdHkocXVhbnRpdHkgRkhJUi5RdWFudGl0eSk6CiAgU3lzdGVtLlF1YW50aXR5IHsgdmFsdWU6IHF1YW50aXR5LnZhbHVlLnZhbHVlLCB1bml0OiBxdWFudGl0eS51bml0LnZhbHVlIH0K"/>
  </content>
  <content>
    <contentType value="application/elm+xml"/>
  </content>
</Library>