Objective

Identify actionable treatment variation within patients with low back pain.


Data Preparation

For this analysis, data was pulled from the following tables in the data warehouse.

Patients were members of BCBS from 2016 who had episodes of low back pain without complications. Ages were between 18-65. ETG outliers were excluded. Members with missing episode numbers were dropped. There were 30,000 episodes included in this analysis out of an original 36,300.

#read in the SAS file 
df = read_sas("C:/Users/scasti01/Desktop/Cost and Use/R/Claims/episodes.sas7bdat") %>%
  #convert data types to proper format
  mutate(MEM_NUM = as.factor(MEM_NUM),
         EPI_TYPE = as.factor(EPI_TYPE),
         etg = as.character(ETG_FULL),
         ETG_OUTLIER = as.factor(ETG_OUTLIER),
         COMPLICATION = as.factor(COMPLICATION),
         COMORBIDITY = as.factor(COMORBIDITY),
         SEVERITY = as.factor(SEVERITY),
         TOS_CAT1_DESC = as.factor(TOS_CAT1_DESC),
         TOS_CAT2_DESC = as.factor(TOS_CAT2_DESC),
         TOS_CAT3_DESC = as.factor(TOS_CAT3_DESC)
         ) %>%
  #exclude members with complications
  #eclude episodes with episodes number 0 (missing values)
  filter(COMPLICATION == 0, EPISODE_NUM != 0, !is.na(EPISODE_NUM) ) %>%
  #find the time difference between each appointment and the start of th episode
  mutate(lag = difftime( INCURRED_DT_DAY, EPI_START_DT, units = "days"),
         surgery_indicator =  ifelse(test = (str_detect(CPT_PROC_DESC, "SURG"))|| any(TOS_CAT2_DESC == "SURGICAL"),
                                                   yes = T, 
                                                   no = F)) %>%
  #count the number of episodes for each patient
  group_by(MEM_NUM) %>%
  mutate( nth_episode = length(unique(EPISODE_NUM))) %>%
  ungroup() %>%
  #create clinical T/F variables for clinical categories
  group_by(EPISODE_NUM) %>%
  summarise( 
            #does the episode have surgery?  these are the ETGs that have surgery at some point in the episode    
             surgery = as.factor(any(ETG_FULL  %in% c(712208001, 712208011, 712208111, 712208101))),
             
             #When does the surgery occur?
             surgery_lag = ifelse( test = any(surgery_indicator),
                                   yes = min(difftime( INCURRED_DT_DAY, EPI_START_DT, units = "days")),
                                   no = 1000),
             #How many physical therapy claims are on record?
             PT_count = sum(ifelse( test = (TOS_CAT2_DESC == "PHYSICAL THERAPY"),
                                    yes = T, 
                                    no = F)),
             
             #did the patient go to more than 7 PT appointments?
             PT = as.factor(any(TOS_CAT2_DESC == "PHYSICAL THERAPY" & PT_count >= 7)),
             #Was there an MRI at any point in the episode?
             MRI_any = as.factor(any(TOS_CAT3_DESC == "MRI" )),
             #Was there an MRI in the first 6 weeks of the episode, and before surgery if there was a surgical outcome?
             MRI_6_weeks = as.factor(any(TOS_CAT3_DESC == "MRI" & (lag <= 6*7) & (lag <= surgery_lag))),
             #was there an MRI in the first 8 weeks and before surgery?
             MRI_8_weeks = as.factor(any(TOS_CAT3_DESC == "MRI" & (lag <= 8*7) & (lag <= surgery_lag))),
             #Did the patient have a spinal manipulation (chiropractic treatment) before surgery?
             spinal_manipulation = as.factor(any(TOS_CAT3_DESC == "SPINAL MANIPULATION" & (lag <= surgery_lag))),
             #did the patient have a diagnostic injection prior to surgery?
             diag_injection = as.factor(any(TOS_CAT3_DESC == "DIAGNOSTIC INJECTION" & (lag <= surgery_lag))),
             #where there xrays taken before surgery?  In the first 6, 8 weeks, and prior to surgery?
             xray_any = as.factor(any(str_detect( CPT_PROC_DESC, "X-RAY") & (lag <= surgery_lag))),
             xray_6_weeks = as.factor(any(str_detect( CPT_PROC_DESC, "X-RAY") & (lag <= 6*7) & (lag <= surgery_lag))),
             xray_8_weeks = as.factor(any(str_detect( CPT_PROC_DESC, "X-RAY") & (lag <= 8*7) & (lag <= surgery_lag))),
             #what was the ETG severity?
             severity1 = as.factor(any(SEVERITY == 1)),
             severity2 = as.factor(any(SEVERITY == 2)),
             severity3 = as.factor(any(SEVERITY == 3)),
             #how old was the member?
             member_age = as.numeric(member_age[1]),
             nth_episode = as.factor(nth_episode[1]),
             MEM_NUM = MEM_NUM[1],
             comorbidity = as.factor(COMORBIDITY[1])
             
  )#end summarise

surgery date test

Do the tos_cat dates agree with the surgery dates?

testdata = 
read_sas("C:/Users/scasti01/Desktop/Cost and Use/R/Claims/episodes.sas7bdat") %>%
  #convert data types to proper format
  mutate(MEM_NUM = as.factor(MEM_NUM),
         EPI_TYPE = as.factor(EPI_TYPE),
         etg = as.character(ETG_FULL),
         ETG_OUTLIER = as.factor(ETG_OUTLIER),
         COMPLICATION = as.factor(COMPLICATION),
         COMORBIDITY = as.factor(COMORBIDITY),
         SEVERITY = as.factor(SEVERITY),
         TOS_CAT1_DESC = as.factor(TOS_CAT1_DESC),
         TOS_CAT2_DESC = as.factor(TOS_CAT2_DESC),
         TOS_CAT3_DESC = as.factor(TOS_CAT3_DESC)
         ) %>%
  #exclude members with complications
  #eclude episodes with episodes number 0 (missing values)
  filter(COMPLICATION == 0, EPISODE_NUM != 0, !is.na(EPISODE_NUM) ) %>%
  #find the time difference between each appointment and the start of th episode
  mutate(lag = difftime( INCURRED_DT_DAY, EPI_START_DT, units = "weeks"))



surgery_test = testdata %>%
  #count the number of episodes for each patient
  group_by(MEM_NUM) %>%
  mutate( nth_episode = length(unique(EPISODE_NUM))) %>%
  ungroup() %>%
  group_by(EPISODE_NUM) %>%
  summarise( 
    surgery_etg = as.factor(any(ETG_FULL  %in% c(712208001, 712208011, 712208111, 712208101))),
    surgery_toscat = as.factor(any((str_detect(CPT_PROC_DESC, "SURG"))|| any(TOS_CAT2_DESC == "SURGICAL"))),
    hit = ifelse(surgery_etg == surgery_toscat, 
                 yes = T, no = F))

confusionMatrix(surgery_test$surgery_etg, surgery_test$surgery_toscat)

testdata %>% 
  filter(ETG_FULL %in% c(712208001, 712208011, 712208111, 712208101)) %>%
  select(HCPCS_PROC_DESC) %>%
  unique()

Validation Set Split

A validation set was created to prevent overfitting the model to the data. A randomly-sampled 20% validation, 80% non-validation split was used. This 80% non-validation set was further split during a 10-fold cross validation which was repeated 3 times.

