Tạo ứng dụng web tương tác (Interactive Web Applications) với R Shiny package
Shiny - Là một R package cho phép tạo các ứng dụng web tương tác vô cùng đơn giản trực tiếp trong môi trường của R. Những gì trong bài này sẽ giúp các bạn bắt đầu với ứng dụng Web tương tác sử dụng Shiny.
Nếu các bạn chưa cài Shiny package thì hãy kết nối máy tính với internet, khởi động R và thực hiện lệnh sau:
> install.packages("shiny")
Ở bài này sẽ hướng dẫn các bạn sử dụng Shiny trong môi trường Rstudio IDE. Rstudio đặc biết thuận tiện để làm việc với Shiny, bởi vì chúng cùng được tạo ra bởi 1 công ty. Tải Rsudio có thể tại đây.
Ví dụ:
Package Shiny được giới thiệu với 11 ví dụ để mô tả khả năng của mình. Mỗi ví dụ là một Shiny- ứng dụng.
Ví dụ Hello Shiny vẽ Histogram của datasetcơ bản đi kèm R faithful. App này cho phép thay đổi số lượng intervals (bins). Người dùng có thể thay đổi số intervals với sự trọ giúp của slider bar và app sẽ trả về kết quả ngay lập tức. Chúng ta sử dụng *Hello Shiny** để làm rõ cấu trúc của một ứng dụng WEB tương tác trong R, trên cơ sở đó có thể tạo những ứng dụng phù hợp riêng cho mình.
Để chạy Hello Shiny:
> library(shiny)
> runExample("01_hello")
Cấu trúc của Apps Shiny:
Ứng dụng trong Shiny được cấu thành từ 2 phần:
- Script với giao diện người dùng
- Script cho Sever.
Script với giao diện người dùng (user interface, UI) quản lý vị trí của các elements và hình dáng bên ngoài cảu ứng dụng. Nó nằm ở file nguồn *ui.R. Đây là bên trong của file ui.R của Hello Shiny**.
ui.R
library(shiny)
# Define UI for application that draws a histogram
shinyUI(fluidPage(
# Application title
titlePanel("Hello Shiny!"),
# Sidebar with a slider input for the number of bins
sidebarLayout(
sidebarPanel(
sliderInput("bins",
"Number of bins:",
min = 1,
max = 50,
value = 30)
),
# Show a plot of the generated distribution
mainPanel(
plotOutput("distPlot")
)
)
))
Script server.R chứa hướng dẫn cần thiết cho computer để tạo ứng dụng của các bạn và quản lý hành vi server.R của Hello Shiny có dạng:
server.R
library(shiny)
# Define server logic required to draw a histogram
shinyServer(function(input, output) {
# Expression that generates a histogram. The expression is
# wrapped in a call to renderPlot to indicate that:
#
# 1) It is "reactive" and therefore should re-execute automatically
# when inputs change
# 2) Its output type is a plot
output$distPlot <- renderPlot({
x <- faithful[, 2] # Old Faithful Geyser data
bins <- seq(min(x), max(x), length.out = input$bins + 1)
# draw the histogram with the specified number of bins
hist(x, breaks = bins, col = 'darkgray', border = 'white')
})
})
Script này nhìn rất đươn giản: Một vài tính toán được thực hiện, sau đó vẽ Histogram với số lượng intervals quy định. Mặt khác các bạn có thể thấy rằng tất cả thành phần của Script đươc chứa trong một hàm thống nhất- renderPlot. Comment trước khi gọi hàm này là một vài giải thích. Các lệnh vẽ Histogram được gói gọn trong renderPlot vì vậy chúng: 1. “Reactive”, có nghĩa là phản ứng với những thay đổi của dữ liệu đầu vào. 2. Kết quả thực hiện là biểu đồ.
Tóm lại hàm renderPlot vẽ biểu đồ, tự động hiệu chỉnh biểu đồ khi có thay đồi dữ liệu đầu vào.
Nếu những giải thích đến đây làm các bạn chưa hiểu- Đừng lo!. Chúng ta sẽ từng bước phân tích cụ thể cách thức để tạo ứng dụng WEB tương tác trong R.
Bây giở các bãn hãy tự mình giải trí với ví dụ Hello Shiny. Xem xét code nguồn một lần nữa và cố gắng hiểu xem App này làm việc như thế nào.
Để khởi động Shiny app:
Tất cả các ứng dụng Shiny có cấu trúc chung: Một vài R- Script được lưu trong một thư mục. Trường hợp đơn giản nhất là chỉ có 2 Scripts- ui.R và server.
Bạn có thể tạo một App Shiny bằng cách: Tạo một thư mục mới(folder)và nhúng vào trong đó 2 scripts ui.R và server.R. Mỗi App hãy chứa trong một Folder riêng biệt và tên của Folder này hãy lấy trùng với tên của App.
Ngoài ra bạn có thể tạo App shiny với rự trọ giúp của master New Project… Từ menu File của RStudio.
Khởi động App mà mình tạo có thể dùng hàm runApp(“Tên của App”). Hãy nhớ rằng tên của App trùng với tên của Folder chứa App đó. Ví dụ files của App Shiny mà các bạn tạo nằm trong folder my_app**. Khi đó để chạy nó thì các bạn gõ code sau:
> library(shiny)
> runApp("my_app")
Lưu ý rằng: runApp làm việc giống như read.csv,* read.table* và rất nhiều các hàm khác trong R. Argument của runApp là path dẫn đến folder chứa App của các bạn. Trong trường hợp trên thì tôi đang folder chứa app đang nằm trong Working Directory nên chỉ đên giản là tên cảu App.
Để biết đường dẫn của Working directory có thể sử dụng hàm getwd, còn để thiết lập working directory mới thì dùng hàm setwd.
Giờ là nhiệm vụ thực hành của các bạn:
Đầu tiên hãy tạo một folder trong working directory mang tên App-1 và copy vào đó 2 script ở phía trên ui.R và server.R (Có nghĩa là scripts từ Hello Shiny).
Chạy App trên với lệnh runApp(“App-1”). Sau đó tắt đi bằng cách ấn nút Esc và thực hiện một số thay đổi trong App-1 này:
- Thay đổi header với “Hello Shiny!” thành “Hello World!” chẳng hạn,
- Thiết lập giá trị nhở nhất của side bar bằng 5
- Thay đổi màu của Histogram từ “darkgray” thành “skyblue”
Khi mọi thứ đã hoàn thành thì hãy chạy App-1 lại . Kết quả phải giống với những gì hiển thị dưới đây:
Với mặc định thì Apps Shiny sẽ được xuất ra ở chế độ thường (normal mode), Chính là những gì thể hiện ở phía trên. Còn đây là Hello Shiny và một số ví dụ xuất theo hướng khác- showcase mode. Khi đó cạnh cửa sổ của App sẽ có demo code của script server.R và ui.R
Nếu các bạn muốn chạy App ở chế độ mô tả “showcase mode” thì thự hiện lệnh sau:
> runApp("App-1", display.mode = "showcase")
Đáp án:
Thay đổi màu cho Histogram trong script “server.R”:
library(shiny)
# Define server logic required to draw a histogram
shinyServer(function(input, output) {
# Expression that generates a histogram. The expression is
# wrapped in a call to renderPlot to indicate that:
#
# 1) It is "reactive" and therefore should
# re-execute automatically when inputs change
# 2) Its output type is a plot
output$distPlot <- renderPlot({
x <- faithful[, 2] # Old Faithful Geyser data
bins <- seq(min(x), max(x), length.out = input$bins + 1)
# draw the histogram with the specified number of bins
hist(x, breaks = bins, col = 'skyblue', border = 'white')
})
})
Và hãy chạy App-1 lại:
- Thực hiện lệnh *runApp(“App-1”)
- Mở phai ui.R hoặc server.R trong trình biên tập của RStudio. Rstudio sẽ nhận diện rằng đang làm việc với App Shiny, nên ở phía góc phải trên của trình biên tập xuất hiện nút Run App. Hãy nhấp chuột vào đó để chạy App hoặc sử dụng tổ hợp phím Ctrl+Shift+Enter.
Theo mặc định thì Rstudio chạy App trong cửa sổ mới, nhưng các bạn có thể chọn phương thức chạy khác:
Kết luận:
Để tạo một App shiny tương tác cần:
- Tạo Folder với tên trùng với tên của App
- Lưu trong folder này Scripts server.R và ui.R
- Chạy ứng dụng (App) với sự giúp đỡ bởi nút Run App trong Rstudio, hàm runApp hoặc với tổ hợp phím Ctrl+Shift+Enter
- Thoát khỏi App bằng cách ân Esc
Tiếp theo:
Bạn có thể tạo những Shiny App mới của mình, hay xem các ví dụ đang có sẵn trong Shiny. Tổng hợp tất cả các ví dụ đang nằm trong bộ sưu tập của Shiny, gồm có 11 ví dụ kèm theo package:
system.file("examples", package="shiny")
runExample("01_hello") # a histogram
runExample("02_text") # tables and data frames
runExample("03_reactivity") # a reactive expression
runExample("04_mpg") # global variables
runExample("05_sliders") # slider bars
runExample("06_tabsets") # tabbed panels
runExample("07_widgets") # help text and submit buttons
runExample("08_html") # Shiny app built from HTML
runExample("09_upload") # file upload wizard
runExample("10_download") # file download wizard
runExample("11_timer") # an automated timer
Và Bây giờ là mục cũng rất quan trọng đó là làm thế nào để chia sẻ các Apps của các bạn:
Bây giờ thì với sự trợ giúp của Shiny các bạn hoàn toàn có thể tạo ra những ứng dụng (Apps) hữa ích cho mình. Nhưng chia sẻ nó chó những người khác thì làm thế nào? Trong mục này chúng ta hãy cùng làm quen với 1 vài con đường để chia sẻ Shiny Apps.
Các bạn có 2 phương pháp chính để chia sẻ Apps:
Chia sẻ Apps dưới dạng 2 files: server.R và ui.R. Đây là phương pháp đơn giản nhất, nhưng nó làm việc khi và chỉ khi trong máy tính của người dùng đã cài đặt R và người dùng đó biết cách làm việc với R. Trong trường hợp này những người dùng có thể chạy scripts của App Shiny nhữ tất cả các Scripts bình thường khác trong R.
Chia sẻ App dưới dạng 1 trang WEB. Đây là phương pháp mà không yêu cầu người dùng của chúng ta biết bất cứ một kiến thức nào về R cả: Người dùng có thể giao tiếp với App bằng web browser (Trình duyệt WEB) giống như bất kỳ một trang WEB nào trên Internet. Người dùng nhận được App đã hoàn toàn sẵn sàng để làm việc.
Chia sẻ App dưới dạng 2 files server.R và ui.R:
Bất cứ người dùng R nào cũng có thể chạy Shiny-Apps. Để làm được điều này thì cần có Scripts của App server.R và ui.R và có thể thêm một vài materials sử dụng trong App (như là folder www hoặc file helpers.R).
Các bạn có thể gửi những files này cho người dùng khác qua mail hoặc lưu chúng trong một nguồi lưu dữ liệu online nào đó rồi chia sẻ quyền tiếp cận với các files này.
Khi nhận được các files này, người dùng lưu chúng vào working directory R và chạy App:
# install.packages("shiny")
library(shiny)
# Run App "census-app"
runApp("census-app")
Shiny có 2 lệnh cho phép chạy files được lưu trữ ở các nguồn lưu trữ online: runUrl, runGitHub, runGist.
runUrl:
runUrl tải về và chạy Shiny-App chứa trong địa chỉ quy định. Để sử dụng runUrl cần phải:
- Lưu thư mục chứa App của các bạn dưới dạng file.zip
- xếp đặt file zip này tren trang Web với link
Bây giờ bất cứ ai cũng có thể copy link (*the weblink) này và vào R thực hiện lệnh sau để chạy App:
library(shiny)
runUrl( "<the weblink>")
runGitHub:
runGitHub( "<your repository name>", "<your user name>")
runGist:
runGist("<gist number>")
runGist("3239667")
Chia sẻ App dưới dạng một trang Web:
Tất cả các phương pháp ở trên để chia sẻ Apps đều có hạn chế: Để sử dụng chúng thì người dùng cần cài R và package Shiny trên R trong máy tính của mình.
Để khắc phục những hạn chế này thì Shiny còn cho phép sử dụng các Apps ngay cả khi người dùng không biết gì về R (không cần cài R và Shiny). Để sử dụng trang một trang Shiny-Web: Lưu Shiny App của các bạn trên internet và cho link dẫn đến đó cho những người khác, họ vào đườn dẫn này và sử sụng Apps của các bạn.
Nếu các bạn biết Hosting (веб-хостинг) là gì, thì các bạn đã hoàn toàn tự có thể lưu Shiny- Apps của mình lên internet. Nếu các bạn muốn đơn giản hơn thì RStudio cho phép sử dụng 3 phương pháp lưu Shiny-Apps dưới dạng một trang Web:
- Shinyapps.io
- Shiny Server
- Shiny Server Pro
Shinyapps.io:
Con đường đơn giản nhất để đưa Shiny-Apps cảu các bạn vào một trang Web- Đây là sử dụng shinyapps.io, trang web chuyên dụng để chứa (hosting) Shiny-Apps từ company Rstudio.
Shinyapps.io cho phép tải Apps của các bạn lên hosting trực tiếp từ môi trường làm việc R. Bạn có thể quản lý các Apps của mình bằng sự giúp đỡ cua các công cụ administrator hosting. Để biết thông tin cụ thể hơn các bạn có thể vào trang shinyapps.io
Shiny Server:
Shiny Server: là một Program thêm Shiny tạo ra Web-Server để lưu trữ các Shiny-Apps. Nó miễn phí và được chia sẻ với mã nguồn mở tương thích trên GitHub.
Shiny Server: được cài trên máy chủ (server) làm viêc dưới sự điều khiển cảu Linux. Để cài Shiny Server các bạn cần Linux hỗ trợ Ubuntu 12.04 hoặc mới (64 bit) hoặc CentOS/RHEL 5 (64 bit).
Để biết thêm chi tiết hơn các bạn có thể vào Shiny Server guide.
Shiny Server Pro:
Shiny Server sở hứu tất cả những gì cần thiết để đăng Apps của các bạn lên Web. Nhưng nếu bạn muốn sử dụng Apps của mình với mục đích thương mại thì bạn cần phải trả phí Server programs khác nhau, nó cho phép triển khai:
- Xác thực với mật khẩu
- Hỗ trợ SLL
- Công cụ để quản trị (admins)
- Hỗ trợ ưu tiên, …
Kết luận:
Shiny-Apps sử dụng dễ dàng. Các bạn có thể chia sẻ các Apps của mình như là R-scripts hoặc dưới dạng một trang Web tương tác. Mỗi phương pháp lại có những ưu và nhược điểm khác nhau.
LS0tDQp0aXRsZTogIkjGsOG7m25nIGThuqtuIHThuqFvIOG7qW5nIGThu6VuZyBXZWIgdMawxqFuZyB0w6FjIHbhu5tpIFIgU2hpbnkgcGFja2FnZSAiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpU4bqhbyDhu6luZyBk4bulbmcgd2ViIHTGsMahbmcgdMOhYyAoSW50ZXJhY3RpdmUgV2ViIEFwcGxpY2F0aW9ucykgduG7m2kgUiBTaGlueSBwYWNrYWdlDQoNCioqU2hpbnkqKiAtIEzDoCBt4buZdCBSIHBhY2thZ2UgY2hvIHBow6lwIHThuqFvIGPDoWMg4bupbmcgZOG7pW5nIHdlYiB0xrDGoW5nIHTDoWMgdsO0ICBjw7luZyDEkcahbiBnaeG6o24gdHLhu7FjIHRp4bq/cCB0cm9uZyBtw7RpIHRyxrDhu51uZyBj4bunYSBSLiBOaOG7r25nIGfDrCB0cm9uZyBiw6BpIG7DoHkgc+G6vSBnacO6cCBjw6FjIGLhuqFuIGLhuq90IMSR4bqndSB24bubaSDhu6luZyBk4bulbmcgV2ViIHTGsMahbmcgdMOhYyBz4butIGThu6VuZyBTaGlueS4NCg0KTuG6v3UgY8OhYyBi4bqhbiBjaMawYSBjw6BpIFNoaW55IHBhY2thZ2UgdGjDrCBow6N5IGvhur90IG7hu5FpIG3DoXkgdMOtbmggduG7m2kgaW50ZXJuZXQsIGto4bufaSDEkeG7mW5nIFIgdsOgIHRo4buxYyBoaeG7h24gbOG7h25oIHNhdToNCg0KYGBge3J9DQo+IGluc3RhbGwucGFja2FnZXMoInNoaW55IikNCmBgYA0KDQrhu54gYsOgaSBuw6B5IHPhur0gaMaw4bubbmcgZOG6q24gY8OhYyBi4bqhbiBz4butIGThu6VuZyBTaGlueSB0cm9uZyBtw7RpIHRyxrDhu51uZyBSc3R1ZGlvIElERS4gUnN0dWRpbyDEkeG6t2MgYmnhur90IHRodeG6rW4gdGnhu4duIMSR4buDIGzDoG0gdmnhu4djIHbhu5tpIFNoaW55LCBi4bufaSB2w6wgY2jDum5nIGPDuW5nIMSRxrDhu6NjIHThuqFvIHJhIGLhu59pIDEgY8O0bmcgdHkuIFThuqNpIFJzdWRpbyBjw7MgdGjhu4MgW3ThuqFpIMSRw6J5XShodHRwczovL3d3dy5yc3R1ZGlvLmNvbS9wcm9kdWN0cy9yc3R1ZGlvLykuDQoNCiMjIFbDrSBk4bulOg0KIVtWw60gZOG7pSDhu6luZyBk4buxbmcgV0VCIHTGsMahbmcgdMOhYyDEkcahbiBnaeG6o24gxJHGsOG7o2MgdOG6oW8gYuG6sW5nIFNoaW55Ll0oQzovVXNlcnMvQVNVUy9Eb2N1bWVudHMvUi9IdXUgaWNoLzAxX2hlbGxvLnBuZykNCg0KUGFja2FnZSBTaGlueSDEkcaw4bujYyBnaeG7m2kgdGhp4buHdSB24bubaSAxMSB2w60gZOG7pSDEkeG7gyBtw7QgdOG6oyBraOG6oyBuxINuZyBj4bunYSBtw6xuaC4gTeG7l2kgdsOtIGThu6UgbMOgIG3hu5l0IFNoaW55LSDhu6luZyBk4bulbmcuDQoNClbDrSBk4bulICoqSGVsbG8gU2hpbnkqKiB24bq9IEhpc3RvZ3JhbSBj4bunYSBkYXRhc2V0Y8ahIGLhuqNuIMSRaSBrw6htIFIgKmZhaXRoZnVsKi4gQXBwIG7DoHkgY2hvIHBow6lwIHRoYXkgxJHhu5VpIHPhu5EgbMaw4bujbmcgaW50ZXJ2YWxzIChiaW5zKS4gTmfGsOG7nWkgZMO5bmcgY8OzIHRo4buDIHRoYXkgxJHhu5VpIHPhu5EgaW50ZXJ2YWxzIHbhu5tpIHPhu7EgdHLhu40gZ2nDunAgY+G7p2EgKnNsaWRlciBiYXIqIHbDoCBhcHAgc+G6vSB0cuG6oyB24buBIGvhur90IHF14bqjIG5nYXkgbOG6rXAgdOG7qWMuIENow7puZyB0YSBz4butIGThu6VuZyAqSGVsbG8gU2hpbnkqKiDEkeG7gyBsw6BtIHLDtSBj4bqldSB0csO6YyBj4bunYSBt4buZdCDhu6luZyBk4bulbmcgV0VCIHTGsMahbmcgdMOhYyB0cm9uZyBSLCB0csOqbiBjxqEgc+G7nyDEkcOzIGPDsyB0aOG7gyB04bqhbyBuaOG7r25nIOG7qW5nIGThu6VuZyBwaMO5IGjhu6NwIHJpw6puZyBjaG8gbcOsbmguDQoNCsSQ4buDIGNo4bqheSAqKkhlbGxvIFNoaW55Kio6DQoNCmBgYHtyfQ0KPiBsaWJyYXJ5KHNoaW55KQ0KPiBydW5FeGFtcGxlKCIwMV9oZWxsbyIpDQpgYGANCg0KIyMgQ+G6pXUgdHLDumMgY+G7p2EgQXBwcyBTaGlueToNCg0KIyMjIyDhu6huZyBk4bulbmcgdHJvbmcgU2hpbnkgxJHGsOG7o2MgY+G6pXUgdGjDoG5oIHThu6sgMiBwaOG6p246DQoqIFNjcmlwdCB24bubaSBnaWFvIGRp4buHbiBuZ8aw4budaSBkw7luZw0KKiBTY3JpcHQgY2hvIFNldmVyLg0KDQpTY3JpcHQgduG7m2kgZ2lhbyBkaeG7h24gbmfGsOG7nWkgZMO5bmcgKHVzZXIgaW50ZXJmYWNlLCBVSSkgcXXhuqNuIGzDvSB24buLIHRyw60gY+G7p2EgY8OhYyBlbGVtZW50cyB2w6AgaMOsbmggZMOhbmcgYsOqbiBuZ2/DoGkgY+G6o3Ug4bupbmcgZOG7pW5nLiBOw7MgbuG6sW0g4bufIGZpbGUgbmd14buTbiAqdWkuUioqLiDEkMOieSBsw6AgYsOqbiB0cm9uZyBj4bunYSBmaWxlICp1aS5SKiBj4bunYSAqKkhlbGxvIFNoaW55KiouDQoNCiMjIyMgdWkuUg0KDQpgYGB7cn0NCmxpYnJhcnkoc2hpbnkpDQoNCiMgRGVmaW5lIFVJIGZvciBhcHBsaWNhdGlvbiB0aGF0IGRyYXdzIGEgaGlzdG9ncmFtDQpzaGlueVVJKGZsdWlkUGFnZSgNCg0KICAjIEFwcGxpY2F0aW9uIHRpdGxlDQogIHRpdGxlUGFuZWwoIkhlbGxvIFNoaW55ISIpLA0KDQogICMgU2lkZWJhciB3aXRoIGEgc2xpZGVyIGlucHV0IGZvciB0aGUgbnVtYmVyIG9mIGJpbnMNCiAgc2lkZWJhckxheW91dCgNCiAgICBzaWRlYmFyUGFuZWwoDQogICAgICBzbGlkZXJJbnB1dCgiYmlucyIsDQogICAgICAgICAgICAgICAgICAiTnVtYmVyIG9mIGJpbnM6IiwNCiAgICAgICAgICAgICAgICAgIG1pbiA9IDEsDQogICAgICAgICAgICAgICAgICBtYXggPSA1MCwNCiAgICAgICAgICAgICAgICAgIHZhbHVlID0gMzApDQogICAgKSwNCg0KICAgICMgU2hvdyBhIHBsb3Qgb2YgdGhlIGdlbmVyYXRlZCBkaXN0cmlidXRpb24NCiAgICBtYWluUGFuZWwoDQogICAgICBwbG90T3V0cHV0KCJkaXN0UGxvdCIpDQogICAgKQ0KICApDQopKQ0KYGBgDQoNCg0KU2NyaXB0ICpzZXJ2ZXIuUiogY2jhu6lhIGjGsOG7m25nIGThuqtuIGPhuqduIHRoaeG6v3QgY2hvIGNvbXB1dGVyIMSR4buDIHThuqFvIOG7qW5nIGThu6VuZyBj4bunYSBjw6FjIGLhuqFuIHbDoCBxdeG6o24gbMO9IGjDoG5oIHZpICpzZXJ2ZXIuUiogY+G7p2EgKipIZWxsbyBTaGlueSoqIGPDsyBk4bqhbmc6DQoNCiMjIyMgc2VydmVyLlINCmBgYHtyfQ0KbGlicmFyeShzaGlueSkNCg0KIyBEZWZpbmUgc2VydmVyIGxvZ2ljIHJlcXVpcmVkIHRvIGRyYXcgYSBoaXN0b2dyYW0NCnNoaW55U2VydmVyKGZ1bmN0aW9uKGlucHV0LCBvdXRwdXQpIHsNCg0KICAjIEV4cHJlc3Npb24gdGhhdCBnZW5lcmF0ZXMgYSBoaXN0b2dyYW0uIFRoZSBleHByZXNzaW9uIGlzDQogICMgd3JhcHBlZCBpbiBhIGNhbGwgdG8gcmVuZGVyUGxvdCB0byBpbmRpY2F0ZSB0aGF0Og0KICAjDQogICMgIDEpIEl0IGlzICJyZWFjdGl2ZSIgYW5kIHRoZXJlZm9yZSBzaG91bGQgcmUtZXhlY3V0ZSBhdXRvbWF0aWNhbGx5DQogICMgICAgIHdoZW4gaW5wdXRzIGNoYW5nZQ0KICAjICAyKSBJdHMgb3V0cHV0IHR5cGUgaXMgYSBwbG90DQoNCiAgb3V0cHV0JGRpc3RQbG90IDwtIHJlbmRlclBsb3Qoew0KICAgIHggICAgPC0gZmFpdGhmdWxbLCAyXSAgIyBPbGQgRmFpdGhmdWwgR2V5c2VyIGRhdGENCiAgICBiaW5zIDwtIHNlcShtaW4oeCksIG1heCh4KSwgbGVuZ3RoLm91dCA9IGlucHV0JGJpbnMgKyAxKQ0KDQogICAgIyBkcmF3IHRoZSBoaXN0b2dyYW0gd2l0aCB0aGUgc3BlY2lmaWVkIG51bWJlciBvZiBiaW5zDQogICAgaGlzdCh4LCBicmVha3MgPSBiaW5zLCBjb2wgPSAnZGFya2dyYXknLCBib3JkZXIgPSAnd2hpdGUnKQ0KICB9KQ0KfSkNCmBgYA0KDQpTY3JpcHQgbsOgeSBuaMOsbiBy4bqldCDEkcawxqFuIGdp4bqjbjogTeG7mXQgdsOgaSB0w61uaCB0b8OhbiDEkcaw4bujYyB0aOG7sWMgaGnhu4duLCBzYXUgxJHDsyB24bq9IEhpc3RvZ3JhbSB24bubaSBz4buRIGzGsOG7o25nIGludGVydmFscyBxdXkgxJHhu4tuaC4gTeG6t3Qga2jDoWMgY8OhYyBi4bqhbiBjw7MgdGjhu4MgdGjhuqV5IHLhurFuZyB04bqldCBj4bqjIHRow6BuaCBwaOG6p24gY+G7p2EgU2NyaXB0IMSRxrDGoWMgY2jhu6lhIHRyb25nIG3hu5l0IGjDoG0gdGjhu5FuZyBuaOG6pXQtICpyZW5kZXJQbG90Ki4gQ29tbWVudCB0csaw4bubYyBraGkgZ+G7jWkgaMOgbSBuw6B5IGzDoCBt4buZdCB2w6BpIGdp4bqjaSB0aMOtY2guIEPDoWMgbOG7h25oIHbhur0gSGlzdG9ncmFtIMSRxrDhu6NjIGfDs2kgZ+G7jW4gdHJvbmcgKnJlbmRlclBsb3QqIHbDrCB24bqteSBjaMO6bmc6IA0KMS4gIlJlYWN0aXZlIiwgY8OzIG5naMSpYSBsw6AgcGjhuqNuIOG7qW5nIHbhu5tpIG5o4buvbmcgdGhheSDEkeG7lWkgY+G7p2EgZOG7ryBsaeG7h3UgxJHhuqd1IHbDoG8uDQoyLiBL4bq/dCBxdeG6oyB0aOG7sWMgaGnhu4duIGzDoCBiaeG7g3UgxJHhu5MuDQoNClTDs20gbOG6oWkgaMOgbSAqcmVuZGVyUGxvdCogduG6vSBiaeG7g3UgxJHhu5MsIHThu7EgxJHhu5luZyBoaeG7h3UgY2jhu4luaCBiaeG7g3UgxJHhu5Mga2hpIGPDsyB0aGF5IMSR4buTaSBk4buvIGxp4buHdSDEkeG6p3UgdsOgby4NCg0KTuG6v3Ugbmjhu69uZyBnaeG6o2kgdGjDrWNoIMSR4bq/biDEkcOieSBsw6BtIGPDoWMgYuG6oW4gY2jGsGEgaGnhu4N1LSAqKsSQ4burbmcgbG8hKiouIENow7puZyB0YSBz4bq9IHThu6tuZyBixrDhu5tjIHBow6JuIHTDrWNoIGPhu6UgdGjhu4MgY8OhY2ggdGjhu6ljIMSR4buDIHThuqFvIOG7qW5nIGThu6VuZyBXRUIgdMawxqFuZyB0w6FjIHRyb25nIFIuDQoNCkLDonkgZ2nhu58gY8OhYyBiw6NuIGjDo3kgdOG7sSBtw6xuaCBnaeG6o2kgdHLDrSB24bubaSB2w60gZOG7pSAqKkhlbGxvIFNoaW55KiouIFhlbSB4w6l0IGNvZGUgbmd14buTbiBt4buZdCBs4bqnbiBu4buvYSB2w6AgY+G7kSBn4bqvbmcgaGnhu4N1IHhlbSBBcHAgbsOgeSBsw6BtIHZp4buHYyBuaMawICB0aOG6vyBuw6BvLg0KDQojIyMjIMSQ4buDIGto4bufaSDEkeG7mW5nIFNoaW55IGFwcDoNCg0KVOG6pXQgY+G6oyBjw6FjIOG7qW5nIGThu6VuZyBTaGlueSBjw7MgY+G6pXUgdHLDumMgY2h1bmc6IE3hu5l0IHbDoGkgUi0gU2NyaXB0IMSRxrDhu6NjIGzGsHUgdHJvbmcgbeG7mXQgdGjGsCBt4bulYy4gVHLGsOG7nW5nIGjhu6NwIMSRxqFuIGdp4bqjbiBuaOG6pXQgbMOgIGNo4buJIGPDsyAyIFNjcmlwdHMtICp1aS5SKiB2w6AgKnNlcnZlciouDQoNCkLhuqFuIGPDsyB0aOG7gyB04bqhbyBt4buZdCBBcHAgU2hpbnkgYuG6sW5nIGPDoWNoOiBU4bqhbyBt4buZdCB0aMawIG3hu6VjIG3hu5tpKGZvbGRlcil2w6AgbmjDum5nIHbDoG8gdHJvbmcgxJHDsyAyIHNjcmlwdHMgKnVpLlIqIHbDoCAqc2VydmVyLlIqLiBN4buXaSBBcHAgaMOjeSBjaOG7qWEgdHJvbmcgbeG7mXQgRm9sZGVyIHJpw6puZyBiaeG7h3QgdsOgIHTDqm4gY+G7p2EgRm9sZGVyIG7DoHkgaMOjeSBs4bqleSB0csO5bmcgduG7m2kgdMOqbiBj4bunYSBBcHAuDQoNCk5nb8OgaSByYSBi4bqhbiBjw7MgdGjhu4MgdOG6oW8gQXBwIHNoaW55IHbhu5tpIHLhu7EgdHLhu40gZ2nDunAgY+G7p2EgbWFzdGVyICpOZXcgUHJvamVjdCouLi4gVOG7qyBtZW51ICpGaWxlKiBj4bunYSBSU3R1ZGlvLg0KDQpLaOG7n2kgxJHhu5luZyBBcHAgbcOgIG3DrG5oIHThuqFvIGPDsyB0aOG7gyBkw7luZyBow6BtICoqcnVuQXBwKCJUw6puIGPhu6dhIEFwcCIpLiBIw6N5IG5o4bubIHLhurFuZyB0w6puIGPhu6dhIEFwcCB0csO5bmcgduG7m2kgdMOqbiBj4bunYSBGb2xkZXIgY2jhu6lhIEFwcCDEkcOzLiBWw60gZOG7pSBmaWxlcyBj4bunYSBBcHAgU2hpbnkgbcOgIGPDoWMgYuG6oW4gdOG6oW8gbuG6sW0gdHJvbmcgZm9sZGVyICoqbXlfYXBwKiouIEtoaSDEkcOzIMSR4buDIGNo4bqheSBuw7MgdGjDrCBjw6FjIGLhuqFuIGfDtSBjb2RlIHNhdToNCg0KYGBge3J9DQo+IGxpYnJhcnkoc2hpbnkpDQo+IHJ1bkFwcCgibXlfYXBwIikNCmBgYA0KDQoqKkzGsHUgw70gcuG6sW5nOioqICpydW5BcHAqIGzDoG0gdmnhu4djIGdp4buRbmcgbmjGsCAqcmVhZC5jc3YqLCogcmVhZC50YWJsZSogdsOgIHLhuqV0IG5oaeG7gXUgY8OhYyBow6BtIGtow6FjIHRyb25nIFIuIEFyZ3VtZW50IGPhu6dhICpydW5BcHAqIGzDoCBwYXRoIGThuqtuIMSR4bq/biBmb2xkZXIgY2jhu6lhIEFwcCBj4bunYSBjw6FjIGLhuqFuLiBUcm9uZyB0csaw4budbmcgaOG7o3AgdHLDqm4gdGjDrCB0w7RpIMSRYW5nIGZvbGRlciBjaOG7qWEgYXBwIMSRYW5nIG7hurFtIHRyb25nICoqV29ya2luZyBEaXJlY3RvcnkqKiBuw6puIGNo4buJIMSRw6puIGdp4bqjbiBsw6AgdMOqbiBj4bqjdSBBcHAuDQoNCsSQ4buDIGJp4bq/dCDEkcaw4budbmcgZOG6q24gY+G7p2EgV29ya2luZyBkaXJlY3RvcnkgY8OzIHRo4buDIHPhu60gZOG7pW5nIGjDoG0gKipnZXR3ZCoqLCBjw7JuIMSR4buDIHRoaeG6v3QgbOG6rXAgd29ya2luZyBkaXJlY3RvcnkgbeG7m2kgdGjDrCBkw7luZyBow6BtICoqc2V0d2QqKi4NCg0KIyMjR2nhu50gbMOgIG5oaeG7h20gduG7pSB0aOG7sWMgaMOgbmggY+G7p2EgY8OhYyBi4bqhbjoNCg0KxJDhuqd1IHRpw6puIGjDo3kgdOG6oW8gbeG7mXQgZm9sZGVyIHRyb25nIHdvcmtpbmcgZGlyZWN0b3J5IG1hbmcgdMOqbiAqKkFwcC0xKiogdsOgIGNvcHkgdsOgbyDEkcOzIDIgc2NyaXB0IOG7nyBwaMOtYSB0csOqbiAqdWkuUiogdsOgICpzZXJ2ZXIuUiogKEPDsyBuZ2jEqWEgbMOgIHNjcmlwdHMgdOG7qyAqKkhlbGxvIFNoaW55KiopLiANCg0KQ2jhuqF5IEFwcCB0csOqbiB24bubaSBs4buHbmggKipydW5BcHAoIkFwcC0xIikuKiogU2F1IMSRw7MgdOG6r3QgxJFpIGLhurFuZyBjw6FjaCDhuqVuIG7DunQgKipFc2MqKiB2w6AgdGjhu7FjIGhp4buHbiBt4buZdCBz4buRIHRoYXkgxJHhu5VpIHRyb25nIEFwcC0xIG7DoHk6DQoNCi0gVGhheSDEkeG7lWkgaGVhZGVyIHbhu5tpICJIZWxsbyBTaGlueSEiIHRow6BuaCAiSGVsbG8gV29ybGQhIiBjaOG6s25nIGjhuqFuLA0KLSBUaGnhur90IGzhuq1wIGdpw6EgdHLhu4sgbmjhu58gbmjhuqV0IGPhu6dhIHNpZGUgYmFyIGLhurFuZyA1DQotIFRoYXkgxJHhu5VpIG3DoHUgY+G7p2EgSGlzdG9ncmFtIHThu6sgImRhcmtncmF5IiB0aMOgbmggInNreWJsdWUiDQoNCktoaSBt4buNaSB0aOG7qSDEkcOjIGhvw6BuIHRow6BuaCB0aMOsIGjDo3kgY2jhuqF5IEFwcC0xIGzhuqFpIC4gS+G6v3QgcXXhuqMgcGjhuqNpIGdp4buRbmcgduG7m2kgbmjhu69uZyBnw6wgaGnhu4NuIHRo4buLIGTGsOG7m2kgxJHDonk6DQoNCiFbXShDOi9Vc2Vycy9BU1VTL0RvY3VtZW50cy9SL0h1dSBpY2gvMDJfaGVsbG8ucG5nKQ0KDQoNClbhu5tpIG3hurdjIMSR4buLbmggdGjDrCBBcHBzIFNoaW55IHPhur0gxJHGsOG7o2MgeHXhuqV0IHJhIOG7nyBjaOG6vyDEkeG7mSB0aMaw4budbmcgKG5vcm1hbCBtb2RlKSwgQ2jDrW5oIGzDoCBuaOG7r25nIGfDrCB0aOG7gyBoaeG7h24g4bufIHBow61hIHRyw6puLiBDw7JuIMSRw6J5IGzDoCAqKkhlbGxvIFNoaW55KiogdsOgIG3hu5l0IHPhu5EgdsOtIGThu6UgeHXhuqV0IHRoZW8gaMaw4bubbmcga2jDoWMtIHNob3djYXNlIG1vZGUuIEtoaSDEkcOzIGPhuqFuaCBj4butYSBz4buVIGPhu6dhIEFwcCBz4bq9IGPDsyBkZW1vIGNvZGUgY+G7p2Egc2NyaXB0ICpzZXJ2ZXIuUiogdsOgICp1aS5SKg0KDQpO4bq/dSBjw6FjIGLhuqFuIG114buRbiBjaOG6oXkgQXBwIOG7nyBjaOG6vyDEkeG7mSBtw7QgdOG6oyAic2hvd2Nhc2UgbW9kZSIgdGjDrCB0aOG7sSBoaeG7h24gbOG7h25oIHNhdToNCg0KYGBge3J9DQo+IHJ1bkFwcCgiQXBwLTEiLCBkaXNwbGF5Lm1vZGUgPSAic2hvd2Nhc2UiKQ0KYGBgDQoNCiMjI8SQw6FwIMOhbjoNCg0KIyMjI1RoYXkgxJHhu5VpIGhlYWRlciBjaG8gQXBwICBj4bunYSBjw6FjIGLhuqFuIHbDoCB0aGnhur90IGzhuq1wIGdpw6EgdHLhu4sgbWluIGNobyBzbGlkZXIgYmFyIHRyb25nICp1aS5SKjoNCg0KYGBge3J9DQpsaWJyYXJ5KHNoaW55KQ0KDQojIERlZmluZSBVSSBmb3IgYXBwbGljYXRpb24gdGhhdCBkcmF3cyBhIGhpc3RvZ3JhbQ0Kc2hpbnlVSShmbHVpZFBhZ2UoDQoNCiAgIyBBcHBsaWNhdGlvbiB0aXRsZQ0KICB0aXRsZVBhbmVsKCJIZWxsbyBXb3JsZCEiKSwNCg0KICAjIFNpZGViYXIgd2l0aCBhIHNsaWRlciBpbnB1dCBmb3IgdGhlIG51bWJlciBvZiBiaW5zDQogIHNpZGViYXJMYXlvdXQoDQogICAgc2lkZWJhclBhbmVsKA0KICAgICAgc2xpZGVySW5wdXQoImJpbnMiLA0KICAgICAgICAgICAgICAgICAgIk51bWJlciBvZiBiaW5zOiIsDQogICAgICAgICAgICAgICAgICBtaW4gPSA1LA0KICAgICAgICAgICAgICAgICAgbWF4ID0gNTAsDQogICAgICAgICAgICAgICAgICB2YWx1ZSA9IDMwKQ0KICAgICksDQoNCiAgICAjIFNob3cgYSBwbG90IG9mIHRoZSBnZW5lcmF0ZWQgZGlzdHJpYnV0aW9uDQogICAgbWFpblBhbmVsKA0KICAgICAgcGxvdE91dHB1dCgiZGlzdFBsb3QiKQ0KICAgICkNCiAgKQ0KKSkNCmBgYA0KDQojIyMjVGhheSDEkeG7lWkgbcOgdSBjaG8gSGlzdG9ncmFtIHRyb25nIHNjcmlwdCAic2VydmVyLlIiOg0KDQpgYGB7cn0NCmxpYnJhcnkoc2hpbnkpDQoNCiMgRGVmaW5lIHNlcnZlciBsb2dpYyByZXF1aXJlZCB0byBkcmF3IGEgaGlzdG9ncmFtDQpzaGlueVNlcnZlcihmdW5jdGlvbihpbnB1dCwgb3V0cHV0KSB7DQoNCiAgIyBFeHByZXNzaW9uIHRoYXQgZ2VuZXJhdGVzIGEgaGlzdG9ncmFtLiBUaGUgZXhwcmVzc2lvbiBpcw0KICAjIHdyYXBwZWQgaW4gYSBjYWxsIHRvIHJlbmRlclBsb3QgdG8gaW5kaWNhdGUgdGhhdDoNCiAgIw0KICAjICAxKSBJdCBpcyAicmVhY3RpdmUiIGFuZCB0aGVyZWZvcmUgc2hvdWxkDQogICMgICAgIHJlLWV4ZWN1dGUgYXV0b21hdGljYWxseSB3aGVuIGlucHV0cyBjaGFuZ2UNCiAgIyAgMikgSXRzIG91dHB1dCB0eXBlIGlzIGEgcGxvdA0KDQogIG91dHB1dCRkaXN0UGxvdCA8LSByZW5kZXJQbG90KHsNCiAgICB4ICAgIDwtIGZhaXRoZnVsWywgMl0gICMgT2xkIEZhaXRoZnVsIEdleXNlciBkYXRhDQogICAgYmlucyA8LSBzZXEobWluKHgpLCBtYXgoeCksIGxlbmd0aC5vdXQgPSBpbnB1dCRiaW5zICsgMSkNCg0KICAgICMgZHJhdyB0aGUgaGlzdG9ncmFtIHdpdGggdGhlIHNwZWNpZmllZCBudW1iZXIgb2YgYmlucw0KICAgIGhpc3QoeCwgYnJlYWtzID0gYmlucywgY29sID0gJ3NreWJsdWUnLCBib3JkZXIgPSAnd2hpdGUnKQ0KICB9KQ0KfSkNCmBgYA0KDQojIyMjIFbDoCBow6N5IGNo4bqheSBBcHAtMSBs4bqhaToNCg0KLSBUaOG7sWMgaGnhu4duIGzhu4duaCAqcnVuQXBwKCJBcHAtMSIpDQotIE3hu58gcGhhaSAqdWkuUiogaG/hurdjICpzZXJ2ZXIuUiogdHJvbmcgdHLDrG5oIGJpw6puIHThuq1wIGPhu6dhIFJTdHVkaW8uIFJzdHVkaW8gc+G6vSBuaOG6rW4gZGnhu4duIHLhurFuZyDEkWFuZyBsw6BtIHZp4buHYyB24bubaSBBcHAgU2hpbnksIG7Dqm4g4bufIHBow61hIGfDs2MgcGjhuqNpIHRyw6puIGPhu6dhIHRyw6xuaCBiacOqbiB04bqtcCB4deG6pXQgaGnhu4duIG7DunQgKipSdW4gQXBwKiouIEjDo3kgbmjhuqVwIGNodeG7mXQgdsOgbyDEkcOzIMSR4buDIGNo4bqheSBBcHAgaG/hurdjIHPhu60gZOG7pW5nIHThu5UgaOG7o3AgcGjDrW0gICoqQ3RybCtTaGlmdCtFbnRlcioqLg0KDQohW10oQzovVXNlcnMvQVNVUy9Eb2N1bWVudHMvUi9IdXUgaWNoLzAzX2hlbGxvLnBuZykNCg0KVGhlbyBt4bq3YyDEkeG7i25oIHRow6wgUnN0dWRpbyBjaOG6oXkgQXBwIHRyb25nIGPhu61hIHPhu5UgbeG7m2ksIG5oxrBuZyBjw6FjIGLhuqFuIGPDsyB0aOG7gyBjaOG7jW4gcGjGsMahbmcgdGjhu6ljIGNo4bqheSBraMOhYzogDQoNCiFbXShDOi9Vc2Vycy9BU1VTL0RvY3VtZW50cy9SL0h1dSBpY2gvMDRfaGVsbG8ucG5nKQ0KDQojIyNL4bq/dCBsdeG6rW46DQoNCiMjIyPEkOG7gyB04bqhbyBt4buZdCBBcHAgc2hpbnkgdMawxqFuZyB0w6FjIGPhuqduOg0KDQotIFThuqFvIEZvbGRlciB24bubaSB0w6puIHRyw7luZyB24bubaSB0w6puIGPhu6dhIEFwcA0KLSBMxrB1IHRyb25nIGZvbGRlciBuw6B5IFNjcmlwdHMgKipzZXJ2ZXIuUioqIHbDoCAqKnVpLlIqKg0KLSBDaOG6oXkg4bupbmcgZOG7pW5nIChBcHApIHbhu5tpIHPhu7EgZ2nDunAgxJHhu6EgYuG7n2kgbsO6dCAqKlJ1biBBcHAqKiB0cm9uZyBSc3R1ZGlvLCBow6BtICoqcnVuQXBwKiogaG/hurdjIHbhu5tpIHThu5UgaOG7o3AgcGjDrW0gKipDdHJsK1NoaWZ0K0VudGVyKioNCi0gVGhvw6F0IGto4buPaSBBcHAgYuG6sW5nIGPDoWNoIMOibiAqKkVzYyoqDQoNCiMjI1Rp4bq/cCB0aGVvOg0KDQpC4bqhbiBjw7MgdGjhu4MgdOG6oW8gbmjhu69uZyBTaGlueSBBcHAgbeG7m2kgY+G7p2EgbcOsbmgsIGhheSB4ZW0gY8OhYyB2w60gZOG7pSDEkWFuZyBjw7Mgc+G6tW4gdHJvbmcgU2hpbnkuIFThu5VuZyBo4bujcCB04bqldCBj4bqjIGPDoWMgdsOtIGThu6UgxJFhbmcgbuG6sW0gdHJvbmcgYuG7mSBzxrB1IHThuq1wIGPhu6dhIFNoaW55LCBn4buTbSBjw7MgMTEgdsOtIGThu6Uga8OobSB0aGVvIHBhY2thZ2U6DQoNCmBgYHtyfQ0Kc3lzdGVtLmZpbGUoImV4YW1wbGVzIiwgcGFja2FnZT0ic2hpbnkiKQ0KDQpydW5FeGFtcGxlKCIwMV9oZWxsbyIpICMgYSBoaXN0b2dyYW0NCnJ1bkV4YW1wbGUoIjAyX3RleHQiKSAjIHRhYmxlcyBhbmQgZGF0YSBmcmFtZXMNCnJ1bkV4YW1wbGUoIjAzX3JlYWN0aXZpdHkiKSAjIGEgcmVhY3RpdmUgZXhwcmVzc2lvbg0KcnVuRXhhbXBsZSgiMDRfbXBnIikgIyBnbG9iYWwgdmFyaWFibGVzDQpydW5FeGFtcGxlKCIwNV9zbGlkZXJzIikgIyBzbGlkZXIgYmFycw0KcnVuRXhhbXBsZSgiMDZfdGFic2V0cyIpICMgdGFiYmVkIHBhbmVscw0KcnVuRXhhbXBsZSgiMDdfd2lkZ2V0cyIpICMgaGVscCB0ZXh0IGFuZCBzdWJtaXQgYnV0dG9ucw0KcnVuRXhhbXBsZSgiMDhfaHRtbCIpICMgU2hpbnkgYXBwIGJ1aWx0IGZyb20gSFRNTA0KcnVuRXhhbXBsZSgiMDlfdXBsb2FkIikgIyBmaWxlIHVwbG9hZCB3aXphcmQNCnJ1bkV4YW1wbGUoIjEwX2Rvd25sb2FkIikgIyBmaWxlIGRvd25sb2FkIHdpemFyZA0KcnVuRXhhbXBsZSgiMTFfdGltZXIiKSAjIGFuIGF1dG9tYXRlZCB0aW1lcg0KYGBgDQoNCiMjIFbDoCBCw6J5IGdp4budIGzDoCBt4bulYyBjxaluZyBy4bqldCBxdWFuIHRy4buNbmcgxJHDsyBsw6AgbMOgbSB0aOG6vyBuw6BvIMSR4buDIGNoaWEgc+G6uyBjw6FjIEFwcHMgY+G7p2EgY8OhYyBi4bqhbjoNCg0KQsOieSBnaeG7nSB0aMOsIHbhu5tpIHPhu7EgdHLhu6MgZ2nDunAgY+G7p2EgU2hpbnkgY8OhYyBi4bqhbiBob8OgbiB0b8OgbiBjw7MgdGjhu4MgdOG6oW8gcmEgbmjhu69uZyDhu6luZyBk4bulbmcgKEFwcHMpIGjhu69hIMOtY2ggY2hvIG3DrG5oLiBOaMawbmcgY2hpYSBz4bq7IG7DsyBjaMOzIG5o4buvbmcgbmfGsOG7nWkga2jDoWMgdGjDrCBsw6BtIHRo4bq/IG7DoG8/IFRyb25nIG3hu6VjIG7DoHkgY2jDum5nIHRhIGjDo3kgY8O5bmcgbMOgbSBxdWVuIHbhu5tpIDEgdsOgaSBjb24gxJHGsOG7nW5nIMSR4buDIGNoaWEgc+G6uyBTaGlueSBBcHBzLg0KDQojIyMjQ8OhYyBi4bqhbiBjw7MgMiBwaMawxqFuZyBwaMOhcCBjaMOtbmggxJHhu4MgY2hpYSBz4bq7IEFwcHM6DQoNCi0gQ2hpYSBz4bq7IEFwcHMgZMaw4bubaSBk4bqhbmcgMiBmaWxlczogKipzZXJ2ZXIuUioqIHbDoCAqKnVpLlIqKi4gxJDDonkgbMOgIHBoxrDGoW5nIHBow6FwIMSRxqFuIGdp4bqjbiBuaOG6pXQsIG5oxrBuZyBuw7MgbMOgbSB2aeG7h2Mga2hpIHbDoCBjaOG7iSBraGkgdHJvbmcgbcOheSB0w61uaCBj4bunYSBuZ8aw4budaSBkw7luZyDEkcOjIGPDoGkgxJHhurd0IFIgdsOgIG5nxrDhu51pIGTDuW5nIMSRw7MgYmnhur90IGPDoWNoIGzDoG0gdmnhu4djIHbhu5tpIFIuIFRyb25nIHRyxrDhu51uZyBo4bujcCBuw6B5IG5o4buvbmcgbmfGsOG7nWkgZMO5bmcgY8OzIHRo4buDIGNo4bqheSBzY3JpcHRzIGPhu6dhIEFwcCBTaGlueSBuaOG7ryB04bqldCBj4bqjIGPDoWMgU2NyaXB0cyBiw6xuaCB0aMaw4budbmcga2jDoWMgdHJvbmcgUi4NCg0KLSBDaGlhIHPhursgQXBwIGTGsOG7m2kgZOG6oW5nIDEgdHJhbmcgV0VCLiDEkMOieSBsw6AgcGjGsMahbmcgcGjDoXAgbcOgIGtow7RuZyB5w6p1IGPhuqd1IG5nxrDhu51pIGTDuW5nIGPhu6dhIGNow7puZyB0YSBiaeG6v3QgYuG6pXQgY+G7qSBt4buZdCBraeG6v24gdGjhu6ljIG7DoG8gduG7gSBSIGPhuqM6IE5nxrDhu51pIGTDuW5nIGPDsyB0aOG7gyBnaWFvIHRp4bq/cCB24bubaSBBcHAgYuG6sW5nIHdlYiBicm93c2VyIChUcsOsbmggZHV54buHdCBXRUIpIGdp4buRbmcgbmjGsCBi4bqldCBr4buzIG3hu5l0IHRyYW5nIFdFQiBuw6BvIHRyw6puIEludGVybmV0LiBOZ8aw4budaSBkw7luZyBuaOG6rW4gxJHGsOG7o2MgQXBwIMSRw6MgaG/DoG4gdG/DoG4gc+G6tW4gc8OgbmcgxJHhu4MgbMOgbSB2aeG7h2MuDQoNCiMjIyBDaGlhIHPhursgQXBwIGTGsOG7m2kgZOG6oW5nIDIgZmlsZXMgKnNlcnZlci5SKiB2w6AgKnVpLlIqOg0KDQpC4bqldCBj4bupIG5nxrDhu51pIGTDuW5nIFIgbsOgbyBjxaluZyBjw7MgdGjhu4MgY2jhuqF5IFNoaW55LUFwcHMuIMSQ4buDIGzDoG0gxJHGsOG7o2MgxJFp4buBdSBuw6B5IHRow6wgY+G6p24gY8OzIFNjcmlwdHMgY+G7p2EgQXBwICoqc2VydmVyLlIqKiB2w6AgKip1aS5SKiogdsOgIGPDsyB0aOG7gyB0aMOqbSBt4buZdCB2w6BpIG1hdGVyaWFscyBz4butIGThu6VuZyB0cm9uZyBBcHAgKG5oxrAgbMOgIGZvbGRlciAqKnd3dyoqIGhv4bq3YyBmaWxlICoqaGVscGVycy5SKiopLg0KDQpDw6FjIGLhuqFuIGPDsyB0aOG7gyBn4butaSBuaOG7r25nIGZpbGVzIG7DoHkgY2hvIG5nxrDhu51pIGTDuW5nIGtow6FjIHF1YSBtYWlsIGhv4bq3YyBsxrB1IGNow7puZyB0cm9uZyBt4buZdCBuZ3Xhu5NpIGzGsHUgZOG7ryBsaeG7h3Ugb25saW5lIG7DoG8gxJHDsyBy4buTaSBjaGlhIHPhursgcXV54buBbiB0aeG6v3AgY+G6rW4gduG7m2kgY8OhYyBmaWxlcyBuw6B5Lg0KDQpLaGkgbmjhuq1uIMSRxrDhu6NjIGPDoWMgZmlsZXMgbsOgeSwgbmfGsOG7nWkgZMO5bmcgbMawdSBjaMO6bmcgdsOgbyB3b3JraW5nIGRpcmVjdG9yeSBSIHbDoCBjaOG6oXkgQXBwOg0KDQpgYGB7cn0NCiMgaW5zdGFsbC5wYWNrYWdlcygic2hpbnkiKQ0KbGlicmFyeShzaGlueSkNCiMgUnVuIEFwcCAiY2Vuc3VzLWFwcCINCnJ1bkFwcCgiY2Vuc3VzLWFwcCIpDQpgYGANCg0KDQojIyMjU2hpbnkgY8OzIDIgbOG7h25oIGNobyBwaMOpcCBjaOG6oXkgZmlsZXMgxJHGsOG7o2MgbMawdSB0cuG7ryDhu58gY8OhYyBuZ3Xhu5NuIGzGsHUgdHLhu68gb25saW5lOiAqKnJ1blVybCoqLCAqKnJ1bkdpdEh1YioqLCAqKnJ1bkdpc3QqKi4NCg0KIyMjIHJ1blVybDoNCioqcnVuVXJsKiogdOG6o2kgduG7gSB2w6AgY2jhuqF5IFNoaW55LUFwcCBjaOG7qWEgdHJvbmcgxJHhu4thIGNo4buJIHF1eSDEkeG7i25oLiDEkOG7gyBz4butIGThu6VuZyAqKnJ1blVybCoqIGPhuqduIHBo4bqjaToNCg0KLSBMxrB1IHRoxrAgbeG7pWMgY2jhu6lhIEFwcCBj4bunYSBjw6FjIGLhuqFuIGTGsOG7m2kgZOG6oW5nIGZpbGUuemlwDQotIHjhur9wIMSR4bq3dCBmaWxlIHppcCBuw6B5IHRyZW4gdHJhbmcgV2ViIHbhu5tpIGxpbmsgDQoNCkLDonkgZ2nhu50gYuG6pXQgY+G7qSBhaSBjxaluZyBjw7MgdGjhu4MgY29weSBsaW5rICgqdGhlIHdlYmxpbmspIG7DoHkgdsOgIHbDoG8gUiB0aOG7sWMgaGnhu4duIGzhu4duaCBzYXUgxJHhu4MgY2jhuqF5IEFwcDoNCg0KYGBge3J9DQpsaWJyYXJ5KHNoaW55KQ0KcnVuVXJsKCAiPHRoZSB3ZWJsaW5rPiIpDQpgYGANCg0KIyMjcnVuR2l0SHViOg0KDQpgYGB7cn0NCnJ1bkdpdEh1YiggIjx5b3VyIHJlcG9zaXRvcnkgbmFtZT4iLCAiPHlvdXIgdXNlciBuYW1lPiIpIA0KYGBgDQoNCiMjI3J1bkdpc3Q6DQoNCmBgYHtyfQ0KcnVuR2lzdCgiPGdpc3QgbnVtYmVyPiIpDQpydW5HaXN0KCIzMjM5NjY3IikNCmBgYA0KDQojIyBDaGlhIHPhursgQXBwIGTGsOG7m2kgZOG6oW5nIG3hu5l0IHRyYW5nIFdlYjoNCg0KVOG6pXQgY+G6oyBjw6FjIHBoxrDGoW5nIHBow6FwIOG7nyB0csOqbiDEkeG7gyBjaGlhIHPhursgQXBwcyDEkeG7gXUgY8OzIGjhuqFuIGNo4bq/OiDEkOG7gyBz4butIGThu6VuZyBjaMO6bmcgdGjDrCBuZ8aw4budaSBkw7luZyBj4bqnbiBjw6BpIFIgdsOgIHBhY2thZ2UgU2hpbnkgdHLDqm4gUiB0cm9uZyBtw6F5IHTDrW5oIGPhu6dhIG3DrG5oLg0KDQrEkOG7gyBraOG6r2MgcGjhu6VjIG5o4buvbmcgaOG6oW4gY2jhur8gbsOgeSB0aMOsIFNoaW55IGPDsm4gY2hvIHBow6lwIHPhu60gZOG7pW5nIGPDoWMgQXBwcyBuZ2F5IGPhuqMga2hpIG5nxrDhu51pIGTDuW5nIGtow7RuZyBiaeG6v3QgZ8OsIHbhu4EgUiAoa2jDtG5nIGPhuqduIGPDoGkgUiB2w6AgU2hpbnkpLiDEkOG7gyBz4butIGThu6VuZyB0cmFuZyBt4buZdCB0cmFuZyBTaGlueS1XZWI6IEzGsHUgU2hpbnkgQXBwIGPhu6dhIGPDoWMgYuG6oW4gdHLDqm4gaW50ZXJuZXQgdsOgIGNobyBsaW5rIGThuqtuIMSR4bq/biDEkcOzIGNobyBuaOG7r25nIG5nxrDhu51pIGtow6FjLCBo4buNIHbDoG8gxJHGsOG7nW4gZOG6q24gbsOgeSB2w6Agc+G7rSBz4bulbmcgQXBwcyBj4bunYSBjw6FjIGLhuqFuLg0KDQpO4bq/dSBjw6FjIGLhuqFuIGJp4bq/dCBIb3N0aW5nICjQstC10LEt0YXQvtGB0YLQuNC90LMpIGzDoCBnw6wsIHRow6wgY8OhYyBi4bqhbiDEkcOjIGhvw6BuIHRvw6BuIHThu7EgY8OzIHRo4buDIGzGsHUgU2hpbnktIEFwcHMgY+G7p2EgbcOsbmggbMOqbiBpbnRlcm5ldC4gTuG6v3UgY8OhYyBi4bqhbiBtdeG7kW4gxJHGoW4gZ2nhuqNuIGjGoW4gdGjDrCBSU3R1ZGlvIGNobyBwaMOpcCBz4butIGThu6VuZyAzIHBoxrDGoW5nIHBow6FwIGzGsHUgU2hpbnktQXBwcyBkxrDhu5tpIGThuqFuZyBt4buZdCB0cmFuZyBXZWI6DQoNCi0gU2hpbnlhcHBzLmlvDQotIFNoaW55IFNlcnZlcg0KLSBTaGlueSBTZXJ2ZXIgUHJvDQoNCiMjIyBTaGlueWFwcHMuaW86DQoNCkNvbiDEkcaw4budbmcgxJHGoW4gZ2nhuqNuIG5o4bqldCDEkeG7gyDEkcawYSBTaGlueS1BcHBzIGPhuqN1IGPDoWMgYuG6oW4gdsOgbyBt4buZdCB0cmFuZyBXZWItIMSQw6J5IGzDoCBz4butIGThu6VuZyBzaGlueWFwcHMuaW8sIHRyYW5nIHdlYiBjaHV5w6puIGThu6VuZyDEkeG7gyBjaOG7qWEgKGhvc3RpbmcpIFNoaW55LUFwcHMgdOG7qyBjb21wYW55IFJzdHVkaW8uDQoNCioqU2hpbnlhcHBzLmlvKiogY2hvIHBow6lwIHThuqNpIEFwcHMgY+G7p2EgY8OhYyBi4bqhbiBsw6puIGhvc3RpbmcgdHLhu7FjIHRp4bq/cCB04burIG3DtGkgdHLGsOG7nW5nIGzDoG0gdmnhu4djIFIuIELhuqFuIGPDsyB0aOG7gyBxdeG6o24gbMO9IGPDoWMgQXBwcyBj4bunYSBtw6xuaCBi4bqxbmcgc+G7sSBnacO6cCDEkeG7oSBjdWEgY8OhYyBjw7RuZyBj4bulIGFkbWluaXN0cmF0b3IgaG9zdGluZy4gxJDhu4MgYmnhur90IHRow7RuZyB0aW4gY+G7pSB0aOG7gyBoxqFuIGPDoWMgYuG6oW4gY8OzIHRo4buDIHbDoG8gdHJhbmcgW3NoaW55YXBwcy5pb10oaHR0cDovL3d3dy5zaGlueWFwcHMuaW8vKSANCg0KDQojIyNTaGlueSBTZXJ2ZXI6DQoNCioqU2hpbnkgU2VydmVyKio6IGzDoCBt4buZdCBQcm9ncmFtIHRow6ptIFNoaW55IHThuqFvIHJhIFdlYi1TZXJ2ZXIgxJHhu4MgbMawdSB0cuG7ryBjw6FjIFNoaW55LUFwcHMuIE7DsyBtaeG7hW4gcGjDrSB2w6AgxJHGsOG7o2MgY2hpYSBz4bq7IHbhu5tpIG3DoyBuZ3Xhu5NuIG3hu58gdMawxqFuZyB0aMOtY2ggdHLDqm4gR2l0SHViLg0KDQoqKlNoaW55IFNlcnZlcioqOiDEkcaw4bujYyBjw6BpIHRyw6puIG3DoXkgY2jhu6cgKHNlcnZlcikgbMOgbSB2acOqYyBkxrDhu5tpIHPhu7EgxJFp4buBdSBraGnhu4NuIGPhuqN1IExpbnV4LiDEkOG7gyBjw6BpIFNoaW55IFNlcnZlciBjw6FjIGLhuqFuIGPhuqduIExpbnV4IGjhu5cgdHLhu6MgVWJ1bnR1IDEyLjA0IGhv4bq3YyBt4bubaSAoNjQgYml0KSBob+G6t2MgQ2VudE9TL1JIRUwgNSAoNjQgYml0KS4NCg0KxJDhu4MgYmnhur90IHRow6ptIGNoaSB0aeG6v3QgaMahbiBjw6FjIGLhuqFuIGPDsyB0aOG7gyB2w6BvICBbU2hpbnkgU2VydmVyIGd1aWRlLl0oaHR0cHM6Ly9naXRodWIuY29tL3JzdHVkaW8vc2hpbnktc2VydmVyL2Jsb2IvbWFzdGVyL1JFQURNRS5tZCkgDQoNCg0KIyMjU2hpbnkgU2VydmVyIFBybzoNCg0KU2hpbnkgU2VydmVyIHPhu58gaOG7qXUgdOG6pXQgY+G6oyBuaOG7r25nIGfDrCBj4bqnbiB0aGnhur90IMSR4buDIMSRxINuZyBBcHBzIGPhu6dhIGPDoWMgYuG6oW4gbMOqbiBXZWIuIE5oxrBuZyBu4bq/dSBi4bqhbiBtdeG7kW4gc+G7rSBk4bulbmcgQXBwcyBj4bunYSBtw6xuaCB24bubaSBt4bulYyDEkcOtY2ggdGjGsMahbmcgbeG6oWkgdGjDrCBi4bqhbiBj4bqnbiBwaOG6o2kgdHLhuqMgcGjDrSBTZXJ2ZXIgcHJvZ3JhbXMga2jDoWMgbmhhdSwgbsOzIGNobyBwaMOpcCB0cmnhu4NuIGtoYWk6DQoNCi0gWMOhYyB0aOG7sWMgduG7m2kgbeG6rXQga2jhuql1DQotIEjhu5cgdHLhu6MgU0xMDQotIEPDtG5nIGPhu6UgxJHhu4MgcXXhuqNuIHRy4buLIChhZG1pbnMpDQotIEjhu5cgdHLhu6MgxrB1IHRpw6puLA0KLi4uDQoNCg0KIyMjS+G6v3QgbHXhuq1uOg0KDQpTaGlueS1BcHBzIHPhu60gZOG7pW5nIGThu4UgZMOgbmcuIEPDoWMgYuG6oW4gY8OzIHRo4buDIGNoaWEgc+G6uyBjw6FjIEFwcHMgY+G7p2EgbcOsbmggbmjGsCBsw6AgUi1zY3JpcHRzIGhv4bq3YyBkxrDhu5tpIGThuqFuZyBt4buZdCB0cmFuZyBXZWIgdMawxqFuZyB0w6FjLiBN4buXaSBwaMawxqFuZyBwaMOhcCBs4bqhaSBjw7Mgbmjhu69uZyDGsHUgdsOgIG5oxrDhu6NjIMSRaeG7g20ga2jDoWMgbmhhdS4NCg0KDQoNCg==