INTRODUCTION

Since almost two years, me and a friend who produces music are looking for a tool : a generator of chords to help beginners to produce music.

Example of a magical chord : https://youtu.be/nhivdSMdMxc

Similar project : https://autochords.com/

Our initial idea was consisted of two parts/ processes:

1 : Importation and analysis of trends

2 : Reverse process :

CRISP Model : Understanding

Problems Bullet Point :

-R or Python or the two

-How to manipulate songs in a database (extern links, midi,wav … ???)

=> MIDI refs : https://www.kaggle.com/code/wfaria/midi-music-data-extraction-using-music21/notebook

-How to compare different databases

-Four chords can may be in different orders but still songs good)

  • “The Call” through “Neopolitan Dreams” are G, D, Em, C

  • “As Long As You Love Me” is C, G, Em, D

  • “Round Here” is C, D, Em, G -“Drunk” is Em, D, G, C

  • “We Are Never Ever Getting Back Together” is C, G, D, Em

  • “I Knew You Were Trouble” is G, D, Em, C (verses) and Em, C, D, G (chorus)

=> I, III, VI dominante et peuvent être interchangées

II and IV sous dominante et peuvent être interchangées

V and VII dominantes et peuvent être interchangées

MINOR CHORD PROGRESSION = i ii° III iv v VI VII

MAJOR CHORD PROGRESSION = I ii iii IV V vi vii°

Business understanding

After few benchmarks we still never find an app well optimized to provide accords depending on actual trends so we know there is a place on the market of “music software and FL’s plug-in” for an app like that.

The principal goals of the app are :

-Depending on input/filters as : genre, favorite artist, country, favorites keys etc …

=> TO:

-Give a key

-Play randomly the best chords

-Export accords as .midi files

-Giving the degrees https://www.youtube.com/watch?v=v3YbEL-_eoI&t=97s&ab_channel=DavidBennettPiano

Data understanding

  1. Last summer I created a first data base with notes, accords and music scale with Fl Studio. => .wav / .fl / .mp3

  2. To import top charts Spotify I have few options :

- Databases on Kaggle/Git-Hub (!! ‘Keys’ are not on every db)

- Creating my own database with a Spotify API (4 sure I have keys)

- Mix of the two previous options

  1. The big problem is to import accords and melodies, the only website I find is : https://www.boiteachansons.net/ and it’s most only pop and rock frenchs songs …

  2. Pep’s Excel files with harmonization and degrees and Chords progressions.

https://www.youtube.com/watch?v=v3YbEL-_eoI&t=97s&ab_channel=DavidBennettPiano

For this project, my objectives are to :

- Import and make analysis of top charts from Spotify to see what’s can be useful in the future on one part

- Import and make analysis of the chords from : boiteachansons.net

-Make a reverse code to output recurrent chords (if there are) from the data I imported with boiteachansons.net and randomize then by using degrees and harmonization.

Track’s audio features I am looking for from Spotify :

https://developer.spotify.com/documentation/web-api/reference/#/operations/get-several-audio-features

Danceability: Describes how suitable a track is for dancing based on a combination of musical elements including tempo, rhythm stability, beat strength, and overall regularity. A value of 0.0 is least danceable and 1.0 is most danceable.

Energy: Is a measure from 0.0 to 1.0 and represents a perceptual measure of intensity and activity. Typically, energetic tracks feel fast, loud, and noisy. For example, death metal has high energy, while a Bach prelude scores low on the scale. Perceptual features contributing to this attribute include dynamic range, perceived loudness, timbre, onset rate, and general entropy.

Key: The key the track is in. Integers map to pitches using standard Pitch Class notation. E.g. 0 = C, 1 = C♯/D♭, 2 = D, and so on.

Loudness: The overall loudness of a track in decibels (dB). Loudness values are averaged across the entire track and are useful for comparing relative loudness of tracks. Loudness is the quality of a sound that is the primary psychological correlate of physical strength (amplitude). Values typical range between -60 and 0 db.

Mode: Mode indicates the modality (major or minor) of a track, the type of scale from which its melodic content is derived. Major is represented by 1 and minor is 0.

Speechiness: Speechiness detects the presence of spoken words in a track. The more exclusively speech-like the recording (e.g. talk show, audio book, poetry), the closer to 1.0 the attribute value. Values above 0.66 describe tracks that are probably made entirely of spoken words. Values between 0.33 and 0.66 describe tracks that may contain both music and speech, either in sections or layered, including such cases as rap music. Values below 0.33 most likely represent music and other non-speech-like tracks.

Acousticness: A confidence measure from 0.0 to 1.0 of whether the track is acoustic. 1.0 represents high confidence the track is acoustic.

Instrumentalness: Predicts whether a track contains no vocals. “Ooh” and “aah” sounds are treated as instrumental in this context. Rap or spoken word tracks are clearly “vocal”. The closer the instrumentalness value is to 1.0, the greater likelihood the track contains no vocal content. Values above 0.5 are intended to represent instrumental tracks, but confidence is higher as the value approaches 1.0.

Liveness: Detects the presence of an audience in the recording. Higher liveness values represent an increased probability that the track was performed live. A value above 0.8 provides strong likelihood that the track is live.

Valence: A measure from 0.0 to 1.0 describing the musical positiveness conveyed by a track. Tracks with high valence sound more positive (e.g. happy, cheerful, euphoric), while tracks with low valence sound more negative (e.g. sad, depressed, angry).

Tempo: The overall estimated tempo of a track in beats per minute (BPM). In musical terminology, tempo is the speed or pace of a given piece and derives directly from the average beat duration.

Duration_ms: The duration of the track in milliseconds.

The time signature: It’s a notational convention to specify how many beats are in each bar (or measure). The time signature ranges from 3 to 7 indicating time signatures of “3/4”, to “7/4”.

==> The most usefull features to provide the best chords are key, mode, tempo and time signature.

DATA PREPARATION

BOITEACHANSONS

To extract data I decided to scrap the website, I wanted to code it first but I found two online tools to make first tries speedly : phantombuster.com and webscraper.io

I just had to choose CSS selectors to copy what I wanted from the links of the Gsheets.

Result with RAW Data :

print(notes.et.tona.top50)

SPOTIFY

API online tool from Spotify

https://developer.spotify.com/console/get-audio-features-several-tracks/?ids=79t297h5zlQXmcNc9Vxb24

Everyone can try easily to get features from a song with an easy-to-use interface but it’s one song per one.

INSERT SCREENSHOTS OF THE RESULT

API in R

links to refer :

https://developer.spotify.com/documentation/general/guides/authorization/ https://developer.spotify.com/documentation/general/guides/authorization/code-flow/ https://developer.spotify.com/documentation/general/guides/authorization/app-settings/

API1 in R
install.packages('spotifyr')
Sys.setenv(SPOTIFY_CLIENT_ID = '4fc767de266746a9a3d38534f3c125f7')
Sys.setenv(SPOTIFY_CLIENT_SECRET = 'bd3711f4c6454215a5d7e6ad97c04038')

access_token <- get_spotify_access_token()

ERROR : Maybe the package who is too old

