This project is part of my DataQuest course and is one of the projects in the SQL module. This project is about the business data for a music download company.
#install.packages("RSQLite")
Warning messages:
1: In readChar(file, size, TRUE) : truncating string with embedded nuls
2: In readChar(file, size, TRUE) : truncating string with embedded nuls
3: In readChar(file, size, TRUE) : truncating string with embedded nuls
4: In readChar(file, size, TRUE) : truncating string with embedded nuls
5: In readChar(file, size, TRUE) : truncating string with embedded nuls
6: In readChar(file, size, TRUE) : truncating string with embedded nuls
library(DBI)
library(RSQLite)
library(readr)
library(stringr)
library(ggplot2)
library(dplyr)
library(purrr)
library(tidyr)
library(magrittr)
set working directory
setwd("C:/Users/Ana/Desktop/Data Analytics/CSV Files")
conn <- dbConnect(SQLite(), "chinook.db")
dbListTables(conn)
[1] "album" "artist" "customer" "employee" "genre" "invoice"
[7] "invoice_line" "main_part" "mainpart1" "media_type" "playlist" "playlist_track"
[13] "track"
#Send and Fetch Query
query_invoice <- "SELECT *
FROM invoice
LIMIT 10;
"
conn <- dbConnect(SQLite(), "chinook.db")
result <- dbSendQuery(conn, query_invoice)
invoice_1 <- dbFetch(result)
dbDisconnect(conn)
There are 1 result in use. The connection will be released when they are closed
invoice_1
#Get Query
conn <- dbConnect(SQLite(), "chinook.db")
invoice <- dbGetQuery(conn, "SELECT * from invoice LIMIT 10")
dbDisconnect(conn)
invoice
Creating the helper functions
db <- 'chinook.db'
run_query <- function(q) {
conn <- dbConnect(SQLite(), db)
result <- dbGetQuery(conn, q)
dbDisconnect(conn)
return(result)
}
show_tables <- function() {
q = "SELECT name, type FROM sqlite_master WHERE type IN ('table', 'view')"
return(run_query(q))
}
show_tables()
Looking at the most popular genres:
genre_query <- "SELECT g.genre_id,
g.name genre,
SUM(il.quantity) quantity,
ROUND(CAST(SUM(il.quantity) AS FLOAT)/(SELECT sum(quantity) FROM invoice_line)*100, 1) percent
FROM genre g
LEFT JOIN track t ON t.genre_id = g.genre_id
LEFT JOIN invoice_line il ON il.track_id = t.track_id
GROUP BY genre
ORDER BY quantity DESC
"
genre_db <- run_query(genre_query)
genre_db
NA
ggplot(data = genre_db,
aes(x = genre, y = quantity)) +
theme(axis.text.x = element_text(angle=60, hjust=1)) +
geom_bar(stat='identity')+
labs(title = "No. of tracks purchased of each genre", x = "Genre", y = "No. of tracks purchased") +
theme(panel.background = element_rect(fill = "white")) +
geom_text(aes(label = percent, y = quantity+100))

NA
NA
The most popular genre is Rock which accounts for 55% of all tracks purchased. The next monst popular is metal (13%) then Alternative & Punk (10.3%). The new artists with their music genre are provided below along with the corresponding genre percent share: Regal - Hip-hop 0.7% Red Tone - Punk 10.3% Meteor and the Girls - Pop 1.3% Slim Jim Bites - Blues 2.6%. Hip Hop is the least popular, so we should remove Regal and keep the other three artists’ albums.
Looking at the performance of the sales staff:
sales_query <- "SELECT e.employee_id,
e.first_name || ' ' || e.last_name AS name,
e.reports_to,
e.hire_date,
e.country,
SUM(total) total_sales
FROM employee e
LEFT JOIN customer c ON c.support_rep_id = e.employee_id
LEFT JOIN invoice i ON i.customer_id = c.customer_id
GROUP BY e.employee_id
"
sales_db <- run_query(sales_query)
sales_db
ggplot(data = sales_db,
aes(x = name, y = total_sales)) +
theme(axis.text.x = element_text(angle=60, hjust=1)) +
geom_bar(stat='identity')+
labs(title = "Total sales by each employee", x = "Employee", y = "Total Sales per Employee") +
theme(panel.background = element_rect(fill = "white")) +
geom_text(aes(label = total_sales, y = total_sales+70))

NA
NA
There are only 3 employees that area Sales Support Reps. Jane Peacock has had the most sales, she joined the company in April 2017. Margaret Park has the second most sales, she joined in May 2017. Steve Johnson has the least sales, but he didn’t join until October 2017. All three have the same supervisor. As there are only 3 support reps, there isn’t enough data to be able to conclude whether any other factor affects how well they perform.
Looking at sales data in different countries:
#The first part of this SQL code looks at the customer table only and replaces the country name with "other" if there is only one customer from a country.
#The next part of the code contains a sub-query (which does most of the work so I'll explain the sub-query first)
#The sub-query joins the customer table to the invoice table. It also creates a new 'logic' column using CASE for when country is "Other".
#The main query then takes all of the columns from the subquery except the logic column and reorders by the logic column.
sales_by_country = '
WITH revised AS (
SELECT
CASE
WHEN (
SELECT count(*)
FROM customer
WHERE country = c.country
) = 1 THEN "Other"
ELSE c.country
END AS country_new,
c.customer_id
FROM customer c
)
SELECT country,
no_customers,
total_sales,
avg_cust_spend,
avg_order_value
FROM (
SELECT r.country_new country,
COUNT(DISTINCT(r.customer_id)) no_customers,
SUM(i.total) total_sales,
ROUND(SUM(i.total)/COUNT(DISTINCT(r.customer_id)),2) avg_cust_spend,
ROUND(avg(i.total),2) avg_order_value,
CASE
WHEN r.country_new = "Other" THEN 1
ELSE 0
END AS logic
FROM revised r
LEFT JOIN invoice i ON i.customer_id = r.customer_id
GROUP BY country_new
ORDER BY total_sales DESC
)
ORDER BY logic
;
'
country_db <- run_query(sales_by_country)
country_db
NA
NA
NA
#remove the entry for Country = 'Other'
country_db <- country_db %>%
filter(country != "Other")
ggplot(data = country_db,
aes(x = country, y = no_customers, fill = country)) +
theme(axis.text.x = element_text(angle=60, hjust=1)) +
geom_bar(stat='identity')+
labs(title = "No. Customers per Country", x = "Country", y = "No. Customers") +
theme(panel.background = element_rect(fill = "white")) +
geom_text(aes(label = no_customers, y = no_customers+0.5))

ggplot(data = country_db,
aes(x = country, y = total_sales, fill = country)) +
theme(axis.text.x = element_text(angle=60, hjust=1)) +
geom_bar(stat='identity') +
labs(title = "Total Sales per Country", x = "Country", y = "Total Sales($)") +
theme(panel.background = element_rect(fill = "white")) +
geom_text(aes(label = total_sales, y = total_sales+50))

