About this project

One important note: this project was originally made in Dutch. I translated every description and comments in the code. Only table names are in Dutch.

This project is all about process improvement when organizing festivals. I designed the SQL database for this project for the festival organizers and used R to manipulate and visualize data.

Workflow:

1. Data collection

2. Pre-processing

3. Exploratory Data Analysis

1.1 Importing libraries:

library(dplyr) # Data manipulation
library(ggplot2) # Data visualization
library(RSQLite) # Database connection
library(forcats) # Factors
library(lubridate) # Dates
library(openxlsx) # Export to Excel
library(rio) # Export to Excel
library(ggrepel) # Pie chart

1.2 Database connection

con <- dbConnect(SQLite(), "/Users/milanpatty/Documents/Business/Semester_2/R/Proftaak/Festivate2.db")

1.3 Check whether the tables have been loaded successfully

as.data.frame(dbListTables(con))
ABCDEFGHIJ0123456789
dbListTables(con)
<fctr>
aanmeldingen
beschikbare_middelen
bezoeker
campagne
campagne_festival
festival
festival_categorie
festival_categorie_festival
festival_partners
inkoop

1.4 Create objects from the tables

aanmeldingen <- dbReadTable(con, 'aanmeldingen')
beschikbare_middelen <- dbReadTable(con, 'beschikbare_middelen')
Column `aantal`: mixed type, first seen values of type integer, coercing other values of type stringColumn `waarde`: mixed type, first seen values of type integer, coercing other values of type string
bezoeker <- dbReadTable(con, 'bezoeker')
campagne <- dbReadTable(con, 'festival')
campagne_festival <- dbReadTable(con, 'campagne_festival')
festival <- dbReadTable(con, 'festival')
festival_categorie <- dbReadTable(con, 'festival_categorie')
festival_categorie_festival <- dbReadTable(con, 'festival_categorie_festival')
festival_partners <- dbReadTable(con, 'festival_partners')
inkoop <- dbReadTable(con, 'inkoop')

inkoop_festival <- dbReadTable(con, 'inkoop_festival')
locatie <- dbReadTable(con, 'locatie')
partners <- dbReadTable(con, 'partners')
sponsoren <- dbReadTable(con, 'sponsoren')
ticket_categorie <- dbReadTable(con, 'ticket_categorie')
tickets <- dbReadTable(con, 'tickets')
verkoop_producten <- dbReadTable(con, 'verkoop_producten')
locatie_festival <- dbReadTable(con, 'locatie_festival')
verkoop_producten_festival <- dbReadTable(con, 'verkoop_producten_festival')
werknemers <- dbReadTable(con, 'werknemers')

werknemers_werkzaamheden <- dbReadTable(con, 'werknemers_werkzaamheden')
werkzaamheden <- dbReadTable(con, 'werkzaamheden')
werkzaamheden_festival <- dbReadTable(con, 'werkzaamheden_festival')

2. Pre-processing

The second step after data collection is the pre-processing of data. This includes various things such as class labelling (indicating the correct data type), data cleaning (correcting, for example, incorrectly spelt names) and the handling of missing values. Furthermore, the data I use from my database is tidy according to the 3 rules:

  1. Each variable has its own column.
  2. Each observation has its own row.
  3. Each value has its own cell.

2.1 Table Location

While setting up the database, a group member responsible for the table miswritten the word ‘capaciteit’(capacity) Of course, we want to fix this bug and make sure it gets correct everywhere. Since bezoekers_capiciteit (visitor_capacity) is a column, it can easily be renamed with the function rename.

locatie <-locatie %>%
  rename(bezoekers_capaciteit = bezoekers_capiciteit)

To see if the change is correct:

colnames(locatie)
[1] "locatie_id"           "naam"                
[3] "adres"                "plaats"              
[5] "areas"                "bezoekers_capaciteit"

3. EDA

3.1 What is the visitor capacity per location?

The figure below shows all locations with the corresponding visitor capacity. These are a total of 15 locations, each of which has its areas. What is striking is that the top 3 locations with the most capacity take up 59.01% of the total capacity and the 5 locations with the least capacity only take up 6.83%. By chance, it does not mean that locations with more areas can have more visitors. The location with the most areas is the Berendonck in Wijchen, where the annual Emporium festival is held. These locations have no fewer than 17 areas and they only take up 6.21% of the total capacity. This substantiation has been validated with calculation functions in Excel and is also stored there.

locatie %>%
  mutate(naam = fct_reorder(naam, bezoekers_capaciteit)) %>% # Mutate for the new column with the correct factor order
  ggplot( aes(x=naam, y=bezoekers_capaciteit)) + # First 2 layers of a plot: Data + Aesthetics
    geom_bar(stat="identity", fill="#f68060", alpha=.7, width=.6) + # 3rd layer of the plot, stat='identity' because I have values for x&y
    coord_flip() + # Switching axis
    xlab('Location') +
    ylab('Visitor capacity')+
    ggtitle('Visitor capacity per location')+
    theme_bw(base_size = 15)

3.2 What are the sales per month?

Below you can see the sales per month divided over 2018, 2019 and 2021. What can be seen in this figure is that there are years where some months have no sales. For 2018 this will be April and for 2019 and 2021 it will both be March. in 2018 total sales were € 491,043.00 compared to 2019 and 2021, where it was both € 113,104.00. Average sales in 2018 were € 61,381.25, in 2019 and 2021 it was both € 14,138.88. These calculations have been validated with Excel’s calculation functions.

SELECT product, ROUND(prijs,2) AS prijs, aantal, leverancier, datum /* Round off to 2 decimal places at Round because it is a price */
FROM inkoop i
JOIN inkoop_festival ink
ON ink.inkoop_id = i.inkoop_id
JOIN festival f
ON ink.festival_id = f.festival_id
afzet$aantal[31] <- 300 # Change quantity because this error is in the database
afzet %>%
  mutate(jaar = year(datum), maand= month(datum), afzet_prijs = aantal * prijs) %>% # Create new columns for the year, month and sales
  group_by(jaar, maand) %>% # Group by year and month
  summarise(afzet = sum(afzet_prijs)) %>% # Minimizing all rows by means of an accumulated sales per year and month

ggplot(aes(maand, afzet, fill=factor(maand))) + geom_bar(stat = 'identity') + facet_grid(~ jaar) + #As color I use the factor levels of month, stat = 'identity' because I use values for x & y, then facet_grid to distribute the visualizations over the years.
  scale_y_continuous(breaks=seq(0,200000,10000)) + # y axis reset
  scale_x_continuous(breaks=seq(3,9,1)) + # reset axis
  ggtitle('Sales per month divided over 2018, 2019 & 2021') +
  xlab('Month') +
  ylab('Sales in €') +
  theme_bw(base_size = 16) +
  theme(legend.position = "none")

afzet_output <- afzet %>% # create an object from this data to later validate it in excel
  mutate(jaar = year(datum), maand= month(datum), afzet_prijs = aantal * prijs)
export(afzet_output, 'afzet.xlsx') # Export

3.3 Which Festivals have a sponsor who sponsors an item with a value above € 5500?

The figure below shows which festivals have sponsored an item with a value above € 5500. What is obvious is that the 2 festivals that have the highest value have both been given a budget that together is 49.8% of the total sponsored values above € 5500. The 5 festivals with the lowest sponsored values account for only 15.34% of the total sponsored amount. These calculations have been validated with excel.

/* Use SQL query and save this output to use in R. Inline view as result set and add a new column for year. Then filter for values above 5,500 euros and values that are not null. */
SELECT festival_naam, sponsor_naam, item, waarde, jaar
FROM  
(
    SELECT f.naam AS festival_naam, item, s.naam AS sponsor_naam, waarde, strftime('%Y',datum) as "jaar"
    FROM festival f
    JOIN beschikbare_middelen bm
    ON bm.festival_id = f.festival_id
    JOIN sponsoren s
    ON s.sponsor_id = bm.sponsor_id
)t
WHERE waarde > 5500 AND waarde IS NOT NULL
AND jaar = '2019'
ORDER BY waarde
options(scipen=999) # Remove math notation
Warning message:
In result_fetch(res@ptr, n = n) :
  Column `waarde`: mixed type, first seen values of type integer, coercing other values of type string
sponsor_lijstje<- data.frame(sponsor_lijstje)[1:10,] # Validate my SQL chunk. The output indicated 10 items above 5000 euro and 3 null values, with this I select only those 10 items that have a value and those 3 others are dropped.
  sponsor_lijstje %>%
  mutate(festival_naam = fct_reorder(festival_naam, waarde)) %>% # use of forcats to rearrange the factor levels based on the values they have so that they go from high to low
  ggplot(aes(festival_naam, waarde, fill=item)) +  # as color I want to show the item being sponsored
  geom_bar(stat='identity', width = .6) + # for the 2 values
  coord_flip() + # Switching the x & y axis
  ggtitle('Festivals in 2019 that were sponsored 1 item with a value above € 5500') +
  xlab('Festival name') +
  ylab('Value item in €') +
  theme_bw(base_size = 16) +
  guides(fill=guide_legend(title="Name Item")) +
  scale_y_continuous(breaks=seq(0,150000,20000)) # rearrange y axis

export(sponsor_lijstje, 'sponsors.xlsx') # export for validation

3.4 Which Festivals have more than 3 partners?

For this query, I am using a CTE as a resultset. I hereby show which festivals have more than 3 partners. In the CTE I select the festivals and count the number of partners they have. I hereby join on 2 tables: partners and the intermediate table festival_partners. Then I group by festival name. In the query, I select all festivals that have more than 3 partners. I validated this by looking in the database which festivals have which partners.

WITH festival_partners_cte
AS
(
SELECT f.naam AS festival_naam, COUNT(p.naam) AS aantal_partners /* Partners count per festival */
FROM festival f
JOIN festival_partners fp
ON fp.festival_id = f.festival_id
JOIN partners p 
ON p.partner_id = fp.partner_id
GROUP BY f.naam
)

SELECT festival_naam, aantal_partners
FROM festival_partners_cte
WHERE aantal_partners > 3 /* Select festivals that have more than 3 partners. */
ABCDEFGHIJ0123456789
festival_naam
<chr>
aantal_partners
<int>
Defqon.14
Drift Festival4

3.5 Which purchasing supplier supplies the most products?

In this query, I show which supplier delivers the most products in numbers and what percentage this is of the total deliveries. First of all, I add up the products by grouping them by the supplier name, then I count all products and divide them by all the products that are in the purchase table. I round this to 2 decimal places. Below you can see that the Makro supplies no less than 33.33% of all products and the numbers 2 and 3 are also good for 33.33% in total. This means that only the top 3 suppliers already supply 66.66% of all products. These calculations have been validated in excel.

SELECT leverancier,COUNT(product) AS aantal, ROUND(100.0 * COUNT(*) / (SELECT COUNT(*) FROM inkoop),2) AS percentage /* calculate percentage */
FROM inkoop
GROUP BY leverancier
ORDER BY percentage DESC
ABCDEFGHIJ0123456789
leverancier
<chr>
aantal
<int>
percentage
<dbl>
makro1033.33
sligro620.00
hanos413.33
goedkoopdrank310.00
wijnvoordeel13.33
watercoolergigant13.33
treb13.33
plus13.33
horecabier13.33
eventtrading13.33