API2in R
spotifyKey <- "bd3711f4c6454215a5d7e6ad97c04038"
spotifySecret <- "4fc767de266746a9a3d38534f3c125f7"
install.packages('Rspotify')
install.packages('httr')
install.packages('jsonlite')
library(Rspotify)
library(httr)
library(jsonlite)
my_oauth <- spotifyOAuth(app_id="xxxx",client_id="yyyy",client_secret="zzzz")
save(my_oauth, file="my_oauth")
load("my_oauth")
tiago <- getUser(user_id="t.mendesdantas",token=my_oauth)

2nd error so let’s try another way for the moment.

Data from Kaggle or GitHub :

I find interesting databases like those ones but they don’t have the KEY feature who’s one of the most important feature

https://www.kaggle.com/datasets/leonardopena/top-spotify-songs-from-20102019-by-year

https://www.kaggle.com/datasets/iamsumat/spotify-top-2000s-mega-dataset

Best ones :

https://www.kaggle.com/datasets/maharshipandya/-spotify-tracks-dataset

https://www.kaggle.com/code/aeryan/spotify-music-analysis/data

In this one I have different genre, lot of tracks, and especially features : key, mode, tempo and time signature.

The real goal is definitely to create a code to use the API from Spotify.

=> Maybe with https://github.com/tgel0/spotify-data/blob/master/notebooks/SpotifyDataRetrieval.ipynb later in Python

CLEANING BOITEACHANSON DATA

head(notes.et.tona.top50)

After some cleaning with Excel I obtained this file

top50v3 <- top50v2 %>%
    # manually re-name columns
           # NEW name             # OLD name
    rename(url       = V1,
           selector = V2,
           chords         = V4,
           key = V5)
names(top50v3)
[1] "url"      "selector" "V3"       "chords"   "key"     
top50v3$V3 <- NULL
names(top50v3)
[1] "url"      "selector" "chords"   "key"     
top50v3 <- top50v3[-1,]
head(top50v3)

CLEANING SPOTIFY KAGGLE DATA

dataset2 <- read.csv("~/datasetspotify/dataspotify2/dataset2.csv")
  head(dataset2)

for the next steps I will follow the processes from that link : https://epirhandbook.com/en/cleaning-data-and-core-functions.html

