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==