3.6 The ratio of sex of visitors

Below you can see the gender ratio of the festival visitors. 52% of the festival visitors are male while 48% of the visitors are female(vrouw). For this chunk I did the following: from the visitor table, I started grouping by gender. Then I started to minimize all rows by using summarise and counting the rows for each gender. Then I started plotting everything. I want to put the percentages on the Y-axis and I want to use the levels of the factor as colour. This chunk has been validated using an SQL query that gives the same result.

bezoeker %>%
  group_by(geslacht) %>% # group by
  summarise(aantal = n()) %>% # count
  mutate(procent = round(100 * aantal / sum(aantal), 1)) %>% # create new column
ggplot(aes(x = '', y = aantal ,fill = factor(geslacht))) + # plotting
  geom_bar(width = 1, stat='identity') + # stat = 'identity' because x and y are used
  coord_polar(theta = "y") + 
  theme_void() + # remove background lines
  ggtitle('The ratio of sex of visitors') +
  guides(fill=guide_legend(title="Sex")) + 
  scale_fill_manual(values=c("#00ccff", "#ff0000")) + # colors
  geom_label_repel(aes(label = procent), size = 5, show.legend = F) 

The validation:

select geslacht,  ROUND(COUNT(*) *100 / (select count(*) FROM bezoeker),2) AS percentage
FROm bezoeker
group by geslacht
ABCDEFGHIJ0123456789
geslacht
<chr>
percentage
<dbl>
man52
vrouw48

3.7 Which visitors went to Mysteryland in 2018 and have also been to Mysteryland in subsequent years?

Below you can see which festival visitors have been to Mysteryland in 2018 and then returned in consecutive years. The first query consists of an inline view and 2 subqueries, the last validation query consists of a CTE and a query. Here I use a CASE statement as an Inline IF to select values that meet a certain condition.

SELECT voornaam, achternaam, bezoeker_id
FROM
(
  SELECT  f.naam AS festival_naam, b.voornaam AS voornaam, b.achternaam AS achternaam, b.bezoeker_id, strftime('%Y',datum) as "jaar"
  FROM festival f
   JOIN aanmeldingen a
  ON a.festival_id = f.festival_id
   JOIN bezoeker b
  ON b.bezoeker_id = a.bezoeker_id
)t
WHERE jaar = '2018' AND festival_naam = 'Mysteryland'
AND bezoeker_id in /* Subquery */
(
    SELECT bezoeker_id
    FROM
(
  /* Inline view */
    SELECT  f.naam AS festival_naam, b.voornaam AS voornaam, b.achternaam AS achternaam, b.bezoeker_id, strftime('%Y',datum) as "jaar"
    FROM festival f
     JOIN aanmeldingen a
    ON a.festival_id = f.festival_id
     JOIN bezoeker b
    ON b.bezoeker_id = a.bezoeker_id
)t
WHERE jaar = '2019' AND festival_naam = 'Mysteryland'
)
AND bezoeker_id IN /* Subquery */
(
SELECT bezoeker_id
FROM
(
  /* Inline view */
  SELECT  f.naam AS festival_naam, b.voornaam AS voornaam, b.achternaam AS achternaam, b.bezoeker_id, strftime('%Y',datum) as "jaar"
  FROM festival f
   JOIN aanmeldingen a
  ON a.festival_id = f.festival_id
   JOIN bezoeker b
  ON b.bezoeker_id = a.bezoeker_id
)t
WHERE jaar = '2021' AND festival_naam = 'Mysteryland'
)
ABCDEFGHIJ0123456789
voornaam
<chr>
achternaam
<chr>
bezoeker_id
<int>
PietJansen2
JanPeters1
BoudewijnStockovich35
WITH result_set AS /* CTE as result set */
(
    SELECT DISTINCT f.naam AS festival_naam, b.voornaam AS voornaam, b.achternaam AS achternaam, b.bezoeker_id, strftime('%Y',datum) as "jaar"
      FROM festival f
       JOIN aanmeldingen a
      ON a.festival_id = f.festival_id
       JOIN bezoeker b
      ON b.bezoeker_id = a.bezoeker_id
  )
  
  SELECT DISTINCT bezoeker_id, voornaam, achternaam, 
  CASE 
    WHEN festival_naam ='Mysteryland' AND jaar = '2018' THEN 'Ja'  /* Case as a replacement for Inline IF */
    WHEN jaar = '2019' AND jaar = '2021' THEN 'Ja' /* Consists of 3 mandatory parts: CASE, WHEN & END, ELSE is optional */
    ELSE 'Nee' END AS geweest
  from result_set
  ORDER BY geweest
ABCDEFGHIJ0123456789
bezoeker_id
<int>
voornaam
<chr>
achternaam
<chr>
geweest
<chr>
2PietJansenJa
1JanPetersJa
35BoudewijnStockovichJa
2PietJansenNee
3HansHendriksNee
4SimoneFrederiksNee
5Liesvan StratenNee
6ErikSmeetsNee
7LindaBouwerNee
8BobGerardsNee

3.8 What is the age distribution of the festival visitors?

The figure below shows the age distribution of the festival visitors by gender. What is obvious is that fewer 40+ men go to festivals compared to 40+ women. Up to the age of 32, more men than women attend festivals, while many young women (18 to 20) attend festivals. The average age of the festival visitors is 27 years and the median is 23. The oldest visitor is 52 for men and 54 for women. The youngest visitors of both men and women are both 18 years old. Man = Male, vrouw = Female

bezoeker$leeftijd <- as.numeric(difftime(Sys.Date(),bezoeker$geboorte_datum, units = "weeks"))/52.25 # age formula
bezoeker$leeftijd <- floor(bezoeker$leeftijd) # to round down

ggplot(bezoeker, aes(x=leeftijd, fill=geslacht)) + # plotting
  geom_histogram(position="identity",binwidth=2, alpha=0.6) + 
  scale_x_continuous(breaks=seq(18,60,2)) + # rearrange the axes
  scale_y_continuous(breaks=seq(0,15,1)) + # rearrange the axes
  geom_vline(aes(xintercept=mean(leeftijd)), color="purple", # Mean 
             linetype="dashed") +
  geom_vline(aes(xintercept=median(leeftijd)), color="black", # Median
             linetype="dashed") +
  labs(title="Age distribution Festival visitors",x="Age", y = "Count")+
  theme_bw(base_size = 16) + 
  scale_fill_manual(values=c("#00ccff", "#ff0000")) # colors

ggplot(bezoeker, aes(x=leeftijd)) + # plotting
  geom_histogram(position="identity",binwidth=2, alpha=0.6) + 
  scale_x_continuous(breaks=seq(18,60,2)) + # rearrange the axes
  scale_y_continuous(breaks=seq(0,15,1)) + # rearrange the axes
  labs(title="Age distribution Festival visitors by sex",x="Age", y = "Count")+
  theme_bw(base_size = 16) + 
  scale_fill_manual(values=c("#00ccff", "#ff0000")) + facet_wrap(~ geslacht) # separate visualization based on sex

3.9 What is the average age per gender of the festival visitors who attend Techno festivals in 2019?

Below is the average age per gender of the festival visitors who went to Techno festivals in 2019. This query consists of 3 parts: The query, inline view as result set and a subquery. First of all, use a cast to round the average age down because SQLite has no floor function. Then I use an inline view where I select all unique visitors who went to festivals in 2019. I also calculate to calculate the age. I also create a subquery that serves as a result set where visitors went to techno festivals. This, combined with the rest, provides an overview of the average age per gender of the festival visitors who go to techno festivals.

/* Cast is used for converting to int data type to round down eventually because SQLite has no floor function. I use an inline view as a result set in which I calculate someone's age with a formula. Finally, I use a subquery that indicates which visitors went to techno festivals.*/
SELECT geslacht ,cast ( AVG(leeftijd) as int ) - ( AVG(leeftijd) < cast ( AVG(leeftijd) as int )) AS gemiddelde_leeftijd 
FROM 
(
  SELECT DISTINCT b.bezoeker_id, voornaam, achternaam,
  (strftime('%Y', 'now') - strftime('%Y', geboorte_datum)) - (strftime('%m-%d', 'now') < strftime('%m-%d', geboorte_datum)) AS leeftijd,   geslacht, strftime('%Y', datum) AS jaar
  FROM bezoeker b
  JOIN aanmeldingen a ON a.bezoeker_id = b.bezoeker_id
  JOIN festival f ON f.festival_id = a.festival_id
  WHERE jaar = '2019' AND b.bezoeker_id IN
  (
     SELECT b.bezoeker_id
     FROM festival f
     JOIN aanmeldingen a ON a.festival_id = f.festival_id
     JOIN bezoeker b on b.bezoeker_id = a.bezoeker_id
     JOIN festival_categorie_festival fcf ON fcf.festival_id = f.festival_id
     JOIN festival_categorie fc ON fc.categorie_id = fcf.categorie_id
     WHERE fc.naam =='Techno'
  )
)t
GROUP BY geslacht
ABCDEFGHIJ0123456789
geslacht
<chr>
gemiddelde_leeftijd
<int>
man25
vrouw29

3.10 What is the average age of the festival visitors who went to Emporium in 2019?

Below is the average age of the festival visitors who went to Emporium in 2019. From the festival table I join 4 other tables: aanmeldingen(registrations), bezoeker(visitor), festival_categorie_festival(between table) & festival_categorie(festival category). Then I filter on the festival name and year of the festival. Then I group by sex and minimize all rows because I want to know the average age based on sex.

festival %>%
  inner_join(aanmeldingen, by='festival_id') %>%
  inner_join(bezoeker, by='bezoeker_id') %>%
  inner_join(festival_categorie_festival, by='festival_id') %>%
  inner_join(festival_categorie, by='categorie_id') %>%
  filter(naam.x == 'Emporium' & year(datum) =='2019') %>% # filtering
  group_by(geslacht) %>% # group by sex
  summarise(gemiddelde_leeftijd = mean(leeftijd.y)) # average age
ABCDEFGHIJ0123456789
geslacht
<chr>
gemiddelde_leeftijd
<dbl>
man33.0
vrouw27.5

3.11 What is the lowest, average and greatest revenue of the products from the sales table for each year?

Below is an overview of the revenue from the sales table for each year. For each year I show the lowest, average and highest turnover that was achieved in that year. From the verkoop_producten (sales_products) table I join 2 other tables: the between table and festival. Then I create a new column for the revenue: the price times the number of pieces is the revenue. Then I group by the year of each date and I minimize the rows with the corresponding revenue for each year.

verkoop_producten %>%
  inner_join(verkoop_producten_festival, by='verkoop_id') %>% # join matching values
  inner_join(festival, by='festival_id') %>% # join matching values
  mutate(omzet = prijs * aantal)  %>% # add new column for sales
  group_by(year(datum)) %>% # group by year
  summarise(gemiddelde_omzet = round(mean(omzet),2), # minimize on average revenue
            laagste_omzet = round(min(omzet),2), # minimize on lowest revenue
            hoogste_omzet = round(max(omzet),2)) # minimize on highest revenue