fraction_Validation <- 0.20
fraction_NonValidation <- 0.80
# Compute sample sizes.
sample_Size_Validation   <- floor(fraction_Validation   * nrow(df))
sample_Size_NonValidation <- floor(fraction_NonValidation * nrow(df))
# Create the randomly-sampled indices for the dataframe. Use setdiff() to
# avoid overlapping subsets of indices.
indices_Validation    <- sort(sample(seq_len(nrow(df)), size = sample_Size_Validation))
indices_Not_Validation <- setdiff(seq_len(nrow(df)), indices_Validation)
# Finally, output the three dataframes for training, validation and test.
dfValidation <- df[indices_Validation, ]
dfNotValidation <- df[indices_Not_Validation,]

Adjusting for Class Inbalance

The surgical rate is only ~6%, which leads to a class inbalance in surgical and non-surgical outcomes. To account for this, the minority population can be oversampled and the majority (surgical) population undersampled. A technique known as Sythetic Minority Oversampling was used from the library SMOTE. To test performance, two versions were used: the first with both over-sampling of the surgical class and undersampling of the non-surgical class, and a second with only over-sampling of the surgical class. For brevity this report only contains the first version, which performed better based on sensitivity and accuracy ratings.

pre_processed_data = dplyr::as_data_frame(dfNotValidation) %>% 
  dplyr::select(-MRI_6_weeks, 
                -xray_6_weeks, 
                -MRI_any, 
                -xray_any, 
                - MEM_NUM,
                - EPISODE_NUM,
                - surgery_lag) %>%
  as.data.frame()

Prior to the oversampling there is about a 95%-5% split between those episodes with surgery and without.

t1 = Sys.time()
rf.data.smote = SMOTE(surgery ~., 
                      data <- pre_processed_data, 
                      perc.over = 1500,
                      perc.under = 100,
                      k = 9)
t2 = Sys.time()
run_time = t1 - t2
run_time
Time difference of -1.45 secs
dim(rf.data.smote)
[1] 23436    13
summary(rf.data.smote$surgery)
 TRUE FALSE 
12096 11340 

After the bootstrap there are roughly an equal number of cases with and without surgery.

Fitting a Model

Because of the number of binary predictors and need to compensate for overfitting, a random forest was used with an automated tuning function from the library caret.

t1 = Sys.time()
# Create model with default paramters
control <- trainControl(method = "repeatedcv", number=10, repeats=3)
seed <- 7
metric <- "Kappa"
set.seed(seed)
mtry <- sqrt(ncol(rf.data.smote))
tunegrid <- expand.grid(.mtry=mtry)
rf_smote <- train(surgery ~., 
                    data= rf.data.smote, 
                    method="rf", 
                    metric=metric, 
                    tuneGrid=tunegrid, 
                    trControl=control)
t2 = Sys.time()
rf_runtime = t2 - t1
rf_runtime
Time difference of 5.044917 mins

Testing the Model

Testing was conducted both against the bootstrapped data and the un-inflated validation set, which contained the raw observations. The cutoff value for the random forest was optimized based on the sensitivity and accuracy of the model.

Test against bootstrapped data

predictor = rf_smote$finalModel$votes[,1]
response = rf.data.smote$surgery 
roc(response, predictor, percent=TRUE,
            # arguments for auc
            plot=TRUE, 
            auc.polygon=TRUE, 
            max.auc.polygon=TRUE, 
            grid=TRUE,
            print.auc=TRUE, 
            show.thres=TRUE)

Call:
roc.default(response = response, predictor = predictor, percent = TRUE,     plot = TRUE, auc.polygon = TRUE, max.auc.polygon = TRUE,     grid = TRUE, print.auc = TRUE, show.thres = TRUE)

Data: predictor in 12096 controls (response TRUE) > 11340 cases (response FALSE).
Area under the curve: 94.19%

The Area Under the Curve (AUC) of 0.942 indicates that the model fits the bootstrapped data well. The interpretation is the probability that a randomly selected subject who ends up with surgery has a test result indicating greater suspicion than that of a randomly chosen subject who does not end up having surgery. (Hanley & McNeil, 1982)

dev.new()
pred = prediction( predictor, response)
perf = performance(pred, measure="tpr",x.measure="cutoff")
plot(perf, 
     main = "True Positive Rate vs Cutoff",
     type = "s")

The true positive rate (sensitivity) is highest when the cutoff is set to close to zero. Because the goal is to correctly identify those patients who have surgery, the cutoff was set close to zero in order to optimize the sensitivity of the model, even at the cost of reduced accuracy. This does not effect the variable importance ranking, however.

dev.new()
pred = prediction( predictor, response)
perf = performance(pred, measure="fpr",x.measure="cutoff")
plot(perf, 
     main = "False Positive Rate vs Cutoff",
     type = "s")

Test Against Validation Set

rf_smote$finalModel$forest$cutoff = c(0.0001, 0.9999)
prediction = predict( object = rf_smote,
                      newdata = dfValidation)
confusionMatrix(prediction, dfValidation$surgery)
Confusion Matrix and Statistics

          Reference
Prediction TRUE FALSE
     TRUE   131  2521
     FALSE   60  3239
                                          
               Accuracy : 0.5663          
                 95% CI : (0.5536, 0.5789)
    No Information Rate : 0.9679          
    P-Value [Acc > NIR] : 1               
                                          
                  Kappa : 0.0343          
 Mcnemar's Test P-Value : <2e-16          
                                          
            Sensitivity : 0.68586         
            Specificity : 0.56233         
         Pos Pred Value : 0.04940         
         Neg Pred Value : 0.98181         
             Prevalence : 0.03210         
         Detection Rate : 0.02201         
   Detection Prevalence : 0.44564         
      Balanced Accuracy : 0.62410         
                                          
       'Positive' Class : TRUE            
                                          

Interpretation

Variable Importance

Logistic Regression of Top Predictors

A logistic regression allows for inferrence on whether or not each predictor increases or decreases the probability of having surgery. A variable with a positive coefficient indicates that this variable increases the likelyhood of the patient having surgery.

The sensitivity of the logistic model is only 42%. The sign of the coefficient is all that matters in this case.

control <- trainControl(method = "repeatedcv", number=10, repeats=3)
logit = train( surgery ~ .,
               data = rf.data.smote, 
               method = "glm",
               family = "binomial", 
               trControl = control)
prediction = predict( object = logit,
                      newdata = dfValidation)
confusionMatrix(prediction, dfValidation$surgery)
Confusion Matrix and Statistics

          Reference
Prediction TRUE FALSE
     TRUE    81  1296
     FALSE  110  4464
                                          
               Accuracy : 0.7637          
                 95% CI : (0.7527, 0.7745)
    No Information Rate : 0.9679          
    P-Value [Acc > NIR] : 1               
                                          
                  Kappa : 0.0497          
 Mcnemar's Test P-Value : <2e-16          
                                          
            Sensitivity : 0.42408         
            Specificity : 0.77500         
         Pos Pred Value : 0.05882         
         Neg Pred Value : 0.97595         
             Prevalence : 0.03210         
         Detection Rate : 0.01361         
   Detection Prevalence : 0.23139         
      Balanced Accuracy : 0.59954         
                                          
       'Positive' Class : TRUE            
                                          

Coefficients

