Nothing serious … for now.

A few days ago, I took the Microsoft Azure AI Fundamentals Certification. Honestly, I was really impressed with how one could implement and publish ML and AI workloads such as Classification, Regression, Computer Vision, Natural Language Processing, etc with almost no code 🤯.

Anyway being the hopeless Romantic I am, I found myself exploring the possibility of leveraging some of the customisable, pretrained models in Azure, through R, to do some cool (and eventually useful 😄) AI workloads.

So here we are: my first attempt in using the Microsoft Azure Cognitive Services from R.

Azure Cognitive Services with R

From the Azure Cognitive Services documentation:

Azure Cognitive Services are cloud-based services with REST APIs, client library SDKs, and user interfaces available to help you build cognitive intelligence into your applications. You can add cognitive features to your applications without having artificial intelligence (AI) or data science skills. Cognitive Services comprises various AI services that enable you to build cognitive solutions that can see, hear, speak, understand, and even make decisions.

In this quick walk-through, we’ll explore the Vision cognitive service, in particular: Computer Vision API. The Computer Vision API allows you to analyze content in images and videos.

Let’s get into all things Azure!

Create a Cognitive Services Resource

We’ll start by creating a Cognitive Services resource in our Azure subscription. If you don’t have an Azure subscription, you can create a free Azure account here: https://azure.microsoft.com/en-gb/

Note: If you already have a Cognitive Services resource, we are good to go. Otherwise, follow the steps below to create one.

  1. Open the Azure portal at https://portal.azure.com, signing in with your Microsoft account.

  2. Click the +Create a resource button, search for Cognitive Services, and create a Cognitive Services resource with the following settings:

  • Subscription: Your Azure subscription.

  • Resource group: Select or create a resource group with a unique name.

  • Region: Choose any available region:

  • Name: Enter a unique name.

  • Pricing tier: S0

  • I confirm I have read and understood the notices: Selected.

Just as a reference, the Cognitive Services resource used when writing this post had the following details (ignore the weird names):

Now with that out of the way, let make Azure, AzureR!

AzureR

AzureR is a family of packages that allow you to work with Azure from R. In this section, we’ll set up an endpoint that can be used to communicate with the features offered by the Azure Cognitive Services.

The AzureCognitive::cognitive_endpoint() function allows us to do just this and requires 3 main arguments:

  • url: The URL of the endpoint

  • service_type: The type (or kind) of service the endpoint will provide. Types of services include ComputerVision, Face, Text, etc. See ?AzureCognitive::cognitive_endpoint() for more details.

  • key: A subscription key to authenticate with the endpoint.

These can be easily obtained from the Azure portal on the Keys and Endpoint page of your cognitive service resource as shown below:

An endpoint can be subsequently created as outlined:

  • In the Azure portal, on the Keys and Endpoint page for your cognitive service resource, copy Key1 or Key 2 and the Endpoint for your resource and paste them in the code below (within the quotation marks):
# Load required packages
library(AzureRMR) # install.packages("AzureRMR")
library(AzureCognitive) # install.packages("AzureCognitive")

# Create Computer Vision endpoint
endp_cv <- cognitive_endpoint(
  url = "____",
  service_type = "ComputerVision",
  key = "____")

We can of course decide to take the R pill and resurrect the above endpoints as follows:

Note: You will have to modify the subscription name, resource group and cognitive service names to match the ones on the Cognitive Services resource you created earlier.

# Load required packages
library(tidyverse)
library(AzureRMR) # install.packages("AzureRMR")
library(AzureCognitive) # install.packages("AzureCognitive")

# Login to Azure Resource Manager
# uses a web browser and access token to sign in
az <- create_azure_login()

# Obtain subscription for which you have permissions
# to manage Azure resources
sub <- az$get_subscription_by_name("Visual Studio Enterprise Subscription")

# Access the resource group associated with 
# the cognitive services you created earlier
rg <- sub$get_resource_group("keras")

# Retrieve cognitive service
cogsvc <- rg$get_cognitive_service("faciem")

# Obtain Subscription keys
keys <- cogsvc$list_keys()
key_1 <- keys %>%
  as_tibble() %>% 
  pull(value) %>%
  pluck(1)

key_2 <- keys %>% 
  as_tibble() %>%
  pull(value) %>% 
  pluck(2)


## Create Azure cognitive services endpoints ##

# Create Computer Vision endpoint
endp_cv <- cognitive_endpoint(
  url = "https://faciem.cognitiveservices.azure.com/",
  service_type = "ComputerVision",
  key = key_1)
endp_cv

# Create Face endpoint
endp_face <- cognitive_endpoint(
  url = "https://faciem.cognitiveservices.azure.com/",
  service_type = "Face",
  key = key_1)
endp_face
## Azure Cognitive Service endpoint
## Service type: computervision_endpoint 
## Endpoint URL: https://faciem.cognitiveservices.azure.com/vision/v2.0

Now that the endpoint is set up, let’s have some fun!

Computer vision: Does this look like … Hadley 🤔 ?

The Computer Vision API provides us with access to advanced algorithms for processing images and returning information. There are a whole lot of cool things you could do with this API, from extracting handwritten text from images, generating a description of an entire image in human-readable language to measuring compliance with social distancing guidelines!

In this post, we’ll make an API call to analyze an image of Hadley Wickham. Chances are you already know him or have used some of the data science tools he builds such as ggplot2 and dplyr.

Hadley Wickham: Source https://twitter.com/hadleywickham

With the computer vision endpoint set, we just have to make an API call and … done! Let’s take a moment and explain some of arguments in the API call below:

  • operation = "analyze": specifies the operation to be performed on a particular image. Depending on the API, examples of operations include analyze, detect, and Optical Character Recognition.

  • details = "celebrities": identifies celebrities if detected in the image.

  • visualFeatures = "adult,faces,description,tags": A string indicating what visual feature types to return. For instance, Description describes the image content with a complete sentence in supported languages.

Please see the Computer Vision APIfor more details and examples about different operations and features within the computer vision API.

# Specify image link
image_link <- "https://pbs.twimg.com/profile_images/905186381995147264/7zKAG5sY_400x400.jpg"

