1 Objektif

Ini adalah catatan untuk memandu secara singkat dalam melakukan eksplorasi data menggunakan R dan beberapa package yang menjadi bagian dari package tidyverse. Tujuan dari tutorial ini adalah agar peserta dapat mencoba mengakses data yang berasal dari database (pada kesempatan ini menggunakan MySQL) melalui R dan menggunakan data dari file CSV yang dapat diunduh. Database yang akan diakses telah disediakan oleh pembicara di localhost-nya. Namun, Anda hanya dapat mengaksesnya ketika kegiatan sedang berlangsung.

2 Cakupan Materi

Materi yang akan dibahas:

  • Operator Pipes untuk beberapa fungsi secara berurutan/sequence
  • Mendapatkan nilai unik/tidak duplikat dari data frame atau variable
  • Subset variable berdasarkan nama/indeks variable
  • Subset baris berdasarkan indeks baris atau nilai dari satu atau beberapa variabel
  • Mengurutkan sebuah data frame berdasarkan satu atau beberapa variable
  • Penanganan NA (missing value)
  • Membuat variabel baru/menghitung dari variabel yang sudah ada
  • Tabel frekuensi dan transformasi tabel
  • Ringkasan (summary) variabel atau berdasarkan group
  • Merge/Join tabel

3 Prasyarat

Untuk dapat mengikuti tutorial ini dengan baik, ada beberapa hal yang perlu dipersiapkan oleh peserta. Yaitu:

  1. Koneksi internet yang baik dan terhubung dalam jaringan yang sama dengan PC pembicara untuk dapat mengakses database.

  2. Menginstall software

    1. R program https://cran.r-project.org/

    2. RStudio https://www.rstudio.com/products/rstudio/download/

  3. Data & Script yang dapat diperoleh dari repository ini dan pada database yang disediakan pembicara.

  4. Package R yang dibutuhkan: readr, tidyr, dplyr, ggplot2 (atau tidyverse), dan RMySQL.

Catatan: Data diperoleh dari packages nycflights13 yang disimpan ke dalam database. Jika Anda ingin mencoba diluar kegiatan atau tidak dapat terhubung dengan database pembicara, Anda dapat menginstall package nycflights13 untuk memperoleh data yang digunakan pada database. Dua data lain berupa file CSV untuk disesuaikan dengan kebutuhan tutorial. Data ini terdiri dari 336,776 penerbangan dari New York City (NYC) selama tahun 2013. Data asli berasal dari US Bureau of Transportation Statistics, dan dapat dilihat dokumentasinya dengan ?nycflights13::flights.

install.packages("nycflights13")
library(nycflights13)

Pastikan Anda sudah berhasil install package tersebut.

4 Install Packages

Jalankan perintah di bawah ini untuk install package (jika Anda belum pernah install) yang akan digunakan untuk dapat mengikuti tutorial ini sampai selesai.

install.packages(c("readr", "tidyr", "dplyr", "ggplot2", "RMySQL"))
# atau                                                                         
install.packages(c("tidyverse", "RMySQL"))                         

Panggil package yang sudah Anda install dengan fungsi library().

# Panggil package yang sudah terisntall
library(RMySQL)
library(readr)
library(tidyr)
library(dplyr)
library(ggplot2)

# atau cukup memanggil
# library(tidyverse)
# untuk memanggil package di atas selain RMySQL

Package reader, tidyr, dplyr dan ggplot2 (dan beberapa package lain yang tidak digunakan di tutorial ini) termasuk dalam bagian package tidyverse. tidyverse adalah kumpulan package yang dibuat oleh Hadley Wickham dkk untuk kebutuhan data science menggunakan R.

  • RMySQL digunakan untuk membuat koneksi antara R dan database MySQL. Beberapa fungsi yang akan digunakan pada tutorial ini antara lain dbConnect() yang berasal dari package DBI untuk membuat koneksi, dbReadTable() untuk import data dari database ke R dan fungsi dbDisconnect() untuk memutuskan koneksi yang sudah tidak digunakan.
  • readr berguna untuk import data dari tabular data file (csv, text file, dll).
  • tidyr memiliki fungi-fungsi untuk “merapihkan” data. Terutama yang sering digunakan adalah fungsi gather() dan spread().
  • dplyr adalah package yang sangat berguna untuk melakukan manipulasi/transformasi data menggunakan R.
  • ggplot2 adalah salah satu package yang sangat banyak digunakan oleh pengguna R untuk kebutuhan visualisasi.

Tidyverse

Tidyverse

tidyverse menggunakan tibble sebagai pengganti data.frame. > Tibbles are data frames, but they tweak some older behaviours to make life a little easier. R is an old language, and some things that were useful 10 or 20 years ago now get in your way. It’s difficult to change base R without breaking existing code, so most innovation occurs in packages – Grolemund & Wickham.

Beberapa kelebihan tibble dibandingkan data.frame diantaranya adalah ketika menampilkan data, tibble tidak menampilkan semua baris dan kolom. Jika ada lebih dari 10 baris data, maka hanya akan ada 10 baris pertama yang ditampilkan dan beberapa variabel sesuai dengan lebar console R Anda. Untuk lebih memahami tentang tibble, silahkan membaca artikel ini.

5 Operator Pipes (%>%)

Sebelum kita mulai, kita akan membahas terlebih dahulu sebuah operator yang sangat berguna dan banyak digunakan oleh pengguna R yang menggunakan tidyverse. Operator ini adalah Pipes (%>%). Perhatikan contoh di bawah ini.

mean(iris$Sepal.Length)
[1] 5.843333

atau

iris$Sepal.Length %>% mean()
[1] 5.843333

Kedua script tersebut melakukan hal yang sama dan menghasilkan nilai yang sama.

Misalkan ada rangkaian dari beberapa fungsi seperti ini, fun1(), fun2() dan fun3() adalah fungsi di R.

output <- fun3(fun2(fun1(dataframe, arg1), arg2), arg3)

Jika Anda diminta untuk mempelajari script seperti ini saya cukup yakin bahwa Anda akan merasa kesulitan untuk mengetahui proses yang akan dilakukan oleh script tersebut. Anda harus meperhatikan argumen yang digunakan untuk fungsi tertentu. Script di atas masih sederhana, hanya ada tiga fungsi. Bayangkan jika banyak fungsi yang digunakan secara berurutan, bentuk di atas akan menjadi:

output <- fun_n(fun_(n-1)(...(fun3(fun2(fun1(dataframe, arg1), arg2), arg3), ...), arg_(n-1)), arg_n)

Dengan konsep tidyverse, kita dapat menggunakan operator %>% agar lebih mudah dalam memahami script karena script tersebut menunjukkan urutan.

5.1 Penjelasan Operator Pipes %>%

Saya akan coba menjelaskan lebih dalam untuk lebih memahami oprator %>%.

Misalkan f(a, x) adalah sebuah fungsi di R dengan argumen a dan x. kemudian fungsi g(b, z) adalah fungsi lain di R dengan argumen b dan z. Dengan menggunakan operator %>% kita dapat menuliskannya sebagai berikut.

# fungsi f(a, x)
f(a, x) # atau
a %>% f(x)

# fungsi g(b, z)
g(b, z) # atau
b %>% g(z)

Dari kedua contoh di atas, dapat dilihat bahwa a adalah argumen pertama untuk fungsi f() dan b adalah argumen pertama untuk fungsi g(). Operator %>% “menyampaikan” objek a sebagai nilai untuk mengisi argumen pertama pada fungsi f(). Perhatikan ilustrasi di bawah ini.

Ilustrasi Pipes 1

Ilustrasi Pipes 1

Misalkan objek a menjadi argumen pertama fungsi f() dengan a %>% f(x). Kemudia hasil dari a %>% f(x) dijadikan argumen pertama dari fungsi g(). Dengan kata lain b <- a %>% f(x) sehingga b %>% g(z). Hal ini dapat dilakukan secara berurutan dengan operator %>% sebagai berikut.

a %>%
    f(x) %>%
    g(z)

Ilustrasi Pipes 2

Ilustrasi Pipes 2

“Fungsi f() diterapkan terhadap objek a sebagai argumen pertama dan x sebagai argumen kedua dari fungsi f() yang kemudian hasilnya digunakan sebagai argumen pertama pada fungsi g() dengan z sebagai argumen kedua.”

Selanjutnya kita akan gunakan operator %>% dalam tutorial ini.

6 Koneksi ke Database dan Import Data

Untuk data yang tersedia di database, Anda dapat membuat koneksi ke database dengan perintah berikut ini.

srv <- "localhost" # ganti dengan IP yang akan diberikan oleh pembicara
port <- 3306
dbn <- "nycflights"
usr <- "user1"
pwd <- "P@ssw0rd"

# Membuat koneksi ke database MySQL
mycon <- dbConnect(MySQL(), 
                   host = srv, 
                   dbname = dbn, 
                   user = usr, 
                   password = pwd, 
                   port = port)

# List tabel yang ada di dalam database
dbListTables(mycon)
[1] "airlines" "airports" "flights"  "planes"   "weather" 

Untuk impor data dari datbase ke R dapat menggunakan fungsi dbReadTable().

flights <- mycon %>% dbReadTable("flights")
airlines <- mycon %>% dbReadTable("airlines")
airports <- mycon %>% dbReadTable("airports")
weather <- mycon %>% dbReadTable("weather")
planes <- mycon %>% dbReadTable("planes")

Penting untuk menutup koneksi ke database setelah selesai mengakses dan tidak menggunakan koneksi tersebut. Memutuskan koneksi dapat menggunakan fungsi dbDisconnect().

# jangan menajalankan fungsi di bawah ini jika Anda masih memerlukan/menggunakan koneksi di atas!
dbDisconnect(mycon)
[1] TRUE

Untuk data CSV yang akan digunakan dapat Anda unduh terlebih dahulu dan simpan di sebuah folder working directory, misalnya di dalam folder D:/aephidayatuloh/R/learning/Rdb dengan nama prices.csv. Untuk mengaktifkan working directory pada folder tersebut Anda dapat gunakan fungsi setwd().

setwd("D:/aephidayatuloh/R/learning/Rdb") # ganti sesuai dengan lokasi folder yang Anda gunakan
download.file(url = "https://raw.githubusercontent.com/aephidayatuloh/manipulasi-data/master/prices.csv", destfile = "prices.csv")
download.file(url = "https://raw.githubusercontent.com/aephidayatuloh/manipulasi-data/master/specialdays.csv", destfile = "specialdays.csv")

Perhatikan bahwa pada file prices.csv pemisah antar kolomnya menggunakan tanda titik koma (;) dan tanda koma (,) sebagai pemisah desimal.

# Import data dari CSV tersebut ke R
prices <- read_delim("prices.csv", delim = ";", escape_double = FALSE, trim_ws = TRUE)
specialdays <- read_csv("specialdays.csv")

Dengan fungsi yang ada pada readr, setelah selesai import data akan muncul tipe atau spesifikasi dari variabel yang dibaca.

Atau dengan cara langsung import tanpa unduh terlebih dahulu.

prices <- read_delim("https://raw.githubusercontent.com/aephidayatuloh/manipulasi-data/master/prices.csv", delim = ";", escape_double = FALSE, trim_ws = TRUE)

specialdays <- read_csv("https://raw.githubusercontent.com/aephidayatuloh/manipulasi-data/master/specialdays.csv")

File prices.csv dibaca menggunakan fungsi read_delim() karena pemisah antar variablenya adalah titik-koma (:), maka dari itu disebutkan juga delim = ";". Untuk file specialdays.csv dibaca menggunakan fungsi read_csv() karena menggunakan tanda koma (,) sebagai pemisah antar variable.

  • Keterangan data:
    • flights: semua penerbangan yang berangkat dari NYC di tahun 2013.
    • weather: data meteorologi per jam untuk masing-masing bandara (airports).
    • planes: information tentang konstruksi masing-masing pesawat.
    • airports: nama dan lokasi bandara.
    • airlines: translasi antara dua huruf kode carrier dan namanya.
    • prices: daftar harga tiket (ticket) dan biaya (cost) per penumpang untuk masing-masing penerbangan.
    • specialdays: daftar hari “istimewa” di Amerika.

Keterangan variable:

flights

Variable Keterangan
year,month,day Date of departure
dep_time, arr_time Actual departure and arrival times (format HHMM or HMM), local tz.
sched_dep_time, sched_arr_time Scheduled departure and arrival times (format HHMM or HMM), local tz.
dep_delay, arr_delay Departure and arrival delays, in minutes. Negative times represent early departures/arrivals.
hour, minute Time of scheduled departure broken into hour and minutes.
carrier Two letter carrier abbreviation. See ?airlines to get name.
tailnum Plane tail number
flight Flight number
origin, dest Origin and destination. See ?airports for additional metadata.
air_time Amount of time spent in the air, in minutes.
distance Distance between airports, in miles.
time_hour Scheduled date and hour of the flight as a POSIXct date. Along with origin, can be used to join flights data to weather data.

airlines

Variable Keterangan
carrier Two letter abbreviation
name Full name

planes

Variable Keterangan
tailnum Tail number
year Year manufactured
type Type of plane
manufacturer,model Manufacturer and model
engines,seats Number of engines and seats
speed Average cruising speed in mph
engine Type of engine

weather

Variable Keterangan
origin Weather station. Named origin to faciliate merging with flights data.
year,month,day,hour Time of recording.
temp,dewp Temperature and dewpoint in Fahrenheit.
humid Relative humidity.
wind_dir,wind_speed,wind_gust Wind direction (in degrees), speed and gust speed (in mph).
precip Precipitation, in inches.
pressure Sea level pressure in millibars.
visib Visibility in miles
time_hour Date and hour of the recording as a POSIXct date.

airports

Variable Keterangan
faa FAA airport code.
name Usual name of the aiport.
lat,lon Location of airport.
alt Altitude, in feet.
tz Timezone offset from GMT.
dst Daylight savings time zone. A = Standard US DST: starts on the second Sunday of March, ends on the first Sunday of November. U = unknown. N = no dst.
tzone IANA time zone, as determined by GeoNames webservice

prices

Variable Keterangan
year,month,day Date of departure
origin,dest Origin and destination. See ?airports for additional metadata.
sched_dep_time Scheduled departure and arrival times (format HHMM or HMM), local tz.
tailnum Plane tail number
ticket Average ticket price per person for a flight
cost Average operating costs per person for a flight

specialdays

Variable Keterangan
year,month,day Date of departure
holiday Name of special day in United States

7 Materi

Setelah berhasil import data dari MySQL dan CSV, selanjutnya kita akan mengeksplorasi data tersebut.

dim(flights)
[1] 336776     19
glimpse(flights)
Observations: 336,776
Variables: 19
$ year           <dbl> 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2013, 201...
$ month          <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ...
$ day            <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ...
$ dep_time       <dbl> 517, 533, 542, 544, 554, 554, 555, 557, 557, 558, 558, 55...
$ sched_dep_time <dbl> 515, 529, 540, 545, 600, 558, 600, 600, 600, 600, 600, 60...
$ dep_delay      <dbl> 2, 4, 2, -1, -6, -4, -5, -3, -3, -2, -2, -2, -2, -2, -1, ...
$ arr_time       <dbl> 830, 850, 923, 1004, 812, 740, 913, 709, 838, 753, 849, 8...
$ sched_arr_time <dbl> 819, 830, 850, 1022, 837, 728, 854, 723, 846, 745, 851, 8...
$ arr_delay      <dbl> 11, 20, 33, -18, -25, 12, 19, -14, -8, 8, -2, -3, 7, -14,...
$ carrier        <chr> "UA", "UA", "AA", "B6", "DL", "UA", "B6", "EV", "B6", "AA...
$ flight         <dbl> 1545, 1714, 1141, 725, 461, 1696, 507, 5708, 79, 301, 49,...
$ tailnum        <chr> "N14228", "N24211", "N619AA", "N804JB", "N668DN", "N39463...
$ origin         <chr> "EWR", "LGA", "JFK", "JFK", "LGA", "EWR", "EWR", "LGA", "...
$ dest           <chr> "IAH", "IAH", "MIA", "BQN", "ATL", "ORD", "FLL", "IAD", "...
$ air_time       <dbl> 227, 227, 160, 183, 116, 150, 158, 53, 140, 138, 149, 158...
$ distance       <dbl> 1400, 1416, 1089, 1576, 762, 719, 1065, 229, 944, 733, 10...
$ hour           <dbl> 5, 5, 5, 5, 6, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 6, 6, 6, ...
$ minute         <dbl> 15, 29, 40, 45, 0, 58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 59, 0, ...
$ time_hour      <chr> "2013-01-01 05:00:00", "2013-01-01 05:00:00", "2013-01-01...
head(flights)
summary(flights)
      year          month             day           dep_time    sched_dep_time
 Min.   :2013   Min.   : 1.000   Min.   : 1.00   Min.   :   1   Min.   : 106  
 1st Qu.:2013   1st Qu.: 4.000   1st Qu.: 8.00   1st Qu.: 907   1st Qu.: 906  
 Median :2013   Median : 7.000   Median :16.00   Median :1401   Median :1359  
 Mean   :2013   Mean   : 6.549   Mean   :15.71   Mean   :1349   Mean   :1344  
 3rd Qu.:2013   3rd Qu.:10.000   3rd Qu.:23.00   3rd Qu.:1744   3rd Qu.:1729  
 Max.   :2013   Max.   :12.000   Max.   :31.00   Max.   :2400   Max.   :2359  
                                                 NA's   :8255                 
   dep_delay          arr_time    sched_arr_time   arr_delay       
 Min.   : -43.00   Min.   :   1   Min.   :   1   Min.   : -86.000  
 1st Qu.:  -5.00   1st Qu.:1104   1st Qu.:1124   1st Qu.: -17.000  
 Median :  -2.00   Median :1535   Median :1556   Median :  -5.000  
 Mean   :  12.64   Mean   :1502   Mean   :1536   Mean   :   6.895  
 3rd Qu.:  11.00   3rd Qu.:1940   3rd Qu.:1945   3rd Qu.:  14.000  
 Max.   :1301.00   Max.   :2400   Max.   :2359   Max.   :1272.000  
 NA's   :8255      NA's   :8713                  NA's   :9430      
   carrier              flight       tailnum             origin         
 Length:336776      Min.   :   1   Length:336776      Length:336776     
 Class :character   1st Qu.: 553   Class :character   Class :character  
 Mode  :character   Median :1496   Mode  :character   Mode  :character  
                    Mean   :1972                                        
                    3rd Qu.:3465                                        
                    Max.   :8500                                        
                                                                        
     dest              air_time        distance         hour           minute     
 Length:336776      Min.   : 20.0   Min.   :  17   Min.   : 1.00   Min.   : 0.00  
 Class :character   1st Qu.: 82.0   1st Qu.: 502   1st Qu.: 9.00   1st Qu.: 8.00  
 Mode  :character   Median :129.0   Median : 872   Median :13.00   Median :29.00  
                    Mean   :150.7   Mean   :1040   Mean   :13.18   Mean   :26.23  
                    3rd Qu.:192.0   3rd Qu.:1389   3rd Qu.:17.00   3rd Qu.:44.00  
                    Max.   :695.0   Max.   :4983   Max.   :23.00   Max.   :59.00  
                    NA's   :9430                                                  
  time_hour        
 Length:336776     
 Class :character  
 Mode  :character  
                   
                   
                   
                   

dim() menampilkan banyaknya baris dan variable dari suatu data frame.

glimpse() serupa dengan str() dari base-R, bertujuan untuk melihat tipe dan struktur objek. Jika objek tersebut adalah data frame, maka akan menghasilkan banyaknya baris (observations) dan variable (variables). Fungsi ini juga menampilkan nama variabel, tipe variabel, dan beberapa baris pertama dari data.

head() berguna untuk melihat atau menampilkan beberapa baris pertama dari data frame. Secara default head() menggunakan n = 6 untuk menentukan banyaknya baris yang akan ditampilkan. Jika Anda ingin melihat 10 baris pertama, maka cukup mennggantinya menjadi head(flights, n = 10). Namun jika yang ingin Anda tampilkan ada beberapa baris terakhir dari data frame, gunakan tail().

