Larry D. Moore, CC BY-SA 4.0 https://creativecommons.org/licenses/by-sa/4.0, via Wikimedia Commons

Background

The following study was conducted in partial fulfillment of the requirements of the Google Data Analytics Career Certificate. All descriptions from this case study are taken from the Google Coursera course associated with this case study. I do not claim authorship of the cases study description, and I have included a link to the PDF here The case study is hypothetically conducted for Bellabeat, a women’s fitness tracker company.This case study involves preparing, cleaning, visualizing, and analyzing data collected from Fitbit users “in order to gain insight into how consumers use non-Bellabeat smart devices.” The data used in this case study comes from the following source: kaggle.com

Guiding Questions and Deliverables

Business Task and Guiding Questions

As was previously mentioned, the business task is to analyzing data collected from Fitbit users “in order to gain insight into how consumers use non-Bellabeat smart devices.” Specifically, I need to discover trends in smart device usage and use those trends to inform Bellabeat’s marketing strategy. I will need to chose one Bellabeat product to apply my insights to the end of the analysis.

The guidelines for the case study summarize my task through the following questions:

  • What are some trends in smart device usage?
  • How could these trends apply to Bellabeat customers?
  • How could these trends help influence Bellabeat marketing strategy?

Deliverables

The guidelines for the case study require me to accomplish the following deliverables:

  • A clear summary of the business task

  • A description of all data sources used

  • Documentation of any cleaning or manipulation of data

  • A summary of my analysis

  • Supporting visualizations and key findings

  • My top high-level content recommendations based on my analysis

The Data

The Date Source

The data packet contained several CSV files tracking Fitbit users’ data. The licensing and DOI of the data set are found here. The description of the data in Kaggle states:

“This data set generated by respondents to a distributed survey via Amazon Mechanical Turk between 03.12.2016-05.12.2016. Thirty eligible Fitbit users consented to the submission of personal tracker data, including minute-level output for physical activity, heart rate, and sleep monitoring.” kaggle.com

Limitations

The limitations of the data are immediately apparent. According to Bellabeat’s website, they have 1.2 million devices synced, but this data set only contains the data from 30 Fitbit users. Even if every Bellabeat user owned synced devices so that the total number of customers was 600,000, I would need a sample size of 273 to have a confidence level of 90% and a margin of error of 5%. In addition to the risk of sampling bias, it should be noted that the data is dated. Given the changes brought by a global pandemic, I would say that the data is nearly irrelevant today because of its age, small size, and relatively short time frame. Another concern is that nothing is known about the demographics of the participants, and this makes it difficult to draw confident conclusions for a company that specializes in women’s health products. I also do not know what survey questions or methods Amazon Mechanical Turk used to collect the data or if those methods could have risked sampling bias. For the sake of the case study, I will overlook these concerns, but I would not present any recommendations to stakeholders based exclusively off this data in a real-world scenario.

The Cleaning Process

Several of the tables contained the data, but in different arrangements. I decided to explore the dailyActivity_merged, sleepDay_merged, weightLogInfo_merged, and the heartrate_seconds_merged CSV files so that I could get the maximum number of variables to work with. I renamed them as daily_activity_merged_raw, sleep_day_merged_raw, weight_log_info_merged_raw, and heart-rate_seconds_merged_raw, respectively.

To begin cleaning the data, I will first install the following packages:

install.packages("tidyverse") 
Installing package into ‘/cloud/lib/x86_64-pc-linux-gnu-library/4.1’
(as ‘lib’ is unspecified)
trying URL 'http://package-proxy/focal/src/contrib/tidyverse_1.3.1.tar.gz'
Content type 'application/x-tar' length 423528 bytes (413 KB)
==================================================
downloaded 413 KB

* installing *binary* package ‘tidyverse’ ...
* DONE (tidyverse)

The downloaded source packages are in
    ‘/tmp/Rtmp5xfOtx/downloaded_packages’
install.packages("skimr")
Installing package into ‘/cloud/lib/x86_64-pc-linux-gnu-library/4.1’
(as ‘lib’ is unspecified)
trying URL 'http://package-proxy/focal/src/contrib/skimr_2.1.3.tar.gz'
Content type 'application/x-tar' length 1220201 bytes (1.2 MB)
==================================================
downloaded 1.2 MB

* installing *binary* package ‘skimr’ ...
* DONE (skimr)

The downloaded source packages are in
    ‘/tmp/Rtmp5xfOtx/downloaded_packages’
install.packages("janitor")
Installing package into ‘/cloud/lib/x86_64-pc-linux-gnu-library/4.1’
(as ‘lib’ is unspecified)
trying URL 'http://package-proxy/focal/src/contrib/janitor_2.1.0.tar.gz'
Content type 'application/x-tar' length 248888 bytes (243 KB)
==================================================
downloaded 243 KB

* installing *binary* package ‘janitor’ ...
* DONE (janitor)

The downloaded source packages are in
    ‘/tmp/Rtmp5xfOtx/downloaded_packages’
install.packages("here")
Installing package into ‘/cloud/lib/x86_64-pc-linux-gnu-library/4.1’
(as ‘lib’ is unspecified)
trying URL 'http://package-proxy/focal/src/contrib/here_1.0.1.tar.gz'
Content type 'application/x-tar' length 52266 bytes (51 KB)
==================================================
downloaded 51 KB

* installing *binary* package ‘here’ ...
* DONE (here)

The downloaded source packages are in
    ‘/tmp/Rtmp5xfOtx/downloaded_packages’

Now, I will load the packages:

library("tidyverse")
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
── Attaching packages ────────────────────────────────── tidyverse 1.3.1 ──
✓ ggplot2 3.3.5     ✓ purrr   0.3.4
✓ tibble  3.1.6     ✓ dplyr   1.0.7
✓ tidyr   1.1.4     ✓ stringr 1.4.0
✓ readr   2.1.0     ✓ forcats 0.5.1
── Conflicts ───────────────────────────────────── tidyverse_conflicts() ──
x dplyr::filter() masks stats::filter()
x dplyr::lag()    masks stats::lag()
library("skimr")
library("janitor")

Attaching package: ‘janitor’

The following objects are masked from ‘package:stats’:

    chisq.test, fisher.test
library("here")
here() starts at /cloud/project
library("lubridate")

Attaching package: ‘lubridate’

The following objects are masked from ‘package:base’:

    date, intersect, setdiff, union
library("ggplot2")
library("dplyr")

Next, I will load the CSV files for analysis and assign variables:

activity_df <- read_csv("daily_activity_merged_raw.csv")
Rows: 940 Columns: 15
── Column specification ───────────────────────────────────────────────────
Delimiter: ","
chr  (1): ActivityDate
dbl (14): Id, TotalSteps, TotalDistance, TrackerDistance, LoggedActivit...

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
sleep_df <- read_csv("sleep_day_merged_raw.csv")
Rows: 413 Columns: 5
── Column specification ───────────────────────────────────────────────────
Delimiter: ","
chr (1): SleepDay
dbl (4): Id, TotalSleepRecords, TotalMinutesAsleep, TotalTimeInBed

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
weight_df <- read_csv("weight_log_info_merged_raw.csv")
Rows: 67 Columns: 8
── Column specification ───────────────────────────────────────────────────
Delimiter: ","
chr (1): Date
dbl (6): Id, WeightKg, WeightPounds, Fat, BMI, LogId
lgl (1): IsManualReport

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
heart_rate_df <- read_csv("heart_rate_seconds_merged_raw.csv")
Rows: 2483658 Columns: 3
── Column specification ───────────────────────────────────────────────────
Delimiter: ","
chr (1): Time
dbl (2): Id, Value

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

It’s time to take a peak at the contents of the data frames:

head(activity_df)
head(sleep_df)
head(weight_df)
head(heart_rate_df)

Let’s get some information on the size of the data frames using skimr:

skim_without_charts(activity_df)
── Data Summary ────────────────────────
                           Values     
Name                       activity_df
Number of rows             940        
Number of columns          15         
_______________________               
Column type frequency:                
  character                1          
  numeric                  14         
________________________              
Group variables            None       

── Variable type: character ───────────────────────────────────────────────
  skim_variable n_missing complete_rate   min   max empty n_unique
1 ActivityDate          0             1     8     9     0       31
  whitespace
1          0

── Variable type: numeric ─────────────────────────────────────────────────
   skim_variable            n_missing complete_rate    mean      sd
 1 Id                               0             1 4.86e+9 2.42e+9
 2 TotalSteps                       0             1 7.64e+3 5.09e+3
 3 TotalDistance                    0             1 5.49e+0 3.92e+0
 4 TrackerDistance                  0             1 5.48e+0 3.91e+0
 5 LoggedActivitiesDistance         0             1 1.08e-1 6.20e-1
 6 VeryActiveDistance               0             1 1.50e+0 2.66e+0
 7 ModeratelyActiveDistance         0             1 5.68e-1 8.84e-1
 8 LightActiveDistance              0             1 3.34e+0 2.04e+0
 9 SedentaryActiveDistance          0             1 1.61e-3 7.35e-3
10 VeryActiveMinutes                0             1 2.12e+1 3.28e+1
11 FairlyActiveMinutes              0             1 1.36e+1 2.00e+1
12 LightlyActiveMinutes             0             1 1.93e+2 1.09e+2
13 SedentaryMinutes                 0             1 9.91e+2 3.01e+2
14 Calories                         0             1 2.30e+3 7.18e+2
           p0           p25     p50     p75    p100
 1 1503960366 2320127002    4.45e+9 6.96e+9 8.88e+9
 2          0       3790.   7.41e+3 1.07e+4 3.60e+4
 3          0          2.62 5.24e+0 7.71e+0 2.80e+1
 4          0          2.62 5.24e+0 7.71e+0 2.80e+1
 5          0          0    0       0       4.94e+0
 6          0          0    2.10e-1 2.05e+0 2.19e+1
 7          0          0    2.40e-1 8.00e-1 6.48e+0
 8          0          1.95 3.36e+0 4.78e+0 1.07e+1
 9          0          0    0       0       1.10e-1
10          0          0    4   e+0 3.2 e+1 2.1 e+2
11          0          0    6   e+0 1.9 e+1 1.43e+2
12          0        127    1.99e+2 2.64e+2 5.18e+2
13          0        730.   1.06e+3 1.23e+3 1.44e+3
14          0       1828.   2.13e+3 2.79e+3 4.9 e+3
skim_without_charts(sleep_df)
── Data Summary ────────────────────────
                           Values  
Name                       sleep_df
Number of rows             413     
Number of columns          5       
_______________________            
Column type frequency:             
  character                1       
  numeric                  4       
________________________           
Group variables            None    

── Variable type: character ───────────────────────────────────────────────
  skim_variable n_missing complete_rate   min   max empty n_unique
1 SleepDay              0             1    20    21     0       31
  whitespace
1          0

── Variable type: numeric ─────────────────────────────────────────────────
  skim_variable      n_missing complete_rate          mean      sd
1 Id                         0             1 5000979403.   2.06e+9
2 TotalSleepRecords          0             1          1.12 3.46e-1
3 TotalMinutesAsleep         0             1        419.   1.18e+2
4 TotalTimeInBed             0             1        459.   1.27e+2
          p0        p25        p50        p75       p100
1 1503960366 3977333714 4702921684 6962181067 8792009665
2          1          1          1          1          3
3         58        361        433        490        796
4         61        403        463        526        961
skim_without_charts(weight_df)
── Data Summary ────────────────────────
                           Values   
Name                       weight_df
Number of rows             67       
Number of columns          8        
_______________________             
Column type frequency:              
  character                1        
  logical                  1        
  numeric                  6        
________________________            
Group variables            None     

── Variable type: character ───────────────────────────────────────────────
  skim_variable n_missing complete_rate   min   max empty n_unique
1 Date                  0             1    19    21     0       56
  whitespace
1          0

── Variable type: logical ─────────────────────────────────────────────────
  skim_variable  n_missing complete_rate  mean count           
1 IsManualReport         0             1 0.612 TRU: 41, FAL: 26

── Variable type: numeric ─────────────────────────────────────────────────
  skim_variable n_missing complete_rate    mean            sd      p0
1 Id                    0        1      7.01e 9 1950321944.   1.50e 9
2 WeightKg              0        1      7.20e 1         13.9  5.26e 1
3 WeightPounds          0        1      1.59e 2         30.7  1.16e 2
4 Fat                  65        0.0299 2.35e 1          2.12 2.2 e 1
5 BMI                   0        1      2.52e 1          3.07 2.15e 1
6 LogId                 0        1      1.46e12  782994784.   1.46e12
      p25     p50     p75    p100
1 6.96e 9 6.96e 9 8.88e 9 8.88e 9
2 6.14e 1 6.25e 1 8.50e 1 1.34e 2
3 1.35e 2 1.38e 2 1.88e 2 2.94e 2
4 2.28e 1 2.35e 1 2.42e 1 2.5 e 1
5 2.40e 1 2.44e 1 2.56e 1 4.75e 1
6 1.46e12 1.46e12 1.46e12 1.46e12
skim_without_charts(heart_rate_df)
── Data Summary ────────────────────────
                           Values       
Name                       heart_rate_df
Number of rows             2483658      
Number of columns          3            
_______________________                 
Column type frequency:                  
  character                1            
  numeric                  2            
________________________                
Group variables            None         

── Variable type: character ───────────────────────────────────────────────
  skim_variable n_missing complete_rate   min   max empty n_unique
1 Time                  0             1    19    21     0   961274
  whitespace
1          0

── Variable type: numeric ─────────────────────────────────────────────────
  skim_variable n_missing complete_rate         mean           sd
1 Id                    0             1 5513764629.  1950223761. 
2 Value                 0             1         77.3         19.4
          p0        p25        p50        p75       p100
1 2022484408 4388161847 5553957443 6962181067 8877689391
2         36         63         73         88        203

Preliminary Observations

Some observations to note at this point:

  • The data sets are in the long format.

  • These data sets have some common column names, so they can be combined.

  • These data sets have different numbers of rows.

  • The IDs are being treated as numbers, not strings.

  • The measurement for distance is unknown, but we will assume it is in km. I am basing this on the following discussion although I would confirm this with the data collection agency if I was actually employed by Bellabeat.

  • The names of the columns need to be formatted into snake case (my personal taste).

  • The weight_df has a relatively small number of rows and is missing a lot of data in the fat column.

  • The heart_rate_df data frame has the largest number of rows of all the data frames.

Cleaning Tasks for activity_df

Here is a summary of the cleaning tasks I wish to perform on this data frame:

  • Convert column names to snake case for easy reading

  • Convert IDs to strings so they can be counted

  • Remove duplicate and empty rows and columns

  • Remove columns with a single value

  • Ensure that the total distance is the sum of the subcategories of distance

  • Create a total column for active minutes

  • Return the day of the week as a string for each date

Here is the code I used to clean the data:

activity_cleaned <- activity_df %>% 
  clean_names() %>% #convert to snake case
  distinct() %>% #remove duplicates
  remove_empty(c("rows", "cols")) %>% #remove empty rows or columns
  remove_constant() %>% #remove columns with a single value
  mutate(total_distance_new = #make sure total distance is calculated properly
           (very_active_distance + 
                                 moderately_active_distance + 
                                 light_active_distance + 
                                 sedentary_active_distance)) %>% 
  mutate(total_active_minutes = 
           very_active_minutes + fairly_active_minutes + #add total active minutes column
           lightly_active_minutes + sedentary_minutes) %>% 
  mutate(date_new = as.Date(mdy(activity_date))) %>% #standardize date
  mutate(day = wday(date_new, TRUE)) %>% #convert date to weekday string
  mutate(id = as.character(id)) %>% #convert id to string
  select(id, date_new, day, total_steps, total_distance_new, total_active_minutes, sedentary_minutes,
         lightly_active_minutes, fairly_active_minutes, very_active_minutes,
         calories) 
Cleaning Tasks for sleep_df

Here is a summary of the cleaning tasks I wish to perform on this data frame:

  • Perform a similar cleaning process used on activity_df

  • Create a lying_awake column that will show how much time participants spend awake in bed

  • Create a column that will round the total sleeping minutes to integer hours for better grouping

Here is the code I used to clean the data:

sleep_cleaned <- sleep_df %>% 
  clean_names() %>% #convert to snake case
  distinct() %>% #remove duplicates
  remove_empty(c("rows", "cols")) %>% #remove empty rows or columns
  remove_constant() %>% #remove columns with a single value
  mutate(date_new = as.Date(mdy_hms(sleep_day))) %>% #standardize date
  mutate(day = wday(date_new, TRUE)) %>% #convert date to weekday string
  mutate(id = as.character(id)) %>% # convert id to string
  mutate(lying_awake = total_time_in_bed - total_minutes_asleep) %>% #how much time is spent awake in bed?
  mutate(hours_asleep = total_minutes_asleep %/% 60) %>% #round minutes to hours
  select(id, date_new, day, total_sleep_records, total_minutes_asleep, hours_asleep, lying_awake, total_time_in_bed)
Cleaning Tasks for weight_df

Here is a summary of the cleaning tasks I wish to perform on this data frame:

  • Perform a similar cleaning process used on activity_df

  • Limit measurements in the metric system only

  • Use the BMI formula to use the weight to calculate a height column in meters in case this reveals anything

Here is the code I used to clean the data:

