library(fpp3)
library(dplyr)
library(ggplot2)
library(readxl)
library(tsibble)
library(psych)
library(tidyr)
library(forecast)

Description

This project consists of 3 parts - two required and one bonus and is worth 15% of your grade. The project is due at 11:59 PM on Sunday Apr 11. I will accept late submissions with a penalty until the meetup after that when we review some projects.

Part A

ATM Forecast ATM624Data.xlsx

In part A, I want you to forecast how much cash is taken out of 4 different ATM machines for May 2010. The data is given in a single file. The variable ‘Cash’ is provided in hundreds of dollars, other than that it is straight forward. I am being somewhat ambiguous on purpose to make this have a little more business feeling. Explain and demonstrate your process, techniques used and not used, and your actual forecast. I am giving you data via an excel file, please provide your written report on your findings, visuals, discussion and your R code via an RPubs link along with the actual.rmd file Also please submit the forecast which you will put in an Excel readable file.

Part B

Forecasting Power ResidentialCustomerForecastLoad-624.xlsx

Part B consists of a simple dataset of residential power usage for January 1998 until December 2013. Your assignment is to model these data and a monthly forecast for 2014. The data is given in a single file. The variable ‘KWH’ is power consumption in Kilowatt hours, the rest is straight forward. Add this to your existing files above.

Part C

BONUS, optional (part or all), Waterflow_Pipe1.xlsx and Waterflow_Pipe2.xlsx

Part C consists of two data sets. These are simple 2 columns sets, however they have different time stamps. Your optional assignment is to time-base sequence the data and aggregate based on hour (example of what this looks like, follows). Note for multiple recordings within an hour, take the mean. Then to determine if the data is stationary and can it be forecast. If so, provide a week forward forecast and present results via Rpubs and .rmd and the forecast in an Excel readable file.

Data Load

https://github.com/GitableGabe/Data624_Data/raw/main/ATM624Data.xlsx

atm_coltype<-c("date","text","numeric")

atm_import<-read_xlsx('ATM624Data.xlsx', col_types = atm_coltype)
# Ommitting Extra Credit as I won't be working on it
# WP1_df<-read_xlsx('Waterflow_Pipe1.xlsx')
# WP2_df<-read_xlsx('Waterflow_Pipe2.xlsx')
power_raw<-read_xlsx('ResidentialCustomerForecastLoad-624.xlsx')

Part A

EDA & Cleanup

head(atm_import%>%
       filter(ATM=="ATM4"))
atm_range<-range(atm_import$DATE)
atm_range[1]
[1] "2009-05-01 UTC"
atm_range[2]
[1] "2010-05-14 UTC"
sapply(atm_import, function(x) sum(is.na(x)))
DATE  ATM Cash 
   0   14   19 
data.frame(atm_import$DATE[atm_import$Cash %in% NA])
  • ATM624Data had attribute type mismatches, and was converted on import.
  • Date conversion somehow kept date time as POSIXct
  • ATM4 shows values in greater decimals any country, with Dinars being the only Country that uses more than 2 decimals when using its currency, but even the dinar stops at the 100th decimal.
  • Date range is 05-01-2009 to 05-14-2010
  • we see the count of NAs in ATM is 14 and Cash column is 19
  • The NA dates vary and are not exclusive to a specific sequential time period that we can just filter out.
  • I am curious about the distribution of cash considering the forecast ask for this project.
atm_import %>% 
  filter(DATE < "2010-05-01", !is.na(ATM)) %>% 
  ggplot(aes(x = Cash)) +
    geom_histogram(bins = 30, color= "blue") +
    facet_wrap(~ ATM, ncol = 2, scales = "free")

(atm_df <- atm_import %>% 
  mutate(DATE = as.Date(DATE)) %>%
   filter(DATE<"2010-05-01")%>%
  pivot_wider(names_from=ATM, values_from = Cash))
atm_df<-atm_df%>%
  as_tsibble(index=DATE)
head(atm_df)
summary(atm_df)
      DATE                 ATM1             ATM2             ATM3              ATM4          
 Min.   :2009-05-01   Min.   :  1.00   Min.   :  0.00   Min.   : 0.0000   Min.   :    1.563  
 1st Qu.:2009-07-31   1st Qu.: 73.00   1st Qu.: 25.50   1st Qu.: 0.0000   1st Qu.:  124.334  
 Median :2009-10-30   Median : 91.00   Median : 67.00   Median : 0.0000   Median :  403.839  
 Mean   :2009-10-30   Mean   : 83.89   Mean   : 62.58   Mean   : 0.7206   Mean   :  474.043  
 3rd Qu.:2010-01-29   3rd Qu.:108.00   3rd Qu.: 93.00   3rd Qu.: 0.0000   3rd Qu.:  704.507  
 Max.   :2010-04-30   Max.   :180.00   Max.   :147.00   Max.   :96.0000   Max.   :10919.762  
                      NA's   :3        NA's   :2                                             
atm_df[!complete.cases(atm_df), ]
atm_df%>%
  select(DATE,ATM3)%>%
  filter(ATM3>0)
  • Converting DATE into a date value made senses type POSIXct may cause future issues.
  • Pivoting allowed us to separate the ATM’s categorically and isolate the NAs for removal.
  • We are able to see that five entries contain NAs and the dates all reside in June
  • ATM3 only has 3 dates with withdrawals 4-28 through 4-30 or 2010, and the distribution plot is arguably a reason to omit this column
  • These results also brings to question whether there may be some seasonality that will impact May’s forecasting
  • Considering the distribution, I chose to replace the missing values with the median, as the skewed values in ATM 3 & 4 I believe with negatively impact the mean

# seasonality
atm_import %>% 
  filter(DATE < "2010-05-01", !is.na(ATM)) %>% 
  ggplot(aes(x = DATE, y = Cash, col = ATM)) +
    geom_line(color="blue") +
    facet_wrap(~ ATM, ncol = 2, scales = "free_y")+
  labs(title = "Seasonality Plot", x = "Date", y = "Cash") +
    theme_minimal()

NA

median_value <- median(atm_df[["ATM1"]], na.rm = TRUE)
atm_df[["ATM1"]][is.na(atm_df[["ATM1"]])] <- median_value
median_value <- median(atm_df[["ATM2"]], na.rm = TRUE)
atm_df[["ATM2"]][is.na(atm_df[["ATM2"]])] <- median_value
atm_df[!complete.cases(atm_df), ]

Forecasts

ATM1

STL Decomposition

The seasonality plot did not show a trend in the long term but a better assessment in weekly interval is likely needed, using resources from Rob J Hyndman and George Athanasopoulos, Forecasting: Principles and Practice (3rd ed) section 3.6 STL decomposition I will perform a STL “Seasonal and Trend decomposition using Loess” decomposition of the series. To make it weekly I’ll set the parameter trend(window = 7) and the season(window='periodic') to impose seasonality element across days of the week.

My reference come directly from the chapter.

          us_retail_employment |>
            model(
              STL(Employed ~ trend(window = 7) +
                             season(window = "periodic"),
              robust = TRUE)) |>
            components() |>
            autoplot()
atm1_df <- atm_df %>% 
  dplyr::select(DATE, ATM1)

atm1_df %>%
  model(
    STL(ATM1 ~ trend(window = 7) +
                   season(window = "periodic"),
    robust = TRUE)) %>%
  components() %>%
  autoplot()

ndiffs(atm1_df$ATM1)
[1] 0
atm1_df %>% 
  ACF(ATM1, lag_max = 30) %>% 
  autoplot()