# Call the computer vision endpoint
call_cognitive_endpoint(
  endpoint = endp_cv,
  operation = "analyze",
  body = list(url = image_link),
  options = list(
    # Request parameters
    details = "celebrities",
    visualFeatures = "adult,faces,description,tags"
  ),
  http_verb = "POST"
)
## $categories
## $categories[[1]]
## $categories[[1]]$name
## [1] "people_portrait"
## 
## $categories[[1]]$score
## [1] 0.7695312
## 
## $categories[[1]]$detail
## $categories[[1]]$detail$celebrities
## $categories[[1]]$detail$celebrities[[1]]
## $categories[[1]]$detail$celebrities[[1]]$name
## [1] "Hadley Wickham"
## 
## $categories[[1]]$detail$celebrities[[1]]$confidence
## [1] 0.9999046
## 
## $categories[[1]]$detail$celebrities[[1]]$faceRectangle
## $categories[[1]]$detail$celebrities[[1]]$faceRectangle$left
## [1] 117
## 
## $categories[[1]]$detail$celebrities[[1]]$faceRectangle$top
## [1] 109
## 
## $categories[[1]]$detail$celebrities[[1]]$faceRectangle$width
## [1] 168
## 
## $categories[[1]]$detail$celebrities[[1]]$faceRectangle$height
## [1] 168
## 
## 
## 
## 
## 
## 
## 
## $adult
## $adult$isAdultContent
## [1] FALSE
## 
## $adult$isRacyContent
## [1] FALSE
## 
## $adult$adultScore
## [1] 0.01674674
## 
## $adult$racyScore
## [1] 0.01742559
## 
## 
## $tags
## $tags[[1]]
## $tags[[1]]$name
## [1] "man"
## 
## $tags[[1]]$confidence
## [1] 0.999713
## 
## 
## $tags[[2]]
## $tags[[2]]$name
## [1] "necktie"
## 
## $tags[[2]]$confidence
## [1] 0.9997081
## 
## 
## $tags[[3]]
## $tags[[3]]$name
## [1] "person"
## 
## $tags[[3]]$confidence
## [1] 0.999671
## 
## 
## $tags[[4]]
## $tags[[4]]$name
## [1] "human face"
## 
## $tags[[4]]$confidence
## [1] 0.9850532
## 
## 
## $tags[[5]]
## $tags[[5]]$name
## [1] "bow"
## 
## $tags[[5]]$confidence
## [1] 0.9765703
## 
## 
## $tags[[6]]
## $tags[[6]]$name
## [1] "wearing"
## 
## $tags[[6]]$confidence
## [1] 0.9742473
## 
## 
## $tags[[7]]
## $tags[[7]]$name
## [1] "smile"
## 
## $tags[[7]]$confidence
## [1] 0.9727616
## 
## 
## $tags[[8]]
## $tags[[8]]$name
## [1] "clothing"
## 
## $tags[[8]]$confidence
## [1] 0.9532967
## 
## 
## $tags[[9]]
## $tags[[9]]$name
## [1] "posing"
## 
## $tags[[9]]$confidence
## [1] 0.8997302
## 
## 
## $tags[[10]]
## $tags[[10]]$name
## [1] "suit"
## 
## $tags[[10]]$confidence
## [1] 0.8962718
## 
## 
## $tags[[11]]
## $tags[[11]]$name
## [1] "indoor"
## 
## $tags[[11]]$confidence
## [1] 0.8934997
## 
## 
## $tags[[12]]
## $tags[[12]]$name
## [1] "tie"
## 
## $tags[[12]]$confidence
## [1] 0.8858114
## 
## 
## $tags[[13]]
## $tags[[13]]$name
## [1] "smiling"
## 
## $tags[[13]]$confidence
## [1] 0.879541
## 
## 
## $tags[[14]]
## $tags[[14]]$name
## [1] "headshot"
## 
## $tags[[14]]$confidence
## [1] 0.7387407
## 
## 
## $tags[[15]]
## $tags[[15]]$name
## [1] "shirt"
## 
## $tags[[15]]$confidence
## [1] 0.7014446
## 
## 
## $tags[[16]]
## $tags[[16]]$name
## [1] "beard"
## 
## $tags[[16]]$confidence
## [1] 0.6043768
## 
## 
## $tags[[17]]
## $tags[[17]]$name
## [1] "purple"
## 
## $tags[[17]]$confidence
## [1] 0.5996998
## 
## 
## $tags[[18]]
## $tags[[18]]$name
## [1] "portrait"
## 
## $tags[[18]]$confidence
## [1] 0.5956337
## 
## 
## $tags[[19]]
## $tags[[19]]$name
## [1] "jacket"
## 
## $tags[[19]]$confidence
## [1] 0.5485496
## 
## 
## $tags[[20]]
## $tags[[20]]$name
## [1] "forehead"
## 
## $tags[[20]]$confidence
## [1] 0.5137761
## 
## 
## $tags[[21]]
## $tags[[21]]$name
## [1] "dressed"
## 
## $tags[[21]]$confidence
## [1] 0.3506995
## 
## 
## 
## $description
## $description$tags
## $description$tags[[1]]
## [1] "man"
## 
## $description$tags[[2]]
## [1] "necktie"
## 
## $description$tags[[3]]
## [1] "person"
## 
## $description$tags[[4]]
## [1] "bow"
## 
## $description$tags[[5]]
## [1] "wearing"
## 
## $description$tags[[6]]
## [1] "clothing"
## 
## $description$tags[[7]]
## [1] "posing"
## 
## $description$tags[[8]]
## [1] "suit"
## 
## $description$tags[[9]]
## [1] "indoor"
## 
## $description$tags[[10]]
## [1] "smiling"
## 
## $description$tags[[11]]
## [1] "shirt"
## 
## $description$tags[[12]]
## [1] "photo"
## 
## $description$tags[[13]]
## [1] "camera"
## 
## $description$tags[[14]]
## [1] "purple"
## 
## $description$tags[[15]]
## [1] "dress"
## 
## $description$tags[[16]]
## [1] "jacket"
## 
## $description$tags[[17]]
## [1] "dressed"
## 
## $description$tags[[18]]
## [1] "standing"
## 
## $description$tags[[19]]
## [1] "neck"
## 
## $description$tags[[20]]
## [1] "glasses"
## 
## $description$tags[[21]]
## [1] "gray"
## 
## $description$tags[[22]]
## [1] "green"
## 
## $description$tags[[23]]
## [1] "old"
## 
## $description$tags[[24]]
## [1] "blue"
## 
## 
## $description$captions
## $description$captions[[1]]
## $description$captions[[1]]$text
## [1] "Hadley Wickham wearing a suit and tie smiling at the camera"
## 
## $description$captions[[1]]$confidence
## [1] 0.9901859
## 
## 
## 
## 
## $faces
## $faces[[1]]
## $faces[[1]]$age
## [1] 34
## 
## $faces[[1]]$gender
## [1] "Male"
## 
## $faces[[1]]$faceRectangle
## $faces[[1]]$faceRectangle$left
## [1] 117
## 
## $faces[[1]]$faceRectangle$top
## [1] 109
## 
## $faces[[1]]$faceRectangle$width
## [1] 168
## 
## $faces[[1]]$faceRectangle$height
## [1] 168
## 
## 
## 
## 
## $requestId
## [1] "4dd756aa-83a5-4d83-9b4b-98287d65bf11"
## 
## $metadata
## $metadata$height
## [1] 400
## 
## $metadata$width
## [1] 400
## 
## $metadata$format
## [1] "Jpeg"