summary() menghasilkan beberapa nilai statistik deskriptif untuk masing-masing variabel jika yang dimasukan sebagai argumen adalah data frame. Untuk variabel numerik maka akan menhasilkan nilai minimum (Min.), quantil pertama (1st Qu.), median (Median), rata-rata (Mean), quantil ketiga (3rd Qu.), maksimum (Max.) dan banyaknya missing value (NA's) jika ada. Untuk variabel kategorik (character atau factor) makan akan menghasilkan banyaknya data (Length), kelas (Class) dan mode (Mode).

7.1 Mengambil nilai unik (tidak duplikasi) dari sebuah variable

Hal ini sama seperti melakukan remove duplicate di Ms Excel untuk satu variabel.

distinct(flights, year)

distinct(flights, origin)

distinct(flights, dest)
NA

Untuk mendapatkan nilai unik dari semua baris berdasarkan variabel tertentu tambahkan opsi .keep_all = TRUE setelah nama variable dalam fungsi distinct().

distinct(flights, dest, .keep_all = TRUE)
distinct(flights)

7.2 Memilih atau membuang beberapa variable yang akan/tidak digunakan

Untuk membuat data frame dari hasil select() Anda dapat menuliskan namadataframe <- select(...). Argumen pertama dari select() adalah data frame yang ingin kita subset variabel tertentu saja. Selanjutnya tuliskan nama variable yang Anda inginkan ada di data frame yang baru. Anda dapat menuliskan nama masing-masing variable atau dapat juga dengan menuliskan urutan (indeks) variabel.

select(flights, month, day, dep_time, dep_delay, arr_time, arr_delay, origin, dest)
select(flights, c(2:4, 6:7, 9, 13:14))

Jika variabel yang ingin Anda pilih cukup banyak sedangkan variabel yang ingin Anda buang lebih sedikit, Anda dapat menuliskan nama variabel dengan menambahkan tanda negatif (-) di depan nama atau indeks masing-masing variabel.

select(flights, -year)
select(flights, -1)

7.3 Memilih baris data berdasarkan indeks baris atau nilai pada variabel tertentu

Ketika Anda ingin memilih sebagian baris saja dari data frame, Anda dapat menggunakan fungsi slice() atau filter().

slice() berfungsi untuk mensubset baris data berdasarkan indeks barisnya. Misalkan, slice(flights, 1:100) berarti Anda memilih data pada posisi baris ke-1 s/d ke-100. Posisi baris 1:100 adalah vector numerik integer yang ingin dipilih.

# memilih baris ke-1 s/d 1000
slice(flights, 1:1000)

# memilih baris ke-1,3,5,6, dan 10
slice(flights, c(1,3,5,6,10))

filter() berfungsi untuk memilih sebagian data berdasarkan nilai dari satu atau lebih variabel.

filter(flights, dep_delay >= 10)
filter(flights, dep_delay >= 10 & origin == "JFK")
filter(flights, month == 1 & day == 1 & origin == "JFK" & dest == "ATL")
filter(flights, is.na(dep_time))

7.4 Mengurutkan data

Mengurutkan data frame menggunakan fungsi arrange() sangat mudah.

# Urutkan data frame flights berdasarkan variabel `origin`
arrange(flights, origin)

# Urutkan data frame flights berdasarkan variabel `origin` dan `dest`
arrange(flights, origin, dest)

# Urutkan data frame flights berdasarkan variabel `origin` secara ascending dan `dest` secara descending
arrange(flights, origin, desc(dest))

Fungsi desc() dalam penggunaan arrange() digunakan jika Anda ingin mengurutkan data secara descending.

7.5 Penanganan missing values

Untuk kebutuhan ilustrasi pada bagian ini dengan fungsi na_if(), Anda akan mengganti nilai NA (missing value) menjadi sebuah nilai tertentu, misalnya jika air_time missing akan diganti menjadi nilai -999.

# Banyaknya NA
sum(is.na(flights["air_time"]))
[1] 9430
flights_na999 <- flights
# Merubah NA dengan -999
flights_na999$air_time <- if_else(is.na(flights$air_time), -999, flights$air_time)

# Banyaknya NA
sum(is.na(flights_na999["air_time"]))
[1] 0
# Banyaknya NA yang sudah diganti -999
sum(flights_na999["air_time"] == -999)
[1] 9430

Selanjutanya, misalkan dari raw data Anda, nilai -999 menunjukkan missingg value dan akan diganti jadi NA.

# Ganti nilai NA menjadi -999
flights_na999$air_time <- na_if(flights_na999$air_time, -999)

# Banyaknya -999
filter(flights_na999, air_time == -999)

# Banyaknya NA
sum(is.na(flights_na999$air_time))
[1] 9430
# Membuang semua baris yang mengandung NA
flights_dropNA <- drop_na(flights)

# Mengganti NA pada masing-masing variabel dengan nilai tertentu
replace_na(flights, list(dep_time = 0, dep_delay = 0, arr_time = mean(flights$arr_time, na.rm = TRUE), arr_delay = median(flights$arr_delay, na.rm = TRUE)))

Fungsi replace_na() menggunakan argumen sebuah list untuk menentukan nilai pengganti NA pada sebuah variabel. Dari contoh di atas, NA pada dep_time dan dep_delay diganti menjadi 0 (nol), NA di arr_time diganti dengan rata-rata arr_time, sedangkan NA di arr_delay diganti dengan nilai median dari arr_delay.

7.6 Operator Pipes %>%

Seringkali proses data preparation membutuhkan banyak proses atau tahapan. Sekarang jika kita ingin melakukan beberapa proses sekaligus, salah satunya kita akan membuat script kurang lebih seperti ini.

selected <- select(flights, dep_delay, arr_time, arr_delay, origin, dest, air_time)
filtered <- filter(selected, dep_delay >= 10 & origin == "JFK")
hasil <- arrange(filtered, origin)
hasil

Atau ada juga yang menuliskan seperti berikut ini.

hasil <- arrange(filter(select(flights, dep_delay, arr_time, arr_delay, origin, dest, air_time), dep_delay >= 10 & origin == "JFK"), origin)
hasil

Jika Anda diminta untuk mempelajari script seperti ini saya cukup yakin bahwa Anda akan merasa kesulitan untuk mengetahui proses yang akan dilakukan oleh script tersebut. Script di atas masih sederhana, hanya ada tiga fungsi. Bayangkan jika banyak fungsi yang digunakan secara berurutan, bentuk di atas akan menjadi:

output <- fun_n(...(fun3(fun2(fun1(dataframe, arg1), arg2), arg3), ...), arg_n)

Jika diperhatikan, argumen pertama dari masing-masing fungsi select(), filter(), arrange() dan beberapa fungsi yang lain di dplyr yang sering digunakan untuk transformasi dan eksplorasi data adalah data frame/tibble atau hasil dari proses sebelumnya. Misalnya, hasil dari fungsi select() adalah data frame yang kemudian diproses dengan fungsi filter() untuk memilih baris data tertentu dan hasilnya diteruskan lagi ke fungsi arrange() untuk diurutkan berdasarkan variabel tertentu.

Dengan konsep tidyverse, kita dapat menggunakan operator Pipes %>% agar lebih mudah dalam memahami script karena script tersebut menunjukkan urutan. Perhatikan contoh di bawah ini.

flights %>% 
  select(dep_delay, arr_time, arr_delay, origin, dest, time_hour) %>% 
  filter(origin == "JFK" & between(dep_delay, -10, 100)) %>% 
  arrange(origin, desc(dest)) 

Jika dibuat ke dalam kalimat:

“Ambil data penerbangan dari data frame flights kemudian pilih variabel-variable tertentu saja. Selanjutnya filter yang origin-nya dari”JFK" dan waktu keberangkatan antara yang lebih awal 10 menit dan yang delay hingga 100 menit. Kemudian urutkan hasil tersebut berdasarkan origin secara ascending dan berdasarkan dest secara descending."

7.7 Membuat variabel baru

flights %>% 
  select(dep_delay, arr_time, arr_delay, origin, dest, time_hour) %>% 
  filter(origin == "JFK" & between(dep_delay, -10, 100)) %>% 
  arrange(origin, desc(dest)) %>% 
  mutate(is_delay = if_else(dep_delay > 0, 1, 0),
         time_hour = as.POSIXct(time_hour))
NA

Jika dibuat ke dalam kalimat:

“Ambil data penerbangan dari data frame flights kemudian pilih variabel-variable tertentu saja. Selanjutnya filter yang origin-nya dari”JFK" dan waktu keberangkatan antara yang lebih awal 10 menit dan yang delay hingga 100 menit. Kemudian urutkan hasil tersebut berdasarkan origin secara ascending dan berdasarkan dest secara descending. Setelah itu membuat variabel baru bernama is_delay yang menunjukkan apakah suatu penerbangan terjadi delay atau tidak. Kemudian konversi variable time_hour yang masih bertipe character menjadi datetime atau <dttm> dengan nama variabel yang sama, yaitu time_hour."

7.8 Tabel frekuensi dan transformasi tabel

Membuat tabel frekuensi berdasarkan origin dan dest. Gunakan fungsi count() untuk mengetahui banyaknya baris.

flights %>%
  count()
Ada sebanyak
baris yang menunjukkan masing-masing penerbangan. Artinya pada tahun 2013 ada sebanyak

jadwal penerbangan.

Untuk membuat tabel frekuensi berdasarkan kategori suatu variabel, perhatikan contoh di bawah ini.

flights %>%
  filter(dest %in% c("ABQ", "ACK", "ALB", "ANC", "ATL")) %>%  
  count(origin, dest)

Script di atas menghitung banyaknya jadwal penerbangan (count()) berdasarkan masing-masing kategori pada origin dan dest.

Untuk membuat tabel frekuensi dua-arah (two-way frequency), dapat menggunakan fungsi spread() dari package tidyr. Dari contoh hasil di atas kita akan membuat kategori pada variabel origin sebagai nama varaibel.

flights %>%
  filter(dest %in% c("ABQ", "ACK", "ALB", "ANC", "ATL")) %>%  
  count(origin, dest) %>%
  spread(key = origin, value = n, fill = 0)

Argumen fill = 0 digunakan untuk mengisi cell yang kosong setelah ditansformasi dengan nilai nol (0).

7.9 Ringkasan (summary) dan Group

Seringkali menghitung nilai statistik/summary diperlukan dalam melakukan eksplorasi data.

  • Membuang NA dengan drop_na() dan menghitung rata-rata delay.
flights %>% 
  drop_na() %>% 
  summarise(rata2_delay = mean(dep_delay))
  • Menghitung rata-rata dari dep_delay berdasarkan origin, dest dan month. Jika ada nilai NA pada variabel dep_delay maka dikelaurkan dari perhitungan.
flights %>% 
  group_by(origin, dest, month) %>% 
  summarise(rata2_delay = mean(dep_delay, na.rm = TRUE)) %>% 
  arrange(rata2_delay) 
NA

group_by() dan summarise() digunakan untuk menghitung statistik berdasarkan grup yg disebutkan pada fungi group_by().

flights %>% 
  group_by(origin, dest) %>% 
  summarise(rata2_delay = mean(dep_delay, na.rm = TRUE))

Hasil dari summarise() adalah sebuah data frame/tibble. Maka dari itu, sebaiknya berikan nama pada hasil summarise(). rata2_delay adalah nama variabel hasil dari mean().

flights %>% 
  group_by(origin, dest) %>% 
  summarise(rata2_delay = mean(dep_delay, na.rm = TRUE)) %>% 
  spread(key = origin, value = rata2_delay) # soon spread() will be replaced with pivot_wider()

spread() membuat variabel baru dari isi sebuah variabel yang disebutkan pada argumen key = sebagai nama variabel, dan nilai dari variabel-variabel baru tersebut adalah nilai dari variabel yang disebutkan di argumen value =.

Note: pada saat tutorial ini dibuat, package tidyr masih menggunakan fungsi spread() dan gather() untuk transformasi data frame. Namun pembuat package tidyr sudah berencana untuk menggantinya dengan nama fungsi baru, yaitu pivot_wider() dan pivot_longer().

flights %>% 
  group_by(origin, dest) %>% 
  summarise(rata2_delay = mean(dep_delay, na.rm = TRUE)) %>% 
  spread(key = origin, value = rata2_delay) %>% # soon replaced with pivot_wider()
  gather(key = origin, value = rata2_delay, -dest) # soon replaced with pivot_longer()
tb <- flights %>%
  group_by(month) %>% 
  count()

plot(tb$month, tb$n, "l")



tb <- flights %>%
  group_by(day) %>% 
  count()

plot(tb$day, tb$n, "l")



tb <- flights %>%
  mutate(dates = as.Date(paste(year, month, day, sep = "-"), format = "%Y-%m-%d")) %>%
  group_by(dates) %>% 
  count()
plot(tb$dates, tb$n, "l")

Fungsi paste berfungsi untuk menggabungkan (concate) dua buah nilai atau vector menjadi character dengan pemisah default antar nilai tersebut adalah . Misalnya

paste("nilai ini", 9)
[1] "nilai ini 9"

Untuk mengganti pemisahnya Anda dapat menyebutkannya pada argumen sep =. Misalnya Anda ingin menggunakan pemisah tanda -, maka

paste("nilai ini", 9, sep = "-")
[1] "nilai ini-9"

Fungsi as.Date() melakukan konversi dari sebuah character menjadi nilai tanggal (date-value) di R. Format tanggal default di R adalah yyyy-mm-dd, namun di R menggunakan format %Y-%m-%d untuk 2019-05-10. Selebihnya Anda dapat melihatnya dengan ?as.Date.

7.10 Merge/Join Tabel

Dalam dunia nyata, terutama di dunia kerja dan perusahaan, jarang sekali data yang digunakan hanya berasal dari satu tabel atau file. Biasanya ada tabel-tabel lain yang harus digunakan untuk mendukung analisis data. Misalnya di bank, di database minimal ada tabel master_customer yang berisi data demografi (nama, tempat & tanggal lahir, alamat, dst), dan transaction yang berisi data transaksi nasabah.

Pada bagian tutorial ini, akan membahas penggunaan beberapa tabel yang sudah dijelaskan di bagian awal mengenai data. Ada 7 dataset yang dapat digunakan. Mungkin untuk melakukan analisis dan mendapatkan hasilnya Anda tidak membutuhkan semuanya.

Dari ketujuh tabel, relasinya digambarkan seperti gambar di bawah ini.

Diagram Relasi Tabel

Diagram Relasi Tabel

  • flights dihubungkan dengan planes via satu variabel, tailnum.

  • flights dihubungkan dengan airlines melaui variabel carrier.

  • flights dihubungkan dengan airports dengan dua cara: via variabel origin dengan variabel faa dan dest dengan variabel faa.

  • flights dihubungkan dengan weather via origin (lokasi), dan year, month, day dan hour.

  • flights dihubungkan dengan prices via year, month, day, hour, flight, origin, dest, sched_dep_time, dan tailnum.

  • flights dihubungkan dengan specialdays via variabel year, month, dan day.

Berikut fungsi untuk merge/join menggunakan package dplyr dan perbandingannya dengan SQL.

dplyr SQL
inner_join(x, y, by = c("a" = "b") SELECT * FROM x INNER JOIN y ON x.a = y.b
left_join(x, y, by = c("a" = "b")) SELECT * FROM x LEFT OUTER JOIN y ON x.a = y.b
right_join(x, y, by = c("a" = "b")) SELECT * FROM x RIGHT OUTER JOIN y ON x.a = y.b
full_join(x, y, by = c("a" = "b")) SELECT * FROM x FULL OUTER JOIN y ON x.a = y.b

Catatan: “INNER” dan “OUTER” pada SQL adalah opsional, dan lebih sering tidak dituliskan.

Berikut ilustrasi untuk menjelaskan merge/join.

Misalkan ada dua buah tabel, x dan y yang masing-masing mempunyai 2 variabel seperti pada gambar di bawah ini. Variabel pertama adalah key dan variabel kedua adalah val.

tabel ilustrasi

Tabel x dan y

x <- data.frame(key = c(1, 2, 3), val = c("x1", "x2", "x3"))
y <- data.frame(key = c(1, 2, 4), val = c("y1", "y2", "y3"))

7.10.1 Inner Join

Ketika melakukan proses inner_join maka akan diambil nilai yang sama dari key yang digunakan dari kedua tabel tersebut. Pada ilustrasi di bawah ini, digunakan inner_join untuk menggabungkan tabel x dan y. Dari kedua tabel tersebut, nilai key yang ada di tabel x dan y adalah 1 dan 2. Maka hasilnya adalah diambil baris data yang sama di kedua tabel tersebut, yaitu nilai key 1 dan 2.

x %>% 
  inner_join(y, by = "key")

Jika ada variabel lain selain key yang namanya sama, maka dibelakang masing-masing nama variabel tersebut akan ditambahkan suffix. suffix secara default adalah suffix = c(".x", ".y"). Artinya karena di masing-masing tabel ada variabel yang sama dan variabel ini bukan sebuah key pada saat join, yaitu val, maka setelah proses join nama val akan diganti menjadi val.x untuk variabel yang berasal dari tabel x dan val.y yang berasal dari tabel y.

Jika ingin mengganti suffix, Anda dapat menggunakan argumen suffix pada fungsi join.

x %>% 
  inner_join(y, by = "key", suffix = c("_x", "_y"))

Ilustrasi Inner Join

Ilustrasi Inner Join

tbl1 <- flights %>%
    inner_join(weather, by = c("year", "month", "day", "hour", "origin", "time_hour"))
glimpse(tbl1)
Observations: 335,220
Variables: 28
$ year           <dbl> 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2013, 201...
$ month          <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ...
$ day            <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ...
$ dep_time       <dbl> 517, 533, 542, 544, 554, 554, 555, 557, 557, 558, 558, 55...
$ sched_dep_time <dbl> 515, 529, 540, 545, 600, 558, 600, 600, 600, 600, 600, 60...
$ dep_delay      <dbl> 2, 4, 2, -1, -6, -4, -5, -3, -3, -2, -2, -2, -2, -2, -1, ...
$ arr_time       <dbl> 830, 850, 923, 1004, 812, 740, 913, 709, 838, 753, 849, 8...
$ sched_arr_time <dbl> 819, 830, 850, 1022, 837, 728, 854, 723, 846, 745, 851, 8...
$ arr_delay      <dbl> 11, 20, 33, -18, -25, 12, 19, -14, -8, 8, -2, -3, 7, -14,...
$ carrier        <chr> "UA", "UA", "AA", "B6", "DL", "UA", "B6", "EV", "B6", "AA...
$ flight         <dbl> 1545, 1714, 1141, 725, 461, 1696, 507, 5708, 79, 301, 49,...
$ tailnum        <chr> "N14228", "N24211", "N619AA", "N804JB", "N668DN", "N39463...
$ origin         <chr> "EWR", "LGA", "JFK", "JFK", "LGA", "EWR", "EWR", "LGA", "...
$ dest           <chr> "IAH", "IAH", "MIA", "BQN", "ATL", "ORD", "FLL", "IAD", "...
$ air_time       <dbl> 227, 227, 160, 183, 116, 150, 158, 53, 140, 138, 149, 158...
$ distance       <dbl> 1400, 1416, 1089, 1576, 762, 719, 1065, 229, 944, 733, 10...
$ hour           <dbl> 5, 5, 5, 5, 6, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 6, 6, 6, ...
$ minute         <dbl> 15, 29, 40, 45, 0, 58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 59, 0, ...
$ time_hour      <chr> "2013-01-01 05:00:00", "2013-01-01 05:00:00", "2013-01-01...
$ temp           <dbl> 39.02, 39.92, 39.02, 39.02, 39.92, 39.02, 37.94, 39.92, 3...
$ dewp           <dbl> 28.04, 24.98, 26.96, 26.96, 24.98, 28.04, 28.04, 24.98, 2...
$ humid          <dbl> 64.43, 54.81, 61.63, 61.63, 54.81, 64.43, 67.21, 54.81, 6...
$ wind_dir       <dbl> 260, 250, 260, 260, 260, 260, 240, 260, 260, 260, 260, 26...
$ wind_speed     <dbl> 12.65858, 14.96014, 14.96014, 14.96014, 16.11092, 12.6585...
$ wind_gust      <dbl> NA, 21.86482, NA, NA, 23.01560, NA, NA, 23.01560, NA, 23....
$ precip         <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...
$ pressure       <dbl> 1011.9, 1011.4, 1012.1, 1012.1, 1011.7, 1011.9, 1012.4, 1...
$ visib          <dbl> 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 1...
head(tbl1)

Karena semua variabel yang namanya sama di data frame flights dan weather digunakan sebagai key maka tidak ada variabel yang ditambahkan suffix.

7.10.2 Left, Right dan Full Outer Join

Perhatikan ilustrasi untuk left, right, dan full outer join.

Ilustrasi Inner Join

Ilustrasi Left, Right dan Full Outer Join

Pada kesempatan ini hanya akan dibahas mengenai left join. Pada dasarnya left_join() mengambil semua baris yang ada di tabel sebelah kiri (LHS), dalam ilustrasi di atas adalah tabel x, dan mencari nilai yang ada pasangannya di tabel sebelah kanan (RHS), yaitu tabel y.

Jika nilai dari variabel key di tabel x tidak ada pasangannya di tabel y maka nilai val untuk baris tersebut akan menjadi missing value atau NA.

tbl2 <- flights %>%
    left_join(airports, by = c("origin" = "faa")) %>%
    left_join(airports, by = c("dest" = "faa"), suffix = c("_origin", "_dest"))
glimpse(tbl2)
Observations: 336,776
Variables: 33
$ year           <dbl> 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2013, 201...
$ month          <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ...
$ day            <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ...
$ dep_time       <dbl> 517, 533, 542, 544, 554, 554, 555, 557, 557, 558, 558, 55...
$ sched_dep_time <dbl> 515, 529, 540, 545, 600, 558, 600, 600, 600, 600, 600, 60...
$ dep_delay      <dbl> 2, 4, 2, -1, -6, -4, -5, -3, -3, -2, -2, -2, -2, -2, -1, ...
$ arr_time       <dbl> 830, 850, 923, 1004, 812, 740, 913, 709, 838, 753, 849, 8...
$ sched_arr_time <dbl> 819, 830, 850, 1022, 837, 728, 854, 723, 846, 745, 851, 8...
$ arr_delay      <dbl> 11, 20, 33, -18, -25, 12, 19, -14, -8, 8, -2, -3, 7, -14,...
$ carrier        <chr> "UA", "UA", "AA", "B6", "DL", "UA", "B6", "EV", "B6", "AA...
$ flight         <dbl> 1545, 1714, 1141, 725, 461, 1696, 507, 5708, 79, 301, 49,...
$ tailnum        <chr> "N14228", "N24211", "N619AA", "N804JB", "N668DN", "N39463...
$ origin         <chr> "EWR", "LGA", "JFK", "JFK", "LGA", "EWR", "EWR", "LGA", "...
$ dest           <chr> "IAH", "IAH", "MIA", "BQN", "ATL", "ORD", "FLL", "IAD", "...
$ air_time       <dbl> 227, 227, 160, 183, 116, 150, 158, 53, 140, 138, 149, 158...
$ distance       <dbl> 1400, 1416, 1089, 1576, 762, 719, 1065, 229, 944, 733, 10...
$ hour           <dbl> 5, 5, 5, 5, 6, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 6, 6, 6, ...
$ minute         <dbl> 15, 29, 40, 45, 0, 58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 59, 0, ...
$ time_hour      <chr> "2013-01-01 05:00:00", "2013-01-01 05:00:00", "2013-01-01...
$ name_origin    <chr> "Newark Liberty Intl", "La Guardia", "John F Kennedy Intl...
$ lat_origin     <dbl> 40.69250, 40.77725, 40.63975, 40.63975, 40.77725, 40.6925...
$ lon_origin     <dbl> -74.16867, -73.87261, -73.77893, -73.77893, -73.87261, -7...
$ alt_origin     <dbl> 18, 22, 13, 13, 22, 18, 18, 22, 13, 22, 13, 13, 13, 18, 2...
$ tz_origin      <dbl> -5, -5, -5, -5, -5, -5, -5, -5, -5, -5, -5, -5, -5, -5, -...
$ dst_origin     <chr> "A", "A", "A", "A", "A", "A", "A", "A", "A", "A", "A", "A...
$ tzone_origin   <chr> "America/New_York", "America/New_York", "America/New_York...
$ name_dest      <chr> "George Bush Intercontinental", "George Bush Intercontine...
$ lat_dest       <dbl> 29.98443, 29.98443, 25.79325, NA, 33.63672, 41.97860, 26....
$ lon_dest       <dbl> -95.34144, -95.34144, -80.29056, NA, -84.42807, -87.90484...
$ alt_dest       <dbl> 97, 97, 8, NA, 1026, 668, 9, 313, 96, 668, 19, 26, 126, 1...
$ tz_dest        <dbl> -6, -6, -5, NA, -5, -6, -5, -5, -5, -6, -5, -5, -8, -8, -...
$ dst_dest       <chr> "A", "A", "A", NA, "A", "A", "A", "A", "A", "A", "A", "A"...
$ tzone_dest     <chr> "America/Chicago", "America/Chicago", "America/New_York",...
head(tbl2)

Jika diperhatikan, ada beberapa variabel dari tbl2 yang namanya menggunakan _origin dan _dest. Hal ini menunjukkan ada variabel yang namanya sama dari hasil left_join() pertama dan kedua, yaitu nama variabel dari data frame airports. Karena dua kali join maka akan ada nama variabel yang sama sehingga namanya ditambahkan suffix.

8 Latihan

Buatlah analisis data untuk kebutuhan berikut!

  1. Penerbangan dengan keterlambatan kedatangannya dua jam atau lebih.
  2. Perbandingan penerbangan ke Houston (IAH atau HOU) berdasarkan bandara asalnya per bulan. Hint: Anda dapat gunakan operator %in%.
  3. Penerbangan dioperasikan oleh United, American, atau Delta. Hint: Anda dapat gunakan operator %in%.
  4. Penerbangan di musim panas (July, August, and September). Hint: Anda dapat gunakan fungsi between().
  5. Kedatangan yang terlambat datang lebih dari dua jam, tetapi tidak berangkat terlambat.
  6. Penerbangan yang delay paling tidak satu jam, tetapi terbang lebih dari 30 menit.
  7. Penerbangan yang berangkat antara tengah malam dan jam 6 pagi (inclusive).
  8. Berapa banyak penerbangan yang nilai tailnum-nya missing? Apa maksud dari baris-baris data ini?
  9. Berapa banyak penerbangan yang nilai dep_time-nya missing? Variable apalagi selain itu yang missing? Apa maksud dari baris-baris data ini?
  10. Bagaimana hubungan penerbangan delay dikarenakan cuaca dan kondisi pesawat?

———————– Semoga Bermanfaat ———————–

Contact me: Aep Hidayatuloh

Email: GitHub: https://github.com/aephidayatuloh

LS0tDQp0aXRsZTogIkVrc3Bsb3Jhc2kgRGF0YSBNZW5nZ3VuYWthbiBSIChUaWR5dmVyc2UgQXBwcm9hY2gpIg0KYXV0aG9yOiAiQWVwIEhpZGF5YXR1bG9oIg0KZGF0ZTogIkxhc3QgVXBkYXRlOiBgciBmb3JtYXQoU3lzLkRhdGUoKSwgJyVZLSVtLSVkJylgIg0Kb3V0cHV0OiANCiAgaHRtbF9ub3RlYm9vazoNCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcw0KICAgIHRoZW1lOiBzcGFjZWxhYg0KICAgIGRmX3ByaW50OiBwYWdlZA0KICAgIHRvYzogeWVzDQogICAgdG9jX2RlcHRoOiA0DQogICAgdG9jX2Zsb2F0OiB0cnVlDQogIGh0bWxfZG9jdW1lbnQ6DQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICB0aGVtZTogc3BhY2VsYWINCiAgICBkZl9wcmludDogcGFnZWQNCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogJzQnDQogICAgdG9jX2Zsb2F0OiB0cnVlDQotLS0NCg0KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4NCg0KYm9keXsgLyogTm9ybWFsICAqLw0KICAgICAgZm9udC1zaXplOiAxMnB4Ow0KICB9DQp0ZCB7ICAvKiBUYWJsZSAgKi8NCiAgZm9udC1zaXplOiAxMnB4Ow0KfQ0KaDEudGl0bGUgew0KICBmb250LXNpemU6IDM4cHg7DQogIGNvbG9yOiBsaWdodGJsdWU7DQogIGZvbnQtd2VpZ2h0OiBib2xkOw0KfQ0KaDEgeyAvKiBIZWFkZXIgMSAqLw0KICBmb250LXNpemU6IDI0cHg7DQogIGNvbG9yOiBEYXJrQmx1ZTsNCn0NCmgyIHsgLyogSGVhZGVyIDIgKi8NCiAgZm9udC1zaXplOiAyMHB4Ow0KICBjb2xvcjogRGFya0JsdWU7DQp9DQpoMyB7IC8qIEhlYWRlciAzICovDQogIGZvbnQtc2l6ZTogMTZweDsNCiMgIGZvbnQtZmFtaWx5OiAiVGltZXMgTmV3IFJvbWFuIiwgVGltZXMsIHNlcmlmOw0KICBjb2xvcjogRGFya0JsdWU7DQp9DQpoNCB7IC8qIEhlYWRlciA0ICovDQogIGZvbnQtc2l6ZTogMTRweDsNCiAgY29sb3I6IERhcmtCbHVlOw0KfQ0KY29kZS5yeyAvKiBDb2RlIGJsb2NrICovDQogICAgZm9udC1zaXplOiAxMnB4Ow0KfQ0KcHJlIHsgLyogQ29kZSBibG9jayAtIGRldGVybWluZXMgY29kZSBzcGFjaW5nIGJldHdlZW4gbGluZXMgKi8NCiAgICBmb250LXNpemU6IDEycHg7DQp9DQo8L3N0eWxlPg0KDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0KI2tuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvPVRSVUUsIGZpZy5oZWlnaHQ9My41LCBmaWcud2lkdGg9OS4yLCByZXN1bHRzPSdob2xkJywgd2FybmluZz1GQUxTRSwgZmlnLnNob3c9J2hvbGQnLCBtZXNzYWdlPUZBTFNFKSANCm9wdGlvbnMoc2NpcGVuID0gOTkpDQpgYGANCg0KDQojIE9iamVrdGlmDQoNCkluaSBhZGFsYWggY2F0YXRhbiB1bnR1ayBtZW1hbmR1IHNlY2FyYSBzaW5na2F0IGRhbGFtIG1lbGFrdWthbiBla3NwbG9yYXNpIGRhdGEgbWVuZ2d1bmFrYW4gUiBkYW4gYmViZXJhcGEgcGFja2FnZSB5YW5nIG1lbmphZGkgYmFnaWFuIGRhcmkgcGFja2FnZSBgdGlkeXZlcnNlYC4gVHVqdWFuIGRhcmkgdHV0b3JpYWwgaW5pIGFkYWxhaCBhZ2FyIHBlc2VydGEgZGFwYXQgbWVuY29iYSBtZW5nYWtzZXMgZGF0YSB5YW5nIGJlcmFzYWwgZGFyaSBkYXRhYmFzZSAocGFkYSBrZXNlbXBhdGFuIGluaSBtZW5nZ3VuYWthbiBNeVNRTCkgbWVsYWx1aSBSIGRhbiBtZW5nZ3VuYWthbiBkYXRhIGRhcmkgZmlsZSBDU1YgeWFuZyBkYXBhdCBkaXVuZHVoLiBEYXRhYmFzZSB5YW5nIGFrYW4gZGlha3NlcyB0ZWxhaCBkaXNlZGlha2FuIG9sZWggcGVtYmljYXJhIGRpIGxvY2FsaG9zdC1ueWEuIE5hbXVuLCBBbmRhIGhhbnlhIGRhcGF0IG1lbmdha3Nlc255YSBrZXRpa2Ega2VnaWF0YW4gc2VkYW5nIGJlcmxhbmdzdW5nLiANCg0KIyBDYWt1cGFuIE1hdGVyaQ0KTWF0ZXJpIHlhbmcgYWthbiBkaWJhaGFzOg0KDQorIE9wZXJhdG9yIFBpcGVzIHVudHVrIGJlYmVyYXBhIGZ1bmdzaSBzZWNhcmEgYmVydXJ1dGFuLypzZXF1ZW5jZSogDQorIE1lbmRhcGF0a2FuIG5pbGFpIHVuaWsvdGlkYWsgZHVwbGlrYXQgZGFyaSBkYXRhIGZyYW1lIGF0YXUgdmFyaWFibGUgDQorIFN1YnNldCB2YXJpYWJsZSBiZXJkYXNhcmthbiBuYW1hL2luZGVrcyB2YXJpYWJsZSANCisgU3Vic2V0IGJhcmlzIGJlcmRhc2Fya2FuIGluZGVrcyBiYXJpcyBhdGF1IG5pbGFpIGRhcmkgc2F0dSBhdGF1IGJlYmVyYXBhIHZhcmlhYmVsIA0KKyBNZW5ndXJ1dGthbiBzZWJ1YWggZGF0YSBmcmFtZSBiZXJkYXNhcmthbiBzYXR1IGF0YXUgYmViZXJhcGEgdmFyaWFibGUgDQorIFBlbmFuZ2FuYW4gTkEgKCptaXNzaW5nIHZhbHVlKikgDQorIE1lbWJ1YXQgdmFyaWFiZWwgYmFydS9tZW5naGl0dW5nIGRhcmkgdmFyaWFiZWwgeWFuZyBzdWRhaCBhZGEgDQorIFRhYmVsIGZyZWt1ZW5zaSBkYW4gdHJhbnNmb3JtYXNpIHRhYmVsIA0KKyBSaW5na2FzYW4gKCpzdW1tYXJ5KikgdmFyaWFiZWwgYXRhdSBiZXJkYXNhcmthbiBncm91cCANCisgTWVyZ2UvSm9pbiB0YWJlbCANCg0KIyBQcmFzeWFyYXQNCg0KVW50dWsgZGFwYXQgbWVuZ2lrdXRpIHR1dG9yaWFsIGluaSBkZW5nYW4gYmFpaywgYWRhIGJlYmVyYXBhIGhhbCB5YW5nIHBlcmx1IGRpcGVyc2lhcGthbiBvbGVoIHBlc2VydGEuIFlhaXR1Og0KDQoxLiBLb25la3NpIGludGVybmV0IHlhbmcgYmFpayBkYW4gdGVyaHVidW5nIGRhbGFtIGphcmluZ2FuIHlhbmcgc2FtYSBkZW5nYW4gUEMgcGVtYmljYXJhIHVudHVrIGRhcGF0IG1lbmdha3NlcyBkYXRhYmFzZS4NCg0KMi4gTWVuZ2luc3RhbGwgc29mdHdhcmUNCg0KICAgIGEuIFIgcHJvZ3JhbSA8aHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvPg0KDQogICAgYi4gUlN0dWRpbyA8aHR0cHM6Ly93d3cucnN0dWRpby5jb20vcHJvZHVjdHMvcnN0dWRpby9kb3dubG9hZC8+DQoNCjMuIERhdGEgJiBTY3JpcHQgeWFuZyBkYXBhdCBkaXBlcm9sZWggZGFyaSBbcmVwb3NpdG9yeSBpbmldKGh0dHBzOi8vZ2l0aHViLmNvbS9hZXBoaWRheWF0dWxvaC9la3NwbG9yYXNpLWRhdGEpIGRhbiBwYWRhIGRhdGFiYXNlIHlhbmcgZGlzZWRpYWthbiBwZW1iaWNhcmEuDQoNCjQuIFBhY2thZ2UgUiB5YW5nIGRpYnV0dWhrYW46IGByZWFkcmAsIGB0aWR5cmAsIGBkcGx5cmAsIGBnZ3Bsb3QyYCAoYXRhdSBgdGlkeXZlcnNlYCksIGRhbiBgUk15U1FMYC4NCg0KQ2F0YXRhbjogRGF0YSBkaXBlcm9sZWggZGFyaSBwYWNrYWdlcyBgbnljZmxpZ2h0czEzYCB5YW5nIGRpc2ltcGFuIGtlIGRhbGFtIGRhdGFiYXNlLiBKaWthIEFuZGEgaW5naW4gbWVuY29iYSBkaWx1YXIga2VnaWF0YW4gYXRhdSB0aWRhayBkYXBhdCB0ZXJodWJ1bmcgZGVuZ2FuIGRhdGFiYXNlIHBlbWJpY2FyYSwgQW5kYSBkYXBhdCBtZW5naW5zdGFsbCBwYWNrYWdlIGBueWNmbGlnaHRzMTNgIHVudHVrIG1lbXBlcm9sZWggZGF0YSB5YW5nIGRpZ3VuYWthbiBwYWRhIGRhdGFiYXNlLiBEdWEgZGF0YSBsYWluIGJlcnVwYSBmaWxlIENTViB1bnR1ayBkaXNlc3VhaWthbiBkZW5nYW4ga2VidXR1aGFuIHR1dG9yaWFsLiANCkRhdGEgaW5pIHRlcmRpcmkgZGFyaSAzMzYsNzc2IHBlbmVyYmFuZ2FuIGRhcmkgTmV3IFlvcmsgQ2l0eSAoTllDKSBzZWxhbWEgdGFodW4gMjAxMy4gRGF0YSBhc2xpIGJlcmFzYWwgZGFyaSBVUyBCdXJlYXUgb2YgVHJhbnNwb3J0YXRpb24gU3RhdGlzdGljcywgZGFuIGRhcGF0IGRpbGloYXQgZG9rdW1lbnRhc2lueWEgZGVuZ2FuIGA/bnljZmxpZ2h0czEzOjpmbGlnaHRzYC4NCg0KYGBge3IgZXZhbD1GQUxTRX0NCmluc3RhbGwucGFja2FnZXMoIm55Y2ZsaWdodHMxMyIpDQpsaWJyYXJ5KG55Y2ZsaWdodHMxMykNCmBgYA0KUGFzdGlrYW4gQW5kYSBzdWRhaCBiZXJoYXNpbCBpbnN0YWxsIHBhY2thZ2UgdGVyc2VidXQuDQoNCiMgSW5zdGFsbCBQYWNrYWdlcw0KDQpKYWxhbmthbiBwZXJpbnRhaCBkaSBiYXdhaCBpbmkgdW50dWsgaW5zdGFsbCBwYWNrYWdlICgqKmppa2EgQW5kYSBiZWx1bSBwZXJuYWggaW5zdGFsbCoqKSB5YW5nIGFrYW4gZGlndW5ha2FuIHVudHVrIGRhcGF0IG1lbmdpa3V0aSB0dXRvcmlhbCBpbmkgc2FtcGFpIHNlbGVzYWkuDQoNCmBgYHtyIGV2YWw9RkFMU0V9DQppbnN0YWxsLnBhY2thZ2VzKGMoInJlYWRyIiwgInRpZHlyIiwgImRwbHlyIiwgImdncGxvdDIiLCAiUk15U1FMIikpDQojIGF0YXUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQppbnN0YWxsLnBhY2thZ2VzKGMoInRpZHl2ZXJzZSIsICJSTXlTUUwiKSkgICAgICAgICAgICAgICAgICAgICAgICAgDQpgYGANCg0KUGFuZ2dpbCBwYWNrYWdlIHlhbmcgc3VkYWggQW5kYSBpbnN0YWxsIGRlbmdhbiBmdW5nc2kgYGxpYnJhcnkoKWAuDQoNCmBgYHtyIGxvYWRwa2d9DQojIFBhbmdnaWwgcGFja2FnZSB5YW5nIHN1ZGFoIHRlcmlzbnRhbGwNCmxpYnJhcnkoUk15U1FMKQ0KbGlicmFyeShyZWFkcikNCmxpYnJhcnkodGlkeXIpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KDQojIGF0YXUgY3VrdXAgbWVtYW5nZ2lsDQojIGxpYnJhcnkodGlkeXZlcnNlKQ0KIyB1bnR1ayBtZW1hbmdnaWwgcGFja2FnZSBkaSBhdGFzIHNlbGFpbiBSTXlTUUwNCmBgYA0KDQpQYWNrYWdlIGByZWFkZXJgLCBgdGlkeXJgLCBgZHBseXJgIGRhbiBgZ2dwbG90MmAgKGRhbiBiZWJlcmFwYSBwYWNrYWdlIGxhaW4geWFuZyB0aWRhayBkaWd1bmFrYW4gZGkgdHV0b3JpYWwgaW5pKSB0ZXJtYXN1ayBkYWxhbSBiYWdpYW4gcGFja2FnZSBgdGlkeXZlcnNlYC4gYHRpZHl2ZXJzZWAgYWRhbGFoIGt1bXB1bGFuIHBhY2thZ2UgeWFuZyBkaWJ1YXQgb2xlaCAqKkhhZGxleSBXaWNraGFtKiogZGtrIHVudHVrIGtlYnV0dWhhbiBkYXRhIHNjaWVuY2UgbWVuZ2d1bmFrYW4gUi4NCg0KKyBgUk15U1FMYCBkaWd1bmFrYW4gdW50dWsgbWVtYnVhdCBrb25la3NpIGFudGFyYSBSIGRhbiBkYXRhYmFzZSBNeVNRTC4gQmViZXJhcGEgZnVuZ3NpIHlhbmcgYWthbiBkaWd1bmFrYW4gcGFkYSB0dXRvcmlhbCBpbmkgYW50YXJhIGxhaW4gYGRiQ29ubmVjdCgpYCB5YW5nIGJlcmFzYWwgZGFyaSBwYWNrYWdlIGBEQklgIHVudHVrIG1lbWJ1YXQga29uZWtzaSwgYGRiUmVhZFRhYmxlKClgIHVudHVrIGltcG9ydCBkYXRhIGRhcmkgZGF0YWJhc2Uga2UgUiBkYW4gZnVuZ3NpIGBkYkRpc2Nvbm5lY3QoKWAgdW50dWsgbWVtdXR1c2thbiBrb25la3NpIHlhbmcgc3VkYWggdGlkYWsgZGlndW5ha2FuLg0KKyBgcmVhZHJgIGJlcmd1bmEgdW50dWsgaW1wb3J0IGRhdGEgZGFyaSB0YWJ1bGFyIGRhdGEgZmlsZSAoY3N2LCB0ZXh0IGZpbGUsIGRsbCkuDQorIGB0aWR5cmAgbWVtaWxpa2kgZnVuZ2ktZnVuZ3NpIHVudHVrICJtZXJhcGloa2FuIiBkYXRhLiBUZXJ1dGFtYSB5YW5nIHNlcmluZyBkaWd1bmFrYW4gYWRhbGFoIGZ1bmdzaSBgZ2F0aGVyKClgIGRhbiBgc3ByZWFkKClgLg0KKyBgZHBseXJgIGFkYWxhaCBwYWNrYWdlIHlhbmcgc2FuZ2F0IGJlcmd1bmEgdW50dWsgbWVsYWt1a2FuIG1hbmlwdWxhc2kvdHJhbnNmb3JtYXNpIGRhdGEgbWVuZ2d1bmFrYW4gUi4NCisgYGdncGxvdDJgIGFkYWxhaCBzYWxhaCBzYXR1IHBhY2thZ2UgeWFuZyBzYW5nYXQgYmFueWFrIGRpZ3VuYWthbiBvbGVoIHBlbmdndW5hIFIgdW50dWsga2VidXR1aGFuIHZpc3VhbGlzYXNpLg0KDQo8cCBhbGlnbj0iY2VudGVyIj4NCiAgICA8aW1nIHNyYz0idGlkeXZlcnNlLnBuZyIgYWx0PSJUaWR5dmVyc2UiPg0KICAgIDxici8+DQogICAgPGJyLz4NCiAgICA8ZW0+VGlkeXZlcnNlPC9lbT4NCjwvcD4NCg0KYHRpZHl2ZXJzZWAgbWVuZ2d1bmFrYW4gKip0aWJibGUqKiBzZWJhZ2FpIHBlbmdnYW50aSBgZGF0YS5mcmFtZWAuIA0KPiBUaWJibGVzIGFyZSBkYXRhIGZyYW1lcywgYnV0IHRoZXkgdHdlYWsgc29tZSBvbGRlciBiZWhhdmlvdXJzIHRvIG1ha2UgbGlmZSBhIGxpdHRsZSBlYXNpZXIuIFIgaXMgYW4gb2xkIGxhbmd1YWdlLCBhbmQgc29tZSB0aGluZ3MgdGhhdCB3ZXJlIHVzZWZ1bCAxMCBvciAyMCB5ZWFycyBhZ28gbm93IGdldCBpbiB5b3VyIHdheS4gSXTigJlzIGRpZmZpY3VsdCB0byBjaGFuZ2UgYmFzZSBSIHdpdGhvdXQgYnJlYWtpbmcgZXhpc3RpbmcgY29kZSwgc28gbW9zdCBpbm5vdmF0aW9uIG9jY3VycyBpbiBwYWNrYWdlcyAtLSBbR3JvbGVtdW5kICYgV2lja2hhbV0oaHR0cHM6Ly9yNGRzLmhhZC5jby5ueikuIA0KDQpCZWJlcmFwYSBrZWxlYmloYW4gdGliYmxlIGRpYmFuZGluZ2thbiBgZGF0YS5mcmFtZWAgZGlhbnRhcmFueWEgYWRhbGFoIGtldGlrYSBtZW5hbXBpbGthbiBkYXRhLCB0aWJibGUgdGlkYWsgbWVuYW1waWxrYW4gc2VtdWEgYmFyaXMgZGFuIGtvbG9tLiBKaWthIGFkYSBsZWJpaCBkYXJpIDEwIGJhcmlzIGRhdGEsIG1ha2EgaGFueWEgYWthbiBhZGEgMTAgYmFyaXMgcGVydGFtYSB5YW5nIGRpdGFtcGlsa2FuIGRhbiBiZWJlcmFwYSB2YXJpYWJlbCBzZXN1YWkgZGVuZ2FuIGxlYmFyIGNvbnNvbGUgUiBBbmRhLiBVbnR1ayBsZWJpaCBtZW1haGFtaSB0ZW50YW5nIHRpYmJsZSwgc2lsYWhrYW4gbWVtYmFjYSBbYXJ0aWtlbCBpbmldKGh0dHBzOi8vcjRkcy5oYWQuY28ubnovdGliYmxlcy5odG1sKS4NCg0KIyBPcGVyYXRvciBQaXBlcyAoYCU+JWApDQoNClNlYmVsdW0ga2l0YSBtdWxhaSwga2l0YSBha2FuIG1lbWJhaGFzIHRlcmxlYmloIGRhaHVsdSBzZWJ1YWggb3BlcmF0b3IgeWFuZyBzYW5nYXQgYmVyZ3VuYSBkYW4gYmFueWFrIGRpZ3VuYWthbiBvbGVoIHBlbmdndW5hIFIgeWFuZyBtZW5nZ3VuYWthbiBgdGlkeXZlcnNlYC4gT3BlcmF0b3IgaW5pIGFkYWxhaCAqKlBpcGVzKiogKGAlPiVgKS4gUGVyaGF0aWthbiBjb250b2ggZGkgYmF3YWggaW5pLg0KDQpgYGB7ciBwaXBlczF9DQptZWFuKGlyaXMkU2VwYWwuTGVuZ3RoKQ0KYGBgDQphdGF1DQpgYGB7ciBwaXBlczJ9DQppcmlzJFNlcGFsLkxlbmd0aCAlPiUgbWVhbigpDQpgYGANCktlZHVhIHNjcmlwdCB0ZXJzZWJ1dCBtZWxha3VrYW4gaGFsIHlhbmcgc2FtYSBkYW4gbWVuZ2hhc2lsa2FuIG5pbGFpIHlhbmcgc2FtYS4NCg0KTWlzYWxrYW4gYWRhIHJhbmdrYWlhbiBkYXJpIGJlYmVyYXBhIGZ1bmdzaSBzZXBlcnRpIGluaSwgYGZ1bjEoKWAsIGBmdW4yKClgIGRhbiBgZnVuMygpYCBhZGFsYWggZnVuZ3NpIGRpIFIuDQoNCmBgYHtyIGV2YWw9RkFMU0V9DQpvdXRwdXQgPC0gZnVuMyhmdW4yKGZ1bjEoZGF0YWZyYW1lLCBhcmcxKSwgYXJnMiksIGFyZzMpDQpgYGANCg0KSmlrYSBBbmRhIGRpbWludGEgdW50dWsgbWVtcGVsYWphcmkgc2NyaXB0IHNlcGVydGkgaW5pIHNheWEgY3VrdXAgeWFraW4gYmFod2EgQW5kYSBha2FuIG1lcmFzYSBrZXN1bGl0YW4gdW50dWsgbWVuZ2V0YWh1aSBwcm9zZXMgeWFuZyBha2FuIGRpbGFrdWthbiBvbGVoIHNjcmlwdCB0ZXJzZWJ1dC4gQW5kYSBoYXJ1cyBtZXBlcmhhdGlrYW4gYXJndW1lbiB5YW5nIGRpZ3VuYWthbiB1bnR1ayBmdW5nc2kgdGVydGVudHUuIFNjcmlwdCBkaSBhdGFzIG1hc2loIHNlZGVyaGFuYSwgaGFueWEgYWRhIHRpZ2EgZnVuZ3NpLiBCYXlhbmdrYW4gamlrYSBiYW55YWsgZnVuZ3NpIHlhbmcgZGlndW5ha2FuIHNlY2FyYSBiZXJ1cnV0YW4sIGJlbnR1ayBkaSBhdGFzIGFrYW4gbWVuamFkaToNCg0KYGBge3IgZXZhbD1GQUxTRX0NCm91dHB1dCA8LSBmdW5fbihmdW5fKG4tMSkoLi4uKGZ1bjMoZnVuMihmdW4xKGRhdGFmcmFtZSwgYXJnMSksIGFyZzIpLCBhcmczKSwgLi4uKSwgYXJnXyhuLTEpKSwgYXJnX24pDQpgYGANCg0KRGVuZ2FuIGtvbnNlcCBgdGlkeXZlcnNlYCwga2l0YSBkYXBhdCBtZW5nZ3VuYWthbiBvcGVyYXRvciBgJT4lYCBhZ2FyIGxlYmloIG11ZGFoIGRhbGFtIG1lbWFoYW1pIHNjcmlwdCBrYXJlbmEgc2NyaXB0IHRlcnNlYnV0IG1lbnVuanVra2FuIHVydXRhbi4NCg0KIyMgUGVuamVsYXNhbiBPcGVyYXRvciBQaXBlcyBgJT4lYA0KDQpTYXlhIGFrYW4gY29iYSBtZW5qZWxhc2thbiBsZWJpaCBkYWxhbSB1bnR1ayBsZWJpaCBtZW1haGFtaSBvcHJhdG9yIGAlPiVgLg0KDQpNaXNhbGthbiBgZihhLCB4KWAgYWRhbGFoIHNlYnVhaCBmdW5nc2kgZGkgUiBkZW5nYW4gYXJndW1lbiBgYWAgZGFuIGB4YC4ga2VtdWRpYW4gZnVuZ3NpIGBnKGIsIHopYCBhZGFsYWggZnVuZ3NpIGxhaW4gZGkgUiBkZW5nYW4gYXJndW1lbiBgYmAgZGFuIGB6YC4gRGVuZ2FuIG1lbmdndW5ha2FuIG9wZXJhdG9yIGAlPiVgIGtpdGEgZGFwYXQgbWVudWxpc2thbm55YSBzZWJhZ2FpIGJlcmlrdXQuDQoNCmBgYHtyIGV2YWw9RkFMU0V9DQojIGZ1bmdzaSBmKGEsIHgpDQpmKGEsIHgpICMgYXRhdQ0KYSAlPiUgZih4KQ0KDQojIGZ1bmdzaSBnKGIsIHopDQpnKGIsIHopICMgYXRhdQ0KYiAlPiUgZyh6KQ0KYGBgDQoNCkRhcmkga2VkdWEgY29udG9oIGRpIGF0YXMsIGRhcGF0IGRpbGloYXQgYmFod2EgYGFgIGFkYWxhaCBhcmd1bWVuIHBlcnRhbWEgdW50dWsgZnVuZ3NpIGBmKClgIGRhbiBgYmAgYWRhbGFoIGFyZ3VtZW4gcGVydGFtYSB1bnR1ayBmdW5nc2kgYGcoKWAuIE9wZXJhdG9yIGAlPiVgICJtZW55YW1wYWlrYW4iIG9iamVrIGBhYCBzZWJhZ2FpIG5pbGFpIHVudHVrIG1lbmdpc2kgYXJndW1lbiBwZXJ0YW1hIHBhZGEgZnVuZ3NpIGBmKClgLiBQZXJoYXRpa2FuIGlsdXN0cmFzaSBkaSBiYXdhaCBpbmkuDQoNCjxwIGFsaWduPSJjZW50ZXIiPg0KICAgIDxpbWcgc3JjPSJwaXBlcy5wbmciIGhlaWdodD0iMTQ4IiB3aWR0aD0iMTQ4IiBhbHQ9IklsdXN0cmFzaSBQaXBlcyAxIj4NCiAgICA8YnIvPg0KICAgIDxici8+DQogICAgPGVtPklsdXN0cmFzaSBQaXBlcyAxPC9lbT4NCjwvcD4NCg0KTWlzYWxrYW4gb2JqZWsgYGFgIG1lbmphZGkgYXJndW1lbiBwZXJ0YW1hIGZ1bmdzaSBgZigpYCBkZW5nYW4gYGEgJT4lIGYoeClgLiBLZW11ZGlhIGhhc2lsIGRhcmkgYGEgJT4lIGYoeClgIGRpamFkaWthbiBhcmd1bWVuIHBlcnRhbWEgZGFyaSBmdW5nc2kgYGcoKWAuIERlbmdhbiBrYXRhIGxhaW4gYGIgPC0gYSAlPiUgZih4KWAgc2VoaW5nZ2EgYGIgJT4lIGcoeilgLiBIYWwgaW5pIGRhcGF0IGRpbGFrdWthbiBzZWNhcmEgYmVydXJ1dGFuIGRlbmdhbiBvcGVyYXRvciBgJT4lYCBzZWJhZ2FpIGJlcmlrdXQuDQoNCmBgYHtyIGV2YWw9RkFMU0V9DQphICU+JQ0KICAgIGYoeCkgJT4lDQogICAgZyh6KQ0KYGBgDQoNCjxwIGFsaWduPSJjZW50ZXIiPg0KICAgIDxpbWcgc3JjPSJwaXBlczIucG5nIiBoZWlnaHQ9IjE0OCIgd2lkdGg9IjI0OCIgYWx0PSJJbHVzdHJhc2kgUGlwZXMgMiI+DQogICAgPGJyLz4NCiAgICA8YnIvPg0KICAgIDxlbT5JbHVzdHJhc2kgUGlwZXMgMjwvZW0+DQo8L3A+DQoNCj4gIkZ1bmdzaSBgZigpYCBkaXRlcmFwa2FuIHRlcmhhZGFwIG9iamVrIGBhYCBzZWJhZ2FpIGFyZ3VtZW4gcGVydGFtYSBkYW4gYHhgIHNlYmFnYWkgYXJndW1lbiBrZWR1YSBkYXJpIGZ1bmdzaSBgZigpYCB5YW5nIGtlbXVkaWFuIGhhc2lsbnlhIGRpZ3VuYWthbiBzZWJhZ2FpIGFyZ3VtZW4gcGVydGFtYSBwYWRhIGZ1bmdzaSBgZygpYCBkZW5nYW4gYHpgIHNlYmFnYWkgYXJndW1lbiBrZWR1YS4iDQoNClNlbGFuanV0bnlhIGtpdGEgYWthbiBndW5ha2FuIG9wZXJhdG9yIGAlPiVgIGRhbGFtIHR1dG9yaWFsIGluaS4NCg0KIyBLb25la3NpIGtlIERhdGFiYXNlIGRhbiBJbXBvcnQgRGF0YQ0KDQpVbnR1ayBkYXRhIHlhbmcgdGVyc2VkaWEgZGkgZGF0YWJhc2UsIEFuZGEgZGFwYXQgbWVtYnVhdCBrb25la3NpIGtlIGRhdGFiYXNlIGRlbmdhbiBwZXJpbnRhaCBiZXJpa3V0IGluaS4NCg0KYGBge3IgZGJ9DQpzcnYgPC0gImxvY2FsaG9zdCIgIyBnYW50aSBkZW5nYW4gSVAgeWFuZyBha2FuIGRpYmVyaWthbiBvbGVoIHBlbWJpY2FyYQ0KcG9ydCA8LSAzMzA2DQpkYm4gPC0gIm55Y2ZsaWdodHMiDQp1c3IgPC0gInVzZXIxIg0KcHdkIDwtICJQQHNzdzByZCINCg0KIyBNZW1idWF0IGtvbmVrc2kga2UgZGF0YWJhc2UgTXlTUUwNCm15Y29uIDwtIGRiQ29ubmVjdChNeVNRTCgpLCANCiAgICAgICAgICAgICAgICAgICBob3N0ID0gc3J2LCANCiAgICAgICAgICAgICAgICAgICBkYm5hbWUgPSBkYm4sIA0KICAgICAgICAgICAgICAgICAgIHVzZXIgPSB1c3IsIA0KICAgICAgICAgICAgICAgICAgIHBhc3N3b3JkID0gcHdkLCANCiAgICAgICAgICAgICAgICAgICBwb3J0ID0gcG9ydCkNCg0KIyBMaXN0IHRhYmVsIHlhbmcgYWRhIGRpIGRhbGFtIGRhdGFiYXNlDQpkYkxpc3RUYWJsZXMobXljb24pDQpgYGANCg0KVW50dWsgaW1wb3IgZGF0YSBkYXJpIGRhdGJhc2Uga2UgUiBkYXBhdCBtZW5nZ3VuYWthbiBmdW5nc2kgYGRiUmVhZFRhYmxlKClgLg0KDQpgYGB7ciBpbXBvcnR9DQpmbGlnaHRzIDwtIG15Y29uICU+JSBkYlJlYWRUYWJsZSgiZmxpZ2h0cyIpDQphaXJsaW5lcyA8LSBteWNvbiAlPiUgZGJSZWFkVGFibGUoImFpcmxpbmVzIikNCmFpcnBvcnRzIDwtIG15Y29uICU+JSBkYlJlYWRUYWJsZSgiYWlycG9ydHMiKQ0Kd2VhdGhlciA8LSBteWNvbiAlPiUgZGJSZWFkVGFibGUoIndlYXRoZXIiKQ0KcGxhbmVzIDwtIG15Y29uICU+JSBkYlJlYWRUYWJsZSgicGxhbmVzIikNCmBgYA0KDQoqKlBlbnRpbmcgdW50dWsgbWVudXR1cCBrb25la3NpIGtlIGRhdGFiYXNlIHNldGVsYWggc2VsZXNhaSBtZW5nYWtzZXMgZGFuIHRpZGFrIG1lbmdndW5ha2FuIGtvbmVrc2kgdGVyc2VidXQuIE1lbXV0dXNrYW4ga29uZWtzaSBkYXBhdCBtZW5nZ3VuYWthbiBmdW5nc2kqKiBgZGJEaXNjb25uZWN0KClgLg0KDQpgYGB7ciBkY30NCiMgamFuZ2FuIG1lbmFqYWxhbmthbiBmdW5nc2kgZGkgYmF3YWggaW5pIGppa2EgQW5kYSBtYXNpaCBtZW1lcmx1a2FuL21lbmdndW5ha2FuIGtvbmVrc2kgZGkgYXRhcyENCmRiRGlzY29ubmVjdChteWNvbikNCmBgYA0KDQpVbnR1ayBkYXRhIENTViB5YW5nIGFrYW4gZGlndW5ha2FuIGRhcGF0IEFuZGEgdW5kdWggdGVybGViaWggZGFodWx1IGRhbiBzaW1wYW4gZGkgc2VidWFoIGZvbGRlciAqd29ya2luZyBkaXJlY3RvcnkqLCBtaXNhbG55YSBkaSBkYWxhbSBmb2xkZXIgYEQ6L2FlcGhpZGF5YXR1bG9oL1IvbGVhcm5pbmcvUmRiYCBkZW5nYW4gbmFtYSBgcHJpY2VzLmNzdmAuIFVudHVrIG1lbmdha3RpZmthbiAqd29ya2luZyBkaXJlY3RvcnkqIHBhZGEgZm9sZGVyIHRlcnNlYnV0IEFuZGEgZGFwYXQgZ3VuYWthbiBmdW5nc2kgYHNldHdkKClgLg0KYGBge3IgZXZhbD1GQUxTRX0NCnNldHdkKCJEOi9hZXBoaWRheWF0dWxvaC9SL2xlYXJuaW5nL1JkYiIpICMgZ2FudGkgc2VzdWFpIGRlbmdhbiBsb2thc2kgZm9sZGVyIHlhbmcgQW5kYSBndW5ha2FuDQpgYGANCg0KYGBge3IgZXZhbD1GQUxTRX0NCmRvd25sb2FkLmZpbGUodXJsID0gImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9hZXBoaWRheWF0dWxvaC9tYW5pcHVsYXNpLWRhdGEvbWFzdGVyL3ByaWNlcy5jc3YiLCBkZXN0ZmlsZSA9ICJwcmljZXMuY3N2IikNCmRvd25sb2FkLmZpbGUodXJsID0gImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9hZXBoaWRheWF0dWxvaC9tYW5pcHVsYXNpLWRhdGEvbWFzdGVyL3NwZWNpYWxkYXlzLmNzdiIsIGRlc3RmaWxlID0gInNwZWNpYWxkYXlzLmNzdiIpDQpgYGANClBlcmhhdGlrYW4gYmFod2EgcGFkYSBmaWxlIGBwcmljZXMuY3N2YCBwZW1pc2FoIGFudGFyIGtvbG9tbnlhIG1lbmdndW5ha2FuIHRhbmRhIHRpdGlrIGtvbWEgKGA7YCkgZGFuIHRhbmRhIGtvbWEgKGAsYCkgc2ViYWdhaSBwZW1pc2FoIGRlc2ltYWwuDQpgYGB7cn0NCiMgSW1wb3J0IGRhdGEgZGFyaSBDU1YgdGVyc2VidXQga2UgUg0KcHJpY2VzIDwtIHJlYWRfZGVsaW0oInByaWNlcy5jc3YiLCBkZWxpbSA9ICI7IiwgZXNjYXBlX2RvdWJsZSA9IEZBTFNFLCB0cmltX3dzID0gVFJVRSkNCnNwZWNpYWxkYXlzIDwtIHJlYWRfY3N2KCJzcGVjaWFsZGF5cy5jc3YiKQ0KYGBgDQpEZW5nYW4gZnVuZ3NpIHlhbmcgYWRhIHBhZGEgYHJlYWRyYCwgc2V0ZWxhaCBzZWxlc2FpIGltcG9ydCBkYXRhIGFrYW4gbXVuY3VsIHRpcGUgYXRhdSBzcGVzaWZpa2FzaSBkYXJpIHZhcmlhYmVsIHlhbmcgZGliYWNhLg0KDQpBdGF1IGRlbmdhbiBjYXJhIGxhbmdzdW5nIGltcG9ydCB0YW5wYSB1bmR1aCB0ZXJsZWJpaCBkYWh1bHUuDQpgYGB7ciBldmFsPUZBTFNFfQ0KcHJpY2VzIDwtIHJlYWRfZGVsaW0oImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9hZXBoaWRheWF0dWxvaC9tYW5pcHVsYXNpLWRhdGEvbWFzdGVyL3ByaWNlcy5jc3YiLCBkZWxpbSA9ICI7IiwgZXNjYXBlX2RvdWJsZSA9IEZBTFNFLCB0cmltX3dzID0gVFJVRSkNCg0Kc3BlY2lhbGRheXMgPC0gcmVhZF9jc3YoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9hZXBoaWRheWF0dWxvaC9tYW5pcHVsYXNpLWRhdGEvbWFzdGVyL3NwZWNpYWxkYXlzLmNzdiIpDQpgYGANCkZpbGUgYHByaWNlcy5jc3ZgIGRpYmFjYSBtZW5nZ3VuYWthbiBmdW5nc2kgYHJlYWRfZGVsaW0oKWAga2FyZW5hIHBlbWlzYWggYW50YXIgdmFyaWFibGVueWEgYWRhbGFoIHRpdGlrLWtvbWEgKGA6YCksIG1ha2EgZGFyaSBpdHUgZGlzZWJ1dGthbiBqdWdhIGBkZWxpbSA9ICI7ImAuIFVudHVrIGZpbGUgYHNwZWNpYWxkYXlzLmNzdmAgZGliYWNhIG1lbmdndW5ha2FuIGZ1bmdzaSBgcmVhZF9jc3YoKWAga2FyZW5hIG1lbmdndW5ha2FuIHRhbmRhIGtvbWEgKGAsYCkgc2ViYWdhaSBwZW1pc2FoIGFudGFyIHZhcmlhYmxlLg0KDQoqIEtldGVyYW5nYW4gZGF0YToNCiAgKyBbYGZsaWdodHNgXShodHRwczovL3d3dy5yZG9jdW1lbnRhdGlvbi5vcmcvcGFja2FnZXMvbnljZmxpZ2h0czEzL3ZlcnNpb25zLzEuMC4wL3RvcGljcy9mbGlnaHRzKTogc2VtdWEgcGVuZXJiYW5nYW4geWFuZyBiZXJhbmdrYXQgZGFyaSBOWUMgZGkgdGFodW4gMjAxMy4NCiAgKyBbYHdlYXRoZXJgXShodHRwczovL3d3dy5yZG9jdW1lbnRhdGlvbi5vcmcvcGFja2FnZXMvbnljZmxpZ2h0czEzL3ZlcnNpb25zLzEuMC4wL3RvcGljcy93ZWF0aGVyKTogZGF0YSBtZXRlb3JvbG9naSBwZXIgamFtIHVudHVrIG1hc2luZy1tYXNpbmcgYmFuZGFyYSAoKmFpcnBvcnRzKikuIA0KICArIFtgcGxhbmVzYF0oaHR0cHM6Ly93d3cucmRvY3VtZW50YXRpb24ub3JnL3BhY2thZ2VzL255Y2ZsaWdodHMxMy92ZXJzaW9ucy8xLjAuMC90b3BpY3MvcGxhbmVzKTogaW5mb3JtYXRpb24gdGVudGFuZyBrb25zdHJ1a3NpIG1hc2luZy1tYXNpbmcgcGVzYXdhdC4gDQogICsgW2BhaXJwb3J0c2BdKGh0dHBzOi8vd3d3LnJkb2N1bWVudGF0aW9uLm9yZy9wYWNrYWdlcy9ueWNmbGlnaHRzMTMvdmVyc2lvbnMvMS4wLjAvdG9waWNzL2FpcnBvcnRzKTogbmFtYSBkYW4gbG9rYXNpIGJhbmRhcmEuIA0KICArIFtgYWlybGluZXNgXShodHRwczovL3d3dy5yZG9jdW1lbnRhdGlvbi5vcmcvcGFja2FnZXMvbnljZmxpZ2h0czEzL3ZlcnNpb25zLzEuMC4wL3RvcGljcy9haXJsaW5lcyk6IHRyYW5zbGFzaSBhbnRhcmEgZHVhIGh1cnVmIGtvZGUgYGNhcnJpZXJgIGRhbiBuYW1hbnlhLg0KICArIFtgcHJpY2VzYF0oKTogZGFmdGFyIGhhcmdhIHRpa2V0IChgdGlja2V0YCkgZGFuIGJpYXlhIChgY29zdGApIHBlciBwZW51bXBhbmcgdW50dWsgbWFzaW5nLW1hc2luZyBwZW5lcmJhbmdhbi4NCiAgKyBbYHNwZWNpYWxkYXlzYF0oKTogZGFmdGFyIGhhcmkgImlzdGltZXdhIiBkaSBBbWVyaWthLg0KDQpLZXRlcmFuZ2FuIHZhcmlhYmxlOg0KDQpgZmxpZ2h0c2ANCg0KfCBWYXJpYWJsZSB8IEtldGVyYW5nYW4gfA0KfC0tLS0tLS0tLS18LS0tLS0tLS0tLS0tfA0KfCBgeWVhcmAsYG1vbnRoYCxgZGF5YCB8IERhdGUgb2YgZGVwYXJ0dXJlIHwNCnwgYGRlcF90aW1lYCwgYGFycl90aW1lYCB8IEFjdHVhbCBkZXBhcnR1cmUgYW5kIGFycml2YWwgdGltZXMgKGZvcm1hdCBISE1NIG9yIEhNTSksIGxvY2FsIHR6LiB8DQp8IGBzY2hlZF9kZXBfdGltZWAsIGBzY2hlZF9hcnJfdGltZWAgfCBTY2hlZHVsZWQgZGVwYXJ0dXJlIGFuZCBhcnJpdmFsIHRpbWVzIChmb3JtYXQgSEhNTSBvciBITU0pLCBsb2NhbCB0ei4gfA0KfCBgZGVwX2RlbGF5YCwgYGFycl9kZWxheWAgfCBEZXBhcnR1cmUgYW5kIGFycml2YWwgZGVsYXlzLCBpbiBtaW51dGVzLiBOZWdhdGl2ZSB0aW1lcyByZXByZXNlbnQgZWFybHkgZGVwYXJ0dXJlcy9hcnJpdmFscy4gfA0KfCBgaG91cmAsIGBtaW51dGVgIHwgVGltZSBvZiBzY2hlZHVsZWQgZGVwYXJ0dXJlIGJyb2tlbiBpbnRvIGhvdXIgYW5kIG1pbnV0ZXMuIHwNCnwgYGNhcnJpZXJgIHwgVHdvIGxldHRlciBjYXJyaWVyIGFiYnJldmlhdGlvbi4gU2VlIGA/YWlybGluZXNgIHRvIGdldCBuYW1lLiB8DQp8IGB0YWlsbnVtYCB8IFBsYW5lIHRhaWwgbnVtYmVyIHwNCnwgYGZsaWdodGAgfCBGbGlnaHQgbnVtYmVyIHwNCnwgYG9yaWdpbmAsIGBkZXN0YCB8IE9yaWdpbiBhbmQgZGVzdGluYXRpb24uIFNlZSBgP2FpcnBvcnRzYCBmb3IgYWRkaXRpb25hbCBtZXRhZGF0YS4gfA0KfCBgYWlyX3RpbWVgIHwgQW1vdW50IG9mIHRpbWUgc3BlbnQgaW4gdGhlIGFpciwgaW4gbWludXRlcy4gfA0KfCBgZGlzdGFuY2VgIHwgRGlzdGFuY2UgYmV0d2VlbiBhaXJwb3J0cywgaW4gbWlsZXMuIHwNCnwgYHRpbWVfaG91cmAgfCBTY2hlZHVsZWQgZGF0ZSBhbmQgaG91ciBvZiB0aGUgZmxpZ2h0IGFzIGEgYFBPU0lYY3RgIGRhdGUuIEFsb25nIHdpdGggb3JpZ2luLCBjYW4gYmUgdXNlZCB0byBqb2luIGBmbGlnaHRzYCBkYXRhIHRvIGB3ZWF0aGVyYCBkYXRhLiB8DQp8IHwgfA0KDQpgYWlybGluZXNgDQoNCnwgVmFyaWFibGUgfCBLZXRlcmFuZ2FuIHwNCnwtLS0tLS0tLS0tfC0tLS0tLS0tLS0tLXwNCnwgYGNhcnJpZXJgIHwgVHdvIGxldHRlciBhYmJyZXZpYXRpb24gfA0KfCBgbmFtZWAgfCBGdWxsIG5hbWUgfA0KfCB8IHwNCg0KYHBsYW5lc2ANCg0KfCBWYXJpYWJsZSB8IEtldGVyYW5nYW4gfA0KfC0tLS0tLS0tLS18LS0tLS0tLS0tLS0tfA0KfCBgdGFpbG51bWAgfCBUYWlsIG51bWJlciB8DQp8IGB5ZWFyYCB8IFllYXIgbWFudWZhY3R1cmVkIHwNCnwgYHR5cGVgIHwgVHlwZSBvZiBwbGFuZSB8DQp8IGBtYW51ZmFjdHVyZXJgLGBtb2RlbGAgfCBNYW51ZmFjdHVyZXIgYW5kIG1vZGVsIHwNCnwgYGVuZ2luZXNgLGBzZWF0c2AgfCBOdW1iZXIgb2YgZW5naW5lcyBhbmQgc2VhdHMgfA0KfCBgc3BlZWRgIHwgQXZlcmFnZSBjcnVpc2luZyBzcGVlZCBpbiBtcGggfA0KfCBgZW5naW5lYCB8IFR5cGUgb2YgZW5naW5lIHwNCnwgfCB8DQoNCmB3ZWF0aGVyYA0KDQp8IFZhcmlhYmxlIHwgS2V0ZXJhbmdhbiB8DQp8LS0tLS0tLS0tLXwtLS0tLS0tLS0tLS18DQp8IGBvcmlnaW5gIHwgV2VhdGhlciBzdGF0aW9uLiBOYW1lZCBvcmlnaW4gdG8gZmFjaWxpYXRlIG1lcmdpbmcgd2l0aCBgZmxpZ2h0c2AgZGF0YS4gfA0KfCBgeWVhcmAsYG1vbnRoYCxgZGF5YCxgaG91cmAgfCBUaW1lIG9mIHJlY29yZGluZy4gfA0KfCBgdGVtcGAsYGRld3BgIHwgVGVtcGVyYXR1cmUgYW5kIGRld3BvaW50IGluIEZhaHJlbmhlaXQuIHwNCnwgYGh1bWlkYCB8IFJlbGF0aXZlIGh1bWlkaXR5LiB8DQp8IGB3aW5kX2RpcmAsYHdpbmRfc3BlZWRgLGB3aW5kX2d1c3RgIHwgV2luZCBkaXJlY3Rpb24gKGluIGRlZ3JlZXMpLCBzcGVlZCBhbmQgZ3VzdCBzcGVlZCAoaW4gbXBoKS4gfA0KfCBgcHJlY2lwYCB8IFByZWNpcGl0YXRpb24sIGluIGluY2hlcy4gfA0KfCBgcHJlc3N1cmVgIHwgU2VhIGxldmVsIHByZXNzdXJlIGluIG1pbGxpYmFycy4gfA0KfCBgdmlzaWJgIHwgVmlzaWJpbGl0eSBpbiBtaWxlcyB8DQp8IGB0aW1lX2hvdXJgIHwgRGF0ZSBhbmQgaG91ciBvZiB0aGUgcmVjb3JkaW5nIGFzIGEgYFBPU0lYY3RgIGRhdGUuIHwNCnwgfCB8DQoNCmBhaXJwb3J0c2ANCg0KfCBWYXJpYWJsZSB8IEtldGVyYW5nYW4gfA0KfC0tLS0tLS0tLS18LS0tLS0tLS0tLS0tfA0KfCBgZmFhYCB8IEZBQSBhaXJwb3J0IGNvZGUuIHwNCnwgYG5hbWVgIHwgVXN1YWwgbmFtZSBvZiB0aGUgYWlwb3J0LiB8DQp8IGBsYXRgLGBsb25gIHwgTG9jYXRpb24gb2YgYWlycG9ydC4gfA0KfCBgYWx0YCB8IEFsdGl0dWRlLCBpbiBmZWV0LiB8DQp8IGB0emAgfCBUaW1lem9uZSBvZmZzZXQgZnJvbSBHTVQuIHwNCnwgYGRzdGAgfCBEYXlsaWdodCBzYXZpbmdzIHRpbWUgem9uZS4gQSA9IFN0YW5kYXJkIFVTIERTVDogc3RhcnRzIG9uIHRoZSBzZWNvbmQgU3VuZGF5IG9mIE1hcmNoLCBlbmRzIG9uIHRoZSBmaXJzdCBTdW5kYXkgb2YgTm92ZW1iZXIuIFUgPSB1bmtub3duLiBOID0gbm8gZHN0LnwNCnwgYHR6b25lYCB8IElBTkEgdGltZSB6b25lLCBhcyBkZXRlcm1pbmVkIGJ5IEdlb05hbWVzIHdlYnNlcnZpY2UgfA0KfCB8IHwNCg0KYHByaWNlc2ANCg0KfCBWYXJpYWJsZSB8IEtldGVyYW5nYW4gfA0KfC0tLS0tLS0tLS18LS0tLS0tLS0tLS0tfA0KfCBgeWVhcmAsYG1vbnRoYCxgZGF5YCB8IERhdGUgb2YgZGVwYXJ0dXJlIHwNCnwgYG9yaWdpbmAsYGRlc3RgIHwgT3JpZ2luIGFuZCBkZXN0aW5hdGlvbi4gU2VlIGA/YWlycG9ydHNgIGZvciBhZGRpdGlvbmFsIG1ldGFkYXRhLiB8DQp8IGBzY2hlZF9kZXBfdGltZWAgfCBTY2hlZHVsZWQgZGVwYXJ0dXJlIGFuZCBhcnJpdmFsIHRpbWVzIChmb3JtYXQgSEhNTSBvciBITU0pLCBsb2NhbCB0ei4gfA0KfCBgdGFpbG51bWAgfCBQbGFuZSB0YWlsIG51bWJlciB8DQp8IGB0aWNrZXRgIHwgQXZlcmFnZSB0aWNrZXQgcHJpY2UgcGVyIHBlcnNvbiBmb3IgYSBmbGlnaHQgfA0KfCBgY29zdGAgfCBBdmVyYWdlIG9wZXJhdGluZyBjb3N0cyBwZXIgcGVyc29uIGZvciBhIGZsaWdodCB8DQp8IHwgfA0KDQpgc3BlY2lhbGRheXNgDQoNCnwgVmFyaWFibGUgfCBLZXRlcmFuZ2FuIHwNCnwtLS0tLS0tLS0tfC0tLS0tLS0tLS0tLXwNCnwgYHllYXJgLGBtb250aGAsYGRheWAgfCBEYXRlIG9mIGRlcGFydHVyZSB8DQp8IGBob2xpZGF5YCB8IE5hbWUgb2Ygc3BlY2lhbCBkYXkgaW4gVW5pdGVkIFN0YXRlcyB8DQp8IHwgfA0KDQoNCiMgTWF0ZXJpDQoNClNldGVsYWggYmVyaGFzaWwgaW1wb3J0IGRhdGEgZGFyaSBNeVNRTCBkYW4gQ1NWLCBzZWxhbmp1dG55YSBraXRhIGFrYW4gbWVuZ2Vrc3Bsb3Jhc2kgZGF0YSB0ZXJzZWJ1dC4NCg0KYGBge3J9DQpkaW0oZmxpZ2h0cykNCmdsaW1wc2UoZmxpZ2h0cykNCmhlYWQoZmxpZ2h0cykNCnN1bW1hcnkoZmxpZ2h0cykNCmBgYA0KDQpgZGltKClgIG1lbmFtcGlsa2FuIGJhbnlha255YSBiYXJpcyBkYW4gdmFyaWFibGUgZGFyaSBzdWF0dSBkYXRhIGZyYW1lLg0KDQpgZ2xpbXBzZSgpYCBzZXJ1cGEgZGVuZ2FuIGBzdHIoKWAgZGFyaSBiYXNlLVIsIGJlcnR1anVhbiB1bnR1ayBtZWxpaGF0IHRpcGUgZGFuIHN0cnVrdHVyIG9iamVrLiBKaWthIG9iamVrIHRlcnNlYnV0IGFkYWxhaCBkYXRhIGZyYW1lLCBtYWthIGFrYW4gbWVuZ2hhc2lsa2FuIGJhbnlha255YSBiYXJpcyAoKipvYnNlcnZhdGlvbnMqKikgZGFuIHZhcmlhYmxlICgqKnZhcmlhYmxlcyoqKS4gRnVuZ3NpIGluaSBqdWdhIG1lbmFtcGlsa2FuIG5hbWEgdmFyaWFiZWwsIHRpcGUgdmFyaWFiZWwsIGRhbiBiZWJlcmFwYSBiYXJpcyBwZXJ0YW1hIGRhcmkgZGF0YS4NCg0KYGhlYWQoKWAgYmVyZ3VuYSB1bnR1ayBtZWxpaGF0IGF0YXUgbWVuYW1waWxrYW4gYmViZXJhcGEgYmFyaXMgcGVydGFtYSBkYXJpIGRhdGEgZnJhbWUuIFNlY2FyYSBkZWZhdWx0IGBoZWFkKClgIG1lbmdndW5ha2FuIGBuID0gNmAgdW50dWsgbWVuZW50dWthbiBiYW55YWtueWEgYmFyaXMgeWFuZyBha2FuIGRpdGFtcGlsa2FuLiBKaWthIEFuZGEgaW5naW4gbWVsaWhhdCAxMCBiYXJpcyBwZXJ0YW1hLCBtYWthIGN1a3VwIG1lbm5nZ2FudGlueWEgbWVuamFkaSBgaGVhZChmbGlnaHRzLCBuID0gMTApYC4gTmFtdW4gamlrYSB5YW5nIGluZ2luIEFuZGEgdGFtcGlsa2FuIGFkYSBiZWJlcmFwYSBiYXJpcyB0ZXJha2hpciBkYXJpIGRhdGEgZnJhbWUsIGd1bmFrYW4gYHRhaWwoKWAuDQoNCmBzdW1tYXJ5KClgIG1lbmdoYXNpbGthbiBiZWJlcmFwYSBuaWxhaSBzdGF0aXN0aWsgZGVza3JpcHRpZiB1bnR1ayBtYXNpbmctbWFzaW5nIHZhcmlhYmVsIGppa2EgeWFuZyBkaW1hc3VrYW4gc2ViYWdhaSBhcmd1bWVuIGFkYWxhaCBkYXRhIGZyYW1lLiBVbnR1ayB2YXJpYWJlbCBudW1lcmlrIG1ha2EgYWthbiBtZW5oYXNpbGthbiBuaWxhaSBtaW5pbXVtIChgTWluLmApLCBxdWFudGlsIHBlcnRhbWEgKGAxc3QgUXUuYCksIG1lZGlhbiAoYE1lZGlhbmApLCByYXRhLXJhdGEgKGBNZWFuYCksIHF1YW50aWwga2V0aWdhIChgM3JkIFF1LmApLCBtYWtzaW11bSAoYE1heC5gKSBkYW4gYmFueWFrbnlhIG1pc3NpbmcgdmFsdWUgKGBOQSdzYCkgamlrYSBhZGEuIFVudHVrIHZhcmlhYmVsIGthdGVnb3JpayAoKmNoYXJhY3RlciogYXRhdSAqZmFjdG9yKikgbWFrYW4gYWthbiBtZW5naGFzaWxrYW4gYmFueWFrbnlhIGRhdGEgKGBMZW5ndGhgKSwga2VsYXMgKGBDbGFzc2ApIGRhbiBtb2RlIChgTW9kZWApLg0KDQojIyBNZW5nYW1iaWwgbmlsYWkgdW5payAodGlkYWsgZHVwbGlrYXNpKSBkYXJpIHNlYnVhaCB2YXJpYWJsZQ0KSGFsIGluaSBzYW1hIHNlcGVydGkgbWVsYWt1a2FuIGByZW1vdmUgZHVwbGljYXRlYCBkaSBNcyBFeGNlbCB1bnR1ayBzYXR1IHZhcmlhYmVsLg0KDQpgYGB7cn0NCmRpc3RpbmN0KGZsaWdodHMsIHllYXIpDQoNCmRpc3RpbmN0KGZsaWdodHMsIG9yaWdpbikNCg0KZGlzdGluY3QoZmxpZ2h0cywgZGVzdCkNCg0KYGBgDQoNClVudHVrIG1lbmRhcGF0a2FuIG5pbGFpIHVuaWsgZGFyaSBzZW11YSBiYXJpcyBiZXJkYXNhcmthbiB2YXJpYWJlbCB0ZXJ0ZW50dSB0YW1iYWhrYW4gb3BzaSBgLmtlZXBfYWxsID0gVFJVRWAgc2V0ZWxhaCBuYW1hIHZhcmlhYmxlIGRhbGFtIGZ1bmdzaSBgZGlzdGluY3QoKWAuDQoNCmBgYHtyfQ0KZGlzdGluY3QoZmxpZ2h0cywgZGVzdCwgLmtlZXBfYWxsID0gVFJVRSkNCmBgYA0KDQpgYGB7cn0NCmRpc3RpbmN0KGZsaWdodHMpDQpgYGANCg0KIyMgTWVtaWxpaCBhdGF1IG1lbWJ1YW5nIGJlYmVyYXBhIHZhcmlhYmxlIHlhbmcgYWthbi90aWRhayBkaWd1bmFrYW4NCg0KDQpVbnR1ayBtZW1idWF0IGRhdGEgZnJhbWUgZGFyaSBoYXNpbCBgc2VsZWN0KClgIEFuZGEgZGFwYXQgbWVudWxpc2thbiBgbmFtYWRhdGFmcmFtZSA8LSBzZWxlY3QoLi4uKWAuIEFyZ3VtZW4gcGVydGFtYSBkYXJpIGBzZWxlY3QoKWAgYWRhbGFoIGRhdGEgZnJhbWUgeWFuZyBpbmdpbiBraXRhIHN1YnNldCB2YXJpYWJlbCB0ZXJ0ZW50dSBzYWphLiBTZWxhbmp1dG55YSB0dWxpc2thbiBuYW1hIHZhcmlhYmxlIHlhbmcgQW5kYSBpbmdpbmthbiBhZGEgZGkgZGF0YSBmcmFtZSB5YW5nIGJhcnUuIEFuZGEgZGFwYXQgbWVudWxpc2thbiBuYW1hIG1hc2luZy1tYXNpbmcgdmFyaWFibGUgYXRhdSBkYXBhdCBqdWdhIGRlbmdhbiBtZW51bGlza2FuIHVydXRhbiAoaW5kZWtzKSB2YXJpYWJlbC4NCg0KYGBge3J9DQpzZWxlY3QoZmxpZ2h0cywgbW9udGgsIGRheSwgZGVwX3RpbWUsIGRlcF9kZWxheSwgYXJyX3RpbWUsIGFycl9kZWxheSwgb3JpZ2luLCBkZXN0KQ0Kc2VsZWN0KGZsaWdodHMsIGMoMjo0LCA2OjcsIDksIDEzOjE0KSkNCmBgYA0KDQpKaWthIHZhcmlhYmVsIHlhbmcgaW5naW4gQW5kYSBwaWxpaCBjdWt1cCBiYW55YWsgc2VkYW5na2FuIHZhcmlhYmVsIHlhbmcgaW5naW4gQW5kYSBidWFuZyBsZWJpaCBzZWRpa2l0LCBBbmRhIGRhcGF0IG1lbnVsaXNrYW4gbmFtYSB2YXJpYWJlbCBkZW5nYW4gbWVuYW1iYWhrYW4gdGFuZGEgbmVnYXRpZiAoYC1gKSBkaSBkZXBhbiBuYW1hIGF0YXUgaW5kZWtzIG1hc2luZy1tYXNpbmcgdmFyaWFiZWwuDQoNCmBgYHtyfQ0Kc2VsZWN0KGZsaWdodHMsIC15ZWFyKQ0Kc2VsZWN0KGZsaWdodHMsIC0xKQ0KYGBgDQoNCiMjIE1lbWlsaWggYmFyaXMgZGF0YSBiZXJkYXNhcmthbiBpbmRla3MgYmFyaXMgYXRhdSBuaWxhaSBwYWRhIHZhcmlhYmVsIHRlcnRlbnR1DQpLZXRpa2EgQW5kYSBpbmdpbiBtZW1pbGloIHNlYmFnaWFuIGJhcmlzIHNhamEgZGFyaSBkYXRhIGZyYW1lLCBBbmRhIGRhcGF0IG1lbmdndW5ha2FuIGZ1bmdzaSBgc2xpY2UoKWAgYXRhdSBgZmlsdGVyKClgLg0KDQpgc2xpY2UoKWAgYmVyZnVuZ3NpIHVudHVrIG1lbnN1YnNldCBiYXJpcyBkYXRhIGJlcmRhc2Fya2FuIGluZGVrcyBiYXJpc255YS4gTWlzYWxrYW4sIGBzbGljZShmbGlnaHRzLCAxOjEwMClgIGJlcmFydGkgQW5kYSBtZW1pbGloIGRhdGEgcGFkYSBwb3Npc2kgYmFyaXMga2UtMSBzL2Qga2UtMTAwLiBQb3Npc2kgYmFyaXMgMToxMDAgYWRhbGFoIHZlY3RvciBudW1lcmlrIGludGVnZXIgeWFuZyBpbmdpbiBkaXBpbGloLiANCg0KYGBge3J9DQojIG1lbWlsaWggYmFyaXMga2UtMSBzL2QgMTAwMA0Kc2xpY2UoZmxpZ2h0cywgMToxMDAwKQ0KDQojIG1lbWlsaWggYmFyaXMga2UtMSwzLDUsNiwgZGFuIDEwDQpzbGljZShmbGlnaHRzLCBjKDEsMyw1LDYsMTApKQ0KYGBgDQoNCmBmaWx0ZXIoKWAgYmVyZnVuZ3NpIHVudHVrIG1lbWlsaWggc2ViYWdpYW4gZGF0YSBiZXJkYXNhcmthbiBuaWxhaSBkYXJpIHNhdHUgYXRhdSBsZWJpaCB2YXJpYWJlbC4NCg0KYGBge3J9DQpmaWx0ZXIoZmxpZ2h0cywgZGVwX2RlbGF5ID49IDEwKQ0KZmlsdGVyKGZsaWdodHMsIGRlcF9kZWxheSA+PSAxMCAmIG9yaWdpbiA9PSAiSkZLIikNCmZpbHRlcihmbGlnaHRzLCBtb250aCA9PSAxICYgZGF5ID09IDEgJiBvcmlnaW4gPT0gIkpGSyIgJiBkZXN0ID09ICJBVEwiKQ0KZmlsdGVyKGZsaWdodHMsIGlzLm5hKGRlcF90aW1lKSkNCmBgYA0KDQojIyBNZW5ndXJ1dGthbiBkYXRhDQoNCk1lbmd1cnV0a2FuIGRhdGEgZnJhbWUgbWVuZ2d1bmFrYW4gZnVuZ3NpIGBhcnJhbmdlKClgIHNhbmdhdCBtdWRhaC4gDQpgYGB7cn0NCiMgVXJ1dGthbiBkYXRhIGZyYW1lIGZsaWdodHMgYmVyZGFzYXJrYW4gdmFyaWFiZWwgYG9yaWdpbmANCmFycmFuZ2UoZmxpZ2h0cywgb3JpZ2luKQ0KDQojIFVydXRrYW4gZGF0YSBmcmFtZSBmbGlnaHRzIGJlcmRhc2Fya2FuIHZhcmlhYmVsIGBvcmlnaW5gIGRhbiBgZGVzdGANCmFycmFuZ2UoZmxpZ2h0cywgb3JpZ2luLCBkZXN0KQ0KDQojIFVydXRrYW4gZGF0YSBmcmFtZSBmbGlnaHRzIGJlcmRhc2Fya2FuIHZhcmlhYmVsIGBvcmlnaW5gIHNlY2FyYSBhc2NlbmRpbmcgZGFuIGBkZXN0YCBzZWNhcmEgZGVzY2VuZGluZw0KYXJyYW5nZShmbGlnaHRzLCBvcmlnaW4sIGRlc2MoZGVzdCkpDQpgYGANCg0KRnVuZ3NpIGBkZXNjKClgIGRhbGFtIHBlbmdndW5hYW4gYGFycmFuZ2UoKWAgZGlndW5ha2FuIGppa2EgQW5kYSBpbmdpbiBtZW5ndXJ1dGthbiBkYXRhIHNlY2FyYSAqZGVzY2VuZGluZyouDQoNCiMjIFBlbmFuZ2FuYW4gKm1pc3NpbmcgdmFsdWVzKg0KDQpVbnR1ayBrZWJ1dHVoYW4gaWx1c3RyYXNpIHBhZGEgYmFnaWFuIGluaSBkZW5nYW4gZnVuZ3NpIGBuYV9pZigpYCwgQW5kYSBha2FuIG1lbmdnYW50aSBuaWxhaSBgTkFgICgqbWlzc2luZyB2YWx1ZSopIG1lbmphZGkgc2VidWFoIG5pbGFpIHRlcnRlbnR1LCBtaXNhbG55YSBqaWthIGBhaXJfdGltZWAgbWlzc2luZyBha2FuIGRpZ2FudGkgbWVuamFkaSBuaWxhaSBgLTk5OWAuDQoNCmBgYHtyfQ0KIyBCYW55YWtueWEgTkENCnN1bShpcy5uYShmbGlnaHRzWyJhaXJfdGltZSJdKSkNCg0KZmxpZ2h0c19uYTk5OSA8LSBmbGlnaHRzDQojIE1lcnViYWggTkEgZGVuZ2FuIC05OTkNCmZsaWdodHNfbmE5OTkkYWlyX3RpbWUgPC0gaWZfZWxzZShpcy5uYShmbGlnaHRzJGFpcl90aW1lKSwgLTk5OSwgZmxpZ2h0cyRhaXJfdGltZSkNCg0KIyBCYW55YWtueWEgTkENCnN1bShpcy5uYShmbGlnaHRzX25hOTk5WyJhaXJfdGltZSJdKSkNCg0KIyBCYW55YWtueWEgTkEgeWFuZyBzdWRhaCBkaWdhbnRpIC05OTkNCnN1bShmbGlnaHRzX25hOTk5WyJhaXJfdGltZSJdID09IC05OTkpDQpgYGANCg0KU2VsYW5qdXRhbnlhLCBtaXNhbGthbiBkYXJpIHJhdyBkYXRhIEFuZGEsIG5pbGFpIGAtOTk5YCBtZW51bmp1a2thbiAqbWlzc2luZ2cgdmFsdWUqIGRhbiBha2FuIGRpZ2FudGkgamFkaSBgTkFgLg0KDQpgYGB7cn0NCiMgR2FudGkgbmlsYWkgTkEgbWVuamFkaSAtOTk5DQpmbGlnaHRzX25hOTk5JGFpcl90aW1lIDwtIG5hX2lmKGZsaWdodHNfbmE5OTkkYWlyX3RpbWUsIC05OTkpDQoNCiMgQmFueWFrbnlhIC05OTkNCmZpbHRlcihmbGlnaHRzX25hOTk5LCBhaXJfdGltZSA9PSAtOTk5KQ0KDQojIEJhbnlha255YSBOQQ0Kc3VtKGlzLm5hKGZsaWdodHNfbmE5OTkkYWlyX3RpbWUpKQ0KDQojIE1lbWJ1YW5nIHNlbXVhIGJhcmlzIHlhbmcgbWVuZ2FuZHVuZyBOQQ0KZmxpZ2h0c19kcm9wTkEgPC0gZHJvcF9uYShmbGlnaHRzKQ0KDQojIE1lbmdnYW50aSBOQSBwYWRhIG1hc2luZy1tYXNpbmcgdmFyaWFiZWwgZGVuZ2FuIG5pbGFpIHRlcnRlbnR1DQpyZXBsYWNlX25hKGZsaWdodHMsIGxpc3QoZGVwX3RpbWUgPSAwLCBkZXBfZGVsYXkgPSAwLCBhcnJfdGltZSA9IG1lYW4oZmxpZ2h0cyRhcnJfdGltZSwgbmEucm0gPSBUUlVFKSwgYXJyX2RlbGF5ID0gbWVkaWFuKGZsaWdodHMkYXJyX2RlbGF5LCBuYS5ybSA9IFRSVUUpKSkNCmBgYA0KDQpGdW5nc2kgYHJlcGxhY2VfbmEoKWAgbWVuZ2d1bmFrYW4gYXJndW1lbiBzZWJ1YWggYGxpc3RgIHVudHVrIG1lbmVudHVrYW4gbmlsYWkgcGVuZ2dhbnRpIE5BIHBhZGEgc2VidWFoIHZhcmlhYmVsLiBEYXJpIGNvbnRvaCBkaSBhdGFzLCBOQSBwYWRhIGBkZXBfdGltZWAgZGFuIGBkZXBfZGVsYXlgIGRpZ2FudGkgbWVuamFkaSAwIChub2wpLCBOQSBkaSBgYXJyX3RpbWVgIGRpZ2FudGkgZGVuZ2FuIHJhdGEtcmF0YSBgYXJyX3RpbWVgLCBzZWRhbmdrYW4gTkEgZGkgYGFycl9kZWxheWAgZGlnYW50aSBkZW5nYW4gbmlsYWkgbWVkaWFuIGRhcmkgYGFycl9kZWxheWAuDQoNCiMjIE9wZXJhdG9yIFBpcGVzICpgJT4lYCoNCg0KU2VyaW5na2FsaSBwcm9zZXMgKmRhdGEgcHJlcGFyYXRpb24qIG1lbWJ1dHVoa2FuIGJhbnlhayBwcm9zZXMgYXRhdSB0YWhhcGFuLiBTZWthcmFuZyBqaWthIGtpdGEgaW5naW4gbWVsYWt1a2FuIGJlYmVyYXBhIHByb3NlcyBzZWthbGlndXMsIHNhbGFoIHNhdHVueWEga2l0YSBha2FuIG1lbWJ1YXQgc2NyaXB0IGt1cmFuZyBsZWJpaCBzZXBlcnRpIGluaS4NCg0KYGBge3J9DQpzZWxlY3RlZCA8LSBzZWxlY3QoZmxpZ2h0cywgZGVwX2RlbGF5LCBhcnJfdGltZSwgYXJyX2RlbGF5LCBvcmlnaW4sIGRlc3QsIGFpcl90aW1lKQ0KZmlsdGVyZWQgPC0gZmlsdGVyKHNlbGVjdGVkLCBkZXBfZGVsYXkgPj0gMTAgJiBvcmlnaW4gPT0gIkpGSyIpDQpoYXNpbCA8LSBhcnJhbmdlKGZpbHRlcmVkLCBvcmlnaW4pDQpoYXNpbA0KYGBgDQoNCkF0YXUgYWRhIGp1Z2EgeWFuZyBtZW51bGlza2FuIHNlcGVydGkgYmVyaWt1dCBpbmkuDQoNCmBgYHtyfQ0KaGFzaWwgPC0gYXJyYW5nZShmaWx0ZXIoc2VsZWN0KGZsaWdodHMsIGRlcF9kZWxheSwgYXJyX3RpbWUsIGFycl9kZWxheSwgb3JpZ2luLCBkZXN0LCBhaXJfdGltZSksIGRlcF9kZWxheSA+PSAxMCAmIG9yaWdpbiA9PSAiSkZLIiksIG9yaWdpbikNCmhhc2lsDQpgYGANCg0KSmlrYSBBbmRhIGRpbWludGEgdW50dWsgbWVtcGVsYWphcmkgc2NyaXB0IHNlcGVydGkgaW5pIHNheWEgY3VrdXAgeWFraW4gYmFod2EgQW5kYSBha2FuIG1lcmFzYSBrZXN1bGl0YW4gdW50dWsgbWVuZ2V0YWh1aSBwcm9zZXMgeWFuZyBha2FuIGRpbGFrdWthbiBvbGVoIHNjcmlwdCB0ZXJzZWJ1dC4gU2NyaXB0IGRpIGF0YXMgbWFzaWggc2VkZXJoYW5hLCBoYW55YSBhZGEgdGlnYSBmdW5nc2kuIEJheWFuZ2thbiBqaWthIGJhbnlhayBmdW5nc2kgeWFuZyBkaWd1bmFrYW4gc2VjYXJhIGJlcnVydXRhbiwgYmVudHVrIGRpIGF0YXMgYWthbiBtZW5qYWRpOg0KDQpgYGB7ciBldmFsPUZBTFNFfQ0Kb3V0cHV0IDwtIGZ1bl9uKC4uLihmdW4zKGZ1bjIoZnVuMShkYXRhZnJhbWUsIGFyZzEpLCBhcmcyKSwgYXJnMyksIC4uLiksIGFyZ19uKQ0KYGBgDQoNCkppa2EgZGlwZXJoYXRpa2FuLCBhcmd1bWVuIHBlcnRhbWEgZGFyaSBtYXNpbmctbWFzaW5nIGZ1bmdzaSBgc2VsZWN0KClgLCBgZmlsdGVyKClgLCBgYXJyYW5nZSgpYCBkYW4gYmViZXJhcGEgZnVuZ3NpIHlhbmcgbGFpbiBkaSBgZHBseXJgIHlhbmcgc2VyaW5nIGRpZ3VuYWthbiB1bnR1ayB0cmFuc2Zvcm1hc2kgZGFuIGVrc3Bsb3Jhc2kgZGF0YSBhZGFsYWggZGF0YSBmcmFtZS8qKnRpYmJsZSoqIGF0YXUgaGFzaWwgZGFyaSBwcm9zZXMgc2ViZWx1bW55YS4gTWlzYWxueWEsIGhhc2lsIGRhcmkgZnVuZ3NpIGBzZWxlY3QoKWAgYWRhbGFoIGRhdGEgZnJhbWUgeWFuZyBrZW11ZGlhbiBkaXByb3NlcyBkZW5nYW4gZnVuZ3NpIGBmaWx0ZXIoKWAgdW50dWsgbWVtaWxpaCBiYXJpcyBkYXRhIHRlcnRlbnR1IGRhbiBoYXNpbG55YSBkaXRlcnVza2FuIGxhZ2kga2UgZnVuZ3NpIGBhcnJhbmdlKClgIHVudHVrIGRpdXJ1dGthbiBiZXJkYXNhcmthbiB2YXJpYWJlbCB0ZXJ0ZW50dS4NCg0KRGVuZ2FuIGtvbnNlcCBgdGlkeXZlcnNlYCwga2l0YSBkYXBhdCBtZW5nZ3VuYWthbiBvcGVyYXRvciBQaXBlcyBgJT4lYCBhZ2FyIGxlYmloIG11ZGFoIGRhbGFtIG1lbWFoYW1pIHNjcmlwdCBrYXJlbmEgc2NyaXB0IHRlcnNlYnV0IG1lbnVuanVra2FuIHVydXRhbi4gUGVyaGF0aWthbiBjb250b2ggZGkgYmF3YWggaW5pLg0KDQpgYGB7cn0NCmZsaWdodHMgJT4lIA0KICBzZWxlY3QoZGVwX2RlbGF5LCBhcnJfdGltZSwgYXJyX2RlbGF5LCBvcmlnaW4sIGRlc3QsIHRpbWVfaG91cikgJT4lIA0KICBmaWx0ZXIob3JpZ2luID09ICJKRksiICYgYmV0d2VlbihkZXBfZGVsYXksIC0xMCwgMTAwKSkgJT4lIA0KICBhcnJhbmdlKG9yaWdpbiwgZGVzYyhkZXN0KSkgDQpgYGANCg0KSmlrYSBkaWJ1YXQga2UgZGFsYW0ga2FsaW1hdDoNCg0KPiAiQW1iaWwgZGF0YSBwZW5lcmJhbmdhbiBkYXJpIGRhdGEgZnJhbWUgYGZsaWdodHNgIGtlbXVkaWFuIHBpbGloIHZhcmlhYmVsLXZhcmlhYmxlIHRlcnRlbnR1IHNhamEuIFNlbGFuanV0bnlhIGZpbHRlciB5YW5nIGBvcmlnaW5gLW55YSBkYXJpICJKRksiIGRhbiB3YWt0dSBrZWJlcmFuZ2thdGFuIGFudGFyYSB5YW5nIGxlYmloIGF3YWwgMTAgbWVuaXQgZGFuIHlhbmcgZGVsYXkgaGluZ2dhIDEwMCBtZW5pdC4gS2VtdWRpYW4gdXJ1dGthbiBoYXNpbCB0ZXJzZWJ1dCBiZXJkYXNhcmthbiBgb3JpZ2luYCBzZWNhcmEgKmFzY2VuZGluZyogZGFuIGJlcmRhc2Fya2FuIGBkZXN0YCBzZWNhcmEgKmRlc2NlbmRpbmcqLiINCg0KDQoNCiMjIE1lbWJ1YXQgdmFyaWFiZWwgYmFydQ0KDQpgYGB7cn0NCmZsaWdodHMgJT4lIA0KICBzZWxlY3QoZGVwX2RlbGF5LCBhcnJfdGltZSwgYXJyX2RlbGF5LCBvcmlnaW4sIGRlc3QsIHRpbWVfaG91cikgJT4lIA0KICBmaWx0ZXIob3JpZ2luID09ICJKRksiICYgYmV0d2VlbihkZXBfZGVsYXksIC0xMCwgMTAwKSkgJT4lIA0KICBhcnJhbmdlKG9yaWdpbiwgZGVzYyhkZXN0KSkgJT4lIA0KICBtdXRhdGUoaXNfZGVsYXkgPSBpZl9lbHNlKGRlcF9kZWxheSA+IDAsIDEsIDApLA0KICAgICAgICAgdGltZV9ob3VyID0gYXMuUE9TSVhjdCh0aW1lX2hvdXIpKQ0KDQpgYGANCkppa2EgZGlidWF0IGtlIGRhbGFtIGthbGltYXQ6DQoNCj4gIkFtYmlsIGRhdGEgcGVuZXJiYW5nYW4gZGFyaSBkYXRhIGZyYW1lIGBmbGlnaHRzYCBrZW11ZGlhbiBwaWxpaCB2YXJpYWJlbC12YXJpYWJsZSB0ZXJ0ZW50dSBzYWphLiBTZWxhbmp1dG55YSBmaWx0ZXIgeWFuZyBgb3JpZ2luYC1ueWEgZGFyaSAiSkZLIiBkYW4gd2FrdHUga2ViZXJhbmdrYXRhbiBhbnRhcmEgeWFuZyBsZWJpaCBhd2FsIDEwIG1lbml0IGRhbiB5YW5nIGRlbGF5IGhpbmdnYSAxMDAgbWVuaXQuIEtlbXVkaWFuIHVydXRrYW4gaGFzaWwgdGVyc2VidXQgYmVyZGFzYXJrYW4gYG9yaWdpbmAgc2VjYXJhICphc2NlbmRpbmcqIGRhbiBiZXJkYXNhcmthbiBgZGVzdGAgc2VjYXJhICpkZXNjZW5kaW5nKi4gU2V0ZWxhaCBpdHUgbWVtYnVhdCB2YXJpYWJlbCBiYXJ1IGJlcm5hbWEgYGlzX2RlbGF5YCB5YW5nIG1lbnVuanVra2FuIGFwYWthaCBzdWF0dSBwZW5lcmJhbmdhbiB0ZXJqYWRpICpkZWxheSogYXRhdSB0aWRhay4gS2VtdWRpYW4ga29udmVyc2kgdmFyaWFibGUgYHRpbWVfaG91cmAgeWFuZyBtYXNpaCBiZXJ0aXBlIGBjaGFyYWN0ZXJgIG1lbmphZGkgYGRhdGV0aW1lYCBhdGF1ICpgPGR0dG0+YCogZGVuZ2FuIG5hbWEgdmFyaWFiZWwgeWFuZyBzYW1hLCB5YWl0dSBgdGltZV9ob3VyYC4iDQoNCiMjIFRhYmVsIGZyZWt1ZW5zaSBkYW4gdHJhbnNmb3JtYXNpIHRhYmVsDQoNCk1lbWJ1YXQgdGFiZWwgZnJla3VlbnNpIGJlcmRhc2Fya2FuIGBvcmlnaW5gIGRhbiBgZGVzdGAuIEd1bmFrYW4gZnVuZ3NpIGBjb3VudCgpYCB1bnR1ayBtZW5nZXRhaHVpIGJhbnlha255YSBiYXJpcy4NCg0KYGBge3J9DQpmbGlnaHRzICU+JQ0KICBjb3VudCgpDQpgYGANCg0KQWRhIHNlYmFueWFrIGByIGNvdW50KGZsaWdodHMpYCBiYXJpcyB5YW5nIG1lbnVuanVra2FuIG1hc2luZy1tYXNpbmcgcGVuZXJiYW5nYW4uIEFydGlueWEgcGFkYSB0YWh1biAyMDEzIGFkYSBzZWJhbnlhayBgciBjb3VudChmbGlnaHRzKWAgamFkd2FsIHBlbmVyYmFuZ2FuLg0KDQpVbnR1ayBtZW1idWF0IHRhYmVsIGZyZWt1ZW5zaSBiZXJkYXNhcmthbiBrYXRlZ29yaSBzdWF0dSB2YXJpYWJlbCwgcGVyaGF0aWthbiBjb250b2ggZGkgYmF3YWggaW5pLg0KDQpgYGB7cn0NCmZsaWdodHMgJT4lDQogIGZpbHRlcihkZXN0ICVpbiUgYygiQUJRIiwgIkFDSyIsICJBTEIiLCAiQU5DIiwgIkFUTCIpKSAlPiUgIA0KICBjb3VudChvcmlnaW4sIGRlc3QpDQpgYGANCg0KU2NyaXB0IGRpIGF0YXMgbWVuZ2hpdHVuZyBiYW55YWtueWEgamFkd2FsIHBlbmVyYmFuZ2FuIChgY291bnQoKWApIGJlcmRhc2Fya2FuIG1hc2luZy1tYXNpbmcga2F0ZWdvcmkgcGFkYSBgb3JpZ2luYCBkYW4gYGRlc3RgLg0KDQpVbnR1ayBtZW1idWF0IHRhYmVsIGZyZWt1ZW5zaSBkdWEtYXJhaCAodHdvLXdheSBmcmVxdWVuY3kpLCBkYXBhdCBtZW5nZ3VuYWthbiBmdW5nc2kgYHNwcmVhZCgpYCBkYXJpIHBhY2thZ2UgYHRpZHlyYC4gRGFyaSBjb250b2ggaGFzaWwgZGkgYXRhcyBraXRhIGFrYW4gbWVtYnVhdCBrYXRlZ29yaSBwYWRhIHZhcmlhYmVsIGBvcmlnaW5gIHNlYmFnYWkgbmFtYSB2YXJhaWJlbC4NCg0KYGBge3J9DQpmbGlnaHRzICU+JQ0KICBmaWx0ZXIoZGVzdCAlaW4lIGMoIkFCUSIsICJBQ0siLCAiQUxCIiwgIkFOQyIsICJBVEwiKSkgJT4lICANCiAgY291bnQob3JpZ2luLCBkZXN0KSAlPiUNCiAgc3ByZWFkKGtleSA9IG9yaWdpbiwgdmFsdWUgPSBuLCBmaWxsID0gMCkNCmBgYA0KDQpBcmd1bWVuIGBmaWxsID0gMGAgZGlndW5ha2FuIHVudHVrIG1lbmdpc2kgKmNlbGwqIHlhbmcga29zb25nIHNldGVsYWggZGl0YW5zZm9ybWFzaSBkZW5nYW4gbmlsYWkgbm9sIChgMGApLiANCg0KIyMgUmluZ2thc2FuICgqc3VtbWFyeSopIGRhbiBHcm91cA0KDQpTZXJpbmdrYWxpIG1lbmdoaXR1bmcgbmlsYWkgc3RhdGlzdGlrL3N1bW1hcnkgZGlwZXJsdWthbiBkYWxhbSBtZWxha3VrYW4gZWtzcGxvcmFzaSBkYXRhLg0KDQorIE1lbWJ1YW5nIE5BIGRlbmdhbiBgZHJvcF9uYSgpYCBkYW4gbWVuZ2hpdHVuZyByYXRhLXJhdGEgKmRlbGF5Ki4NCmBgYHtyfQ0KZmxpZ2h0cyAlPiUgDQogIGRyb3BfbmEoKSAlPiUgDQogIHN1bW1hcmlzZShyYXRhMl9kZWxheSA9IG1lYW4oZGVwX2RlbGF5KSkNCmBgYA0KDQorIE1lbmdoaXR1bmcgcmF0YS1yYXRhIGRhcmkgYGRlcF9kZWxheWAgYmVyZGFzYXJrYW4gYG9yaWdpbmAsIGBkZXN0YCBkYW4gYG1vbnRoYC4gSmlrYSBhZGEgbmlsYWkgYE5BYCBwYWRhIHZhcmlhYmVsIGBkZXBfZGVsYXlgIG1ha2EgZGlrZWxhdXJrYW4gZGFyaSBwZXJoaXR1bmdhbi4NCg0KYGBge3J9DQpmbGlnaHRzICU+JSANCiAgZ3JvdXBfYnkob3JpZ2luLCBkZXN0LCBtb250aCkgJT4lIA0KICBzdW1tYXJpc2UocmF0YTJfZGVsYXkgPSBtZWFuKGRlcF9kZWxheSwgbmEucm0gPSBUUlVFKSkgJT4lIA0KICBhcnJhbmdlKHJhdGEyX2RlbGF5KSANCg0KYGBgDQoNCmBncm91cF9ieSgpYCBkYW4gYHN1bW1hcmlzZSgpYCBkaWd1bmFrYW4gdW50dWsgbWVuZ2hpdHVuZyBzdGF0aXN0aWsgYmVyZGFzYXJrYW4gZ3J1cCB5ZyBkaXNlYnV0a2FuIHBhZGEgZnVuZ2kgYGdyb3VwX2J5KClgLiANCg0KYGBge3J9DQpmbGlnaHRzICU+JSANCiAgZ3JvdXBfYnkob3JpZ2luLCBkZXN0KSAlPiUgDQogIHN1bW1hcmlzZShyYXRhMl9kZWxheSA9IG1lYW4oZGVwX2RlbGF5LCBuYS5ybSA9IFRSVUUpKQ0KYGBgDQpIYXNpbCBkYXJpIGBzdW1tYXJpc2UoKWAgYWRhbGFoIHNlYnVhaCBkYXRhIGZyYW1lL3RpYmJsZS4gTWFrYSBkYXJpIGl0dSwgc2ViYWlrbnlhIGJlcmlrYW4gbmFtYSBwYWRhIGhhc2lsIGBzdW1tYXJpc2UoKWAuIGByYXRhMl9kZWxheWAgYWRhbGFoIG5hbWEgdmFyaWFiZWwgaGFzaWwgZGFyaSBgbWVhbigpYC4NCg0KYGBge3J9DQpmbGlnaHRzICU+JSANCiAgZ3JvdXBfYnkob3JpZ2luLCBkZXN0KSAlPiUgDQogIHN1bW1hcmlzZShyYXRhMl9kZWxheSA9IG1lYW4oZGVwX2RlbGF5LCBuYS5ybSA9IFRSVUUpKSAlPiUgDQogIHNwcmVhZChrZXkgPSBvcmlnaW4sIHZhbHVlID0gcmF0YTJfZGVsYXkpICMgc29vbiBzcHJlYWQoKSB3aWxsIGJlIHJlcGxhY2VkIHdpdGggcGl2b3Rfd2lkZXIoKQ0KYGBgDQpgc3ByZWFkKClgIG1lbWJ1YXQgdmFyaWFiZWwgYmFydSBkYXJpIGlzaSBzZWJ1YWggdmFyaWFiZWwgeWFuZyBkaXNlYnV0a2FuIHBhZGEgYXJndW1lbiBga2V5ID1gIHNlYmFnYWkgbmFtYSB2YXJpYWJlbCwgZGFuIG5pbGFpIGRhcmkgdmFyaWFiZWwtdmFyaWFiZWwgYmFydSB0ZXJzZWJ1dCBhZGFsYWggbmlsYWkgZGFyaSB2YXJpYWJlbCB5YW5nIGRpc2VidXRrYW4gZGkgYXJndW1lbiBgdmFsdWUgPWAuDQoNCioqTm90ZToqKiBwYWRhIHNhYXQgdHV0b3JpYWwgaW5pIGRpYnVhdCwgcGFja2FnZSBgdGlkeXJgIG1hc2loIG1lbmdndW5ha2FuIGZ1bmdzaSBgc3ByZWFkKClgIGRhbiBgZ2F0aGVyKClgIHVudHVrIHRyYW5zZm9ybWFzaSBkYXRhIGZyYW1lLiBOYW11biBwZW1idWF0IHBhY2thZ2UgYHRpZHlyYCBzdWRhaCBiZXJlbmNhbmEgdW50dWsgbWVuZ2dhbnRpbnlhIGRlbmdhbiBuYW1hIGZ1bmdzaSBiYXJ1LCB5YWl0dSBgcGl2b3Rfd2lkZXIoKWAgZGFuIGBwaXZvdF9sb25nZXIoKWAuDQoNCmBgYHtyfQ0KZmxpZ2h0cyAlPiUgDQogIGdyb3VwX2J5KG9yaWdpbiwgZGVzdCkgJT4lIA0KICBzdW1tYXJpc2UocmF0YTJfZGVsYXkgPSBtZWFuKGRlcF9kZWxheSwgbmEucm0gPSBUUlVFKSkgJT4lIA0KICBzcHJlYWQoa2V5ID0gb3JpZ2luLCB2YWx1ZSA9IHJhdGEyX2RlbGF5KSAlPiUgIyBzb29uIHJlcGxhY2VkIHdpdGggcGl2b3Rfd2lkZXIoKQ0KICBnYXRoZXIoa2V5ID0gb3JpZ2luLCB2YWx1ZSA9IHJhdGEyX2RlbGF5LCAtZGVzdCkgIyBzb29uIHJlcGxhY2VkIHdpdGggcGl2b3RfbG9uZ2VyKCkNCmBgYA0KDQpgYGB7cn0NCnRiIDwtIGZsaWdodHMgJT4lDQogIGdyb3VwX2J5KG1vbnRoKSAlPiUgDQogIGNvdW50KCkNCg0KcGxvdCh0YiRtb250aCwgdGIkbiwgImwiKQ0KDQoNCnRiIDwtIGZsaWdodHMgJT4lDQogIGdyb3VwX2J5KGRheSkgJT4lIA0KICBjb3VudCgpDQoNCnBsb3QodGIkZGF5LCB0YiRuLCAibCIpDQoNCg0KdGIgPC0gZmxpZ2h0cyAlPiUNCiAgbXV0YXRlKGRhdGVzID0gYXMuRGF0ZShwYXN0ZSh5ZWFyLCBtb250aCwgZGF5LCBzZXAgPSAiLSIpLCBmb3JtYXQgPSAiJVktJW0tJWQiKSkgJT4lDQogIGdyb3VwX2J5KGRhdGVzKSAlPiUgDQogIGNvdW50KCkNCnBsb3QodGIkZGF0ZXMsIHRiJG4sICJsIikNCg0KYGBgDQoNCkZ1bmdzaSBgcGFzdGVgIGJlcmZ1bmdzaSB1bnR1ayBtZW5nZ2FidW5na2FuICgqY29uY2F0ZSopIGR1YSBidWFoIG5pbGFpIGF0YXUgdmVjdG9yIG1lbmphZGkgKmNoYXJhY3RlciogZGVuZ2FuIHBlbWlzYWggKmRlZmF1bHQqIGFudGFyIG5pbGFpIHRlcnNlYnV0IGFkYWxhaCA8c3Bhc2k+LiBNaXNhbG55YSANCmBgYHtyfQ0KcGFzdGUoIm5pbGFpIGluaSIsIDkpDQpgYGANCg0KVW50dWsgbWVuZ2dhbnRpIHBlbWlzYWhueWEgQW5kYSBkYXBhdCBtZW55ZWJ1dGthbm55YSBwYWRhIGFyZ3VtZW4gYHNlcCA9YC4gTWlzYWxueWEgQW5kYSBpbmdpbiBtZW5nZ3VuYWthbiBwZW1pc2FoIHRhbmRhIGAtYCwgbWFrYSANCmBgYHtyfQ0KcGFzdGUoIm5pbGFpIGluaSIsIDksIHNlcCA9ICItIikNCmBgYA0KRnVuZ3NpIGBhcy5EYXRlKClgIG1lbGFrdWthbiBrb252ZXJzaSBkYXJpIHNlYnVhaCBjaGFyYWN0ZXIgbWVuamFkaSBuaWxhaSB0YW5nZ2FsICgqZGF0ZS12YWx1ZSopIGRpIFIuIEZvcm1hdCB0YW5nZ2FsIGRlZmF1bHQgZGkgUiBhZGFsYWggYHl5eXktbW0tZGRgLCBuYW11biBkaSBSIG1lbmdndW5ha2FuIGZvcm1hdCBgJVktJW0tJWRgIHVudHVrIGAyMDE5LTA1LTEwYC4gU2VsZWJpaG55YSBBbmRhIGRhcGF0IG1lbGloYXRueWEgZGVuZ2FuIGA/YXMuRGF0ZWAuDQoNCiMjIE1lcmdlL0pvaW4gVGFiZWwNCg0KRGFsYW0gZHVuaWEgbnlhdGEsIHRlcnV0YW1hIGRpIGR1bmlhIGtlcmphIGRhbiBwZXJ1c2FoYWFuLCBqYXJhbmcgc2VrYWxpIGRhdGEgeWFuZyBkaWd1bmFrYW4gaGFueWEgYmVyYXNhbCBkYXJpIHNhdHUgdGFiZWwgYXRhdSBmaWxlLiBCaWFzYW55YSBhZGEgdGFiZWwtdGFiZWwgbGFpbiB5YW5nIGhhcnVzIGRpZ3VuYWthbiB1bnR1ayBtZW5kdWt1bmcgYW5hbGlzaXMgZGF0YS4gTWlzYWxueWEgZGkgYmFuaywgZGkgZGF0YWJhc2UgbWluaW1hbCBhZGEgdGFiZWwgYG1hc3Rlcl9jdXN0b21lcmAgeWFuZyBiZXJpc2kgZGF0YSBkZW1vZ3JhZmkgKG5hbWEsIHRlbXBhdCAmIHRhbmdnYWwgbGFoaXIsIGFsYW1hdCwgZHN0KSwgZGFuIGB0cmFuc2FjdGlvbmAgeWFuZyBiZXJpc2kgZGF0YSB0cmFuc2Frc2kgbmFzYWJhaC4NCg0KUGFkYSBiYWdpYW4gdHV0b3JpYWwgaW5pLCBha2FuIG1lbWJhaGFzIHBlbmdndW5hYW4gYmViZXJhcGEgdGFiZWwgeWFuZyBzdWRhaCBkaWplbGFza2FuIGRpIGJhZ2lhbiBhd2FsIG1lbmdlbmFpIGRhdGEuIEFkYSA3IGRhdGFzZXQgeWFuZyBkYXBhdCBkaWd1bmFrYW4uIE11bmdraW4gdW50dWsgbWVsYWt1a2FuIGFuYWxpc2lzIGRhbiBtZW5kYXBhdGthbiBoYXNpbG55YSBBbmRhIHRpZGFrIG1lbWJ1dHVoa2FuIHNlbXVhbnlhLg0KDQpEYXJpIGtldHVqdWggdGFiZWwsIHJlbGFzaW55YSBkaWdhbWJhcmthbiBzZXBlcnRpIGdhbWJhciBkaSBiYXdhaCBpbmkuDQoNCjxwIGFsaWduPSJjZW50ZXIiPg0KICAgIDxpbWcgc3JjPSJyZWxhdGlvbnMyLnBuZyIgYWx0PSJEaWFncmFtIFJlbGFzaSBUYWJlbCI+DQogICAgPGJyLz4NCiAgICA8YnIvPg0KICAgIDxlbT5EaWFncmFtIFJlbGFzaSBUYWJlbDwvZW0+DQo8L3A+DQoNCisgYGZsaWdodHNgIGRpaHVidW5na2FuIGRlbmdhbiBgcGxhbmVzYCB2aWEgc2F0dSB2YXJpYWJlbCwgYHRhaWxudW1gLg0KDQorIGBmbGlnaHRzYCBkaWh1YnVuZ2thbiBkZW5nYW4gYGFpcmxpbmVzYCBtZWxhdWkgdmFyaWFiZWwgYGNhcnJpZXJgLg0KDQorIGBmbGlnaHRzYCBkaWh1YnVuZ2thbiBkZW5nYW4gYGFpcnBvcnRzYCBkZW5nYW4gZHVhIGNhcmE6IHZpYSB2YXJpYWJlbCBgb3JpZ2luYCBkZW5nYW4gdmFyaWFiZWwgYGZhYWAgZGFuIGBkZXN0YCBkZW5nYW4gdmFyaWFiZWwgYGZhYWAuDQoNCisgYGZsaWdodHNgIGRpaHVidW5na2FuIGRlbmdhbiBgd2VhdGhlcmAgdmlhIGBvcmlnaW5gIChsb2thc2kpLCBkYW4gYHllYXJgLCBgbW9udGhgLCBgZGF5YCBkYW4gYGhvdXJgLg0KDQorIGBmbGlnaHRzYCBkaWh1YnVuZ2thbiBkZW5nYW4gYHByaWNlc2AgdmlhIGB5ZWFyYCwgYG1vbnRoYCwgYGRheWAsIGBob3VyYCwgYGZsaWdodGAsIGBvcmlnaW5gLCBgZGVzdGAsIGBzY2hlZF9kZXBfdGltZWAsIGRhbiBgdGFpbG51bWAuDQoNCisgYGZsaWdodHNgIGRpaHVidW5na2FuIGRlbmdhbiBgc3BlY2lhbGRheXNgIHZpYSB2YXJpYWJlbCBgeWVhcmAsIGBtb250aGAsIGRhbiBgZGF5YC4NCg0KQmVyaWt1dCBmdW5nc2kgdW50dWsgbWVyZ2Uvam9pbiBtZW5nZ3VuYWthbiBwYWNrYWdlIGBkcGx5cmAgZGFuIHBlcmJhbmRpbmdhbm55YSBkZW5nYW4gU1FMLg0KDQp8ZHBseXJ8U1FMfA0KfC0tLXwtLS0tLXwNCnxgaW5uZXJfam9pbih4LCB5LCBieSA9IGMoImEiID0gImIiKWAgfGBTRUxFQ1QgKiBGUk9NIHggSU5ORVIgSk9JTiB5IE9OIHguYSA9IHkuYmB8DQp8YGxlZnRfam9pbih4LCB5LCBieSA9IGMoImEiID0gImIiKSlgIHxgU0VMRUNUICogRlJPTSB4IExFRlQgT1VURVIgSk9JTiB5IE9OIHguYSA9IHkuYmB8DQp8YHJpZ2h0X2pvaW4oeCwgeSwgYnkgPSBjKCJhIiA9ICJiIikpYCB8YFNFTEVDVCAqIEZST00geCBSSUdIVCBPVVRFUiBKT0lOIHkgT04geC5hID0geS5iYHwNCnxgZnVsbF9qb2luKHgsIHksIGJ5ID0gYygiYSIgPSAiYiIpKWAgfGBTRUxFQ1QgKiBGUk9NIHggRlVMTCBPVVRFUiBKT0lOIHkgT04geC5hID0geS5iYHwNCnwgfCB8DQoNCioqQ2F0YXRhbjoqKiAiSU5ORVIiIGRhbiAiT1VURVIiIHBhZGEgU1FMIGFkYWxhaCBvcHNpb25hbCwgZGFuIGxlYmloIHNlcmluZyB0aWRhayBkaXR1bGlza2FuLg0KDQpCZXJpa3V0IGlsdXN0cmFzaSB1bnR1ayBtZW5qZWxhc2thbiBtZXJnZS9qb2luLg0KDQpNaXNhbGthbiBhZGEgZHVhIGJ1YWggdGFiZWwsIGB4YCBkYW4gYHlgIHlhbmcgbWFzaW5nLW1hc2luZyBtZW1wdW55YWkgMiB2YXJpYWJlbCBzZXBlcnRpIHBhZGEgZ2FtYmFyIGRpIGJhd2FoIGluaS4gVmFyaWFiZWwgcGVydGFtYSBhZGFsYWggKiprZXkqKiBkYW4gdmFyaWFiZWwga2VkdWEgYWRhbGFoICoqdmFsKiouIA0KDQo8cCBhbGlnbj0iY2VudGVyIj4NCiAgICA8aW1nIHNyYz0iam9pbi1zZXR1cC5wbmciIGFsdD0idGFiZWwgaWx1c3RyYXNpIj4NCiAgICA8YnIvPg0KICAgIDxici8+DQogICAgPGVtPlRhYmVsIHggZGFuIHk8L2VtPg0KPC9wPg0KDQpgYGB7cn0NCnggPC0gZGF0YS5mcmFtZShrZXkgPSBjKDEsIDIsIDMpLCB2YWwgPSBjKCJ4MSIsICJ4MiIsICJ4MyIpKQ0KeSA8LSBkYXRhLmZyYW1lKGtleSA9IGMoMSwgMiwgNCksIHZhbCA9IGMoInkxIiwgInkyIiwgInkzIikpDQpgYGANCg0KDQojIyMgSW5uZXIgSm9pbg0KDQpLZXRpa2EgbWVsYWt1a2FuIHByb3NlcyBgaW5uZXJfam9pbmAgbWFrYSBha2FuIGRpYW1iaWwgbmlsYWkgeWFuZyBzYW1hIGRhcmkgKiprZXkqKiB5YW5nIGRpZ3VuYWthbiBkYXJpIGtlZHVhIHRhYmVsIHRlcnNlYnV0LiBQYWRhIGlsdXN0cmFzaSBkaSBiYXdhaCBpbmksIGRpZ3VuYWthbiBgaW5uZXJfam9pbmAgdW50dWsgbWVuZ2dhYnVuZ2thbiB0YWJlbCBgeGAgZGFuIGB5YC4gRGFyaSBrZWR1YSB0YWJlbCB0ZXJzZWJ1dCwgbmlsYWkgKiprZXkqKiB5YW5nIGFkYSBkaSB0YWJlbCBgeGAgZGFuIGB5YCBhZGFsYWggMSBkYW4gMi4gTWFrYSBoYXNpbG55YSBhZGFsYWggZGlhbWJpbCBiYXJpcyBkYXRhIHlhbmcgc2FtYSBkaSBrZWR1YSB0YWJlbCB0ZXJzZWJ1dCwgeWFpdHUgbmlsYWkgKiprZXkqKiAxIGRhbiAyLg0KDQpgYGB7cn0NCnggJT4lIA0KICBpbm5lcl9qb2luKHksIGJ5ID0gImtleSIpDQpgYGANCg0KSmlrYSBhZGEgdmFyaWFiZWwgbGFpbiBzZWxhaW4ga2V5IHlhbmcgbmFtYW55YSBzYW1hLCBtYWthIGRpYmVsYWthbmcgbWFzaW5nLW1hc2luZyBuYW1hIHZhcmlhYmVsIHRlcnNlYnV0IGFrYW4gZGl0YW1iYWhrYW4gYHN1ZmZpeGAuIGBzdWZmaXhgIHNlY2FyYSBkZWZhdWx0IGFkYWxhaCBgc3VmZml4ID0gYygiLngiLCAiLnkiKWAuIEFydGlueWEga2FyZW5hIGRpIG1hc2luZy1tYXNpbmcgdGFiZWwgYWRhIHZhcmlhYmVsIHlhbmcgc2FtYSBkYW4gdmFyaWFiZWwgaW5pIGJ1a2FuIHNlYnVhaCAqKmtleSoqIHBhZGEgc2FhdCBqb2luLCB5YWl0dSBgdmFsYCwgbWFrYSBzZXRlbGFoIHByb3NlcyBqb2luIG5hbWEgYHZhbGAgYWthbiBkaWdhbnRpIG1lbmphZGkgYHZhbC54YCB1bnR1ayB2YXJpYWJlbCB5YW5nIGJlcmFzYWwgZGFyaSB0YWJlbCBgeGAgZGFuIGB2YWwueWAgeWFuZyBiZXJhc2FsIGRhcmkgdGFiZWwgYHlgLg0KDQpKaWthIGluZ2luIG1lbmdnYW50aSBgc3VmZml4YCwgQW5kYSBkYXBhdCBtZW5nZ3VuYWthbiBhcmd1bWVuIGBzdWZmaXhgIHBhZGEgZnVuZ3NpIGpvaW4uDQoNCmBgYHtyfQ0KeCAlPiUgDQogIGlubmVyX2pvaW4oeSwgYnkgPSAia2V5Iiwgc3VmZml4ID0gYygiX3giLCAiX3kiKSkNCmBgYA0KDQo8cCBhbGlnbj0iY2VudGVyIj4NCiAgICA8aW1nIHNyYz0iam9pbi1pbm5lci5wbmciIGhlaWdodD0iNzAlIiB3aWR0aD0iNzAlIiBhbHQ9IklsdXN0cmFzaSBJbm5lciBKb2luIj4NCiAgICA8YnIvPg0KICAgIDxici8+DQogICAgPGVtPklsdXN0cmFzaSBJbm5lciBKb2luPC9lbT4NCjwvcD4NCg0KYGBge3J9DQp0YmwxIDwtIGZsaWdodHMgJT4lDQogICAgaW5uZXJfam9pbih3ZWF0aGVyLCBieSA9IGMoInllYXIiLCAibW9udGgiLCAiZGF5IiwgImhvdXIiLCAib3JpZ2luIiwgInRpbWVfaG91ciIpKQ0KZ2xpbXBzZSh0YmwxKQ0KaGVhZCh0YmwxKQ0KYGBgDQoNCkthcmVuYSBzZW11YSB2YXJpYWJlbCB5YW5nIG5hbWFueWEgc2FtYSBkaSBkYXRhIGZyYW1lIGBmbGlnaHRzYCBkYW4gYHdlYXRoZXJgIGRpZ3VuYWthbiBzZWJhZ2FpICoqa2V5KiogbWFrYSB0aWRhayBhZGEgdmFyaWFiZWwgeWFuZyBkaXRhbWJhaGthbiBgc3VmZml4YC4NCg0KIyMjIExlZnQsIFJpZ2h0IGRhbiBGdWxsIE91dGVyIEpvaW4NCg0KUGVyaGF0aWthbiBpbHVzdHJhc2kgdW50dWsgbGVmdCwgcmlnaHQsIGRhbiBmdWxsIG91dGVyIGpvaW4uDQoNCjxwIGFsaWduPSJjZW50ZXIiPg0KICAgIDxpbWcgc3JjPSJqb2luLW91dGVyLnBuZyIgaGVpZ2h0PSI3MCUiIHdpZHRoPSI3MCUiIGFsdD0iSWx1c3RyYXNpIElubmVyIEpvaW4iPg0KICAgIDxici8+DQogICAgPGJyLz4NCiAgICA8ZW0+SWx1c3RyYXNpIExlZnQsIFJpZ2h0IGRhbiBGdWxsIE91dGVyIEpvaW48L2VtPg0KPC9wPg0KDQpQYWRhIGtlc2VtcGF0YW4gaW5pIGhhbnlhIGFrYW4gZGliYWhhcyBtZW5nZW5haSBsZWZ0IGpvaW4uIFBhZGEgZGFzYXJueWEgYGxlZnRfam9pbigpYCBtZW5nYW1iaWwgc2VtdWEgYmFyaXMgeWFuZyBhZGEgZGkgdGFiZWwgc2ViZWxhaCBraXJpIChMSFMpLCBkYWxhbSBpbHVzdHJhc2kgZGkgYXRhcyBhZGFsYWggdGFiZWwgYHhgLCBkYW4gbWVuY2FyaSBuaWxhaSB5YW5nIGFkYSBwYXNhbmdhbm55YSBkaSB0YWJlbCBzZWJlbGFoIGthbmFuIChSSFMpLCB5YWl0dSB0YWJlbCBgeWAuDQoNCkppa2EgbmlsYWkgZGFyaSB2YXJpYWJlbCAqKmtleSoqIGRpIHRhYmVsIGB4YCB0aWRhayBhZGEgcGFzYW5nYW5ueWEgZGkgdGFiZWwgYHlgIG1ha2EgbmlsYWkgKip2YWwqKiB1bnR1ayBiYXJpcyB0ZXJzZWJ1dCBha2FuIG1lbmphZGkgKm1pc3NpbmcgdmFsdWUqIGF0YXUgTkEuDQoNCmBgYHtyfQ0KdGJsMiA8LSBmbGlnaHRzICU+JQ0KICAgIGxlZnRfam9pbihhaXJwb3J0cywgYnkgPSBjKCJvcmlnaW4iID0gImZhYSIpKSAlPiUNCiAgICBsZWZ0X2pvaW4oYWlycG9ydHMsIGJ5ID0gYygiZGVzdCIgPSAiZmFhIiksIHN1ZmZpeCA9IGMoIl9vcmlnaW4iLCAiX2Rlc3QiKSkNCmdsaW1wc2UodGJsMikNCmhlYWQodGJsMikNCmBgYA0KDQpKaWthIGRpcGVyaGF0aWthbiwgYWRhIGJlYmVyYXBhIHZhcmlhYmVsIGRhcmkgYHRibDJgIHlhbmcgbmFtYW55YSBtZW5nZ3VuYWthbiBgX29yaWdpbmAgZGFuIGBfZGVzdGAuIEhhbCBpbmkgbWVudW5qdWtrYW4gYWRhIHZhcmlhYmVsIHlhbmcgbmFtYW55YSBzYW1hIGRhcmkgaGFzaWwgYGxlZnRfam9pbigpYCBwZXJ0YW1hIGRhbiBrZWR1YSwgeWFpdHUgbmFtYSB2YXJpYWJlbCBkYXJpIGRhdGEgZnJhbWUgYGFpcnBvcnRzYC4gS2FyZW5hIGR1YSBrYWxpIGpvaW4gbWFrYSBha2FuIGFkYSBuYW1hIHZhcmlhYmVsIHlhbmcgc2FtYSBzZWhpbmdnYSBuYW1hbnlhIGRpdGFtYmFoa2FuIGBzdWZmaXhgLg0KDQojIExhdGloYW4NCkJ1YXRsYWggYW5hbGlzaXMgZGF0YSB1bnR1ayBrZWJ1dHVoYW4gYmVyaWt1dCENCg0KMS4gUGVuZXJiYW5nYW4gZGVuZ2FuIGtldGVybGFtYmF0YW4ga2VkYXRhbmdhbm55YSBkdWEgamFtIGF0YXUgbGViaWguDQoyLiBQZXJiYW5kaW5nYW4gcGVuZXJiYW5nYW4ga2UgSG91c3RvbiAoYElBSGAgYXRhdSBgSE9VYCkgYmVyZGFzYXJrYW4gYmFuZGFyYSBhc2FsbnlhIHBlciBidWxhbi4gDQogIEhpbnQ6IEFuZGEgZGFwYXQgZ3VuYWthbiBvcGVyYXRvciBgJWluJWAuIA0KMy4gUGVuZXJiYW5nYW4gZGlvcGVyYXNpa2FuIG9sZWggVW5pdGVkLCBBbWVyaWNhbiwgYXRhdSBEZWx0YS4gDQogIEhpbnQ6IEFuZGEgZGFwYXQgZ3VuYWthbiBvcGVyYXRvciBgJWluJWAuDQo0LiBQZW5lcmJhbmdhbiBkaSBtdXNpbSBwYW5hcyAoSnVseSwgQXVndXN0LCBhbmQgU2VwdGVtYmVyKS4gDQogIEhpbnQ6IEFuZGEgZGFwYXQgZ3VuYWthbiBmdW5nc2kgYGJldHdlZW4oKWAuDQo1LiBLZWRhdGFuZ2FuIHlhbmcgdGVybGFtYmF0IGRhdGFuZyBsZWJpaCBkYXJpIGR1YSBqYW0sIHRldGFwaSB0aWRhayBiZXJhbmdrYXQgdGVybGFtYmF0Lg0KNi4gUGVuZXJiYW5nYW4geWFuZyAqZGVsYXkqIHBhbGluZyB0aWRhayBzYXR1IGphbSwgdGV0YXBpIHRlcmJhbmcgbGViaWggZGFyaSAzMCBtZW5pdC4NCjcuIFBlbmVyYmFuZ2FuIHlhbmcgYmVyYW5na2F0IGFudGFyYSB0ZW5nYWggbWFsYW0gZGFuIGphbSA2IHBhZ2kgKCppbmNsdXNpdmUqKS4NCjguIEJlcmFwYSBiYW55YWsgcGVuZXJiYW5nYW4geWFuZyBuaWxhaSBgdGFpbG51bWAtbnlhIG1pc3Npbmc/IEFwYSBtYWtzdWQgZGFyaSBiYXJpcy1iYXJpcyBkYXRhIGluaT8NCjkuIEJlcmFwYSBiYW55YWsgcGVuZXJiYW5nYW4geWFuZyBuaWxhaSBgZGVwX3RpbWVgLW55YSBtaXNzaW5nPyBWYXJpYWJsZSBhcGFsYWdpIHNlbGFpbiBpdHUgeWFuZyBtaXNzaW5nPyBBcGEgbWFrc3VkIGRhcmkgYmFyaXMtYmFyaXMgZGF0YSBpbmk/DQoxMC4gQmFnYWltYW5hIGh1YnVuZ2FuIHBlbmVyYmFuZ2FuICpkZWxheSogZGlrYXJlbmFrYW4gY3VhY2EgZGFuIGtvbmRpc2kgcGVzYXdhdD8NCg0KPHAgYWxpZ249ImNlbnRlciI+DQo8c3Ryb25nPi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIFNlbW9nYSBCZXJtYW5mYWF0IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tPC9zdHJvbmc+DQo8L3A+DQoNCioqQ29udGFjdCBtZToqKg0KKipBZXAqKiBIaWRheWF0dWxvaA0KDQpFbWFpbDogPGFlcGhpZGF5YXR1bG9oLm1haWxAZ21haWwuY29tPg0KR2l0SHViOiA8aHR0cHM6Ly9naXRodWIuY29tL2FlcGhpZGF5YXR1bG9oPg0KDQoNCg0K