ABCDEFGHIJ0123456789
year(datum)
<dbl>
gemiddelde_omzet
<dbl>
laagste_omzet
<dbl>
hoogste_omzet
<dbl>
201825395.87440080000
201916906.47440062785
202125120.40440080000

3.12 How many pop festivals were organized per year?

Below is a small overview where you can see how many pop festivals are held each year. First of all, I select the festival table and join 4 other tables, 2 intermediate tables and the festival_category + location table. Then I group by each year and I filter by the music genre and finally I count this so that you can see how many pop festivals there are given each year.

festival %>%
  inner_join(festival_categorie_festival, by='festival_id') %>% # join matching values
  inner_join(festival_categorie, by='categorie_id') %>% # join matching values
  inner_join(locatie_festival, by='festival_id') %>% # join matching values
  inner_join(locatie, by='locatie_id') %>% # join matching values
  group_by(year(datum.x)) %>% # group by year
  filter(genre=='Pop') %>% # filter on pop
  count(genre) # count genres
ABCDEFGHIJ0123456789
year(datum.x)
<dbl>
genre
<chr>
n
<int>
2018Pop3
2019Pop3
2021Pop3
LS0tCnRpdGxlOiAiUHJvamVjdDogRmVzdGl2YXRlIChFREEpIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIyBBYm91dCB0aGlzIHByb2plY3QKT25lIGltcG9ydGFudCBub3RlOiB0aGlzIHByb2plY3Qgd2FzIG9yaWdpbmFsbHkgbWFkZSBpbiBEdXRjaC4gSSB0cmFuc2xhdGVkIGV2ZXJ5IGRlc2NyaXB0aW9uIGFuZCBjb21tZW50cyBpbiB0aGUgY29kZS4gT25seSB0YWJsZSBuYW1lcyBhcmUgaW4gRHV0Y2guCgpUaGlzIHByb2plY3QgaXMgYWxsIGFib3V0IHByb2Nlc3MgaW1wcm92ZW1lbnQgd2hlbiBvcmdhbml6aW5nIGZlc3RpdmFscy4gSSBkZXNpZ25lZCB0aGUgU1FMIGRhdGFiYXNlIGZvciB0aGlzIHByb2plY3QgZm9yIHRoZSBmZXN0aXZhbCBvcmdhbml6ZXJzIGFuZCB1c2VkIFIgdG8gbWFuaXB1bGF0ZSBhbmQgdmlzdWFsaXplIGRhdGEuCgojIyMgV29ya2Zsb3c6CgojIyMgMS4gRGF0YSBjb2xsZWN0aW9uCiMjIyAyLiBQcmUtcHJvY2Vzc2luZwojIyMgMy4gRXhwbG9yYXRvcnkgRGF0YSBBbmFseXNpcwoKIyMjIDEuMSBJbXBvcnRpbmcgbGlicmFyaWVzOgpgYGB7ciBlY2hvPVRSVUV9CmxpYnJhcnkoZHBseXIpICMgRGF0YSBtYW5pcHVsYXRpb24KbGlicmFyeShnZ3Bsb3QyKSAjIERhdGEgdmlzdWFsaXphdGlvbgpsaWJyYXJ5KFJTUUxpdGUpICMgRGF0YWJhc2UgY29ubmVjdGlvbgpsaWJyYXJ5KGZvcmNhdHMpICMgRmFjdG9ycwpsaWJyYXJ5KGx1YnJpZGF0ZSkgIyBEYXRlcwpsaWJyYXJ5KG9wZW54bHN4KSAjIEV4cG9ydCB0byBFeGNlbApsaWJyYXJ5KHJpbykgIyBFeHBvcnQgdG8gRXhjZWwKbGlicmFyeShnZ3JlcGVsKSAjIFBpZSBjaGFydApgYGAKCiMjIyAxLjIgRGF0YWJhc2UgY29ubmVjdGlvbiAKYGBge3IgZWNobz1UUlVFfQpjb24gPC0gZGJDb25uZWN0KFNRTGl0ZSgpLCAiL1VzZXJzL21pbGFucGF0dHkvRG9jdW1lbnRzL0J1c2luZXNzL1NlbWVzdGVyXzIvUi9Qcm9mdGFhay9GZXN0aXZhdGUyLmRiIikKYGBgCgojIyMgMS4zIENoZWNrIHdoZXRoZXIgdGhlIHRhYmxlcyBoYXZlIGJlZW4gbG9hZGVkIHN1Y2Nlc3NmdWxseQpgYGB7cn0KYXMuZGF0YS5mcmFtZShkYkxpc3RUYWJsZXMoY29uKSkKYGBgCgojIyMgMS40IENyZWF0ZSBvYmplY3RzIGZyb20gdGhlIHRhYmxlcwpgYGB7ciBlY2hvPVRSVUV9CmFhbm1lbGRpbmdlbiA8LSBkYlJlYWRUYWJsZShjb24sICdhYW5tZWxkaW5nZW4nKQpiZXNjaGlrYmFyZV9taWRkZWxlbiA8LSBkYlJlYWRUYWJsZShjb24sICdiZXNjaGlrYmFyZV9taWRkZWxlbicpCmJlem9la2VyIDwtIGRiUmVhZFRhYmxlKGNvbiwgJ2Jlem9la2VyJykKY2FtcGFnbmUgPC0gZGJSZWFkVGFibGUoY29uLCAnZmVzdGl2YWwnKQpjYW1wYWduZV9mZXN0aXZhbCA8LSBkYlJlYWRUYWJsZShjb24sICdjYW1wYWduZV9mZXN0aXZhbCcpCmZlc3RpdmFsIDwtIGRiUmVhZFRhYmxlKGNvbiwgJ2Zlc3RpdmFsJykKZmVzdGl2YWxfY2F0ZWdvcmllIDwtIGRiUmVhZFRhYmxlKGNvbiwgJ2Zlc3RpdmFsX2NhdGVnb3JpZScpCmZlc3RpdmFsX2NhdGVnb3JpZV9mZXN0aXZhbCA8LSBkYlJlYWRUYWJsZShjb24sICdmZXN0aXZhbF9jYXRlZ29yaWVfZmVzdGl2YWwnKQpmZXN0aXZhbF9wYXJ0bmVycyA8LSBkYlJlYWRUYWJsZShjb24sICdmZXN0aXZhbF9wYXJ0bmVycycpCmlua29vcCA8LSBkYlJlYWRUYWJsZShjb24sICdpbmtvb3AnKQoKaW5rb29wX2Zlc3RpdmFsIDwtIGRiUmVhZFRhYmxlKGNvbiwgJ2lua29vcF9mZXN0aXZhbCcpCmxvY2F0aWUgPC0gZGJSZWFkVGFibGUoY29uLCAnbG9jYXRpZScpCnBhcnRuZXJzIDwtIGRiUmVhZFRhYmxlKGNvbiwgJ3BhcnRuZXJzJykKc3BvbnNvcmVuIDwtIGRiUmVhZFRhYmxlKGNvbiwgJ3Nwb25zb3JlbicpCnRpY2tldF9jYXRlZ29yaWUgPC0gZGJSZWFkVGFibGUoY29uLCAndGlja2V0X2NhdGVnb3JpZScpCnRpY2tldHMgPC0gZGJSZWFkVGFibGUoY29uLCAndGlja2V0cycpCnZlcmtvb3BfcHJvZHVjdGVuIDwtIGRiUmVhZFRhYmxlKGNvbiwgJ3Zlcmtvb3BfcHJvZHVjdGVuJykKbG9jYXRpZV9mZXN0aXZhbCA8LSBkYlJlYWRUYWJsZShjb24sICdsb2NhdGllX2Zlc3RpdmFsJykKdmVya29vcF9wcm9kdWN0ZW5fZmVzdGl2YWwgPC0gZGJSZWFkVGFibGUoY29uLCAndmVya29vcF9wcm9kdWN0ZW5fZmVzdGl2YWwnKQp3ZXJrbmVtZXJzIDwtIGRiUmVhZFRhYmxlKGNvbiwgJ3dlcmtuZW1lcnMnKQoKd2Vya25lbWVyc193ZXJremFhbWhlZGVuIDwtIGRiUmVhZFRhYmxlKGNvbiwgJ3dlcmtuZW1lcnNfd2Vya3phYW1oZWRlbicpCndlcmt6YWFtaGVkZW4gPC0gZGJSZWFkVGFibGUoY29uLCAnd2Vya3phYW1oZWRlbicpCndlcmt6YWFtaGVkZW5fZmVzdGl2YWwgPC0gZGJSZWFkVGFibGUoY29uLCAnd2Vya3phYW1oZWRlbl9mZXN0aXZhbCcpCmBgYAoKIyMgMi4gUHJlLXByb2Nlc3NpbmcKVGhlIHNlY29uZCBzdGVwIGFmdGVyIGRhdGEgY29sbGVjdGlvbiBpcyB0aGUgcHJlLXByb2Nlc3Npbmcgb2YgZGF0YS4gVGhpcyBpbmNsdWRlcyB2YXJpb3VzIHRoaW5ncyBzdWNoIGFzIGNsYXNzIGxhYmVsbGluZyAoaW5kaWNhdGluZyB0aGUgY29ycmVjdCBkYXRhIHR5cGUpLCBkYXRhIGNsZWFuaW5nIChjb3JyZWN0aW5nLCBmb3IgZXhhbXBsZSwgaW5jb3JyZWN0bHkgc3BlbHQgbmFtZXMpIGFuZCB0aGUgaGFuZGxpbmcgb2YgbWlzc2luZyB2YWx1ZXMuIEZ1cnRoZXJtb3JlLCB0aGUgZGF0YSBJIHVzZSBmcm9tIG15IGRhdGFiYXNlIGlzIHRpZHkgYWNjb3JkaW5nIHRvIHRoZSAzIHJ1bGVzOgoKMS4gRWFjaCB2YXJpYWJsZSBoYXMgaXRzIG93biBjb2x1bW4uCjIuIEVhY2ggb2JzZXJ2YXRpb24gaGFzIGl0cyBvd24gcm93LgozLiBFYWNoIHZhbHVlIGhhcyBpdHMgb3duIGNlbGwuCgojIyMgMi4xIFRhYmxlIExvY2F0aW9uCldoaWxlIHNldHRpbmcgdXAgdGhlIGRhdGFiYXNlLCBhIGdyb3VwIG1lbWJlciByZXNwb25zaWJsZSBmb3IgdGhlIHRhYmxlIG1pc3dyaXR0ZW4gdGhlIHdvcmQgJ2NhcGFjaXRlaXQnKGNhcGFjaXR5KSBPZiBjb3Vyc2UsIHdlIHdhbnQgdG8gZml4IHRoaXMgYnVnIGFuZCBtYWtlIHN1cmUgaXQgZ2V0cyBjb3JyZWN0IGV2ZXJ5d2hlcmUuIFNpbmNlIGJlem9la2Vyc19jYXBpY2l0ZWl0ICh2aXNpdG9yX2NhcGFjaXR5KSBpcyBhIGNvbHVtbiwgaXQgY2FuIGVhc2lseSBiZSByZW5hbWVkIHdpdGggdGhlIGZ1bmN0aW9uIGByZW5hbWVgLgpgYGB7cn0KbG9jYXRpZSA8LWxvY2F0aWUgJT4lCiAgcmVuYW1lKGJlem9la2Vyc19jYXBhY2l0ZWl0ID0gYmV6b2VrZXJzX2NhcGljaXRlaXQpCmBgYApUbyBzZWUgaWYgdGhlIGNoYW5nZSBpcyBjb3JyZWN0OgpgYGB7cn0KY29sbmFtZXMobG9jYXRpZSkKYGBgCgojIyAzLiBFREEKCiMjIyAzLjEgV2hhdCBpcyB0aGUgdmlzaXRvciBjYXBhY2l0eSBwZXIgbG9jYXRpb24/ClRoZSBmaWd1cmUgYmVsb3cgc2hvd3MgYWxsIGxvY2F0aW9ucyB3aXRoIHRoZSBjb3JyZXNwb25kaW5nIHZpc2l0b3IgY2FwYWNpdHkuIFRoZXNlIGFyZSBhIHRvdGFsIG9mIDE1IGxvY2F0aW9ucywgZWFjaCBvZiB3aGljaCBoYXMgaXRzIGFyZWFzLiBXaGF0IGlzIHN0cmlraW5nIGlzIHRoYXQgdGhlIHRvcCAzIGxvY2F0aW9ucyB3aXRoIHRoZSBtb3N0IGNhcGFjaXR5IHRha2UgdXAgNTkuMDElIG9mIHRoZSB0b3RhbCBjYXBhY2l0eSBhbmQgdGhlIDUgbG9jYXRpb25zIHdpdGggdGhlIGxlYXN0IGNhcGFjaXR5IG9ubHkgdGFrZSB1cCA2LjgzJS4gQnkgY2hhbmNlLCBpdCBkb2VzIG5vdCBtZWFuIHRoYXQgbG9jYXRpb25zIHdpdGggbW9yZSBhcmVhcyBjYW4gaGF2ZSBtb3JlIHZpc2l0b3JzLiBUaGUgbG9jYXRpb24gd2l0aCB0aGUgbW9zdCBhcmVhcyBpcyB0aGUgQmVyZW5kb25jayBpbiBXaWpjaGVuLCB3aGVyZSB0aGUgYW5udWFsIEVtcG9yaXVtIGZlc3RpdmFsIGlzIGhlbGQuIFRoZXNlIGxvY2F0aW9ucyBoYXZlIG5vIGZld2VyIHRoYW4gMTcgYXJlYXMgYW5kIHRoZXkgb25seSB0YWtlIHVwIDYuMjElIG9mIHRoZSB0b3RhbCBjYXBhY2l0eS4gVGhpcyBzdWJzdGFudGlhdGlvbiBoYXMgYmVlbiB2YWxpZGF0ZWQgd2l0aCBjYWxjdWxhdGlvbiBmdW5jdGlvbnMgaW4gRXhjZWwgYW5kIGlzIGFsc28gc3RvcmVkIHRoZXJlLgpgYGB7cn0KbG9jYXRpZSAlPiUKICBtdXRhdGUobmFhbSA9IGZjdF9yZW9yZGVyKG5hYW0sIGJlem9la2Vyc19jYXBhY2l0ZWl0KSkgJT4lICMgTXV0YXRlIGZvciB0aGUgbmV3IGNvbHVtbiB3aXRoIHRoZSBjb3JyZWN0IGZhY3RvciBvcmRlcgogIGdncGxvdCggYWVzKHg9bmFhbSwgeT1iZXpvZWtlcnNfY2FwYWNpdGVpdCkpICsgIyBGaXJzdCAyIGxheWVycyBvZiBhIHBsb3Q6IERhdGEgKyBBZXN0aGV0aWNzCiAgICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIsIGZpbGw9IiNmNjgwNjAiLCBhbHBoYT0uNywgd2lkdGg9LjYpICsgIyAzcmQgbGF5ZXIgb2YgdGhlIHBsb3QsIHN0YXQ9J2lkZW50aXR5JyBiZWNhdXNlIEkgaGF2ZSB2YWx1ZXMgZm9yIHgmeQogICAgY29vcmRfZmxpcCgpICsgIyBTd2l0Y2hpbmcgYXhpcwogICAgeGxhYignTG9jYXRpb24nKSArCiAgICB5bGFiKCdWaXNpdG9yIGNhcGFjaXR5JykrCiAgICBnZ3RpdGxlKCdWaXNpdG9yIGNhcGFjaXR5IHBlciBsb2NhdGlvbicpKwogICAgdGhlbWVfYncoYmFzZV9zaXplID0gMTUpCmBgYAoKIyMjIDMuMiBXaGF0IGFyZSB0aGUgc2FsZXMgcGVyIG1vbnRoPwpCZWxvdyB5b3UgY2FuIHNlZSB0aGUgc2FsZXMgcGVyIG1vbnRoIGRpdmlkZWQgb3ZlciAyMDE4LCAyMDE5IGFuZCAyMDIxLiBXaGF0IGNhbiBiZSBzZWVuIGluIHRoaXMgZmlndXJlIGlzIHRoYXQgdGhlcmUgYXJlIHllYXJzIHdoZXJlIHNvbWUgbW9udGhzIGhhdmUgbm8gc2FsZXMuIEZvciAyMDE4IHRoaXMgd2lsbCBiZSBBcHJpbCBhbmQgZm9yIDIwMTkgYW5kIDIwMjEgaXQgd2lsbCBib3RoIGJlIE1hcmNoLiBpbiAyMDE4IHRvdGFsIHNhbGVzIHdlcmUg4oKsIDQ5MSwwNDMuMDAgY29tcGFyZWQgdG8gMjAxOSBhbmQgMjAyMSwgd2hlcmUgaXQgd2FzIGJvdGgg4oKsIDExMywxMDQuMDAuIEF2ZXJhZ2Ugc2FsZXMgaW4gMjAxOCB3ZXJlIOKCrCA2MSwzODEuMjUsIGluIDIwMTkgYW5kIDIwMjEgaXQgd2FzIGJvdGgg4oKsIDE0LDEzOC44OC4gVGhlc2UgY2FsY3VsYXRpb25zIGhhdmUgYmVlbiB2YWxpZGF0ZWQgd2l0aCBFeGNlbCdzIGNhbGN1bGF0aW9uIGZ1bmN0aW9ucy4KCmBgYHtzcWwgY29ubmVjdGlvbj1jb24sIG91dHB1dC52YXI9J2FmemV0J30KU0VMRUNUIHByb2R1Y3QsIFJPVU5EKHByaWpzLDIpIEFTIHByaWpzLCBhYW50YWwsIGxldmVyYW5jaWVyLCBkYXR1bSAvKiBSb3VuZCBvZmYgdG8gMiBkZWNpbWFsIHBsYWNlcyBhdCBSb3VuZCBiZWNhdXNlIGl0IGlzIGEgcHJpY2UgKi8KRlJPTSBpbmtvb3AgaQpKT0lOIGlua29vcF9mZXN0aXZhbCBpbmsKT04gaW5rLmlua29vcF9pZCA9IGkuaW5rb29wX2lkCkpPSU4gZmVzdGl2YWwgZgpPTiBpbmsuZmVzdGl2YWxfaWQgPSBmLmZlc3RpdmFsX2lkCmBgYAoKYGBge3J9CmFmemV0JGFhbnRhbFszMV0gPC0gMzAwICMgQ2hhbmdlIHF1YW50aXR5IGJlY2F1c2UgdGhpcyBlcnJvciBpcyBpbiB0aGUgZGF0YWJhc2UKYGBgCgpgYGB7ciBmaWcud2lkdGg9MTJ9CmFmemV0ICU+JQogIG11dGF0ZShqYWFyID0geWVhcihkYXR1bSksIG1hYW5kPSBtb250aChkYXR1bSksIGFmemV0X3ByaWpzID0gYWFudGFsICogcHJpanMpICU+JSAjIENyZWF0ZSBuZXcgY29sdW1ucyBmb3IgdGhlIHllYXIsIG1vbnRoIGFuZCBzYWxlcwogIGdyb3VwX2J5KGphYXIsIG1hYW5kKSAlPiUgIyBHcm91cCBieSB5ZWFyIGFuZCBtb250aAogIHN1bW1hcmlzZShhZnpldCA9IHN1bShhZnpldF9wcmlqcykpICU+JSAjIE1pbmltaXppbmcgYWxsIHJvd3MgYnkgbWVhbnMgb2YgYW4gYWNjdW11bGF0ZWQgc2FsZXMgcGVyIHllYXIgYW5kIG1vbnRoCgpnZ3Bsb3QoYWVzKG1hYW5kLCBhZnpldCwgZmlsbD1mYWN0b3IobWFhbmQpKSkgKyBnZW9tX2JhcihzdGF0ID0gJ2lkZW50aXR5JykgKyBmYWNldF9ncmlkKH4gamFhcikgKyAjQXMgY29sb3IgSSB1c2UgdGhlIGZhY3RvciBsZXZlbHMgb2YgbW9udGgsIHN0YXQgPSAnaWRlbnRpdHknIGJlY2F1c2UgSSB1c2UgdmFsdWVzIGZvciB4ICYgeSwgdGhlbiBmYWNldF9ncmlkIHRvIGRpc3RyaWJ1dGUgdGhlIHZpc3VhbGl6YXRpb25zIG92ZXIgdGhlIHllYXJzLgogIHNjYWxlX3lfY29udGludW91cyhicmVha3M9c2VxKDAsMjAwMDAwLDEwMDAwKSkgKyAjIHkgYXhpcyByZXNldAogIHNjYWxlX3hfY29udGludW91cyhicmVha3M9c2VxKDMsOSwxKSkgKyAjIHJlc2V0IGF4aXMKICBnZ3RpdGxlKCdTYWxlcyBwZXIgbW9udGggZGl2aWRlZCBvdmVyIDIwMTgsIDIwMTkgJiAyMDIxJykgKwogIHhsYWIoJ01vbnRoJykgKwogIHlsYWIoJ1NhbGVzIGluIOKCrCcpICsKICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNikgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKYGBgCgpgYGB7cn0KYWZ6ZXRfb3V0cHV0IDwtIGFmemV0ICU+JSAjIGNyZWF0ZSBhbiBvYmplY3QgZnJvbSB0aGlzIGRhdGEgdG8gbGF0ZXIgdmFsaWRhdGUgaXQgaW4gZXhjZWwKICBtdXRhdGUoamFhciA9IHllYXIoZGF0dW0pLCBtYWFuZD0gbW9udGgoZGF0dW0pLCBhZnpldF9wcmlqcyA9IGFhbnRhbCAqIHByaWpzKQpgYGAKCmBgYHtyfQpleHBvcnQoYWZ6ZXRfb3V0cHV0LCAnYWZ6ZXQueGxzeCcpICMgRXhwb3J0CmBgYAoKIyMjIDMuMyBXaGljaCBGZXN0aXZhbHMgaGF2ZSBhIHNwb25zb3Igd2hvIHNwb25zb3JzIGFuIGl0ZW0gd2l0aCBhIHZhbHVlIGFib3ZlIOKCrCA1NTAwPwpUaGUgZmlndXJlIGJlbG93IHNob3dzIHdoaWNoIGZlc3RpdmFscyBoYXZlIHNwb25zb3JlZCBhbiBpdGVtIHdpdGggYSB2YWx1ZSBhYm92ZSDigqwgNTUwMC4gV2hhdCBpcyBvYnZpb3VzIGlzIHRoYXQgdGhlIDIgZmVzdGl2YWxzIHRoYXQgaGF2ZSB0aGUgaGlnaGVzdCB2YWx1ZSBoYXZlIGJvdGggYmVlbiBnaXZlbiBhIGJ1ZGdldCB0aGF0IHRvZ2V0aGVyIGlzIDQ5LjglIG9mIHRoZSB0b3RhbCBzcG9uc29yZWQgdmFsdWVzIGFib3ZlIOKCrCA1NTAwLiBUaGUgNSBmZXN0aXZhbHMgd2l0aCB0aGUgbG93ZXN0IHNwb25zb3JlZCB2YWx1ZXMgYWNjb3VudCBmb3Igb25seSAxNS4zNCUgb2YgdGhlIHRvdGFsIHNwb25zb3JlZCBhbW91bnQuIFRoZXNlIGNhbGN1bGF0aW9ucyBoYXZlIGJlZW4gdmFsaWRhdGVkIHdpdGggZXhjZWwuCmBgYHtzcWwgY29ubmVjdGlvbj1jb24sIG91dHB1dC52YXI9J3Nwb25zb3JfbGlqc3RqZSd9Ci8qIFVzZSBTUUwgcXVlcnkgYW5kIHNhdmUgdGhpcyBvdXRwdXQgdG8gdXNlIGluIFIuIElubGluZSB2aWV3IGFzIHJlc3VsdCBzZXQgYW5kIGFkZCBhIG5ldyBjb2x1bW4gZm9yIHllYXIuIFRoZW4gZmlsdGVyIGZvciB2YWx1ZXMgYWJvdmUgNSw1MDAgZXVyb3MgYW5kIHZhbHVlcyB0aGF0IGFyZSBub3QgbnVsbC4gKi8KU0VMRUNUIGZlc3RpdmFsX25hYW0sIHNwb25zb3JfbmFhbSwgaXRlbSwgd2FhcmRlLCBqYWFyCkZST00gIAooCiAgICBTRUxFQ1QgZi5uYWFtIEFTIGZlc3RpdmFsX25hYW0sIGl0ZW0sIHMubmFhbSBBUyBzcG9uc29yX25hYW0sIHdhYXJkZSwgc3RyZnRpbWUoJyVZJyxkYXR1bSkgYXMgImphYXIiCiAgICBGUk9NIGZlc3RpdmFsIGYKICAgIEpPSU4gYmVzY2hpa2JhcmVfbWlkZGVsZW4gYm0KICAgIE9OIGJtLmZlc3RpdmFsX2lkID0gZi5mZXN0aXZhbF9pZAogICAgSk9JTiBzcG9uc29yZW4gcwogICAgT04gcy5zcG9uc29yX2lkID0gYm0uc3BvbnNvcl9pZAopdApXSEVSRSB3YWFyZGUgPiA1NTAwIEFORCB3YWFyZGUgSVMgTk9UIE5VTEwKQU5EIGphYXIgPSAnMjAxOScKT1JERVIgQlkgd2FhcmRlCmBgYAoKYGBge3IgZmlnLndpZHRoPTE1fQpvcHRpb25zKHNjaXBlbj05OTkpICMgUmVtb3ZlIG1hdGggbm90YXRpb24Kc3BvbnNvcl9saWpzdGplPC0gZGF0YS5mcmFtZShzcG9uc29yX2xpanN0amUpWzE6MTAsXSAjIFZhbGlkYXRlIG15IFNRTCBjaHVuay4gVGhlIG91dHB1dCBpbmRpY2F0ZWQgMTAgaXRlbXMgYWJvdmUgNTAwMCBldXJvIGFuZCAzIG51bGwgdmFsdWVzLCB3aXRoIHRoaXMgSSBzZWxlY3Qgb25seSB0aG9zZSAxMCBpdGVtcyB0aGF0IGhhdmUgYSB2YWx1ZSBhbmQgdGhvc2UgMyBvdGhlcnMgYXJlIGRyb3BwZWQuCiAgc3BvbnNvcl9saWpzdGplICU+JQogIG11dGF0ZShmZXN0aXZhbF9uYWFtID0gZmN0X3Jlb3JkZXIoZmVzdGl2YWxfbmFhbSwgd2FhcmRlKSkgJT4lICMgdXNlIG9mIGZvcmNhdHMgdG8gcmVhcnJhbmdlIHRoZSBmYWN0b3IgbGV2ZWxzIGJhc2VkIG9uIHRoZSB2YWx1ZXMgdGhleSBoYXZlIHNvIHRoYXQgdGhleSBnbyBmcm9tIGhpZ2ggdG8gbG93CiAgZ2dwbG90KGFlcyhmZXN0aXZhbF9uYWFtLCB3YWFyZGUsIGZpbGw9aXRlbSkpICsgICMgYXMgY29sb3IgSSB3YW50IHRvIHNob3cgdGhlIGl0ZW0gYmVpbmcgc3BvbnNvcmVkCiAgZ2VvbV9iYXIoc3RhdD0naWRlbnRpdHknLCB3aWR0aCA9IC42KSArICMgZm9yIHRoZSAyIHZhbHVlcwogIGNvb3JkX2ZsaXAoKSArICMgU3dpdGNoaW5nIHRoZSB4ICYgeSBheGlzCiAgZ2d0aXRsZSgnRmVzdGl2YWxzIGluIDIwMTkgdGhhdCB3ZXJlIHNwb25zb3JlZCAxIGl0ZW0gd2l0aCBhIHZhbHVlIGFib3ZlIOKCrCA1NTAwJykgKwogIHhsYWIoJ0Zlc3RpdmFsIG5hbWUnKSArCiAgeWxhYignVmFsdWUgaXRlbSBpbiDigqwnKSArCiAgdGhlbWVfYncoYmFzZV9zaXplID0gMTYpICsKICBndWlkZXMoZmlsbD1ndWlkZV9sZWdlbmQodGl0bGU9Ik5hbWUgSXRlbSIpKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcz1zZXEoMCwxNTAwMDAsMjAwMDApKSAjIHJlYXJyYW5nZSB5IGF4aXMKYGBgCmBgYHtyfQpleHBvcnQoc3BvbnNvcl9saWpzdGplLCAnc3BvbnNvcnMueGxzeCcpICMgZXhwb3J0IGZvciB2YWxpZGF0aW9uCmBgYAoKCiMjIyAzLjQgV2hpY2ggRmVzdGl2YWxzIGhhdmUgbW9yZSB0aGFuIDMgcGFydG5lcnM/CkZvciB0aGlzIHF1ZXJ5LCBJIGFtIHVzaW5nIGEgQ1RFIGFzIGEgcmVzdWx0c2V0LiBJIGhlcmVieSBzaG93IHdoaWNoIGZlc3RpdmFscyBoYXZlIG1vcmUgdGhhbiAzIHBhcnRuZXJzLiBJbiB0aGUgQ1RFIEkgc2VsZWN0IHRoZSBmZXN0aXZhbHMgYW5kIGNvdW50IHRoZSBudW1iZXIgb2YgcGFydG5lcnMgdGhleSBoYXZlLiBJIGhlcmVieSBqb2luIG9uIDIgdGFibGVzOiBwYXJ0bmVycyBhbmQgdGhlIGludGVybWVkaWF0ZSB0YWJsZSBmZXN0aXZhbF9wYXJ0bmVycy4gVGhlbiBJIGdyb3VwIGJ5IGZlc3RpdmFsIG5hbWUuIEluIHRoZSBxdWVyeSwgSSBzZWxlY3QgYWxsIGZlc3RpdmFscyB0aGF0IGhhdmUgbW9yZSB0aGFuIDMgcGFydG5lcnMuIEkgdmFsaWRhdGVkIHRoaXMgYnkgbG9va2luZyBpbiB0aGUgZGF0YWJhc2Ugd2hpY2ggZmVzdGl2YWxzIGhhdmUgd2hpY2ggcGFydG5lcnMuCmBgYHtzcWwgY29ubmVjdGlvbj1jb259CldJVEggZmVzdGl2YWxfcGFydG5lcnNfY3RlCkFTCigKU0VMRUNUIGYubmFhbSBBUyBmZXN0aXZhbF9uYWFtLCBDT1VOVChwLm5hYW0pIEFTIGFhbnRhbF9wYXJ0bmVycyAvKiBQYXJ0bmVycyBjb3VudCBwZXIgZmVzdGl2YWwgKi8KRlJPTSBmZXN0aXZhbCBmCkpPSU4gZmVzdGl2YWxfcGFydG5lcnMgZnAKT04gZnAuZmVzdGl2YWxfaWQgPSBmLmZlc3RpdmFsX2lkCkpPSU4gcGFydG5lcnMgcCAKT04gcC5wYXJ0bmVyX2lkID0gZnAucGFydG5lcl9pZApHUk9VUCBCWSBmLm5hYW0KKQoKU0VMRUNUIGZlc3RpdmFsX25hYW0sIGFhbnRhbF9wYXJ0bmVycwpGUk9NIGZlc3RpdmFsX3BhcnRuZXJzX2N0ZQpXSEVSRSBhYW50YWxfcGFydG5lcnMgPiAzIC8qIFNlbGVjdCBmZXN0aXZhbHMgdGhhdCBoYXZlIG1vcmUgdGhhbiAzIHBhcnRuZXJzLiAqLwoKYGBgCgoKIyMjIDMuNSBXaGljaCBwdXJjaGFzaW5nIHN1cHBsaWVyIHN1cHBsaWVzIHRoZSBtb3N0IHByb2R1Y3RzPwpJbiB0aGlzIHF1ZXJ5LCBJIHNob3cgd2hpY2ggc3VwcGxpZXIgZGVsaXZlcnMgdGhlIG1vc3QgcHJvZHVjdHMgaW4gbnVtYmVycyBhbmQgd2hhdCBwZXJjZW50YWdlIHRoaXMgaXMgb2YgdGhlIHRvdGFsIGRlbGl2ZXJpZXMuIEZpcnN0IG9mIGFsbCwgSSBhZGQgdXAgdGhlIHByb2R1Y3RzIGJ5IGdyb3VwaW5nIHRoZW0gYnkgdGhlIHN1cHBsaWVyIG5hbWUsIHRoZW4gSSBjb3VudCBhbGwgcHJvZHVjdHMgYW5kIGRpdmlkZSB0aGVtIGJ5IGFsbCB0aGUgcHJvZHVjdHMgdGhhdCBhcmUgaW4gdGhlIHB1cmNoYXNlIHRhYmxlLiBJIHJvdW5kIHRoaXMgdG8gMiBkZWNpbWFsIHBsYWNlcy4gQmVsb3cgeW91IGNhbiBzZWUgdGhhdCB0aGUgTWFrcm8gc3VwcGxpZXMgbm8gbGVzcyB0aGFuIDMzLjMzJSBvZiBhbGwgcHJvZHVjdHMgYW5kIHRoZSBudW1iZXJzIDIgYW5kIDMgYXJlIGFsc28gZ29vZCBmb3IgMzMuMzMlIGluIHRvdGFsLiBUaGlzIG1lYW5zIHRoYXQgb25seSB0aGUgdG9wIDMgc3VwcGxpZXJzIGFscmVhZHkgc3VwcGx5IDY2LjY2JSBvZiBhbGwgcHJvZHVjdHMuIFRoZXNlIGNhbGN1bGF0aW9ucyBoYXZlIGJlZW4gdmFsaWRhdGVkIGluIGV4Y2VsLgpgYGB7c3FsIGNvbm5lY3Rpb249Y29ufQpTRUxFQ1QgbGV2ZXJhbmNpZXIsQ09VTlQocHJvZHVjdCkgQVMgYWFudGFsLCBST1VORCgxMDAuMCAqIENPVU5UKCopIC8gKFNFTEVDVCBDT1VOVCgqKSBGUk9NIGlua29vcCksMikgQVMgcGVyY2VudGFnZSAvKiBjYWxjdWxhdGUgcGVyY2VudGFnZSAqLwpGUk9NIGlua29vcApHUk9VUCBCWSBsZXZlcmFuY2llcgpPUkRFUiBCWSBwZXJjZW50YWdlIERFU0MKYGBgCgojIyMgMy42IFRoZSByYXRpbyBvZiBzZXggb2YgdmlzaXRvcnMKQmVsb3cgeW91IGNhbiBzZWUgdGhlIGdlbmRlciByYXRpbyBvZiB0aGUgZmVzdGl2YWwgdmlzaXRvcnMuIDUyJSBvZiB0aGUgZmVzdGl2YWwgdmlzaXRvcnMgYXJlIG1hbGUgd2hpbGUgNDglIG9mIHRoZSB2aXNpdG9ycyBhcmUgZmVtYWxlKHZyb3V3KS4gRm9yIHRoaXMgY2h1bmsgSSBkaWQgdGhlIGZvbGxvd2luZzogZnJvbSB0aGUgdmlzaXRvciB0YWJsZSwgSSBzdGFydGVkIGdyb3VwaW5nIGJ5IGdlbmRlci4gVGhlbiBJIHN0YXJ0ZWQgdG8gbWluaW1pemUgYWxsIHJvd3MgYnkgdXNpbmcgc3VtbWFyaXNlIGFuZCBjb3VudGluZyB0aGUgcm93cyBmb3IgZWFjaCBnZW5kZXIuIFRoZW4gSSBzdGFydGVkIHBsb3R0aW5nIGV2ZXJ5dGhpbmcuIEkgd2FudCB0byBwdXQgdGhlIHBlcmNlbnRhZ2VzIG9uIHRoZSBZLWF4aXMgYW5kIEkgd2FudCB0byB1c2UgdGhlIGxldmVscyBvZiB0aGUgZmFjdG9yIGFzIGNvbG91ci4gVGhpcyBjaHVuayBoYXMgYmVlbiB2YWxpZGF0ZWQgdXNpbmcgYW4gU1FMIHF1ZXJ5IHRoYXQgZ2l2ZXMgdGhlIHNhbWUgcmVzdWx0LgpgYGB7cn0KYmV6b2VrZXIgJT4lCiAgZ3JvdXBfYnkoZ2VzbGFjaHQpICU+JSAjIGdyb3VwIGJ5CiAgc3VtbWFyaXNlKGFhbnRhbCA9IG4oKSkgJT4lICMgY291bnQKICBtdXRhdGUocHJvY2VudCA9IHJvdW5kKDEwMCAqIGFhbnRhbCAvIHN1bShhYW50YWwpLCAxKSkgJT4lICMgY3JlYXRlIG5ldyBjb2x1bW4KZ2dwbG90KGFlcyh4ID0gJycsIHkgPSBhYW50YWwgLGZpbGwgPSBmYWN0b3IoZ2VzbGFjaHQpKSkgKyAjIHBsb3R0aW5nCiAgZ2VvbV9iYXIod2lkdGggPSAxLCBzdGF0PSdpZGVudGl0eScpICsgIyBzdGF0ID0gJ2lkZW50aXR5JyBiZWNhdXNlIHggYW5kIHkgYXJlIHVzZWQKICBjb29yZF9wb2xhcih0aGV0YSA9ICJ5IikgKyAKICB0aGVtZV92b2lkKCkgKyAjIHJlbW92ZSBiYWNrZ3JvdW5kIGxpbmVzCiAgZ2d0aXRsZSgnVGhlIHJhdGlvIG9mIHNleCBvZiB2aXNpdG9ycycpICsKICBndWlkZXMoZmlsbD1ndWlkZV9sZWdlbmQodGl0bGU9IlNleCIpKSArIAogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKCIjMDBjY2ZmIiwgIiNmZjAwMDAiKSkgKyAjIGNvbG9ycwogIGdlb21fbGFiZWxfcmVwZWwoYWVzKGxhYmVsID0gcHJvY2VudCksIHNpemUgPSA1LCBzaG93LmxlZ2VuZCA9IEYpIApgYGAKClRoZSB2YWxpZGF0aW9uOgpgYGB7c3FsIGNvbm5lY3Rpb249Y29ufQpzZWxlY3QgZ2VzbGFjaHQsICBST1VORChDT1VOVCgqKSAqMTAwIC8gKHNlbGVjdCBjb3VudCgqKSBGUk9NIGJlem9la2VyKSwyKSBBUyBwZXJjZW50YWdlCkZST20gYmV6b2VrZXIKZ3JvdXAgYnkgZ2VzbGFjaHQKYGBgCgojIyMgMy43IFdoaWNoIHZpc2l0b3JzIHdlbnQgdG8gTXlzdGVyeWxhbmQgaW4gMjAxOCBhbmQgaGF2ZSBhbHNvIGJlZW4gdG8gTXlzdGVyeWxhbmQgaW4gc3Vic2VxdWVudCB5ZWFycz8KQmVsb3cgeW91IGNhbiBzZWUgd2hpY2ggZmVzdGl2YWwgdmlzaXRvcnMgaGF2ZSBiZWVuIHRvIE15c3RlcnlsYW5kIGluIDIwMTggYW5kIHRoZW4gcmV0dXJuZWQgaW4gY29uc2VjdXRpdmUgeWVhcnMuIFRoZSBmaXJzdCBxdWVyeSBjb25zaXN0cyBvZiBhbiBpbmxpbmUgdmlldyBhbmQgMiBzdWJxdWVyaWVzLCB0aGUgbGFzdCB2YWxpZGF0aW9uIHF1ZXJ5IGNvbnNpc3RzIG9mIGEgQ1RFIGFuZCBhIHF1ZXJ5LiBIZXJlIEkgdXNlIGEgQ0FTRSBzdGF0ZW1lbnQgYXMgYW4gSW5saW5lIElGIHRvIHNlbGVjdCB2YWx1ZXMgdGhhdCBtZWV0IGEgY2VydGFpbiBjb25kaXRpb24uCmBgYHtzcWwgY29ubmVjdGlvbj1jb259ClNFTEVDVCB2b29ybmFhbSwgYWNodGVybmFhbSwgYmV6b2VrZXJfaWQKRlJPTQooCiAgU0VMRUNUICBmLm5hYW0gQVMgZmVzdGl2YWxfbmFhbSwgYi52b29ybmFhbSBBUyB2b29ybmFhbSwgYi5hY2h0ZXJuYWFtIEFTIGFjaHRlcm5hYW0sIGIuYmV6b2VrZXJfaWQsIHN0cmZ0aW1lKCclWScsZGF0dW0pIGFzICJqYWFyIgogIEZST00gZmVzdGl2YWwgZgogICBKT0lOIGFhbm1lbGRpbmdlbiBhCiAgT04gYS5mZXN0aXZhbF9pZCA9IGYuZmVzdGl2YWxfaWQKICAgSk9JTiBiZXpvZWtlciBiCiAgT04gYi5iZXpvZWtlcl9pZCA9IGEuYmV6b2VrZXJfaWQKKXQKV0hFUkUgamFhciA9ICcyMDE4JyBBTkQgZmVzdGl2YWxfbmFhbSA9ICdNeXN0ZXJ5bGFuZCcKQU5EIGJlem9la2VyX2lkIGluIC8qIFN1YnF1ZXJ5ICovCigKCVNFTEVDVCBiZXpvZWtlcl9pZAoJRlJPTQooCiAgLyogSW5saW5lIHZpZXcgKi8KCVNFTEVDVCAgZi5uYWFtIEFTIGZlc3RpdmFsX25hYW0sIGIudm9vcm5hYW0gQVMgdm9vcm5hYW0sIGIuYWNodGVybmFhbSBBUyBhY2h0ZXJuYWFtLCBiLmJlem9la2VyX2lkLCBzdHJmdGltZSgnJVknLGRhdHVtKSBhcyAiamFhciIKCUZST00gZmVzdGl2YWwgZgoJIEpPSU4gYWFubWVsZGluZ2VuIGEKCU9OIGEuZmVzdGl2YWxfaWQgPSBmLmZlc3RpdmFsX2lkCgkgSk9JTiBiZXpvZWtlciBiCglPTiBiLmJlem9la2VyX2lkID0gYS5iZXpvZWtlcl9pZAopdApXSEVSRSBqYWFyID0gJzIwMTknIEFORCBmZXN0aXZhbF9uYWFtID0gJ015c3RlcnlsYW5kJwopCkFORCBiZXpvZWtlcl9pZCBJTiAvKiBTdWJxdWVyeSAqLwooClNFTEVDVCBiZXpvZWtlcl9pZApGUk9NCigKICAvKiBJbmxpbmUgdmlldyAqLwogIFNFTEVDVCAgZi5uYWFtIEFTIGZlc3RpdmFsX25hYW0sIGIudm9vcm5hYW0gQVMgdm9vcm5hYW0sIGIuYWNodGVybmFhbSBBUyBhY2h0ZXJuYWFtLCBiLmJlem9la2VyX2lkLCBzdHJmdGltZSgnJVknLGRhdHVtKSBhcyAiamFhciIKICBGUk9NIGZlc3RpdmFsIGYKICAgSk9JTiBhYW5tZWxkaW5nZW4gYQogIE9OIGEuZmVzdGl2YWxfaWQgPSBmLmZlc3RpdmFsX2lkCiAgIEpPSU4gYmV6b2VrZXIgYgogIE9OIGIuYmV6b2VrZXJfaWQgPSBhLmJlem9la2VyX2lkCil0CldIRVJFIGphYXIgPSAnMjAyMScgQU5EIGZlc3RpdmFsX25hYW0gPSAnTXlzdGVyeWxhbmQnCikKYGBgCgpgYGB7c3FsIGNvbm5lY3Rpb249Y29ufQpXSVRIIHJlc3VsdF9zZXQgQVMgLyogQ1RFIGFzIHJlc3VsdCBzZXQgKi8KKAoJU0VMRUNUIERJU1RJTkNUIGYubmFhbSBBUyBmZXN0aXZhbF9uYWFtLCBiLnZvb3JuYWFtIEFTIHZvb3JuYWFtLCBiLmFjaHRlcm5hYW0gQVMgYWNodGVybmFhbSwgYi5iZXpvZWtlcl9pZCwgc3RyZnRpbWUoJyVZJyxkYXR1bSkgYXMgImphYXIiCgkgIEZST00gZmVzdGl2YWwgZgoJICAgSk9JTiBhYW5tZWxkaW5nZW4gYQoJICBPTiBhLmZlc3RpdmFsX2lkID0gZi5mZXN0aXZhbF9pZAoJICAgSk9JTiBiZXpvZWtlciBiCgkgIE9OIGIuYmV6b2VrZXJfaWQgPSBhLmJlem9la2VyX2lkCiAgKQogIAogIFNFTEVDVCBESVNUSU5DVCBiZXpvZWtlcl9pZCwgdm9vcm5hYW0sIGFjaHRlcm5hYW0sIAogIENBU0UgCglXSEVOIGZlc3RpdmFsX25hYW0gPSdNeXN0ZXJ5bGFuZCcgQU5EIGphYXIgPSAnMjAxOCcgVEhFTiAnSmEnICAvKiBDYXNlIGFzIGEgcmVwbGFjZW1lbnQgZm9yIElubGluZSBJRiAqLwoJV0hFTiBqYWFyID0gJzIwMTknIEFORCBqYWFyID0gJzIwMjEnIFRIRU4gJ0phJyAvKiBDb25zaXN0cyBvZiAzIG1hbmRhdG9yeSBwYXJ0czogQ0FTRSwgV0hFTiAmIEVORCwgRUxTRSBpcyBvcHRpb25hbCAqLwoJRUxTRSAnTmVlJyBFTkQgQVMgZ2V3ZWVzdAogIGZyb20gcmVzdWx0X3NldAogIE9SREVSIEJZIGdld2Vlc3QKYGBgCgojIyMgMy44IFdoYXQgaXMgdGhlIGFnZSBkaXN0cmlidXRpb24gb2YgdGhlIGZlc3RpdmFsIHZpc2l0b3JzPwpUaGUgZmlndXJlIGJlbG93IHNob3dzIHRoZSBhZ2UgZGlzdHJpYnV0aW9uIG9mIHRoZSBmZXN0aXZhbCB2aXNpdG9ycyBieSBnZW5kZXIuIFdoYXQgaXMgb2J2aW91cyBpcyB0aGF0IGZld2VyIDQwKyBtZW4gZ28gdG8gZmVzdGl2YWxzIGNvbXBhcmVkIHRvIDQwKyB3b21lbi4gVXAgdG8gdGhlIGFnZSBvZiAzMiwgbW9yZSBtZW4gdGhhbiB3b21lbiBhdHRlbmQgZmVzdGl2YWxzLCB3aGlsZSBtYW55IHlvdW5nIHdvbWVuICgxOCB0byAyMCkgYXR0ZW5kIGZlc3RpdmFscy4gVGhlIGF2ZXJhZ2UgYWdlIG9mIHRoZSBmZXN0aXZhbCB2aXNpdG9ycyBpcyAyNyB5ZWFycyBhbmQgdGhlIG1lZGlhbiBpcyAyMy4gVGhlIG9sZGVzdCB2aXNpdG9yIGlzIDUyIGZvciBtZW4gYW5kIDU0IGZvciB3b21lbi4gVGhlIHlvdW5nZXN0IHZpc2l0b3JzIG9mIGJvdGggbWVuIGFuZCB3b21lbiBhcmUgYm90aCAxOCB5ZWFycyBvbGQuCk1hbiA9IE1hbGUsIHZyb3V3ID0gRmVtYWxlCmBgYHtyIGZpZy53aWR0aD0xMn0KYmV6b2VrZXIkbGVlZnRpamQgPC0gYXMubnVtZXJpYyhkaWZmdGltZShTeXMuRGF0ZSgpLGJlem9la2VyJGdlYm9vcnRlX2RhdHVtLCB1bml0cyA9ICJ3ZWVrcyIpKS81Mi4yNSAjIGFnZSBmb3JtdWxhCmJlem9la2VyJGxlZWZ0aWpkIDwtIGZsb29yKGJlem9la2VyJGxlZWZ0aWpkKSAjIHRvIHJvdW5kIGRvd24KCmdncGxvdChiZXpvZWtlciwgYWVzKHg9bGVlZnRpamQsIGZpbGw9Z2VzbGFjaHQpKSArICMgcGxvdHRpbmcKICBnZW9tX2hpc3RvZ3JhbShwb3NpdGlvbj0iaWRlbnRpdHkiLGJpbndpZHRoPTIsIGFscGhhPTAuNikgKyAKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzPXNlcSgxOCw2MCwyKSkgKyAjIHJlYXJyYW5nZSB0aGUgYXhlcwogIHNjYWxlX3lfY29udGludW91cyhicmVha3M9c2VxKDAsMTUsMSkpICsgIyByZWFycmFuZ2UgdGhlIGF4ZXMKICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0PW1lYW4obGVlZnRpamQpKSwgY29sb3I9InB1cnBsZSIsICMgTWVhbiAKICAgICAgICAgICAgIGxpbmV0eXBlPSJkYXNoZWQiKSArCiAgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdD1tZWRpYW4obGVlZnRpamQpKSwgY29sb3I9ImJsYWNrIiwgIyBNZWRpYW4KICAgICAgICAgICAgIGxpbmV0eXBlPSJkYXNoZWQiKSArCiAgbGFicyh0aXRsZT0iQWdlIGRpc3RyaWJ1dGlvbiBGZXN0aXZhbCB2aXNpdG9ycyIseD0iQWdlIiwgeSA9ICJDb3VudCIpKwogIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDE2KSArIAogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKCIjMDBjY2ZmIiwgIiNmZjAwMDAiKSkgIyBjb2xvcnMKYGBgCgpgYGB7ciBmaWcud2lkdGg9MTJ9CmdncGxvdChiZXpvZWtlciwgYWVzKHg9bGVlZnRpamQpKSArICMgcGxvdHRpbmcKICBnZW9tX2hpc3RvZ3JhbShwb3NpdGlvbj0iaWRlbnRpdHkiLGJpbndpZHRoPTIsIGFscGhhPTAuNikgKyAKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzPXNlcSgxOCw2MCwyKSkgKyAjIHJlYXJyYW5nZSB0aGUgYXhlcwogIHNjYWxlX3lfY29udGludW91cyhicmVha3M9c2VxKDAsMTUsMSkpICsgIyByZWFycmFuZ2UgdGhlIGF4ZXMKICBsYWJzKHRpdGxlPSJBZ2UgZGlzdHJpYnV0aW9uIEZlc3RpdmFsIHZpc2l0b3JzIGJ5IHNleCIseD0iQWdlIiwgeSA9ICJDb3VudCIpKwogIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDE2KSArIAogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKCIjMDBjY2ZmIiwgIiNmZjAwMDAiKSkgKyBmYWNldF93cmFwKH4gZ2VzbGFjaHQpICMgc2VwYXJhdGUgdmlzdWFsaXphdGlvbiBiYXNlZCBvbiBzZXgKYGBgCgojIyMgMy45IFdoYXQgaXMgdGhlIGF2ZXJhZ2UgYWdlIHBlciBnZW5kZXIgb2YgdGhlIGZlc3RpdmFsIHZpc2l0b3JzIHdobyBhdHRlbmQgVGVjaG5vIGZlc3RpdmFscyBpbiAyMDE5PwpCZWxvdyBpcyB0aGUgYXZlcmFnZSBhZ2UgcGVyIGdlbmRlciBvZiB0aGUgZmVzdGl2YWwgdmlzaXRvcnMgd2hvIHdlbnQgdG8gVGVjaG5vIGZlc3RpdmFscyBpbiAyMDE5LiBUaGlzIHF1ZXJ5IGNvbnNpc3RzIG9mIDMgcGFydHM6IFRoZSBxdWVyeSwgaW5saW5lIHZpZXcgYXMgcmVzdWx0IHNldCBhbmQgYSBzdWJxdWVyeS4gRmlyc3Qgb2YgYWxsLCB1c2UgYSBjYXN0IHRvIHJvdW5kIHRoZSBhdmVyYWdlIGFnZSBkb3duIGJlY2F1c2UgU1FMaXRlIGhhcyBubyBmbG9vciBmdW5jdGlvbi4gVGhlbiBJIHVzZSBhbiBpbmxpbmUgdmlldyB3aGVyZSBJIHNlbGVjdCBhbGwgdW5pcXVlIHZpc2l0b3JzIHdobyB3ZW50IHRvIGZlc3RpdmFscyBpbiAyMDE5LiBJIGFsc28gY2FsY3VsYXRlIHRvIGNhbGN1bGF0ZSB0aGUgYWdlLiBJIGFsc28gY3JlYXRlIGEgc3VicXVlcnkgdGhhdCBzZXJ2ZXMgYXMgYSByZXN1bHQgc2V0IHdoZXJlIHZpc2l0b3JzIHdlbnQgdG8gdGVjaG5vIGZlc3RpdmFscy4gVGhpcywgY29tYmluZWQgd2l0aCB0aGUgcmVzdCwgcHJvdmlkZXMgYW4gb3ZlcnZpZXcgb2YgdGhlIGF2ZXJhZ2UgYWdlIHBlciBnZW5kZXIgb2YgdGhlIGZlc3RpdmFsIHZpc2l0b3JzIHdobyBnbyB0byB0ZWNobm8gZmVzdGl2YWxzLgpgYGB7c3FsIGNvbm5lY3Rpb249Y29ufQovKiBDYXN0IGlzIHVzZWQgZm9yIGNvbnZlcnRpbmcgdG8gaW50IGRhdGEgdHlwZSB0byByb3VuZCBkb3duIGV2ZW50dWFsbHkgYmVjYXVzZSBTUUxpdGUgaGFzIG5vIGZsb29yIGZ1bmN0aW9uLiBJIHVzZSBhbiBpbmxpbmUgdmlldyBhcyBhIHJlc3VsdCBzZXQgaW4gd2hpY2ggSSBjYWxjdWxhdGUgc29tZW9uZSdzIGFnZSB3aXRoIGEgZm9ybXVsYS4gRmluYWxseSwgSSB1c2UgYSBzdWJxdWVyeSB0aGF0IGluZGljYXRlcyB3aGljaCB2aXNpdG9ycyB3ZW50IHRvIHRlY2hubyBmZXN0aXZhbHMuKi8KU0VMRUNUIGdlc2xhY2h0ICxjYXN0ICggQVZHKGxlZWZ0aWpkKSBhcyBpbnQgKSAtICggQVZHKGxlZWZ0aWpkKSA8IGNhc3QgKCBBVkcobGVlZnRpamQpIGFzIGludCApKSBBUyBnZW1pZGRlbGRlX2xlZWZ0aWpkIApGUk9NIAooCiAgU0VMRUNUIERJU1RJTkNUIGIuYmV6b2VrZXJfaWQsIHZvb3JuYWFtLCBhY2h0ZXJuYWFtLAogIChzdHJmdGltZSgnJVknLCAnbm93JykgLSBzdHJmdGltZSgnJVknLCBnZWJvb3J0ZV9kYXR1bSkpIC0gKHN0cmZ0aW1lKCclbS0lZCcsICdub3cnKSA8IHN0cmZ0aW1lKCclbS0lZCcsIGdlYm9vcnRlX2RhdHVtKSkgQVMgbGVlZnRpamQsICAgZ2VzbGFjaHQsIHN0cmZ0aW1lKCclWScsIGRhdHVtKSBBUyBqYWFyCiAgRlJPTSBiZXpvZWtlciBiCiAgSk9JTiBhYW5tZWxkaW5nZW4gYSBPTiBhLmJlem9la2VyX2lkID0gYi5iZXpvZWtlcl9pZAogIEpPSU4gZmVzdGl2YWwgZiBPTiBmLmZlc3RpdmFsX2lkID0gYS5mZXN0aXZhbF9pZAogIFdIRVJFIGphYXIgPSAnMjAxOScgQU5EIGIuYmV6b2VrZXJfaWQgSU4KICAoCiAgICAgU0VMRUNUIGIuYmV6b2VrZXJfaWQKICAgICBGUk9NIGZlc3RpdmFsIGYKICAgICBKT0lOIGFhbm1lbGRpbmdlbiBhIE9OIGEuZmVzdGl2YWxfaWQgPSBmLmZlc3RpdmFsX2lkCiAgICAgSk9JTiBiZXpvZWtlciBiIG9uIGIuYmV6b2VrZXJfaWQgPSBhLmJlem9la2VyX2lkCiAgICAgSk9JTiBmZXN0aXZhbF9jYXRlZ29yaWVfZmVzdGl2YWwgZmNmIE9OIGZjZi5mZXN0aXZhbF9pZCA9IGYuZmVzdGl2YWxfaWQKICAgICBKT0lOIGZlc3RpdmFsX2NhdGVnb3JpZSBmYyBPTiBmYy5jYXRlZ29yaWVfaWQgPSBmY2YuY2F0ZWdvcmllX2lkCiAgICAgV0hFUkUgZmMubmFhbSA9PSdUZWNobm8nCiAgKQopdApHUk9VUCBCWSBnZXNsYWNodApgYGAKCiMjIyAzLjEwIFdoYXQgaXMgdGhlIGF2ZXJhZ2UgYWdlIG9mIHRoZSBmZXN0aXZhbCB2aXNpdG9ycyB3aG8gd2VudCB0byBFbXBvcml1bSBpbiAyMDE5PwpCZWxvdyBpcyB0aGUgYXZlcmFnZSBhZ2Ugb2YgdGhlIGZlc3RpdmFsIHZpc2l0b3JzIHdobyB3ZW50IHRvIEVtcG9yaXVtIGluIDIwMTkuIEZyb20gdGhlIGZlc3RpdmFsIHRhYmxlIEkgam9pbiA0IG90aGVyIHRhYmxlczogYWFubWVsZGluZ2VuKHJlZ2lzdHJhdGlvbnMpLCBiZXpvZWtlcih2aXNpdG9yKSwgZmVzdGl2YWxfY2F0ZWdvcmllX2Zlc3RpdmFsKGJldHdlZW4gdGFibGUpICYgZmVzdGl2YWxfY2F0ZWdvcmllKGZlc3RpdmFsIGNhdGVnb3J5KS4gVGhlbiBJIGZpbHRlciBvbiB0aGUgZmVzdGl2YWwgbmFtZSBhbmQgeWVhciBvZiB0aGUgZmVzdGl2YWwuIFRoZW4gSSBncm91cCBieSBzZXggYW5kIG1pbmltaXplIGFsbCByb3dzIGJlY2F1c2UgSSB3YW50IHRvIGtub3cgdGhlIGF2ZXJhZ2UgYWdlIGJhc2VkIG9uIHNleC4KYGBge3J9CmZlc3RpdmFsICU+JQogIGlubmVyX2pvaW4oYWFubWVsZGluZ2VuLCBieT0nZmVzdGl2YWxfaWQnKSAlPiUKICBpbm5lcl9qb2luKGJlem9la2VyLCBieT0nYmV6b2VrZXJfaWQnKSAlPiUKICBpbm5lcl9qb2luKGZlc3RpdmFsX2NhdGVnb3JpZV9mZXN0aXZhbCwgYnk9J2Zlc3RpdmFsX2lkJykgJT4lCiAgaW5uZXJfam9pbihmZXN0aXZhbF9jYXRlZ29yaWUsIGJ5PSdjYXRlZ29yaWVfaWQnKSAlPiUKICBmaWx0ZXIobmFhbS54ID09ICdFbXBvcml1bScgJiB5ZWFyKGRhdHVtKSA9PScyMDE5JykgJT4lICMgZmlsdGVyaW5nCiAgZ3JvdXBfYnkoZ2VzbGFjaHQpICU+JSAjIGdyb3VwIGJ5IHNleAogIHN1bW1hcmlzZShnZW1pZGRlbGRlX2xlZWZ0aWpkID0gbWVhbihsZWVmdGlqZC55KSkgIyBhdmVyYWdlIGFnZQpgYGAKCiMjIyAzLjExIFdoYXQgaXMgdGhlIGxvd2VzdCwgYXZlcmFnZSBhbmQgZ3JlYXRlc3QgcmV2ZW51ZSBvZiB0aGUgcHJvZHVjdHMgZnJvbSB0aGUgc2FsZXMgdGFibGUgZm9yIGVhY2ggeWVhcj8KQmVsb3cgaXMgYW4gb3ZlcnZpZXcgb2YgdGhlIHJldmVudWUgZnJvbSB0aGUgc2FsZXMgdGFibGUgZm9yIGVhY2ggeWVhci4gRm9yIGVhY2ggeWVhciBJIHNob3cgdGhlIGxvd2VzdCwgYXZlcmFnZSBhbmQgaGlnaGVzdCB0dXJub3ZlciB0aGF0IHdhcyBhY2hpZXZlZCBpbiB0aGF0IHllYXIuIEZyb20gdGhlIHZlcmtvb3BfcHJvZHVjdGVuIChzYWxlc19wcm9kdWN0cykgdGFibGUgSSBqb2luIDIgb3RoZXIgdGFibGVzOiB0aGUgYmV0d2VlbiB0YWJsZSBhbmQgZmVzdGl2YWwuIFRoZW4gSSBjcmVhdGUgYSBuZXcgY29sdW1uIGZvciB0aGUgcmV2ZW51ZTogdGhlIHByaWNlIHRpbWVzIHRoZSBudW1iZXIgb2YgcGllY2VzIGlzIHRoZSByZXZlbnVlLiBUaGVuIEkgZ3JvdXAgYnkgdGhlIHllYXIgb2YgZWFjaCBkYXRlIGFuZCBJIG1pbmltaXplIHRoZSByb3dzIHdpdGggdGhlIGNvcnJlc3BvbmRpbmcgcmV2ZW51ZSBmb3IgZWFjaCB5ZWFyLgpgYGB7cn0KdmVya29vcF9wcm9kdWN0ZW4gJT4lCiAgaW5uZXJfam9pbih2ZXJrb29wX3Byb2R1Y3Rlbl9mZXN0aXZhbCwgYnk9J3Zlcmtvb3BfaWQnKSAlPiUgIyBqb2luIG1hdGNoaW5nIHZhbHVlcwogIGlubmVyX2pvaW4oZmVzdGl2YWwsIGJ5PSdmZXN0aXZhbF9pZCcpICU+JSAjIGpvaW4gbWF0Y2hpbmcgdmFsdWVzCiAgbXV0YXRlKG9temV0ID0gcHJpanMgKiBhYW50YWwpICAlPiUgIyBhZGQgbmV3IGNvbHVtbiBmb3Igc2FsZXMKICBncm91cF9ieSh5ZWFyKGRhdHVtKSkgJT4lICMgZ3JvdXAgYnkgeWVhcgogIHN1bW1hcmlzZShnZW1pZGRlbGRlX29temV0ID0gcm91bmQobWVhbihvbXpldCksMiksICMgbWluaW1pemUgb24gYXZlcmFnZSByZXZlbnVlCiAgICAgICAgICAgIGxhYWdzdGVfb216ZXQgPSByb3VuZChtaW4ob216ZXQpLDIpLCAjIG1pbmltaXplIG9uIGxvd2VzdCByZXZlbnVlCiAgICAgICAgICAgIGhvb2dzdGVfb216ZXQgPSByb3VuZChtYXgob216ZXQpLDIpKSAjIG1pbmltaXplIG9uIGhpZ2hlc3QgcmV2ZW51ZQpgYGAKCiMjIyAzLjEyIEhvdyBtYW55IHBvcCBmZXN0aXZhbHMgd2VyZSBvcmdhbml6ZWQgcGVyIHllYXI/CkJlbG93IGlzIGEgc21hbGwgb3ZlcnZpZXcgd2hlcmUgeW91IGNhbiBzZWUgaG93IG1hbnkgcG9wIGZlc3RpdmFscyBhcmUgaGVsZCBlYWNoIHllYXIuIEZpcnN0IG9mIGFsbCwgSSBzZWxlY3QgdGhlIGZlc3RpdmFsIHRhYmxlIGFuZCBqb2luIDQgb3RoZXIgdGFibGVzLCAyIGludGVybWVkaWF0ZSB0YWJsZXMgYW5kIHRoZSBmZXN0aXZhbF9jYXRlZ29yeSArIGxvY2F0aW9uIHRhYmxlLiBUaGVuIEkgZ3JvdXAgYnkgZWFjaCB5ZWFyIGFuZCBJIGZpbHRlciBieSB0aGUgbXVzaWMgZ2VucmUgYW5kIGZpbmFsbHkgSSBjb3VudCB0aGlzIHNvIHRoYXQgeW91IGNhbiBzZWUgaG93IG1hbnkgcG9wIGZlc3RpdmFscyB0aGVyZSBhcmUgZ2l2ZW4gZWFjaCB5ZWFyLgpgYGB7cn0KZmVzdGl2YWwgJT4lCiAgaW5uZXJfam9pbihmZXN0aXZhbF9jYXRlZ29yaWVfZmVzdGl2YWwsIGJ5PSdmZXN0aXZhbF9pZCcpICU+JSAjIGpvaW4gbWF0Y2hpbmcgdmFsdWVzCiAgaW5uZXJfam9pbihmZXN0aXZhbF9jYXRlZ29yaWUsIGJ5PSdjYXRlZ29yaWVfaWQnKSAlPiUgIyBqb2luIG1hdGNoaW5nIHZhbHVlcwogIGlubmVyX2pvaW4obG9jYXRpZV9mZXN0aXZhbCwgYnk9J2Zlc3RpdmFsX2lkJykgJT4lICMgam9pbiBtYXRjaGluZyB2YWx1ZXMKICBpbm5lcl9qb2luKGxvY2F0aWUsIGJ5PSdsb2NhdGllX2lkJykgJT4lICMgam9pbiBtYXRjaGluZyB2YWx1ZXMKICBncm91cF9ieSh5ZWFyKGRhdHVtLngpKSAlPiUgIyBncm91cCBieSB5ZWFyCiAgZmlsdGVyKGdlbnJlPT0nUG9wJykgJT4lICMgZmlsdGVyIG9uIHBvcAogIGNvdW50KGdlbnJlKSAjIGNvdW50IGdlbnJlcwpgYGAKCgoKCgoK