install.packages("here")
Installation du package dans ‘C:/Users/Microtek/AppData/Local/R/win-library/4.2’
(car ‘lib’ n'est pas spécifié)
trying URL 'https://cran.rstudio.com/bin/windows/contrib/4.2/here_1.0.1.zip'
Content type 'application/zip' length 63959 bytes (62 KB)
downloaded 62 KB
le package ‘here’ a été décompressé et les sommes MD5 ont été vérifiées avec succés

Les packages binaires téléchargés sont dans
    C:\Users\Microtek\AppData\Local\Temp\RtmpKKAIjY\downloaded_packages
install.packages("janitor")
Installation du package dans ‘C:/Users/Microtek/AppData/Local/R/win-library/4.2’
(car ‘lib’ n'est pas spécifié)
trying URL 'https://cran.rstudio.com/bin/windows/contrib/4.2/janitor_2.1.0.zip'
Content type 'application/zip' length 252986 bytes (247 KB)
downloaded 247 KB
le package ‘janitor’ a été décompressé et les sommes MD5 ont été vérifiées avec succés

Les packages binaires téléchargés sont dans
    C:\Users\Microtek\AppData\Local\Temp\RtmpKKAIjY\downloaded_packages
install.packages("lubridate")
Installation du package dans ‘C:/Users/Microtek/AppData/Local/R/win-library/4.2’
(car ‘lib’ n'est pas spécifié)
trying URL 'https://cran.rstudio.com/bin/windows/contrib/4.2/lubridate_1.9.0.zip'
Content type 'application/zip' length 942344 bytes (920 KB)
downloaded 920 KB
le package ‘lubridate’ a été décompressé et les sommes MD5 ont été vérifiées avec succés

Les packages binaires téléchargés sont dans
    C:\Users\Microtek\AppData\Local\Temp\RtmpKKAIjY\downloaded_packages
install.packages("matchmaker")
Installation du package dans ‘C:/Users/Microtek/AppData/Local/R/win-library/4.2’
(car ‘lib’ n'est pas spécifié)
trying URL 'https://cran.rstudio.com/bin/windows/contrib/4.2/matchmaker_0.1.1.zip'
Content type 'application/zip' length 58501 bytes (57 KB)
downloaded 57 KB
le package ‘matchmaker’ a été décompressé et les sommes MD5 ont été vérifiées avec succés

Les packages binaires téléchargés sont dans
    C:\Users\Microtek\AppData\Local\Temp\RtmpKKAIjY\downloaded_packages
install.packages("epitkit")
Installation du package dans ‘C:/Users/Microtek/AppData/Local/R/win-library/4.2’
(car ‘lib’ n'est pas spécifié)
Warning in install.packages :
  le package ‘epitkit’ n'est pas disponible for this version of R

Une version de ce package pour votre version de R est peut-être disponible ailleurs,
Voyez des idées à
https://cran.r-project.org/doc/manuals/r-patched/R-admin.html#Installing-packages
install.packages("magrittr")
Error in install.packages : Updating loaded packages
library(rio)
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
The following rio suggested packages are not installed: ‘arrow’, ‘feather’, ‘fst’, ‘hexView’, ‘pzfx’, ‘readODS’, ‘rmatio’
Use 'install_formats()' to install them
skimr::skim(dataset2)
── Data Summary ────────────────────────
                           Values  
Name                       dataset2
Number of rows             114000  
Number of columns          21      
_______________________            
Column type frequency:             
  character                6       
  numeric                  15      
________________________           
Group variables            None    
names(dataset2)
 [1] "X"                "track_id"         "artists"          "album_name"       "track_name"       "popularity"       "duration_ms"     
 [8] "explicit"         "danceability"     "energy"           "key"              "loudness"         "mode"             "speechiness"     
[15] "acousticness"     "instrumentalness" "liveness"         "valence"          "tempo"            "time_signature"   "track_genre"     
dataset2 %>% 
  select(track_name,key,mode,tempo,time_signature,track_genre,popularity) %>% 
  names()
[1] "track_name"     "key"            "mode"          
[4] "tempo"          "time_signature" "track_genre"   
[7] "popularity"    
ds2_by_genre <- dataset2 %>% 
  group_by(track_genre)
ds2_by_genre
count(dataset2,track_genre)
NA
  ds2_by_genre %>% 
  group_by(track_genre) %>%     # group data by unique values in column track_genre
  summarise(n_rows = n()) 
dataset2 %>%
count(track_genre="jazz",key)
keyjazz_summary <- dataset2 %>% 
  count(track_genre="jazz",key) %>%                     # group and count by gender (produces "n" column)
  mutate(                                # create percent of column - note the denominator
    percent = scales::percent(n / sum(n))) 

# print
keyjazz_summary
dataset2 %>%                      
  count(track_genre, key) %>%     # group and tabulate counts by two columns
  ggplot()+                       # pass new data frame to ggplot
    geom_col(                     # create bar plot
      mapping = aes(   
        x = (track_genre),              # map outcome to x-axis
        fill = key,           # map age_cat to the fill
        y = n))     

key_by_genre <- dataset2 %>%                  # begin with linelist
  group_by(track_genre) %>%                         # group by outcome 
  count(key) %>%                            # group and count by age_cat, and then remove age_cat grouping
  mutate(percent = scales::percent(n / sum(n))) # calculate percent - note the denominator is by outcome group
key_by_genre

MODELING :

SPOTIFY : Check Kaggle et autres

=> exporting best keys per genre only for songs with popularity > 0,6

BOITEACHANSON : Check les cours

=> output best chords and best keys

=> how to create chords with notes.wav

top50v3 %>%
count(key)
LS0tDQp0aXRsZTogIlIgTm90ZWJvb2siDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCmVkaXRvcl9vcHRpb25zOiANCiAgbWFya2Rvd246IA0KICAgIHdyYXA6IDcyDQotLS0NCg0KIyBJTlRST0RVQ1RJT04NCg0KU2luY2UgYWxtb3N0IHR3byB5ZWFycywgbWUgYW5kIGEgZnJpZW5kIHdobyBwcm9kdWNlcyBtdXNpYyBhcmUgbG9va2luZw0KZm9yIGEgdG9vbCA6IGEgZ2VuZXJhdG9yIG9mIGNob3JkcyB0byBoZWxwIGJlZ2lubmVycyB0byBwcm9kdWNlIG11c2ljLg0KDQpFeGFtcGxlIG9mIGEgbWFnaWNhbCBjaG9yZCA6IDxodHRwczovL3lvdXR1LmJlL25oaXZkU01kTXhjPg0KDQpTaW1pbGFyIHByb2plY3QgOiA8aHR0cHM6Ly9hdXRvY2hvcmRzLmNvbS8+DQoNCk91ciBpbml0aWFsIGlkZWEgd2FzIGNvbnNpc3RlZCBvZiB0d28gcGFydHMvIHByb2Nlc3NlczoNCg0KMSA6IEltcG9ydGF0aW9uIGFuZCBhbmFseXNpcyBvZiB0cmVuZHMNCg0KLSAgIEltcG9ydGluZyB0aGUgdG9wIGNoYXJ0cyBvZiBTcG90aWZ5IGZvciBkaWZmZXJlbnQgZ2VucmVzIGluDQogICAgZGlmZmVyZW50IGNvdW50cmllcyBhbmQgY29tcGFyaW5nIHRoZW0gdG8gc2VlIHdoYXQgYXJlIHRoZQ0KICAgIGNoYXJhY3RlcmlzdGljcyBvZiBhIGhpdC4NCi0gICBJbXBvcnRpbmcgdGhlIG5vdGVzIG9mIHRob3NlIHNvbmdzIHRvIHNlZSBpZiB0aGVyZSBhcmUgcmVjdXJyZW50DQogICAgY2hvcmRzL3Byb2dyZXNzaW9ucyBpbiB0aGUgbW9zdCBwb3B1bGFyIHNvbmdzIGFuZCB3aGljaCBvbmVzLg0KDQoyIDogUmV2ZXJzZSBwcm9jZXNzIDoNCg0KLSAgIFByb3ZpZGluZyB0byBhIGJlZ2lubmVyIG11c2ljIHByb2R1Y3RlciByYW5kb20gY2hvcmRzIGRlcGVuZGluZyBvbg0KICAgIHdoYXQgZ2VucmUgb2YgbXVzaWMgaGUgd2FudHMgdG8gcHJvZHVjdCBhbmQgdGhlIHRyZW5kcyBvZiB0aGUNCiAgICBtb21lbnQuDQoNCiMjIENSSVNQIE1vZGVsIDogVW5kZXJzdGFuZGluZw0KDQojIyMgUHJvYmxlbXMgQnVsbGV0IFBvaW50IDoNCg0KLVIgb3IgUHl0aG9uIG9yIHRoZSB0d28NCg0KLUhvdyB0byBtYW5pcHVsYXRlIHNvbmdzIGluIGEgZGF0YWJhc2UgKGV4dGVybiBsaW5rcywgbWlkaSx3YXYgLi4uID8/PykNCg0KPVw+IE1JREkgcmVmcyA6DQo8aHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9jb2RlL3dmYXJpYS9taWRpLW11c2ljLWRhdGEtZXh0cmFjdGlvbi11c2luZy1tdXNpYzIxL25vdGVib29rPg0KDQotSG93IHRvIGNvbXBhcmUgZGlmZmVyZW50IGRhdGFiYXNlcw0KDQotRm91ciBjaG9yZHMgY2FuIG1heSBiZSBpbiBkaWZmZXJlbnQgb3JkZXJzIGJ1dCBzdGlsbCBzb25ncyBnb29kKQ0KDQotICAgIlRoZSBDYWxsIiB0aHJvdWdoICJOZW9wb2xpdGFuIERyZWFtcyIgYXJlIEcsIEQsIEVtLCBDDQoNCi0gICAiQXMgTG9uZyBBcyBZb3UgTG92ZSBNZSIgaXMgQywgRywgRW0sIEQNCg0KLSAgICJSb3VuZCBIZXJlIiBpcyBDLCBELCBFbSwgRyAtIkRydW5rIiBpcyBFbSwgRCwgRywgQw0KDQotICAgIldlIEFyZSBOZXZlciBFdmVyIEdldHRpbmcgQmFjayBUb2dldGhlciIgaXMgQywgRywgRCwgRW0NCg0KLSAgICJJIEtuZXcgWW91IFdlcmUgVHJvdWJsZSIgaXMgRywgRCwgRW0sIEMgKHZlcnNlcykgYW5kIEVtLCBDLCBELCBHDQogICAgKGNob3J1cykNCg0KPVw+IEksIElJSSwgVkkgZG9taW5hbnRlIGV0IHBldXZlbnQgw6p0cmUgaW50ZXJjaGFuZ8OpZXMNCg0KSUkgYW5kIElWIHNvdXMgZG9taW5hbnRlIGV0IHBldXZlbnQgw6p0cmUgaW50ZXJjaGFuZ8OpZXMNCg0KViBhbmQgVklJIGRvbWluYW50ZXMgZXQgcGV1dmVudCDDqnRyZSBpbnRlcmNoYW5nw6llcw0KDQoqKk1JTk9SIENIT1JEIFBST0dSRVNTSU9OID0gaSBpacKwIElJSSBpdiB2IFZJIFZJSSoqDQoNCioqTUFKT1IgQ0hPUkQgUFJPR1JFU1NJT04gPSBJIGlpIGlpaSBJViBWIHZpIHZpacKwKioNCg0KIyMjIEJ1c2luZXNzIHVuZGVyc3RhbmRpbmcNCg0KQWZ0ZXIgZmV3IGJlbmNobWFya3Mgd2Ugc3RpbGwgbmV2ZXIgZmluZCBhbiBhcHAgd2VsbCBvcHRpbWl6ZWQgdG8NCnByb3ZpZGUgYWNjb3JkcyBkZXBlbmRpbmcgb24gYWN0dWFsIHRyZW5kcyBzbyB3ZSBrbm93IHRoZXJlIGlzIGEgcGxhY2UNCm9uIHRoZSBtYXJrZXQgb2YgIm11c2ljIHNvZnR3YXJlIGFuZCBGTCdzIHBsdWctaW4iIGZvciBhbiBhcHAgbGlrZSB0aGF0Lg0KDQpUaGUgcHJpbmNpcGFsIGdvYWxzIG9mIHRoZSBhcHAgYXJlIDoNCg0KLURlcGVuZGluZyBvbiBpbnB1dC9maWx0ZXJzIGFzIDogZ2VucmUsIGZhdm9yaXRlIGFydGlzdCwgY291bnRyeSwNCmZhdm9yaXRlcyBrZXlzIGV0YyAuLi4NCg0KPVw+IFRPOg0KDQotR2l2ZSBhIGtleQ0KDQotUGxheSByYW5kb21seSB0aGUgYmVzdCBjaG9yZHMNCg0KLUV4cG9ydCBhY2NvcmRzIGFzIC5taWRpIGZpbGVzDQoNCi1HaXZpbmcgdGhlIGRlZ3JlZXMNCjxodHRwczovL3d3dy55b3V0dWJlLmNvbS93YXRjaD92PXYzWWJFTC1fZW9JJnQ9OTdzJmFiX2NoYW5uZWw9RGF2aWRCZW5uZXR0UGlhbm8+DQoNCiMjIyBEYXRhIHVuZGVyc3RhbmRpbmcNCg0KMSkgTGFzdCBzdW1tZXIgSSBjcmVhdGVkIGEgZmlyc3QgZGF0YSBiYXNlIHdpdGggbm90ZXMsIGFjY29yZHMgYW5kIG11c2ljDQpzY2FsZSB3aXRoIEZsIFN0dWRpby4gPVw+IC53YXYgLyAuZmwgLyAubXAzDQoNCjIpIFRvIGltcG9ydCB0b3AgY2hhcnRzIFNwb3RpZnkgSSBoYXZlIGZldyBvcHRpb25zIDoNCg0KXC0gRGF0YWJhc2VzIG9uIEthZ2dsZS9HaXQtSHViICghISAnS2V5cycgYXJlIG5vdCBvbiBldmVyeSBkYikNCg0KXC0gQ3JlYXRpbmcgbXkgb3duIGRhdGFiYXNlIHdpdGggYSBTcG90aWZ5IEFQSSAoNCBzdXJlIEkgaGF2ZSBrZXlzKQ0KDQpcLSBNaXggb2YgdGhlIHR3byBwcmV2aW91cyBvcHRpb25zDQoNCjMpIFRoZSBiaWcgcHJvYmxlbSBpcyB0byBpbXBvcnQgYWNjb3JkcyBhbmQgbWVsb2RpZXMsIHRoZSBvbmx5IHdlYnNpdGUgSQ0KZmluZCBpcyA6IDxodHRwczovL3d3dy5ib2l0ZWFjaGFuc29ucy5uZXQvPiBhbmQgaXQncyBtb3N0IG9ubHkgcG9wIGFuZA0Kcm9jayBmcmVuY2hzIHNvbmdzIC4uLg0KDQo0KSBQZXAncyBFeGNlbCBmaWxlcyB3aXRoIGhhcm1vbml6YXRpb24gYW5kIGRlZ3JlZXMgYW5kIENob3Jkcw0KcHJvZ3Jlc3Npb25zLg0KDQo8aHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj12M1liRUwtX2VvSSZ0PTk3cyZhYl9jaGFubmVsPURhdmlkQmVubmV0dFBpYW5vPg0KDQpGb3IgdGhpcyBwcm9qZWN0LCBteSBvYmplY3RpdmVzIGFyZSB0byA6DQoNClwtIEltcG9ydCBhbmQgbWFrZSBhbmFseXNpcyBvZiB0b3AgY2hhcnRzIGZyb20gU3BvdGlmeSB0byBzZWUgd2hhdCdzIGNhbg0KYmUgdXNlZnVsIGluIHRoZSBmdXR1cmUgb24gb25lIHBhcnQNCg0KXC0gSW1wb3J0IGFuZCBtYWtlIGFuYWx5c2lzIG9mIHRoZSBjaG9yZHMgZnJvbSA6IGJvaXRlYWNoYW5zb25zLm5ldA0KDQotTWFrZSBhIHJldmVyc2UgY29kZSB0byBvdXRwdXQgcmVjdXJyZW50IGNob3JkcyAoaWYgdGhlcmUgYXJlKSBmcm9tIHRoZQ0KZGF0YSBJIGltcG9ydGVkIHdpdGggYm9pdGVhY2hhbnNvbnMubmV0IGFuZCByYW5kb21pemUgdGhlbiBieSB1c2luZw0KZGVncmVlcyBhbmQgaGFybW9uaXphdGlvbi4NCg0KIyMjIyBUcmFjaydzIGF1ZGlvIGZlYXR1cmVzIEkgYW0gbG9va2luZyBmb3IgZnJvbSBTcG90aWZ5IDoNCg0KPGh0dHBzOi8vZGV2ZWxvcGVyLnNwb3RpZnkuY29tL2RvY3VtZW50YXRpb24vd2ViLWFwaS9yZWZlcmVuY2UvIy9vcGVyYXRpb25zL2dldC1zZXZlcmFsLWF1ZGlvLWZlYXR1cmVzPg0KDQoqKkRhbmNlYWJpbGl0eSoqOiBEZXNjcmliZXMgaG93IHN1aXRhYmxlIGEgdHJhY2sgaXMgZm9yIGRhbmNpbmcgYmFzZWQgb24NCmEgY29tYmluYXRpb24gb2YgbXVzaWNhbCBlbGVtZW50cyBpbmNsdWRpbmcgdGVtcG8sIHJoeXRobSBzdGFiaWxpdHksDQpiZWF0IHN0cmVuZ3RoLCBhbmQgb3ZlcmFsbCByZWd1bGFyaXR5LiBBIHZhbHVlIG9mIDAuMCBpcyBsZWFzdCBkYW5jZWFibGUNCmFuZCAxLjAgaXMgbW9zdCBkYW5jZWFibGUuDQoNCioqRW5lcmd5Kio6IElzIGEgbWVhc3VyZSBmcm9tIDAuMCB0byAxLjAgYW5kIHJlcHJlc2VudHMgYSBwZXJjZXB0dWFsDQptZWFzdXJlIG9mIGludGVuc2l0eSBhbmQgYWN0aXZpdHkuIFR5cGljYWxseSwgZW5lcmdldGljIHRyYWNrcyBmZWVsDQpmYXN0LCBsb3VkLCBhbmQgbm9pc3kuIEZvciBleGFtcGxlLCBkZWF0aCBtZXRhbCBoYXMgaGlnaCBlbmVyZ3ksIHdoaWxlIGENCkJhY2ggcHJlbHVkZSBzY29yZXMgbG93IG9uIHRoZSBzY2FsZS4gUGVyY2VwdHVhbCBmZWF0dXJlcyBjb250cmlidXRpbmcNCnRvIHRoaXMgYXR0cmlidXRlIGluY2x1ZGUgZHluYW1pYyByYW5nZSwgcGVyY2VpdmVkIGxvdWRuZXNzLCB0aW1icmUsDQpvbnNldCByYXRlLCBhbmQgZ2VuZXJhbCBlbnRyb3B5Lg0KDQoqKktleSoqOiBUaGUga2V5IHRoZSB0cmFjayBpcyBpbi4gSW50ZWdlcnMgbWFwIHRvIHBpdGNoZXMgdXNpbmcgc3RhbmRhcmQNClBpdGNoIENsYXNzIG5vdGF0aW9uLiBFLmcuIDAgPSBDLCAxID0gQ+KZry9E4pmtLCAyID0gRCwgYW5kIHNvIG9uLg0KDQoqKkxvdWRuZXNzKio6IFRoZSBvdmVyYWxsIGxvdWRuZXNzIG9mIGEgdHJhY2sgaW4gZGVjaWJlbHMgKGRCKS4gTG91ZG5lc3MNCnZhbHVlcyBhcmUgYXZlcmFnZWQgYWNyb3NzIHRoZSBlbnRpcmUgdHJhY2sgYW5kIGFyZSB1c2VmdWwgZm9yIGNvbXBhcmluZw0KcmVsYXRpdmUgbG91ZG5lc3Mgb2YgdHJhY2tzLiBMb3VkbmVzcyBpcyB0aGUgcXVhbGl0eSBvZiBhIHNvdW5kIHRoYXQgaXMNCnRoZSBwcmltYXJ5IHBzeWNob2xvZ2ljYWwgY29ycmVsYXRlIG9mIHBoeXNpY2FsIHN0cmVuZ3RoIChhbXBsaXR1ZGUpLg0KVmFsdWVzIHR5cGljYWwgcmFuZ2UgYmV0d2VlbiAtNjAgYW5kIDAgZGIuDQoNCioqTW9kZSoqOiBNb2RlIGluZGljYXRlcyB0aGUgbW9kYWxpdHkgKG1ham9yIG9yIG1pbm9yKSBvZiBhIHRyYWNrLCB0aGUNCnR5cGUgb2Ygc2NhbGUgZnJvbSB3aGljaCBpdHMgbWVsb2RpYyBjb250ZW50IGlzIGRlcml2ZWQuIE1ham9yIGlzDQpyZXByZXNlbnRlZCBieSAxIGFuZCBtaW5vciBpcyAwLg0KDQoqKlNwZWVjaGluZXNzKio6IFNwZWVjaGluZXNzIGRldGVjdHMgdGhlIHByZXNlbmNlIG9mIHNwb2tlbiB3b3JkcyBpbiBhDQp0cmFjay4gVGhlIG1vcmUgZXhjbHVzaXZlbHkgc3BlZWNoLWxpa2UgdGhlIHJlY29yZGluZyAoZS5nLiB0YWxrIHNob3csDQphdWRpbyBib29rLCBwb2V0cnkpLCB0aGUgY2xvc2VyIHRvIDEuMCB0aGUgYXR0cmlidXRlIHZhbHVlLiBWYWx1ZXMgYWJvdmUNCjAuNjYgZGVzY3JpYmUgdHJhY2tzIHRoYXQgYXJlIHByb2JhYmx5IG1hZGUgZW50aXJlbHkgb2Ygc3Bva2VuIHdvcmRzLg0KVmFsdWVzIGJldHdlZW4gMC4zMyBhbmQgMC42NiBkZXNjcmliZSB0cmFja3MgdGhhdCBtYXkgY29udGFpbiBib3RoIG11c2ljDQphbmQgc3BlZWNoLCBlaXRoZXIgaW4gc2VjdGlvbnMgb3IgbGF5ZXJlZCwgaW5jbHVkaW5nIHN1Y2ggY2FzZXMgYXMgcmFwDQptdXNpYy4gVmFsdWVzIGJlbG93IDAuMzMgbW9zdCBsaWtlbHkgcmVwcmVzZW50IG11c2ljIGFuZCBvdGhlcg0Kbm9uLXNwZWVjaC1saWtlIHRyYWNrcy4NCg0KKipBY291c3RpY25lc3MqKjogQSBjb25maWRlbmNlIG1lYXN1cmUgZnJvbSAwLjAgdG8gMS4wIG9mIHdoZXRoZXIgdGhlDQp0cmFjayBpcyBhY291c3RpYy4gMS4wIHJlcHJlc2VudHMgaGlnaCBjb25maWRlbmNlIHRoZSB0cmFjayBpcyBhY291c3RpYy4NCg0KKipJbnN0cnVtZW50YWxuZXNzKio6IFByZWRpY3RzIHdoZXRoZXIgYSB0cmFjayBjb250YWlucyBubyB2b2NhbHMuICJPb2giDQphbmQgImFhaCIgc291bmRzIGFyZSB0cmVhdGVkIGFzIGluc3RydW1lbnRhbCBpbiB0aGlzIGNvbnRleHQuIFJhcCBvcg0Kc3Bva2VuIHdvcmQgdHJhY2tzIGFyZSBjbGVhcmx5ICJ2b2NhbCIuIFRoZSBjbG9zZXIgdGhlIGluc3RydW1lbnRhbG5lc3MNCnZhbHVlIGlzIHRvIDEuMCwgdGhlIGdyZWF0ZXIgbGlrZWxpaG9vZCB0aGUgdHJhY2sgY29udGFpbnMgbm8gdm9jYWwNCmNvbnRlbnQuIFZhbHVlcyBhYm92ZSAwLjUgYXJlIGludGVuZGVkIHRvIHJlcHJlc2VudCBpbnN0cnVtZW50YWwgdHJhY2tzLA0KYnV0IGNvbmZpZGVuY2UgaXMgaGlnaGVyIGFzIHRoZSB2YWx1ZSBhcHByb2FjaGVzIDEuMC4NCg0KKipMaXZlbmVzcyoqOiBEZXRlY3RzIHRoZSBwcmVzZW5jZSBvZiBhbiBhdWRpZW5jZSBpbiB0aGUgcmVjb3JkaW5nLg0KSGlnaGVyIGxpdmVuZXNzIHZhbHVlcyByZXByZXNlbnQgYW4gaW5jcmVhc2VkIHByb2JhYmlsaXR5IHRoYXQgdGhlIHRyYWNrDQp3YXMgcGVyZm9ybWVkIGxpdmUuIEEgdmFsdWUgYWJvdmUgMC44IHByb3ZpZGVzIHN0cm9uZyBsaWtlbGlob29kIHRoYXQNCnRoZSB0cmFjayBpcyBsaXZlLg0KDQoqKlZhbGVuY2UqKjogQSBtZWFzdXJlIGZyb20gMC4wIHRvIDEuMCBkZXNjcmliaW5nIHRoZSBtdXNpY2FsDQpwb3NpdGl2ZW5lc3MgY29udmV5ZWQgYnkgYSB0cmFjay4gVHJhY2tzIHdpdGggaGlnaCB2YWxlbmNlIHNvdW5kIG1vcmUNCnBvc2l0aXZlIChlLmcuIGhhcHB5LCBjaGVlcmZ1bCwgZXVwaG9yaWMpLCB3aGlsZSB0cmFja3Mgd2l0aCBsb3cgdmFsZW5jZQ0Kc291bmQgbW9yZSBuZWdhdGl2ZSAoZS5nLiBzYWQsIGRlcHJlc3NlZCwgYW5ncnkpLg0KDQoqKlRlbXBvKio6IFRoZSBvdmVyYWxsIGVzdGltYXRlZCB0ZW1wbyBvZiBhIHRyYWNrIGluIGJlYXRzIHBlciBtaW51dGUNCihCUE0pLiBJbiBtdXNpY2FsIHRlcm1pbm9sb2d5LCB0ZW1wbyBpcyB0aGUgc3BlZWQgb3IgcGFjZSBvZiBhIGdpdmVuDQpwaWVjZSBhbmQgZGVyaXZlcyBkaXJlY3RseSBmcm9tIHRoZSBhdmVyYWdlIGJlYXQgZHVyYXRpb24uDQoNCioqRHVyYXRpb25fbXMqKjogVGhlIGR1cmF0aW9uIG9mIHRoZSB0cmFjayBpbiBtaWxsaXNlY29uZHMuDQoNCioqVGhlIHRpbWUgc2lnbmF0dXJlKio6IEl0J3MgYSBub3RhdGlvbmFsIGNvbnZlbnRpb24gdG8gc3BlY2lmeSBob3cgbWFueQ0KYmVhdHMgYXJlIGluIGVhY2ggYmFyIChvciBtZWFzdXJlKS4gVGhlIHRpbWUgc2lnbmF0dXJlIHJhbmdlcyBmcm9tIDMgdG8NCjcgaW5kaWNhdGluZyB0aW1lIHNpZ25hdHVyZXMgb2YgIjMvNCIsIHRvICI3LzQiLg0KDQo9PVw+IFRoZSBtb3N0IHVzZWZ1bGwgZmVhdHVyZXMgdG8gcHJvdmlkZSB0aGUgYmVzdCBjaG9yZHMgYXJlICoqa2V5LA0KbW9kZSwgdGVtcG8gYW5kIHRpbWUgc2lnbmF0dXJlLioqDQoNCiMjIERBVEEgUFJFUEFSQVRJT04NCg0KIyMjIEJPSVRFQUNIQU5TT05TDQoNClRvIGV4dHJhY3QgZGF0YSBJIGRlY2lkZWQgdG8gc2NyYXAgdGhlIHdlYnNpdGUsIEkgd2FudGVkIHRvIGNvZGUgaXQNCmZpcnN0IGJ1dCBJIGZvdW5kIHR3byBvbmxpbmUgdG9vbHMgdG8gbWFrZSBmaXJzdCB0cmllcyBzcGVlZGx5IDoNCnBoYW50b21idXN0ZXIuY29tIGFuZCB3ZWJzY3JhcGVyLmlvDQoNCi0gICBXaXRoIHdlYnNjcmFwZWQgSSBzY3JhcGVkIHRoZSBsaW5rcyBmb3IgZWFjaCBzb25ncyBmcm9tIHRoZSB0b3A1MCBvZg0KICAgIHRoZSBzaXRlIDogPGh0dHBzOi8vd3d3LmJvaXRlYWNoYW5zb25zLm5ldC9wYXJ0aXRpb25zL3RvcDUwQ2hhbnNvbnM+DQoNCi0gICBBZnRlciB0aGF0LCBJIHN0b2NrZWQgdGhlIGxpbmtzIGluIGEgRy1zaGVldHMgOg0KICAgIDxodHRwczovL2RvY3MuZ29vZ2xlLmNvbS9zcHJlYWRzaGVldHMvZC8xNnFDVTF2M3UyZGJwcDcwckFpSGlLa3BmYkRTUndwSXJNb2ZvUXhyaVgxUS9lZGl0I2dpZD0wPg0KDQotICAgVG8gZmluaXNoIEkgZG93bmxvYWRlZCBjaG9yZHMgYW5kIGtleXMgZm9yIGVhY2ggc29uZ3Mgd2l0aA0KICAgIFBoYW50b21idXN0ZXIsIHRoZSBmb3JtdWxhciBsb29rZWQgbGlrZSB0aGF0IGluIEpTT04gOg0KDQogICAgeyAidXJscyI6IFsgeyAibGluayI6DQogICAgIjxodHRwczovL2RvY3MuZ29vZ2xlLmNvbS9zcHJlYWRzaGVldHMvZC8xNnFDVTF2M3UyZGJwcDcwckFpSGlLa3BmYkRTUndwSXJNb2ZvUXhyaVgxUS9lZGl0P3VzcD1zaGFyaW5nPiIsDQogICAgInNlbGVjdG9ycyI6IFsgeyAic2VsZWN0b3IiOiAiI2RpYWdBY2NvcmRzIGRpdi5kTm9tQWNjIiwgImxhYmVsIjoNCiAgICAibm90ZXMiIH0sIHsgInNlbGVjdG9yIjogImxpLmxpVG9uYWxFbG10U2xjdCIsICJsYWJlbCI6ICJ0b25hbGl0w6kiDQogICAgfV0sICJ0aW1lVG9XYWl0U2VsZWN0b3IiOiA1MDAwIH0gXSwgImNzdk5hbWUiOiAibm90ZXMgZXQgdG9uYSB0b3A1MCINCg0KSSBqdXN0IGhhZCB0byBjaG9vc2UgQ1NTIHNlbGVjdG9ycyB0byBjb3B5IHdoYXQgSSB3YW50ZWQgZnJvbSB0aGUgbGlua3MNCm9mIHRoZSBHc2hlZXRzLg0KDQpSZXN1bHQgd2l0aCBSQVcgRGF0YSA6DQoNCmBgYHtyfQ0KcHJpbnQobm90ZXMuZXQudG9uYS50b3A1MCkNCmBgYA0KDQojIyMgU1BPVElGWQ0KDQojIyMjIEFQSSBvbmxpbmUgdG9vbCBmcm9tIFNwb3RpZnkNCg0KPGh0dHBzOi8vZGV2ZWxvcGVyLnNwb3RpZnkuY29tL2NvbnNvbGUvZ2V0LWF1ZGlvLWZlYXR1cmVzLXNldmVyYWwtdHJhY2tzLz9pZHM9Nzl0Mjk3aDV6bFFYbWNOYzlWeGIyND4NCg0KRXZlcnlvbmUgY2FuIHRyeSBlYXNpbHkgdG8gZ2V0IGZlYXR1cmVzIGZyb20gYSBzb25nIHdpdGggYW4gZWFzeS10by11c2UNCmludGVyZmFjZSBidXQgaXQncyBvbmUgc29uZyBwZXIgb25lLg0KDQojIyMjIElOU0VSVCBTQ1JFRU5TSE9UUyBPRiBUSEUgUkVTVUxUDQoNCiMjIyMgQVBJIGluIFINCg0KbGlua3MgdG8gcmVmZXIgOg0KDQo8aHR0cHM6Ly9kZXZlbG9wZXIuc3BvdGlmeS5jb20vZG9jdW1lbnRhdGlvbi9nZW5lcmFsL2d1aWRlcy9hdXRob3JpemF0aW9uLz4NCjxodHRwczovL2RldmVsb3Blci5zcG90aWZ5LmNvbS9kb2N1bWVudGF0aW9uL2dlbmVyYWwvZ3VpZGVzL2F1dGhvcml6YXRpb24vY29kZS1mbG93Lz4NCjxodHRwczovL2RldmVsb3Blci5zcG90aWZ5LmNvbS9kb2N1bWVudGF0aW9uL2dlbmVyYWwvZ3VpZGVzL2F1dGhvcml6YXRpb24vYXBwLXNldHRpbmdzLz4NCg0KIyMjIyMgQVBJMSBpbiBSDQoNCmBgYHtyfQ0KaW5zdGFsbC5wYWNrYWdlcygnc3BvdGlmeXInKQ0KYGBgDQoNCmBgYHtyfQ0KU3lzLnNldGVudihTUE9USUZZX0NMSUVOVF9JRCA9ICc0ZmM3NjdkZTI2Njc0NmE5YTNkMzg1MzRmM2MxMjVmNycpDQpTeXMuc2V0ZW52KFNQT1RJRllfQ0xJRU5UX1NFQ1JFVCA9ICdiZDM3MTFmNGM2NDU0MjE1YTVkN2U2YWQ5N2MwNDAzOCcpDQoNCmFjY2Vzc190b2tlbiA8LSBnZXRfc3BvdGlmeV9hY2Nlc3NfdG9rZW4oKQ0KYGBgDQoNCkVSUk9SIDogTWF5YmUgdGhlIHBhY2thZ2Ugd2hvIGlzIHRvbyBvbGQNCg0KIyMjIyMgQVBJMmluIFINCg0KYGBge3J9DQpzcG90aWZ5S2V5IDwtICJiZDM3MTFmNGM2NDU0MjE1YTVkN2U2YWQ5N2MwNDAzOCINCnNwb3RpZnlTZWNyZXQgPC0gIjRmYzc2N2RlMjY2NzQ2YTlhM2QzODUzNGYzYzEyNWY3Ig0KYGBgDQoNCmBgYHtyfQ0KaW5zdGFsbC5wYWNrYWdlcygnUnNwb3RpZnknKQ0KaW5zdGFsbC5wYWNrYWdlcygnaHR0cicpDQppbnN0YWxsLnBhY2thZ2VzKCdqc29ubGl0ZScpDQpsaWJyYXJ5KFJzcG90aWZ5KQ0KbGlicmFyeShodHRyKQ0KbGlicmFyeShqc29ubGl0ZSkNCg0KYGBgDQoNCmBgYHtyfQ0KbXlfb2F1dGggPC0gc3BvdGlmeU9BdXRoKGFwcF9pZD0ieHh4eCIsY2xpZW50X2lkPSJ5eXl5IixjbGllbnRfc2VjcmV0PSJ6enp6IikNCnNhdmUobXlfb2F1dGgsIGZpbGU9Im15X29hdXRoIikNCmxvYWQoIm15X29hdXRoIikNCnRpYWdvIDwtIGdldFVzZXIodXNlcl9pZD0idC5tZW5kZXNkYW50YXMiLHRva2VuPW15X29hdXRoKQ0KYGBgDQoNCjJuZCBlcnJvciBzbyBsZXQncyB0cnkgYW5vdGhlciB3YXkgZm9yIHRoZSBtb21lbnQuDQoNCiMjIyMgRGF0YSBmcm9tIEthZ2dsZSBvciBHaXRIdWIgOg0KDQpJIGZpbmQgaW50ZXJlc3RpbmcgZGF0YWJhc2VzIGxpa2UgdGhvc2Ugb25lcyBidXQgdGhleSBkb24ndCBoYXZlIHRoZSBLRVkNCmZlYXR1cmUgd2hvJ3Mgb25lIG9mIHRoZSBtb3N0IGltcG9ydGFudCBmZWF0dXJlDQoNCjxodHRwczovL3d3dy5rYWdnbGUuY29tL2RhdGFzZXRzL2xlb25hcmRvcGVuYS90b3Atc3BvdGlmeS1zb25ncy1mcm9tLTIwMTAyMDE5LWJ5LXllYXI+DQoNCjxodHRwczovL3d3dy5rYWdnbGUuY29tL2RhdGFzZXRzL2lhbXN1bWF0L3Nwb3RpZnktdG9wLTIwMDBzLW1lZ2EtZGF0YXNldD4NCg0KKipCZXN0IG9uZXMgOioqDQoNCjxodHRwczovL3d3dy5rYWdnbGUuY29tL2RhdGFzZXRzL21haGFyc2hpcGFuZHlhLy1zcG90aWZ5LXRyYWNrcy1kYXRhc2V0Pg0KDQo8aHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9jb2RlL2Flcnlhbi9zcG90aWZ5LW11c2ljLWFuYWx5c2lzL2RhdGE+DQoNCkluIHRoaXMgb25lIEkgaGF2ZSBkaWZmZXJlbnQgZ2VucmUsIGxvdCBvZiB0cmFja3MsIGFuZCBlc3BlY2lhbGx5DQpmZWF0dXJlcyA6ICoqa2V5LCBtb2RlLCB0ZW1wbyBhbmQgdGltZSBzaWduYXR1cmUuKioNCg0KKioqVGhlIHJlYWwgZ29hbCBpcyBkZWZpbml0ZWx5IHRvIGNyZWF0ZSBhIGNvZGUgdG8gdXNlIHRoZSBBUEkgZnJvbQ0KU3BvdGlmeS4qKioNCg0KPVw+IE1heWJlIHdpdGgNCjxodHRwczovL2dpdGh1Yi5jb20vdGdlbDAvc3BvdGlmeS1kYXRhL2Jsb2IvbWFzdGVyL25vdGVib29rcy9TcG90aWZ5RGF0YVJldHJpZXZhbC5pcHluYj4NCmxhdGVyIGluIFB5dGhvbg0KDQojIyMjIENMRUFOSU5HIEJPSVRFQUNIQU5TT04gREFUQQ0KDQpgYGB7cn0NCmhlYWQobm90ZXMuZXQudG9uYS50b3A1MCkNCmBgYA0KDQpBZnRlciBzb21lIGNsZWFuaW5nIHdpdGggRXhjZWwgSSBvYnRhaW5lZCB0aGlzIGZpbGUNCg0KYGBge3J9DQpoZWFkKHRvcDUwdjIpDQpgYGANCg0KYGBge3J9DQp0b3A1MHYzIDwtIHRvcDUwdjIgJT4lDQogICAgIyBtYW51YWxseSByZS1uYW1lIGNvbHVtbnMNCiAgICAgICAgICAgIyBORVcgbmFtZSAgICAgICAgICAgICAjIE9MRCBuYW1lDQogICAgcmVuYW1lKHVybCAgICAgICA9IFYxLA0KICAgICAgICAgICBzZWxlY3RvciA9IFYyLA0KICAgICAgICAgICBjaG9yZHMgICAgICAgICA9IFY0LA0KICAgICAgICAgICBrZXkgPSBWNSkNCm5hbWVzKHRvcDUwdjMpDQpgYGANCg0KYGBge3J9DQp0b3A1MHYzJFYzIDwtIE5VTEwNCm5hbWVzKHRvcDUwdjMpDQpgYGANCg0KYGBge3J9DQp0b3A1MHYzIDwtIHRvcDUwdjNbLTEsXQ0KaGVhZCh0b3A1MHYzKQ0KYGBgDQoNCiMjIyMgQ0xFQU5JTkcgU1BPVElGWSBLQUdHTEUgREFUQQ0KDQpgYGB7cn0NCmRhdGFzZXQyIDwtIHJlYWQuY3N2KCJ+L2RhdGFzZXRzcG90aWZ5L2RhdGFzcG90aWZ5Mi9kYXRhc2V0Mi5jc3YiKQ0KICBoZWFkKGRhdGFzZXQyKQ0KYGBgDQoNCmZvciB0aGUgbmV4dCBzdGVwcyBJIHdpbGwgZm9sbG93IHRoZSBwcm9jZXNzZXMgZnJvbSB0aGF0IGxpbmsgOg0KPGh0dHBzOi8vZXBpcmhhbmRib29rLmNvbS9lbi9jbGVhbmluZy1kYXRhLWFuZC1jb3JlLWZ1bmN0aW9ucy5odG1sPg0KDQpgYGB7cn0NCmluc3RhbGwucGFja2FnZXMoImhlcmUiKQ0KaW5zdGFsbC5wYWNrYWdlcygiamFuaXRvciIpDQppbnN0YWxsLnBhY2thZ2VzKCJsdWJyaWRhdGUiKQ0KaW5zdGFsbC5wYWNrYWdlcygibWF0Y2htYWtlciIpDQppbnN0YWxsLnBhY2thZ2VzKCJlcGl0a2l0IikNCmBgYA0KDQpgYGB7cn0NCmluc3RhbGwucGFja2FnZXMoIm1hZ3JpdHRyIikNCmBgYA0KDQpgYGB7cn0NCmxpYnJhcnkobWFncml0dHIpDQpsaWJyYXJ5KGphbml0b3IpDQpsaWJyYXJ5KGhlcmUpDQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShyaW8pDQpgYGANCg0KYGBge3J9DQpza2ltcjo6c2tpbShkYXRhc2V0MikNCmBgYA0KDQpgYGB7cn0NCm5hbWVzKGRhdGFzZXQyKQ0KDQpgYGANCg0KYGBge3J9DQpkYXRhc2V0MiAlPiUgDQogIHNlbGVjdCh0cmFja19uYW1lLGtleSxtb2RlLHRlbXBvLHRpbWVfc2lnbmF0dXJlLHRyYWNrX2dlbnJlLHBvcHVsYXJpdHkpICU+JSANCiAgbmFtZXMoKQ0KDQpgYGANCg0KYGBge3J9DQpkczJfYnlfZ2VucmUgPC0gZGF0YXNldDIgJT4lIA0KICBncm91cF9ieSh0cmFja19nZW5yZSkNCmRzMl9ieV9nZW5yZQ0KYGBgDQoNCmBgYHtyfQ0KY291bnQoZGF0YXNldDIsdHJhY2tfZ2VucmUpDQoNCmBgYA0KDQpgYGB7cn0NCiAgZHMyX2J5X2dlbnJlICU+JSANCiAgZ3JvdXBfYnkodHJhY2tfZ2VucmUpICU+JSAgICAgIyBncm91cCBkYXRhIGJ5IHVuaXF1ZSB2YWx1ZXMgaW4gY29sdW1uIHRyYWNrX2dlbnJlDQogIHN1bW1hcmlzZShuX3Jvd3MgPSBuKCkpIA0KYGBgDQoNCmBgYHtyfQ0KZGF0YXNldDIgJT4lDQpjb3VudCh0cmFja19nZW5yZT0iamF6eiIsa2V5KQ0KYGBgDQoNCmBgYHtyfQ0Ka2V5amF6el9zdW1tYXJ5IDwtIGRhdGFzZXQyICU+JSANCiAgY291bnQodHJhY2tfZ2VucmU9ImphenoiLGtleSkgJT4lICAgICAgICAgICAgICAgICAgICAgIyBncm91cCBhbmQgY291bnQgYnkgZ2VuZGVyIChwcm9kdWNlcyAibiIgY29sdW1uKQ0KICBtdXRhdGUoICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIGNyZWF0ZSBwZXJjZW50IG9mIGNvbHVtbiAtIG5vdGUgdGhlIGRlbm9taW5hdG9yDQogICAgcGVyY2VudCA9IHNjYWxlczo6cGVyY2VudChuIC8gc3VtKG4pKSkgDQoNCiMgcHJpbnQNCmtleWphenpfc3VtbWFyeQ0KYGBgDQoNCmBgYHtyfQ0KZGF0YXNldDIgJT4lICAgICAgICAgICAgICAgICAgICAgIA0KICBjb3VudCh0cmFja19nZW5yZSwga2V5KSAlPiUgICAgICMgZ3JvdXAgYW5kIHRhYnVsYXRlIGNvdW50cyBieSB0d28gY29sdW1ucw0KICBnZ3Bsb3QoKSsgICAgICAgICAgICAgICAgICAgICAgICMgcGFzcyBuZXcgZGF0YSBmcmFtZSB0byBnZ3Bsb3QNCiAgICBnZW9tX2NvbCggICAgICAgICAgICAgICAgICAgICAjIGNyZWF0ZSBiYXIgcGxvdA0KICAgICAgbWFwcGluZyA9IGFlcyggICANCiAgICAgICAgeCA9ICh0cmFja19nZW5yZSksICAgICAgICAgICAgICAjIG1hcCBvdXRjb21lIHRvIHgtYXhpcw0KICAgICAgICBmaWxsID0ga2V5LCAgICAgICAgICAgIyBtYXAgYWdlX2NhdCB0byB0aGUgZmlsbA0KICAgICAgICB5ID0gbikpICAgICANCmBgYA0KDQpgYGB7cn0NCmtleV9ieV9nZW5yZSA8LSBkYXRhc2V0MiAlPiUgICAgICAgICAgICAgICAgICANCiAgZ3JvdXBfYnkodHJhY2tfZ2VucmUpICU+JSAgICAgICAgICAgICAgICAgICAgICAgICANCiAgY291bnQoa2V5KSAlPiUgICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgbXV0YXRlKHBlcmNlbnQgPSBzY2FsZXM6OnBlcmNlbnQobiAvIHN1bShuKSkpIA0Ka2V5X2J5X2dlbnJlDQpgYGANCg0KIyMjIE1PREVMSU5HIDoNCg0KU1BPVElGWSA6IENoZWNrIEthZ2dsZSBldCBhdXRyZXMNCg0KPVw+IGV4cG9ydGluZyBiZXN0IGtleXMgcGVyIGdlbnJlIG9ubHkgZm9yIHNvbmdzIHdpdGggcG9wdWxhcml0eSBcPiAwLDYNCg0KQk9JVEVBQ0hBTlNPTiA6IENoZWNrIGxlcyBjb3Vycw0KDQo9XD4gb3V0cHV0IGJlc3QgY2hvcmRzIGFuZCBiZXN0IGtleXMNCg0KPVw+IGhvdyB0byBjcmVhdGUgY2hvcmRzIHdpdGggbm90ZXMud2F2DQoNCmBgYHtyfQ0KdG9wNTB2MyAlPiUNCmNvdW50KGtleSkNCmBgYA0K