ggplot(data = country_db,
aes(x = country, y = avg_cust_spend, fill = country)) +
theme(axis.text.x = element_text(angle=60, hjust=1)) +
geom_bar(stat='identity') +
labs(title = "Average Customer Spend per Country", x = "Country", y = "Average Customer Spend($)") +
theme(panel.background = element_rect(fill = "white")) +
geom_text(aes(label = avg_cust_spend, y = avg_cust_spend+5))

ggplot(data = country_db,
aes(x = country, y = avg_order_value, fill = country)) +
theme(axis.text.x = element_text(angle=60, hjust=1)) +
geom_bar(stat='identity') +
labs(title = "Average Order Value per Country", x = "Country", y = "Average Order Value($)") +
theme(panel.background = element_rect(fill = "white")) +
geom_text(aes(label = avg_order_value, y = avg_order_value+0.5))

The greatest total sales is from the US market followed by Canada. However, the average customer spend in Canada is the lowest out of all the countries (exc other). Some target marketing at existing customers could increase the spend per customer and hence increase total sales. The Czech Republic has only 2 customers but the average customer spend is the highest. Could some target marketing in the Czech Replublic reap rewards with high spenders?
Looking at what percentage of sales are ‘whole album purchases’:
I will calculate what percentage of all purchases are ‘whole album purchases’ i.e the customer selects and buys a whole album, rather than selects a number of tracks (from different albums)
#My solution uses a completely different method from what was suggested in the guidance notes.
#My method is based on the fact that if a customer buys a whole album then these 2 things will be true:
# 1 - There will only be 1 distinct album number per invoice i.e. when you COUNT(DISTINCT album_id) for each invoice it will return 1
# 2 - The number of tracks purchased will equal the number of tracks on the album of the first album number on their invoice
#If both these things are true, then the customer purchased an album.
#The query is split into a first part - a_t_nos (album track numbers), then the main part which includes a subquery.
#The first part takes the album table and joins it to the track table. It then groups by album_id and counts the number of tracks so the result is a table a_t_nos of albums and the corresponding number of tracks on each album.
#The main part, starting with the subquery, then takes the invoice_line table and joins it to the track table and then joins the a_t_nos table onto that. It groups by invoice_line. For each invoice_line, it calculates:
# - the number of tracks bought
# - the number of albums on each invoice i.e. COUNT(DISTINCT album_id)
# - it leaves the columns invoice_id, album_id and tracks_in_album
#It then adds a logic column called 'album'. This column is given the value of 1 if BOTH of these two conditions are satisfied:
# 1. Is the number of distinct albums on this invoice 1?
# 2. Is the number of tracks on the invoice equal to the number of tracks in the album number?
#If they're both satisfied, then it's an album!
#Lastly, the encompassing query sums the number of instances where the album column = 1 (i.e. this is the number of invoices which relate to whole album purchases by customers). It counts the album column (i.e. this is the total number of invoices). It calculates the percentage of invoices which are whole-album purchases.
is_it_an_album = '
WITH a_t_nos AS
(
SELECT a.album_id,
t.track_id,
COUNT(t.track_id) tracks_in_album
FROM album a
INNER JOIN track t ON t.album_id = a.album_id
GROUP BY a.album_id
)
SELECT SUM(album) invoices_for_albums,
COUNT(album) all_invoices,
ROUND(CAST(SUM(album) AS FLOAT)/COUNT(album), 2) percent_invoices_for_albums
FROM(
SELECT il.invoice_id,
COUNT(il.track_id) tracks_bought,
COUNT(DISTINCT t.album_id) sole_album_check,
atn.album_id,
atn.tracks_in_album,
CASE
WHEN COUNT(DISTINCT t.album_id) = 1 AND COUNT(il.track_id) = atn.tracks_in_album THEN 1
ELSE 0
END AS album
FROM invoice_line il
INNER JOIN track t ON t.track_id = il.track_id
LEFT JOIN a_t_nos atn ON atn.album_id = t.album_id
GROUP BY invoice_id
)
;
'
run_query(is_it_an_album)
NA
Of all purchases, 19% are whole album purchases. This is quite a large proportion of total purchases so it wouldn’t be advisable to change strategy.
My method was different from the suggested method. Here is the model answer showing the suggested method which uses EXCEPT to run 2 lists against each other to check if they are the same. I have commented the code.
#The first part of the code takes the invoice_id and calculates the first_track_id (using MIN) from each invoice and puts this in a table called invoice_first_track.
#The next bit in the code is actually the last bit to be calculated so I'll skip this for now. See [1].
#The 'main bit' of the code takes the table invoice_first_track (with alias ift) and selects all the columns (ifs.*) plus it adds a case column called album_purchase which it asigns a YES or NO to.
#Within the 'CASE' part:
#selects all the tracks from the album number corresponding to the album number from the first_track_id column in ifs
#so that's the 'true album list'
#next it needs to compare that to the list of tracks on that invoice. i.e. it takes the track_id from il where invoice_id corresponds to the ifs invoice_id.
#It checks if it returns NULL
#Then it also has to do it the other way round
#they both have to return NULL
#If they both return NULL then a 'yes' is put in the "album_purchase" column. ELSE NO.
#[1] Lastly go back up to the code from before which is the last bit to be calculated. This calculates columns album_purchase, number_of_invoices and percent
albums_vs_tracks = '
WITH invoice_first_track AS
(
SELECT
il.invoice_id invoice_id,
MIN(il.track_id) first_track_id
FROM invoice_line il
GROUP BY 1
)
SELECT
album_purchase,
COUNT(invoice_id) number_of_invoices,
CAST(count(invoice_id) AS FLOAT) / (
SELECT COUNT(*) FROM invoice
) percent
FROM
(
SELECT
ifs.*,
CASE
WHEN
(
SELECT t.track_id FROM track t
WHERE t.album_id = (
SELECT t2.album_id FROM track t2
WHERE t2.track_id = ifs.first_track_id
)
EXCEPT
SELECT il2.track_id FROM invoice_line il2
WHERE il2.invoice_id = ifs.invoice_id
) IS NULL
AND
(
SELECT il2.track_id FROM invoice_line il2
WHERE il2.invoice_id = ifs.invoice_id
EXCEPT
SELECT t.track_id FROM track t
WHERE t.album_id = (
SELECT t2.album_id FROM track t2
WHERE t2.track_id = ifs.first_track_id
)
) IS NULL
THEN "yes"
ELSE "no"
END AS "album_purchase"
FROM invoice_first_track ifs
)
GROUP BY album_purchase;
'
run_query(albums_vs_tracks)
NA
LS0tDQp0aXRsZTogIlNRTCBCdXNpbmVzcyBBbmFseXNpcyBQcm9qZWN0Ig0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KVGhpcyBwcm9qZWN0IGlzIHBhcnQgb2YgbXkgRGF0YVF1ZXN0IGNvdXJzZSBhbmQgaXMgb25lIG9mIHRoZSBwcm9qZWN0cyBpbiB0aGUgU1FMIG1vZHVsZS4NClRoaXMgcHJvamVjdCBpcyBhYm91dCB0aGUgYnVzaW5lc3MgZGF0YSBmb3IgYSBtdXNpYyBkb3dubG9hZCBjb21wYW55LiANCg0KYGBge3J9DQojaW5zdGFsbC5wYWNrYWdlcygiUlNRTGl0ZSIpDQoNCmBgYA0KYGBge3J9DQpsaWJyYXJ5KERCSSkNCmxpYnJhcnkoUlNRTGl0ZSkNCmxpYnJhcnkocmVhZHIpDQpsaWJyYXJ5KHN0cmluZ3IpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShwdXJycikNCmxpYnJhcnkodGlkeXIpDQpsaWJyYXJ5KG1hZ3JpdHRyKQ0KYGBgDQpzZXQgd29ya2luZyBkaXJlY3RvcnkNCmBgYHtyfQ0Kc2V0d2QoIkM6L1VzZXJzL0FuYS9EZXNrdG9wL0RhdGEgQW5hbHl0aWNzL0NTViBGaWxlcyIpDQpjb25uIDwtIGRiQ29ubmVjdChTUUxpdGUoKSwgImNoaW5vb2suZGIiKQ0KZGJMaXN0VGFibGVzKGNvbm4pDQpgYGANCg0KYGBge3J9DQojU2VuZCBhbmQgRmV0Y2ggUXVlcnkNCg0KcXVlcnlfaW52b2ljZSA8LSAiU0VMRUNUICogDQogICAgICAgICAgICBGUk9NIGludm9pY2UgDQogICAgICAgICAgICBMSU1JVCAxMDsNCiAgICAgICAgICAgICINCmNvbm4gPC0gZGJDb25uZWN0KFNRTGl0ZSgpLCAiY2hpbm9vay5kYiIpDQpyZXN1bHQgPC0gZGJTZW5kUXVlcnkoY29ubiwgcXVlcnlfaW52b2ljZSkNCmludm9pY2VfMSA8LSBkYkZldGNoKHJlc3VsdCkNCmRiRGlzY29ubmVjdChjb25uKQ0KDQppbnZvaWNlXzENCmBgYA0KDQoNCmBgYHtyfQ0KI0dldCBRdWVyeQ0KDQpjb25uIDwtIGRiQ29ubmVjdChTUUxpdGUoKSwgImNoaW5vb2suZGIiKQ0KaW52b2ljZSA8LSBkYkdldFF1ZXJ5KGNvbm4sICJTRUxFQ1QgKiBmcm9tIGludm9pY2UgTElNSVQgMTAiKQ0KZGJEaXNjb25uZWN0KGNvbm4pDQoNCmludm9pY2UNCmBgYA0KDQpDcmVhdGluZyB0aGUgaGVscGVyIGZ1bmN0aW9ucw0KDQpgYGB7cn0NCmRiIDwtICdjaGlub29rLmRiJw0KcnVuX3F1ZXJ5IDwtIGZ1bmN0aW9uKHEpIHsNCiAgY29ubiA8LSBkYkNvbm5lY3QoU1FMaXRlKCksIGRiKQ0KICByZXN1bHQgPC0gZGJHZXRRdWVyeShjb25uLCBxKQ0KICBkYkRpc2Nvbm5lY3QoY29ubikNCiAgcmV0dXJuKHJlc3VsdCkNCn0NCg0Kc2hvd190YWJsZXMgPC0gZnVuY3Rpb24oKSB7DQogIHEgPSAiU0VMRUNUIG5hbWUsIHR5cGUgRlJPTSBzcWxpdGVfbWFzdGVyIFdIRVJFIHR5cGUgSU4gKCd0YWJsZScsICd2aWV3JykiDQogIHJldHVybihydW5fcXVlcnkocSkpDQp9DQoNCnNob3dfdGFibGVzKCkNCmBgYA0KTG9va2luZyBhdCB0aGUgbW9zdCBwb3B1bGFyIGdlbnJlczoNCmBgYHtyfQ0KZ2VucmVfcXVlcnkgPC0gICJTRUxFQ1QgZy5nZW5yZV9pZCwgDQogICAgICAgICAgICAgICAgICAgICAgICBnLm5hbWUgZ2VucmUsIA0KICAgICAgICAgICAgICAgICAgICAgICAgU1VNKGlsLnF1YW50aXR5KSBxdWFudGl0eSwgDQogICAgICAgICAgICAgICAgICAgICAgICBST1VORChDQVNUKFNVTShpbC5xdWFudGl0eSkgQVMgRkxPQVQpLyhTRUxFQ1Qgc3VtKHF1YW50aXR5KSBGUk9NIGludm9pY2VfbGluZSkqMTAwLCAxKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGVyY2VudA0KICAgICAgICAgICAgICAgIEZST00gZ2VucmUgZw0KICAgICAgICAgICAgICAgIExFRlQgSk9JTiB0cmFjayB0IE9OIHQuZ2VucmVfaWQgPSBnLmdlbnJlX2lkDQogICAgICAgICAgICAgICAgTEVGVCBKT0lOIGludm9pY2VfbGluZSBpbCBPTiBpbC50cmFja19pZCA9IHQudHJhY2tfaWQNCiAgICAgICAgICAgICAgICBHUk9VUCBCWSBnZW5yZQ0KICAgICAgICAgICAgICAgIE9SREVSIEJZIHF1YW50aXR5IERFU0MNCiAgICAgICAgICAgICAgICAiDQpnZW5yZV9kYiA8LSBydW5fcXVlcnkoZ2VucmVfcXVlcnkpDQpnZW5yZV9kYg0KDQpgYGANCmBgYHtyfQ0KDQpnZ3Bsb3QoZGF0YSA9IGdlbnJlX2RiLA0KICAgICAgICBhZXMoeCA9IGdlbnJlLCB5ID0gcXVhbnRpdHkpKSArDQogICAgICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlPTYwLCBoanVzdD0xKSkgKw0KICAgICAgICBnZW9tX2JhcihzdGF0PSdpZGVudGl0eScpKw0KICAgICAgICBsYWJzKHRpdGxlID0gIk5vLiBvZiB0cmFja3MgcHVyY2hhc2VkIG9mIGVhY2ggZ2VucmUiLCB4ID0gIkdlbnJlIiwgeSA9ICJOby4gb2YgdHJhY2tzIHB1cmNoYXNlZCIpICsNCiAgICAgICAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gIndoaXRlIikpICsNCiAgICAgICAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHBlcmNlbnQsIHkgPSBxdWFudGl0eSsxMDApKQ0KICAgICAgIA0KDQpgYGANClRoZSBtb3N0IHBvcHVsYXIgZ2VucmUgaXMgUm9jayB3aGljaCBhY2NvdW50cyBmb3IgNTUlIG9mIGFsbCB0cmFja3MgcHVyY2hhc2VkLiBUaGUgbmV4dCBtb25zdCBwb3B1bGFyIGlzIG1ldGFsICgxMyUpIHRoZW4gQWx0ZXJuYXRpdmUgJiBQdW5rICgxMC4zJSkuDQpUaGUgbmV3IGFydGlzdHMgd2l0aCB0aGVpciBtdXNpYyBnZW5yZSBhcmUgcHJvdmlkZWQgYmVsb3cgYWxvbmcgd2l0aCB0aGUgY29ycmVzcG9uZGluZyBnZW5yZSBwZXJjZW50IHNoYXJlOg0KUmVnYWwgLSBIaXAtaG9wIDAuNyUNClJlZCBUb25lIC0gUHVuayAxMC4zJQ0KTWV0ZW9yIGFuZCB0aGUgR2lybHMgLSBQb3AgMS4zJQ0KU2xpbSBKaW0gQml0ZXMgLSBCbHVlcyAyLjYlLg0KSGlwIEhvcCBpcyB0aGUgbGVhc3QgcG9wdWxhciwgc28gd2Ugc2hvdWxkIHJlbW92ZSBSZWdhbCBhbmQga2VlcCB0aGUgb3RoZXIgdGhyZWUgYXJ0aXN0cycgYWxidW1zLiANCg0KTG9va2luZyBhdCB0aGUgcGVyZm9ybWFuY2Ugb2YgdGhlIHNhbGVzIHN0YWZmOg0KYGBge3J9DQpzYWxlc19xdWVyeSA8LSAgIlNFTEVDVCBlLmVtcGxveWVlX2lkLA0KICAgICAgICAgICAgICAgICAgICAgICAgZS5maXJzdF9uYW1lIHx8ICcgJyB8fCBlLmxhc3RfbmFtZSAgQVMgbmFtZSwNCiAgICAgICAgICAgICAgICAgICAgICAgIGUucmVwb3J0c190bywNCiAgICAgICAgICAgICAgICAgICAgICAgIGUuaGlyZV9kYXRlLA0KICAgICAgICAgICAgICAgICAgICAgICAgZS5jb3VudHJ5LA0KICAgICAgICAgICAgICAgICAgICAgICAgU1VNKHRvdGFsKSB0b3RhbF9zYWxlcw0KICAgICAgICAgICAgICAgIEZST00gZW1wbG95ZWUgZQ0KICAgICAgICAgICAgICAgIExFRlQgSk9JTiBjdXN0b21lciBjIE9OIGMuc3VwcG9ydF9yZXBfaWQgPSBlLmVtcGxveWVlX2lkDQogICAgICAgICAgICAgICAgTEVGVCBKT0lOIGludm9pY2UgaSBPTiBpLmN1c3RvbWVyX2lkID0gYy5jdXN0b21lcl9pZA0KICAgICAgICAgICAgICAgIEdST1VQIEJZIGUuZW1wbG95ZWVfaWQNCiAgICAgICAgICAgICAgICAiDQoNCnNhbGVzX2RiIDwtIHJ1bl9xdWVyeShzYWxlc19xdWVyeSkNCnNhbGVzX2RiDQpgYGANCmBgYHtyfQ0KZ2dwbG90KGRhdGEgPSBzYWxlc19kYiwNCiAgICAgICAgYWVzKHggPSBuYW1lLCB5ID0gdG90YWxfc2FsZXMpKSArDQogICAgICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlPTYwLCBoanVzdD0xKSkgKw0KICAgICAgICBnZW9tX2JhcihzdGF0PSdpZGVudGl0eScpKw0KICAgICAgICBsYWJzKHRpdGxlID0gIlRvdGFsIHNhbGVzIGJ5IGVhY2ggZW1wbG95ZWUiLCB4ID0gIkVtcGxveWVlIiwgeSA9ICJUb3RhbCBTYWxlcyBwZXIgRW1wbG95ZWUiKSArDQogICAgICAgIHRoZW1lKHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ3aGl0ZSIpKSArDQogICAgICAgIGdlb21fdGV4dChhZXMobGFiZWwgPSB0b3RhbF9zYWxlcywgeSA9IHRvdGFsX3NhbGVzKzcwKSkNCg0KDQpgYGANClRoZXJlIGFyZSBvbmx5IDMgZW1wbG95ZWVzIHRoYXQgYXJlYSBTYWxlcyBTdXBwb3J0IFJlcHMuDQpKYW5lIFBlYWNvY2sgaGFzIGhhZCB0aGUgbW9zdCBzYWxlcywgc2hlIGpvaW5lZCB0aGUgY29tcGFueSBpbiBBcHJpbCAyMDE3Lg0KTWFyZ2FyZXQgUGFyayBoYXMgdGhlIHNlY29uZCBtb3N0IHNhbGVzLCBzaGUgam9pbmVkIGluIE1heSAyMDE3Lg0KU3RldmUgSm9obnNvbiBoYXMgdGhlIGxlYXN0IHNhbGVzLCBidXQgaGUgZGlkbid0IGpvaW4gdW50aWwgT2N0b2JlciAyMDE3Lg0KQWxsIHRocmVlIGhhdmUgdGhlIHNhbWUgc3VwZXJ2aXNvci4NCkFzIHRoZXJlIGFyZSBvbmx5IDMgc3VwcG9ydCByZXBzLCB0aGVyZSBpc24ndCBlbm91Z2ggZGF0YSB0byBiZSBhYmxlIHRvIGNvbmNsdWRlIHdoZXRoZXIgYW55IG90aGVyIGZhY3RvciBhZmZlY3RzIGhvdyB3ZWxsIHRoZXkgcGVyZm9ybS4gDQoNCkxvb2tpbmcgYXQgc2FsZXMgZGF0YSBpbiBkaWZmZXJlbnQgY291bnRyaWVzOg0KYGBge3J9DQojVGhlIGZpcnN0IHBhcnQgb2YgdGhpcyBTUUwgY29kZSBsb29rcyBhdCB0aGUgY3VzdG9tZXIgdGFibGUgb25seSBhbmQgcmVwbGFjZXMgdGhlIGNvdW50cnkgbmFtZSB3aXRoICJvdGhlciIgaWYgdGhlcmUgaXMgb25seSBvbmUgY3VzdG9tZXIgZnJvbSBhIGNvdW50cnkuDQojVGhlIG5leHQgcGFydCBvZiB0aGUgY29kZSBjb250YWlucyBhIHN1Yi1xdWVyeSAod2hpY2ggZG9lcyBtb3N0IG9mIHRoZSB3b3JrIHNvIEknbGwgZXhwbGFpbiB0aGUgc3ViLXF1ZXJ5IGZpcnN0KQ0KI1RoZSBzdWItcXVlcnkgam9pbnMgdGhlIGN1c3RvbWVyIHRhYmxlIHRvIHRoZSBpbnZvaWNlIHRhYmxlLiBJdCBhbHNvIGNyZWF0ZXMgYSBuZXcgJ2xvZ2ljJyBjb2x1bW4gdXNpbmcgQ0FTRSBmb3Igd2hlbiBjb3VudHJ5IGlzICJPdGhlciIuDQojVGhlIG1haW4gcXVlcnkgdGhlbiB0YWtlcyBhbGwgb2YgdGhlIGNvbHVtbnMgZnJvbSB0aGUgc3VicXVlcnkgZXhjZXB0IHRoZSBsb2dpYyBjb2x1bW4gYW5kIHJlb3JkZXJzIGJ5IHRoZSBsb2dpYyBjb2x1bW4uDQoNCnNhbGVzX2J5X2NvdW50cnkgPSAnDQoNCldJVEggcmV2aXNlZCBBUyAoDQogICAgICAgICAgICAgICAgU0VMRUNUIA0KICAgICAgICAgICAgICAgICAgQ0FTRSANCiAgICAgICAgICAgICAgICAgIFdIRU4gKA0KICAgICAgICAgICAgICAgICAgICAgICAgU0VMRUNUIGNvdW50KCopDQogICAgICAgICAgICAgICAgICAgICAgICBGUk9NIGN1c3RvbWVyDQogICAgICAgICAgICAgICAgICAgICAgICBXSEVSRSBjb3VudHJ5ID0gYy5jb3VudHJ5DQogICAgICAgICAgICAgICAgICAgICAgICApID0gMSBUSEVOICJPdGhlciINCiAgICAgICAgICAgICAgICAgIEVMU0UgYy5jb3VudHJ5DQogICAgICAgICAgICAgICAgICBFTkQgQVMgY291bnRyeV9uZXcsDQogICAgICAgICAgICAgICAgICBjLmN1c3RvbWVyX2lkDQogICAgICAgICAgICAgICAgRlJPTSBjdXN0b21lciBjDQogICAgICAgICAgICAgICAgKQ0KDQpTRUxFQ1QgY291bnRyeSwNCiAgICAgICBub19jdXN0b21lcnMsDQogICAgICAgdG90YWxfc2FsZXMsDQogICAgICAgYXZnX2N1c3Rfc3BlbmQsDQogICAgICAgYXZnX29yZGVyX3ZhbHVlDQpGUk9NICgNCiAgICAgIFNFTEVDVCAgci5jb3VudHJ5X25ldyBjb3VudHJ5LA0KICAgICAgICAgICAgICBDT1VOVChESVNUSU5DVChyLmN1c3RvbWVyX2lkKSkgbm9fY3VzdG9tZXJzLA0KICAgICAgICAgICAgICBTVU0oaS50b3RhbCkgdG90YWxfc2FsZXMsDQogICAgICAgICAgICAgIFJPVU5EKFNVTShpLnRvdGFsKS9DT1VOVChESVNUSU5DVChyLmN1c3RvbWVyX2lkKSksMikgYXZnX2N1c3Rfc3BlbmQsDQogICAgICAgICAgICAgIFJPVU5EKGF2ZyhpLnRvdGFsKSwyKSBhdmdfb3JkZXJfdmFsdWUsDQogICAgICAgICAgICAgIENBU0UNCiAgICAgICAgICAgICAgV0hFTiByLmNvdW50cnlfbmV3ID0gIk90aGVyIiBUSEVOIDENCiAgICAgICAgICAgICAgRUxTRSAwDQogICAgICAgICAgICAgIEVORCBBUyBsb2dpYw0KICAgICAgRlJPTSByZXZpc2VkIHINCiAgICAgIExFRlQgSk9JTiBpbnZvaWNlIGkgT04gaS5jdXN0b21lcl9pZCA9IHIuY3VzdG9tZXJfaWQNCiAgICAgIEdST1VQIEJZIGNvdW50cnlfbmV3DQogICAgICBPUkRFUiBCWSB0b3RhbF9zYWxlcyBERVNDDQogICAgICApDQpPUkRFUiBCWSBsb2dpYyANCjsNCicNCg0KY291bnRyeV9kYiA8LSBydW5fcXVlcnkoc2FsZXNfYnlfY291bnRyeSkNCmNvdW50cnlfZGINCg0KDQoNCmBgYA0KYGBge3J9DQoNCg0KDQpgYGANCmBgYHtyfQ0KI3JlbW92ZSB0aGUgZW50cnkgZm9yIENvdW50cnkgPSAnT3RoZXInIA0KDQpjb3VudHJ5X2RiIDwtIGNvdW50cnlfZGIgJT4lDQogIGZpbHRlcihjb3VudHJ5ICE9ICJPdGhlciIpDQoNCmdncGxvdChkYXRhID0gY291bnRyeV9kYiwNCiAgICAgICAgYWVzKHggPSBjb3VudHJ5LCB5ID0gbm9fY3VzdG9tZXJzLCBmaWxsID0gY291bnRyeSkpICsNCiAgICAgICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9NjAsIGhqdXN0PTEpKSArDQogICAgICAgIGdlb21fYmFyKHN0YXQ9J2lkZW50aXR5JykrDQogICAgICAgIGxhYnModGl0bGUgPSAiTm8uIEN1c3RvbWVycyBwZXIgQ291bnRyeSIsIHggPSAiQ291bnRyeSIsIHkgPSAiTm8uIEN1c3RvbWVycyIpICsNCiAgICAgICAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gIndoaXRlIikpICsNCiAgICAgICAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IG5vX2N1c3RvbWVycywgeSA9IG5vX2N1c3RvbWVycyswLjUpKQ0KICAgICAgDQpnZ3Bsb3QoZGF0YSA9IGNvdW50cnlfZGIsDQogICAgICAgIGFlcyh4ID0gY291bnRyeSwgeSA9IHRvdGFsX3NhbGVzLCBmaWxsID0gY291bnRyeSkpICsNCiAgICAgICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9NjAsIGhqdXN0PTEpKSArDQogICAgICAgIGdlb21fYmFyKHN0YXQ9J2lkZW50aXR5JykgKw0KICAgICAgICBsYWJzKHRpdGxlID0gIlRvdGFsIFNhbGVzIHBlciBDb3VudHJ5IiwgeCA9ICJDb3VudHJ5IiwgeSA9ICJUb3RhbCBTYWxlcygkKSIpICsNCiAgICAgICAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gIndoaXRlIikpICsNCiAgICAgICAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHRvdGFsX3NhbGVzLCB5ID0gdG90YWxfc2FsZXMrNTApKQ0KDQpnZ3Bsb3QoZGF0YSA9IGNvdW50cnlfZGIsDQogICAgICAgIGFlcyh4ID0gY291bnRyeSwgeSA9IGF2Z19jdXN0X3NwZW5kLCBmaWxsID0gY291bnRyeSkpICsNCiAgICAgICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9NjAsIGhqdXN0PTEpKSArDQogICAgICAgIGdlb21fYmFyKHN0YXQ9J2lkZW50aXR5JykgKw0KICAgICAgICBsYWJzKHRpdGxlID0gIkF2ZXJhZ2UgQ3VzdG9tZXIgU3BlbmQgcGVyIENvdW50cnkiLCB4ID0gIkNvdW50cnkiLCB5ID0gIkF2ZXJhZ2UgQ3VzdG9tZXIgU3BlbmQoJCkiKSArDQogICAgICAgIHRoZW1lKHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ3aGl0ZSIpKSArDQogICAgICAgIGdlb21fdGV4dChhZXMobGFiZWwgPSBhdmdfY3VzdF9zcGVuZCwgeSA9IGF2Z19jdXN0X3NwZW5kKzUpKQ0KDQpnZ3Bsb3QoZGF0YSA9IGNvdW50cnlfZGIsDQogICAgICAgIGFlcyh4ID0gY291bnRyeSwgeSA9IGF2Z19vcmRlcl92YWx1ZSwgZmlsbCA9IGNvdW50cnkpKSArDQogICAgICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlPTYwLCBoanVzdD0xKSkgKw0KICAgICAgICBnZW9tX2JhcihzdGF0PSdpZGVudGl0eScpICsNCiAgICAgICAgbGFicyh0aXRsZSA9ICJBdmVyYWdlIE9yZGVyIFZhbHVlIHBlciBDb3VudHJ5IiwgeCA9ICJDb3VudHJ5IiwgeSA9ICJBdmVyYWdlIE9yZGVyIFZhbHVlKCQpIikgKw0KICAgICAgICB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSAid2hpdGUiKSkgKw0KICAgICAgICBnZW9tX3RleHQoYWVzKGxhYmVsID0gYXZnX29yZGVyX3ZhbHVlLCB5ID0gYXZnX29yZGVyX3ZhbHVlKzAuNSkpDQpgYGANClRoZSBncmVhdGVzdCB0b3RhbCBzYWxlcyBpcyBmcm9tIHRoZSBVUyBtYXJrZXQgZm9sbG93ZWQgYnkgQ2FuYWRhLiBIb3dldmVyLCB0aGUgYXZlcmFnZSBjdXN0b21lciBzcGVuZCBpbiBDYW5hZGEgaXMgdGhlIGxvd2VzdCBvdXQgb2YgYWxsIHRoZSBjb3VudHJpZXMgKGV4YyBvdGhlcikuIFNvbWUgdGFyZ2V0IG1hcmtldGluZyBhdCBleGlzdGluZyBjdXN0b21lcnMgY291bGQgaW5jcmVhc2UgdGhlIHNwZW5kIHBlciBjdXN0b21lciBhbmQgaGVuY2UgaW5jcmVhc2UgdG90YWwgc2FsZXMuDQpUaGUgQ3plY2ggUmVwdWJsaWMgaGFzIG9ubHkgMiBjdXN0b21lcnMgYnV0IHRoZSBhdmVyYWdlIGN1c3RvbWVyIHNwZW5kIGlzIHRoZSBoaWdoZXN0LiBDb3VsZCBzb21lIHRhcmdldCBtYXJrZXRpbmcgaW4gdGhlIEN6ZWNoIFJlcGx1YmxpYyByZWFwIHJld2FyZHMgd2l0aCBoaWdoIHNwZW5kZXJzPw0KDQpMb29raW5nIGF0IHdoYXQgcGVyY2VudGFnZSBvZiBzYWxlcyBhcmUgJ3dob2xlIGFsYnVtIHB1cmNoYXNlcyc6DQoNCkkgd2lsbCBjYWxjdWxhdGUgd2hhdCBwZXJjZW50YWdlIG9mIGFsbCBwdXJjaGFzZXMgYXJlICd3aG9sZSBhbGJ1bSBwdXJjaGFzZXMnIGkuZSB0aGUgY3VzdG9tZXIgc2VsZWN0cyBhbmQgYnV5cyBhIHdob2xlIGFsYnVtLCByYXRoZXIgdGhhbiBzZWxlY3RzIGEgbnVtYmVyIG9mIHRyYWNrcyAoZnJvbSBkaWZmZXJlbnQgYWxidW1zKQ0KDQpgYGB7cn0NCiNNeSBzb2x1dGlvbiB1c2VzIGEgY29tcGxldGVseSBkaWZmZXJlbnQgbWV0aG9kIGZyb20gd2hhdCB3YXMgc3VnZ2VzdGVkIGluIHRoZSBndWlkYW5jZSBub3Rlcy4NCiNNeSBtZXRob2QgaXMgYmFzZWQgb24gdGhlIGZhY3QgdGhhdCBpZiBhIGN1c3RvbWVyIGJ1eXMgYSB3aG9sZSBhbGJ1bSB0aGVuIHRoZXNlIDIgdGhpbmdzIHdpbGwgYmUgdHJ1ZToNCiMgMSAtIFRoZXJlIHdpbGwgb25seSBiZSAxIGRpc3RpbmN0IGFsYnVtIG51bWJlciBwZXIgaW52b2ljZSBpLmUuIHdoZW4geW91IENPVU5UKERJU1RJTkNUIGFsYnVtX2lkKSBmb3IgZWFjaCBpbnZvaWNlIGl0IHdpbGwgcmV0dXJuIDENCiMgMiAtIFRoZSBudW1iZXIgb2YgdHJhY2tzIHB1cmNoYXNlZCB3aWxsIGVxdWFsIHRoZSBudW1iZXIgb2YgdHJhY2tzIG9uIHRoZSBhbGJ1bSBvZiB0aGUgZmlyc3QgYWxidW0gbnVtYmVyIG9uIHRoZWlyIGludm9pY2UNCiNJZiBib3RoIHRoZXNlIHRoaW5ncyBhcmUgdHJ1ZSwgdGhlbiB0aGUgY3VzdG9tZXIgcHVyY2hhc2VkIGFuIGFsYnVtLg0KDQojVGhlIHF1ZXJ5IGlzIHNwbGl0IGludG8gYSBmaXJzdCBwYXJ0IC0gYV90X25vcyAoYWxidW0gdHJhY2sgbnVtYmVycyksIHRoZW4gdGhlIG1haW4gcGFydCB3aGljaCBpbmNsdWRlcyBhIHN1YnF1ZXJ5Lg0KDQojVGhlIGZpcnN0IHBhcnQgdGFrZXMgdGhlIGFsYnVtIHRhYmxlIGFuZCBqb2lucyBpdCB0byB0aGUgdHJhY2sgdGFibGUuIEl0IHRoZW4gZ3JvdXBzIGJ5IGFsYnVtX2lkIGFuZCBjb3VudHMgdGhlIG51bWJlciBvZiB0cmFja3Mgc28gdGhlIHJlc3VsdCBpcyBhIHRhYmxlIGFfdF9ub3Mgb2YgYWxidW1zIGFuZCB0aGUgY29ycmVzcG9uZGluZyBudW1iZXIgb2YgdHJhY2tzIG9uIGVhY2ggYWxidW0uDQoNCiNUaGUgbWFpbiBwYXJ0LCBzdGFydGluZyB3aXRoIHRoZSBzdWJxdWVyeSwgdGhlbiB0YWtlcyB0aGUgaW52b2ljZV9saW5lIHRhYmxlIGFuZCBqb2lucyBpdCB0byB0aGUgdHJhY2sgdGFibGUgYW5kIHRoZW4gam9pbnMgdGhlIGFfdF9ub3MgdGFibGUgb250byB0aGF0LiBJdCBncm91cHMgYnkgaW52b2ljZV9saW5lLiBGb3IgZWFjaCBpbnZvaWNlX2xpbmUsIGl0IGNhbGN1bGF0ZXM6DQojIC0gdGhlIG51bWJlciBvZiB0cmFja3MgYm91Z2h0DQojIC0gdGhlIG51bWJlciBvZiBhbGJ1bXMgb24gZWFjaCBpbnZvaWNlIGkuZS4gQ09VTlQoRElTVElOQ1QgYWxidW1faWQpIA0KIyAtIGl0IGxlYXZlcyB0aGUgY29sdW1ucyBpbnZvaWNlX2lkLCBhbGJ1bV9pZCBhbmQgdHJhY2tzX2luX2FsYnVtDQojSXQgdGhlbiBhZGRzIGEgbG9naWMgY29sdW1uIGNhbGxlZCAnYWxidW0nLiBUaGlzIGNvbHVtbiBpcyBnaXZlbiB0aGUgdmFsdWUgb2YgMSBpZiBCT1RIIG9mIHRoZXNlIHR3byBjb25kaXRpb25zIGFyZSBzYXRpc2ZpZWQ6DQojIDEuIElzIHRoZSBudW1iZXIgb2YgZGlzdGluY3QgYWxidW1zIG9uIHRoaXMgaW52b2ljZSAxPyANCiMgMi4gSXMgdGhlIG51bWJlciBvZiB0cmFja3Mgb24gdGhlIGludm9pY2UgZXF1YWwgdG8gdGhlIG51bWJlciBvZiB0cmFja3MgaW4gdGhlIGFsYnVtIG51bWJlcj8NCiNJZiB0aGV5J3JlIGJvdGggc2F0aXNmaWVkLCB0aGVuIGl0J3MgYW4gYWxidW0hDQoNCiNMYXN0bHksIHRoZSBlbmNvbXBhc3NpbmcgcXVlcnkgc3VtcyB0aGUgbnVtYmVyIG9mIGluc3RhbmNlcyB3aGVyZSB0aGUgYWxidW0gY29sdW1uID0gMSAoaS5lLiB0aGlzIGlzIHRoZSBudW1iZXIgb2YgaW52b2ljZXMgd2hpY2ggcmVsYXRlIHRvIHdob2xlIGFsYnVtIHB1cmNoYXNlcyBieSBjdXN0b21lcnMpLiBJdCBjb3VudHMgdGhlIGFsYnVtIGNvbHVtbiAoaS5lLiB0aGlzIGlzIHRoZSB0b3RhbCBudW1iZXIgb2YgaW52b2ljZXMpLiBJdCBjYWxjdWxhdGVzIHRoZSBwZXJjZW50YWdlIG9mIGludm9pY2VzIHdoaWNoIGFyZSB3aG9sZS1hbGJ1bSBwdXJjaGFzZXMuDQoNCmlzX2l0X2FuX2FsYnVtID0gJw0KV0lUSCBhX3Rfbm9zIEFTDQogICAgICAgICAgICAgICgNCiAgICAgICAgICAgICAgU0VMRUNUICBhLmFsYnVtX2lkLA0KICAgICAgICAgICAgICAgICAgICAgIHQudHJhY2tfaWQsDQogICAgICAgICAgICAgICAgICAgICAgQ09VTlQodC50cmFja19pZCkgdHJhY2tzX2luX2FsYnVtDQogICAgICAgICAgICAgIEZST00gYWxidW0gYQ0KICAgICAgICAgICAgICBJTk5FUiBKT0lOIHRyYWNrIHQgT04gdC5hbGJ1bV9pZCA9IGEuYWxidW1faWQNCiAgICAgICAgICAgICAgR1JPVVAgQlkgYS5hbGJ1bV9pZA0KICAgICAgICAgICAgICApDQoNClNFTEVDVCAgU1VNKGFsYnVtKSBpbnZvaWNlc19mb3JfYWxidW1zLA0KICAgICAgICBDT1VOVChhbGJ1bSkgYWxsX2ludm9pY2VzLA0KICAgICAgICBST1VORChDQVNUKFNVTShhbGJ1bSkgQVMgRkxPQVQpL0NPVU5UKGFsYnVtKSwgMikgcGVyY2VudF9pbnZvaWNlc19mb3JfYWxidW1zDQpGUk9NKA0KICAgIFNFTEVDVCAgaWwuaW52b2ljZV9pZCwNCiAgICAgICAgICAgIENPVU5UKGlsLnRyYWNrX2lkKSB0cmFja3NfYm91Z2h0LA0KICAgICAgICAgICAgQ09VTlQoRElTVElOQ1QgdC5hbGJ1bV9pZCkgc29sZV9hbGJ1bV9jaGVjaywNCiAgICAgICAgICAgIGF0bi5hbGJ1bV9pZCwNCiAgICAgICAgICAgIGF0bi50cmFja3NfaW5fYWxidW0sDQogICAgICAgICAgICBDQVNFDQogICAgICAgICAgICAgIFdIRU4gQ09VTlQoRElTVElOQ1QgdC5hbGJ1bV9pZCkgPSAxIEFORCBDT1VOVChpbC50cmFja19pZCkgPSBhdG4udHJhY2tzX2luX2FsYnVtIFRIRU4gMQ0KICAgICAgICAgICAgICBFTFNFIDANCiAgICAgICAgICAgICAgRU5EIEFTIGFsYnVtDQogICAgICAgIA0KICAgIEZST00gaW52b2ljZV9saW5lIGlsDQogICAgSU5ORVIgSk9JTiB0cmFjayB0IE9OIHQudHJhY2tfaWQgPSBpbC50cmFja19pZA0KICAgIExFRlQgSk9JTiBhX3Rfbm9zIGF0biBPTiBhdG4uYWxidW1faWQgPSB0LmFsYnVtX2lkDQogICAgR1JPVVAgQlkgaW52b2ljZV9pZA0KICApDQo7DQonDQoNCnJ1bl9xdWVyeShpc19pdF9hbl9hbGJ1bSkNCg0KYGBgDQpPZiBhbGwgcHVyY2hhc2VzLCAxOSUgYXJlIHdob2xlIGFsYnVtIHB1cmNoYXNlcy4gVGhpcyBpcyBxdWl0ZSBhIGxhcmdlIHByb3BvcnRpb24gb2YgdG90YWwgcHVyY2hhc2VzIHNvIGl0IHdvdWxkbid0IGJlIGFkdmlzYWJsZSB0byBjaGFuZ2Ugc3RyYXRlZ3kuDQoNCk15IG1ldGhvZCB3YXMgZGlmZmVyZW50IGZyb20gdGhlIHN1Z2dlc3RlZCBtZXRob2QuIEhlcmUgaXMgdGhlIG1vZGVsIGFuc3dlciBzaG93aW5nIHRoZSBzdWdnZXN0ZWQgbWV0aG9kIHdoaWNoIHVzZXMgRVhDRVBUIHRvIHJ1biAyIGxpc3RzIGFnYWluc3QgZWFjaCBvdGhlciB0byBjaGVjayBpZiB0aGV5IGFyZSB0aGUgc2FtZS4gSSBoYXZlIGNvbW1lbnRlZCB0aGUgY29kZS4NCmBgYHtyfQ0KI1RoZSBmaXJzdCBwYXJ0IG9mIHRoZSBjb2RlIHRha2VzIHRoZSBpbnZvaWNlX2lkIGFuZCBjYWxjdWxhdGVzIHRoZSBmaXJzdF90cmFja19pZCAodXNpbmcgTUlOKSBmcm9tIGVhY2ggaW52b2ljZSBhbmQgcHV0cyB0aGlzIGluIGEgdGFibGUgY2FsbGVkIGludm9pY2VfZmlyc3RfdHJhY2suDQoNCiNUaGUgbmV4dCBiaXQgaW4gdGhlIGNvZGUgaXMgYWN0dWFsbHkgdGhlIGxhc3QgYml0IHRvIGJlIGNhbGN1bGF0ZWQgc28gSSdsbCBza2lwIHRoaXMgZm9yIG5vdy4gU2VlIFsxXS4NCg0KI1RoZSAnbWFpbiBiaXQnIG9mIHRoZSBjb2RlIHRha2VzIHRoZSB0YWJsZSBpbnZvaWNlX2ZpcnN0X3RyYWNrICh3aXRoIGFsaWFzIGlmdCkgYW5kIHNlbGVjdHMgYWxsIHRoZSBjb2x1bW5zIChpZnMuKikgcGx1cyBpdCBhZGRzIGEgY2FzZSBjb2x1bW4gY2FsbGVkIGFsYnVtX3B1cmNoYXNlIHdoaWNoIGl0IGFzaWducyBhIFlFUyBvciBOTyB0by4NCiNXaXRoaW4gdGhlICdDQVNFJyBwYXJ0Og0KDQojc2VsZWN0cyBhbGwgdGhlIHRyYWNrcyBmcm9tIHRoZSBhbGJ1bSBudW1iZXIgY29ycmVzcG9uZGluZyB0byB0aGUgYWxidW0gbnVtYmVyIGZyb20gdGhlIGZpcnN0X3RyYWNrX2lkIGNvbHVtbiBpbiBpZnMNCiNzbyB0aGF0J3MgdGhlICd0cnVlIGFsYnVtIGxpc3QnDQojbmV4dCBpdCBuZWVkcyB0byBjb21wYXJlIHRoYXQgdG8gdGhlIGxpc3Qgb2YgdHJhY2tzIG9uIHRoYXQgaW52b2ljZS4gaS5lLiBpdCB0YWtlcyB0aGUgdHJhY2tfaWQgZnJvbSBpbCB3aGVyZSBpbnZvaWNlX2lkIGNvcnJlc3BvbmRzIHRvIHRoZSBpZnMgaW52b2ljZV9pZC4NCiNJdCBjaGVja3MgaWYgaXQgcmV0dXJucyBOVUxMDQojVGhlbiBpdCBhbHNvIGhhcyB0byBkbyBpdCB0aGUgb3RoZXIgd2F5IHJvdW5kDQojdGhleSBib3RoIGhhdmUgdG8gcmV0dXJuIE5VTEwNCiNJZiB0aGV5IGJvdGggcmV0dXJuIE5VTEwgdGhlbiBhICd5ZXMnIGlzIHB1dCBpbiB0aGUgImFsYnVtX3B1cmNoYXNlIiBjb2x1bW4uIEVMU0UgTk8uIA0KDQojWzFdIExhc3RseSBnbyBiYWNrIHVwIHRvIHRoZSBjb2RlIGZyb20gYmVmb3JlIHdoaWNoIGlzIHRoZSBsYXN0IGJpdCB0byBiZSBjYWxjdWxhdGVkLiBUaGlzIGNhbGN1bGF0ZXMgY29sdW1ucyBhbGJ1bV9wdXJjaGFzZSwgbnVtYmVyX29mX2ludm9pY2VzIGFuZCBwZXJjZW50DQoNCg0KYWxidW1zX3ZzX3RyYWNrcyA9ICcNCldJVEggaW52b2ljZV9maXJzdF90cmFjayBBUw0KICAgICgNCiAgICAgU0VMRUNUDQogICAgICAgICBpbC5pbnZvaWNlX2lkIGludm9pY2VfaWQsDQogICAgICAgICBNSU4oaWwudHJhY2tfaWQpIGZpcnN0X3RyYWNrX2lkDQogICAgIEZST00gaW52b2ljZV9saW5lIGlsDQogICAgIEdST1VQIEJZIDENCiAgICApDQogICAgDQpTRUxFQ1QNCiAgICBhbGJ1bV9wdXJjaGFzZSwNCiAgICBDT1VOVChpbnZvaWNlX2lkKSBudW1iZXJfb2ZfaW52b2ljZXMsDQogICAgQ0FTVChjb3VudChpbnZvaWNlX2lkKSBBUyBGTE9BVCkgLyAoDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFNFTEVDVCBDT1VOVCgqKSBGUk9NIGludm9pY2UNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSBwZXJjZW50DQpGUk9NDQogICAgKA0KICAgIFNFTEVDVA0KICAgICAgICBpZnMuKiwNCiAgICAgICAgQ0FTRQ0KICAgICAgICAgICAgV0hFTg0KICAgICAgICAgICAgICAgICAoDQogICAgICAgICAgICAgICAgICBTRUxFQ1QgdC50cmFja19pZCBGUk9NIHRyYWNrIHQNCiAgICAgICAgICAgICAgICAgIFdIRVJFIHQuYWxidW1faWQgPSAoDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFNFTEVDVCB0Mi5hbGJ1bV9pZCBGUk9NIHRyYWNrIHQyIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBXSEVSRSB0Mi50cmFja19pZCA9IGlmcy5maXJzdF90cmFja19pZA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkgDQogICAgICAgICAgICAgICAgICBFWENFUFQgDQogICAgICAgICAgICAgICAgICBTRUxFQ1QgaWwyLnRyYWNrX2lkIEZST00gaW52b2ljZV9saW5lIGlsMg0KICAgICAgICAgICAgICAgICAgV0hFUkUgaWwyLmludm9pY2VfaWQgPSBpZnMuaW52b2ljZV9pZA0KICAgICAgICAgICAgICAgICApIElTIE5VTEwNCiAgICAgICAgICAgICBBTkQNCiAgICAgICAgICAgICAgICAgKA0KICAgICAgICAgICAgICAgICAgU0VMRUNUIGlsMi50cmFja19pZCBGUk9NIGludm9pY2VfbGluZSBpbDINCiAgICAgICAgICAgICAgICAgIFdIRVJFIGlsMi5pbnZvaWNlX2lkID0gaWZzLmludm9pY2VfaWQNCiAgICAgICAgICAgICAgICAgIEVYQ0VQVCANCiAgICAgICAgICAgICAgICAgIFNFTEVDVCB0LnRyYWNrX2lkIEZST00gdHJhY2sgdA0KICAgICAgICAgICAgICAgICAgV0hFUkUgdC5hbGJ1bV9pZCA9ICgNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgU0VMRUNUIHQyLmFsYnVtX2lkIEZST00gdHJhY2sgdDINCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgV0hFUkUgdDIudHJhY2tfaWQgPSBpZnMuZmlyc3RfdHJhY2tfaWQNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApIA0KICAgICAgICAgICAgICAgICApIElTIE5VTEwNCiAgICAgICAgICAgICBUSEVOICJ5ZXMiDQogICAgICAgICAgICAgRUxTRSAibm8iDQogICAgICAgICBFTkQgQVMgImFsYnVtX3B1cmNoYXNlIg0KICAgICBGUk9NIGludm9pY2VfZmlyc3RfdHJhY2sgaWZzDQogICAgKQ0KR1JPVVAgQlkgYWxidW1fcHVyY2hhc2U7DQonDQpydW5fcXVlcnkoYWxidW1zX3ZzX3RyYWNrcykNCg0KYGBgDQoNCiAgICAgICAgICAgIA0KDQoNCg==