weight_cleaned <- weight_df %>% 
  clean_names() %>% #convert to snake case
  distinct() %>% #remove duplicates
  remove_empty(c("rows", "cols")) %>% #remove empty rows or columns
  remove_constant() %>%   #remove columns with a single value
  mutate(height_m = (weight_kg / bmi ) ^ 0.5) %>% # use weight and bmi to calculate height
  mutate(date_new = as.Date(mdy_hms(date))) %>% 
  mutate(day = wday(date_new, TRUE)) %>% # convert date to weekday string
  mutate(id = as.character(id)) %>% # convert id to string
  select(log_id, id, date_new, day, weight_kg, height_m, bmi, is_manual_report) 
Cleaning Tasks for heart_rate_df

Here is a summary of the cleaning tasks I wish to perform on this data frame:

  • Perform a similar cleaning process used on activity_df

  • Rename the value column to make the data easier to understand at glance

Here is the code I used to clean the data:

heart_rate_cleaned <- heart_rate_df %>% 
  clean_names() %>% #convert to snake case
  distinct() %>% #remove duplicates
  remove_empty(c("rows", "cols")) %>% #remove empty rows or columns
  remove_constant() %>%  #remove columns with a single value
  rename("heart_rate" = value) %>%  #clarify value of this column
  mutate(id = as.character(id)) %>% # convert id to string
  select(everything())
Combining Data Frames

After cleaning the previous data frames, I am interested in combining some of them for ease of analysis. I will use an inner join to merge the daily activity and sleep data frames:

activity_sleep_combined <- inner_join(activity_cleaned, sleep_cleaned) 
Joining, by = c("id", "date_new", "day")
Confirming the Cleaning Process

I will now confirm that the frames have been cleaned properly using skimr:

skim_without_charts(activity_cleaned)
── Data Summary ────────────────────────
                           Values          
Name                       activity_cleaned
Number of rows             940             
Number of columns          11              
_______________________                    
Column type frequency:                     
  character                1               
  Date                     1               
  factor                   1               
  numeric                  8               
________________________                   
Group variables            None            

── Variable type: character ───────────────────────────────────────────────
  skim_variable n_missing complete_rate   min   max empty n_unique
1 id                    0             1    10    10     0       33
  whitespace
1          0

── Variable type: Date ────────────────────────────────────────────────────
  skim_variable n_missing complete_rate min        max        median    
1 date_new              0             1 2016-04-12 2016-05-12 2016-04-26
  n_unique
1       31

── Variable type: factor ──────────────────────────────────────────────────
  skim_variable n_missing complete_rate ordered n_unique
1 day                   0             1 TRUE           7
  top_counts                            
1 Tue: 152, Wed: 150, Thu: 147, Fri: 126

── Variable type: numeric ─────────────────────────────────────────────────
  skim_variable          n_missing complete_rate    mean      sd    p0
1 total_steps                    0             1 7638.   5087.       0
2 total_distance_new             0             1    5.41    3.94     0
3 total_active_minutes           0             1 1219.    266.       2
4 sedentary_minutes              0             1  991.    301.       0
5 lightly_active_minutes         0             1  193.    109.       0
6 fairly_active_minutes          0             1   13.6    20.0      0
7 very_active_minutes            0             1   21.2    32.8      0
8 calories                       0             1 2304.    718.       0
      p25     p50      p75    p100
1 3790.   7406.   10727    36019  
2    2.54    5.17     7.64    28.0
3  990.   1440     1440     1440  
4  730.   1058.    1230.    1440  
5  127     199      264      518  
6    0       6       19      143  
7    0       4       32      210  
8 1828.   2134     2793.    4900  
skim_without_charts(sleep_cleaned)
── Data Summary ────────────────────────
                           Values       
Name                       sleep_cleaned
Number of rows             410          
Number of columns          8            
_______________________                 
Column type frequency:                  
  character                1            
  Date                     1            
  factor                   1            
  numeric                  5            
________________________                
Group variables            None         

── Variable type: character ───────────────────────────────────────────────
  skim_variable n_missing complete_rate   min   max empty n_unique
1 id                    0             1    10    10     0       24
  whitespace
1          0

── Variable type: Date ────────────────────────────────────────────────────
  skim_variable n_missing complete_rate min        max        median    
1 date_new              0             1 2016-04-12 2016-05-12 2016-04-27
  n_unique
1       31

── Variable type: factor ──────────────────────────────────────────────────
  skim_variable n_missing complete_rate ordered n_unique
1 day                   0             1 TRUE           7
  top_counts                        
1 Wed: 66, Tue: 65, Thu: 64, Fri: 57

── Variable type: numeric ─────────────────────────────────────────────────
  skim_variable        n_missing complete_rate   mean      sd    p0   p25
1 total_sleep_records          0             1   1.12   0.347     1    1 
2 total_minutes_asleep         0             1 419.   119.       58  361 
3 hours_asleep                 0             1   6.48   2.00      0    6 
4 lying_awake                  0             1  39.3   46.7       0   17 
5 total_time_in_bed            0             1 458.   127.       61  404.
    p50   p75  p100
1   1       1     3
2 432.    490   796
3   7       8    13
4  25.5    40   371
5 463     526   961
skim_without_charts(weight_cleaned)
── Data Summary ────────────────────────
                           Values        
Name                       weight_cleaned
Number of rows             67            
Number of columns          8             
_______________________                  
Column type frequency:                   
  character                1             
  Date                     1             
  factor                   1             
  logical                  1             
  numeric                  4             
________________________                 
Group variables            None          

── Variable type: character ───────────────────────────────────────────────
  skim_variable n_missing complete_rate   min   max empty n_unique
1 id                    0             1    10    10     0        8
  whitespace
1          0

── Variable type: Date ────────────────────────────────────────────────────
  skim_variable n_missing complete_rate min        max        median    
1 date_new              0             1 2016-04-12 2016-05-12 2016-04-27
  n_unique
1       31

── Variable type: factor ──────────────────────────────────────────────────
  skim_variable n_missing complete_rate ordered n_unique
1 day                   0             1 TRUE           7
  top_counts                        
1 Mon: 12, Wed: 12, Thu: 11, Sun: 10

── Variable type: logical ─────────────────────────────────────────────────
  skim_variable    n_missing complete_rate  mean count           
1 is_manual_report         0             1 0.612 TRU: 41, FAL: 26

── Variable type: numeric ─────────────────────────────────────────────────
  skim_variable n_missing complete_rate    mean            sd      p0
1 log_id                0             1 1.46e12 782994784.    1.46e12
2 weight_kg             0             1 7.20e 1        13.9   5.26e 1
3 height_m              0             1 1.69e 0         0.111 1.52e 0
4 bmi                   0             1 2.52e 1         3.07  2.15e 1
      p25     p50     p75    p100
1 1.46e12 1.46e12 1.46e12 1.46e12
2 6.14e 1 6.25e 1 8.50e 1 1.34e 2
3 1.60e 0 1.60e 0 1.83e 0 1.83e 0
4 2.40e 1 2.44e 1 2.56e 1 4.75e 1
skim_without_charts(heart_rate_cleaned)
── Data Summary ────────────────────────
                           Values            
Name                       heart_rate_cleaned
Number of rows             2483658           
Number of columns          3                 
_______________________                      
Column type frequency:                       
  character                2                 
  numeric                  1                 
________________________                     
Group variables            None              

── Variable type: character ───────────────────────────────────────────────
  skim_variable n_missing complete_rate   min   max empty n_unique
1 id                    0             1    10    10     0       14
2 time                  0             1    19    21     0   961274
  whitespace
1          0
2          0

── Variable type: numeric ─────────────────────────────────────────────────
  skim_variable n_missing complete_rate  mean    sd    p0   p25   p50   p75
1 heart_rate            0             1  77.3  19.4    36    63    73    88
   p100
1   203
skim_without_charts(activity_sleep_combined)
── Data Summary ────────────────────────
                           Values                 
Name                       activity_sleep_combined
Number of rows             410                    
Number of columns          16                     
_______________________                           
Column type frequency:                            
  character                1                      
  Date                     1                      
  factor                   1                      
  numeric                  13                     
________________________                          
Group variables            None                   

── Variable type: character ───────────────────────────────────────────────
  skim_variable n_missing complete_rate   min   max empty n_unique
1 id                    0             1    10    10     0       24
  whitespace
1          0

── Variable type: Date ────────────────────────────────────────────────────
  skim_variable n_missing complete_rate min        max        median    
1 date_new              0             1 2016-04-12 2016-05-12 2016-04-27
  n_unique
1       31

── Variable type: factor ──────────────────────────────────────────────────
  skim_variable n_missing complete_rate ordered n_unique
1 day                   0             1 TRUE           7
  top_counts                        
1 Wed: 66, Tue: 65, Thu: 64, Fri: 57

── Variable type: numeric ─────────────────────────────────────────────────
   skim_variable          n_missing complete_rate    mean       sd       p0
 1 total_steps                    0             1 8515.   4157.     17     
 2 total_distance_new             0             1    5.98    3.02    0.0100
 3 total_active_minutes           0             1  972.    168.      2     
 4 sedentary_minutes              0             1  712.    166.      0     
 5 lightly_active_minutes         0             1  217.     86.7     2     
 6 fairly_active_minutes          0             1   17.9    22.4     0     
 7 very_active_minutes            0             1   25.0    36.2     0     
 8 calories                       0             1 2389.    758.    257     
 9 total_sleep_records            0             1    1.12    0.347   1     
10 total_minutes_asleep           0             1  419.    119.     58     
11 hours_asleep                   0             1    6.48    2.00    0     
12 lying_awake                    0             1   39.3    46.7     0     
13 total_time_in_bed              0             1  458.    127.     61     
       p25     p50      p75    p100
 1 5189.   8913    11370.   22770  
 2    3.58    6.25     7.92    17.5
 3  906.    983     1042     1398  
 4  631.    717      783.    1265  
 5  158     208      263      518  
 6    0      11       26.8    143  
 7    0       9       38      210  
 8 1841    2207     2920     4900  
 9    1       1        1        3  
10  361     432.     490      796  
11    6       7        8       13  
12   17      25.5     40      371  
13  404.    463      526      961  

Analyzing the Data

Observations from activity_cleaned

When looking at the information for this data frame, I was initially confused because there were 33 unique IDs. The description of the data said that there were 30 participants in this study. I visited on of the discussions on Kaggle in which one user commented that some users may be logging data with more than one device. I will assume that this is the correct explanation.

Observations from activity_cleaned

Although it is scientifically accepted that physical activity will result in burned calories, for the sake of testing our data, lets compare daily steps and calories:

ggplot(data = activity_cleaned, aes(x = calories, y = total_steps, color = total_steps)) + 
  geom_point() +  geom_smooth() + labs(title = "Steps vs. Calories", x = "Calories", y = "Total Steps", color = "Total Steps")
`geom_smooth()` using method = 'loess' and formula 'y ~ x'

From the chart, we can see a positive correlation between calories and steps. Let’s compare calories with total distance:

ggplot(data = activity_cleaned, aes(x = calories, y = total_distance_new, color = total_distance_new)) + 
  geom_point() +  geom_smooth() + labs(title = "Distance vs. Calories", x = "Calories", y = "Total Distance (Km)", color = "Km")
`geom_smooth()` using method = 'loess' and formula 'y ~ x'

Note that I am assuming that the distance is in Km per a Kaggle discussion. Since there is a positive correlation between steps and calories, and distance and calories, I want to know which days have the most steps and distances logged. We will begin with the steps. We see that the average total steps is slightly lower on the weekends:

ggplot(data = activity_cleaned) + geom_point(mapping = aes(x = day, y = total_steps, color = total_steps)) + geom_boxplot(mapping = aes(x = day, y = total_steps)) + labs(title = "Total Steps", subtitle = "Frequency Distributions by Day of the Week (4/12/16 - 5/12/16)", x = "Day", y = " Total Steps", colour = "Steps")

The same holds true for the average of total distance:

ggplot(data = activity_cleaned) + geom_point(mapping = aes(x = day, y = total_distance_new, color = total_distance_new)) + geom_boxplot(mapping = aes(x = day, y = total_distance_new)) + labs(title = "Total Distance", subtitle = "Frequency Distributions by Day of the Week (4/12/16 - 5/12/16)", x = "Day", y = " Total Distance (Km)", colour = "Km")

Although we might assume that calories burned will also be lower on the weekends, lets make sure:

ggplot(data = activity_cleaned) + geom_point(mapping = aes(x = day, y = calories, color = calories)) + geom_boxplot(mapping = aes(x = day, y = calories)) + labs(title = "Calories Burned", subtitle = "Frequency Distributions by Day of the Week (4/12/16 - 5/12/16)", x = "Day", y = " Calories", colour = "Calories")

Our hypothesis is confirmed although it should be noted that Thursday’s average seems to be slightly lower than Saturday’s.

Next, I will plot the active minutes distributions by weekday. We will consider the total active minutes by day of the week, and then we will consider the different levels of activity within these totals.

ggplot(data = activity_cleaned) + geom_point(mapping = aes(x = day, y = total_active_minutes, color = total_active_minutes), color = "blue") + geom_boxplot(mapping = aes(x = day, y = total_active_minutes)) + labs(title = "Total Active Minutes", subtitle = "Frequency Distributions by Day of the Week (4/12/16 - 5/12/16)", x = "Day", y = " Total Active Minutes")

I found that the total active minutes logged by Fitbit users tends to be fairly stable throughout the week with the exception of Thursday. The distributions for the weekends are also wider than the weekdays.

ggplot(data = activity_cleaned) + geom_point(mapping = aes(x = day, y = sedentary_minutes, color = sedentary_minutes), color = "green") + geom_boxplot(mapping = aes(x = day, y = sedentary_minutes)) + labs(title = "Sedentary Minutes", subtitle = "Frequency Distributions by Day of the Week (4/12/16 - 5/12/16)", x = "Day", y = "Sedentary Minutes")

The amount of sedentary minutes is also pretty uniform, again with the exception of Thursday. Saturday has one of the highest averages of sedentary minutes, while Thursday has one of the lowest.

I noticed is that Saturdays had the highest average and maximum of lightly active minutes while Sunday had one of the lowest.

ggplot(data = activity_cleaned) + geom_point(mapping = aes(x = day, y = lightly_active_minutes, color = lightly_active_minutes), color = "violet") + geom_boxplot(mapping = aes(x = day, y = lightly_active_minutes)) + labs(title = "Lightly Active Minutes", subtitle = "Frequency Distributions by Day of the Week (4/12/16 - 5/12/16)", x = "Day", y = "Lightly Active Minutes")

I noticed that the average number of fairly active minutes logged was lower on the weekends.

ggplot(data = activity_cleaned) + geom_point(mapping = aes(x = day, y = fairly_active_minutes, color = fairly_active_minutes), color = "purple") + geom_boxplot(mapping = aes(x = day, y = fairly_active_minutes)) + labs(title = "Fairly Active Minutes", subtitle = "Frequency Distributions by Day of the Week (4/12/16 - 5/12/16)", x = "Day", y = "Fairly Active Minutes")

The very active minutes seem to peak Mon - Wed, but if you look at the outliers, it seems that some people do their exercise on the weekends because Sunday and Saturday have a few outliers while the maximums for the weekdays are lower.

ggplot(data = activity_cleaned) + geom_point(mapping = aes(x = day, y = very_active_minutes, color = very_active_minutes), color = "orange") + geom_boxplot(mapping = aes(x = day, y = very_active_minutes)) + labs(title = "Very Active Minutes", subtitle = "Frequency Distributions by Day of the Week (4/12/16 - 5/12/16)", x = "Day", y = " Very Active Minutes")

Observations from sleep_cleaned

As with the daily activity, we can look at how sleeping hours as well as hours spent awake in bed compare between days of the week. Note that this data frame is much smaller than the activity data frame. Let’s begin with minutes asleep:

ggplot(data = sleep_cleaned) + geom_point(mapping = aes(x = day, y = total_minutes_asleep, color = total_minutes_asleep), color = "blue") + geom_boxplot(mapping = aes(x = day, y = total_minutes_asleep)) + labs(title = "Daily Sleep Averages", subtitle = "Frequency Distributions by Day of the Week (4/12/16 - 5/12/16)", x = "Day", y = "Total Minutes Asleep")

From this plot, it appears that Sunday is the most popular sleep day. Since I rounded the minutes into hours during the cleaning process, let’s see what this looks like in hours using a histogram:

ggplot(data = sleep_cleaned) + geom_col(mapping = aes(x = day, y = hours_asleep, alpha = hours_asleep), color = "purple") + labs(title = "Daily Sleep Totals", subtitle = "A Histogram of Data Logged 4/12/16 - 5/12/16", x = "Day", y = "Hours Asleep", alpha = "Hours (Rounded)")

While at first it appears that the histogram contradicts the previous graph, the fill shows that a lot of sleep logged on on weekends is longer, but more people are logging more sleep throughout the week in smaller amounts.

Since the data frame contains total minutes asleep and total time in bed, I want to see how much time people are spending in bed awake:

ggplot(data = sleep_cleaned) + geom_point(mapping = aes(x = day, y = lying_awake, color = lying_awake), color = "green") + geom_boxplot(mapping = aes(x = day, y = lying_awake)) + labs(title = "Total Time Awake in Bed", subtitle = "Frequency Distributions by Day of the Week (4/12/16 - 5/12/16)", x = "Day", y = "Waking Minutes")