The STL decomposition wasn’t as telling as I would have liked, however the ACF plot presents lags at 2, 5, and 7. I believe, given the week starts on Sunday, that this represents Monday, Thursday and Saturday as the days with the most lag. 7 has shown the value with the most significant lag. There is a decreasing trend with the ACF plot, and supports that the data is non-stationary would require differencing however \(r_ 1's\) small value and the results of the ndiff() function, showing the first number of differences as 0, negates that suspicion.

ARIMA

Seasonal naive method was my preferred choice considering the seasonality, and so we can use the prior time period’s withdrawals to conduct our forecast, but I also like to default to Auto ARIMA for the optimized selection. I assume ETS and ARIMA wont perform as well but will await for the comparisons. Below we filter out the data residing in May, the month we are forecasting.

# train
atm1_train <- atm1_df %>%
  filter(DATE <= "2010-04-01")


atm1_fit <- atm1_train %>%
  model(
    SNAIVE = SNAIVE(ATM1),
    ETS = ETS(ATM1),
    ARIMA = ARIMA(ATM1),
    `Auto ARIMA` = ARIMA(ATM1, stepwise = FALSE, approx = FALSE)
  )

# forecast April
atm1_forecast <- atm1_fit %>%
  forecast(h = 30)

#plot
atm1_forecast %>%
  autoplot(atm1_df, level = NULL)+
  facet_wrap( ~ .model, scales = "free_y") +
  guides(colour = guide_legend(title = "Forecast"))+
  labs(title= "ATM1 Forecasts | April") +
  xlab("Date") +
  ylab("$$$ (In Hundreds)") 

# RMSE
accuracy(atm1_forecast, atm1_df) %>%
  select(.model, RMSE:MAPE)

When interpreting the results, the model with the lowest RMSE and MAE value and the MPE and MAPE values closes to zero the best performing. This is true in all cases for ETS indicating it is the best performing.

Forecast

** Reference**

                      aus_economy |>
                        model(ETS(Population)) |>
                        forecast(h = "5 years") |>
                        autoplot(aus_economy |> filter(Year >= 2000)) +
                        labs(title = "Australian population",
                             y = "People (millions)")
# remade the model from source
atm1_fit_ets <- atm1_df %>% 
  model(ETS = ETS(ATM1))

atm1_forecast_ets <- atm1_fit_ets %>% 
  forecast(h=30)

atm1_forecast_ets %>% 
  autoplot(atm1_df) +
  labs(title = "ATM1 Forecast (ETS) | May",
       y = "$$$ (in Hundreds)")

(atm1_forecast_results <- 
  as.data.frame(atm1_forecast_ets) %>%
    select(DATE, .mean) %>% 
      rename(Date = DATE, Cash = .mean)%>%
        mutate(Cash=round(Cash,2)))

ATM2

STL Decomposition

atm2_df <- atm_df %>% 
  dplyr::select(DATE, ATM2)

atm2_df %>%
  model(
    STL(ATM2 ~ trend(window = 7) +
                   season(window = "periodic"),
    robust = TRUE)) %>%
  components() %>%
  autoplot()

ndiffs(atm2_df$ATM2)
[1] 1
unitroot_ndiffs(atm2_df$ATM2)
ndiffs 
     1 
atm2_df %>% 
  ACF(ATM2, lag_max = 30) %>% 
  autoplot()

The approach with ATM2 is a rinse and repeat but in this case differencing is needed and achieved with the below code

atm2_df <- atm2_df %>% 
  mutate(diff_ATM2= difference(ATM2))

ARIMA

Below we again filter out data and identify our best model but include both differenced and non-differenced data.


atm2_train <- atm2_df %>%
  filter(DATE <= "2010-04-01")

#run seasonal related models without the differenced data
atm2_fit_nondiff <- atm2_train %>%
  model(
    SNAIVE = SNAIVE(ATM2),
    ETS = ETS(ATM2),
  )

#run models with differenced data
atm2_fit_diff <- atm2_train %>%
  slice(2:336) %>% 
  model(
    ETS_diff = ETS(diff_ATM2),
    ARIMA = ARIMA(diff_ATM2),
   `Auto ARIMA` = ARIMA(diff_ATM2, stepwise = FALSE, approx = FALSE)
  )

#forecast_ATM2 April
atm2_forecast_nondiff <- atm2_fit_nondiff %>%
  forecast(h = 30)

#forecast_ATM2 April
atm2__forecast_diff <- atm2_fit_diff %>%
  forecast(h = 30)

#plot
atm2_forecast_nondiff %>%
  autoplot(atm2_df, level = NULL)+
  facet_wrap( ~ .model, scales = "free_y") +
  guides(colour = guide_legend(title = "Forecast"))+
  labs(title= "ATM2 Forecasts | April") +
  xlab("Date") +
  ylab("$$$ (In Hundreds)") 


#plot 2
atm2__forecast_diff %>%
  autoplot(atm2_df, level = NULL)+
  facet_wrap( ~ .model, scales = "free_y") +
  guides(colour = guide_legend(title = "Forecast"))+
  labs(title= "ATM2 Forecasts | April") +
  xlab("Date") +
  ylab("Cash")

accuracy(atm2_forecast_nondiff, atm2_df) %>%
  select(.model, RMSE:MAPE)

accuracy(atm2__forecast_diff, atm2_df) %>%
  select(.model, RMSE:MAPE)
NA

Among the reuslts, the non-difference ETS model had the lowest RMSE & MAE, and MPE & MAPE closest to zero, making it the optimal choice.

Forecast

atm2_fit_ets <- atm2_df %>% 
  model(
    ETS = ETS(ATM2))

#generate the values
atm2_forecast_ets <- atm2_fit_ets %>% 
  forecast(h=30)

#plot
atm2_forecast_ets %>% 
  autoplot(atm2_df) +
  labs(title = "ATM2 - ETS Forecast | May 2010",
       y = "$$$ (In Hundreds)")

(atm2_forecast_results <- 
  as.data.frame(atm2_forecast_ets) %>%
    select(DATE, .mean) %>% 
      rename(Date = DATE, Cash = .mean)%>%
        mutate(Cash=round(Cash,2)))

ATM3

ATM3 was ultimately omitted, considering the limited date range and skewed distributions. It can be considered when more data is provided.

ATM4

STL Decomposition

atm4_df <- atm_df %>% 
  select(DATE, ATM4)

atm4_df %>%
  model(
    STL(ATM4 ~ trend(window = 7) +
                   season(window = "periodic"),
    robust = TRUE)) %>%
  components() %>%
  autoplot()

Considering the variance from the time series, I decided to tranform the data before forecasting using box-cox transformation

Box-Cox

Reference

Forecasting Principles and Practice

      lambda <- aus_production |>
        features(Gas, features = guerrero) |>
        pull(lambda_guerrero)
      aus_production |>
        autoplot(box_cox(Gas, lambda)) +
        labs(y = "",
             title = latex2exp::TeX(paste0(
               "Transformed gas production with $\\lambda$ = ",
               round(lambda,2))))
(atm4_lambda <- atm4_df %>%
  features(ATM4, features = guerrero) %>%
  pull(lambda_guerrero))
[1] -0.0737252
atm4_transformed <- BoxCox(atm4_df$ATM4, lambda = atm4_lambda)

# Extract the transformed data

atm4_df$ATM4_T<-atm4_transformed

#plot
atm4_df%>% 
  autoplot(ATM4_T) 

ndiffs(atm4_df$ATM4)
[1] 0
ndiffs(atm4_df$ATM4_T)
[1] 0
atm4_df %>% 
  ACF(ATM4_T, lag_max = 28) %>% 
  autoplot()

Using ndiff() we identify that theres no need for differencing, and the ACF shows

The ACF plot below suggest lags 7 consistently and on 2 other occasions in different periods. Despite the ndiff() function resulting in 0, if believe this does require differencing using the transformed data.

atm4_df <- atm4_df %>% 
  mutate(diff_ATM4= difference(ATM4_T))

ARIMA


atm4_train <- atm4_df %>%
  filter(DATE <= "2010-04-01")

#run seasonal related models without the differenced data
atm4_fit_nondiff <- atm4_train %>%
  model(
    SNAIVE = SNAIVE(ATM4_T),
    ETS = ETS(ATM4_T),
  )

#run models with differenced data
atm4_fit_diff <- atm4_train %>%
  slice(2:336) %>% 
  model(
    ETS_diff = ETS(diff_ATM4),
    ARIMA = ARIMA(diff_ATM4),
   `Auto ARIMA` = ARIMA(diff_ATM4, stepwise = FALSE, approx = FALSE)
  )

#forecast_ATM2 April
atm4_forecast_nondiff <- atm4_fit_nondiff %>%
  forecast(h = 30)

#forecast_ATM2 April
atm4__forecast_diff <- atm4_fit_diff %>%
  forecast(h = 30)

#plot
atm4_forecast_nondiff %>%
  autoplot(atm4_df, level = NULL)+
  facet_wrap( ~ .model, scales = "free_y") +
  guides(colour = guide_legend(title = "Forecast"))+
  labs(title= "ATM4 Forecasts | April") +
  xlab("Date") +
  ylab("$$$ (In Hundreds)") 


#plot 2
atm4__forecast_diff %>%
  autoplot(atm4_df, level = NULL)+
  facet_wrap( ~ .model, scales = "free_y") +
  guides(colour = guide_legend(title = "Forecast"))+
  labs(title= "ATM4 Forecasts | April") +
  xlab("Date") +
  ylab("Cash")

accuracy(atm4_forecast_nondiff, atm4_df) %>%
  select(.model, RMSE:MAPE)

accuracy(atm4__forecast_diff, atm4_df) %>%
  select(.model, RMSE:MAPE)
NA

Of the models, SNAIVE for non differenced data was the most accurate so I will proceed with this.

Forecast

atm4_fit_snaive <- atm4_df %>% 
  model(
    SNAIVE = SNAIVE(ATM4_T))

#generate the values
atm4_forecast_snaive <- atm4_fit_snaive %>% 
  forecast(h=30)

#plot
atm4_forecast_snaive %>% 
  autoplot(atm4_df) +
  labs(title = "ATM2 - SNAIVE Forecast | May 2010",
       y = "$$$ (In Hundreds)")

(atm4_forecast_results <- 
  as.data.frame(atm4_forecast_snaive) %>%
    select(DATE, .mean) %>% 
      rename(Date = DATE, Cash = .mean)%>%
        mutate(Cash=round(Cash,2)))

Part B

EDA & Cleanup

str(power_raw)
tibble [192 × 3] (S3: tbl_df/tbl/data.frame)
 $ CaseSequence: num [1:192] 733 734 735 736 737 738 739 740 741 742 ...
 $ YYYY-MMM    : chr [1:192] "1998-Jan" "1998-Feb" "1998-Mar" "1998-Apr" ...
 $ KWH         : num [1:192] 6862583 5838198 5420658 5010364 4665377 ...
describe(power_raw)
data.frame(power_raw$`YYYY-MMM`[power_raw$KWH %in% NA])

I renamed the YYYY-MMM for preference to DATE. The cleanup is a change of type for DATE, removal of CaseSequence as it does not help our model, and reducing our model to values in the thousands for ease of analysis. Like before we’ll also be indexing by DATE

#change variable type 
power_df <- power_raw %>% 
  mutate(DATE = yearmonth(`YYYY-MMM`), KWH = KWH/1000) %>%
  select(-CaseSequence, -'YYYY-MMM') %>% 
  tsibble(index= DATE)
head(power_df)
ggplot(power_df, aes(x=KWH))+
  geom_histogram(bins=40)+
  labs(title = "Monthly Distributions Residential Power Usage | Jan '98 - Dec '13")


power_df %>%
  autoplot(KWH) +
  labs(title = "Monthly Distributions Residential Power Usage | Jan '98 - Dec '13")

The data has an apparent outlier and is right skewed that appears in both plots, and resides sometime after January of 2010.

# made a copy of the data first
power_df2<-power_df
power_df2$KWH <- na.interp(power_df2$KWH)
power_df2$KWH <- replace(power_df2$KWH, power_df2$KWH == min(power_df2$KWH),
                          median(power_df2$KWH))

Considering the distribution, I again thought it best to replace the missing value with the median, but considering I will be using that method to address the outlier, I decided to use na.interp since its a tool used by the author of our textbook Rob J Hydman’s github repo. Regardless, the transformation below shows its still right skewed but shows seasonality with an upward trend.


ggplot(power_df2, aes(x=KWH))+
  geom_histogram()+
  labs(title = "Monthly Distributions Residential Power Usage | Jan '98 - Dec '13")


#summary
summary(power_df2$KWH)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   4313    5444    6330    6532    7609   10656 
#ts plot
power_df2 %>%
  autoplot(KWH) +
  labs(title = "Monthly Distributions Residential Power Usage | Jan '98 - Dec '13")+
  ylab(label= "KWH (Thousands)")

Before forecasting I will transform the data using a Box-Cox transformation.

#get lambda
(power_lambda <- power_df2 %>%
  features(KWH, features = guerrero) %>%
  pull(lambda_guerrero))
[1] -0.2130548
power_df2 <- power_df2 %>% 
    mutate(KWH_bc = box_cox(KWH, power_lambda))


summary(power_df2$KWH_bc)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  3.905   3.943   3.967   3.967   3.994   4.043 
ggplot(power_df2, aes(x=KWH_bc))+
  geom_histogram()+
  labs(title = "Monthly Distributions Residential Power Usage | Jan '98 - Dec '13")


power_df2 %>% 
  autoplot(KWH_bc) +
  labs(y = "KWH in Thousands",
       title = "Transformed KWH with Lambda = -0.2130548")

STL Decomposition

  • STL decomposition again used to identify seasonality, variance, etc.
  • ndiff() and ACF will identify if differencing is needed.
power_df2 %>%
  model(
    STL(KWH_bc ~ trend(window = 13) +
                   season(window = "periodic"),
                      robust = TRUE)) %>%
                        components() %>%
                            autoplot()

ndiffs(power_df2$KWH_bc)
[1] 1
power_df2 %>% 
  ACF(KWH_bc, lag_max = 36) %>% 
  autoplot()

Differencing is needed.

diff_power <- power_df2 %>% 
  mutate(diff_KWH= difference(KWH), diff_KWH_bc = difference(KWH_bc))
  
  • Differencing created
  • NA and some columns need removal
diff_power<-diff_power%>%
  select(-KWH, -KWH_bc)%>%
                        slice(-1)

ndiffs(diff_power$diff_KWH_bc)
[1] 0

Forecast

#Differenced data for arima

#split
power_train_diff <- diff_power %>% 
  filter(year(DATE) < 2013)

#models
power_fit_diff <- power_train_diff %>% 
    model(
    ARIMA = ARIMA(diff_KWH),
    `Auto ARIMA` = ARIMA(diff_KWH, stepwise = FALSE, approx = FALSE)
  )

#forecast of 2013
power_forecast_diff <- power_fit_diff %>% 
  forecast(h = "1 year")

#plot
power_forecast_diff %>%
  autoplot(diff_power, level = NULL)+
  facet_wrap( ~ .model, scales = "free_y") +
  guides(colour = guide_legend(title = "Forecast"))+
  labs(title= "KWH Forecasts | Jan '13 - Dec '13")+
  xlab("Month") +
  ylab("KWH in Thousands") 

#split
power_train <- power_df2 %>% 
  filter(year(DATE) < 2013)

#models
power_fit <- power_train %>% 
    model(
    ETS = ETS(KWH),
    `Additive ETS` = ETS(KWH ~ error("A") + trend("A") + season("A")),
    SNAIVE = SNAIVE(KWH)
  )

#forecast of 2013
power_forecast <- power_fit %>% 
  forecast(h = "1 year")

#plot
power_forecast %>%
  autoplot(power_df2, level = NULL)+
  facet_wrap( ~ .model, scales = "free_y") +
  guides(colour = guide_legend(title = "Forecast"))+
  labs(title= "KWH Forecasts | Jan '13 - Dec '13")+
  xlab("Month") +
  ylab("KWH in Thousands") 

#find ARIMA RMSE, MAE
accuracy(power_forecast_diff, diff_power) %>%
  select(.model, RMSE:MAE)

#find other RMSE, MAE
accuracy(power_forecast, power_df2) %>%
  select(.model, RMSE:MAE)

Additive ETS is the best model based on RMSE and MAE

#reproduce the mode using the original dataset 
power_ETS_fit <- power_df2 %>% 
  model(`Additive ETS` = ETS(KWH ~ error("A") + trend("A") + season("A")))

#generate the values
power_ETS_forecast <- power_ETS_fit %>% 
  forecast(h=12)

#plot
power_ETS_forecast %>% 
  autoplot(power_df2) +
  labs(title = "Monthly Residential Power Usage (Additive ETS |2024)",
       y = "KWH in Thousands")

NA
(power_forecast_results <- 
  as.data.frame(power_ETS_forecast) %>%
    select(DATE, .mean) %>% 
      rename('KWH Forecast' = .mean))
LS0tDQp0aXRsZTogJ0RBVEEgNjI0OiBQUkVESUNUSVZFIEFOQUxZVElDUyBQcm9qZWN0IDEnDQphdXRob3I6ICJHYWJyaWVsIENhbXBvcyINCmRhdGU6ICJMYXN0IGVkaXRlZCBgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVCICVkLCAlWScpYCINCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdA0KICBnZW9tZXRyeTogbGVmdD0wLjVjbSxyaWdodD0wLjVjbSx0b3A9MWNtLGJvdHRvbT0yY20NCiAgaHRtbF9kb2N1bWVudDoNCiAgICBkZl9wcmludDogcGFnZWQNCiAgcGRmX2RvY3VtZW50Og0KICAgIGxhdGV4X2VuZ2luZTogeGVsYXRleA0KdXJsY29sb3I6IGJsdWUNCi0tLQ0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCmxpYnJhcnkoZnBwMykNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KHJlYWR4bCkNCmxpYnJhcnkodHNpYmJsZSkNCmxpYnJhcnkocHN5Y2gpDQpsaWJyYXJ5KHRpZHlyKQ0KbGlicmFyeShmb3JlY2FzdCkNCmBgYA0KDQojIERlc2NyaXB0aW9uDQogDQpUaGlzIHByb2plY3QgY29uc2lzdHMgb2YgMyBwYXJ0cyAtIHR3byByZXF1aXJlZCBhbmQgb25lIGJvbnVzIGFuZCBpcyB3b3J0aCAxNSUgb2YgeW91ciBncmFkZS4gIFRoZSBwcm9qZWN0IGlzIGR1ZSBhdCAxMTo1OSBQTSBvbiBTdW5kYXkgQXByIDExLiAgSSB3aWxsIGFjY2VwdCBsYXRlIHN1Ym1pc3Npb25zIHdpdGggYSBwZW5hbHR5IHVudGlsIHRoZSBtZWV0dXAgYWZ0ZXIgdGhhdCB3aGVuIHdlIHJldmlldyBzb21lIHByb2plY3RzLg0KDQojIyBQYXJ0IEEgDQoNCioqQVRNIEZvcmVjYXN0KioNCltBVE02MjREYXRhLnhsc3hdKGh0dHBzOi8vYmJob3N0ZWQuY3VueS5lZHUvYmJjc3dlYmRhdi9waWQtODE2MzA5NDYtZHQtY29udGVudC1yaWQtNjM2MDEyMzk5XzEveGlkLTYzNjAxMjM5OV8xKQ0KIA0KSW4gcGFydCBBLCBJIHdhbnQgeW91IHRvIGZvcmVjYXN0IGhvdyBtdWNoIGNhc2ggaXMgdGFrZW4gb3V0IG9mIDQgZGlmZmVyZW50IEFUTSBtYWNoaW5lcyBmb3IgTWF5IDIwMTAuICBUaGUgZGF0YSBpcyBnaXZlbiBpbiBhIHNpbmdsZSBmaWxlLiAgVGhlIHZhcmlhYmxlIOKAmENhc2jigJkgaXMgcHJvdmlkZWQgaW4gaHVuZHJlZHMgb2YgZG9sbGFycywgb3RoZXIgdGhhbiB0aGF0IGl0IGlzIHN0cmFpZ2h0IGZvcndhcmQuICAgSSBhbSBiZWluZyBzb21ld2hhdCBhbWJpZ3VvdXMgb24gcHVycG9zZSB0byBtYWtlIHRoaXMgaGF2ZSBhIGxpdHRsZSBtb3JlIGJ1c2luZXNzIGZlZWxpbmcuICBFeHBsYWluIGFuZCBkZW1vbnN0cmF0ZSB5b3VyIHByb2Nlc3MsIHRlY2huaXF1ZXMgdXNlZCBhbmQgbm90IHVzZWQsIGFuZCB5b3VyIGFjdHVhbCBmb3JlY2FzdC4gIEkgYW0gZ2l2aW5nIHlvdSBkYXRhIHZpYSBhbiBleGNlbCBmaWxlLCBwbGVhc2UgcHJvdmlkZSB5b3VyIHdyaXR0ZW4gcmVwb3J0IG9uIHlvdXIgZmluZGluZ3MsIHZpc3VhbHMsIGRpc2N1c3Npb24gYW5kIHlvdXIgUiBjb2RlIHZpYSBhbiBSUHVicyBsaW5rIGFsb25nIHdpdGggdGhlIGFjdHVhbC5ybWQgZmlsZSAgQWxzbyBwbGVhc2Ugc3VibWl0IHRoZSBmb3JlY2FzdCB3aGljaCB5b3Ugd2lsbCBwdXQgaW4gYW4gRXhjZWwgcmVhZGFibGUgZmlsZS4NCiANCiMjIFBhcnQgQg0KDQpGb3JlY2FzdGluZyBQb3dlcg0KW1Jlc2lkZW50aWFsQ3VzdG9tZXJGb3JlY2FzdExvYWQtNjI0Lnhsc3hdKGh0dHBzOi8vYmJob3N0ZWQuY3VueS5lZHUvYmJjc3dlYmRhdi9waWQtODE2MzA5NDctZHQtY29udGVudC1yaWQtNjM2MDE1MjA3XzEveGlkLTYzNjAxNTIwN18xKQ0KIA0KUGFydCBCIGNvbnNpc3RzIG9mIGEgc2ltcGxlIGRhdGFzZXQgb2YgcmVzaWRlbnRpYWwgcG93ZXIgdXNhZ2UgZm9yIEphbnVhcnkgMTk5OCB1bnRpbCBEZWNlbWJlciAyMDEzLiAgWW91ciBhc3NpZ25tZW50IGlzIHRvIG1vZGVsIHRoZXNlIGRhdGEgYW5kIGEgbW9udGhseSBmb3JlY2FzdCBmb3IgMjAxNC4gIFRoZSBkYXRhIGlzIGdpdmVuIGluIGEgc2luZ2xlIGZpbGUuICBUaGUgdmFyaWFibGUg4oCYS1dI4oCZIGlzIHBvd2VyIGNvbnN1bXB0aW9uIGluIEtpbG93YXR0IGhvdXJzLCB0aGUgcmVzdCBpcyBzdHJhaWdodCBmb3J3YXJkLiAgICBBZGQgdGhpcyB0byB5b3VyIGV4aXN0aW5nIGZpbGVzIGFib3ZlLiANCiANCiANCiMjIFBhcnQgQyANCg0KQk9OVVMsIG9wdGlvbmFsIChwYXJ0IG9yIGFsbCksIFtXYXRlcmZsb3dfUGlwZTEueGxzeF0oaHR0cHM6Ly9iYmhvc3RlZC5jdW55LmVkdS9iYmNzd2ViZGF2L3BpZC04MTYzMDk0OC1kdC1jb250ZW50LXJpZC02MzYwMTUyMTNfMS94aWQtNjM2MDE1MjEzXzEpIGFuZCBbV2F0ZXJmbG93X1BpcGUyLnhsc3hdKGh0dHBzOi8vYmJob3N0ZWQuY3VueS5lZHUvYmJjc3dlYmRhdi9waWQtODE2MzA5NDktZHQtY29udGVudC1yaWQtNjM2MDE1MjE0XzEveGlkLTYzNjAxNTIxNF8xKQ0KIA0KUGFydCBDIGNvbnNpc3RzIG9mIHR3byBkYXRhIHNldHMuICBUaGVzZSBhcmUgc2ltcGxlIDIgY29sdW1ucyBzZXRzLCBob3dldmVyIHRoZXkgaGF2ZSBkaWZmZXJlbnQgdGltZSBzdGFtcHMuICBZb3VyIG9wdGlvbmFsIGFzc2lnbm1lbnQgaXMgdG8gdGltZS1iYXNlIHNlcXVlbmNlIHRoZSBkYXRhIGFuZCBhZ2dyZWdhdGUgYmFzZWQgb24gaG91ciAoZXhhbXBsZSBvZiB3aGF0IHRoaXMgbG9va3MgbGlrZSwgZm9sbG93cykuICBOb3RlIGZvciBtdWx0aXBsZSByZWNvcmRpbmdzIHdpdGhpbiBhbiBob3VyLCB0YWtlIHRoZSBtZWFuLiAgVGhlbiB0byBkZXRlcm1pbmUgaWYgdGhlIGRhdGEgaXMgc3RhdGlvbmFyeSBhbmQgY2FuIGl0IGJlIGZvcmVjYXN0LiAgSWYgc28sIHByb3ZpZGUgYSB3ZWVrIGZvcndhcmQgZm9yZWNhc3QgYW5kIHByZXNlbnQgcmVzdWx0cyB2aWEgUnB1YnMgYW5kIC5ybWQgYW5kIHRoZSBmb3JlY2FzdCBpbiBhbiBFeGNlbCByZWFkYWJsZSBmaWxlLg0KDQojIyBEYXRhIExvYWQNCg0KaHR0cHM6Ly9naXRodWIuY29tL0dpdGFibGVHYWJlL0RhdGE2MjRfRGF0YS9yYXcvbWFpbi9BVE02MjREYXRhLnhsc3gNCg0KYGBge3IsIHdhcm5pbmc9RkFMU0V9DQphdG1fY29sdHlwZTwtYygiZGF0ZSIsInRleHQiLCJudW1lcmljIikNCg0KYXRtX2ltcG9ydDwtcmVhZF94bHN4KCdBVE02MjREYXRhLnhsc3gnLCBjb2xfdHlwZXMgPSBhdG1fY29sdHlwZSkNCiMgT21taXR0aW5nIEV4dHJhIENyZWRpdCBhcyBJIHdvbid0IGJlIHdvcmtpbmcgb24gaXQNCiMgV1AxX2RmPC1yZWFkX3hsc3goJ1dhdGVyZmxvd19QaXBlMS54bHN4JykNCiMgV1AyX2RmPC1yZWFkX3hsc3goJ1dhdGVyZmxvd19QaXBlMi54bHN4JykNCmBgYA0KDQoNCmBgYHtyfQ0KcG93ZXJfcmF3PC1yZWFkX3hsc3goJ1Jlc2lkZW50aWFsQ3VzdG9tZXJGb3JlY2FzdExvYWQtNjI0Lnhsc3gnKQ0KYGBgDQoNCiMgUGFydCBBDQoNCiMjIEVEQSAmIENsZWFudXANCg0KDQpgYGB7cn0NCmhlYWQoYXRtX2ltcG9ydCU+JQ0KICAgICAgIGZpbHRlcihBVE09PSJBVE00IikpDQpgYGANCg0KYGBge3J9DQphdG1fcmFuZ2U8LXJhbmdlKGF0bV9pbXBvcnQkREFURSkNCmF0bV9yYW5nZVsxXQ0KYXRtX3JhbmdlWzJdDQpgYGANCg0KYGBge3J9DQpzYXBwbHkoYXRtX2ltcG9ydCwgZnVuY3Rpb24oeCkgc3VtKGlzLm5hKHgpKSkNCmBgYA0KDQpgYGB7cn0NCmRhdGEuZnJhbWUoYXRtX2ltcG9ydCREQVRFW2F0bV9pbXBvcnQkQ2FzaCAlaW4lIE5BXSkNCmBgYA0KDQoqIEFUTTYyNERhdGEgaGFkIGF0dHJpYnV0ZSB0eXBlIG1pc21hdGNoZXMsIGFuZCB3YXMgY29udmVydGVkIG9uIGltcG9ydC4NCiogRGF0ZSBjb252ZXJzaW9uIHNvbWVob3cga2VwdCBkYXRlIHRpbWUgYXMgUE9TSVhjdA0KKiBBVE00IHNob3dzIHZhbHVlcyBpbiBncmVhdGVyIGRlY2ltYWxzIGFueSBjb3VudHJ5LCB3aXRoIERpbmFycyBiZWluZyB0aGUgb25seSBDb3VudHJ5IHRoYXQgdXNlcyBtb3JlIHRoYW4gMiBkZWNpbWFscyB3aGVuIHVzaW5nIGl0cyBjdXJyZW5jeSwgYnV0IGV2ZW4gdGhlIGRpbmFyIHN0b3BzIGF0IHRoZSAxMDB0aCBkZWNpbWFsLg0KKiBEYXRlIHJhbmdlIGlzIDA1LTAxLTIwMDkgdG8gMDUtMTQtMjAxMA0KKiB3ZSBzZWUgdGhlIGNvdW50IG9mIE5BcyBpbiBBVE0gaXMgMTQgYW5kIENhc2ggY29sdW1uIGlzIDE5DQoqIFRoZSBOQSBkYXRlcyB2YXJ5IGFuZCBhcmUgbm90IGV4Y2x1c2l2ZSB0byBhIHNwZWNpZmljIHNlcXVlbnRpYWwgdGltZSBwZXJpb2QgdGhhdCB3ZSBjYW4ganVzdCBmaWx0ZXIgb3V0Lg0KKiBJIGFtIGN1cmlvdXMgYWJvdXQgdGhlIGRpc3RyaWJ1dGlvbiBvZiBjYXNoIGNvbnNpZGVyaW5nIHRoZSBmb3JlY2FzdCBhc2sgZm9yIHRoaXMgcHJvamVjdC4NCg0KDQoNCg0KYGBge3J9DQphdG1faW1wb3J0ICU+JSANCiAgZmlsdGVyKERBVEUgPCAiMjAxMC0wNS0wMSIsICFpcy5uYShBVE0pKSAlPiUgDQogIGdncGxvdChhZXMoeCA9IENhc2gpKSArDQogICAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDMwLCBjb2xvcj0gImJsdWUiKSArDQogICAgZmFjZXRfd3JhcCh+IEFUTSwgbmNvbCA9IDIsIHNjYWxlcyA9ICJmcmVlIikNCmBgYA0KDQoNCmBgYHtyfQ0KKGF0bV9kZiA8LSBhdG1faW1wb3J0ICU+JSANCiAgbXV0YXRlKERBVEUgPSBhcy5EYXRlKERBVEUpKSAlPiUNCiAgIGZpbHRlcihEQVRFPCIyMDEwLTA1LTAxIiklPiUNCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbT1BVE0sIHZhbHVlc19mcm9tID0gQ2FzaCkpDQpgYGANCg0KYGBge3J9DQphdG1fZGY8LWF0bV9kZiU+JQ0KICBhc190c2liYmxlKGluZGV4PURBVEUpDQpoZWFkKGF0bV9kZikNCmBgYA0KDQpgYGB7cn0NCnN1bW1hcnkoYXRtX2RmKQ0KYGBgDQoNCg0KYGBge3J9DQphdG1fZGZbIWNvbXBsZXRlLmNhc2VzKGF0bV9kZiksIF0NCmBgYA0KDQoNCg0KDQoNCg0KYGBge3J9DQphdG1fZGYlPiUNCiAgc2VsZWN0KERBVEUsQVRNMyklPiUNCiAgZmlsdGVyKEFUTTM+MCkNCmBgYA0KDQoqIENvbnZlcnRpbmcgYERBVEVgIGludG8gYSBkYXRlIHZhbHVlIG1hZGUgc2Vuc2VzIHR5cGUgUE9TSVhjdCBtYXkgY2F1c2UgZnV0dXJlIGlzc3Vlcy4NCiogUGl2b3RpbmcgYWxsb3dlZCB1cyB0byBzZXBhcmF0ZSB0aGUgQVRNJ3MgY2F0ZWdvcmljYWxseSBhbmQgaXNvbGF0ZSB0aGUgTkFzIGZvciByZW1vdmFsLg0KKiBXZSBhcmUgYWJsZSB0byBzZWUgdGhhdCBmaXZlIGVudHJpZXMgY29udGFpbiBOQXMgYW5kIHRoZSBkYXRlcyBhbGwgcmVzaWRlIGluIEp1bmUNCiogQVRNMyBvbmx5IGhhcyAzIGRhdGVzIHdpdGggd2l0aGRyYXdhbHMgNC0yOCB0aHJvdWdoIDQtMzAgb3IgMjAxMCwgYW5kIHRoZSBkaXN0cmlidXRpb24gcGxvdCBpcyBhcmd1YWJseSBhIHJlYXNvbiB0byBvbWl0IHRoaXMgY29sdW1uDQoqIFRoZXNlIHJlc3VsdHMgYWxzbyBicmluZ3MgdG8gcXVlc3Rpb24gd2hldGhlciB0aGVyZSBtYXkgYmUgc29tZSBzZWFzb25hbGl0eSB0aGF0IHdpbGwgaW1wYWN0IE1heSdzIGZvcmVjYXN0aW5nDQoqIENvbnNpZGVyaW5nIHRoZSBkaXN0cmlidXRpb24sIEkgY2hvc2UgdG8gcmVwbGFjZSB0aGUgbWlzc2luZyB2YWx1ZXMgd2l0aCB0aGUgbWVkaWFuLCBhcyB0aGUgc2tld2VkIHZhbHVlcyBpbiBBVE0gMyAmIDQgSSBiZWxpZXZlIHdpdGggbmVnYXRpdmVseSBpbXBhY3QgdGhlIG1lYW4NCg0KYGBge3J9DQoNCiMgc2Vhc29uYWxpdHkNCmF0bV9pbXBvcnQgJT4lIA0KICBmaWx0ZXIoREFURSA8ICIyMDEwLTA1LTAxIiwgIWlzLm5hKEFUTSkpICU+JSANCiAgZ2dwbG90KGFlcyh4ID0gREFURSwgeSA9IENhc2gsIGNvbCA9IEFUTSkpICsNCiAgICBnZW9tX2xpbmUoY29sb3I9ImJsdWUiKSArDQogICAgZmFjZXRfd3JhcCh+IEFUTSwgbmNvbCA9IDIsIHNjYWxlcyA9ICJmcmVlX3kiKSsNCiAgbGFicyh0aXRsZSA9ICJTZWFzb25hbGl0eSBQbG90IiwgeCA9ICJEYXRlIiwgeSA9ICJDYXNoIikgKw0KICAgIHRoZW1lX21pbmltYWwoKQ0KICAgIA0KYGBgDQoNCmBgYHtyfQ0KDQptZWRpYW5fdmFsdWUgPC0gbWVkaWFuKGF0bV9kZltbIkFUTTEiXV0sIG5hLnJtID0gVFJVRSkNCmF0bV9kZltbIkFUTTEiXV1baXMubmEoYXRtX2RmW1siQVRNMSJdXSldIDwtIG1lZGlhbl92YWx1ZQ0KbWVkaWFuX3ZhbHVlIDwtIG1lZGlhbihhdG1fZGZbWyJBVE0yIl1dLCBuYS5ybSA9IFRSVUUpDQphdG1fZGZbWyJBVE0yIl1dW2lzLm5hKGF0bV9kZltbIkFUTTIiXV0pXSA8LSBtZWRpYW5fdmFsdWUNCg0KYGBgDQoNCg0KYGBge3J9DQphdG1fZGZbIWNvbXBsZXRlLmNhc2VzKGF0bV9kZiksIF0NCmBgYA0KIyMgRm9yZWNhc3RzDQoNCiMjIyBBVE0xDQoNCiMjIyMgU1RMIERlY29tcG9zaXRpb24NCg0KVGhlIHNlYXNvbmFsaXR5IHBsb3QgZGlkIG5vdCBzaG93IGEgdHJlbmQgaW4gdGhlIGxvbmcgdGVybSBidXQgYSBiZXR0ZXIgYXNzZXNzbWVudCBpbiB3ZWVrbHkgaW50ZXJ2YWwgaXMgbGlrZWx5IG5lZWRlZCwgdXNpbmcgcmVzb3VyY2VzIGZyb20gW1JvYiBKIEh5bmRtYW4gYW5kIEdlb3JnZSBBdGhhbmFzb3BvdWxvcywgRm9yZWNhc3Rpbmc6IFByaW5jaXBsZXMgYW5kIFByYWN0aWNlICgzcmQgZWQpIHNlY3Rpb24gMy42IFNUTCBkZWNvbXBvc2l0aW9uXShodHRwczovL290ZXh0cy5jb20vZnBwMy9zdGwuaHRtbCkgSSB3aWxsIHBlcmZvcm0gYSBTVEwgIlNlYXNvbmFsIGFuZCBUcmVuZCBkZWNvbXBvc2l0aW9uIHVzaW5nIExvZXNzIiBkZWNvbXBvc2l0aW9uIG9mIHRoZSBzZXJpZXMuIFRvIG1ha2UgaXQgd2Vla2x5IEknbGwgc2V0IHRoZSBwYXJhbWV0ZXIgYHRyZW5kKHdpbmRvdyA9IDcpYCBhbmQgdGhlIGBzZWFzb24od2luZG93PSdwZXJpb2RpYycpYCB0byBpbXBvc2Ugc2Vhc29uYWxpdHkgZWxlbWVudCBhY3Jvc3MgZGF5cyBvZiB0aGUgd2Vlay4NCg0KTXkgcmVmZXJlbmNlIGNvbWUgZGlyZWN0bHkgZnJvbSB0aGUgY2hhcHRlci4NCg0KICAgICAgICAgICAgICB1c19yZXRhaWxfZW1wbG95bWVudCB8Pg0KICAgICAgICAgICAgICAgIG1vZGVsKA0KICAgICAgICAgICAgICAgICAgU1RMKEVtcGxveWVkIH4gdHJlbmQod2luZG93ID0gNykgKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2Vhc29uKHdpbmRvdyA9ICJwZXJpb2RpYyIpLA0KICAgICAgICAgICAgICAgICAgcm9idXN0ID0gVFJVRSkpIHw+DQogICAgICAgICAgICAgICAgY29tcG9uZW50cygpIHw+DQogICAgICAgICAgICAgICAgYXV0b3Bsb3QoKQ0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KYXRtMV9kZiA8LSBhdG1fZGYgJT4lIA0KICBkcGx5cjo6c2VsZWN0KERBVEUsIEFUTTEpDQoNCmF0bTFfZGYgJT4lDQogIG1vZGVsKA0KICAgIFNUTChBVE0xIH4gdHJlbmQod2luZG93ID0gNykgKw0KICAgICAgICAgICAgICAgICAgIHNlYXNvbih3aW5kb3cgPSAicGVyaW9kaWMiKSwNCiAgICByb2J1c3QgPSBUUlVFKSkgJT4lDQogIGNvbXBvbmVudHMoKSAlPiUNCiAgYXV0b3Bsb3QoKQ0KYGBgDQoNCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCm5kaWZmcyhhdG0xX2RmJEFUTTEpDQpgYGANCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmF0bTFfZGYgJT4lIA0KICBBQ0YoQVRNMSwgbGFnX21heCA9IDMwKSAlPiUgDQogIGF1dG9wbG90KCkNCmBgYA0KDQpUaGUgU1RMIGRlY29tcG9zaXRpb24gd2Fzbid0IGFzIHRlbGxpbmcgYXMgSSB3b3VsZCBoYXZlIGxpa2VkLCBob3dldmVyIHRoZSBBQ0YgcGxvdCBwcmVzZW50cyBsYWdzIGF0IDIsIDUsIGFuZCA3LiBJIGJlbGlldmUsIGdpdmVuIHRoZSB3ZWVrIHN0YXJ0cyBvbiBTdW5kYXksIHRoYXQgdGhpcyByZXByZXNlbnRzIE1vbmRheSwgVGh1cnNkYXkgYW5kIFNhdHVyZGF5IGFzIHRoZSBkYXlzIHdpdGggdGhlIG1vc3QgbGFnLiA3IGhhcyBzaG93biB0aGUgdmFsdWUgd2l0aCB0aGUgbW9zdCBzaWduaWZpY2FudCBsYWcuIFRoZXJlIGlzIGEgZGVjcmVhc2luZyB0cmVuZCB3aXRoIHRoZSBBQ0YgcGxvdCwgYW5kIHN1cHBvcnRzIHRoYXQgdGhlIGRhdGEgaXMgbm9uLXN0YXRpb25hcnkgd291bGQgcmVxdWlyZSBkaWZmZXJlbmNpbmcgaG93ZXZlciAkcl8gMSdzJCBzbWFsbCB2YWx1ZSBhbmQgdGhlIHJlc3VsdHMgb2YgdGhlIGBuZGlmZigpYCBmdW5jdGlvbiwgc2hvd2luZyB0aGUgZmlyc3QgbnVtYmVyIG9mIGRpZmZlcmVuY2VzIGFzIDAsIG5lZ2F0ZXMgdGhhdCBzdXNwaWNpb24uDQoNCiMjIyMgQVJJTUENCg0KU2Vhc29uYWwgbmFpdmUgbWV0aG9kIHdhcyBteSBwcmVmZXJyZWQgY2hvaWNlIGNvbnNpZGVyaW5nIHRoZSBzZWFzb25hbGl0eSwgYW5kIHNvIHdlIGNhbiB1c2UgdGhlIHByaW9yIHRpbWUgcGVyaW9k4oCZcyB3aXRoZHJhd2FscyB0byBjb25kdWN0IG91ciBmb3JlY2FzdCwgYnV0IEkgYWxzbyBsaWtlIHRvIGRlZmF1bHQgdG8gYEF1dG8gQVJJTUFgIGZvciB0aGUgb3B0aW1pemVkIHNlbGVjdGlvbi4gSSBhc3N1bWUgRVRTIGFuZCBBUklNQSB3b250IHBlcmZvcm0gYXMgd2VsbCBidXQgd2lsbCBhd2FpdCBmb3IgdGhlIGNvbXBhcmlzb25zLiBCZWxvdyB3ZSBmaWx0ZXIgb3V0IHRoZSBkYXRhIHJlc2lkaW5nIGluIE1heSwgdGhlIG1vbnRoIHdlIGFyZSBmb3JlY2FzdGluZy4NCg0KYGBge3IgZmlnLmhlaWdodD0xMCwgZmlnLndpZHRoPTE1LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KIyB0cmFpbg0KYXRtMV90cmFpbiA8LSBhdG0xX2RmICU+JQ0KICBmaWx0ZXIoREFURSA8PSAiMjAxMC0wNC0wMSIpDQoNCg0KYXRtMV9maXQgPC0gYXRtMV90cmFpbiAlPiUNCiAgbW9kZWwoDQogICAgU05BSVZFID0gU05BSVZFKEFUTTEpLA0KICAgIEVUUyA9IEVUUyhBVE0xKSwNCiAgICBBUklNQSA9IEFSSU1BKEFUTTEpLA0KICAgIGBBdXRvIEFSSU1BYCA9IEFSSU1BKEFUTTEsIHN0ZXB3aXNlID0gRkFMU0UsIGFwcHJveCA9IEZBTFNFKQ0KICApDQoNCiMgZm9yZWNhc3QgQXByaWwNCmF0bTFfZm9yZWNhc3QgPC0gYXRtMV9maXQgJT4lDQogIGZvcmVjYXN0KGggPSAzMCkNCg0KI3Bsb3QNCmF0bTFfZm9yZWNhc3QgJT4lDQogIGF1dG9wbG90KGF0bTFfZGYsIGxldmVsID0gTlVMTCkrDQogIGZhY2V0X3dyYXAoIH4gLm1vZGVsLCBzY2FsZXMgPSAiZnJlZV95IikgKw0KICBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKHRpdGxlID0gIkZvcmVjYXN0IikpKw0KICBsYWJzKHRpdGxlPSAiQVRNMSBGb3JlY2FzdHMgfCBBcHJpbCIpICsNCiAgeGxhYigiRGF0ZSIpICsNCiAgeWxhYigiJCQkIChJbiBIdW5kcmVkcykiKSANCmBgYA0KDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojIFJNU0UNCmFjY3VyYWN5KGF0bTFfZm9yZWNhc3QsIGF0bTFfZGYpICU+JQ0KICBzZWxlY3QoLm1vZGVsLCBSTVNFOk1BUEUpDQpgYGANCg0KDQpXaGVuIGludGVycHJldGluZyB0aGUgcmVzdWx0cywgdGhlIG1vZGVsIHdpdGggdGhlIGxvd2VzdCBSTVNFIGFuZCBNQUUgdmFsdWUgYW5kIHRoZSBNUEUgYW5kIE1BUEUgdmFsdWVzIGNsb3NlcyB0byB6ZXJvIHRoZSBiZXN0IHBlcmZvcm1pbmcuIFRoaXMgaXMgdHJ1ZSBpbiBhbGwgY2FzZXMgZm9yIEVUUyBpbmRpY2F0aW5nIGl0IGlzIHRoZSBiZXN0IHBlcmZvcm1pbmcuDQoNCiMjIyMgRm9yZWNhc3QNCg0KKiogUmVmZXJlbmNlKioNCg0KICAgICAgICAgICAgICAgICAgICAgICAgICBhdXNfZWNvbm9teSB8Pg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsKEVUUyhQb3B1bGF0aW9uKSkgfD4NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb3JlY2FzdChoID0gIjUgeWVhcnMiKSB8Pg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF1dG9wbG90KGF1c19lY29ub215IHw+IGZpbHRlcihZZWFyID49IDIwMDApKSArDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFicyh0aXRsZSA9ICJBdXN0cmFsaWFuIHBvcHVsYXRpb24iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9ICJQZW9wbGUgKG1pbGxpb25zKSIpDQoNCg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KIyByZW1hZGUgdGhlIG1vZGVsIGZyb20gc291cmNlDQphdG0xX2ZpdF9ldHMgPC0gYXRtMV9kZiAlPiUgDQogIG1vZGVsKEVUUyA9IEVUUyhBVE0xKSkNCg0KYXRtMV9mb3JlY2FzdF9ldHMgPC0gYXRtMV9maXRfZXRzICU+JSANCiAgZm9yZWNhc3QoaD0zMCkNCg0KYXRtMV9mb3JlY2FzdF9ldHMgJT4lIA0KICBhdXRvcGxvdChhdG0xX2RmKSArDQogIGxhYnModGl0bGUgPSAiQVRNMSBGb3JlY2FzdCAoRVRTKSB8IE1heSIsDQogICAgICAgeSA9ICIkJCQgKGluIEh1bmRyZWRzKSIpDQpgYGANCg0KYGBge3J9DQooYXRtMV9mb3JlY2FzdF9yZXN1bHRzIDwtIA0KICBhcy5kYXRhLmZyYW1lKGF0bTFfZm9yZWNhc3RfZXRzKSAlPiUNCiAgICBzZWxlY3QoREFURSwgLm1lYW4pICU+JSANCiAgICAgIHJlbmFtZShEYXRlID0gREFURSwgQ2FzaCA9IC5tZWFuKSU+JQ0KICAgICAgICBtdXRhdGUoQ2FzaD1yb3VuZChDYXNoLDIpKSkNCmBgYA0KDQojIyMgQVRNMg0KDQojIyMjIFNUTCBEZWNvbXBvc2l0aW9uDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQphdG0yX2RmIDwtIGF0bV9kZiAlPiUgDQogIGRwbHlyOjpzZWxlY3QoREFURSwgQVRNMikNCg0KYXRtMl9kZiAlPiUNCiAgbW9kZWwoDQogICAgU1RMKEFUTTIgfiB0cmVuZCh3aW5kb3cgPSA3KSArDQogICAgICAgICAgICAgICAgICAgc2Vhc29uKHdpbmRvdyA9ICJwZXJpb2RpYyIpLA0KICAgIHJvYnVzdCA9IFRSVUUpKSAlPiUNCiAgY29tcG9uZW50cygpICU+JQ0KICBhdXRvcGxvdCgpDQpgYGANCg0KYGBge3J9DQpuZGlmZnMoYXRtMl9kZiRBVE0yKQ0KYGBgDQoNCmBgYHtyfQ0KdW5pdHJvb3RfbmRpZmZzKGF0bTJfZGYkQVRNMikNCmBgYA0KDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQphdG0yX2RmICU+JSANCiAgQUNGKEFUTTIsIGxhZ19tYXggPSAzMCkgJT4lIA0KICBhdXRvcGxvdCgpDQpgYGANCg0KVGhlIGFwcHJvYWNoIHdpdGggQVRNMiBpcyBhIHJpbnNlIGFuZCByZXBlYXQgYnV0IGluIHRoaXMgY2FzZSBkaWZmZXJlbmNpbmcgaXMgbmVlZGVkIGFuZCBhY2hpZXZlZCB3aXRoIHRoZSBiZWxvdyBjb2RlDQoNCmBgYHtyfQ0KYXRtMl9kZiA8LSBhdG0yX2RmICU+JSANCiAgbXV0YXRlKGRpZmZfQVRNMj0gZGlmZmVyZW5jZShBVE0yKSkNCmBgYA0KDQojIyMjIEFSSU1BDQoNCkJlbG93IHdlIGFnYWluIGZpbHRlciBvdXQgZGF0YSBhbmQgaWRlbnRpZnkgb3VyIGJlc3QgbW9kZWwgYnV0IGluY2x1ZGUgYm90aCBkaWZmZXJlbmNlZCBhbmQgbm9uLWRpZmZlcmVuY2VkIGRhdGEuDQoNCmBgYHtyIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTE1LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KDQphdG0yX3RyYWluIDwtIGF0bTJfZGYgJT4lDQogIGZpbHRlcihEQVRFIDw9ICIyMDEwLTA0LTAxIikNCg0KI3J1biBzZWFzb25hbCByZWxhdGVkIG1vZGVscyB3aXRob3V0IHRoZSBkaWZmZXJlbmNlZCBkYXRhDQphdG0yX2ZpdF9ub25kaWZmIDwtIGF0bTJfdHJhaW4gJT4lDQogIG1vZGVsKA0KICAgIFNOQUlWRSA9IFNOQUlWRShBVE0yKSwNCiAgICBFVFMgPSBFVFMoQVRNMiksDQogICkNCg0KI3J1biBtb2RlbHMgd2l0aCBkaWZmZXJlbmNlZCBkYXRhDQphdG0yX2ZpdF9kaWZmIDwtIGF0bTJfdHJhaW4gJT4lDQogIHNsaWNlKDI6MzM2KSAlPiUgDQogIG1vZGVsKA0KICAgIEVUU19kaWZmID0gRVRTKGRpZmZfQVRNMiksDQogICAgQVJJTUEgPSBBUklNQShkaWZmX0FUTTIpLA0KICAgYEF1dG8gQVJJTUFgID0gQVJJTUEoZGlmZl9BVE0yLCBzdGVwd2lzZSA9IEZBTFNFLCBhcHByb3ggPSBGQUxTRSkNCiAgKQ0KDQojZm9yZWNhc3RfQVRNMiBBcHJpbA0KYXRtMl9mb3JlY2FzdF9ub25kaWZmIDwtIGF0bTJfZml0X25vbmRpZmYgJT4lDQogIGZvcmVjYXN0KGggPSAzMCkNCg0KI2ZvcmVjYXN0X0FUTTIgQXByaWwNCmF0bTJfX2ZvcmVjYXN0X2RpZmYgPC0gYXRtMl9maXRfZGlmZiAlPiUNCiAgZm9yZWNhc3QoaCA9IDMwKQ0KDQojcGxvdA0KYXRtMl9mb3JlY2FzdF9ub25kaWZmICU+JQ0KICBhdXRvcGxvdChhdG0yX2RmLCBsZXZlbCA9IE5VTEwpKw0KICBmYWNldF93cmFwKCB+IC5tb2RlbCwgc2NhbGVzID0gImZyZWVfeSIpICsNCiAgZ3VpZGVzKGNvbG91ciA9IGd1aWRlX2xlZ2VuZCh0aXRsZSA9ICJGb3JlY2FzdCIpKSsNCiAgbGFicyh0aXRsZT0gIkFUTTIgRm9yZWNhc3RzIHwgQXByaWwiKSArDQogIHhsYWIoIkRhdGUiKSArDQogIHlsYWIoIiQkJCAoSW4gSHVuZHJlZHMpIikgDQoNCiNwbG90IDINCmF0bTJfX2ZvcmVjYXN0X2RpZmYgJT4lDQogIGF1dG9wbG90KGF0bTJfZGYsIGxldmVsID0gTlVMTCkrDQogIGZhY2V0X3dyYXAoIH4gLm1vZGVsLCBzY2FsZXMgPSAiZnJlZV95IikgKw0KICBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKHRpdGxlID0gIkZvcmVjYXN0IikpKw0KICBsYWJzKHRpdGxlPSAiQVRNMiBGb3JlY2FzdHMgfCBBcHJpbCIpICsNCiAgeGxhYigiRGF0ZSIpICsNCiAgeWxhYigiQ2FzaCIpDQpgYGANCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmFjY3VyYWN5KGF0bTJfZm9yZWNhc3Rfbm9uZGlmZiwgYXRtMl9kZikgJT4lDQogIHNlbGVjdCgubW9kZWwsIFJNU0U6TUFQRSkNCg0KYWNjdXJhY3koYXRtMl9fZm9yZWNhc3RfZGlmZiwgYXRtMl9kZikgJT4lDQogIHNlbGVjdCgubW9kZWwsIFJNU0U6TUFQRSkNCg0KYGBgDQoNCkFtb25nIHRoZSByZXVzbHRzLCB0aGUgbm9uLWRpZmZlcmVuY2UgRVRTIG1vZGVsIGhhZCB0aGUgbG93ZXN0IFJNU0UgJiBNQUUsIGFuZCBNUEUgJiBNQVBFIGNsb3Nlc3QgdG8gemVybywgbWFraW5nIGl0IHRoZSBvcHRpbWFsIGNob2ljZS4NCg0KIyMjIyBGb3JlY2FzdA0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KYXRtMl9maXRfZXRzIDwtIGF0bTJfZGYgJT4lIA0KICBtb2RlbCgNCiAgICBFVFMgPSBFVFMoQVRNMikpDQoNCiNnZW5lcmF0ZSB0aGUgdmFsdWVzDQphdG0yX2ZvcmVjYXN0X2V0cyA8LSBhdG0yX2ZpdF9ldHMgJT4lIA0KICBmb3JlY2FzdChoPTMwKQ0KDQojcGxvdA0KYXRtMl9mb3JlY2FzdF9ldHMgJT4lIA0KICBhdXRvcGxvdChhdG0yX2RmKSArDQogIGxhYnModGl0bGUgPSAiQVRNMiAtIEVUUyBGb3JlY2FzdCB8IE1heSAyMDEwIiwNCiAgICAgICB5ID0gIiQkJCAoSW4gSHVuZHJlZHMpIikNCmBgYA0KDQpgYGB7cn0NCihhdG0yX2ZvcmVjYXN0X3Jlc3VsdHMgPC0gDQogIGFzLmRhdGEuZnJhbWUoYXRtMl9mb3JlY2FzdF9ldHMpICU+JQ0KICAgIHNlbGVjdChEQVRFLCAubWVhbikgJT4lIA0KICAgICAgcmVuYW1lKERhdGUgPSBEQVRFLCBDYXNoID0gLm1lYW4pJT4lDQogICAgICAgIG11dGF0ZShDYXNoPXJvdW5kKENhc2gsMikpKQ0KYGBgDQoNCiMjIyBBVE0zDQoNCkFUTTMgd2FzIHVsdGltYXRlbHkgb21pdHRlZCwgY29uc2lkZXJpbmcgdGhlIGxpbWl0ZWQgZGF0ZSByYW5nZSBhbmQgc2tld2VkIGRpc3RyaWJ1dGlvbnMuIEl0IGNhbiBiZSBjb25zaWRlcmVkIHdoZW4gbW9yZSBkYXRhIGlzIHByb3ZpZGVkLg0KDQojIyMgQVRNNA0KDQojIyMjIFNUTCBEZWNvbXBvc2l0aW9uDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQphdG00X2RmIDwtIGF0bV9kZiAlPiUgDQogIHNlbGVjdChEQVRFLCBBVE00KQ0KDQphdG00X2RmICU+JQ0KICBtb2RlbCgNCiAgICBTVEwoQVRNNCB+IHRyZW5kKHdpbmRvdyA9IDcpICsNCiAgICAgICAgICAgICAgICAgICBzZWFzb24od2luZG93ID0gInBlcmlvZGljIiksDQogICAgcm9idXN0ID0gVFJVRSkpICU+JQ0KICBjb21wb25lbnRzKCkgJT4lDQogIGF1dG9wbG90KCkNCmBgYA0KDQpDb25zaWRlcmluZyB0aGUgdmFyaWFuY2UgZnJvbSB0aGUgdGltZSBzZXJpZXMsIEkgZGVjaWRlZCB0byB0cmFuZm9ybSB0aGUgZGF0YSBiZWZvcmUgZm9yZWNhc3RpbmcgdXNpbmcgYm94LWNveCB0cmFuc2Zvcm1hdGlvbg0KDQojIyMjIEJveC1Db3gNCg0KKipSZWZlcmVuY2UqKg0KDQpbRm9yZWNhc3RpbmcgUHJpbmNpcGxlcyBhbmQgUHJhY3RpY2VdKGh0dHBzOi8vb3RleHRzLmNvbS9mcHAzL3RyYW5zZm9ybWF0aW9ucy5odG1sKQ0KDQogICAgICAgICAgbGFtYmRhIDwtIGF1c19wcm9kdWN0aW9uIHw+DQogICAgICAgICAgICBmZWF0dXJlcyhHYXMsIGZlYXR1cmVzID0gZ3VlcnJlcm8pIHw+DQogICAgICAgICAgICBwdWxsKGxhbWJkYV9ndWVycmVybykNCiAgICAgICAgICBhdXNfcHJvZHVjdGlvbiB8Pg0KICAgICAgICAgICAgYXV0b3Bsb3QoYm94X2NveChHYXMsIGxhbWJkYSkpICsNCiAgICAgICAgICAgIGxhYnMoeSA9ICIiLA0KICAgICAgICAgICAgICAgICB0aXRsZSA9IGxhdGV4MmV4cDo6VGVYKHBhc3RlMCgNCiAgICAgICAgICAgICAgICAgICAiVHJhbnNmb3JtZWQgZ2FzIHByb2R1Y3Rpb24gd2l0aCAkXFxsYW1iZGEkID0gIiwNCiAgICAgICAgICAgICAgICAgICByb3VuZChsYW1iZGEsMikpKSkNCg0KYGBge3J9DQooYXRtNF9sYW1iZGEgPC0gYXRtNF9kZiAlPiUNCiAgZmVhdHVyZXMoQVRNNCwgZmVhdHVyZXMgPSBndWVycmVybykgJT4lDQogIHB1bGwobGFtYmRhX2d1ZXJyZXJvKSkNCmBgYA0KDQpgYGB7cn0NCmF0bTRfdHJhbnNmb3JtZWQgPC0gQm94Q294KGF0bTRfZGYkQVRNNCwgbGFtYmRhID0gYXRtNF9sYW1iZGEpDQoNCiMgRXh0cmFjdCB0aGUgdHJhbnNmb3JtZWQgZGF0YQ0KDQphdG00X2RmJEFUTTRfVDwtYXRtNF90cmFuc2Zvcm1lZA0KDQojcGxvdA0KYXRtNF9kZiU+JSANCiAgYXV0b3Bsb3QoQVRNNF9UKSANCmBgYA0KDQoNCmBgYHtyfQ0KbmRpZmZzKGF0bTRfZGYkQVRNNCkNCg0KbmRpZmZzKGF0bTRfZGYkQVRNNF9UKQ0KYGBgDQoNCmBgYHtyfQ0KYXRtNF9kZiAlPiUgDQogIEFDRihBVE00X1QsIGxhZ19tYXggPSAyOCkgJT4lIA0KICBhdXRvcGxvdCgpDQpgYGANCg0KVXNpbmcgbmRpZmYoKSB3ZSBpZGVudGlmeSB0aGF0IHRoZXJlcyBubyBuZWVkIGZvciBkaWZmZXJlbmNpbmcsIGFuZCB0aGUgQUNGIHNob3dzDQoNClRoZSBBQ0YgcGxvdCBiZWxvdyBzdWdnZXN0IGxhZ3MgNyBjb25zaXN0ZW50bHkgYW5kIG9uIDIgb3RoZXIgb2NjYXNpb25zIGluIGRpZmZlcmVudCBwZXJpb2RzLiBEZXNwaXRlIHRoZSBuZGlmZigpIGZ1bmN0aW9uIHJlc3VsdGluZyBpbiAwLCBpZiBiZWxpZXZlIHRoaXMgZG9lcyByZXF1aXJlIGRpZmZlcmVuY2luZyB1c2luZyB0aGUgdHJhbnNmb3JtZWQgZGF0YS4NCg0KDQpgYGB7cn0NCmF0bTRfZGYgPC0gYXRtNF9kZiAlPiUgDQogIG11dGF0ZShkaWZmX0FUTTQ9IGRpZmZlcmVuY2UoQVRNNF9UKSkNCmBgYA0KDQojIyMjIEFSSU1BDQoNCmBgYHtyIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTE1LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KDQphdG00X3RyYWluIDwtIGF0bTRfZGYgJT4lDQogIGZpbHRlcihEQVRFIDw9ICIyMDEwLTA0LTAxIikNCg0KI3J1biBzZWFzb25hbCByZWxhdGVkIG1vZGVscyB3aXRob3V0IHRoZSBkaWZmZXJlbmNlZCBkYXRhDQphdG00X2ZpdF9ub25kaWZmIDwtIGF0bTRfdHJhaW4gJT4lDQogIG1vZGVsKA0KICAgIFNOQUlWRSA9IFNOQUlWRShBVE00X1QpLA0KICAgIEVUUyA9IEVUUyhBVE00X1QpLA0KICApDQoNCiNydW4gbW9kZWxzIHdpdGggZGlmZmVyZW5jZWQgZGF0YQ0KYXRtNF9maXRfZGlmZiA8LSBhdG00X3RyYWluICU+JQ0KICBzbGljZSgyOjMzNikgJT4lIA0KICBtb2RlbCgNCiAgICBFVFNfZGlmZiA9IEVUUyhkaWZmX0FUTTQpLA0KICAgIEFSSU1BID0gQVJJTUEoZGlmZl9BVE00KSwNCiAgIGBBdXRvIEFSSU1BYCA9IEFSSU1BKGRpZmZfQVRNNCwgc3RlcHdpc2UgPSBGQUxTRSwgYXBwcm94ID0gRkFMU0UpDQogICkNCg0KI2ZvcmVjYXN0X0FUTTIgQXByaWwNCmF0bTRfZm9yZWNhc3Rfbm9uZGlmZiA8LSBhdG00X2ZpdF9ub25kaWZmICU+JQ0KICBmb3JlY2FzdChoID0gMzApDQoNCiNmb3JlY2FzdF9BVE0yIEFwcmlsDQphdG00X19mb3JlY2FzdF9kaWZmIDwtIGF0bTRfZml0X2RpZmYgJT4lDQogIGZvcmVjYXN0KGggPSAzMCkNCg0KI3Bsb3QNCmF0bTRfZm9yZWNhc3Rfbm9uZGlmZiAlPiUNCiAgYXV0b3Bsb3QoYXRtNF9kZiwgbGV2ZWwgPSBOVUxMKSsNCiAgZmFjZXRfd3JhcCggfiAubW9kZWwsIHNjYWxlcyA9ICJmcmVlX3kiKSArDQogIGd1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQodGl0bGUgPSAiRm9yZWNhc3QiKSkrDQogIGxhYnModGl0bGU9ICJBVE00IEZvcmVjYXN0cyB8IEFwcmlsIikgKw0KICB4bGFiKCJEYXRlIikgKw0KICB5bGFiKCIkJCQgKEluIEh1bmRyZWRzKSIpIA0KDQojcGxvdCAyDQphdG00X19mb3JlY2FzdF9kaWZmICU+JQ0KICBhdXRvcGxvdChhdG00X2RmLCBsZXZlbCA9IE5VTEwpKw0KICBmYWNldF93cmFwKCB+IC5tb2RlbCwgc2NhbGVzID0gImZyZWVfeSIpICsNCiAgZ3VpZGVzKGNvbG91ciA9IGd1aWRlX2xlZ2VuZCh0aXRsZSA9ICJGb3JlY2FzdCIpKSsNCiAgbGFicyh0aXRsZT0gIkFUTTQgRm9yZWNhc3RzIHwgQXByaWwiKSArDQogIHhsYWIoIkRhdGUiKSArDQogIHlsYWIoIkNhc2giKQ0KYGBgDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQphY2N1cmFjeShhdG00X2ZvcmVjYXN0X25vbmRpZmYsIGF0bTRfZGYpICU+JQ0KICBzZWxlY3QoLm1vZGVsLCBSTVNFOk1BUEUpDQoNCmFjY3VyYWN5KGF0bTRfX2ZvcmVjYXN0X2RpZmYsIGF0bTRfZGYpICU+JQ0KICBzZWxlY3QoLm1vZGVsLCBSTVNFOk1BUEUpDQoNCmBgYA0KDQpPZiB0aGUgbW9kZWxzLCBTTkFJVkUgZm9yIG5vbiBkaWZmZXJlbmNlZCBkYXRhIHdhcyB0aGUgbW9zdCBhY2N1cmF0ZSBzbyBJIHdpbGwgcHJvY2VlZCB3aXRoIHRoaXMuDQoNCiMjIyMgRm9yZWNhc3QNCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmF0bTRfZml0X3NuYWl2ZSA8LSBhdG00X2RmICU+JSANCiAgbW9kZWwoDQogICAgU05BSVZFID0gU05BSVZFKEFUTTRfVCkpDQoNCiNnZW5lcmF0ZSB0aGUgdmFsdWVzDQphdG00X2ZvcmVjYXN0X3NuYWl2ZSA8LSBhdG00X2ZpdF9zbmFpdmUgJT4lIA0KICBmb3JlY2FzdChoPTMwKQ0KDQojcGxvdA0KYXRtNF9mb3JlY2FzdF9zbmFpdmUgJT4lIA0KICBhdXRvcGxvdChhdG00X2RmKSArDQogIGxhYnModGl0bGUgPSAiQVRNMiAtIFNOQUlWRSBGb3JlY2FzdCB8IE1heSAyMDEwIiwNCiAgICAgICB5ID0gIiQkJCAoSW4gSHVuZHJlZHMpIikNCmBgYA0KDQpgYGB7cn0NCihhdG00X2ZvcmVjYXN0X3Jlc3VsdHMgPC0gDQogIGFzLmRhdGEuZnJhbWUoYXRtNF9mb3JlY2FzdF9zbmFpdmUpICU+JQ0KICAgIHNlbGVjdChEQVRFLCAubWVhbikgJT4lIA0KICAgICAgcmVuYW1lKERhdGUgPSBEQVRFLCBDYXNoID0gLm1lYW4pJT4lDQogICAgICAgIG11dGF0ZShDYXNoPXJvdW5kKENhc2gsMikpKQ0KYGBgDQoNCiMgUGFydCBCDQoNCiMjIEVEQSAmIENsZWFudXANCg0KDQpgYGB7cn0NCnN0cihwb3dlcl9yYXcpDQpgYGANCg0KYGBge3J9DQpkZXNjcmliZShwb3dlcl9yYXcpDQpgYGANCg0KDQpgYGB7cn0NCmRhdGEuZnJhbWUocG93ZXJfcmF3JGBZWVlZLU1NTWBbcG93ZXJfcmF3JEtXSCAlaW4lIE5BXSkNCmBgYA0KDQpJIHJlbmFtZWQgdGhlIGBZWVlZLU1NTWAgZm9yIHByZWZlcmVuY2UgdG8gYERBVEVgLiBUaGUgY2xlYW51cCBpcyBhIGNoYW5nZSBvZiB0eXBlIGZvciBgREFURWAsIHJlbW92YWwgb2YgYENhc2VTZXF1ZW5jZWAgYXMgaXQgZG9lcyBub3QgaGVscCBvdXIgbW9kZWwsIGFuZCByZWR1Y2luZyBvdXIgbW9kZWwgdG8gdmFsdWVzIGluIHRoZSB0aG91c2FuZHMgZm9yIGVhc2Ugb2YgYW5hbHlzaXMuDQpMaWtlIGJlZm9yZSB3ZSdsbCBhbHNvIGJlIGluZGV4aW5nIGJ5IERBVEUNCg0KYGBge3J9DQojY2hhbmdlIHZhcmlhYmxlIHR5cGUgDQpwb3dlcl9kZiA8LSBwb3dlcl9yYXcgJT4lIA0KICBtdXRhdGUoREFURSA9IHllYXJtb250aChgWVlZWS1NTU1gKSwgS1dIID0gS1dILzEwMDApICU+JQ0KICBzZWxlY3QoLUNhc2VTZXF1ZW5jZSwgLSdZWVlZLU1NTScpICU+JSANCiAgdHNpYmJsZShpbmRleD0gREFURSkNCg0KYGBgDQoNCmBgYHtyfQ0KaGVhZChwb3dlcl9kZikNCmBgYA0KDQpgYGB7ciwgd2FybmluZz1GQUxTRX0NCmdncGxvdChwb3dlcl9kZiwgYWVzKHg9S1dIKSkrDQogIGdlb21faGlzdG9ncmFtKGJpbnM9NDApKw0KICBsYWJzKHRpdGxlID0gIk1vbnRobHkgRGlzdHJpYnV0aW9ucyBSZXNpZGVudGlhbCBQb3dlciBVc2FnZSB8IEphbiAnOTggLSBEZWMgJzEzIikNCg0KcG93ZXJfZGYgJT4lDQogIGF1dG9wbG90KEtXSCkgKw0KICBsYWJzKHRpdGxlID0gIk1vbnRobHkgRGlzdHJpYnV0aW9ucyBSZXNpZGVudGlhbCBQb3dlciBVc2FnZSB8IEphbiAnOTggLSBEZWMgJzEzIikNCmBgYA0KDQoNClRoZSBkYXRhIGhhcyBhbiBhcHBhcmVudCBvdXRsaWVyIGFuZCBpcyByaWdodCBza2V3ZWQgdGhhdCBhcHBlYXJzIGluIGJvdGggcGxvdHMsIGFuZCByZXNpZGVzIHNvbWV0aW1lIGFmdGVyIEphbnVhcnkgb2YgMjAxMC4NCg0KYGBge3J9DQojIG1hZGUgYSBjb3B5IG9mIHRoZSBkYXRhIGZpcnN0DQpwb3dlcl9kZjI8LXBvd2VyX2RmDQpwb3dlcl9kZjIkS1dIIDwtIG5hLmludGVycChwb3dlcl9kZjIkS1dIKQ0KcG93ZXJfZGYyJEtXSCA8LSByZXBsYWNlKHBvd2VyX2RmMiRLV0gsIHBvd2VyX2RmMiRLV0ggPT0gbWluKHBvd2VyX2RmMiRLV0gpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBtZWRpYW4ocG93ZXJfZGYyJEtXSCkpDQpgYGANCg0KQ29uc2lkZXJpbmcgdGhlIGRpc3RyaWJ1dGlvbiwgSSBhZ2FpbiB0aG91Z2h0IGl0IGJlc3QgdG8gcmVwbGFjZSB0aGUgbWlzc2luZyB2YWx1ZSB3aXRoIHRoZSBtZWRpYW4sIGJ1dCBjb25zaWRlcmluZyBJIHdpbGwgYmUgdXNpbmcgdGhhdCBtZXRob2QgdG8gYWRkcmVzcyB0aGUgb3V0bGllciwgSSBkZWNpZGVkIHRvIHVzZSBgbmEuaW50ZXJwYCBzaW5jZSBpdHMgYSB0b29sIHVzZWQgYnkgdGhlIGF1dGhvciBvZiBvdXIgdGV4dGJvb2sgUm9iIEogSHlkbWFuJ3MgW2dpdGh1YiByZXBvXShodHRwczovL2dpdGh1Yi5jb20vcm9iamh5bmRtYW4vZm9yZWNhc3QvYmxvYi9tYXN0ZXIvbWFuL25hLmludGVycC5SZCkuIFJlZ2FyZGxlc3MsIHRoZSB0cmFuc2Zvcm1hdGlvbiBiZWxvdyBzaG93cyBpdHMgc3RpbGwgcmlnaHQgc2tld2VkIGJ1dCBzaG93cyBzZWFzb25hbGl0eSB3aXRoIGFuIHVwd2FyZCB0cmVuZC4NCg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KDQpnZ3Bsb3QocG93ZXJfZGYyLCBhZXMoeD1LV0gpKSsNCiAgZ2VvbV9oaXN0b2dyYW0oKSsNCiAgbGFicyh0aXRsZSA9ICJNb250aGx5IERpc3RyaWJ1dGlvbnMgUmVzaWRlbnRpYWwgUG93ZXIgVXNhZ2UgfCBKYW4gJzk4IC0gRGVjICcxMyIpDQoNCiNzdW1tYXJ5DQpzdW1tYXJ5KHBvd2VyX2RmMiRLV0gpDQoNCiN0cyBwbG90DQpwb3dlcl9kZjIgJT4lDQogIGF1dG9wbG90KEtXSCkgKw0KICBsYWJzKHRpdGxlID0gIk1vbnRobHkgRGlzdHJpYnV0aW9ucyBSZXNpZGVudGlhbCBQb3dlciBVc2FnZSB8IEphbiAnOTggLSBEZWMgJzEzIikrDQogIHlsYWIobGFiZWw9ICJLV0ggKFRob3VzYW5kcykiKQ0KYGBgDQoNCkJlZm9yZSBmb3JlY2FzdGluZyBJIHdpbGwgdHJhbnNmb3JtIHRoZSBkYXRhIHVzaW5nIGEgQm94LUNveCB0cmFuc2Zvcm1hdGlvbi4NCg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KI2dldCBsYW1iZGENCihwb3dlcl9sYW1iZGEgPC0gcG93ZXJfZGYyICU+JQ0KICBmZWF0dXJlcyhLV0gsIGZlYXR1cmVzID0gZ3VlcnJlcm8pICU+JQ0KICBwdWxsKGxhbWJkYV9ndWVycmVybykpDQpgYGANCg0KYGBge3J9DQpwb3dlcl9kZjIgPC0gcG93ZXJfZGYyICU+JSANCiAgICBtdXRhdGUoS1dIX2JjID0gYm94X2NveChLV0gsIHBvd2VyX2xhbWJkYSkpDQoNCg0Kc3VtbWFyeShwb3dlcl9kZjIkS1dIX2JjKQ0KYGBgDQoNCg0KYGBge3J9DQpnZ3Bsb3QocG93ZXJfZGYyLCBhZXMoeD1LV0hfYmMpKSsNCiAgZ2VvbV9oaXN0b2dyYW0oKSsNCiAgbGFicyh0aXRsZSA9ICJNb250aGx5IERpc3RyaWJ1dGlvbnMgUmVzaWRlbnRpYWwgUG93ZXIgVXNhZ2UgfCBKYW4gJzk4IC0gRGVjICcxMyIpDQoNCnBvd2VyX2RmMiAlPiUgDQogIGF1dG9wbG90KEtXSF9iYykgKw0KICBsYWJzKHkgPSAiS1dIIGluIFRob3VzYW5kcyIsDQogICAgICAgdGl0bGUgPSAiVHJhbnNmb3JtZWQgS1dIIHdpdGggTGFtYmRhID0gLTAuMjEzMDU0OCIpDQpgYGANCg0KIyMgU1RMIERlY29tcG9zaXRpb24NCg0KKiBTVEwgZGVjb21wb3NpdGlvbiBhZ2FpbiB1c2VkIHRvIGlkZW50aWZ5IHNlYXNvbmFsaXR5LCB2YXJpYW5jZSwgZXRjLg0KKiBuZGlmZigpIGFuZCBBQ0Ygd2lsbCBpZGVudGlmeSBpZiBkaWZmZXJlbmNpbmcgaXMgbmVlZGVkLg0KDQpgYGB7cn0NCnBvd2VyX2RmMiAlPiUNCiAgbW9kZWwoDQogICAgU1RMKEtXSF9iYyB+IHRyZW5kKHdpbmRvdyA9IDEzKSArDQogICAgICAgICAgICAgICAgICAgc2Vhc29uKHdpbmRvdyA9ICJwZXJpb2RpYyIpLA0KICAgICAgICAgICAgICAgICAgICAgIHJvYnVzdCA9IFRSVUUpKSAlPiUNCiAgICAgICAgICAgICAgICAgICAgICAgIGNvbXBvbmVudHMoKSAlPiUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBhdXRvcGxvdCgpDQpgYGANCg0KYGBge3J9DQpuZGlmZnMocG93ZXJfZGYyJEtXSF9iYykNCmBgYA0KDQoNCg0KYGBge3J9DQpwb3dlcl9kZjIgJT4lIA0KICBBQ0YoS1dIX2JjLCBsYWdfbWF4ID0gMzYpICU+JSANCiAgYXV0b3Bsb3QoKQ0KYGBgDQoNCkRpZmZlcmVuY2luZyBpcyBuZWVkZWQuDQoNCmBgYHtyfQ0KZGlmZl9wb3dlciA8LSBwb3dlcl9kZjIgJT4lIA0KICBtdXRhdGUoZGlmZl9LV0g9IGRpZmZlcmVuY2UoS1dIKSwgZGlmZl9LV0hfYmMgPSBkaWZmZXJlbmNlKEtXSF9iYykpDQogIA0KYGBgDQoNCiogRGlmZmVyZW5jaW5nIGNyZWF0ZWQNCiogTkEgYW5kIHNvbWUgY29sdW1ucyBuZWVkIHJlbW92YWwNCg0KYGBge3J9DQpkaWZmX3Bvd2VyPC1kaWZmX3Bvd2VyJT4lDQogIHNlbGVjdCgtS1dILCAtS1dIX2JjKSU+JQ0KICAgICAgICAgICAgICAgICAgICAgICAgc2xpY2UoLTEpDQoNCm5kaWZmcyhkaWZmX3Bvd2VyJGRpZmZfS1dIX2JjKQ0KYGBgDQoNCiMjIEZvcmVjYXN0DQoNCmBgYHtyIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD0xNSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiNEaWZmZXJlbmNlZCBkYXRhIGZvciBhcmltYQ0KDQojc3BsaXQNCnBvd2VyX3RyYWluX2RpZmYgPC0gZGlmZl9wb3dlciAlPiUgDQogIGZpbHRlcih5ZWFyKERBVEUpIDwgMjAxMykNCg0KI21vZGVscw0KcG93ZXJfZml0X2RpZmYgPC0gcG93ZXJfdHJhaW5fZGlmZiAlPiUgDQogICAgbW9kZWwoDQogICAgQVJJTUEgPSBBUklNQShkaWZmX0tXSCksDQogICAgYEF1dG8gQVJJTUFgID0gQVJJTUEoZGlmZl9LV0gsIHN0ZXB3aXNlID0gRkFMU0UsIGFwcHJveCA9IEZBTFNFKQ0KICApDQoNCiNmb3JlY2FzdCBvZiAyMDEzDQpwb3dlcl9mb3JlY2FzdF9kaWZmIDwtIHBvd2VyX2ZpdF9kaWZmICU+JSANCiAgZm9yZWNhc3QoaCA9ICIxIHllYXIiKQ0KDQojcGxvdA0KcG93ZXJfZm9yZWNhc3RfZGlmZiAlPiUNCiAgYXV0b3Bsb3QoZGlmZl9wb3dlciwgbGV2ZWwgPSBOVUxMKSsNCiAgZmFjZXRfd3JhcCggfiAubW9kZWwsIHNjYWxlcyA9ICJmcmVlX3kiKSArDQogIGd1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQodGl0bGUgPSAiRm9yZWNhc3QiKSkrDQogIGxhYnModGl0bGU9ICJLV0ggRm9yZWNhc3RzIHwgSmFuICcxMyAtIERlYyAnMTMiKSsNCiAgeGxhYigiTW9udGgiKSArDQogIHlsYWIoIktXSCBpbiBUaG91c2FuZHMiKSANCmBgYA0KDQoNCmBgYHtyIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD0xNSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiNzcGxpdA0KcG93ZXJfdHJhaW4gPC0gcG93ZXJfZGYyICU+JSANCiAgZmlsdGVyKHllYXIoREFURSkgPCAyMDEzKQ0KDQojbW9kZWxzDQpwb3dlcl9maXQgPC0gcG93ZXJfdHJhaW4gJT4lIA0KICAgIG1vZGVsKA0KICAgIEVUUyA9IEVUUyhLV0gpLA0KICAgIGBBZGRpdGl2ZSBFVFNgID0gRVRTKEtXSCB+IGVycm9yKCJBIikgKyB0cmVuZCgiQSIpICsgc2Vhc29uKCJBIikpLA0KICAgIFNOQUlWRSA9IFNOQUlWRShLV0gpDQogICkNCg0KI2ZvcmVjYXN0IG9mIDIwMTMNCnBvd2VyX2ZvcmVjYXN0IDwtIHBvd2VyX2ZpdCAlPiUgDQogIGZvcmVjYXN0KGggPSAiMSB5ZWFyIikNCg0KI3Bsb3QNCnBvd2VyX2ZvcmVjYXN0ICU+JQ0KICBhdXRvcGxvdChwb3dlcl9kZjIsIGxldmVsID0gTlVMTCkrDQogIGZhY2V0X3dyYXAoIH4gLm1vZGVsLCBzY2FsZXMgPSAiZnJlZV95IikgKw0KICBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKHRpdGxlID0gIkZvcmVjYXN0IikpKw0KICBsYWJzKHRpdGxlPSAiS1dIIEZvcmVjYXN0cyB8IEphbiAnMTMgLSBEZWMgJzEzIikrDQogIHhsYWIoIk1vbnRoIikgKw0KICB5bGFiKCJLV0ggaW4gVGhvdXNhbmRzIikgDQpgYGANCg0KDQpgYGB7cn0NCiNmaW5kIEFSSU1BIFJNU0UsIE1BRQ0KYWNjdXJhY3kocG93ZXJfZm9yZWNhc3RfZGlmZiwgZGlmZl9wb3dlcikgJT4lDQogIHNlbGVjdCgubW9kZWwsIFJNU0U6TUFFKQ0KDQojZmluZCBvdGhlciBSTVNFLCBNQUUNCmFjY3VyYWN5KHBvd2VyX2ZvcmVjYXN0LCBwb3dlcl9kZjIpICU+JQ0KICBzZWxlY3QoLm1vZGVsLCBSTVNFOk1BRSkNCmBgYA0KDQpBZGRpdGl2ZSBFVFMgaXMgdGhlIGJlc3QgbW9kZWwgYmFzZWQgb24gUk1TRSBhbmQgTUFFDQoNCmBgYHtyfQ0KI3JlcHJvZHVjZSB0aGUgbW9kZSB1c2luZyB0aGUgb3JpZ2luYWwgZGF0YXNldCANCnBvd2VyX0VUU19maXQgPC0gcG93ZXJfZGYyICU+JSANCiAgbW9kZWwoYEFkZGl0aXZlIEVUU2AgPSBFVFMoS1dIIH4gZXJyb3IoIkEiKSArIHRyZW5kKCJBIikgKyBzZWFzb24oIkEiKSkpDQoNCiNnZW5lcmF0ZSB0aGUgdmFsdWVzDQpwb3dlcl9FVFNfZm9yZWNhc3QgPC0gcG93ZXJfRVRTX2ZpdCAlPiUgDQogIGZvcmVjYXN0KGg9MTIpDQoNCiNwbG90DQpwb3dlcl9FVFNfZm9yZWNhc3QgJT4lIA0KICBhdXRvcGxvdChwb3dlcl9kZjIpICsNCiAgbGFicyh0aXRsZSA9ICJNb250aGx5IFJlc2lkZW50aWFsIFBvd2VyIFVzYWdlIChBZGRpdGl2ZSBFVFMgfDIwMjQpIiwNCiAgICAgICB5ID0gIktXSCBpbiBUaG91c2FuZHMiKQ0KICAgICAgIA0KYGBgDQoNCg0KYGBge3J9DQoocG93ZXJfZm9yZWNhc3RfcmVzdWx0cyA8LSANCiAgYXMuZGF0YS5mcmFtZShwb3dlcl9FVFNfZm9yZWNhc3QpICU+JQ0KICAgIHNlbGVjdChEQVRFLCAubWVhbikgJT4lIA0KICAgICAgcmVuYW1lKCdLV0ggRm9yZWNhc3QnID0gLm1lYW4pKQ0KYGBg