There goes the response of the API call. Not bad, the suit part though… 😄! Probably this indicates that the model associates a tie with a suit.

Let’s put some of the responses into a tibble and display them as a gt table object.

library(gt)
cv_response <- tibble(
  request_parameter = c("categories", "tags", "celebrity_details", "tags", "tags",
"isAdultContent", "isRacyContent", "faces", "description"),
  
  response = c("people_portrait", "human face", "Hadley Wickham", "necktie", "beard",
"FALSE", "FALSE", "estimated age",
"Hadley Wickham wearing a suit and tie smiling at the camera"),
  
  score = c(0.7695312, 0.9850532,
            0.999904632568359, 0.9997081, 0.6043768,
            0.01674674, 0.01742559, 34, 0.9901859) 
)

cv_response %>% 
  gt() %>% 
  tab_header(
    title = md(
    "<img src = 'https://pbs.twimg.com/profile_images/905186381995147264/7zKAG5sY_400x400.jpg'
align = 'center' style = 'height:39px'>
Hadley Wickham wearing a suit and tie smiling at the camera "),
    subtitle = md("Computer Vision API reponse")) %>% 
  cols_align(
    align = "left",
    columns = 1) %>% 
    cols_align(
      align = "center",
      columns = 2) %>% 
    cols_align(
      align = "right",
      columns = 3)
Hadley Wickham wearing a suit and tie smiling at the camera
Computer Vision API reponse
request_parameter response score
categories people_portrait 0.76953120
tags human face 0.98505320
celebrity_details Hadley Wickham 0.99990463
tags necktie 0.99970810
tags beard 0.60437680
isAdultContent FALSE 0.01674674
isRacyContent FALSE 0.01742559
faces estimated age 34.00000000
description Hadley Wickham wearing a suit and tie smiling at the camera 0.99018590


Much better! We’ll wrap up this short walk-through at that. We may have only explored a small section of the Computer Vision API (in particular Image Analysis) but this can be extended to other operations such as Optical character recognition or even other cognitive services such as Speech. This means you can even do the learning labs associated with the Microsoft Azure AI Fundamentals Certification in R! Hopefully we’ll get to R some of them soon 😎!

Thanks for reading!

Be sure to check out great blogs, tutorials and other formats of R resources coming out every day at RWeekly.org!

Further reading

Happy leaRning,

Eric, Gold Microsoft Learn Student Ambassador.


LS0tDQp0aXRsZTogIkEgcXVpY2sgZmx5Ynk6IEF6dXJlIENvZ25pdGl2ZSBTZXJ2aWNlcyBmcm9tIFIiDQpvdXRwdXQ6DQogICAgICBybWFya2Rvd246Omh0bWxfZG9jdW1lbnQ6DQogICAgICAgIGRmX3ByaW50OiBwYWdlZA0KICAgICAgICBjc3M6IHN0eWxlXzcuY3NzDQogICAgICAgIHRoZW1lOiBmbGF0bHkNCiAgICAgICAgaGlnaGxpZ2h0OiBicmVlemVkYXJrDQogICAgICAgIHRvYzogeWVzDQogICAgICAgIHRvY19mbG9hdDogeWVzDQogICAgICAgIGNvZGVfZG93bmxvYWQ6IHllcw0KLS0tDQoNCiMjIE5vdGhpbmcgc2VyaW91cyAuLi4gZm9yIG5vdy4NCg0KQSBmZXcgZGF5cyBhZ28sIEkgdG9vayB0aGUgTWljcm9zb2Z0IFtBenVyZSBBSSBGdW5kYW1lbnRhbHMgQ2VydGlmaWNhdGlvbl0oaHR0cHM6Ly9kb2NzLm1pY3Jvc29mdC5jb20vZW4tZ2IvbGVhcm4vY2VydGlmaWNhdGlvbnMvYXp1cmUtYWktZnVuZGFtZW50YWxzLykuIEhvbmVzdGx5LCBJIHdhcyByZWFsbHkgaW1wcmVzc2VkIHdpdGggaG93IG9uZSBjb3VsZCBpbXBsZW1lbnQgYW5kIHB1Ymxpc2ggTUwgYW5kIEFJIHdvcmtsb2FkcyBzdWNoIGFzIENsYXNzaWZpY2F0aW9uLCBSZWdyZXNzaW9uLCBDb21wdXRlciBWaXNpb24sIE5hdHVyYWwgTGFuZ3VhZ2UgUHJvY2Vzc2luZywgZXRjIHdpdGggYWxtb3N0IG5vIGNvZGUg8J+kry4NCg0KQW55d2F5IGJlaW5nIHRoZSBob3BlbGVzcyAqKlIqKm9tYW50aWMgSSBhbSwgSSBmb3VuZCBteXNlbGYgZXhwbG9yaW5nIHRoZSBwb3NzaWJpbGl0eSBvZiBsZXZlcmFnaW5nIHNvbWUgb2YgdGhlIGN1c3RvbWlzYWJsZSwgcHJldHJhaW5lZCBtb2RlbHMgaW4gQXp1cmUsIHRocm91Z2ggUiwgdG8gZG8gc29tZSBjb29sIChhbmQgZXZlbnR1YWxseSB1c2VmdWwg8J+YhCkgQUkgd29ya2xvYWRzLg0KDQpTbyBoZXJlIHdlIGFyZTogbXkgZmlyc3QgYXR0ZW1wdCBpbiB1c2luZyB0aGUgTWljcm9zb2Z0IEF6dXJlIENvZ25pdGl2ZSBTZXJ2aWNlcyBmcm9tIFIuDQoNCiMjIEF6dXJlIENvZ25pdGl2ZSBTZXJ2aWNlcyB3aXRoIFINCg0KRnJvbSB0aGUgW0F6dXJlIENvZ25pdGl2ZSBTZXJ2aWNlcyBkb2N1bWVudGF0aW9uXShodHRwczovL2RvY3MubWljcm9zb2Z0LmNvbS9lbi1nYi9henVyZS9jb2duaXRpdmUtc2VydmljZXMvd2hhdC1hcmUtY29nbml0aXZlLXNlcnZpY2VzKToNCg0KPiBBenVyZSBDb2duaXRpdmUgU2VydmljZXMgYXJlIGNsb3VkLWJhc2VkIHNlcnZpY2VzIHdpdGggUkVTVCBBUElzLCBjbGllbnQgbGlicmFyeSBTREtzLCBhbmQgdXNlciBpbnRlcmZhY2VzIGF2YWlsYWJsZSB0byBoZWxwIHlvdSBidWlsZCBjb2duaXRpdmUgaW50ZWxsaWdlbmNlIGludG8geW91ciBhcHBsaWNhdGlvbnMuIFlvdSBjYW4gYWRkIGNvZ25pdGl2ZSBmZWF0dXJlcyB0byB5b3VyIGFwcGxpY2F0aW9ucyB3aXRob3V0IGhhdmluZyBhcnRpZmljaWFsIGludGVsbGlnZW5jZSAoQUkpIG9yIGRhdGEgc2NpZW5jZSBza2lsbHMuIENvZ25pdGl2ZSBTZXJ2aWNlcyBjb21wcmlzZXMgdmFyaW91cyBBSSBzZXJ2aWNlcyB0aGF0IGVuYWJsZSB5b3UgdG8gYnVpbGQgY29nbml0aXZlIHNvbHV0aW9ucyB0aGF0IGNhbiBzZWUsIGhlYXIsIHNwZWFrLCB1bmRlcnN0YW5kLCBhbmQgZXZlbiBtYWtlIGRlY2lzaW9ucy4NCg0KSW4gdGhpcyBxdWljayB3YWxrLXRocm91Z2gsIHdlJ2xsIGV4cGxvcmUgdGhlIFtWaXNpb25dKGh0dHBzOi8vYXp1cmUubWljcm9zb2Z0LmNvbS9lbi1nYi9zZXJ2aWNlcy9jb2duaXRpdmUtc2VydmljZXMvI2FwaSkgY29nbml0aXZlIHNlcnZpY2UsIGluIHBhcnRpY3VsYXI6IGBDb21wdXRlciBWaXNpb24gQVBJYC4gVGhlIGBDb21wdXRlciBWaXNpb25gIEFQSSBhbGxvd3MgeW91IHRvIGFuYWx5emUgY29udGVudCBpbiBpbWFnZXMgYW5kIHZpZGVvcy4NCg0KTGV0J3MgZ2V0IGludG8gYWxsIHRoaW5ncyBBenVyZSENCg0KIyMjIENyZWF0ZSBhIENvZ25pdGl2ZSBTZXJ2aWNlcyBSZXNvdXJjZSB7I2Nzcn0NCg0KV2UnbGwgc3RhcnQgYnkgY3JlYXRpbmcgYSAqKkNvZ25pdGl2ZSBTZXJ2aWNlcyoqIHJlc291cmNlIGluIG91ciBBenVyZSBzdWJzY3JpcHRpb24uIElmIHlvdSBkb24ndCBoYXZlIGFuIEF6dXJlIHN1YnNjcmlwdGlvbiwgeW91IGNhbiBjcmVhdGUgYSBmcmVlIEF6dXJlIGFjY291bnQgaGVyZTogPGh0dHBzOi8vYXp1cmUubWljcm9zb2Z0LmNvbS9lbi1nYi8+DQoNCj4gKipOb3RlKio6IElmIHlvdSBhbHJlYWR5IGhhdmUgYSBDb2duaXRpdmUgU2VydmljZXMgcmVzb3VyY2UsIHdlIGFyZSBnb29kIHRvIGdvLiBPdGhlcndpc2UsIGZvbGxvdyB0aGUgc3RlcHMgYmVsb3cgdG8gY3JlYXRlIG9uZS4NCg0KMS4gIE9wZW4gdGhlIEF6dXJlIHBvcnRhbCBhdCBbaHR0cHM6Ly9wb3J0YWwuYXp1cmUuY29tXShodHRwczovL3BvcnRhbC5henVyZS5jb20vKSwgc2lnbmluZyBpbiB3aXRoIHlvdXIgTWljcm9zb2Z0IGFjY291bnQuDQoNCjIuICBDbGljayB0aGUgKirvvItDcmVhdGUgYSByZXNvdXJjZSoqIGJ1dHRvbiwgc2VhcmNoIGZvciAqQ29nbml0aXZlIFNlcnZpY2VzKiwgYW5kIGNyZWF0ZSBhICoqQ29nbml0aXZlIFNlcnZpY2VzKiogcmVzb3VyY2Ugd2l0aCB0aGUgZm9sbG93aW5nIHNldHRpbmdzOg0KDQotICAgKipTdWJzY3JpcHRpb24qKjogKllvdXIgQXp1cmUgc3Vic2NyaXB0aW9uKi4NCg0KLSAgICoqUmVzb3VyY2UgZ3JvdXAqKjogKlNlbGVjdCBvciBjcmVhdGUgYSByZXNvdXJjZSBncm91cCB3aXRoIGEgdW5pcXVlIG5hbWUqLg0KDQotICAgKipSZWdpb24qKjogKkNob29zZSBhbnkgYXZhaWxhYmxlIHJlZ2lvbio6DQoNCi0gICAqKk5hbWUqKjogKkVudGVyIGEgdW5pcXVlIG5hbWUqLg0KDQotICAgKipQcmljaW5nIHRpZXIqKjogUzANCg0KLSAgICoqSSBjb25maXJtIEkgaGF2ZSByZWFkIGFuZCB1bmRlcnN0b29kIHRoZSBub3RpY2VzKio6IFNlbGVjdGVkLg0KDQpKdXN0IGFzIGEgcmVmZXJlbmNlLCB0aGUgQ29nbml0aXZlIFNlcnZpY2VzIHJlc291cmNlIHVzZWQgd2hlbiB3cml0aW5nIHRoaXMgcG9zdCBoYWQgdGhlIGZvbGxvd2luZyBkZXRhaWxzIChpZ25vcmUgdGhlIHdlaXJkIG5hbWVzKToNCg0KIVtdKGNvZ3NmLnBuZyl7d2lkdGg9IjQ1MCJ9DQoNCk5vdyB3aXRoIHRoYXQgb3V0IG9mIHRoZSB3YXksIGxldCBtYWtlIEF6dXJlLCBBenVyZVIhDQoNCiMjIyBBenVyZVINCg0KW0F6dXJlUl0oaHR0cHM6Ly9naXRodWIuY29tL0F6dXJlL0F6dXJlUikgaXMgYSBmYW1pbHkgb2YgcGFja2FnZXMgdGhhdCBhbGxvdyB5b3UgdG8gd29yayB3aXRoIEF6dXJlIGZyb20gUi4gSW4gdGhpcyBzZWN0aW9uLCB3ZSdsbCBzZXQgdXAgYW4gZW5kcG9pbnQgdGhhdCBjYW4gYmUgdXNlZCB0byBjb21tdW5pY2F0ZSB3aXRoIHRoZSBmZWF0dXJlcyBvZmZlcmVkIGJ5IHRoZSBBenVyZSBDb2duaXRpdmUgU2VydmljZXMuDQoNClRoZSBgQXp1cmVDb2duaXRpdmU6OmNvZ25pdGl2ZV9lbmRwb2ludCgpYCBmdW5jdGlvbiBhbGxvd3MgdXMgdG8gZG8ganVzdCB0aGlzIGFuZCByZXF1aXJlcyAzIG1haW4gYXJndW1lbnRzOg0KDQotICAgYHVybGA6IFRoZSBVUkwgb2YgdGhlIGVuZHBvaW50DQoNCi0gICBgc2VydmljZV90eXBlYDogVGhlIHR5cGUgKG9yIGtpbmQpIG9mIHNlcnZpY2UgdGhlIGVuZHBvaW50IHdpbGwgcHJvdmlkZS4gVHlwZXMgb2Ygc2VydmljZXMgaW5jbHVkZSAqQ29tcHV0ZXJWaXNpb24qLCAqRmFjZSwgVGV4dCwgZXRjLiogU2VlIGA/QXp1cmVDb2duaXRpdmU6OmNvZ25pdGl2ZV9lbmRwb2ludCgpYCBmb3IgbW9yZSBkZXRhaWxzLg0KDQotICAgYGtleWA6IEEgc3Vic2NyaXB0aW9uIGtleSB0byBhdXRoZW50aWNhdGUgd2l0aCB0aGUgZW5kcG9pbnQuDQoNClRoZXNlIGNhbiBiZSBlYXNpbHkgb2J0YWluZWQgZnJvbSB0aGUgQXp1cmUgcG9ydGFsIG9uIHRoZSAqKktleXMgYW5kIEVuZHBvaW50KiogcGFnZSBvZiB5b3VyIGNvZ25pdGl2ZSBzZXJ2aWNlIHJlc291cmNlIGFzIHNob3duIGJlbG93Og0KDQohW10oa2V5cy5wbmcpe3dpZHRoPSI0NTAifQ0KDQpBbiBlbmRwb2ludCBjYW4gYmUgc3Vic2VxdWVudGx5IGNyZWF0ZWQgYXMgb3V0bGluZWQ6DQoNCi0gICBJbiB0aGUgQXp1cmUgcG9ydGFsLCBvbiB0aGUgKipLZXlzIGFuZCBFbmRwb2ludCoqIHBhZ2UgZm9yIHlvdXIgY29nbml0aXZlIHNlcnZpY2UgcmVzb3VyY2UsIGNvcHkgKipLZXkxKiogb3IgKipLZXkgMioqIGFuZCB0aGUgKipFbmRwb2ludCoqIGZvciB5b3VyIHJlc291cmNlIGFuZCBwYXN0ZSB0aGVtIGluIHRoZSBjb2RlIGJlbG93ICh3aXRoaW4gdGhlIHF1b3RhdGlvbiBtYXJrcyk6DQoNCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9VFJVRX0NCiMgTG9hZCByZXF1aXJlZCBwYWNrYWdlcw0KbGlicmFyeShBenVyZVJNUikgIyBpbnN0YWxsLnBhY2thZ2VzKCJBenVyZVJNUiIpDQpsaWJyYXJ5KEF6dXJlQ29nbml0aXZlKSAjIGluc3RhbGwucGFja2FnZXMoIkF6dXJlQ29nbml0aXZlIikNCg0KIyBDcmVhdGUgQ29tcHV0ZXIgVmlzaW9uIGVuZHBvaW50DQplbmRwX2N2IDwtIGNvZ25pdGl2ZV9lbmRwb2ludCgNCiAgdXJsID0gIl9fX18iLA0KICBzZXJ2aWNlX3R5cGUgPSAiQ29tcHV0ZXJWaXNpb24iLA0KICBrZXkgPSAiX19fXyIpDQoNCg0KDQoNCmBgYA0KDQpXZSBjYW4gb2YgY291cnNlIGRlY2lkZSB0byB0YWtlIHRoZSBgUmAgcGlsbCBhbmQgcmVzdXJyZWN0IHRoZSBhYm92ZSBlbmRwb2ludHMgYXMgZm9sbG93czoNCg0KPiAqKmBOb3RlYCoqOiBZb3Ugd2lsbCBoYXZlIHRvIG1vZGlmeSB0aGUgc3Vic2NyaXB0aW9uIG5hbWUsIHJlc291cmNlIGdyb3VwIGFuZCBjb2duaXRpdmUgc2VydmljZSBuYW1lcyB0byBtYXRjaCB0aGUgb25lcyBvbiB0aGUgQ29nbml0aXZlIFNlcnZpY2VzIHJlc291cmNlIHlvdSBjcmVhdGVkIGVhcmxpZXIuDQoNCmBgYHtyIGV2YWw9RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9VH0NCiMgTG9hZCByZXF1aXJlZCBwYWNrYWdlcw0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KEF6dXJlUk1SKSAjIGluc3RhbGwucGFja2FnZXMoIkF6dXJlUk1SIikNCmxpYnJhcnkoQXp1cmVDb2duaXRpdmUpICMgaW5zdGFsbC5wYWNrYWdlcygiQXp1cmVDb2duaXRpdmUiKQ0KDQojIExvZ2luIHRvIEF6dXJlIFJlc291cmNlIE1hbmFnZXINCiMgdXNlcyBhIHdlYiBicm93c2VyIGFuZCBhY2Nlc3MgdG9rZW4gdG8gc2lnbiBpbg0KYXogPC0gY3JlYXRlX2F6dXJlX2xvZ2luKCkNCg0KIyBPYnRhaW4gc3Vic2NyaXB0aW9uIGZvciB3aGljaCB5b3UgaGF2ZSBwZXJtaXNzaW9ucw0KIyB0byBtYW5hZ2UgQXp1cmUgcmVzb3VyY2VzDQpzdWIgPC0gYXokZ2V0X3N1YnNjcmlwdGlvbl9ieV9uYW1lKCJWaXN1YWwgU3R1ZGlvIEVudGVycHJpc2UgU3Vic2NyaXB0aW9uIikNCg0KIyBBY2Nlc3MgdGhlIHJlc291cmNlIGdyb3VwIGFzc29jaWF0ZWQgd2l0aCANCiMgdGhlIGNvZ25pdGl2ZSBzZXJ2aWNlcyB5b3UgY3JlYXRlZCBlYXJsaWVyDQpyZyA8LSBzdWIkZ2V0X3Jlc291cmNlX2dyb3VwKCJrZXJhcyIpDQoNCiMgUmV0cmlldmUgY29nbml0aXZlIHNlcnZpY2UNCmNvZ3N2YyA8LSByZyRnZXRfY29nbml0aXZlX3NlcnZpY2UoImZhY2llbSIpDQoNCiMgT2J0YWluIFN1YnNjcmlwdGlvbiBrZXlzDQprZXlzIDwtIGNvZ3N2YyRsaXN0X2tleXMoKQ0Ka2V5XzEgPC0ga2V5cyAlPiUNCiAgYXNfdGliYmxlKCkgJT4lIA0KICBwdWxsKHZhbHVlKSAlPiUNCiAgcGx1Y2soMSkNCg0Ka2V5XzIgPC0ga2V5cyAlPiUgDQogIGFzX3RpYmJsZSgpICU+JQ0KICBwdWxsKHZhbHVlKSAlPiUgDQogIHBsdWNrKDIpDQoNCg0KIyMgQ3JlYXRlIEF6dXJlIGNvZ25pdGl2ZSBzZXJ2aWNlcyBlbmRwb2ludHMgIyMNCg0KIyBDcmVhdGUgQ29tcHV0ZXIgVmlzaW9uIGVuZHBvaW50DQplbmRwX2N2IDwtIGNvZ25pdGl2ZV9lbmRwb2ludCgNCiAgdXJsID0gImh0dHBzOi8vZmFjaWVtLmNvZ25pdGl2ZXNlcnZpY2VzLmF6dXJlLmNvbS8iLA0KICBzZXJ2aWNlX3R5cGUgPSAiQ29tcHV0ZXJWaXNpb24iLA0KICBrZXkgPSBrZXlfMSkNCmVuZHBfY3YNCg0KIyBDcmVhdGUgRmFjZSBlbmRwb2ludA0KZW5kcF9mYWNlIDwtIGNvZ25pdGl2ZV9lbmRwb2ludCgNCiAgdXJsID0gImh0dHBzOi8vZmFjaWVtLmNvZ25pdGl2ZXNlcnZpY2VzLmF6dXJlLmNvbS8iLA0KICBzZXJ2aWNlX3R5cGUgPSAiRmFjZSIsDQogIGtleSA9IGtleV8xKQ0KZW5kcF9mYWNlDQpgYGANCg0KYGBge3IgZWNobz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiMgTG9hZCByZXF1aXJlZCBwYWNrYWdlcw0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KEF6dXJlUk1SKSAjIGluc3RhbGwucGFja2FnZXMoIkF6dXJlUk1SIikNCmxpYnJhcnkoQXp1cmVDb2duaXRpdmUpICMgaW5zdGFsbC5wYWNrYWdlcygiQXp1cmVDb2duaXRpdmUiKQ0KDQojIHJldHJpZXZlIHRoZSBsb2dpbiBpbiBzdWJzZXF1ZW50IHNlc3Npb25zDQpheiA8LSBnZXRfYXp1cmVfbG9naW4oKQ0KDQojIE9idGFpbiBzdWJzY3JpcHRpb24gZm9yIHdoaWNoIHlvdSBoYXZlIHBlcm1pc3Npb25zDQojIHRvIG1hbmFnZSBBenVyZSByZXNvdXJjZXMNCnN1YiA8LSBheiRnZXRfc3Vic2NyaXB0aW9uX2J5X25hbWUoIlZpc3VhbCBTdHVkaW8gRW50ZXJwcmlzZSBTdWJzY3JpcHRpb24iKQ0KDQojIEFjY2VzcyB0aGUgcmVzb3VyY2UgZ3JvdXAgYXNzb2NpYXRlZCB3aXRoIA0KIyB0aGUgY29nbml0aXZlIHNlcnZpY2VzIHlvdSBjcmVhdGVkIGVhcmxpZXINCnJnIDwtIHN1YiRnZXRfcmVzb3VyY2VfZ3JvdXAoImtlcmFzIikNCg0KIyBSZXRyaWV2ZSBjb2duaXRpdmUgc2VydmljZQ0KY29nc3ZjIDwtIHJnJGdldF9jb2duaXRpdmVfc2VydmljZSgiZmFjaWVtIikNCg0KIyBPYnRhaW4gU3Vic2NyaXB0aW9uIGtleXMNCmtleXMgPC0gY29nc3ZjJGxpc3Rfa2V5cygpDQprZXlfMSA8LSBrZXlzICU+JQ0KICBhc190aWJibGUoKSAlPiUgDQogIHB1bGwodmFsdWUpICU+JQ0KICBwbHVjaygxKQ0KDQprZXlfMiA8LSBrZXlzICU+JSANCiAgYXNfdGliYmxlKCkgJT4lDQogIHB1bGwodmFsdWUpICU+JSANCiAgcGx1Y2soMikNCg0KDQojIyBDcmVhdGUgQXp1cmUgY29nbml0aXZlIHNlcnZpY2VzIGVuZHBvaW50cyAjIw0KDQojIENyZWF0ZSBDb21wdXRlciBWaXNpb24gZW5kcG9pbnQNCmVuZHBfY3YgPC0gY29nbml0aXZlX2VuZHBvaW50KA0KICB1cmwgPSAiaHR0cHM6Ly9mYWNpZW0uY29nbml0aXZlc2VydmljZXMuYXp1cmUuY29tLyIsDQogIHNlcnZpY2VfdHlwZSA9ICJDb21wdXRlclZpc2lvbiIsDQogIGtleSA9IGtleV8xKQ0KZW5kcF9jdg0KDQpgYGANCg0KTm93IHRoYXQgdGhlIGVuZHBvaW50IGlzIHNldCB1cCwgbGV0J3MgaGF2ZSBzb21lIGZ1biENCg0KIyMjIENvbXB1dGVyIHZpc2lvbjogRG9lcyB0aGlzIGxvb2sgbGlrZSAuLi4gSGFkbGV5IPCfpJQgPw0KDQpUaGUgW0NvbXB1dGVyIFZpc2lvbiBBUEldKGh0dHBzOi8vZG9jcy5taWNyb3NvZnQuY29tL2VuLWdiL2F6dXJlL2NvZ25pdGl2ZS1zZXJ2aWNlcy9jb21wdXRlci12aXNpb24vKSBwcm92aWRlcyB1cyB3aXRoIGFjY2VzcyB0byBhZHZhbmNlZCBhbGdvcml0aG1zIGZvciBwcm9jZXNzaW5nIGltYWdlcyBhbmQgcmV0dXJuaW5nIGluZm9ybWF0aW9uLiBUaGVyZSBhcmUgYSB3aG9sZSBsb3Qgb2YgY29vbCB0aGluZ3MgeW91IGNvdWxkIGRvIHdpdGggdGhpcyBBUEksIGZyb20gZXh0cmFjdGluZyBoYW5kd3JpdHRlbiB0ZXh0IGZyb20gaW1hZ2VzLCBnZW5lcmF0aW5nIGEgZGVzY3JpcHRpb24gb2YgYW4gZW50aXJlIGltYWdlIGluIGh1bWFuLXJlYWRhYmxlIGxhbmd1YWdlIHRvIG1lYXN1cmluZyBjb21wbGlhbmNlIHdpdGggc29jaWFsIGRpc3RhbmNpbmcgZ3VpZGVsaW5lcyENCg0KSW4gdGhpcyBwb3N0LCB3ZSdsbCBtYWtlIGFuIEFQSSBjYWxsIHRvICoqYW5hbHl6ZSoqIGFuIGltYWdlIG9mIEhhZGxleSBXaWNraGFtLiBDaGFuY2VzIGFyZSB5b3UgYWxyZWFkeSBrbm93IGhpbSBvciBoYXZlIHVzZWQgc29tZSBvZiB0aGUgZGF0YSBzY2llbmNlIHRvb2xzIGhlIGJ1aWxkcyBzdWNoIGFzIGdncGxvdDIgYW5kIGRwbHlyLg0KDQohW0hhZGxleSBXaWNraGFtOiBTb3VyY2UgPGh0dHBzOi8vdHdpdHRlci5jb20vaGFkbGV5d2lja2hhbT5dKGhhZGxleS5qcGcpe3dpZHRoPSIzMDAifQ0KDQpXaXRoIHRoZSBjb21wdXRlciB2aXNpb24gZW5kcG9pbnQgc2V0LCB3ZSBqdXN0IGhhdmUgdG8gbWFrZSBhbiBBUEkgY2FsbCBhbmQgLi4uIGRvbmUhIExldCdzIHRha2UgYSBtb21lbnQgYW5kIGV4cGxhaW4gc29tZSBvZiBhcmd1bWVudHMgaW4gdGhlIEFQSSBjYWxsIGJlbG93Og0KDQotICAgYG9wZXJhdGlvbiA9ICJhbmFseXplImA6IHNwZWNpZmllcyB0aGUgb3BlcmF0aW9uIHRvIGJlIHBlcmZvcm1lZCBvbiBhIHBhcnRpY3VsYXIgaW1hZ2UuIERlcGVuZGluZyBvbiB0aGUgQVBJLCBleGFtcGxlcyBvZiBvcGVyYXRpb25zIGluY2x1ZGUgYW5hbHl6ZSwgZGV0ZWN0LCBhbmQgT3B0aWNhbCBDaGFyYWN0ZXIgUmVjb2duaXRpb24uDQoNCi0gICBgZGV0YWlscyA9ICJjZWxlYnJpdGllcyJgOiBpZGVudGlmaWVzIGNlbGVicml0aWVzIGlmIGRldGVjdGVkIGluIHRoZSBpbWFnZS4NCg0KLSAgIGB2aXN1YWxGZWF0dXJlcyA9ICJhZHVsdCxmYWNlcyxkZXNjcmlwdGlvbix0YWdzImA6IEEgc3RyaW5nIGluZGljYXRpbmcgd2hhdCB2aXN1YWwgZmVhdHVyZSB0eXBlcyB0byByZXR1cm4uIEZvciBpbnN0YW5jZSwgKipEZXNjcmlwdGlvbioqIGRlc2NyaWJlcyB0aGUgaW1hZ2UgY29udGVudCB3aXRoIGEgY29tcGxldGUgc2VudGVuY2UgaW4gc3VwcG9ydGVkIGxhbmd1YWdlcy4NCg0KUGxlYXNlIHNlZSB0aGUgWyoqQ29tcHV0ZXIgVmlzaW9uIEFQSSoqXShodHRwczovL3dlc3RjZW50cmFsdXMuZGV2LmNvZ25pdGl2ZS5taWNyb3NvZnQuY29tL2RvY3Mvc2VydmljZXMvY29tcHV0ZXItdmlzaW9uLXYzLTIvb3BlcmF0aW9ucy81NmY5MWYyZTc3OGRhZjE0YTQ5OWYyMWIpZm9yIG1vcmUgZGV0YWlscyBhbmQgZXhhbXBsZXMgYWJvdXQgZGlmZmVyZW50IG9wZXJhdGlvbnMgYW5kIGZlYXR1cmVzIHdpdGhpbiB0aGUgY29tcHV0ZXIgdmlzaW9uIEFQSS4NCg0KYGBge3J9DQojIFNwZWNpZnkgaW1hZ2UgbGluaw0KaW1hZ2VfbGluayA8LSAiaHR0cHM6Ly9wYnMudHdpbWcuY29tL3Byb2ZpbGVfaW1hZ2VzLzkwNTE4NjM4MTk5NTE0NzI2NC83ektBRzVzWV80MDB4NDAwLmpwZyINCg0KIyBDYWxsIHRoZSBjb21wdXRlciB2aXNpb24gZW5kcG9pbnQNCmNhbGxfY29nbml0aXZlX2VuZHBvaW50KA0KICBlbmRwb2ludCA9IGVuZHBfY3YsDQogIG9wZXJhdGlvbiA9ICJhbmFseXplIiwNCiAgYm9keSA9IGxpc3QodXJsID0gaW1hZ2VfbGluayksDQogIG9wdGlvbnMgPSBsaXN0KA0KICAgICMgUmVxdWVzdCBwYXJhbWV0ZXJzDQogICAgZGV0YWlscyA9ICJjZWxlYnJpdGllcyIsDQogICAgdmlzdWFsRmVhdHVyZXMgPSAiYWR1bHQsZmFjZXMsZGVzY3JpcHRpb24sdGFncyINCiAgKSwNCiAgaHR0cF92ZXJiID0gIlBPU1QiDQopDQpgYGANCg0KPGJyPg0KDQpUaGVyZSBnb2VzIHRoZSByZXNwb25zZSBvZiB0aGUgQVBJIGNhbGwuIE5vdCBiYWQsIHRoZSBzdWl0IHBhcnQgdGhvdWdoLi4uIPCfmIQhIFByb2JhYmx5IHRoaXMgaW5kaWNhdGVzIHRoYXQgdGhlIG1vZGVsIGFzc29jaWF0ZXMgYSB0aWUgd2l0aCBhIHN1aXQuDQoNCkxldCdzIHB1dCBzb21lIG9mIHRoZSByZXNwb25zZXMgaW50byBhIHRpYmJsZSBhbmQgZGlzcGxheSB0aGVtIGFzIGEgYGd0YCB0YWJsZSBvYmplY3QuDQoNCmBgYHtyfQ0KbGlicmFyeShndCkNCmN2X3Jlc3BvbnNlIDwtIHRpYmJsZSgNCiAgcmVxdWVzdF9wYXJhbWV0ZXIgPSBjKCJjYXRlZ29yaWVzIiwgInRhZ3MiLCAiY2VsZWJyaXR5X2RldGFpbHMiLCAidGFncyIsICJ0YWdzIiwNCiJpc0FkdWx0Q29udGVudCIsICJpc1JhY3lDb250ZW50IiwgImZhY2VzIiwgImRlc2NyaXB0aW9uIiksDQogIA0KICByZXNwb25zZSA9IGMoInBlb3BsZV9wb3J0cmFpdCIsICJodW1hbiBmYWNlIiwgIkhhZGxleSBXaWNraGFtIiwgIm5lY2t0aWUiLCAiYmVhcmQiLA0KIkZBTFNFIiwgIkZBTFNFIiwgImVzdGltYXRlZCBhZ2UiLA0KIkhhZGxleSBXaWNraGFtIHdlYXJpbmcgYSBzdWl0IGFuZCB0aWUgc21pbGluZyBhdCB0aGUgY2FtZXJhIiksDQogIA0KICBzY29yZSA9IGMoMC43Njk1MzEyLCAwLjk4NTA1MzIsDQogICAgICAgICAgICAwLjk5OTkwNDYzMjU2ODM1OSwgMC45OTk3MDgxLCAwLjYwNDM3NjgsDQogICAgICAgICAgICAwLjAxNjc0Njc0LCAwLjAxNzQyNTU5LCAzNCwgMC45OTAxODU5KSANCikNCg0KY3ZfcmVzcG9uc2UgJT4lIA0KICBndCgpICU+JSANCiAgdGFiX2hlYWRlcigNCiAgICB0aXRsZSA9IG1kKA0KICAgICI8aW1nIHNyYyA9ICdodHRwczovL3Bicy50d2ltZy5jb20vcHJvZmlsZV9pbWFnZXMvOTA1MTg2MzgxOTk1MTQ3MjY0Lzd6S0FHNXNZXzQwMHg0MDAuanBnJw0KYWxpZ24gPSAnY2VudGVyJyBzdHlsZSA9ICdoZWlnaHQ6MzlweCc+DQpIYWRsZXkgV2lja2hhbSB3ZWFyaW5nIGEgc3VpdCBhbmQgdGllIHNtaWxpbmcgYXQgdGhlIGNhbWVyYSAiKSwNCiAgICBzdWJ0aXRsZSA9IG1kKCJDb21wdXRlciBWaXNpb24gQVBJIHJlcG9uc2UiKSkgJT4lIA0KICBjb2xzX2FsaWduKA0KICAgIGFsaWduID0gImxlZnQiLA0KICAgIGNvbHVtbnMgPSAxKSAlPiUgDQogICAgY29sc19hbGlnbigNCiAgICAgIGFsaWduID0gImNlbnRlciIsDQogICAgICBjb2x1bW5zID0gMikgJT4lIA0KICAgIGNvbHNfYWxpZ24oDQogICAgICBhbGlnbiA9ICJyaWdodCIsDQogICAgICBjb2x1bW5zID0gMykNCmBgYA0KDQo8YnI+DQoNCk11Y2ggYmV0dGVyISBXZSdsbCB3cmFwIHVwIHRoaXMgc2hvcnQgd2Fsay10aHJvdWdoIGF0IHRoYXQuIFdlIG1heSBoYXZlIG9ubHkgZXhwbG9yZWQgYSBzbWFsbCBzZWN0aW9uIG9mIHRoZSBDb21wdXRlciBWaXNpb24gQVBJIChpbiBwYXJ0aWN1bGFyIEltYWdlIEFuYWx5c2lzKSBidXQgdGhpcyBjYW4gYmUgZXh0ZW5kZWQgdG8gb3RoZXIgb3BlcmF0aW9ucyBzdWNoIGFzIFsqKk9wdGljYWwgY2hhcmFjdGVyIHJlY29nbml0aW9uKipdKGh0dHBzOi8vZG9jcy5taWNyb3NvZnQuY29tL2VuLWdiL2F6dXJlL2NvZ25pdGl2ZS1zZXJ2aWNlcy9jb21wdXRlci12aXNpb24vb3ZlcnZpZXctb2NyKSBvciBldmVuIG90aGVyIGNvZ25pdGl2ZSBzZXJ2aWNlcyBzdWNoIGFzIFsqKlNwZWVjaCoqXShodHRwczovL2RvY3MubWljcm9zb2Z0LmNvbS9lbi1nYi9henVyZS9jb2duaXRpdmUtc2VydmljZXMvc3BlZWNoLXNlcnZpY2UvKS4gVGhpcyBtZWFucyB5b3UgY2FuIGV2ZW4gZG8gdGhlIGxlYXJuaW5nIGxhYnMgYXNzb2NpYXRlZCB3aXRoIHRoZSBNaWNyb3NvZnQgQXp1cmUgQUkgRnVuZGFtZW50YWxzIENlcnRpZmljYXRpb24gaW4gUiEgSG9wZWZ1bGx5IHdlJ2xsIGdldCB0byBSIHNvbWUgb2YgdGhlbSBzb29uIPCfmI4hDQoNClRoYW5rcyBmb3IgcmVhZGluZyENCg0KQmUgc3VyZSB0byBjaGVjayBvdXQgZ3JlYXQgYmxvZ3MsIHR1dG9yaWFscyBhbmQgb3RoZXIgZm9ybWF0cyBvZiBSIHJlc291cmNlcyBjb21pbmcgb3V0IGV2ZXJ5IGRheSBhdCBbUldlZWtseS5vcmddKGh0dHBzOi8vcndlZWtseS5vcmcvKSENCg0KIyMgRnVydGhlciByZWFkaW5nDQoNCi0gICBbTWljcm9zb2Z0IEF6dXJlIEFJIEZ1bmRhbWVudGFscyBsZWFybmluZyBwYXRoc10oaHR0cHM6Ly9kb2NzLm1pY3Jvc29mdC5jb20vZW4tZ2IvbGVhcm4vY2VydGlmaWNhdGlvbnMvYXp1cmUtYWktZnVuZGFtZW50YWxzLz90YWI9dGFiLWxlYXJuaW5nLXBhdGhzKQ0KDQotICAgW0F6dXJlUl0oaHR0cHM6Ly9naXRodWIuY29tL0F6dXJlL0F6dXJlUik6IGEgZmFtaWx5IG9mIHBhY2thZ2VzIGZvciB3b3JraW5nIHdpdGggQXp1cmUgZnJvbSBSLg0KDQpIYXBweSBsZWFSbmluZywNCg0KW0VyaWNdKGh0dHBzOi8vdHdpdHRlci5jb20vZXJpY250YXkpLCAqR29sZCBNaWNyb3NvZnQgTGVhcm4gU3R1ZGVudCBBbWJhc3NhZG9yKi4NCg0KXA0K