We can see that the distributions are pretty similar from day to day, but Friday, Saturday, and Sunday also have some outliers. It looks like a few people logged long minutes awake in bed on the weekends. This could be the reason why the scatter plot and histogram differ appear differently at first glance. Let’s check this data with a histogram:

ggplot(data = sleep_cleaned) + geom_col(mapping = aes(x = day, y = lying_awake, alpha = lying_awake), color = "orange") + labs(title = "Waking Minutes Totals", subtitle = "A Histogram of Data Logged 4/12/16 - 5/12/16", x = "Day", y = "Waking Minutes", alpha = "Minutes")

From this we can see that a lot of people are lying awake in bed on off days while some prefer to get a lot of sleep.

There is one more way that I want to visualize this data:

ggplot(data = sleep_cleaned) + geom_col(mapping = aes(x = day, y = total_time_in_bed, fill = "red")) +
  geom_col(mapping = aes(x = day, y = lying_awake, fill = "blue"))  + labs(title = "Total Time in Bed Breakdown", subtitle = "Awake vs. Asleep 4/12/16 - 5/12/16", x = "Day", y = "Total Minutes", fill = "Minutes") + scale_fill_hue(labels = c("Waking Minutes", "Total Minutes in Bed"))

This visualization is perhaps the clearest way to understand the proportional relationship between waking minutes and total time in bed. We can see that Fri - Sun has proportionally large portions of time spent awake in bed.

Observations from weight_cleaned

The weight cleaned data frame has a very small number of rows compared to the other data frames. Instead of focusing exclusively on the numbers logged, I wanted to think about the behavior of the participants since so few of them logged their weights. One of the columns contains Booleans to the statement “is manual report.” I am curious to see how the weights compare when logged in manually to when they are logged in automatically. Let’s take a look at the following plot:

ggplot(data = weight_cleaned) + geom_violin(mapping = aes(x = is_manual_report, y = weight_kg)) + labs(title = "Manual vs. Automatic Logging of Weight", subtitle = "Why are Manually Recorded Weights Lower?", x = "Manual Log?", y = "Weight (kg)")

While I can’t make a definitive conclusion from so small a sample, I would want to explore user bias that may skew the data they enter. It appears that weight of people who enter weight manually is much lower than that of people who are logged automatically.

Observations from heart_rate_cleaned

The heart rate data frame contains heart rate information for each user by the second, and this data frame contains 2,483,658 rows. Instead of looking at daily trends, I want to focus on the high and low heart rates to try to guess how people are using their Fitbits and if I see anything abnormal. Looking at an article published by Cleveland Clinic, I can see that for normal adults, 60 to 100 beats per minute is considered normal, so I will filter out the extremes. I will create variables to help filter the data:

heart_rate_high <- heart_rate_cleaned %>% subset(heart_rate >= 100)
skim(heart_rate_high)
── Data Summary ────────────────────────
                           Values         
Name                       heart_rate_high
Number of rows             289007         
Number of columns          3              
_______________________                   
Column type frequency:                    
  character                1              
  numeric                  2              
________________________                  
Group variables            None           

── Variable type: character ───────────────────────────────────────────────
  skim_variable n_missing complete_rate   min   max empty n_unique
1 time                  0             1    19    21     0   246749
  whitespace
1          0

── Variable type: numeric ─────────────────────────────────────────────────
  skim_variable n_missing complete_rate        mean           sd         p0
1 id                    0             1 5881498937. 2023965701.  2022484408
2 heart_rate            0             1        116.         17.1        100
         p25        p50        p75       p100 hist 
1 4388161847 6117666160 7007744171 8877689391 ▃▆▇▇▆
2        104        110        122        203 ▇▂▁▁▁
heart_rate_low <- heart_rate_cleaned %>% subset(heart_rate <= 60)
skim(heart_rate_low)
── Data Summary ────────────────────────
                           Values        
Name                       heart_rate_low
Number of rows             435480        
Number of columns          3             
_______________________                  
Column type frequency:                   
  character                1             
  numeric                  2             
________________________                 
Group variables            None          

── Variable type: character ───────────────────────────────────────────────
  skim_variable n_missing complete_rate   min   max empty n_unique
1 time                  0             1    19    21     0   328154
  whitespace
1          0

── Variable type: numeric ─────────────────────────────────────────────────
  skim_variable n_missing complete_rate         mean            sd
1 id                    0             1 5517483664.  1627398472.  
2 heart_rate            0             1         55.7          3.82
          p0        p25        p50        p75       p100 hist 
1 2022484408 4388161847 5553957443 5577150313 8877689391 ▁▆▇▁▂
2         36         54         57         59         60 ▁▁▁▃▇

Now we can plot this data:

ggplot(data = heart_rate_high) + geom_bar(mapping = aes(x = heart_rate), fill = "red") + labs(title = "High Heart Rate", subtitle = "Frequences 4/12/16 - 5/12/16", x = "Heart Rates (BPM)", y = "Frequency") + annotate("text", x = 150, y = 10000, label = "Heart Rate >= 100 BPM", color = "red", fontface = "bold", size = 6)

ggplot(data = heart_rate_low) + geom_bar(mapping = aes(x = heart_rate), fill = "blue") + labs(title = "Low Heart Rate", subtitle = "Frequences 4/12/16 - 5/12/16", x = "Heart Rates (BPM)", y = "Frequency") + annotate("text", x = 43, y = 40000, label = "Heart Rate <= 60 BPM", color = "blue", fontface = "bold", size = 6)

From this data, I can see that both high and low heart rates have occurred amount the participants. This means that the participants are using their Fitbits for sleep and intense physical activity. Some of the participants may be older, or they may be athletes, so I cannot say for certain if the heart rates are at dangerous levels for the individuals who logged them. I would want my fitness tracker to alert me if my heart rate was at an extreme level, like 43 bpm or 180 bpm.

Observations from activity_sleep_combined

Finally, I want to see if there is any connection between sleep and calories to try to discover trends in the data logged by Fitbit users:

ggplot(data = activity_sleep_combined) + 
  geom_col(mapping = aes(x = hours_asleep, y = calories, alpha = calories), fill = "red") + labs(title = "Sleep and Calories", subtitle = "Totals 4/12/16 - 5/12/16", x = "Hours Asleep", y = "Calories", alpha = "Calories")

From this plot, we can see that most calories burned, including the high-level burns are logged by users who get between 6 and 8 hours of sleep a night. This might be because this is how much most people sleep on average, but we know scientifically that sleep is essential for overall wellness.

Conclusions and Recommendations

Summary of Findings

By visualizing the cleaned data, I made the following observations:

  • People are logging less activity on the weekends with the exception of a few outliers who logged intense workouts.

  • People log the most waking minutes in bed on Fri - Sun.

  • People are using their Fitbits to track activity more than they use them to keep track of weight, sleep, and heart rate (based on the total number of unique IDs for each data set).

  • People who log their weights manually tend to log lighter weights than those who don’t.

  • Some people are logging extreme heart rates, both fast and slow. These could be dangerous depending on the activity, age, and fitness-level of the user.

Recommendations

For my recommendations, I must choose one Bellabeat product to apply them too. The case study describes the following products thusly:

  • Bellabeat app: The Bellabeat app provides users with health data related to their activity, sleep, stress, menstrual cycle, and mindfulness habits. This data can help users better understand their current habits and make healthy decisions. The Bellabeat app connects to their line of smart wellness products.

  • Leaf: Bellabeat’s classic wellness tracker can be worn as a bracelet, necklace, or clip. The Leaf tracker connects to the Bellabeat app to track activity, sleep, and stress.

  • Time: This wellness watch combines the timeless look of a classic timepiece with smart technology to track user activity, sleep, and stress. The Time watch connects to the Bellabeat app to provide you with insights into your daily wellness.

  • Spring: This is a water bottle that tracks daily water intake using smart technology to ensure that you are appropriately hydrated throughout the day. The Spring bottle connects to the Bellabeat app to track your hydration levels.

  • Bellabeat membership: Bellabeat also offers a subscription-based membership program for users. Membership gives users 24/7 access to fully personalized guidance on nutrition, activity, sleep, health and beauty, and mindfulness based on their lifestyle and goals.

I will choose the Bellabeat membership because it is accessed through the app and can be highly customized. Bellabeat can encourage users to analyze their data based on the unique needs of their bodies. Here are my top recommendations for the marketing team:

Heart Rate Alert System

Make sure that the Bellabeat membership includes a heart rate alert system based on age and bmi. This could be a huge selling point for older customers because Bellabeat currently seems to market themselves to younger women (judging from their website). A well designed alert system paired with a reliable tracking device could save an older woman’s life and help Bellabeat compete with traditional emergency devices for seniors like Life Alert.

Better Weight Tracking

Try to synchronize the app with a weighing system to encourage users to track their weights digitally and not have to enter them manually. While there was not enough data to be certain, the numbers (and human pride) seem to indicate that weights entered manually are not as reliable as those done automatically.

AI Sleep Coach

Create an AI sleep coach that will help users track their sleep and create better sleeping habits. Syncing this to a device that specializes in comfort for sleeping would perhaps make more customers log their sleep records on a regular basis. Advertising for fitness trackers (including Bellabeat’s) is often filled with images of Olympic-level athletes running marathons or striking advanced yoga poses. Humans need sleep to be able to do these kinds of extreme activities and any kind of activity at all. While some users might be sleeping lightly on the weekends out of habit, the AI sleep coach could remind users to get in some light activity instead of being sedentary.The AI coach can determine what is healthy for the user.

Final Thoughts

Bellabeat has done good work in finding a niche in the growing market of fitness trackers. By focusing on women, they have made their products personable to their customers. Bellabeat can take their marketing strategy a step further by focusing on “holes” in current fitness tracker advertisement. Instead of trying to take on companies like Fitbit in the area of exercise data logging, they have an opportunity to reach a wider audience by focusing on wellness beyond physical exertion. By marketing to seniors and improving their sleep training program, they can reach a wider audience and make a significant impact in the lives of more women.