exp(coef(logit$finalModel))
            (Intercept)             surgery_lag                PT_count                  PTTRUE         MRI_6_weeksTRUE spinal_manipulationTRUE      diag_injectionTRUE       xray_6_weeksFALSE 
             0.01892649              1.00467641              0.94601127              0.43084952              0.24220599              1.72237896              0.21415402              7.79866040 
          severity1TRUE          severity2FALSE           severity3TRUE              member_age            nth_episode2            comorbidity1 
             2.77189418              2.70571302              0.36142152              1.01904203              0.44337266              0.82503731 
LS0tDQp0aXRsZTogIklkZW50aWZ5aW5nIEFjdGlvbmFibGUgVHJlYXRtZW50IFZhcmlhdGlvbiBpbiBFcGlzb2RlcyBvZiBMb3cgQmFjayBQYWluIg0KYXV0aG9yOiAiU2FtdWVsIENhc3RpbGxvIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50OiANCiAgICB0b2M6IHRydWUNCiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdA0KLS0tDQoNCmBgYHtyIGVjaG8gPSBULCBpbmNsdWRlID0gRiwgZXZhbCAgPSBUfQ0KI21vZGVsIGZpdHRpbmcgcGFja2FnZXMNCnJlcXVpcmUocmFuZG9tRm9yZXN0KQ0KbGlicmFyeShyZlV0aWxpdGllcykNCmxpYnJhcnkoY2FyZXQpDQpsaWJyYXJ5KERNd1IpDQpsaWJyYXJ5KHBST0MpDQpsaWJyYXJ5KFJPQ1IpDQojdmlzdWFsaXphaXRvbiBwYWNrYWdlcw0KbGlicmFyeShnb29nbGVWaXMpDQpsaWJyYXJ5KHN0cmluZ3IpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGdyaWRFeHRyYSkNCmxpYnJhcnkoZ2dyYXBoKQ0KbGlicmFyeShpZ3JhcGgpDQpsaWJyYXJ5KGtuaXRyKQ0KI2RhdGEgbWFuaXB1bGF0aW9uIHBhY2thZ2VzDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoaGF2ZW4pDQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCiNzZXR3ZCgiQzovVXNlcnMvc2Nhc3RpMDEvRGVza3RvcC9Db3N0IGFuZCBVc2UvRGF0YS9DbGFpbXMiKQ0KYGBgDQoNCiNPYmplY3RpdmUNCg0KSWRlbnRpZnkgYWN0aW9uYWJsZSB0cmVhdG1lbnQgdmFyaWF0aW9uIHdpdGhpbiBwYXRpZW50cyB3aXRoIGxvdyBiYWNrIHBhaW4uDQoNCioqKg0KDQojRGF0YSBQcmVwYXJhdGlvbg0KDQpGb3IgdGhpcyBhbmFseXNpcywgZGF0YSB3YXMgcHVsbGVkIGZyb20gdGhlIGZvbGxvd2luZyB0YWJsZXMgaW4gdGhlIGRhdGEgd2FyZWhvdXNlLiAgDQoNCiogVl9FVEdfTUVEX0NMQUlNUw0KKiBWX01FRElDQUxfU0VSVklDRQ0KKiBWX1NURF9UT1NfQ0FURUdPUlkNCiogVl9DTEFJTV9QUk9DRURVUkUNCiogVl9JQ0QxMF9ESUFHX0dSUA0KKiBWX0lOQ1VSUkVEX0RBVEUNCg0KUGF0aWVudHMgd2VyZSBtZW1iZXJzIG9mIEJDQlMgZnJvbSAyMDE2IHdobyBoYWQgZXBpc29kZXMgb2YgbG93IGJhY2sgcGFpbiB3aXRob3V0IGNvbXBsaWNhdGlvbnMuICBBZ2VzIHdlcmUgYmV0d2VlbiAxOC02NS4gIEVURyBvdXRsaWVycyB3ZXJlIGV4Y2x1ZGVkLiAgTWVtYmVycyB3aXRoIG1pc3NpbmcgZXBpc29kZSBudW1iZXJzIHdlcmUgZHJvcHBlZC4gIFRoZXJlIHdlcmUgMzAsMDAwIGVwaXNvZGVzIGluY2x1ZGVkIGluIHRoaXMgYW5hbHlzaXMgb3V0IG9mIGFuIG9yaWdpbmFsIDM2LDMwMC4gIA0KDQpgYGB7ciBnbG9iYWxfb3B0aW9ucywgbWVzc2FnZT0gRiwgd2FybmluZz0gRiwgZXZhbCA9IFQsIGVjaG8gPSBUfQ0KI3JlYWQgaW4gdGhlIFNBUyBmaWxlIA0KZGYgPSByZWFkX3NhcygiQzovVXNlcnMvc2Nhc3RpMDEvRGVza3RvcC9Db3N0IGFuZCBVc2UvUi9DbGFpbXMvZXBpc29kZXMuc2FzN2JkYXQiKSAlPiUNCiAgI2NvbnZlcnQgZGF0YSB0eXBlcyB0byBwcm9wZXIgZm9ybWF0DQogIG11dGF0ZShNRU1fTlVNID0gYXMuZmFjdG9yKE1FTV9OVU0pLA0KICAgICAgICAgRVBJX1RZUEUgPSBhcy5mYWN0b3IoRVBJX1RZUEUpLA0KICAgICAgICAgZXRnID0gYXMuY2hhcmFjdGVyKEVUR19GVUxMKSwNCiAgICAgICAgIEVUR19PVVRMSUVSID0gYXMuZmFjdG9yKEVUR19PVVRMSUVSKSwNCiAgICAgICAgIENPTVBMSUNBVElPTiA9IGFzLmZhY3RvcihDT01QTElDQVRJT04pLA0KICAgICAgICAgQ09NT1JCSURJVFkgPSBhcy5mYWN0b3IoQ09NT1JCSURJVFkpLA0KICAgICAgICAgU0VWRVJJVFkgPSBhcy5mYWN0b3IoU0VWRVJJVFkpLA0KICAgICAgICAgVE9TX0NBVDFfREVTQyA9IGFzLmZhY3RvcihUT1NfQ0FUMV9ERVNDKSwNCiAgICAgICAgIFRPU19DQVQyX0RFU0MgPSBhcy5mYWN0b3IoVE9TX0NBVDJfREVTQyksDQogICAgICAgICBUT1NfQ0FUM19ERVNDID0gYXMuZmFjdG9yKFRPU19DQVQzX0RFU0MpDQogICAgICAgICApICU+JQ0KICAjZXhjbHVkZSBtZW1iZXJzIHdpdGggY29tcGxpY2F0aW9ucw0KICAjZWNsdWRlIGVwaXNvZGVzIHdpdGggZXBpc29kZXMgbnVtYmVyIDAgKG1pc3NpbmcgdmFsdWVzKQ0KICBmaWx0ZXIoQ09NUExJQ0FUSU9OID09IDAsIEVQSVNPREVfTlVNICE9IDAsICFpcy5uYShFUElTT0RFX05VTSkgKSAlPiUNCiAgI2ZpbmQgdGhlIHRpbWUgZGlmZmVyZW5jZSBiZXR3ZWVuIGVhY2ggYXBwb2ludG1lbnQgYW5kIHRoZSBzdGFydCBvZiB0aCBlcGlzb2RlDQogIG11dGF0ZShsYWcgPSBkaWZmdGltZSggSU5DVVJSRURfRFRfREFZLCBFUElfU1RBUlRfRFQsIHVuaXRzID0gImRheXMiKSwNCiAgICAgICAgIHN1cmdlcnlfaW5kaWNhdG9yID0gIGlmZWxzZSh0ZXN0ID0gKHN0cl9kZXRlY3QoQ1BUX1BST0NfREVTQywgIlNVUkciKSl8fCBhbnkoVE9TX0NBVDJfREVTQyA9PSAiU1VSR0lDQUwiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHllcyA9IFQsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbm8gPSBGKSkgJT4lDQogICNjb3VudCB0aGUgbnVtYmVyIG9mIGVwaXNvZGVzIGZvciBlYWNoIHBhdGllbnQNCiAgZ3JvdXBfYnkoTUVNX05VTSkgJT4lDQogIG11dGF0ZSggbnRoX2VwaXNvZGUgPSBsZW5ndGgodW5pcXVlKEVQSVNPREVfTlVNKSkpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogICNjcmVhdGUgY2xpbmljYWwgVC9GIHZhcmlhYmxlcyBmb3IgY2xpbmljYWwgY2F0ZWdvcmllcw0KICBncm91cF9ieShFUElTT0RFX05VTSkgJT4lDQogIHN1bW1hcmlzZSggDQogICAgICAgICAgICAjZG9lcyB0aGUgZXBpc29kZSBoYXZlIHN1cmdlcnk/ICB0aGVzZSBhcmUgdGhlIEVUR3MgdGhhdCBoYXZlIHN1cmdlcnkgYXQgc29tZSBwb2ludCBpbiB0aGUgZXBpc29kZSAgICANCiAgICAgICAgICAgICBzdXJnZXJ5ID0gYXMuZmFjdG9yKGFueShFVEdfRlVMTCAgJWluJSBjKDcxMjIwODAwMSwgNzEyMjA4MDExLCA3MTIyMDgxMTEsIDcxMjIwODEwMSkpKSwNCiAgICAgICAgICAgICAjV2hlbiBkb2VzIHRoZSBzdXJnZXJ5IG9jY3VyPw0KICAgICAgICAgICAgIHN1cmdlcnlfbGFnID0gaWZlbHNlKCB0ZXN0ID0gYW55KHN1cmdlcnlfaW5kaWNhdG9yKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeWVzID0gbWluKGRpZmZ0aW1lKCBJTkNVUlJFRF9EVF9EQVksIEVQSV9TVEFSVF9EVCwgdW5pdHMgPSAiZGF5cyIpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbm8gPSAxMDAwKSwNCiAgICAgICAgICAgICAjSG93IG1hbnkgcGh5c2ljYWwgdGhlcmFweSBjbGFpbXMgYXJlIG9uIHJlY29yZD8NCiAgICAgICAgICAgICBQVF9jb3VudCA9IHN1bShpZmVsc2UoIHRlc3QgPSAoVE9TX0NBVDJfREVTQyA9PSAiUEhZU0lDQUwgVEhFUkFQWSIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeWVzID0gVCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBubyA9IEYpKSwNCiAgICAgICAgICAgICAjZGlkIHRoZSBwYXRpZW50IGdvIHRvIG1vcmUgdGhhbiA3IFBUIGFwcG9pbnRtZW50cz8NCiAgICAgICAgICAgICBQVCA9IGFzLmZhY3RvcihhbnkoVE9TX0NBVDJfREVTQyA9PSAiUEhZU0lDQUwgVEhFUkFQWSIgJiBQVF9jb3VudCA+PSA3KSksDQogICAgICAgICAgICAgI1dhcyB0aGVyZSBhbiBNUkkgYXQgYW55IHBvaW50IGluIHRoZSBlcGlzb2RlIGJlZm9yZSBzdXJnZXJ5PyAgQXQgNiB3ZWVrcywgOCB3ZWVrcz8NCiAgICAgICAgICAgICBNUklfYW55ID0gYXMuZmFjdG9yKGFueShUT1NfQ0FUM19ERVNDID09ICJNUkkiICkpLA0KICAgICAgICAgICAgIE1SSV82X3dlZWtzID0gYXMuZmFjdG9yKGFueShUT1NfQ0FUM19ERVNDID09ICJNUkkiICYgKGxhZyA8PSA2KjcpICYgKGxhZyA8PSBzdXJnZXJ5X2xhZykpKSwNCiAgICAgICAgICAgICBNUklfOF93ZWVrcyA9IGFzLmZhY3RvcihhbnkoVE9TX0NBVDNfREVTQyA9PSAiTVJJIiAmIChsYWcgPD0gOCo3KSAmIChsYWcgPD0gc3VyZ2VyeV9sYWcpKSksDQogICAgICAgICAgICAgI0RpZCB0aGUgcGF0aWVudCBoYXZlIGEgc3BpbmFsIG1hbmlwdWxhdGlvbiAoY2hpcm9wcmFjdGljIHRyZWF0bWVudCkgYmVmb3JlIHN1cmdlcnk/DQogICAgICAgICAgICAgc3BpbmFsX21hbmlwdWxhdGlvbiA9IGFzLmZhY3RvcihhbnkoVE9TX0NBVDNfREVTQyA9PSAiU1BJTkFMIE1BTklQVUxBVElPTiIgJiAobGFnIDw9IHN1cmdlcnlfbGFnKSkpLA0KICAgICAgICAgICAgICNkaWQgdGhlIHBhdGllbnQgaGF2ZSBhIGRpYWdub3N0aWMgaW5qZWN0aW9uIHByaW9yIHRvIHN1cmdlcnk/DQogICAgICAgICAgICAgZGlhZ19pbmplY3Rpb24gPSBhcy5mYWN0b3IoYW55KFRPU19DQVQzX0RFU0MgPT0gIkRJQUdOT1NUSUMgSU5KRUNUSU9OIiAmIChsYWcgPD0gc3VyZ2VyeV9sYWcpKSksDQogICAgICAgICAgICAgI3doZXJlIHRoZXJlIHhyYXlzIHRha2VuIGJlZm9yZSBzdXJnZXJ5PyAgSW4gdGhlIGZpcnN0IDYsIDggd2Vla3MsIGFuZCBwcmlvciB0byBzdXJnZXJ5Pw0KICAgICAgICAgICAgIHhyYXlfYW55ID0gYXMuZmFjdG9yKGFueShzdHJfZGV0ZWN0KCBDUFRfUFJPQ19ERVNDLCAiWC1SQVkiKSAmIChsYWcgPD0gc3VyZ2VyeV9sYWcpKSksDQogICAgICAgICAgICAgeHJheV82X3dlZWtzID0gYXMuZmFjdG9yKGFueShzdHJfZGV0ZWN0KCBDUFRfUFJPQ19ERVNDLCAiWC1SQVkiKSAmIChsYWcgPD0gNio3KSAmIChsYWcgPD0gc3VyZ2VyeV9sYWcpKSksDQogICAgICAgICAgICAgeHJheV84X3dlZWtzID0gYXMuZmFjdG9yKGFueShzdHJfZGV0ZWN0KCBDUFRfUFJPQ19ERVNDLCAiWC1SQVkiKSAmIChsYWcgPD0gOCo3KSAmIChsYWcgPD0gc3VyZ2VyeV9sYWcpKSksDQogICAgICAgICAgICAgI3doYXQgd2FzIHRoZSBFVEcgc2V2ZXJpdHk/DQogICAgICAgICAgICAgc2V2ZXJpdHkxID0gYXMuZmFjdG9yKGFueShTRVZFUklUWSA9PSAxKSksDQogICAgICAgICAgICAgc2V2ZXJpdHkyID0gYXMuZmFjdG9yKGFueShTRVZFUklUWSA9PSAyKSksDQogICAgICAgICAgICAgc2V2ZXJpdHkzID0gYXMuZmFjdG9yKGFueShTRVZFUklUWSA9PSAzKSksDQogICAgICAgICAgICAgI2hvdyBvbGQgd2FzIHRoZSBtZW1iZXI/DQogICAgICAgICAgICAgbWVtYmVyX2FnZSA9IGFzLm51bWVyaWMobWVtYmVyX2FnZVsxXSksDQogICAgICAgICAgICAgI0hvdyBtYW55IGVwaXNvZGVzIGRpZCB0aGlzIG1lbWJlciBoYXZlIGluIHRoZSBjdXJyZW50IHllYXI/ICBBbGwgbWVtYmVycyBpbiAyMDE2IGhhdmUgYXQgbW9zdCAyIGVwaXNvZGVzDQogICAgICAgICAgICAgbnRoX2VwaXNvZGUgPSBhcy5mYWN0b3IobnRoX2VwaXNvZGVbMV0pLA0KICAgICAgICAgICAgIE1FTV9OVU0gPSBNRU1fTlVNWzFdLA0KICAgICAgICAgICAgICNEaWQgdGhlIG1lbWJlciBoYXZlIGEgY29tb3JiaWRpdHk/DQogICAgICAgICAgICAgY29tb3JiaWRpdHkgPSBhcy5mYWN0b3IoQ09NT1JCSURJVFlbMV0pDQogICAgICAgICAgICAgDQogICkjZW5kIHN1bW1hcmlzZQ0KDQpgYGANCg0KI3N1cmdlcnkgZGF0ZSB0ZXN0DQoNCkRvIHRoZSB0b3NfY2F0IGRhdGVzIGFncmVlIHdpdGggdGhlIHN1cmdlcnkgZGF0ZXM/DQpgYGB7cn0NCnRlc3RkYXRhID0gDQpyZWFkX3NhcygiQzovVXNlcnMvc2Nhc3RpMDEvRGVza3RvcC9Db3N0IGFuZCBVc2UvUi9DbGFpbXMvZXBpc29kZXMuc2FzN2JkYXQiKSAlPiUNCiAgI2NvbnZlcnQgZGF0YSB0eXBlcyB0byBwcm9wZXIgZm9ybWF0DQogIG11dGF0ZShNRU1fTlVNID0gYXMuZmFjdG9yKE1FTV9OVU0pLA0KICAgICAgICAgRVBJX1RZUEUgPSBhcy5mYWN0b3IoRVBJX1RZUEUpLA0KICAgICAgICAgZXRnID0gYXMuY2hhcmFjdGVyKEVUR19GVUxMKSwNCiAgICAgICAgIEVUR19PVVRMSUVSID0gYXMuZmFjdG9yKEVUR19PVVRMSUVSKSwNCiAgICAgICAgIENPTVBMSUNBVElPTiA9IGFzLmZhY3RvcihDT01QTElDQVRJT04pLA0KICAgICAgICAgQ09NT1JCSURJVFkgPSBhcy5mYWN0b3IoQ09NT1JCSURJVFkpLA0KICAgICAgICAgU0VWRVJJVFkgPSBhcy5mYWN0b3IoU0VWRVJJVFkpLA0KICAgICAgICAgVE9TX0NBVDFfREVTQyA9IGFzLmZhY3RvcihUT1NfQ0FUMV9ERVNDKSwNCiAgICAgICAgIFRPU19DQVQyX0RFU0MgPSBhcy5mYWN0b3IoVE9TX0NBVDJfREVTQyksDQogICAgICAgICBUT1NfQ0FUM19ERVNDID0gYXMuZmFjdG9yKFRPU19DQVQzX0RFU0MpDQogICAgICAgICApICU+JQ0KICAjZXhjbHVkZSBtZW1iZXJzIHdpdGggY29tcGxpY2F0aW9ucw0KICAjZWNsdWRlIGVwaXNvZGVzIHdpdGggZXBpc29kZXMgbnVtYmVyIDAgKG1pc3NpbmcgdmFsdWVzKQ0KICBmaWx0ZXIoQ09NUExJQ0FUSU9OID09IDAsIEVQSVNPREVfTlVNICE9IDAsICFpcy5uYShFUElTT0RFX05VTSkgKSAlPiUNCiAgI2ZpbmQgdGhlIHRpbWUgZGlmZmVyZW5jZSBiZXR3ZWVuIGVhY2ggYXBwb2ludG1lbnQgYW5kIHRoZSBzdGFydCBvZiB0aCBlcGlzb2RlDQogIG11dGF0ZShsYWcgPSBkaWZmdGltZSggSU5DVVJSRURfRFRfREFZLCBFUElfU1RBUlRfRFQsIHVuaXRzID0gIndlZWtzIikpDQoNCg0KDQpzdXJnZXJ5X3Rlc3QgPSB0ZXN0ZGF0YSAlPiUNCiAgI2NvdW50IHRoZSBudW1iZXIgb2YgZXBpc29kZXMgZm9yIGVhY2ggcGF0aWVudA0KICBncm91cF9ieShNRU1fTlVNKSAlPiUNCiAgbXV0YXRlKCBudGhfZXBpc29kZSA9IGxlbmd0aCh1bmlxdWUoRVBJU09ERV9OVU0pKSkgJT4lDQogIHVuZ3JvdXAoKSAlPiUNCiAgZ3JvdXBfYnkoRVBJU09ERV9OVU0pICU+JQ0KICBzdW1tYXJpc2UoIA0KICAgIHN1cmdlcnlfZXRnID0gYXMuZmFjdG9yKGFueShFVEdfRlVMTCAgJWluJSBjKDcxMjIwODAwMSwgNzEyMjA4MDExLCA3MTIyMDgxMTEsIDcxMjIwODEwMSkpKSwNCiAgICBzdXJnZXJ5X3Rvc2NhdCA9IGFzLmZhY3RvcihhbnkoKHN0cl9kZXRlY3QoQ1BUX1BST0NfREVTQywgIlNVUkciKSl8fCBhbnkoVE9TX0NBVDJfREVTQyA9PSAiU1VSR0lDQUwiKSkpLA0KICAgIGhpdCA9IGlmZWxzZShzdXJnZXJ5X2V0ZyA9PSBzdXJnZXJ5X3Rvc2NhdCwgDQogICAgICAgICAgICAgICAgIHllcyA9IFQsIG5vID0gRikpDQoNCmNvbmZ1c2lvbk1hdHJpeChzdXJnZXJ5X3Rlc3Qkc3VyZ2VyeV9ldGcsIHN1cmdlcnlfdGVzdCRzdXJnZXJ5X3Rvc2NhdCkNCg0KdGVzdGRhdGEgJT4lIA0KICBmaWx0ZXIoRVRHX0ZVTEwgJWluJSBjKDcxMjIwODAwMSwgNzEyMjA4MDExLCA3MTIyMDgxMTEsIDcxMjIwODEwMSkpICU+JQ0KICBzZWxlY3QoSENQQ1NfUFJPQ19ERVNDKSAlPiUNCiAgdW5pcXVlKCkNCg0KDQpgYGANCg0KDQojI1ZhbGlkYXRpb24gU2V0IFNwbGl0DQoNCkEgdmFsaWRhdGlvbiBzZXQgd2FzIGNyZWF0ZWQgdG8gcHJldmVudCBvdmVyZml0dGluZyB0aGUgbW9kZWwgdG8gdGhlIGRhdGEuICBBIHJhbmRvbWx5LXNhbXBsZWQgMjAlIHZhbGlkYXRpb24sIDgwJSBub24tdmFsaWRhdGlvbiBzcGxpdCB3YXMgdXNlZC4gIFRoaXMgODAlIG5vbi12YWxpZGF0aW9uIHNldCB3YXMgZnVydGhlciBzcGxpdCBkdXJpbmcgYSAxMC1mb2xkIGNyb3NzIHZhbGlkYXRpb24gd2hpY2ggd2FzIHJlcGVhdGVkIDMgdGltZXMuDQoNCmBgYHtyfQ0KZnJhY3Rpb25fVmFsaWRhdGlvbiA8LSAwLjIwDQpmcmFjdGlvbl9Ob25WYWxpZGF0aW9uIDwtIDAuODANCg0KIyBDb21wdXRlIHNhbXBsZSBzaXplcy4NCnNhbXBsZV9TaXplX1ZhbGlkYXRpb24gICA8LSBmbG9vcihmcmFjdGlvbl9WYWxpZGF0aW9uICAgKiBucm93KGRmKSkNCnNhbXBsZV9TaXplX05vblZhbGlkYXRpb24gPC0gZmxvb3IoZnJhY3Rpb25fTm9uVmFsaWRhdGlvbiAqIG5yb3coZGYpKQ0KDQojIENyZWF0ZSB0aGUgcmFuZG9tbHktc2FtcGxlZCBpbmRpY2VzIGZvciB0aGUgZGF0YWZyYW1lLiBVc2Ugc2V0ZGlmZigpIHRvDQojIGF2b2lkIG92ZXJsYXBwaW5nIHN1YnNldHMgb2YgaW5kaWNlcy4NCmluZGljZXNfVmFsaWRhdGlvbiAgICA8LSBzb3J0KHNhbXBsZShzZXFfbGVuKG5yb3coZGYpKSwgc2l6ZSA9IHNhbXBsZV9TaXplX1ZhbGlkYXRpb24pKQ0KaW5kaWNlc19Ob3RfVmFsaWRhdGlvbiA8LSBzZXRkaWZmKHNlcV9sZW4obnJvdyhkZikpLCBpbmRpY2VzX1ZhbGlkYXRpb24pDQoNCiMgRmluYWxseSwgb3V0cHV0IHRoZSB0aHJlZSBkYXRhZnJhbWVzIGZvciB0cmFpbmluZywgdmFsaWRhdGlvbiBhbmQgdGVzdC4NCmRmVmFsaWRhdGlvbiA8LSBkZltpbmRpY2VzX1ZhbGlkYXRpb24sIF0NCmRmTm90VmFsaWRhdGlvbiA8LSBkZltpbmRpY2VzX05vdF9WYWxpZGF0aW9uLF0NCmBgYA0KDQojI0FkanVzdGluZyBmb3IgQ2xhc3MgSW5iYWxhbmNlDQoNClRoZSBzdXJnaWNhbCByYXRlIGlzIG9ubHkgfjYlLCB3aGljaCBsZWFkcyB0byBhIGNsYXNzIGluYmFsYW5jZSBpbiBzdXJnaWNhbCBhbmQgbm9uLXN1cmdpY2FsIG91dGNvbWVzLiAgVG8gYWNjb3VudCBmb3IgdGhpcywgdGhlIG1pbm9yaXR5IHBvcHVsYXRpb24gY2FuIGJlIG92ZXJzYW1wbGVkIGFuZCB0aGUgbWFqb3JpdHkgKHN1cmdpY2FsKSBwb3B1bGF0aW9uIHVuZGVyc2FtcGxlZC4gIEEgdGVjaG5pcXVlIGtub3duIGFzIFN5dGhldGljIE1pbm9yaXR5IE92ZXJzYW1wbGluZyB3YXMgdXNlZCBmcm9tIHRoZSBsaWJyYXJ5IGBTTU9URWAuICBUbyB0ZXN0IHBlcmZvcm1hbmNlLCB0d28gdmVyc2lvbnMgd2VyZSB1c2VkOiB0aGUgZmlyc3Qgd2l0aCBib3RoIG92ZXItc2FtcGxpbmcgb2YgdGhlIHN1cmdpY2FsIGNsYXNzIGFuZCB1bmRlcnNhbXBsaW5nIG9mIHRoZSBub24tc3VyZ2ljYWwgY2xhc3MsIGFuZCBhIHNlY29uZCB3aXRoIG9ubHkgb3Zlci1zYW1wbGluZyBvZiB0aGUgc3VyZ2ljYWwgY2xhc3MuICBGb3IgYnJldml0eSB0aGlzIHJlcG9ydCBvbmx5IGNvbnRhaW5zIHRoZSBmaXJzdCB2ZXJzaW9uLCB3aGljaCBwZXJmb3JtZWQgYmV0dGVyIGJhc2VkIG9uIHNlbnNpdGl2aXR5IGFuZCBhY2N1cmFjeSByYXRpbmdzLg0KDQpgYGB7cn0NCnByZV9wcm9jZXNzZWRfZGF0YSA9IGRwbHlyOjphc19kYXRhX2ZyYW1lKGRmTm90VmFsaWRhdGlvbikgJT4lIA0KICBkcGx5cjo6c2VsZWN0KC1NUklfNl93ZWVrcywgDQogICAgICAgICAgICAgICAgLXhyYXlfNl93ZWVrcywgDQogICAgICAgICAgICAgICAgLU1SSV9hbnksIA0KICAgICAgICAgICAgICAgIC14cmF5X2FueSwgDQogICAgICAgICAgICAgICAgLSBNRU1fTlVNLA0KICAgICAgICAgICAgICAgIC0gRVBJU09ERV9OVU0sDQogICAgICAgICAgICAgICAgLSBzdXJnZXJ5X2xhZykgJT4lDQogIGFzLmRhdGEuZnJhbWUoKQ0KDQpgYGANCg0KUHJpb3IgdG8gdGhlIG92ZXJzYW1wbGluZyB0aGVyZSBpcyBhYm91dCBhIDk1JS01JSBzcGxpdCBiZXR3ZWVuIHRob3NlIGVwaXNvZGVzIHdpdGggc3VyZ2VyeSBhbmQgd2l0aG91dC4NCg0KYGBge3IgfQ0KDQp0MSA9IFN5cy50aW1lKCkNCnJmLmRhdGEuc21vdGUgPSBTTU9URShzdXJnZXJ5IH4uLCANCiAgICAgICAgICAgICAgICAgICAgICBkYXRhIDwtIHByZV9wcm9jZXNzZWRfZGF0YSwgDQogICAgICAgICAgICAgICAgICAgICAgcGVyYy5vdmVyID0gMTUwMCwNCiAgICAgICAgICAgICAgICAgICAgICBwZXJjLnVuZGVyID0gMTAwLA0KICAgICAgICAgICAgICAgICAgICAgIGsgPSA5KQ0KDQp0MiA9IFN5cy50aW1lKCkNCnJ1bl90aW1lID0gdDEgLSB0Mg0KcnVuX3RpbWUNCmRpbShyZi5kYXRhLnNtb3RlKQ0Kc3VtbWFyeShyZi5kYXRhLnNtb3RlJHN1cmdlcnkpDQpgYGANCg0KQWZ0ZXIgdGhlIGJvb3RzdHJhcCB0aGVyZSBhcmUgcm91Z2hseSBhbiBlcXVhbCBudW1iZXIgb2YgY2FzZXMgd2l0aCBhbmQgd2l0aG91dCBzdXJnZXJ5Lg0KDQojRml0dGluZyBhIE1vZGVsDQoNCkJlY2F1c2Ugb2YgdGhlIG51bWJlciBvZiBiaW5hcnkgcHJlZGljdG9ycyBhbmQgbmVlZCB0byBjb21wZW5zYXRlIGZvciBvdmVyZml0dGluZywgYSByYW5kb20gZm9yZXN0IHdhcyB1c2VkIHdpdGggYW4gYXV0b21hdGVkIHR1bmluZyBmdW5jdGlvbiBmcm9tIHRoZSBsaWJyYXJ5IGBjYXJldGAuDQoNCmBgYHtyIH0NCnQxID0gU3lzLnRpbWUoKQ0KIyBDcmVhdGUgbW9kZWwgd2l0aCBkZWZhdWx0IHBhcmFtdGVycw0KY29udHJvbCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kID0gInJlcGVhdGVkY3YiLCBudW1iZXI9MTAsIHJlcGVhdHM9MykNCnNlZWQgPC0gNw0KbWV0cmljIDwtICJLYXBwYSINCnNldC5zZWVkKHNlZWQpDQptdHJ5IDwtIHNxcnQobmNvbChyZi5kYXRhLnNtb3RlKSkNCnR1bmVncmlkIDwtIGV4cGFuZC5ncmlkKC5tdHJ5PW10cnkpDQoNCnJmX3Ntb3RlIDwtIHRyYWluKHN1cmdlcnkgfi4sIA0KICAgICAgICAgICAgICAgICAgICBkYXRhPSByZi5kYXRhLnNtb3RlLCANCiAgICAgICAgICAgICAgICAgICAgbWV0aG9kPSJyZiIsIA0KICAgICAgICAgICAgICAgICAgICBtZXRyaWM9bWV0cmljLCANCiAgICAgICAgICAgICAgICAgICAgdHVuZUdyaWQ9dHVuZWdyaWQsIA0KICAgICAgICAgICAgICAgICAgICB0ckNvbnRyb2w9Y29udHJvbCkNCg0KdDIgPSBTeXMudGltZSgpDQpyZl9ydW50aW1lID0gdDIgLSB0MQ0KcmZfcnVudGltZQ0KYGBgDQoNCg0KIyNUZXN0aW5nIHRoZSBNb2RlbA0KDQpUZXN0aW5nIHdhcyBjb25kdWN0ZWQgYm90aCBhZ2FpbnN0IHRoZSBib290c3RyYXBwZWQgZGF0YSBhbmQgdGhlIHVuLWluZmxhdGVkIHZhbGlkYXRpb24gc2V0LCB3aGljaCBjb250YWluZWQgdGhlIHJhdyBvYnNlcnZhdGlvbnMuICBUaGUgY3V0b2ZmIHZhbHVlIGZvciB0aGUgcmFuZG9tIGZvcmVzdCB3YXMgb3B0aW1pemVkIGJhc2VkIG9uIHRoZSBzZW5zaXRpdml0eSBhbmQgYWNjdXJhY3kgb2YgdGhlIG1vZGVsLg0KDQojI1Rlc3QgYWdhaW5zdCBib290c3RyYXBwZWQgZGF0YQ0KDQpgYGB7ciBmaWcud2lkdGg9IDQsIGZpZy5oZWlnaHQ9IDR9DQoNCnByZWRpY3RvciA9IHJmX3Ntb3RlJGZpbmFsTW9kZWwkdm90ZXNbLDFdDQpyZXNwb25zZSA9IHJmLmRhdGEuc21vdGUkc3VyZ2VyeSANCg0Kcm9jKHJlc3BvbnNlLCBwcmVkaWN0b3IsIHBlcmNlbnQ9VFJVRSwNCiAgICAgICAgICAgICMgYXJndW1lbnRzIGZvciBhdWMNCiAgICAgICAgICAgIHBsb3Q9VFJVRSwgDQogICAgICAgICAgICBhdWMucG9seWdvbj1UUlVFLCANCiAgICAgICAgICAgIG1heC5hdWMucG9seWdvbj1UUlVFLCANCiAgICAgICAgICAgIGdyaWQ9VFJVRSwNCiAgICAgICAgICAgIHByaW50LmF1Yz1UUlVFLCANCiAgICAgICAgICAgIHNob3cudGhyZXM9VFJVRSkNCmBgYA0KDQpUaGUgQXJlYSBVbmRlciB0aGUgQ3VydmUgKEFVQykgb2YgMC45NDIgaW5kaWNhdGVzIHRoYXQgdGhlIG1vZGVsIGZpdHMgdGhlIGJvb3RzdHJhcHBlZCBkYXRhIHdlbGwuICBUaGUgaW50ZXJwcmV0YXRpb24gaXMgdGhlIHByb2JhYmlsaXR5IHRoYXQgYSByYW5kb21seSBzZWxlY3RlZCBzdWJqZWN0IHdobyBlbmRzIHVwIHdpdGggc3VyZ2VyeSBoYXMgYSB0ZXN0IHJlc3VsdCBpbmRpY2F0aW5nIGdyZWF0ZXIgc3VzcGljaW9uIHRoYW4gdGhhdCBvZiBhIHJhbmRvbWx5IGNob3NlbiBzdWJqZWN0IHdobyBkb2VzIG5vdCBlbmQgdXAgaGF2aW5nIHN1cmdlcnkuIChIYW5sZXkgJiBNY05laWwsIDE5ODIpDQoNCmBgYHtyIGZpZy53aWR0aD0gNCwgZmlnLmhlaWdodD0gNH0NCmRldi5uZXcoKQ0KcHJlZCA9IHByZWRpY3Rpb24oIHByZWRpY3RvciwgcmVzcG9uc2UpDQpwZXJmID0gcGVyZm9ybWFuY2UocHJlZCwgbWVhc3VyZT0idHByIix4Lm1lYXN1cmU9ImN1dG9mZiIpDQpwbG90KHBlcmYsIA0KICAgICBtYWluID0gIlRydWUgUG9zaXRpdmUgUmF0ZSB2cyBDdXRvZmYiLA0KICAgICB0eXBlID0gInMiKQ0KYGBgDQpUaGUgdHJ1ZSBwb3NpdGl2ZSByYXRlIChzZW5zaXRpdml0eSkgaXMgaGlnaGVzdCB3aGVuIHRoZSBjdXRvZmYgaXMgc2V0IHRvIGNsb3NlIHRvIHplcm8uICBCZWNhdXNlIHRoZSBnb2FsIGlzIHRvIGNvcnJlY3RseSBpZGVudGlmeSB0aG9zZSBwYXRpZW50cyB3aG8gaGF2ZSBzdXJnZXJ5LCB0aGUgY3V0b2ZmIHdhcyBzZXQgY2xvc2UgdG8gemVybyBpbiBvcmRlciB0byBvcHRpbWl6ZSB0aGUgc2Vuc2l0aXZpdHkgb2YgdGhlIG1vZGVsLCBldmVuIGF0IHRoZSBjb3N0IG9mIHJlZHVjZWQgYWNjdXJhY3kuICBUaGlzIGRvZXMgbm90IGVmZmVjdCB0aGUgdmFyaWFibGUgaW1wb3J0YW5jZSByYW5raW5nLCBob3dldmVyLiAgDQoNCmBgYHtyIH0NCmRldi5uZXcoKQ0KcHJlZCA9IHByZWRpY3Rpb24oIHByZWRpY3RvciwgcmVzcG9uc2UpDQpwZXJmID0gcGVyZm9ybWFuY2UocHJlZCwgbWVhc3VyZT0iZnByIix4Lm1lYXN1cmU9ImN1dG9mZiIpDQpwbG90KHBlcmYsIA0KICAgICBtYWluID0gIkZhbHNlIFBvc2l0aXZlIFJhdGUgdnMgQ3V0b2ZmIiwNCiAgICAgdHlwZSA9ICJzIikNCmBgYA0KDQojI1Rlc3QgQWdhaW5zdCBWYWxpZGF0aW9uIFNldA0KDQpgYGB7ciB9DQpyZl9zbW90ZSRmaW5hbE1vZGVsJGZvcmVzdCRjdXRvZmYgPSBjKDAuMDAwMSwgMC45OTk5KQ0KcHJlZGljdGlvbiA9IHByZWRpY3QoIG9iamVjdCA9IHJmX3Ntb3RlLA0KICAgICAgICAgICAgICAgICAgICAgIG5ld2RhdGEgPSBkZlZhbGlkYXRpb24pDQoNCmNvbmZ1c2lvbk1hdHJpeChwcmVkaWN0aW9uLCBkZlZhbGlkYXRpb24kc3VyZ2VyeSkNCmBgYA0KDQojSW50ZXJwcmV0YXRpb24NCg0KIyNWYXJpYWJsZSBJbXBvcnRhbmNlDQpgYGB7ciB9DQpuYW1lcyA9IGMoIk51bWJlciBvZiBQVCBWaXNpdHMiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQW55IFBUIFkvTiIsICJNUkkgaW4gdGhlIGZpcnN0IDggd2Vla3MiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQ2hpcm9wcmFjdGljIFNwaW5hbCBNYW5pcHVsYXRpb24iLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiRGlhZ25vc3RpYyBJbmplY3Rpb24iLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiWFJheSBpbiB0aGUgZmlyc3QgOCB3ZWVrcyIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJTZXZlcml0eSAxIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiU2V2ZXJpdHkgMiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlNldmVyaXR5IDMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJNZW1iZXIgQWdlIiwgIk51bWJlciBvZiBFcGlzb2RlIiwgIkNvbW9yYmlkaXR5IFkvTiIpDQoNCmltcG9ydGFuY2UgPSB2YXJJbXAocmZfc21vdGUkZmluYWxNb2RlbCkkT3ZlcmFsbA0KDQpwb3NpdGlvbnMgPSBkYXRhX2ZyYW1lKG5hbWVzLCBpbXBvcnRhbmNlKSAlPiUNCiAgYXJyYW5nZShpbXBvcnRhbmNlKSAlPiUNCiAgc2VsZWN0KG5hbWVzKSANCg0KDQpwb3NpdGlvbnMgPC0gcG9zaXRpb25zJG5hbWVzDQoNCmdnZGF0YSA9IGRhdGFfZnJhbWUobmFtZXMsIGltcG9ydGFuY2UpDQoNCmdncGxvdChnZ2RhdGEsIGFlcyh4ID0gbmFtZXMsIHkgPSBpbXBvcnRhbmNlKSkgKyANCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAibGlnaHRibHVlIiwgY29sb3IgPSAiZ3JleSIpICsgIA0KICBzY2FsZV94X2Rpc2NyZXRlKGxpbWl0cyA9IHBvc2l0aW9ucykgKyANCiAgY29vcmRfZmxpcCgpICsgeWxhYigiUmVsYXRpdmUgSW1wb3JhbmNlIikgKyANCiAgeGxhYigiVmFyaWFibGUiKSArIA0KICB0aGVtZV9saWdodCgpICsgDQogIHRoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT0xNikpDQpgYGANCg0KDQojI0xvZ2lzdGljIFJlZ3Jlc3Npb24gb2YgVG9wIFByZWRpY3RvcnMNCg0KQSBsb2dpc3RpYyByZWdyZXNzaW9uIGFsbG93cyBmb3IgaW5mZXJyZW5jZSBvbiB3aGV0aGVyIG9yIG5vdCBlYWNoIHByZWRpY3RvciBpbmNyZWFzZXMgb3IgZGVjcmVhc2VzIHRoZSBwcm9iYWJpbGl0eSBvZiBoYXZpbmcgc3VyZ2VyeS4gIEEgdmFyaWFibGUgd2l0aCBhIHBvc2l0aXZlIGNvZWZmaWNpZW50IGluZGljYXRlcyB0aGF0IHRoaXMgdmFyaWFibGUgaW5jcmVhc2VzIHRoZSBsaWtlbHlob29kIG9mIHRoZSBwYXRpZW50IGhhdmluZyBzdXJnZXJ5Lg0KDQpUaGUgc2Vuc2l0aXZpdHkgb2YgdGhlIGxvZ2lzdGljIG1vZGVsIGlzIG9ubHkgNDIlLiAgVGhlIHNpZ24gb2YgdGhlIGNvZWZmaWNpZW50IGlzIGFsbCB0aGF0IG1hdHRlcnMgaW4gdGhpcyBjYXNlLg0KYGBge3IgfQ0KY29udHJvbCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kID0gInJlcGVhdGVkY3YiLCBudW1iZXI9MTAsIHJlcGVhdHM9MykNCg0KbG9naXQgPSB0cmFpbiggc3VyZ2VyeSB+IC4sDQogICAgICAgICAgICAgICBkYXRhID0gcmYuZGF0YS5zbW90ZSwgDQogICAgICAgICAgICAgICBtZXRob2QgPSAiZ2xtIiwNCiAgICAgICAgICAgICAgIGZhbWlseSA9ICJiaW5vbWlhbCIsIA0KICAgICAgICAgICAgICAgdHJDb250cm9sID0gY29udHJvbCkNCg0KDQpwcmVkaWN0aW9uID0gcHJlZGljdCggb2JqZWN0ID0gbG9naXQsDQogICAgICAgICAgICAgICAgICAgICAgbmV3ZGF0YSA9IGRmVmFsaWRhdGlvbikNCg0KY29uZnVzaW9uTWF0cml4KHByZWRpY3Rpb24sIGRmVmFsaWRhdGlvbiRzdXJnZXJ5KQ0KYGBgDQojI0NvZWZmaWNpZW50cw0KYGBge3J9DQpleHAoY29lZihsb2dpdCRmaW5hbE1vZGVsKSkNCmBgYA0KDQoNCg0K