LS0tCnRpdGxlOiAiQmVsbGFiZWF0IENhc2UgU3R1ZHkgUmVwb3J0IgphdXRob3I6ICJKb25hdGhhbiBTY29maWVsZCIKZGF0ZTogIjEyLzMvMjAyMSIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGUgPSBGQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQpgYGAKCiFbTGFycnkgRC4gTW9vcmUsIENDIEJZLVNBIDQuMCBodHRwczovL2NyZWF0aXZlY29tbW9ucy5vcmcvbGljZW5zZXMvYnktc2EvNC4wLCB2aWEgV2lraW1lZGlhIENvbW1vbnNdKEV4ZXJjaXNlX1RyZWFkbWlsbF9Db252ZXlfTW90aW9uLmpwZWcpCgojIyBCYWNrZ3JvdW5kCgpUaGUgZm9sbG93aW5nIHN0dWR5IHdhcyBjb25kdWN0ZWQgaW4gcGFydGlhbCBmdWxmaWxsbWVudCBvZiB0aGUgcmVxdWlyZW1lbnRzIG9mIHRoZSAqKkdvb2dsZSBEYXRhIEFuYWx5dGljcyBDYXJlZXIgQ2VydGlmaWNhdGUuKiogQWxsIGRlc2NyaXB0aW9ucyBmcm9tIHRoaXMgY2FzZSBzdHVkeSBhcmUgdGFrZW4gZnJvbSB0aGUgR29vZ2xlIENvdXJzZXJhIGNvdXJzZSBhc3NvY2lhdGVkIHdpdGggdGhpcyBjYXNlIHN0dWR5LiAqKkkgZG8gbm90IGNsYWltIGF1dGhvcnNoaXAgb2YgdGhlIGNhc2VzIHN0dWR5IGRlc2NyaXB0aW9uKiosIGFuZCBJIGhhdmUgaW5jbHVkZWQgYSBsaW5rIHRvIHRoZSBQREYgW2hlcmVdKGh0dHBzOi8vZHJpdmUuZ29vZ2xlLmNvbS9maWxlL2QvMTZxZVBVRmRuemc0eFMweW1XeF9nclJVM2Nsd2lhcC1DL3ZpZXc/dXNwPXNoYXJpbmcpIFRoZSBjYXNlIHN0dWR5IGlzIGh5cG90aGV0aWNhbGx5IGNvbmR1Y3RlZCBmb3IgW0JlbGxhYmVhdF0oaHR0cHM6Ly9iZWxsYWJlYXQuY29tLyksIGEgd29tZW4ncyBmaXRuZXNzIHRyYWNrZXIgY29tcGFueS5UaGlzIGNhc2Ugc3R1ZHkgaW52b2x2ZXMgcHJlcGFyaW5nLCBjbGVhbmluZywgdmlzdWFsaXppbmcsIGFuZCBhbmFseXppbmcgZGF0YSBjb2xsZWN0ZWQgZnJvbSBGaXRiaXQgdXNlcnMgImluIG9yZGVyIHRvIGdhaW4gaW5zaWdodCBpbnRvIGhvdyBjb25zdW1lcnMgdXNlIG5vbi1CZWxsYWJlYXQgc21hcnQgZGV2aWNlcy4iIFRoZSBkYXRhIHVzZWQgaW4gdGhpcyBjYXNlIHN0dWR5IGNvbWVzIGZyb20gdGhlIGZvbGxvd2luZyBzb3VyY2U6IFtrYWdnbGUuY29tXShodHRwczovL3d3dy5rYWdnbGUuY29tL2FyYXNobmljL2ZpdGJpdCkKCiMjIEd1aWRpbmcgUXVlc3Rpb25zIGFuZCBEZWxpdmVyYWJsZXMKCiMjIyMgQnVzaW5lc3MgVGFzayBhbmQgR3VpZGluZyBRdWVzdGlvbnMKCkFzIHdhcyBwcmV2aW91c2x5IG1lbnRpb25lZCwgdGhlIGJ1c2luZXNzIHRhc2sgaXMgdG8gIGFuYWx5emluZyBkYXRhIGNvbGxlY3RlZCBmcm9tIEZpdGJpdCB1c2VycyAiaW4gb3JkZXIgdG8gZ2FpbiBpbnNpZ2h0IGludG8gaG93IGNvbnN1bWVycyB1c2Ugbm9uLUJlbGxhYmVhdCBzbWFydCBkZXZpY2VzLiIgU3BlY2lmaWNhbGx5LCBJIG5lZWQgdG8gZGlzY292ZXIgdHJlbmRzIGluIHNtYXJ0IGRldmljZSB1c2FnZSBhbmQgdXNlIHRob3NlIHRyZW5kcyB0byBpbmZvcm0gQmVsbGFiZWF0J3MgbWFya2V0aW5nIHN0cmF0ZWd5LiBJIHdpbGwgbmVlZCB0byBjaG9zZSBvbmUgQmVsbGFiZWF0IHByb2R1Y3QgdG8gYXBwbHkgbXkgaW5zaWdodHMgdG8gdGhlIGVuZCBvZiB0aGUgYW5hbHlzaXMuCgpUaGUgZ3VpZGVsaW5lcyBmb3IgdGhlIGNhc2Ugc3R1ZHkgc3VtbWFyaXplIG15IHRhc2sgdGhyb3VnaCB0aGUgZm9sbG93aW5nIHF1ZXN0aW9uczogCgoqIFdoYXQgYXJlIHNvbWUgdHJlbmRzIGluIHNtYXJ0IGRldmljZSB1c2FnZT8gCiogSG93IGNvdWxkIHRoZXNlIHRyZW5kcyBhcHBseSB0byBCZWxsYWJlYXQgY3VzdG9tZXJzPyAKKiBIb3cgY291bGQgdGhlc2UgdHJlbmRzIGhlbHAgaW5mbHVlbmNlIEJlbGxhYmVhdCBtYXJrZXRpbmcgc3RyYXRlZ3k/IAoKIyMjIyBEZWxpdmVyYWJsZXMKClRoZSBndWlkZWxpbmVzIGZvciB0aGUgY2FzZSBzdHVkeSByZXF1aXJlIG1lIHRvIGFjY29tcGxpc2ggdGhlIGZvbGxvd2luZyBkZWxpdmVyYWJsZXM6IAoKKiBBIGNsZWFyIHN1bW1hcnkgb2YgdGhlIGJ1c2luZXNzIHRhc2sgCgoqIEEgZGVzY3JpcHRpb24gb2YgYWxsIGRhdGEgc291cmNlcyB1c2VkIAoKKiBEb2N1bWVudGF0aW9uIG9mIGFueSBjbGVhbmluZyBvciBtYW5pcHVsYXRpb24gb2YgZGF0YSAKCiogQSBzdW1tYXJ5IG9mIG15IGFuYWx5c2lzIAoKKiBTdXBwb3J0aW5nIHZpc3VhbGl6YXRpb25zIGFuZCBrZXkgZmluZGluZ3MgCgoqIE15IHRvcCBoaWdoLWxldmVsIGNvbnRlbnQgcmVjb21tZW5kYXRpb25zIGJhc2VkIG9uIG15IGFuYWx5c2lzIAoKIyMgVGhlIERhdGEgCgojIyMjIFRoZSBEYXRlIFNvdXJjZQoKVGhlIGRhdGEgcGFja2V0IGNvbnRhaW5lZCBzZXZlcmFsIENTViBmaWxlcyB0cmFja2luZyBGaXRiaXQgdXNlcnMnIGRhdGEuIFRoZSBsaWNlbnNpbmcgYW5kIERPSSBvZiB0aGUgZGF0YSBzZXQgYXJlIGZvdW5kIFtoZXJlXShodHRwczovL3plbm9kby5vcmcvcmVjb3JkLzUzODk0Iy5ZYXI3THZITUszSikuIFRoZSBkZXNjcmlwdGlvbiBvZiB0aGUgZGF0YSBpbiBLYWdnbGUgc3RhdGVzOgoKIlRoaXMgZGF0YSBzZXQgZ2VuZXJhdGVkIGJ5IHJlc3BvbmRlbnRzIHRvIGEgZGlzdHJpYnV0ZWQgc3VydmV5IHZpYSBBbWF6b24gTWVjaGFuaWNhbCBUdXJrIGJldHdlZW4gICAgICAgICAgICAwMy4xMi4yMDE2LTA1LjEyLjIwMTYuIFRoaXJ0eSBlbGlnaWJsZSBGaXRiaXQgdXNlcnMgY29uc2VudGVkIHRvIHRoZSBzdWJtaXNzaW9uIG9mIHBlcnNvbmFsIHRyYWNrZXIgZGF0YSwgaW5jbHVkaW5nIG1pbnV0ZS1sZXZlbCBvdXRwdXQgZm9yIHBoeXNpY2FsIGFjdGl2aXR5LCBoZWFydCByYXRlLCBhbmQgc2xlZXAgbW9uaXRvcmluZy4iIFtrYWdnbGUuY29tXShodHRwczovL3d3dy5rYWdnbGUuY29tL2FyYXNobmljL2ZpdGJpdCkKCiMjIyMgTGltaXRhdGlvbnMKClRoZSBsaW1pdGF0aW9ucyBvZiB0aGUgZGF0YSBhcmUgaW1tZWRpYXRlbHkgYXBwYXJlbnQuIEFjY29yZGluZyB0byBCZWxsYWJlYXQncyB3ZWJzaXRlLCB0aGV5IGhhdmUgMS4yIG1pbGxpb24gZGV2aWNlcyBzeW5jZWQsIGJ1dCB0aGlzIGRhdGEgc2V0IG9ubHkgY29udGFpbnMgdGhlIGRhdGEgZnJvbSAzMCBGaXRiaXQgdXNlcnMuIEV2ZW4gaWYgZXZlcnkgQmVsbGFiZWF0IHVzZXIgb3duZWQgc3luY2VkIGRldmljZXMgc28gdGhhdCB0aGUgdG90YWwgbnVtYmVyIG9mIGN1c3RvbWVycyB3YXMgNjAwLDAwMCwgSSB3b3VsZCBuZWVkIGEgc2FtcGxlIHNpemUgb2YgMjczIHRvIGhhdmUgYSBjb25maWRlbmNlIGxldmVsIG9mIDkwJSBhbmQgYSBtYXJnaW4gb2YgZXJyb3Igb2YgNSUuIEluIGFkZGl0aW9uIHRvIHRoZSByaXNrIG9mIHNhbXBsaW5nIGJpYXMsIGl0IHNob3VsZCBiZSBub3RlZCB0aGF0IHRoZSBkYXRhIGlzICpkYXRlZCouIEdpdmVuIHRoZSBjaGFuZ2VzIGJyb3VnaHQgYnkgYSBnbG9iYWwgcGFuZGVtaWMsIEkgd291bGQgc2F5IHRoYXQgdGhlIGRhdGEgaXMgbmVhcmx5IGlycmVsZXZhbnQgdG9kYXkgYmVjYXVzZSBvZiBpdHMgYWdlLCBzbWFsbCBzaXplLCBhbmQgcmVsYXRpdmVseSBzaG9ydCB0aW1lIGZyYW1lLiBBbm90aGVyIGNvbmNlcm4gaXMgdGhhdCBub3RoaW5nIGlzIGtub3duIGFib3V0IHRoZSBkZW1vZ3JhcGhpY3Mgb2YgdGhlIHBhcnRpY2lwYW50cywgYW5kIHRoaXMgbWFrZXMgaXQgZGlmZmljdWx0IHRvIGRyYXcgY29uZmlkZW50IGNvbmNsdXNpb25zIGZvciBhIGNvbXBhbnkgdGhhdCBzcGVjaWFsaXplcyBpbiAqd29tZW4ncyogaGVhbHRoIHByb2R1Y3RzLiBJIGFsc28gZG8gbm90IGtub3cgd2hhdCBzdXJ2ZXkgcXVlc3Rpb25zIG9yIG1ldGhvZHMgQW1hem9uIE1lY2hhbmljYWwgVHVyayB1c2VkIHRvIGNvbGxlY3QgdGhlIGRhdGEgb3IgaWYgdGhvc2UgbWV0aG9kcyBjb3VsZCBoYXZlIHJpc2tlZCBzYW1wbGluZyBiaWFzLiBGb3IgdGhlIHNha2Ugb2YgdGhlIGNhc2Ugc3R1ZHksIEkgd2lsbCBvdmVybG9vayB0aGVzZSBjb25jZXJucywgYnV0ICpJIHdvdWxkIG5vdCBwcmVzZW50IGFueSByZWNvbW1lbmRhdGlvbnMgdG8gc3Rha2Vob2xkZXJzIGJhc2VkIGV4Y2x1c2l2ZWx5IG9mZiB0aGlzIGRhdGEgaW4gYSByZWFsLXdvcmxkIHNjZW5hcmlvLioKCiMjIFRoZSBDbGVhbmluZyBQcm9jZXNzCgpTZXZlcmFsIG9mIHRoZSB0YWJsZXMgY29udGFpbmVkIHRoZSBkYXRhLCBidXQgaW4gZGlmZmVyZW50IGFycmFuZ2VtZW50cy4gSSBkZWNpZGVkIHRvIGV4cGxvcmUgIHRoZSAqKmRhaWx5QWN0aXZpdHlfbWVyZ2VkKiosICoqc2xlZXBEYXlfbWVyZ2VkKiosCioqd2VpZ2h0TG9nSW5mb19tZXJnZWQqKiwgYW5kIHRoZSAqKmhlYXJ0cmF0ZV9zZWNvbmRzX21lcmdlZCoqIENTViBmaWxlcyBzbyB0aGF0IEkgY291bGQgZ2V0IHRoZSBtYXhpbXVtIG51bWJlciBvZiB2YXJpYWJsZXMgdG8gd29yayB3aXRoLiBJIHJlbmFtZWQgdGhlbSBhcyAqKmRhaWx5X2FjdGl2aXR5X21lcmdlZF9yYXcqKiwgKipzbGVlcF9kYXlfbWVyZ2VkX3JhdyoqLCAqKndlaWdodF9sb2dfaW5mb19tZXJnZWRfcmF3KiosIGFuZCAqKmhlYXJ0LXJhdGVfc2Vjb25kc19tZXJnZWRfcmF3LCByZXNwZWN0aXZlbHkqKi4KClRvIGJlZ2luIGNsZWFuaW5nIHRoZSBkYXRhLCBJIHdpbGwgZmlyc3QgaW5zdGFsbCB0aGUgZm9sbG93aW5nIHBhY2thZ2VzOgoKCmBgYHtyIGluc3RhbGwgcGFja2FnZXMsIGVjaG89VFJVRX0KaW5zdGFsbC5wYWNrYWdlcygidGlkeXZlcnNlIikgCmluc3RhbGwucGFja2FnZXMoInNraW1yIikKaW5zdGFsbC5wYWNrYWdlcygiamFuaXRvciIpCmluc3RhbGwucGFja2FnZXMoImhlcmUiKQpgYGAKCk5vdywgSSB3aWxsIGxvYWQgdGhlIHBhY2thZ2VzOgoKYGBge3IgbG9hZCBwYWNrYWdlcywgZWNobz1UUlVFfQpsaWJyYXJ5KCJ0aWR5dmVyc2UiKQpsaWJyYXJ5KCJza2ltciIpCmxpYnJhcnkoImphbml0b3IiKQpsaWJyYXJ5KCJoZXJlIikKbGlicmFyeSgibHVicmlkYXRlIikKbGlicmFyeSgiZ2dwbG90MiIpCmxpYnJhcnkoImRwbHlyIikKYGBgCk5leHQsIEkgd2lsbCBsb2FkIHRoZSBDU1YgZmlsZXMgZm9yIGFuYWx5c2lzIGFuZCBhc3NpZ24gdmFyaWFibGVzOgoKYGBge3IgZGF0YWZyYW1lcywgZWNobz1UUlVFfQphY3Rpdml0eV9kZiA8LSByZWFkX2NzdigiZGFpbHlfYWN0aXZpdHlfbWVyZ2VkX3Jhdy5jc3YiKQpzbGVlcF9kZiA8LSByZWFkX2Nzdigic2xlZXBfZGF5X21lcmdlZF9yYXcuY3N2IikKd2VpZ2h0X2RmIDwtIHJlYWRfY3N2KCJ3ZWlnaHRfbG9nX2luZm9fbWVyZ2VkX3Jhdy5jc3YiKQpoZWFydF9yYXRlX2RmIDwtIHJlYWRfY3N2KCJoZWFydF9yYXRlX3NlY29uZHNfbWVyZ2VkX3Jhdy5jc3YiKQpgYGAKCkl0J3MgdGltZSB0byB0YWtlIGEgcGVhayBhdCB0aGUgY29udGVudHMgb2YgdGhlIGRhdGEgZnJhbWVzOgoKYGBge3IgaGVhZCwgZWNobz1UUlVFfQpoZWFkKGFjdGl2aXR5X2RmKQpoZWFkKHNsZWVwX2RmKQpoZWFkKHdlaWdodF9kZikKaGVhZChoZWFydF9yYXRlX2RmKQpgYGAKTGV0J3MgZ2V0IHNvbWUgaW5mb3JtYXRpb24gb24gdGhlIHNpemUgb2YgdGhlIGRhdGEgZnJhbWVzIHVzaW5nICoqc2tpbXIqKjoKCmBgYHtyIHNraW1yLCBlY2hvPVRSVUV9CnNraW1fd2l0aG91dF9jaGFydHMoYWN0aXZpdHlfZGYpCnNraW1fd2l0aG91dF9jaGFydHMoc2xlZXBfZGYpCnNraW1fd2l0aG91dF9jaGFydHMod2VpZ2h0X2RmKQpza2ltX3dpdGhvdXRfY2hhcnRzKGhlYXJ0X3JhdGVfZGYpCmBgYAoKIyMjIyBQcmVsaW1pbmFyeSBPYnNlcnZhdGlvbnMKClNvbWUgb2JzZXJ2YXRpb25zIHRvIG5vdGUgYXQgdGhpcyBwb2ludDoKCiogVGhlIGRhdGEgc2V0cyBhcmUgaW4gdGhlICoqbG9uZyoqIGZvcm1hdC4KCiogVGhlc2UgZGF0YSBzZXRzIGhhdmUgc29tZSBjb21tb24gY29sdW1uIG5hbWVzLCBzbyB0aGV5IGNhbiBiZSBjb21iaW5lZC4KCiogVGhlc2UgZGF0YSBzZXRzIGhhdmUgZGlmZmVyZW50IG51bWJlcnMgb2Ygcm93cy4KCiogVGhlIElEcyBhcmUgYmVpbmcgdHJlYXRlZCBhcyBudW1iZXJzLCBub3Qgc3RyaW5ncy4KCiogVGhlIG1lYXN1cmVtZW50IGZvciBkaXN0YW5jZSBpcyB1bmtub3duLCBidXQgd2Ugd2lsbCBhc3N1bWUgaXQgaXMgaW4ga20uIEkgYW0gYmFzaW5nIHRoaXMgb24gdGhlIGZvbGxvd2luZyBbZGlzY3Vzc2lvbl0oaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9hcmFzaG5pYy9maXRiaXQvZGlzY3Vzc2lvbi8yNTYyODQpIGFsdGhvdWdoIEkgd291bGQgY29uZmlybSB0aGlzIHdpdGggdGhlIGRhdGEgY29sbGVjdGlvbiBhZ2VuY3kgaWYgSSB3YXMgYWN0dWFsbHkgZW1wbG95ZWQgYnkgQmVsbGFiZWF0LgoKKiBUaGUgbmFtZXMgb2YgdGhlIGNvbHVtbnMgbmVlZCB0byBiZSBmb3JtYXR0ZWQgaW50byBzbmFrZSBjYXNlIChteSBwZXJzb25hbCB0YXN0ZSkuCgoqIFRoZSAqKndlaWdodF9kZioqIGhhcyBhIHJlbGF0aXZlbHkgc21hbGwgbnVtYmVyIG9mIHJvd3MgYW5kIGlzIG1pc3NpbmcgYSBsb3Qgb2YgZGF0YSBpbiB0aGUgKipmYXQqKiBjb2x1bW4uCgoqIFRoZSAqKmhlYXJ0X3JhdGVfZGYqKiBkYXRhIGZyYW1lIGhhcyB0aGUgbGFyZ2VzdCBudW1iZXIgb2Ygcm93cyBvZiBhbGwgdGhlIGRhdGEgZnJhbWVzLgoKIyMjIyMgQ2xlYW5pbmcgVGFza3MgZm9yIGFjdGl2aXR5X2RmCgpIZXJlIGlzIGEgc3VtbWFyeSBvZiB0aGUgY2xlYW5pbmcgdGFza3MgSSB3aXNoIHRvIHBlcmZvcm0gb24gdGhpcyBkYXRhIGZyYW1lOgoKKiBDb252ZXJ0IGNvbHVtbiBuYW1lcyB0byBzbmFrZSBjYXNlIGZvciBlYXN5IHJlYWRpbmcKCiogQ29udmVydCBJRHMgdG8gc3RyaW5ncyBzbyB0aGV5IGNhbiBiZSBjb3VudGVkIAoKKiBSZW1vdmUgZHVwbGljYXRlIGFuZCBlbXB0eSByb3dzIGFuZCBjb2x1bW5zCgoqIFJlbW92ZSBjb2x1bW5zIHdpdGggYSBzaW5nbGUgdmFsdWUKCiogRW5zdXJlIHRoYXQgdGhlIHRvdGFsIGRpc3RhbmNlIGlzIHRoZSBzdW0gb2YgdGhlIHN1YmNhdGVnb3JpZXMgb2YgZGlzdGFuY2UKCiogQ3JlYXRlIGEgdG90YWwgY29sdW1uIGZvciBhY3RpdmUgbWludXRlcwoKKiBSZXR1cm4gdGhlIGRheSBvZiB0aGUgd2VlayBhcyBhIHN0cmluZyBmb3IgZWFjaCBkYXRlCgpIZXJlIGlzIHRoZSBjb2RlIEkgdXNlZCB0byBjbGVhbiB0aGUgZGF0YToKCmBgYHtyIGFjdGl2aXR5X2NsZWFuZWQsIGVjaG89VFJVRX0KYWN0aXZpdHlfY2xlYW5lZCA8LSBhY3Rpdml0eV9kZiAlPiUgCiAgY2xlYW5fbmFtZXMoKSAlPiUgI2NvbnZlcnQgdG8gc25ha2UgY2FzZQogIGRpc3RpbmN0KCkgJT4lICNyZW1vdmUgZHVwbGljYXRlcwogIHJlbW92ZV9lbXB0eShjKCJyb3dzIiwgImNvbHMiKSkgJT4lICNyZW1vdmUgZW1wdHkgcm93cyBvciBjb2x1bW5zCiAgcmVtb3ZlX2NvbnN0YW50KCkgJT4lICNyZW1vdmUgY29sdW1ucyB3aXRoIGEgc2luZ2xlIHZhbHVlCiAgbXV0YXRlKHRvdGFsX2Rpc3RhbmNlX25ldyA9ICNtYWtlIHN1cmUgdG90YWwgZGlzdGFuY2UgaXMgY2FsY3VsYXRlZCBwcm9wZXJseQogICAgICAgICAgICh2ZXJ5X2FjdGl2ZV9kaXN0YW5jZSArIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlcmF0ZWx5X2FjdGl2ZV9kaXN0YW5jZSArIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaWdodF9hY3RpdmVfZGlzdGFuY2UgKyAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VkZW50YXJ5X2FjdGl2ZV9kaXN0YW5jZSkpICU+JSAKICBtdXRhdGUodG90YWxfYWN0aXZlX21pbnV0ZXMgPSAKICAgICAgICAgICB2ZXJ5X2FjdGl2ZV9taW51dGVzICsgZmFpcmx5X2FjdGl2ZV9taW51dGVzICsgI2FkZCB0b3RhbCBhY3RpdmUgbWludXRlcyBjb2x1bW4KICAgICAgICAgICBsaWdodGx5X2FjdGl2ZV9taW51dGVzICsgc2VkZW50YXJ5X21pbnV0ZXMpICU+JSAKICBtdXRhdGUoZGF0ZV9uZXcgPSBhcy5EYXRlKG1keShhY3Rpdml0eV9kYXRlKSkpICU+JSAjc3RhbmRhcmRpemUgZGF0ZQogIG11dGF0ZShkYXkgPSB3ZGF5KGRhdGVfbmV3LCBUUlVFKSkgJT4lICNjb252ZXJ0IGRhdGUgdG8gd2Vla2RheSBzdHJpbmcKICBtdXRhdGUoaWQgPSBhcy5jaGFyYWN0ZXIoaWQpKSAlPiUgI2NvbnZlcnQgaWQgdG8gc3RyaW5nCiAgc2VsZWN0KGlkLCBkYXRlX25ldywgZGF5LCB0b3RhbF9zdGVwcywgdG90YWxfZGlzdGFuY2VfbmV3LCB0b3RhbF9hY3RpdmVfbWludXRlcywgc2VkZW50YXJ5X21pbnV0ZXMsCiAgICAgICAgIGxpZ2h0bHlfYWN0aXZlX21pbnV0ZXMsIGZhaXJseV9hY3RpdmVfbWludXRlcywgdmVyeV9hY3RpdmVfbWludXRlcywKICAgICAgICAgY2Fsb3JpZXMpIApgYGAKCgojIyMjIyBDbGVhbmluZyBUYXNrcyBmb3Igc2xlZXBfZGYKCkhlcmUgaXMgYSBzdW1tYXJ5IG9mIHRoZSBjbGVhbmluZyB0YXNrcyBJIHdpc2ggdG8gcGVyZm9ybSBvbiB0aGlzIGRhdGEgZnJhbWU6CgoKKiBQZXJmb3JtIGEgc2ltaWxhciBjbGVhbmluZyBwcm9jZXNzIHVzZWQgb24gYWN0aXZpdHlfZGYKCiogQ3JlYXRlIGEgKipseWluZ19hd2FrZSoqIGNvbHVtbiB0aGF0IHdpbGwgc2hvdyBob3cgbXVjaCB0aW1lIHBhcnRpY2lwYW50cyBzcGVuZCBhd2FrZSBpbiBiZWQKCiogQ3JlYXRlIGEgY29sdW1uIHRoYXQgd2lsbCByb3VuZCB0aGUgdG90YWwgc2xlZXBpbmcgbWludXRlcyB0byBpbnRlZ2VyIGhvdXJzIGZvciBiZXR0ZXIgZ3JvdXBpbmcKCkhlcmUgaXMgdGhlIGNvZGUgSSB1c2VkIHRvIGNsZWFuIHRoZSBkYXRhOgoKCmBgYHtyIHNsZWVwX2NsZWFuZWQsIGVjaG89VFJVRX0Kc2xlZXBfY2xlYW5lZCA8LSBzbGVlcF9kZiAlPiUgCiAgY2xlYW5fbmFtZXMoKSAlPiUgI2NvbnZlcnQgdG8gc25ha2UgY2FzZQogIGRpc3RpbmN0KCkgJT4lICNyZW1vdmUgZHVwbGljYXRlcwogIHJlbW92ZV9lbXB0eShjKCJyb3dzIiwgImNvbHMiKSkgJT4lICNyZW1vdmUgZW1wdHkgcm93cyBvciBjb2x1bW5zCiAgcmVtb3ZlX2NvbnN0YW50KCkgJT4lICNyZW1vdmUgY29sdW1ucyB3aXRoIGEgc2luZ2xlIHZhbHVlCiAgbXV0YXRlKGRhdGVfbmV3ID0gYXMuRGF0ZShtZHlfaG1zKHNsZWVwX2RheSkpKSAlPiUgI3N0YW5kYXJkaXplIGRhdGUKICBtdXRhdGUoZGF5ID0gd2RheShkYXRlX25ldywgVFJVRSkpICU+JSAjY29udmVydCBkYXRlIHRvIHdlZWtkYXkgc3RyaW5nCiAgbXV0YXRlKGlkID0gYXMuY2hhcmFjdGVyKGlkKSkgJT4lICMgY29udmVydCBpZCB0byBzdHJpbmcKICBtdXRhdGUobHlpbmdfYXdha2UgPSB0b3RhbF90aW1lX2luX2JlZCAtIHRvdGFsX21pbnV0ZXNfYXNsZWVwKSAlPiUgI2hvdyBtdWNoIHRpbWUgaXMgc3BlbnQgYXdha2UgaW4gYmVkPwogIG11dGF0ZShob3Vyc19hc2xlZXAgPSB0b3RhbF9taW51dGVzX2FzbGVlcCAlLyUgNjApICU+JSAjcm91bmQgbWludXRlcyB0byBob3VycwogIHNlbGVjdChpZCwgZGF0ZV9uZXcsIGRheSwgdG90YWxfc2xlZXBfcmVjb3JkcywgdG90YWxfbWludXRlc19hc2xlZXAsIGhvdXJzX2FzbGVlcCwgbHlpbmdfYXdha2UsIHRvdGFsX3RpbWVfaW5fYmVkKQpgYGAKCiMjIyMjIENsZWFuaW5nIFRhc2tzIGZvciB3ZWlnaHRfZGYKCkhlcmUgaXMgYSBzdW1tYXJ5IG9mIHRoZSBjbGVhbmluZyB0YXNrcyBJIHdpc2ggdG8gcGVyZm9ybSBvbiB0aGlzIGRhdGEgZnJhbWU6CgoqIFBlcmZvcm0gYSBzaW1pbGFyIGNsZWFuaW5nIHByb2Nlc3MgdXNlZCBvbiBhY3Rpdml0eV9kZgoKKiBMaW1pdCBtZWFzdXJlbWVudHMgaW4gdGhlIG1ldHJpYyBzeXN0ZW0gb25seQoKKiBVc2UgdGhlIEJNSSBmb3JtdWxhIHRvIHVzZSB0aGUgd2VpZ2h0IHRvIGNhbGN1bGF0ZSBhIGhlaWdodCBjb2x1bW4gaW4gbWV0ZXJzIGluIGNhc2UgdGhpcyByZXZlYWxzIGFueXRoaW5nCgpIZXJlIGlzIHRoZSBjb2RlIEkgdXNlZCB0byBjbGVhbiB0aGUgZGF0YToKCgpgYGB7ciB3ZWlnaHRfY2xlYW5lZCwgZWNobz1UUlVFfQp3ZWlnaHRfY2xlYW5lZCA8LSB3ZWlnaHRfZGYgJT4lIAogIGNsZWFuX25hbWVzKCkgJT4lICNjb252ZXJ0IHRvIHNuYWtlIGNhc2UKICBkaXN0aW5jdCgpICU+JSAjcmVtb3ZlIGR1cGxpY2F0ZXMKICByZW1vdmVfZW1wdHkoYygicm93cyIsICJjb2xzIikpICU+JSAjcmVtb3ZlIGVtcHR5IHJvd3Mgb3IgY29sdW1ucwogIHJlbW92ZV9jb25zdGFudCgpICU+JSAgICNyZW1vdmUgY29sdW1ucyB3aXRoIGEgc2luZ2xlIHZhbHVlCiAgbXV0YXRlKGhlaWdodF9tID0gKHdlaWdodF9rZyAvIGJtaSApIF4gMC41KSAlPiUgIyB1c2Ugd2VpZ2h0IGFuZCBibWkgdG8gY2FsY3VsYXRlIGhlaWdodAogIG11dGF0ZShkYXRlX25ldyA9IGFzLkRhdGUobWR5X2htcyhkYXRlKSkpICU+JSAKICBtdXRhdGUoZGF5ID0gd2RheShkYXRlX25ldywgVFJVRSkpICU+JSAjIGNvbnZlcnQgZGF0ZSB0byB3ZWVrZGF5IHN0cmluZwogIG11dGF0ZShpZCA9IGFzLmNoYXJhY3RlcihpZCkpICU+JSAjIGNvbnZlcnQgaWQgdG8gc3RyaW5nCiAgc2VsZWN0KGxvZ19pZCwgaWQsIGRhdGVfbmV3LCBkYXksIHdlaWdodF9rZywgaGVpZ2h0X20sIGJtaSwgaXNfbWFudWFsX3JlcG9ydCkgCmBgYAoKCiMjIyMjIENsZWFuaW5nIFRhc2tzIGZvciBoZWFydF9yYXRlX2RmCgpIZXJlIGlzIGEgc3VtbWFyeSBvZiB0aGUgY2xlYW5pbmcgdGFza3MgSSB3aXNoIHRvIHBlcmZvcm0gb24gdGhpcyBkYXRhIGZyYW1lOgoKKiBQZXJmb3JtIGEgc2ltaWxhciBjbGVhbmluZyBwcm9jZXNzIHVzZWQgb24gYWN0aXZpdHlfZGYKCiogUmVuYW1lIHRoZSAqKnZhbHVlKiogY29sdW1uIHRvIG1ha2UgdGhlIGRhdGEgZWFzaWVyIHRvIHVuZGVyc3RhbmQgYXQgZ2xhbmNlCgpIZXJlIGlzIHRoZSBjb2RlIEkgdXNlZCB0byBjbGVhbiB0aGUgZGF0YToKCgpgYGB7ciBoZWFydF9yYXRlX2NsZWFuZWQsIGVjaG89VFJVRX0KaGVhcnRfcmF0ZV9jbGVhbmVkIDwtIGhlYXJ0X3JhdGVfZGYgJT4lIAogIGNsZWFuX25hbWVzKCkgJT4lICNjb252ZXJ0IHRvIHNuYWtlIGNhc2UKICBkaXN0aW5jdCgpICU+JSAjcmVtb3ZlIGR1cGxpY2F0ZXMKICByZW1vdmVfZW1wdHkoYygicm93cyIsICJjb2xzIikpICU+JSAjcmVtb3ZlIGVtcHR5IHJvd3Mgb3IgY29sdW1ucwogIHJlbW92ZV9jb25zdGFudCgpICU+JSAgI3JlbW92ZSBjb2x1bW5zIHdpdGggYSBzaW5nbGUgdmFsdWUKICByZW5hbWUoImhlYXJ0X3JhdGUiID0gdmFsdWUpICU+JSAgI2NsYXJpZnkgdmFsdWUgb2YgdGhpcyBjb2x1bW4KICBtdXRhdGUoaWQgPSBhcy5jaGFyYWN0ZXIoaWQpKSAlPiUgIyBjb252ZXJ0IGlkIHRvIHN0cmluZwogIHNlbGVjdChldmVyeXRoaW5nKCkpCmBgYAoKIyMjIyMgQ29tYmluaW5nIERhdGEgRnJhbWVzCgpBZnRlciBjbGVhbmluZyB0aGUgcHJldmlvdXMgZGF0YSBmcmFtZXMsIEkgYW0gaW50ZXJlc3RlZCBpbiBjb21iaW5pbmcgc29tZSBvZiB0aGVtIGZvciBlYXNlIG9mIGFuYWx5c2lzLiBJIHdpbGwgdXNlIGFuIGlubmVyIGpvaW4gdG8gbWVyZ2UgdGhlIGRhaWx5IGFjdGl2aXR5IGFuZCBzbGVlcCBkYXRhIGZyYW1lczoKCmBgYHtyIGpvaW5pbmcgZGF0YSwgZWNobz1UUlVFfQphY3Rpdml0eV9zbGVlcF9jb21iaW5lZCA8LSBpbm5lcl9qb2luKGFjdGl2aXR5X2NsZWFuZWQsIHNsZWVwX2NsZWFuZWQpIApgYGAKIyMjIyMgQ29uZmlybWluZyB0aGUgQ2xlYW5pbmcgUHJvY2VzcwoKSSB3aWxsIG5vdyBjb25maXJtIHRoYXQgdGhlIGZyYW1lcyBoYXZlIGJlZW4gY2xlYW5lZCBwcm9wZXJseSB1c2luZyAqKnNraW1yKio6CgpgYGB7ciBjbGVhbmluZyBjb25maXJtYXRpb24sIGVjaG89VFJVRX0Kc2tpbV93aXRob3V0X2NoYXJ0cyhhY3Rpdml0eV9jbGVhbmVkKQpza2ltX3dpdGhvdXRfY2hhcnRzKHNsZWVwX2NsZWFuZWQpCnNraW1fd2l0aG91dF9jaGFydHMod2VpZ2h0X2NsZWFuZWQpCnNraW1fd2l0aG91dF9jaGFydHMoaGVhcnRfcmF0ZV9jbGVhbmVkKQpza2ltX3dpdGhvdXRfY2hhcnRzKGFjdGl2aXR5X3NsZWVwX2NvbWJpbmVkKQpgYGAKCiMjIyMgQW5hbHl6aW5nIHRoZSBEYXRhCgojIyMjIyBPYnNlcnZhdGlvbnMgZnJvbSBhY3Rpdml0eV9jbGVhbmVkCgpXaGVuIGxvb2tpbmcgYXQgdGhlIGluZm9ybWF0aW9uIGZvciB0aGlzIGRhdGEgZnJhbWUsIEkgd2FzIGluaXRpYWxseSBjb25mdXNlZCBiZWNhdXNlIHRoZXJlIHdlcmUgKiozMyB1bmlxdWUgSURzKiouIFRoZSBkZXNjcmlwdGlvbiBvZiB0aGUgZGF0YSBzYWlkIHRoYXQgdGhlcmUgd2VyZSAqKjMwIHBhcnRpY2lwYW50cyoqIGluIHRoaXMgc3R1ZHkuIEkgdmlzaXRlZCBvbiBvZiB0aGUgW2Rpc2N1c3Npb25zXShodHRwczovL3d3dy5rYWdnbGUuY29tL2FyYXNobmljL2ZpdGJpdC9kaXNjdXNzaW9uLzI4MTg3NCkgb24gS2FnZ2xlIGluIHdoaWNoIG9uZSB1c2VyIGNvbW1lbnRlZCB0aGF0IHNvbWUgdXNlcnMgbWF5IGJlIGxvZ2dpbmcgZGF0YSB3aXRoIG1vcmUgdGhhbiBvbmUgZGV2aWNlLiAqSSB3aWxsIGFzc3VtZSB0aGF0IHRoaXMgaXMgdGhlIGNvcnJlY3QgZXhwbGFuYXRpb24uKiAKCiMjIyMjIE9ic2VydmF0aW9ucyBmcm9tIGFjdGl2aXR5X2NsZWFuZWQKCkFsdGhvdWdoIGl0IGlzIHNjaWVudGlmaWNhbGx5IGFjY2VwdGVkIHRoYXQgcGh5c2ljYWwgYWN0aXZpdHkgd2lsbCByZXN1bHQgaW4gYnVybmVkIGNhbG9yaWVzLCBmb3IgdGhlIHNha2Ugb2YgdGVzdGluZyBvdXIgZGF0YSwgbGV0cyBjb21wYXJlIGRhaWx5IHN0ZXBzIGFuZCBjYWxvcmllczoKCmBgYHtyIHN0ZXBzIHRvIGNhbG9yaWVzLCBlY2hvPVRSVUV9CmdncGxvdChkYXRhID0gYWN0aXZpdHlfY2xlYW5lZCwgYWVzKHggPSBjYWxvcmllcywgeSA9IHRvdGFsX3N0ZXBzLCBjb2xvciA9IHRvdGFsX3N0ZXBzKSkgKyAKICBnZW9tX3BvaW50KCkgKyAgZ2VvbV9zbW9vdGgoKSArIGxhYnModGl0bGUgPSAiU3RlcHMgdnMuIENhbG9yaWVzIiwgeCA9ICJDYWxvcmllcyIsIHkgPSAiVG90YWwgU3RlcHMiLCBjb2xvciA9ICJUb3RhbCBTdGVwcyIpCmBgYApGcm9tIHRoZSBjaGFydCwgd2UgY2FuIHNlZSBhICpwb3NpdGl2ZSBjb3JyZWxhdGlvbiogYmV0d2VlbiBjYWxvcmllcyBhbmQgc3RlcHMuIExldCdzIGNvbXBhcmUgY2Fsb3JpZXMgd2l0aCB0b3RhbCBkaXN0YW5jZToKCmBgYHtyIGRpc3RhbmNlIHRvIGNhbG9yaWVzLCBlY2hvPVRSVUV9CmdncGxvdChkYXRhID0gYWN0aXZpdHlfY2xlYW5lZCwgYWVzKHggPSBjYWxvcmllcywgeSA9IHRvdGFsX2Rpc3RhbmNlX25ldywgY29sb3IgPSB0b3RhbF9kaXN0YW5jZV9uZXcpKSArIAogIGdlb21fcG9pbnQoKSArICBnZW9tX3Ntb290aCgpICsgbGFicyh0aXRsZSA9ICJEaXN0YW5jZSB2cy4gQ2Fsb3JpZXMiLCB4ID0gIkNhbG9yaWVzIiwgeSA9ICJUb3RhbCBEaXN0YW5jZSAoS20pIiwgY29sb3IgPSAiS20iKQpgYGAKTm90ZSB0aGF0IEkgYW0gYXNzdW1pbmcgdGhhdCB0aGUgZGlzdGFuY2UgaXMgaW4gS20gcGVyIGEgS2FnZ2xlIFtkaXNjdXNzaW9uXShodHRwczovL3d3dy5rYWdnbGUuY29tL2FyYXNobmljL2ZpdGJpdC9kaXNjdXNzaW9uLzI1NjI4NCkuIFNpbmNlIHRoZXJlIGlzIGEgcG9zaXRpdmUgY29ycmVsYXRpb24gYmV0d2VlbiBzdGVwcyBhbmQgY2Fsb3JpZXMsIGFuZCBkaXN0YW5jZSBhbmQgY2Fsb3JpZXMsIEkgd2FudCB0byBrbm93IHdoaWNoIGRheXMgaGF2ZSB0aGUgbW9zdCBzdGVwcyBhbmQgZGlzdGFuY2VzIGxvZ2dlZC4gV2Ugd2lsbCBiZWdpbiB3aXRoIHRoZSBzdGVwcy4gV2Ugc2VlICoqdGhhdCB0aGUgYXZlcmFnZSB0b3RhbCBzdGVwcyBpcyBzbGlnaHRseSBsb3dlciBvbiB0aGUgd2Vla2VuZHM6KiogCgoKYGBge3IgdG90YWwgc3RlcHMsIGVjaG89VFJVRX0KZ2dwbG90KGRhdGEgPSBhY3Rpdml0eV9jbGVhbmVkKSArIGdlb21fcG9pbnQobWFwcGluZyA9IGFlcyh4ID0gZGF5LCB5ID0gdG90YWxfc3RlcHMsIGNvbG9yID0gdG90YWxfc3RlcHMpKSArIGdlb21fYm94cGxvdChtYXBwaW5nID0gYWVzKHggPSBkYXksIHkgPSB0b3RhbF9zdGVwcykpICsgbGFicyh0aXRsZSA9ICJUb3RhbCBTdGVwcyIsIHN1YnRpdGxlID0gIkZyZXF1ZW5jeSBEaXN0cmlidXRpb25zIGJ5IERheSBvZiB0aGUgV2VlayAoNC8xMi8xNiAtIDUvMTIvMTYpIiwgeCA9ICJEYXkiLCB5ID0gIiBUb3RhbCBTdGVwcyIsIGNvbG91ciA9ICJTdGVwcyIpCmBgYApUaGUgc2FtZSBob2xkcyB0cnVlIGZvciB0aGUgYXZlcmFnZSBvZiB0b3RhbCBkaXN0YW5jZToKCmBgYHtyIHRvdGFsIGRpc3RhbmNlLCBlY2hvPVRSVUV9CmdncGxvdChkYXRhID0gYWN0aXZpdHlfY2xlYW5lZCkgKyBnZW9tX3BvaW50KG1hcHBpbmcgPSBhZXMoeCA9IGRheSwgeSA9IHRvdGFsX2Rpc3RhbmNlX25ldywgY29sb3IgPSB0b3RhbF9kaXN0YW5jZV9uZXcpKSArIGdlb21fYm94cGxvdChtYXBwaW5nID0gYWVzKHggPSBkYXksIHkgPSB0b3RhbF9kaXN0YW5jZV9uZXcpKSArIGxhYnModGl0bGUgPSAiVG90YWwgRGlzdGFuY2UiLCBzdWJ0aXRsZSA9ICJGcmVxdWVuY3kgRGlzdHJpYnV0aW9ucyBieSBEYXkgb2YgdGhlIFdlZWsgKDQvMTIvMTYgLSA1LzEyLzE2KSIsIHggPSAiRGF5IiwgeSA9ICIgVG90YWwgRGlzdGFuY2UgKEttKSIsIGNvbG91ciA9ICJLbSIpCmBgYAoKQWx0aG91Z2ggd2UgbWlnaHQgYXNzdW1lIHRoYXQgY2Fsb3JpZXMgYnVybmVkIHdpbGwgYWxzbyBiZSBsb3dlciBvbiB0aGUgd2Vla2VuZHMsIGxldHMgbWFrZSBzdXJlOgoKYGBge3IgY2Fsb3JpZXMgYnVybmVkfQpnZ3Bsb3QoZGF0YSA9IGFjdGl2aXR5X2NsZWFuZWQpICsgZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKHggPSBkYXksIHkgPSBjYWxvcmllcywgY29sb3IgPSBjYWxvcmllcykpICsgZ2VvbV9ib3hwbG90KG1hcHBpbmcgPSBhZXMoeCA9IGRheSwgeSA9IGNhbG9yaWVzKSkgKyBsYWJzKHRpdGxlID0gIkNhbG9yaWVzIEJ1cm5lZCIsIHN1YnRpdGxlID0gIkZyZXF1ZW5jeSBEaXN0cmlidXRpb25zIGJ5IERheSBvZiB0aGUgV2VlayAoNC8xMi8xNiAtIDUvMTIvMTYpIiwgeCA9ICJEYXkiLCB5ID0gIiBDYWxvcmllcyIsIGNvbG91ciA9ICJDYWxvcmllcyIpCmBgYAoKT3VyIGh5cG90aGVzaXMgaXMgY29uZmlybWVkIGFsdGhvdWdoIGl0IHNob3VsZCBiZSBub3RlZCB0aGF0ICpUaHVyc2RheSdzIGF2ZXJhZ2Ugc2VlbXMgdG8gYmUgc2xpZ2h0bHkgbG93ZXIgdGhhbiBTYXR1cmRheSdzLioKCk5leHQsIEkgd2lsbCBwbG90IHRoZSBhY3RpdmUgbWludXRlcyBkaXN0cmlidXRpb25zIGJ5IHdlZWtkYXkuIFdlIHdpbGwgY29uc2lkZXIgdGhlIHRvdGFsIGFjdGl2ZSBtaW51dGVzIGJ5IGRheSBvZiB0aGUgd2VlaywgYW5kIHRoZW4gd2Ugd2lsbCBjb25zaWRlciB0aGUgZGlmZmVyZW50IGxldmVscyBvZiBhY3Rpdml0eSB3aXRoaW4gdGhlc2UgdG90YWxzLgoKYGBge3J9CmdncGxvdChkYXRhID0gYWN0aXZpdHlfY2xlYW5lZCkgKyBnZW9tX3BvaW50KG1hcHBpbmcgPSBhZXMoeCA9IGRheSwgeSA9IHRvdGFsX2FjdGl2ZV9taW51dGVzLCBjb2xvciA9IHRvdGFsX2FjdGl2ZV9taW51dGVzKSwgY29sb3IgPSAiYmx1ZSIpICsgZ2VvbV9ib3hwbG90KG1hcHBpbmcgPSBhZXMoeCA9IGRheSwgeSA9IHRvdGFsX2FjdGl2ZV9taW51dGVzKSkgKyBsYWJzKHRpdGxlID0gIlRvdGFsIEFjdGl2ZSBNaW51dGVzIiwgc3VidGl0bGUgPSAiRnJlcXVlbmN5IERpc3RyaWJ1dGlvbnMgYnkgRGF5IG9mIHRoZSBXZWVrICg0LzEyLzE2IC0gNS8xMi8xNikiLCB4ID0gIkRheSIsIHkgPSAiIFRvdGFsIEFjdGl2ZSBNaW51dGVzIikKYGBgCgoKSSBmb3VuZCB0aGF0IHRoZSB0b3RhbCBhY3RpdmUgbWludXRlcyBsb2dnZWQgYnkgRml0Yml0IHVzZXJzIHRlbmRzIHRvIGJlIGZhaXJseSBzdGFibGUgdGhyb3VnaG91dCB0aGUgd2VlayB3aXRoIHRoZSBleGNlcHRpb24gb2YgVGh1cnNkYXkuIFRoZSBkaXN0cmlidXRpb25zIGZvciB0aGUgd2Vla2VuZHMgYXJlIGFsc28gd2lkZXIgdGhhbiB0aGUgd2Vla2RheXMuCgpgYGB7ciAsIGVjaG89VFJVRX0KZ2dwbG90KGRhdGEgPSBhY3Rpdml0eV9jbGVhbmVkKSArIGdlb21fcG9pbnQobWFwcGluZyA9IGFlcyh4ID0gZGF5LCB5ID0gc2VkZW50YXJ5X21pbnV0ZXMsIGNvbG9yID0gc2VkZW50YXJ5X21pbnV0ZXMpLCBjb2xvciA9ICJncmVlbiIpICsgZ2VvbV9ib3hwbG90KG1hcHBpbmcgPSBhZXMoeCA9IGRheSwgeSA9IHNlZGVudGFyeV9taW51dGVzKSkgKyBsYWJzKHRpdGxlID0gIlNlZGVudGFyeSBNaW51dGVzIiwgc3VidGl0bGUgPSAiRnJlcXVlbmN5IERpc3RyaWJ1dGlvbnMgYnkgRGF5IG9mIHRoZSBXZWVrICg0LzEyLzE2IC0gNS8xMi8xNikiLCB4ID0gIkRheSIsIHkgPSAiU2VkZW50YXJ5IE1pbnV0ZXMiKQpgYGAKVGhlIGFtb3VudCBvZiBzZWRlbnRhcnkgbWludXRlcyBpcyBhbHNvIHByZXR0eSB1bmlmb3JtLCBhZ2FpbiB3aXRoIHRoZSBleGNlcHRpb24gb2YgVGh1cnNkYXkuIFNhdHVyZGF5IGhhcyBvbmUgb2YgdGhlIGhpZ2hlc3QgYXZlcmFnZXMgb2Ygc2VkZW50YXJ5IG1pbnV0ZXMsIHdoaWxlIFRodXJzZGF5IGhhcyBvbmUgb2YgdGhlIGxvd2VzdC4KCkkgbm90aWNlZCBpcyB0aGF0IFNhdHVyZGF5cyBoYWQgdGhlIGhpZ2hlc3QgYXZlcmFnZSBhbmQgbWF4aW11bSBvZiBsaWdodGx5IGFjdGl2ZSBtaW51dGVzIHdoaWxlIFN1bmRheSBoYWQgb25lIG9mIHRoZSBsb3dlc3QuIAoKYGBge3IgbGlnaHRseSBhY3RpdmUgbWludXRlcywgZWNobz1UUlVFfQpnZ3Bsb3QoZGF0YSA9IGFjdGl2aXR5X2NsZWFuZWQpICsgZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKHggPSBkYXksIHkgPSBsaWdodGx5X2FjdGl2ZV9taW51dGVzLCBjb2xvciA9IGxpZ2h0bHlfYWN0aXZlX21pbnV0ZXMpLCBjb2xvciA9ICJ2aW9sZXQiKSArIGdlb21fYm94cGxvdChtYXBwaW5nID0gYWVzKHggPSBkYXksIHkgPSBsaWdodGx5X2FjdGl2ZV9taW51dGVzKSkgKyBsYWJzKHRpdGxlID0gIkxpZ2h0bHkgQWN0aXZlIE1pbnV0ZXMiLCBzdWJ0aXRsZSA9ICJGcmVxdWVuY3kgRGlzdHJpYnV0aW9ucyBieSBEYXkgb2YgdGhlIFdlZWsgKDQvMTIvMTYgLSA1LzEyLzE2KSIsIHggPSAiRGF5IiwgeSA9ICJMaWdodGx5IEFjdGl2ZSBNaW51dGVzIikKYGBgCkkgbm90aWNlZCB0aGF0IHRoZSBhdmVyYWdlIG51bWJlciBvZiBmYWlybHkgYWN0aXZlIG1pbnV0ZXMgbG9nZ2VkIHdhcyBsb3dlciBvbiB0aGUgd2Vla2VuZHMuCgpgYGB7ciBmYWlybHkgYWN0aXZlIG1pbnV0ZXMsIGVjaG89VFJVRX0KZ2dwbG90KGRhdGEgPSBhY3Rpdml0eV9jbGVhbmVkKSArIGdlb21fcG9pbnQobWFwcGluZyA9IGFlcyh4ID0gZGF5LCB5ID0gZmFpcmx5X2FjdGl2ZV9taW51dGVzLCBjb2xvciA9IGZhaXJseV9hY3RpdmVfbWludXRlcyksIGNvbG9yID0gInB1cnBsZSIpICsgZ2VvbV9ib3hwbG90KG1hcHBpbmcgPSBhZXMoeCA9IGRheSwgeSA9IGZhaXJseV9hY3RpdmVfbWludXRlcykpICsgbGFicyh0aXRsZSA9ICJGYWlybHkgQWN0aXZlIE1pbnV0ZXMiLCBzdWJ0aXRsZSA9ICJGcmVxdWVuY3kgRGlzdHJpYnV0aW9ucyBieSBEYXkgb2YgdGhlIFdlZWsgKDQvMTIvMTYgLSA1LzEyLzE2KSIsIHggPSAiRGF5IiwgeSA9ICJGYWlybHkgQWN0aXZlIE1pbnV0ZXMiKQpgYGAKVGhlIHZlcnkgYWN0aXZlIG1pbnV0ZXMgc2VlbSB0byBwZWFrIE1vbiAtIFdlZCwgYnV0IGlmIHlvdSBsb29rIGF0IHRoZSBvdXRsaWVycywgaXQgc2VlbXMgdGhhdCBzb21lIHBlb3BsZSBkbyB0aGVpciBleGVyY2lzZSBvbiB0aGUgd2Vla2VuZHMgYmVjYXVzZSBTdW5kYXkgYW5kIFNhdHVyZGF5IGhhdmUgYSBmZXcgb3V0bGllcnMgd2hpbGUgdGhlIG1heGltdW1zIGZvciB0aGUgd2Vla2RheXMgYXJlIGxvd2VyLiAKCmBgYHtyIHZlcnkgYWN0aXZlIG1pbnV0ZXMsIGVjaG89VFJVRX0KZ2dwbG90KGRhdGEgPSBhY3Rpdml0eV9jbGVhbmVkKSArIGdlb21fcG9pbnQobWFwcGluZyA9IGFlcyh4ID0gZGF5LCB5ID0gdmVyeV9hY3RpdmVfbWludXRlcywgY29sb3IgPSB2ZXJ5X2FjdGl2ZV9taW51dGVzKSwgY29sb3IgPSAib3JhbmdlIikgKyBnZW9tX2JveHBsb3QobWFwcGluZyA9IGFlcyh4ID0gZGF5LCB5ID0gdmVyeV9hY3RpdmVfbWludXRlcykpICsgbGFicyh0aXRsZSA9ICJWZXJ5IEFjdGl2ZSBNaW51dGVzIiwgc3VidGl0bGUgPSAiRnJlcXVlbmN5IERpc3RyaWJ1dGlvbnMgYnkgRGF5IG9mIHRoZSBXZWVrICg0LzEyLzE2IC0gNS8xMi8xNikiLCB4ID0gIkRheSIsIHkgPSAiIFZlcnkgQWN0aXZlIE1pbnV0ZXMiKQpgYGAKCiMjIyMjIE9ic2VydmF0aW9ucyBmcm9tIHNsZWVwX2NsZWFuZWQKCkFzIHdpdGggdGhlIGRhaWx5IGFjdGl2aXR5LCB3ZSBjYW4gbG9vayBhdCBob3cgc2xlZXBpbmcgaG91cnMgYXMgd2VsbCBhcyBob3VycyBzcGVudCBhd2FrZSBpbiBiZWQgY29tcGFyZSBiZXR3ZWVuIGRheXMgb2YgdGhlIHdlZWsuIE5vdGUgdGhhdCB0aGlzIGRhdGEgZnJhbWUgaXMgbXVjaCBzbWFsbGVyIHRoYW4gdGhlIGFjdGl2aXR5IGRhdGEgZnJhbWUuIExldCdzIGJlZ2luIHdpdGggbWludXRlcyBhc2xlZXA6CgpgYGB7ciBzbGVlcCBzY2F0dGVyLCBlY2hvPVRSVUV9CmdncGxvdChkYXRhID0gc2xlZXBfY2xlYW5lZCkgKyBnZW9tX3BvaW50KG1hcHBpbmcgPSBhZXMoeCA9IGRheSwgeSA9IHRvdGFsX21pbnV0ZXNfYXNsZWVwLCBjb2xvciA9IHRvdGFsX21pbnV0ZXNfYXNsZWVwKSwgY29sb3IgPSAiYmx1ZSIpICsgZ2VvbV9ib3hwbG90KG1hcHBpbmcgPSBhZXMoeCA9IGRheSwgeSA9IHRvdGFsX21pbnV0ZXNfYXNsZWVwKSkgKyBsYWJzKHRpdGxlID0gIkRhaWx5IFNsZWVwIEF2ZXJhZ2VzIiwgc3VidGl0bGUgPSAiRnJlcXVlbmN5IERpc3RyaWJ1dGlvbnMgYnkgRGF5IG9mIHRoZSBXZWVrICg0LzEyLzE2IC0gNS8xMi8xNikiLCB4ID0gIkRheSIsIHkgPSAiVG90YWwgTWludXRlcyBBc2xlZXAiKQpgYGAKRnJvbSB0aGlzIHBsb3QsIGl0IGFwcGVhcnMgdGhhdCBTdW5kYXkgaXMgdGhlIG1vc3QgcG9wdWxhciBzbGVlcCBkYXkuIFNpbmNlIEkgcm91bmRlZCB0aGUgbWludXRlcyBpbnRvIGhvdXJzIGR1cmluZyB0aGUgY2xlYW5pbmcgcHJvY2VzcywgbGV0J3Mgc2VlIHdoYXQgdGhpcyBsb29rcyBsaWtlIGluIGhvdXJzIHVzaW5nIGEgaGlzdG9ncmFtOgoKYGBge3Igc2xlZXAgaW4gaG91cnMsIGVjaG89VFJVRX0KZ2dwbG90KGRhdGEgPSBzbGVlcF9jbGVhbmVkKSArIGdlb21fY29sKG1hcHBpbmcgPSBhZXMoeCA9IGRheSwgeSA9IGhvdXJzX2FzbGVlcCwgYWxwaGEgPSBob3Vyc19hc2xlZXApLCBjb2xvciA9ICJwdXJwbGUiKSArIGxhYnModGl0bGUgPSAiRGFpbHkgU2xlZXAgVG90YWxzIiwgc3VidGl0bGUgPSAiQSBIaXN0b2dyYW0gb2YgRGF0YSBMb2dnZWQgNC8xMi8xNiAtIDUvMTIvMTYiLCB4ID0gIkRheSIsIHkgPSAiSG91cnMgQXNsZWVwIiwgYWxwaGEgPSAiSG91cnMgKFJvdW5kZWQpIikKYGBgCldoaWxlIGF0IGZpcnN0IGl0IGFwcGVhcnMgdGhhdCB0aGUgaGlzdG9ncmFtIGNvbnRyYWRpY3RzIHRoZSBwcmV2aW91cyBncmFwaCwgdGhlIGZpbGwgc2hvd3MgdGhhdCBhIGxvdCBvZiBzbGVlcCBsb2dnZWQgb24gb24gd2Vla2VuZHMgaXMgbG9uZ2VyLCBidXQgbW9yZSBwZW9wbGUgYXJlIGxvZ2dpbmcgbW9yZSBzbGVlcCB0aHJvdWdob3V0IHRoZSB3ZWVrIGluIHNtYWxsZXIgYW1vdW50cy4gCgpTaW5jZSB0aGUgZGF0YSBmcmFtZSBjb250YWlucyB0b3RhbCBtaW51dGVzIGFzbGVlcCBhbmQgdG90YWwgdGltZSBpbiBiZWQsIEkgd2FudCB0byBzZWUgaG93IG11Y2ggdGltZSBwZW9wbGUgYXJlIHNwZW5kaW5nIGluIGJlZCBhd2FrZToKCmBgYHtyIGx5aW5uZ19hd2FrZTEsIGVjaG89VFJVRX0KZ2dwbG90KGRhdGEgPSBzbGVlcF9jbGVhbmVkKSArIGdlb21fcG9pbnQobWFwcGluZyA9IGFlcyh4ID0gZGF5LCB5ID0gbHlpbmdfYXdha2UsIGNvbG9yID0gbHlpbmdfYXdha2UpLCBjb2xvciA9ICJncmVlbiIpICsgZ2VvbV9ib3hwbG90KG1hcHBpbmcgPSBhZXMoeCA9IGRheSwgeSA9IGx5aW5nX2F3YWtlKSkgKyBsYWJzKHRpdGxlID0gIlRvdGFsIFRpbWUgQXdha2UgaW4gQmVkIiwgc3VidGl0bGUgPSAiRnJlcXVlbmN5IERpc3RyaWJ1dGlvbnMgYnkgRGF5IG9mIHRoZSBXZWVrICg0LzEyLzE2IC0gNS8xMi8xNikiLCB4ID0gIkRheSIsIHkgPSAiV2FraW5nIE1pbnV0ZXMiKQpgYGAKV2UgY2FuIHNlZSB0aGF0IHRoZSBkaXN0cmlidXRpb25zIGFyZSBwcmV0dHkgc2ltaWxhciBmcm9tIGRheSB0byBkYXksIGJ1dCBGcmlkYXksIApTYXR1cmRheSwgYW5kIFN1bmRheSBhbHNvIGhhdmUgc29tZSBvdXRsaWVycy4gSXQgbG9va3MgbGlrZSBhIGZldyBwZW9wbGUgbG9nZ2VkIGxvbmcgbWludXRlcyBhd2FrZSBpbiBiZWQgb24gdGhlIHdlZWtlbmRzLiBUaGlzIGNvdWxkIGJlIHRoZSByZWFzb24gd2h5IHRoZSBzY2F0dGVyIHBsb3QgYW5kIGhpc3RvZ3JhbSBkaWZmZXIgYXBwZWFyIGRpZmZlcmVudGx5IGF0IGZpcnN0IGdsYW5jZS4gTGV0J3MgY2hlY2sgdGhpcyBkYXRhIHdpdGggYSBoaXN0b2dyYW06IAoKYGBge3IgbHlpbmdfYXdha2UyLCBlY2hvPVRSVUV9CmdncGxvdChkYXRhID0gc2xlZXBfY2xlYW5lZCkgKyBnZW9tX2NvbChtYXBwaW5nID0gYWVzKHggPSBkYXksIHkgPSBseWluZ19hd2FrZSwgYWxwaGEgPSBseWluZ19hd2FrZSksIGNvbG9yID0gIm9yYW5nZSIpICsgbGFicyh0aXRsZSA9ICJXYWtpbmcgTWludXRlcyBUb3RhbHMiLCBzdWJ0aXRsZSA9ICJBIEhpc3RvZ3JhbSBvZiBEYXRhIExvZ2dlZCA0LzEyLzE2IC0gNS8xMi8xNiIsIHggPSAiRGF5IiwgeSA9ICJXYWtpbmcgTWludXRlcyIsIGFscGhhID0gIk1pbnV0ZXMiKQpgYGAKCkZyb20gdGhpcyB3ZSBjYW4gc2VlIHRoYXQgYSBsb3Qgb2YgcGVvcGxlIGFyZSBseWluZyBhd2FrZSBpbiBiZWQgb24gb2ZmIGRheXMgd2hpbGUgc29tZSBwcmVmZXIgdG8gZ2V0IGEgbG90IG9mIHNsZWVwLiAKClRoZXJlIGlzIG9uZSBtb3JlIHdheSB0aGF0IEkgd2FudCB0byB2aXN1YWxpemUgdGhpcyBkYXRhOgoKYGBge3IgdG90YWwgdGltZSBpbiBiZWQsIGVjaG89VFJVRX0KZ2dwbG90KGRhdGEgPSBzbGVlcF9jbGVhbmVkKSArIGdlb21fY29sKG1hcHBpbmcgPSBhZXMoeCA9IGRheSwgeSA9IHRvdGFsX3RpbWVfaW5fYmVkLCBmaWxsID0gInJlZCIpKSArCiAgZ2VvbV9jb2wobWFwcGluZyA9IGFlcyh4ID0gZGF5LCB5ID0gbHlpbmdfYXdha2UsIGZpbGwgPSAiYmx1ZSIpKSAgKyBsYWJzKHRpdGxlID0gIlRvdGFsIFRpbWUgaW4gQmVkIEJyZWFrZG93biIsIHN1YnRpdGxlID0gIkF3YWtlIHZzLiBBc2xlZXAgNC8xMi8xNiAtIDUvMTIvMTYiLCB4ID0gIkRheSIsIHkgPSAiVG90YWwgTWludXRlcyIsIGZpbGwgPSAiTWludXRlcyIpICsgc2NhbGVfZmlsbF9odWUobGFiZWxzID0gYygiV2FraW5nIE1pbnV0ZXMiLCAiVG90YWwgTWludXRlcyBpbiBCZWQiKSkKYGBgClRoaXMgdmlzdWFsaXphdGlvbiBpcyBwZXJoYXBzIHRoZSBjbGVhcmVzdCB3YXkgdG8gdW5kZXJzdGFuZCB0aGUgcHJvcG9ydGlvbmFsIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHdha2luZyBtaW51dGVzIGFuZCB0b3RhbCB0aW1lIGluIGJlZC4gV2UgY2FuIHNlZSB0aGF0IEZyaSAtIFN1biBoYXMgcHJvcG9ydGlvbmFsbHkgbGFyZ2UgcG9ydGlvbnMgb2YgdGltZSBzcGVudCBhd2FrZSBpbiBiZWQuCgojIyMjIyBPYnNlcnZhdGlvbnMgZnJvbSB3ZWlnaHRfY2xlYW5lZAoKVGhlIHdlaWdodCBjbGVhbmVkIGRhdGEgZnJhbWUgaGFzIGEgdmVyeSBzbWFsbCBudW1iZXIgb2Ygcm93cyBjb21wYXJlZCB0byB0aGUgb3RoZXIgZGF0YSBmcmFtZXMuIEluc3RlYWQgb2YgZm9jdXNpbmcgZXhjbHVzaXZlbHkgb24gdGhlIG51bWJlcnMgbG9nZ2VkLCBJIHdhbnRlZCB0byB0aGluayBhYm91dCB0aGUgKmJlaGF2aW9yKiBvZiB0aGUgcGFydGljaXBhbnRzIHNpbmNlIHNvIGZldyBvZiB0aGVtIGxvZ2dlZCB0aGVpciB3ZWlnaHRzLiBPbmUgb2YgdGhlIGNvbHVtbnMgY29udGFpbnMgQm9vbGVhbnMgdG8gdGhlIHN0YXRlbWVudCAiaXMgbWFudWFsIHJlcG9ydC4iIEkgYW0gY3VyaW91cyB0byBzZWUgaG93IHRoZSB3ZWlnaHRzIGNvbXBhcmUgd2hlbiBsb2dnZWQgaW4gbWFudWFsbHkgdG8gd2hlbiB0aGV5IGFyZSBsb2dnZWQgaW4gYXV0b21hdGljYWxseS4gTGV0J3MgdGFrZSBhIGxvb2sgYXQgdGhlIGZvbGxvd2luZyBwbG90OiAKCmBgYHtyIG1hbnVhbCB2cyBhdXRvLCBlY2hvPVRSVUV9CmdncGxvdChkYXRhID0gd2VpZ2h0X2NsZWFuZWQpICsgZ2VvbV92aW9saW4obWFwcGluZyA9IGFlcyh4ID0gaXNfbWFudWFsX3JlcG9ydCwgeSA9IHdlaWdodF9rZykpICsgbGFicyh0aXRsZSA9ICJNYW51YWwgdnMuIEF1dG9tYXRpYyBMb2dnaW5nIG9mIFdlaWdodCIsIHN1YnRpdGxlID0gIldoeSBhcmUgTWFudWFsbHkgUmVjb3JkZWQgV2VpZ2h0cyBMb3dlcj8iLCB4ID0gIk1hbnVhbCBMb2c/IiwgeSA9ICJXZWlnaHQgKGtnKSIpCmBgYApXaGlsZSBJIGNhbid0IG1ha2UgYSBkZWZpbml0aXZlIGNvbmNsdXNpb24gZnJvbSBzbyBzbWFsbCBhIHNhbXBsZSwgSSB3b3VsZCB3YW50IHRvIGV4cGxvcmUgdXNlciBiaWFzIHRoYXQgbWF5IHNrZXcgdGhlIGRhdGEgdGhleSBlbnRlci4gSXQgYXBwZWFycyB0aGF0IHdlaWdodCBvZiBwZW9wbGUgd2hvIGVudGVyIHdlaWdodCBtYW51YWxseSBpcyBtdWNoIGxvd2VyIHRoYW4gdGhhdCBvZiBwZW9wbGUgd2hvIGFyZSBsb2dnZWQgYXV0b21hdGljYWxseS4KCiMjIyMjIE9ic2VydmF0aW9ucyBmcm9tIGhlYXJ0X3JhdGVfY2xlYW5lZAoKVGhlIGhlYXJ0IHJhdGUgZGF0YSBmcmFtZSBjb250YWlucyBoZWFydCByYXRlIGluZm9ybWF0aW9uIGZvciBlYWNoIHVzZXIgYnkgdGhlICpzZWNvbmQqLCBhbmQgdGhpcyBkYXRhIGZyYW1lIGNvbnRhaW5zICoqMiw0ODMsNjU4IHJvd3MqKi4gSW5zdGVhZCBvZiBsb29raW5nIGF0IGRhaWx5IHRyZW5kcywgSSB3YW50IHRvIGZvY3VzIG9uIHRoZSBoaWdoIGFuZCBsb3cgaGVhcnQgcmF0ZXMgdG8gdHJ5IHRvIGd1ZXNzIGhvdyBwZW9wbGUgYXJlIHVzaW5nIHRoZWlyIEZpdGJpdHMgYW5kIGlmIEkgc2VlIGFueXRoaW5nIGFibm9ybWFsLiBMb29raW5nIGF0IGFuIGFydGljbGUgcHVibGlzaGVkIGJ5IFtDbGV2ZWxhbmQgQ2xpbmljXShodHRwczovL215LmNsZXZlbGFuZGNsaW5pYy5vcmcvaGVhbHRoL2RpYWdub3N0aWNzLzE3NDAyLXB1bHNlLS1oZWFydC1yYXRlKSwgSSBjYW4gc2VlIHRoYXQgZm9yIG5vcm1hbCBhZHVsdHMsIDYwIHRvIDEwMCBiZWF0cyBwZXIgbWludXRlIGlzIGNvbnNpZGVyZWQgbm9ybWFsLCBzbyBJIHdpbGwgZmlsdGVyIG91dCB0aGUgZXh0cmVtZXMuIEkgd2lsbCBjcmVhdGUgdmFyaWFibGVzIHRvIGhlbHAgZmlsdGVyIHRoZSBkYXRhOgoKYGBge3IgZmlsdGVyaW5nIGhlYXJ0IHJhdGVzLCBlY2hvPVRSVUV9CmhlYXJ0X3JhdGVfaGlnaCA8LSBoZWFydF9yYXRlX2NsZWFuZWQgJT4lIHN1YnNldChoZWFydF9yYXRlID49IDEwMCkKc2tpbShoZWFydF9yYXRlX2hpZ2gpCmhlYXJ0X3JhdGVfbG93IDwtIGhlYXJ0X3JhdGVfY2xlYW5lZCAlPiUgc3Vic2V0KGhlYXJ0X3JhdGUgPD0gNjApCnNraW0oaGVhcnRfcmF0ZV9sb3cpCmBgYApOb3cgd2UgY2FuIHBsb3QgdGhpcyBkYXRhOgoKYGBge3IgaGVhcnRfcmF0ZSwgZWNobz1UUlVFfQpnZ3Bsb3QoZGF0YSA9IGhlYXJ0X3JhdGVfaGlnaCkgKyBnZW9tX2JhcihtYXBwaW5nID0gYWVzKHggPSBoZWFydF9yYXRlKSwgZmlsbCA9ICJyZWQiKSArIGxhYnModGl0bGUgPSAiSGlnaCBIZWFydCBSYXRlIiwgc3VidGl0bGUgPSAiRnJlcXVlbmNlcyA0LzEyLzE2IC0gNS8xMi8xNiIsIHggPSAiSGVhcnQgUmF0ZXMgKEJQTSkiLCB5ID0gIkZyZXF1ZW5jeSIpICsgYW5ub3RhdGUoInRleHQiLCB4ID0gMTUwLCB5ID0gMTAwMDAsIGxhYmVsID0gIkhlYXJ0IFJhdGUgPj0gMTAwIEJQTSIsIGNvbG9yID0gInJlZCIsIGZvbnRmYWNlID0gImJvbGQiLCBzaXplID0gNikKZ2dwbG90KGRhdGEgPSBoZWFydF9yYXRlX2xvdykgKyBnZW9tX2JhcihtYXBwaW5nID0gYWVzKHggPSBoZWFydF9yYXRlKSwgZmlsbCA9ICJibHVlIikgKyBsYWJzKHRpdGxlID0gIkxvdyBIZWFydCBSYXRlIiwgc3VidGl0bGUgPSAiRnJlcXVlbmNlcyA0LzEyLzE2IC0gNS8xMi8xNiIsIHggPSAiSGVhcnQgUmF0ZXMgKEJQTSkiLCB5ID0gIkZyZXF1ZW5jeSIpICsgYW5ub3RhdGUoInRleHQiLCB4ID0gNDMsIHkgPSA0MDAwMCwgbGFiZWwgPSAiSGVhcnQgUmF0ZSA8PSA2MCBCUE0iLCBjb2xvciA9ICJibHVlIiwgZm9udGZhY2UgPSAiYm9sZCIsIHNpemUgPSA2KQpgYGAKRnJvbSB0aGlzIGRhdGEsIEkgY2FuIHNlZSB0aGF0IGJvdGggaGlnaCBhbmQgbG93IGhlYXJ0IHJhdGVzIGhhdmUgb2NjdXJyZWQgYW1vdW50IHRoZSBwYXJ0aWNpcGFudHMuIFRoaXMgbWVhbnMgdGhhdCB0aGUgcGFydGljaXBhbnRzIGFyZSB1c2luZyB0aGVpciBGaXRiaXRzIGZvciBzbGVlcCBhbmQgaW50ZW5zZSBwaHlzaWNhbCBhY3Rpdml0eS4gU29tZSBvZiB0aGUgcGFydGljaXBhbnRzIG1heSBiZSBvbGRlciwgb3IgdGhleSBtYXkgYmUgYXRobGV0ZXMsIHNvIEkgY2Fubm90IHNheSBmb3IgY2VydGFpbiBpZiB0aGUgaGVhcnQgcmF0ZXMgYXJlIGF0IGRhbmdlcm91cyBsZXZlbHMgZm9yIHRoZSBpbmRpdmlkdWFscyB3aG8gbG9nZ2VkIHRoZW0uICpJIHdvdWxkIHdhbnQgbXkgZml0bmVzcyB0cmFja2VyIHRvIGFsZXJ0IG1lIGlmIG15IGhlYXJ0IHJhdGUgd2FzIGF0IGFuIGV4dHJlbWUgbGV2ZWwsIGxpa2UgNDMgYnBtIG9yIDE4MCBicG0uKgoKIyMjIyMgT2JzZXJ2YXRpb25zIGZyb20gYWN0aXZpdHlfc2xlZXBfY29tYmluZWQKCkZpbmFsbHksIEkgd2FudCB0byBzZWUgaWYgdGhlcmUgaXMgYW55IGNvbm5lY3Rpb24gYmV0d2VlbiBzbGVlcCBhbmQgY2Fsb3JpZXMgdG8gdHJ5IHRvIGRpc2NvdmVyIHRyZW5kcyBpbiB0aGUgZGF0YSBsb2dnZWQgYnkgRml0Yml0IHVzZXJzOgoKYGBge3IgY2Fsb3JpZXMgdG8gc2xlZXAsIGVjaG89VFJVRX0KZ2dwbG90KGRhdGEgPSBhY3Rpdml0eV9zbGVlcF9jb21iaW5lZCkgKyAKICBnZW9tX2NvbChtYXBwaW5nID0gYWVzKHggPSBob3Vyc19hc2xlZXAsIHkgPSBjYWxvcmllcywgYWxwaGEgPSBjYWxvcmllcyksIGZpbGwgPSAicmVkIikgKyBsYWJzKHRpdGxlID0gIlNsZWVwIGFuZCBDYWxvcmllcyIsIHN1YnRpdGxlID0gIlRvdGFscyA0LzEyLzE2IC0gNS8xMi8xNiIsIHggPSAiSG91cnMgQXNsZWVwIiwgeSA9ICJDYWxvcmllcyIsIGFscGhhID0gIkNhbG9yaWVzIikKYGBgCkZyb20gdGhpcyBwbG90LCB3ZSBjYW4gc2VlIHRoYXQgbW9zdCBjYWxvcmllcyBidXJuZWQsIGluY2x1ZGluZyB0aGUgaGlnaC1sZXZlbCBidXJucyBhcmUgbG9nZ2VkIGJ5IHVzZXJzIHdobyBnZXQgYmV0d2VlbiA2IGFuZCA4IGhvdXJzIG9mIHNsZWVwIGEgbmlnaHQuIFRoaXMgbWlnaHQgYmUgYmVjYXVzZSB0aGlzIGlzIGhvdyBtdWNoIG1vc3QgcGVvcGxlIHNsZWVwIG9uIGF2ZXJhZ2UsIGJ1dCB3ZSBrbm93IHNjaWVudGlmaWNhbGx5IHRoYXQgc2xlZXAgaXMgZXNzZW50aWFsIGZvciBvdmVyYWxsIHdlbGxuZXNzLgoKIyMgQ29uY2x1c2lvbnMgYW5kIFJlY29tbWVuZGF0aW9ucwoKIyMjIyBTdW1tYXJ5IG9mIEZpbmRpbmdzCgpCeSB2aXN1YWxpemluZyB0aGUgY2xlYW5lZCBkYXRhLCBJIG1hZGUgdGhlIGZvbGxvd2luZyBvYnNlcnZhdGlvbnM6CgoqIFBlb3BsZSBhcmUgbG9nZ2luZyBsZXNzIGFjdGl2aXR5IG9uIHRoZSB3ZWVrZW5kcyB3aXRoIHRoZSBleGNlcHRpb24gb2YgYSBmZXcgb3V0bGllcnMgd2hvIGxvZ2dlZCBpbnRlbnNlIHdvcmtvdXRzLgoKKiBQZW9wbGUgbG9nIHRoZSBtb3N0IHdha2luZyBtaW51dGVzIGluIGJlZCBvbiBGcmkgLSBTdW4uCgoqIFBlb3BsZSBhcmUgdXNpbmcgdGhlaXIgRml0Yml0cyB0byB0cmFjayBhY3Rpdml0eSBtb3JlIHRoYW4gdGhleSB1c2UgdGhlbSB0byBrZWVwIHRyYWNrIG9mIHdlaWdodCwgc2xlZXAsIGFuZCBoZWFydCByYXRlIChiYXNlZCBvbiB0aGUgdG90YWwgbnVtYmVyIG9mIHVuaXF1ZSBJRHMgZm9yIGVhY2ggZGF0YSBzZXQpLgoKKiBQZW9wbGUgd2hvIGxvZyB0aGVpciB3ZWlnaHRzIG1hbnVhbGx5IHRlbmQgdG8gbG9nIGxpZ2h0ZXIgd2VpZ2h0cyB0aGFuIHRob3NlIHdobyBkb24ndC4KCiogU29tZSBwZW9wbGUgYXJlIGxvZ2dpbmcgZXh0cmVtZSBoZWFydCByYXRlcywgYm90aCBmYXN0IGFuZCBzbG93LiAqVGhlc2UgY291bGQgYmUgZGFuZ2Vyb3VzIGRlcGVuZGluZyBvbiB0aGUgYWN0aXZpdHksIGFnZSwgYW5kIGZpdG5lc3MtbGV2ZWwgb2YgdGhlIHVzZXIuKgoKIyMjIyBSZWNvbW1lbmRhdGlvbnMKCkZvciBteSByZWNvbW1lbmRhdGlvbnMsIEkgbXVzdCBjaG9vc2Ugb25lIEJlbGxhYmVhdCBwcm9kdWN0IHRvIGFwcGx5IHRoZW0gdG9vLiBUaGUgY2FzZSBzdHVkeSBkZXNjcmliZXMgdGhlIGZvbGxvd2luZyBwcm9kdWN0cyB0aHVzbHk6CgoqICoqQmVsbGFiZWF0IGFwcDoqKiBUaGUgQmVsbGFiZWF0IGFwcCBwcm92aWRlcyB1c2VycyB3aXRoIGhlYWx0aCBkYXRhIHJlbGF0ZWQgdG8gdGhlaXIgYWN0aXZpdHksIHNsZWVwLCBzdHJlc3MsCm1lbnN0cnVhbCBjeWNsZSwgYW5kIG1pbmRmdWxuZXNzIGhhYml0cy4gVGhpcyBkYXRhIGNhbiBoZWxwIHVzZXJzIGJldHRlciB1bmRlcnN0YW5kIHRoZWlyIGN1cnJlbnQgaGFiaXRzIGFuZAptYWtlIGhlYWx0aHkgZGVjaXNpb25zLiBUaGUgQmVsbGFiZWF0IGFwcCBjb25uZWN0cyB0byB0aGVpciBsaW5lIG9mIHNtYXJ0IHdlbGxuZXNzIHByb2R1Y3RzLgoKKiAqKkxlYWY6KiogQmVsbGFiZWF04oCZcyBjbGFzc2ljIHdlbGxuZXNzIHRyYWNrZXIgY2FuIGJlIHdvcm4gYXMgYSBicmFjZWxldCwgbmVja2xhY2UsIG9yIGNsaXAuIFRoZSBMZWFmIHRyYWNrZXIgY29ubmVjdHMgdG8gdGhlIEJlbGxhYmVhdCBhcHAgdG8gdHJhY2sgYWN0aXZpdHksIHNsZWVwLCBhbmQgc3RyZXNzLgoKKiAqKlRpbWU6KiogVGhpcyB3ZWxsbmVzcyB3YXRjaCBjb21iaW5lcyB0aGUgdGltZWxlc3MgbG9vayBvZiBhIGNsYXNzaWMgdGltZXBpZWNlIHdpdGggc21hcnQgdGVjaG5vbG9neSB0byB0cmFjayB1c2VyIGFjdGl2aXR5LCBzbGVlcCwgYW5kIHN0cmVzcy4gVGhlIFRpbWUgd2F0Y2ggY29ubmVjdHMgdG8gdGhlIEJlbGxhYmVhdCBhcHAgdG8gcHJvdmlkZSB5b3Ugd2l0aCBpbnNpZ2h0cyBpbnRvIHlvdXIgZGFpbHkgd2VsbG5lc3MuCgoqICoqU3ByaW5nOioqIFRoaXMgaXMgYSB3YXRlciBib3R0bGUgdGhhdCB0cmFja3MgZGFpbHkgd2F0ZXIgaW50YWtlIHVzaW5nIHNtYXJ0IHRlY2hub2xvZ3kgdG8gZW5zdXJlIHRoYXQgeW91IGFyZQphcHByb3ByaWF0ZWx5IGh5ZHJhdGVkIHRocm91Z2hvdXQgdGhlIGRheS4gVGhlIFNwcmluZyBib3R0bGUgY29ubmVjdHMgdG8gdGhlIEJlbGxhYmVhdCBhcHAgdG8gdHJhY2sgeW91cgpoeWRyYXRpb24gbGV2ZWxzLgoKKiAqKkJlbGxhYmVhdCBtZW1iZXJzaGlwOioqIEJlbGxhYmVhdCBhbHNvIG9mZmVycyBhIHN1YnNjcmlwdGlvbi1iYXNlZCBtZW1iZXJzaGlwIHByb2dyYW0gZm9yIHVzZXJzLgpNZW1iZXJzaGlwIGdpdmVzIHVzZXJzIDI0LzcgYWNjZXNzIHRvIGZ1bGx5IHBlcnNvbmFsaXplZCBndWlkYW5jZSBvbiBudXRyaXRpb24sIGFjdGl2aXR5LCBzbGVlcCwgaGVhbHRoIGFuZApiZWF1dHksIGFuZCBtaW5kZnVsbmVzcyBiYXNlZCBvbiB0aGVpciBsaWZlc3R5bGUgYW5kIGdvYWxzLgoKSSB3aWxsIGNob29zZSB0aGUgKipCZWxsYWJlYXQgbWVtYmVyc2hpcCoqIGJlY2F1c2UgaXQgaXMgYWNjZXNzZWQgdGhyb3VnaCB0aGUgYXBwIGFuZCBjYW4gYmUgaGlnaGx5IGN1c3RvbWl6ZWQuIEJlbGxhYmVhdCBjYW4gZW5jb3VyYWdlIHVzZXJzIHRvIGFuYWx5emUgdGhlaXIgZGF0YSBiYXNlZCBvbiB0aGUgdW5pcXVlIG5lZWRzIG9mIHRoZWlyIGJvZGllcy4gSGVyZSBhcmUgbXkgdG9wIHJlY29tbWVuZGF0aW9ucyBmb3IgdGhlIG1hcmtldGluZyB0ZWFtOgoKIyMjIyMgSGVhcnQgUmF0ZSBBbGVydCBTeXN0ZW0KCk1ha2Ugc3VyZSB0aGF0IHRoZSBCZWxsYWJlYXQgbWVtYmVyc2hpcCBpbmNsdWRlcyBhIGhlYXJ0IHJhdGUgYWxlcnQgc3lzdGVtIGJhc2VkIG9uIGFnZSBhbmQgYm1pLiBUaGlzIGNvdWxkIGJlIGEgaHVnZSBzZWxsaW5nIHBvaW50IGZvciBvbGRlciBjdXN0b21lcnMgYmVjYXVzZSBCZWxsYWJlYXQgY3VycmVudGx5IHNlZW1zIHRvIG1hcmtldCB0aGVtc2VsdmVzIHRvIHlvdW5nZXIgd29tZW4gKGp1ZGdpbmcgZnJvbSB0aGVpciB3ZWJzaXRlKS4gQSB3ZWxsIGRlc2lnbmVkIGFsZXJ0IHN5c3RlbSBwYWlyZWQgd2l0aCBhIHJlbGlhYmxlIHRyYWNraW5nIGRldmljZSBjb3VsZCBzYXZlIGFuIG9sZGVyIHdvbWFuJ3MgbGlmZSBhbmQgaGVscCBCZWxsYWJlYXQgY29tcGV0ZSB3aXRoIHRyYWRpdGlvbmFsIGVtZXJnZW5jeSBkZXZpY2VzIGZvciBzZW5pb3JzIGxpa2UgW0xpZmUgQWxlcnRdKGh0dHA6Ly93d3cubGlmZWFsZXJ0LmNvbS8pLiAKCiMjIyMjIEJldHRlciBXZWlnaHQgVHJhY2tpbmcKClRyeSB0byBzeW5jaHJvbml6ZSB0aGUgYXBwIHdpdGggYSB3ZWlnaGluZyBzeXN0ZW0gdG8gZW5jb3VyYWdlIHVzZXJzIHRvIHRyYWNrIHRoZWlyIHdlaWdodHMgZGlnaXRhbGx5IGFuZCBub3QgaGF2ZSB0byBlbnRlciB0aGVtIG1hbnVhbGx5LiBXaGlsZSB0aGVyZSB3YXMgbm90IGVub3VnaCBkYXRhIHRvIGJlIGNlcnRhaW4sIHRoZSBudW1iZXJzIChhbmQgaHVtYW4gcHJpZGUpIHNlZW0gdG8gaW5kaWNhdGUgdGhhdCB3ZWlnaHRzIGVudGVyZWQgbWFudWFsbHkgYXJlIG5vdCBhcyByZWxpYWJsZSBhcyB0aG9zZSBkb25lIGF1dG9tYXRpY2FsbHkuCgojIyMjIyBBSSBTbGVlcCBDb2FjaAoKQ3JlYXRlIGFuIEFJIHNsZWVwIGNvYWNoIHRoYXQgd2lsbCBoZWxwIHVzZXJzIHRyYWNrIHRoZWlyIHNsZWVwIGFuZCBjcmVhdGUgYmV0dGVyIHNsZWVwaW5nIGhhYml0cy4gU3luY2luZyB0aGlzIHRvIGEgZGV2aWNlIHRoYXQgc3BlY2lhbGl6ZXMgaW4gY29tZm9ydCBmb3Igc2xlZXBpbmcgd291bGQgcGVyaGFwcyBtYWtlIG1vcmUgY3VzdG9tZXJzIGxvZyB0aGVpciBzbGVlcCByZWNvcmRzIG9uIGEgcmVndWxhciBiYXNpcy4gQWR2ZXJ0aXNpbmcgZm9yIGZpdG5lc3MgdHJhY2tlcnMgKGluY2x1ZGluZyBCZWxsYWJlYXQncykgaXMgb2Z0ZW4gZmlsbGVkIHdpdGggaW1hZ2VzIG9mIE9seW1waWMtbGV2ZWwgYXRobGV0ZXMgcnVubmluZyBtYXJhdGhvbnMgb3Igc3RyaWtpbmcgYWR2YW5jZWQgeW9nYSBwb3Nlcy4gSHVtYW5zIG5lZWQgc2xlZXAgdG8gYmUgYWJsZSB0byBkbyB0aGVzZSBraW5kcyBvZiBleHRyZW1lIGFjdGl2aXRpZXMgYW5kIGFueSBraW5kIG9mIGFjdGl2aXR5IGF0IGFsbC4gV2hpbGUgc29tZSB1c2VycyBtaWdodCBiZSBzbGVlcGluZyBsaWdodGx5IG9uIHRoZSB3ZWVrZW5kcyBvdXQgb2YgaGFiaXQsIHRoZSBBSSBzbGVlcCBjb2FjaCBjb3VsZCByZW1pbmQgdXNlcnMgdG8gZ2V0IGluIHNvbWUgbGlnaHQgYWN0aXZpdHkgaW5zdGVhZCBvZiBiZWluZyBzZWRlbnRhcnkuVGhlIEFJIGNvYWNoIGNhbiBkZXRlcm1pbmUgd2hhdCBpcyBoZWFsdGh5IGZvciB0aGUgdXNlci4KCiMjIEZpbmFsIFRob3VnaHRzCgpCZWxsYWJlYXQgaGFzIGRvbmUgZ29vZCB3b3JrIGluIGZpbmRpbmcgYSBuaWNoZSBpbiB0aGUgZ3Jvd2luZyBtYXJrZXQgb2YgZml0bmVzcyB0cmFja2Vycy4gQnkgZm9jdXNpbmcgb24gd29tZW4sIHRoZXkgaGF2ZSBtYWRlIHRoZWlyIHByb2R1Y3RzIHBlcnNvbmFibGUgdG8gdGhlaXIgY3VzdG9tZXJzLiBCZWxsYWJlYXQgY2FuIHRha2UgdGhlaXIgbWFya2V0aW5nIHN0cmF0ZWd5IGEgc3RlcCBmdXJ0aGVyIGJ5IGZvY3VzaW5nIG9uICJob2xlcyIgaW4gY3VycmVudCBmaXRuZXNzIHRyYWNrZXIgYWR2ZXJ0aXNlbWVudC4gSW5zdGVhZCBvZiB0cnlpbmcgdG8gdGFrZSBvbiBjb21wYW5pZXMgbGlrZSBGaXRiaXQgaW4gdGhlIGFyZWEgb2YgZXhlcmNpc2UgZGF0YSBsb2dnaW5nLCB0aGV5IGhhdmUgYW4gb3Bwb3J0dW5pdHkgdG8gcmVhY2ggYSB3aWRlciBhdWRpZW5jZSBieSBmb2N1c2luZyBvbiB3ZWxsbmVzcyBiZXlvbmQgcGh5c2ljYWwgZXhlcnRpb24uIEJ5IG1hcmtldGluZyB0byBzZW5pb3JzIGFuZCBpbXByb3ZpbmcgdGhlaXIgc2xlZXAgdHJhaW5pbmcgcHJvZ3JhbSwgdGhleSBjYW4gcmVhY2ggYSB3aWRlciBhdWRpZW5jZSBhbmQgbWFrZSBhIHNpZ25pZmljYW50IGltcGFjdCBpbiB0aGUgbGl2ZXMgb2YgbW9yZSB3b21lbi4KCgoK