Ce tutoriel est le fruit de mes découvertes sur le portail R et
n’engage que moi. Ce n’est évidemment pas la seule manière de travailler
sur le portail R. Tous les éléments de code devraient fonctionner à
condition que vous ayez accès à l’Echantillon SNDS (ESND) qui est
utilisé comme exemple. C’est le cas de tous les accès permanents au
SNDS.
Connexion à la base de
données
On apprend dans R - Guide_utilisateur_RStudio_
V1.2.pdf que ce bloc est indispensable :
library(ROracle) # charger le package Roracle
drv <- dbDriver("Oracle") # obtenir le pilote pour se connecter à une base oracle
conn <- dbConnect(drv, dbname = "IPIAMPR2.WORLD") # se connecter à la base IPIAMPR2.WORLD
Sys.setenv(TZ = "Europe/Paris") # fuseaux horaires
Sys.setenv(ORA_SDTZ = "Europe/Paris")
Il permet de se connecter à la base de données Oracle de la Cnam.
Vous pourriez être tenté par une connexion via le pannel
Connections de RStudio mais cela n’est pas possible. Pour
l’explication technique, RStudio ne supporte à ce jour que les
connexions ODBC et Spark tandis que
ROracle utilise une connexion DBI.
On crée une fausse table pour le tutoriel.
# Créer un jeu ex nihilo
faux0 <- data.frame(x = 1:4, y = c(1, NA), z = c(rep("A",3), "B"))
try(dbRemoveTable(conn, "PROV"), silent = T) # supprimer la table si elle existe déjà, sinon erreur
dbWriteTable(conn, "PROV", faux0) # écrire la table
faux <- tbl(conn, "PROV") # charger la table
Les deux manières : SQL
ou dplyr
La guide de la Cnam propose de travailler soit en SQL Oracle soit en
syntaxe dplyr. Illustration et comparaison :
Exemple SQL
Oracle
Exemple : on récupère le référentiel de
biologie.
dbGetQuery(conn, "select * from ir_bio_r")[1:5,]
⚠
ne pas utiliser fetch first 100 rows only
qui n’est pas
optimisé et n’aboutit pas (explications)
comme dans la commande suivante :
# NE PAS EXECUTER, N'ABOUTIT PAS
# dbGetQuery(conn, "select * from ER_PRS_F fetch first 100 rows only")
Alors que celle-ci est immédiate (commentée car il s’agit de
résultats individuels non publiables) :
# dbGetQuery(conn, "SELECT * FROM ER_PRS_F WHERE ROWNUM <= 100.0")
Exemple Dplyr
Exemple : on récupère le référentiel de
biologie.
library(dplyr) # le paquet dplyr est nécessaire pour cette méthode
library(dbplyr) # je recommande de charger également dbplyr pour quelques fonctions suppémentaires
irbior <- tbl(conn, "IR_BIO_R")
irbior %>% collect %>% head(5)
Quelle manière
choisir ?
Les habitués de SAS préféreront peut-être travailler directement en
SQL en envoyant les requêtes via dbGetQuery()
.
Personnellement, et même si je n’aime pas dplyr
en temps
normal, je trouve la manière dplyr
beaucoup plus souple. En
particulier elle permet de faire les choses progressivement grâce à
l’évaluation paresseuse (lazy). Le logiciel n’exécute pas
directement chaque étape mais les enregistre jusqu’au moment où il n’a
plus le choix, par exemple lorsqu’on lui demande un comptage.
Exemple : on peut créer un objet
erprsf
avec des filtres usuels sur
ESND_ER_PRS_F
(échantillon SNDS, 2% des bénéficiaires). Si
l’on ne collecte pas le résultat, l’opération est immédiate puisqu’on ne
réalise pas réellement le filtre. Ensuite, on a tout le loisir de
repartir de cet objet pour filtrer sur telle et telle prestation. Un
dbGetQuery
sur ERE_PRS_F
avec des filtres
usuels serait impossible car demanderait trop de ressources en mémoire
vive. Donc il faudrait, pour chaque ciblage de prestation, remettre tous
les filtres depuis ERE_PRS_F
si l’on passait par SQL
Oracle. Traduction en code :
# Immédiat (évaluation paresseuse)
erprsf <- tbl(conn, in_schema("MEPSGP_156", "ESND_ER_PRS_F")) %>%
filter(
FLX_DIS_DTD - EXE_SOI_DTD < 183 & # on enlève
# les soins remontés 6 mois après la date de début pour raisonner à durée de flux contante
DPN_QLF != 71 &
PRS_DPN_QLP != 71 &
CPL_MAJ_TOP < 2 &
CPL_AFF_COD != 16 &
(!BEN_SEX_COD %in% c("0")) &
BEN_NAI_ANN > 1800 &
BEN_NAI_ANN <= 2016
) %>%
select(BEN_NIR_PSA, BEN_RNG_GEM, BEN_SEX_COD, BEN_AMA_COD, BEN_RES_DPT, BEN_RES_COM, FLX_DIS_DTD, EXE_SOI_AMD, EXE_SOI_DTD, PRS_NAT_REF, PSE_ACT_NAT, PSE_SPE_COD, PRS_ACT_QTE)
Filtrer les vaccins contre la grippe (codes
prestations 3331) en repartant de l’objet erprsf
:
erprsf %>% filter(PRS_NAT_REF == "3331") %>% tally
Filtrer les soins des médecins généralistes (codes
spécialité 1, 22 ou 23) en repartant de l’objet erprsf
:
erprsf %>% filter(PSE_SPE_COD %in% c(1, 22, 23)) %>% tally
La plupart des commandes présentées ensuite concernent la manière
dplyr.
REGLE D’OR
Il faut bien choisir le moment du rapatriement en R
(collect
ou dbGetQuery
).
Globalement, plus ce moment vient tard,
mieux c’est. Autrement dit plus on fait travailler
Oracle au lieu de R, mieux c’est. C’est le même principe si on
travaillait sur un autre moteur de base de données ou sur Spark. Ne
jamais rapatrier des données de prestations (par exemple un bout de
ER_PRS_F
), cela va être très long tout en risquant de
surcharger la mémoire vive du serveur, conduisant à un mail de la Cnam
et l’arrêt de la session. On peut éventuellement rapatrier une table
avec autant de lignes que d’individus, à condition de sélectionner peu
de colonnes. Ensuite finir en R.
Traduire une commande
dplyr en SQL Oracle
Dans les coulisses, le paquet dbplyr traduit le code
type dplyr en SQL Oracle. Pour s’en convaincre, on peut montrer la
requête effectivement exécutée avec show_query()
ou
remote_query()
tbl(conn, "IR_BIO_R") %>% show_query # du paquet dplyr
<SQL>
SELECT *
FROM "IR_BIO_R"
tbl(conn, "IR_BIO_R") %>% remote_query # du paquet dbplyr
<SQL> SELECT *
FROM "IR_BIO_R"
show_query
affiche la commande sous la forme d’un
message tandis que remote_query
renvoie la commande sous la
forme d’un résultat, ce qui peut permettre d’enregistrer le code de la
requête comme on le verra plus bas.
show_query()
et remote_query()
peuvent être
pratiques si l’on veut travailler directement avec le SQL Oracle (via
dbGetQuery()
) mais que l’on est rouillé. Ces commandes nous
donnent la traduction SQL oubliée d’une commande dplyr que l’on
connait.
Astuce : on peut montrer la traduction en même temps qu’on demande
l’exécution de la requête en enchainant par de nouvelles commandes après
show_query
:
tbl(conn, "IR_BIO_R") %>%
show_query %>%
head(1) %>%
collect
<SQL>
SELECT *
FROM "IR_BIO_R"
Commandes dplyr non
supportées
Toutes les commandes dplyr ne fonctionneront pas car le paquet
dbplyr ne les traduit pas toutes.
Par exemple, la commande slice
qui doit permettre de
sélectionner une ligne par son numéro, ne peut pas être traduite :
# Ne fonctionne PAS sur une connexion à une base Oracle :
# tbl(conn, "IR_BIO_R") %>% slice(100)
# Alors qu'elle fonctionne sur un tableau déjà rappatrié en R :
tbl(conn, "IR_BIO_R") %>% collect %>% slice(100)
Fonctions
courantes
Principalement des fonctions dbplyr.
Créer une colonne :
mutate
On peut modifier une colonne ou la créer si elle n’existe pas
encore.
Exemple sur le faux jeu : passer la colonne
x
au carré.
faux %>% mutate(x = x^2)
Créer une nouvelle colonne avec le carré de la
colonne x
.
faux %>% mutate(x2 = x^2)
Pour éviter les problèmes, respecter la casse ; si
on crée une colonne sous le nom de “NoM”, il vaut mieux l’appeler par
“NoM”. “nom” marchera dans certains cas (en SQL) et pas dans
d’autres.
⚠
quelques raccourcis habituels en R ne fonctionnent plus
Par exemple, sur un objet R (ici rappatrié via
collect
), on peut créer une colonne avec une
condition.
faux %>% collect %>% mutate(x2 = x > 2)
Sur une base Oracle (sans le
collect
), cela générera une erreur :
faux %>% mutate(x2 = x > 2) %>% show_query() %>% collect
<SQL>
SELECT "x", "y", "z", "x" > 2.0 AS "x2"
FROM ("PROV")
Erreur dans .oci.SendQuery(conn, statement, data = data, prefetch = prefetch, :
ORA-00923: mot-clé FROM absent à l'emplacement prévu
Et il faudra expliciter avec ifelse
, qui sera traduit
par CASE WHEN
en SQL :
faux %>% mutate(x2 = ifelse(x > 2, 1, 0)) %>% show_query() %>% collect # fonctionne
<SQL>
SELECT "x", "y", "z", CASE WHEN ("x" > 2.0) THEN (1.0) WHEN NOT("x" > 2.0) THEN (0.0) END AS "x2"
FROM ("PROV")
Astuce :
manipuler une colonne juste après sa création
On peut utiliser dans le même mutate
une colonne qui
vient d’être créée.
On crée une variable x3
qui
dépend d’une variable x2
créée par la même
occasion.
faux %>% mutate(x2 = x^2,
x3 = x2 + 10)
Ordre :
arrange
On peut ordonner avec arrange
qui va créer un
ORDER BY
en SQL :
faux %>% arrange(-x) %>% show_query() %>% collect
<SQL>
SELECT *
FROM ("PROV")
ORDER BY -"x"
-x
permet d’obtenir un ordre décroissant tout comme
desc(x)
.
Dédoublonnage :
distinct
La fonction dbplyr distinct
correspond à la fonction R
base unique
, elle élimine les lignes en doublons.
Exemple avec notre faux jeu de
données.
faux %>% select(y) %>% distinct
Comptes :
tally
tally
permet de compter le nombre de lignes et va être
traduit par un COUNT(*)
en SQL :
mcoc <- tbl(conn, in_schema("MEPSGP_156", "ESND_T_MCO19C"))
mcoc %>%
select("NIR_ANO_17") %>%
distinct %>%
tally %>%
show_query %>%
collect
<SQL>
SELECT COUNT(*) AS "n"
FROM (SELECT DISTINCT "NIR_ANO_17"
FROM ("MEPSGP_156"."ESND_T_MCO19C") ) "q01"
Groupages :
group_by
tally
fonctionne aussi avec group_by
si
l’on souhaite grouper par une variable.
Exemple on veut compter le nombre de séjours
finis chaque mois de l’année 2019.
mcoc %>%
group_by(EXE_SOI_AMF) %>%
tally %>%
show_query %>%
collect
<SQL>
SELECT "EXE_SOI_AMF", COUNT(*) AS "n"
FROM ("MEPSGP_156"."ESND_T_MCO19C")
GROUP BY "EXE_SOI_AMF"
count
est un raccourci qui fait la même chose. Notez que
c’est exactement la même traduction SQL :
mcoc %>%
count(EXE_SOI_AMF) %>%
show_query %>%
collect
<SQL>
SELECT "EXE_SOI_AMF", COUNT(*) AS "n"
FROM ("MEPSGP_156"."ESND_T_MCO19C")
GROUP BY "EXE_SOI_AMF"
Agrégations
tally
est un raccourci pour
summarise(n=n())
faux %>%
group_by(y) %>%
summarise(n = n()) %>% # au lieu de tally
show_query %>%
collect
<SQL>
SELECT "y", COUNT(*) AS "n"
FROM ("PROV")
GROUP BY "y"
On peut choisir d’agréger d’une autre manière, en prenant un compte
unique, la moyenne, la somme, le max :
faux %>% summarise(n = n(),
n_unique = n_distinct(y),
moy = mean(x),
tot = sum(x),
max = max(x))
Parfois il est pratique d’ordonner et de prendre la première
(first()
) ou la dernière valeur (last()
).
Malheureusement, ces deux commandes ne fonctionnent qu’en tant que
fonction de fenêtrage (avec mutate()
) et non en fonction
d’agrégation (avec summarize
). Ainsi cette commande échoue
:
faux %>% summarise(premiere = first(x))
Erreur : `first()` is only available in a windowed (`mutate()`) context
Une astuce consiste à coupler mutate()
avec
select()
et distinct()
:
faux %>%
group_by(z) %>%
mutate(premiere = first(x)) %>%
select(z, premiere) %>%
distinct
Mais il faut veiller à choisir les bonnes colonnes dans le
select
pour arriver au résultat escompté.
Fonctions de
fenêtrage
Première
observation
On peut utiliser group_by
puis arrange
(ou
dans l’autre sens) pour ensuite créer une variable de rang ou filtrer
directement sur le rang 1.
faux %>%
arrange(-x) %>%
group_by(y) %>%
filter(row_number() == 1)
Avis : ORDER BY is ignored in subqueries without LIMIT
ℹ Do you need to move arrange() later in the pipeline or use window_order() instead?
On reçoit un avertissement
ORDER BY is ignored in subqueries without LIMIT
mais cela
semble pourtant bien fonctionner, comme on peut le voir si on remplace
-x
par x
puisque le résultat est cohérent. À
manipuler avec précaution.
row_number
accepte un argument d’ordre, ce qui permet de
gagner en concision :
faux %>%
group_by(y) %>%
filter(row_number(-x) == 1)
Bémol : row_number
n’accepte qu’une variable d’ordre, à
la différence de arrange
.
La fonction window_order()
pourrait être la solution
:
faux %>%
group_by(y) %>%
window_order(-x, z) %>%
filter(row_number() == 1)
En plus le tibble garde les variables de groupage et d’ordre en
attributs et les affiche, ce qui est pratique.
Agrégation par
recyclage
L’agrégation par recyclage est un type particulier de fonction de
fenêtrage. A la différence des fonctions d’agrégation, on garde le même
nombre de lignes mais on réplique l’information au sein de chaque
groupe. Il suffit de remplacer summarise()
par
mutate()
.
Exemple :
faux %>%
group_by(z) %>%
mutate(som = n_distinct(x))
Pour les
amateurs
Pour des fonctions de fenêtrage plus complexes, on est obligés de
passer par des méthodes plus proches de SQL. Un option est la fonction
win_over
qui aide à construire son fenêtrage.
faux %>%
mutate(rang = dbplyr::win_over(con = conn, expr = sql("RANK()"), partition = "y", order = "x"))
Mais il n’est pas possible d’utiliser un ordre décroissant à ma
connaissance.
Rien ne vaut le 100% SQL dans ce cas :
faux %>%
mutate(rang = sql('RANK() OVER (PARTITION BY "y" ORDER BY "x" DESC)'))
Valeurs manquantes
: NVL
On peut facilement remplacer les valeurs manquantes dans Oracle avec
la fonction NVL
(no value).
Exemple avec notre faux jeu de
données.
faux %>%
mutate(y2 = NVL(y, NA), # ne change rien
y3 = NVL(y, 0)) # remplace par 0
# NVL(y, "A") ne fonctionne pas car y est de type numeric
# Exemple réel (commenté car résultats individuels)
# irbenr <- tbl(conn, "IR_BEN_R")
# irbenr %>%
# select(IND_RNM_BEN) %>%
# mutate(IND_RNM_BEN2 = NVL(IND_RNM_BEN, "NA")) # On peut mettre ce que l'on veut à la place de "NA"
Empiler des tables
: union_all
Equivalent de la fonction R rbind
:
union_all(faux, faux) %>% show_query() %>% collect
<SQL>
(SELECT *
FROM "PROV")
UNION ALL
(SELECT *
FROM "PROV")
Jointures
D’après le manuel
de dbplyr
, toutes les jointures possibles et leur
équivalent en SQL :
inner_join(x, y)
:
SELECT * FROM x JOIN y ON x.a = y.a
left_join(x, y)
:
SELECT * FROM x LEFT JOIN y ON x.a = y.a
right_join(x, y)
:
SELECT * FROM x RIGHT JOIN y ON x.a = y.a
full_join(x, y)
:
SELECT * FROM x FULL JOIN y ON x.a = y.a
semi_join(x, y)
:
SELECT * FROM x WHERE EXISTS (SELECT 1 FROM y WHERE x.a = y.a)
anti_join(x, y)
:
SELECT * FROM x WHERE NOT EXISTS (SELECT 1 FROM y WHERE x.a = y.a)
Pour chercher plus de fonctions, le
mieux est encore de consulter le manuel de
dbplyr.
Utiliser SQL dans
dplyr
Il est possible d’utiliser des commandes SQL directement dans la
syntaxe dplyr lorsqu’un besoin n’est pas couvert par une commande
dplyr.
Il y a trois manières de faire : avec des fonctions SQL, avec des
opérateurs SQL, avec des bouts de code SQL.
Fonctions
Si une fonction n’est pas reconnue par R, elle va être laissée telle
quelle pour Oracle, ce qui est très pratique. A vrai dire, c’était le
cas de la fonction NVL
présentée ci-dessus.
Exemple : la fonction CONCAT
pour
concaténer n’existe pas en R mais en Oracle :
tbl(conn, "T_MCO15C") %>% mutate(ano_retour = CONCAT(NIR_RET, NAI_RET)) %>% show_query
<SQL>
SELECT "EXE_SOI_AMD", "EXE_SOI_DTD", "EXE_SOI_AMF", "EXE_SOI_DTF", "COH_NAI_RET", "COH_SEX_RET", "DAT_RET", "ENT_DAT", "ETA_NUM", "FHO_RET", "FOR_NUM", "HOS_NNE_RET", "HOS_NN_MAM", "HOS_ORG_RET", "HOS_PLO", "NAI_RET", "NIR_ANO_17", "NIR_ANO_MAM", "NIR_RET", "NUM_DAT_AT", "NUM_DAT_AT_RET", "ORG_CPL_NUM_RET", "PMS_RET", "RNG_BEN", "RNG_BEN_RET", "RNG_NAI", "RNG_NAI_RET", "RSA_NUM", "SEJ_MER_RET", "SEJ_NUM", "SEJ_RET", "SEX_RET", "SOR_ANN", "SOR_DAT", "SOR_MOI", "VID_HOSP_FOR", CONCAT("NIR_RET", "NAI_RET") AS "ano_retour"
FROM ("T_MCO15C")
L’inconvénient de ces fonctions est qu’elles ne s’auto-complètent pas
en appuyant sur TAB et qu’on ne dispose pas d’une documentation
intégrée. Il faut utiliser son navigateur pour les découvrir et
apprendre à les utiliser. Exemple
de documentation pour CONCAT
.
Opérateurs
Si un opérateur entre %
n’est pas reconnu par R, il va
être laissé tel quel et Oracle pourra le comprendre.
Exemple sur un filtre classique pour retirer
les séjours de MCO avec une erreur sur le NIR, on utilise
%||%
qui va devenir simplement ||
et qui
permet la concaténation de plusieurs variables :
mcoc %>%
mutate(ano_retour = NIR_RET %||% NAI_RET %||% SEX_RET %||%
SEJ_RET %||% FHO_RET %||% PMS_RET %||% DAT_RET %||% COH_NAI_RET %||% COH_SEX_RET) %>%
filter(ano_retour == "000000000") %>%
tally %>%
show_query %>%
collect
<SQL>
SELECT COUNT(*) AS "n"
FROM (SELECT "EXE_SOI_AMD", "EXE_SOI_DTD", "EXE_SOI_AMF", "EXE_SOI_DTF", "COH_NAI_RET", "COH_SEX_RET", "DAT_RET", "ENT_ANN", "ENT_DAT", "ENT_MOI", "ETA_NUM", "ETA_NUM_RET", "FHO_RET", "FOR_NUM", "HOS_NNE_RET", "HOS_NN_MAM", "HOS_ORG_RET", "HOS_PLO", "ID_MAM_ENF", "IPP_BEN_ANO", "IPP_BEN_ANO_RET", "NAI_RET", "NIR_ANO_17", "NIR_RET", "NUM_DAT_AT", "NUM_DAT_AT_RET", "ORG_CPL_NUM", "ORG_CPL_NUM_RET", "PMS_RET", "RNG_BEN", "RNG_BEN_RET", "RNG_NAI", "RNG_NAI_RET", "RSA_NUM", "SEJ_MER_RET", "SEJ_NUM", "SEJ_RET", "SEX_RET", "SOR_DAT", "VID_HOSP_FOR", "NIR_RET" || "NAI_RET" || "SEX_RET" || "SEJ_RET" || "FHO_RET" || "PMS_RET" || "DAT_RET" || "COH_NAI_RET" || "COH_SEX_RET" AS "ano_retour"
FROM ("MEPSGP_156"."ESND_T_MCO19C") ) "q01"
WHERE ("ano_retour" = '000000000')
L’exemple le plus pratique est l’opérateur LIKE
qui
permet d’utiliser les expressions
régulières (regex) Oracle pour décrire des chaînes de caractères
complexes. Si on veut que PRS_NAT_REF
commence par 31 :
erprsf %>% filter(PRS_NAT_REF %LIKE% "31%") %>% show_query
<SQL>
SELECT *
FROM (SELECT "BEN_NIR_PSA", "BEN_RNG_GEM", "BEN_SEX_COD", "BEN_AMA_COD", "BEN_RES_DPT", "BEN_RES_COM", "FLX_DIS_DTD", "EXE_SOI_AMD", "EXE_SOI_DTD", "PRS_NAT_REF", "PSE_ACT_NAT", "PSE_SPE_COD", "PRS_ACT_QTE"
FROM ("MEPSGP_156"."ESND_ER_PRS_F")
WHERE ("FLX_DIS_DTD" - "EXE_SOI_DTD" < 183.0 AND "DPN_QLF" != 71.0 AND "PRS_DPN_QLP" != 71.0 AND "CPL_MAJ_TOP" < 2.0 AND "CPL_AFF_COD" != 16.0 AND (NOT("BEN_SEX_COD" IN ('0'))) AND "BEN_NAI_ANN" > 1800.0 AND "BEN_NAI_ANN" <= 2016.0)) "q01"
WHERE ("PRS_NAT_REF" LIKE '31%')
⚠
les regex Oracle ne fonctionnent pas de la même manière que les regex
courantes sur Linux.
Bouts de code
SQL
On peut toujours glisser des bouts de code SQL s’il n’y a quelque
chose que l’on ne sait pas faire en syntaxe dplyr. Pour ça on utilisera
la commande sql
.
erprsf %>% filter(PSE_SPE_COD == sql("ANY (1,2,3)")) %>% show_query()
<SQL>
SELECT *
FROM (SELECT "BEN_NIR_PSA", "BEN_RNG_GEM", "BEN_SEX_COD", "BEN_AMA_COD", "BEN_RES_DPT", "BEN_RES_COM", "FLX_DIS_DTD", "EXE_SOI_AMD", "EXE_SOI_DTD", "PRS_NAT_REF", "PSE_ACT_NAT", "PSE_SPE_COD", "PRS_ACT_QTE"
FROM ("MEPSGP_156"."ESND_ER_PRS_F")
WHERE ("FLX_DIS_DTD" - "EXE_SOI_DTD" < 183.0 AND "DPN_QLF" != 71.0 AND "PRS_DPN_QLP" != 71.0 AND "CPL_MAJ_TOP" < 2.0 AND "CPL_AFF_COD" != 16.0 AND (NOT("BEN_SEX_COD" IN ('0'))) AND "BEN_NAI_ANN" > 1800.0 AND "BEN_NAI_ANN" <= 2016.0)) "q01"
WHERE ("PSE_SPE_COD" = ANY (1,2,3))
Dans cet exemple, on aurait facilement pu
remplacer la condition par PSE_SPE_COD %in% (1,2,3)
, mais
dans quelques cas le SQL est la seule solution, en particulier pour les
fonctions de fenêtrage.
Autre cas d’usage : pour éviter des ifelse imbriqués, on
utilisera directement la fonction SQL CASE WHEN
. Par
exemple si on veut remplir BEN_RES_DPT2016_COMP
avec le
département au début 2016, BEN_RES_DPT2016
, préalablement
extrait. Lorsqu’il est absent on va chercher dans 2017
(BEN_RES_DPT2017
), 2015, 2018, 2014, 2019, 2013, 2020. Si
on a rien dans toutes ces années (pour un non consommant), on prend ces
infos dans IR_BEN_R
(BEN_RES_DPT
).
# Ce code vient du projet beneficiairesSNDS (02_production.R), où il fonctionne
# irbenr4 %>%
# mutate(BEN_RES_DPT2016_COMP = sql('CASE
# WHEN NOT((("BEN_RES_DPT2016") IS NULL)) THEN ("BEN_RES_DPT2016")
# WHEN NOT((("BEN_RES_DPT2017") IS NULL)) THEN ("BEN_RES_DPT2017")
# WHEN NOT((("BEN_RES_DPT2015") IS NULL)) THEN ("BEN_RES_DPT2015")
# WHEN NOT((("BEN_RES_DPT2018") IS NULL)) THEN ("BEN_RES_DPT2018")
# WHEN NOT((("BEN_RES_DPT2019") IS NULL)) THEN ("BEN_RES_DPT2019")
# WHEN NOT((("BEN_RES_DPT2013") IS NULL)) THEN ("BEN_RES_DPT2013")
# WHEN NOT((("BEN_RES_DPT2020") IS NULL)) THEN ("BEN_RES_DPT2020")
# ELSE "BEN_RES_DPT"
# END')
Les dates
Pas vraiment facile à gérer. Le guide CNAM donne l’indication pour un
filtre en utilisant la fonction Oracle TO_DATE
:
irbior %>%
filter(BIO_CRE_DAT >= TO_DATE('2017-01-01','yyyy-MM-dd')) %>%
head(5) %>%
collect
⚠
il s’agit de dateHeure. Deux dates peuvent ne différer que de quelques
heures. Le référentiel des bénéficiaires est un bon exemple quand on
compare la date d’insertion dans le référentiel
(BEN_DTE_INS
) et la date de mise à jour
(BEN_IDT_MAJ
) :
irbenr <- tbl(conn, "IR_BEN_R")
irbenr %>%
select(BEN_DTE_INS, BEN_IDT_MAJ) %>%
filter(BEN_DTE_INS == TO_DATE('2020-03-11','yyyy-MM-dd')
& BEN_IDT_MAJ < TO_DATE('2020-03-12','yyyy-MM-dd')
) %>%
mutate(identiques = ifelse(BEN_DTE_INS == BEN_IDT_MAJ, 1, 0)) %>%
head(5) %>%
collect
Remarque 1 : il y a un problème de fuseau horaire à
l’affichage des résultats (2020-03-10 23:00:00 et non 2020-03-11
00:00:00). Ce problème se trouve du côté de l’affichage en R et non du
côté d’Oracle car le filtre a bien porté sur le 11 mars et non le
10.
Remarque 2 : pour ces lignes l’insertion et la mise à jour
ont été faites le même jour mais à des heures différentes, d’où la
colonne identiques
à 0
. Il faut donc être
vigilant quand on compare des dates si l’on ne souhaite une précision
qu’au jour près.
De dateHeure à
date
Pour perdre le détail de l’heure, on peut utiliser la fonction SQL
TRUNC
:
irbenr %>%
filter(BEN_DTE_INS == TO_DATE('2020-03-11','yyyy-MM-dd')
& BEN_IDT_MAJ < TO_DATE('2020-03-12','yyyy-MM-dd')
) %>%
select(BEN_IDT_MAJ) %>%
mutate(BEN_IDT_MAJ_trunc = TRUNC(BEN_IDT_MAJ)) %>% # équivalent :
# mutate(BEN_IDT_MAJ_trunc = sql("TRUNC(BEN_IDT_MAJ)")) %>%
head(5) %>%
collect
Pour une comparaison de deux dates, une autre solution aurait été de
s’assurer que la valeur absolue de la différence est inférieure à 1
(abs(BEN_DTE_INS - BEN_IDT_MAJ)<1
) mais cela ne revient
pas exactement au même car on vérifie alors que moins de 24h se sont
écoulées entre les deux dates et non qu’elles tombent sur le même
jour.
Analogie entre
dbplyr et SparkR
Il y a une claire analogie entre SparkR et ROracle. Dans les deux cas
R envoie des requêtes à un logiciel externe. Les paquets
SparkR et ROracle font la traduction,
Spark ou Oracle font le travail. Ensuite le résultat est rapatrié en
R.
L’analogie va plus loin puisque SparkR a repris le vocabulaire de
dplyr (select
, filter
…), ce qui est
globalement assez pratique. Il y a quelques subtilités qu’on découvre
par la pratique. Par exemple le head
de dbplyr ne rapatrie
pas en R à la différence de SparkR qui intègre un collect
dans son head
. Autre subtilité, les premières lignes sont
toujours les mêmes avec Oracle, c’est-à-dire que deux head
donnent le même résultat. En Spark les lignes ne sont pas vraiment
ordonnées et deux head
donnent des résultats
différents.
En SparkR, il faut nommer les colonnes via
donnees$colonne
alors qu’on peut utiliser
colonne
en dbplyr.
En SparkR, on peut utiliser les fonctions temporelles pratiques
year
ou month
que l’on ne trouve pas en
ROracle où il faudra par exemple passer par
sql("EXTRACT(YEAR FROM BEN_DCD_DTE)")
.
Un nom de variable
variable
Lorsque l’on souhaite utiliser une variable au lieu du nom en dur
d’une variable, on peut utiliser les opérateurs dplyr !!
(bang-bang operator) et !!!
(big bang
operator), qui servent à pré-évaluer un objet.
Au lieu de
faux %>% summarize(moy = mean(x))
Je peux utiliser :
nomVariable <- "x"
faux %>% summarize(moy = mean(!!sym(nomVariable)))
Ce qui peut être pratique pour boucler sur des colonnes.
Une variante pour certains cas simples consiste à utiliser la
fonction sql()
qui injecte du code SQL (présentée ici):
nomVariable <- "Z"
faux %>%
mutate(Z = z) %>% # contrainte supplémentaire :
# sql va passer le code en majuscules, il faut donc des noms
# de colonne en majuscules pour que ça fonctionne
group_by(grp = sql(nomVariable)) %>% tally
Si vous êtes habitué à utiliser get()
pour ce genre
d’usages, cela ne fonctionnera pas ici car le get
sera
transmis à Oracle :
faux %>% summarize(moy = mean(get(nomVariable))) %>% show_query %>% collect
<SQL>
SELECT AVG(get('Z')) AS "moy"
FROM ("PROV")
Erreur dans .oci.SendQuery(conn, statement, data = data, prefetch = prefetch, :
ORA-00904: "GET" : identificateur non valide
Exemple réel pour exporter les effectifs de
chaque pathologie de la cartographie des pathologies en 2018
(30s).
ctind <- tbl(conn, in_schema("MEPSGP_156", "ESND_CT_IND_G8_2016"))
# Les variables qui nous intéressent commencent par le mot TOP ou SUP
tops <- colnames(ctind)[colnames(ctind) %like% "^(TOP|SUP)"]
# On va enregistrer les résultats dans une liste qu'on initie
liste <- list()
for (top in tops){
print(top)
prov <- ctind %>%
filter(!!sym(top) == 1) %>% # technique pour avoir un nom de colonne variable
tally %>%
mutate(top = top) %>% # renommer la colonne avec le nom de la pathologie
collect %>%
setDT
liste <- append(liste, list(prov)) # ajouter à la liste
}
tab <- rbindlist(liste) # empiler la liste
setcolorder(tab, "top") # réordonner les colonnes avec top en premier
# Ecriture pour export
# fwrite(tab, "~/EXPORT/effectifs_cartoPathos.csv")
tab[1:10,]
Pour déployer plusieurs variables dans une fonction,
il faudra se tourner vers le big-bang operator,
!!!
:
variables <- c("x", "z")
faux %>% select(!!!variables)
Forcer l’évaluation
en R
La ligne de partage entre ce qui est évalué en R et ce qui est évalué
en SQL est ténue. La preuve par l’exemple : dans la commande suivante id
est bien remplacé par sa valeur, tout fonctionne :
id <- 3
faux %>% filter(x == id)
Alors que si on cherche à prendre un élément dans un vecteur, il fait
une traduction SQL étrange avec un CASE WHEN
qui génère une
erreur :
ids <- 3:4
faux %>% filter(x == ids[1]) %>% show_query %>% collect
<SQL>
SELECT *
FROM ("PROV")
WHERE ("x" = CASE WHEN (1.0) THEN ((3, 4)) END)
Erreur dans .oci.SendQuery(conn, statement, data = data, prefetch = prefetch, :
ORA-00920: opérateur relationnel non valide
La pré-évaluation par l’opérateur bang-bang !!
nous sortira de ce mauvais pas :
ids <- 3:4
faux %>% filter(x == !!ids[1]) %>% show_query %>% collect
<SQL>
SELECT *
FROM ("PROV")
WHERE ("x" = 3)
Cela vaut aussi pour des fonctions qui générerait du code SQL :
contenuSQL <- function() sql('POWER("x", 2.0)')
faux %>% mutate(x2 = contenuSQL()) # ERREUR
Erreur dans .oci.SendQuery(conn, statement, data = data, prefetch = prefetch, :
ORA-00904: "CONTENUSQL" : identificateur non valide
faux %>% mutate(x2 = !!contenuSQL()) # FONCTIONNE
Tables et vues
Il n’y a pas de manière graphique pour naviguer dans les tables
disponibles comme sur SAS. Si on connait le nom de la table, on
l’appelle directement. Sinon, pour retrouver une table, on peut toujours
aller chercher son nom dans SAS. Si l’on ne souhaite pas lancer SAS, on
peut toujours utiliser les commandes ci-dessous.
N.B. : la plupart des noms de tables que l’on connaît ne renvoient
pas vraiment à des tables mais à des
vues, c’est-à-dire des enregistrements de requêtes, ou
à des synonymes, c’est-à-dire des surnoms.
Lister les tables
publiques
Dans une base Oracle, il existe des tables spéciales qui listent les
objets :
all_tables
liste les tables ;
all_views
liste les vues ;
all_synonyms
les synonymes ;
all_objects
tous les objets (tables,
vues, synonymes, index, séquences, éditions, paquets…).
Je déconseille le chargement en mémoire d’une de ses tables, assez
long. Par exemple, la table all_tables
se charge en 15
minutes alors que la plupart des objets qui nous intéressent (des vues
et des synonymes) en sont absents.
Mieux vaut faire la recherche directement en Oracle sur la table
all_objects
. Imaginons que l’on cherche tous les objets qui
contiennent CT_IND
pour la table centrale de la
cartographie des pathologies.
system.time({
tables <- dbGetQuery(conn,
"SELECT owner, object_name, object_id, object_type, created FROM all_objects WHERE object_name LIKE '%CT_IND%'")
# '%CT_IND%' est une expression régulière Oracle où % est remplacé par autant de caractères que nécessaire
}) # 8 secondes
utilisateur système écoulé
0.004 0.001 3.117
tables$OWNER <- nettoy(tables$OWNER) # retrait de l'identifiant utilisateur
tables[, c("OWNER", "OBJECT_NAME", "OBJECT_TYPE")]
Certes avec R, nous n’avons pas l’arborescence comme dans SAS pour
chercher les tables mais cela est aussi un atout. Par
exemple ici j’ai pu lister toutes les tables relatives à la cartographie
alors que les anciennes versions se trouvent dans ORAVUE et les
nouvelles dans ORAMEPS. Sur SAS, il faut savoir où chercher pour
dérouler la bonne bibliothèque.
On découvre aussi le type de l’objet et on peut ajouter sa date de
création (created
).
Lister les tables
privées
Pour lister les tables de l’utilisateur, il suffit d’utiliser
user_tables
au lieu de all_tables
. En
l’occurrence, il s’agira normalement de tables et non
de vues et synonymes donc il n’est pas nécessaire d’utiliser
user_objects
.
system.time({user_tables <- dbGetQuery(conn, "SELECT TABLE_NAME FROM user_tables")}) # 2
utilisateur système écoulé
0.002 0.001 2.581
if (nrow(user_tables) == 1) {
user_tables
} else {
warning("Il y a d'autres tables que le faux jeu de données, on affiche rien.")
}
Avis : Il y a d'autres tables que le faux jeu de données, on affiche rien.
Les tables listées ici se trouvent dans la bibliothèque ORAUSER sur
SAS. On retrouve bien la table PROV
qui est notre faux jeu
de données.
Charger une table
d’un autre schéma
En Oracle, un schéma est un ensemble d’objets d’une base de données.
Un schéma correspond à un propriétaire (owner) et a le même nom
que lui. Est-ce utile de savoir cela ? Oui car si vous voulez travailler
sur l’ESND, vous allez avoir un problème :
tbl(conn, "ESND_ER_PRS_F")
Erreur dans .oci.GetQuery(conn, statement, data = data, prefetch = prefetch, :
ORA-00942: Table ou vue inexistante
La vue n’est pas trouvée. On vérifie qu’elle existe bien :
tables <- dbGetQuery(conn, "SELECT owner, object_name, object_type FROM all_objects WHERE object_name LIKE '%ESND_ER_PRS%'")
tables$OWNER <- nettoy(tables$OWNER) # retrait de l'identifiant utilisateur
tables
Elle existe mais elle appartient au schéma/propriétaire
MEPSGP_156
(dans le cas du profil 156), qui diffère du
schéma ADGPP
de ER_PRS_F
. Le schéma de
ER_PRS_F
doit être celui par défaut de telle sorte que
tbl(conn, "ER_PRS_F")
fonctionne alors que
tbl(conn, "ESND_ER_PRS_F")
ne fonctionne pas. On peut
préciser un schéma différent du schéma par défaut grâce à
in_schema()
:
# erprsf <- tbl(conn, in_schema("PREGP_156", "ESND_ER_PRS_F")) # ne fonctionne pas
erprsf <- tbl(conn, in_schema("MEPSGP_156", "ESND_ER_PRS_F")) # fonctionne
Les bibliothèques
SAS
Les tables SAS ne sont pas accessibles. Sur SAS elles portent un
pictogramme de loupe.
Pour les ramener dans Oracle (dans Orauser) et ainsi pouvoir les
manipuler avec ROracle, il faut exécuter une requête sur SAS.
Par exemple si l’on veut récupérer la table
EXTRACTION_PATIENTS2019TR de consopat :
# Code optimisé qui vient du GUIDE DES BONNES PRATIQUES SAS - v2.0.pdf
proc sql;
drop table orauser.tableoracle;
create table orauser.tableoracle (BULKLOAD=yes BL_DATAFILE="%sysfunc(pathname(work))/ttt.xyz" BL_DELETE_DATAFILE=yes) as select * from CONSOPAT.EXTRACTION_PATIENTS2019TR;
quit;
Il y a un fort intérêt à sélectionner
quelques variables au lieu de select *
pour accélerer
l’exécution et réduire la place prise sur ORAUSER.
Une autre solution consiste à charger les tables SAS avec le paquet
haven. Cela fonctionne avec les vraies tables :
haven::read_sas("~/consopat/es_mts_v.sas7bdat")[1:4,]
Mais pas avec les vues SAS (.sas7bvew) :
haven::read_sas("~/consopat/extraction_patients2021tr.sas7bvew")
Erreur : Failed to parse /sasdata/prd/commun/data/consopat/extraction_patients2021tr.sas7bvew: Invalid file, or file has unsupported features.
Pas de solution apparente (voir
cette discussion sur Stackoverflow), il faut recourir au
rapatriement en SAS sur Orauser.
Sauvegarder une table
intermédiaire
Il faut bien distinguer les tableaux R des pointeurs vers les tables
Oracle :
a <- tbl(conn, "IR_BIO_R") # a est un pointeur vers la table Oracle
a <- tbl(conn, "IR_BIO_R") %>% collect # a est une table R
Si on possède une table R, on peut suivre le guide de la Cnam pour
l’enregistrer sous Oracle avec :
# On supprime la table si elle existe déjà, sinon erreur
try(dbRemoveTable(conn, "PROV"), silent = T) # le nom doit être en majuscule
# Ecriture à proprement parler
dbWriteTable(conn, "PROV", faux0) # faux0 est un data.frame
Mais cela implique un collect auparavant. Le circuit est alors
Oracle > R > Oracle ce qui est sous-optimal puisque le
collect
est une opération coûteuse. Une meilleure solution
est de ne pas rapatrier l’objet en R, de tout faire en Oracle.
Théoriquement la fonction dbplyr compute
réalise cela et
doit être très pratique :
# ERREUR A CE JOUR
# a <- tbl(conn, "IR_PHA_R") %>%
# filter(PHA_ATC_C03 == "A10") %>% # Médicaments du diabète
# compute
Elle doit enregistrer le résultat du calcul en tant que table Oracle
et la relire pour faire de a
un pointeur. Cela permettrait
donc de faire un point d’arrêt qui évite de reprendre tous les calculs
depuis le début.
Hélas la fonction compute
ne marche pas avec une base
Oracle pour l’instant car la traduction oublie le mot clé GLOBAL dans la
requête. Le bug concerne aussi la fonction copy_to
, qui
fait le contraire du collect
, c’est-à-dire envoyer à Oracle
un objet R. J’ai enregistré un ticket sur
le Github du projet dbplyr, le bug a été traité et le
compute
devrait bientôt de nouveau fonctionner.
En attendant je propose cette fonction artisanale qui fait la même
chose et je la couple avec une fonction de nettoyage pour supprimer les
tables provisoires de temps en temps :
# Supprimer des tables :
# - lesquelles = "prov" : les tables provisoires
# - lesquelles = "provRecentes" : les tables provisoires avant aujourd'hui
# - lesquelles = "toutes" : toutes les tables de l'utilisateur
supprTables <- function(lesquelles = "provRecentes"){
vect0 <- dbGetQuery(conn, "SELECT TABLE_NAME FROM user_tables")$TABLE_NAME
# On filtre les tables provisoires
vect1 <- vect0[grepl("PROV[0-9]{14}", vect0, perl = T)]
vect2 <- gsub("PROV([0-9]{14})", "\\1", vect1, perl = T)
vect2 <- as.Date(vect2, "%Y%m%d")
if (lesquelles == "toutes") {
vectAsupp <- vect0
} else if (lesquelles == "prov"){
vectAsupp <- vect1
} else {
vectAsupp <- vect1[vect2 < Sys.Date()]
}
if (length(vectAsupp)) sprintf("Tables supprimées : %s", paste(vectAsupp, collapse = ", "))
for (nomTable in vectAsupp) {
try(dbRemoveTable(conn, nomTable), silent = T)
}
}
# Equivalent fonctionnel de la fonction compute()
compute2 <- function(tbl_oracle) {
# On commence par un nettoyage des anciennes tables provisoires
# si on a oublié de le faire
supprTables(lesquelles = "provRecentes")
# On choisir un nom daté pour enregistrer la table
nomTable <- paste0("PROV", format(Sys.time(), "%Y%m%d%H%M%S"))
# renvoie une erreur si la table n'existe pas
system.time({
#
dbExecute(conn, paste0("CREATE TABLE ", nomTable," AS (", remote_query(tbl_oracle), ")"))
})
tbl(conn, nomTable)
}
Exemple d’utilisation
# Longs calculs (ici courts pour l'exemple)
a <- tbl(conn, "IR_PHA_R") %>%
filter(PHA_ATC_C03 == "A10")
# On écrit une table intermédiaire
a <- compute2(a)
Preuve que a
est un pointeur vers
une table intermédiaire et non une suite d’instructions :
a %>% show_query
<SQL>
SELECT *
FROM "PROV20230213145207"
Pour nettoyer les tables intermédiaires écrites, portant le nom de
“PROV”, vous pouvez exécuter de temps en temps la fonction
supprTables
:
supprTables(lesquelles = "prov")
Après nettoyage, le pointeur a
est cassé :
a # "ORA-00942: Table ou vue inexistante"
Erreur dans .oci.SendQuery(conn, statement, data = data, prefetch = prefetch, :
ORA-00942: Table ou vue inexistante
Orauser
Que ce soit via
dbWriteTable(conn, "nomTABLE", unDataFrameR)
ou la fonction
ci-dessus compute2
, on écrit sur l’espace Orauser, qui est
un espace partagé. Contrairement à SASDATA1, il n’y a
pas de quotas individuels. L’espace est quand même contraint et la Cnam
envoie régulièrement des mails pour faire supprimer des tables et éviter
la saturation de l’espace partagé. La bonne pratique est de supprimer
ses tables dès que possible. Le nettoyage peut se faire de façon
visuelle sur SAS ou avec la fonction supprTables
définie
ci-dessus.
Git
Logiquement, il n’est pas possible de connecter son projet à un dépôt
Git sur Github ou Gitlab puisqu’il s’agit d’une bulle sécurisée. Cela
n’empêche pas l’utilisation de Git en local. Ça ne permet pas de
travailler à plusieurs sur le même code (quoi que ça s’envisage avec un
espace projet) mais cela permet tout de même de faire le versionnage de
son code tout en le sécurisant.
Pour initialiser un dossier Git : File > New
project > New Directory > New Project puis
choisir un nom de projet, un emplacement et cocher
Create a git repository. Enfin valider avec Create project
as subdirectory.
Ensuite on peut utiliser Git normalement à partir du panneau en haut
à droite. Si l’on veut faire certaines opérations non incluses dans le
panneau, on peut toujours utiliser la commande system
pour
lancer une commande système.
Quelques exemples :
system(
# Décommenter la ligne intéressante
# Configurer son nom
# "git config --global user.name 'Samuel Allain'"
# Vérifier
# "git config --global user.name"
# Configurer son adresse mail
# "git config --global user.email 'samuel.allain@sante.gouv.fr'"
# Vérifier
# "git config --global user.email"
# Etat du dépôt
# "git status"
# Ajouter une étiquette
# "git tag v0.2"
# Supprimer une étiquette
# "git tag -d v0.2"
# Version de Git
"git --version"
)
git version 1.8.3.1
Bémol : lors de la pseudonymisation, il est
difficile de gérer l’historique Git, constitué d’une foule de fichiers
dans le dossier .git. En effet l’utilitaire fourni par la Cnam, BRUS,
demande de valider chaque fichier, un à un.
LS0tCnRpdGxlOiAiUHJhdGlxdWVyIFIgc3VyIGxlIHBvcnRhaWwgU05EUyIKc3VidGl0bGU6ICJDb21wbMOpbWVudHMgZCd1biB1dGlsaXNhdGV1ciBhdSBndWlkZSBDbmFtIgphdXRob3I6ICJTYW11ZWwgQUxMQUlOIChEUkVFUykiCmRhdGUgOiAiT2N0b2JyZSAyMDIxIC0gRGVybmnDqHJlIE1BSiBmw6l2cmllciAyMDIzIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgICMgY29kZV9mb2xkaW5nOiBoaWRlCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcwogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogMwogICAgdG9jX2Zsb2F0OiB5ZXMKICBodG1sX2RvY3VtZW50OgogICAgIyBjb2RlX2ZvbGRpbmc6IGhpZGUKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKICAgIG51bWJlcl9zZWN0aW9uczogeWVzCiAgICB0b2M6IHllcwogICAgdG9jX2RlcHRoOiAzCiAgICB0b2NfZmxvYXQ6IHllcwpmb250c2l6ZTogMTJwdAotLS0KCkNlIHR1dG9yaWVsIGVzdCBsZSBmcnVpdCBkZSBtZXMgZMOpY291dmVydGVzIHN1ciBsZSBwb3J0YWlsIFIgZXQgbidlbmdhZ2UgcXVlIG1vaS4gQ2Ugbidlc3Qgw6l2aWRlbW1lbnQgcGFzIGxhIHNldWxlIG1hbmnDqHJlIGRlIHRyYXZhaWxsZXIgc3VyIGxlIHBvcnRhaWwgUi4KVG91cyBsZXMgw6lsw6ltZW50cyBkZSBjb2RlIGRldnJhaWVudCBmb25jdGlvbm5lciDDoCBjb25kaXRpb24gcXVlIHZvdXMgYXlleiBhY2PDqHMgw6AgbCdFY2hhbnRpbGxvbiBTTkRTIChFU05EKSBxdWkgZXN0IHV0aWxpc8OpIGNvbW1lIGV4ZW1wbGUuIEMnZXN0IGxlIGNhcyBkZSB0b3VzIGxlcyBhY2PDqHMgcGVybWFuZW50cyBhdSBTTkRTLgoKKlZvdXMgcG91dmV6IHTDqWzDqWNoYXJnZXIgbGUgLlJtZCBwb3VyIGV4w6ljdXRlciBsZSBjb2RlIHZvdXMtbcOqbWUgZW4gc8OpbGVjdGlvbm5hbnQgbGUgYm91dG9uIGVuIGhhdXQgw6AgZHJvaXRlIGRlIGNldHRlIHBhZ2UgIkNvZGUmbmJzcDvilr4iIHB1aXMgIkRvd25sb2FkIFJtZCIqCgojIENvbW1lbnQgYWNjw6lkZXIgw6AgUiA/CgpMYSBkw6ltYXJjaGUgZCdhY2PDqHMgZXN0IGV4cGxpcXXDqWUgZGFucyBsZSBkb2N1bWVudCAqKipSIC0gQWNjw6hzIMOgIFIgc3VyIHBvcnRhaWwgU05EUy5wZGYqKiogcXVlIGwnb24gdHJvdXZlIGRhbnMgbCdvbmdsZXQgKkFjY3VlaWwqIGR1IFtwb3J0YWlsXShodHRwczovL3BvcnRhaWwuc25paXJhbS5hbWVsaS5mci8pLgoKIyBSZXNzb3VyY2VzIMOgIGxpcmUKCisgKihpbmRpc3BlbnNhYmxlKSogZ3VpZGUgdXRpbGlzYXRldXIgZGUgbGEgQ25hbSwgKioqUiAtIEd1aWRlX3V0aWxpc2F0ZXVyX1JTdHVkaW9fIFYxLjIucGRmKioqIHF1ZSBsJ29uIHRyb3V2ZSBkYW5zIGwnb25nbGV0ICpBY2N1ZWlsKiBkdSBbcG9ydGFpbF0oaHR0cHM6Ly9wb3J0YWlsLnNuaWlyYW0uYW1lbGkuZnIvKS4KTGVzIGluZm9ybWF0aW9ucyBsZXMgcGx1cyBpbXBvcnRhbnRlcyBzJ3kgdHJvdXZlbnQgZXQgY2UgdHV0b3JpZWwgbmUgZmFpdCBxdWUgY29tcGzDqXRlci48YnI+Kio8Zm9udCBjb2xvcj0iI2NmMDUwNSI+Q2UgdHV0b3JpZWwgbidlc3QgcGFzIGNvbXByw6loZW5zaWJsZSBzYW5zIGxlIGd1aWRlIGRlIGxhIENuYW0uPC9mb250PioqCisgKih1dGlsZSkqIGxlcyBjb25zZWlscyBkZSAqKipSIC0gQWNjw6hzIMOgIFIgc3VyIHBvcnRhaWwgU05EUy5wZGYqKiogcXVlIGwnb24gdHJvdXZlIGRhbnMgbCdvbmdsZXQgKkFjY3VlaWwqIGR1IFtwb3J0YWlsXShodHRwczovL3BvcnRhaWwuc25paXJhbS5hbWVsaS5mci8pLgorICoodXRpbGUgc2kgb24gbmUgY29ubmHDrnQgcGFzIGJpZW4gUiwgcGV1IHV0aWxlIHNpbm9uKSogbGUgKipzdXBwb3J0IGRlIGxhIGZvcm1hdGlvbiBSIHByb3Bvc8OpZSBwYXIgbGEgQ25hbSoqIHF1ZSBsJ29uIHRyb3V2ZXJhIHN1ciBsZSBbcG9ydGFpbF0oaHR0cHM6Ly9wb3J0YWlsLnNuaWlyYW0uYW1lbGkuZnIvKSAoIipUYWJsZWF1eCBldCByZXF1w6p0ZXMqIiA+IFNBUCBCdXNpbmVzT2JqZWN0cyA+IERvY3VtZW50cyA+IERvc3NpZXJzID4gU05JSVJBTSA+IF9Eb2N1bWVudGF0aW9uIFNOSUlSQU0tU05EUyA+IEZvcm1hdGlvbiA+IHN1cHBvcnQgZGVzIGZvcm1hdGlvbnMgU05JSVJBTSA+IDExLUxvZ2ljaWVscyBSKS4gUGx1dMO0dCBnw6luw6lyYWxpc3RlLCBwb3VyIGFwcHJlbmRyZSBSLCB0csOocyBwZXUgZGUgcG9pbnRzIHNww6ljaWZpcXVlcyBhdSBTTkRTLgorICoodXRpbGUpKiBsZSAqKipHdWlkZSBkZSBib25uZXMgcHJhdGlxdWVzIFNBUyAtIHYyLjEucGRmKioqIHF1aSBzZSB0cm91dmUgc3VyIGxhIHBhZ2UgZCdhY2N1ZWlsIGR1IHBvcnRhaWwuClVuIGNlcnRhaW4gbm9tYnJlIGRlIHBvaW50cyBzdXIgbCdvcHRpbWlzYXRpb24gZGVzIGNvZGVzIHZhbGVudCBhdXNzaSBwb3VyIFIuCisgKih1dGlsZSkqIGxlICoqc3VwcG9ydCBkZSBmb3JtYXRpb24gRG9ubsOpZXMgaW5kaXZpZHVlbGxlcyBiw6luw6lmaWNpYWlyZXMqKiwgw6AgcmVsaXJlIGRldXggZm9pcyBwYXIgc2VtYWluZSwgZXQgcXVlIGwnb24gdHJvdXZlcmE8IS0tICsgc3VyIGxlIHLDqXNlYXUgbWluaXN0w6hyZSBbIkk6XFNORFNcRm9ybWF0aW9uIFNOSUlSLUFNXFN1cHBvcnRzIGRlIGZvcm1hdGlvblw4LUZvcm1hdGlvbiBEQ0lSUyAtIERDSVIgLSBqYW52IDIwMjEgLSBkaXN0YW5jaWVsLnBkZiJdKCJJOlxcU05EU1xcRm9ybWF0aW9uIFNOSUlSLUFNXFxTdXBwb3J0cyBkZSBmb3JtYXRpb25cXDgtRm9ybWF0aW9uIERDSVJTIC0gRENJUiAtIGphbnYgMjAyMSAtIGRpc3RhbmNpZWwucGRmIikgLS0+CnN1ciBsZSBbcG9ydGFpbF0oaHR0cHM6Ly9wb3J0YWlsLnNuaWlyYW0uYW1lbGkuZnIvKSAoIipUYWJsZWF1eCBldCByZXF1w6p0ZXMqIiA+IFNBUCBCdXNpbmVzT2JqZWN0cyA+IERvY3VtZW50cyA+IERvc3NpZXJzID4gU05JSVJBTSA+IF9Eb2N1bWVudGF0aW9uIFNOSUlSQU0tU05EUyA+IEZvcm1hdGlvbiA+IHN1cHBvcnQgZGVzIGZvcm1hdGlvbnMgU05JSVJBTSA+IDctRElCKS4KCiMgUGFyYW3DqXRyYWdlIGRlIFJTdHVkaW8KCkxhIENuYW0gYSByYXBwZWzDqSBwYXIgbWFpbCBxdWVscXVlcyBwYXJhbcOodHJlcyBpbXBvcnRhbnRzIHBvdXIgUlN0dWRpbywgcXVpIHZhbGVudCBkJ2FpbGxldXJzIGF1LWRlbMOgIGR1IHBvcnRhaWwgZGUgbGEgQ25hbS4KSWxzIMOpdml0ZW50IGRlIHJlY2hhcmdlciBsZXMgb2JqZXRzIGF1IGTDqW1hcnJhZ2UgZGUgbGEgc2Vzc2lvbiwgY2UgcXVpIHBldXQgw6p0cmUgdHLDqHMgbG9uZy4KCkRhbnMgbGUgbWVudSAqVG9vbHMqID4gKkdsb2JhbCBPcHRpb25zKiA6CgorIETDqXNhY3RpdmVyIMKrIFJlc3RvcmUgbW9zdCByZWNlbnRseSBvcGVuIHByb2plY3QgYXQgc3RhcnR1cCDCuworIETDqXNhY3RpdmVyIMKrIFJlc3RvcmUgLlJEYXRhIGludG8gd29ya3NwYWNlIGF0IHN0YXJ0dXAgwrsKKyBEw6lzYWN0aXZlciDCqyBTYXZlIHdvcmtzcGFjZSB0byAuUkRhdGEgb24gZXhpdCDCuwoKCiMgQ29ubmV4aW9uIMOgIGxhIGJhc2UgZGUgZG9ubsOpZXMKCmBgYHtyIGluY2x1ZGU9RkFMU0V9CiMgUXVlbCBlc3QgbW9uIGlkZW50aWZpYW50ID8KaWQgPC0gZ3N1YigiLipcXC8oMzAuKj8pXFwvLioiLCAiXFwxIiwgZ2V0d2QoKSkKCiMgRm9uY3Rpb24gcXVpIHJlbXBsYWNlIHNvbiBpZGVudGlmaWFudCBwYXIgdW4gaWRlbnRpZmlhbnQgZmljdGlmIGRhbnMgdW4gdmVjdGV1ciwgcG91ciDDqXZpdGVyIHNhIGRpZmZ1c2lvbgpuZXR0b3kgPC0gZnVuY3Rpb24odmVjdCkgewogIHJlbXAgPC0gIjAxYTAxMjM0NTY3ODkwMSIKICB2ZWN0IDwtIGdzdWIodG9sb3dlcihpZCksIHJlbXAsIHZlY3QsIGZpeGVkID0gVCkKICB2ZWN0IDwtIGdzdWIodG91cHBlcihpZCksIHJlbXAsIHZlY3QsIGZpeGVkID0gVCkKICB2ZWN0Cn0KYGBgCgpPbiBhcHByZW5kIGRhbnMgKioqUiAtIEd1aWRlX3V0aWxpc2F0ZXVyX1JTdHVkaW9fIFYxLjIucGRmKioqIHF1ZSBjZSBibG9jIGVzdCBpbmRpc3BlbnNhYmxlIDoKCmBgYHtyIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkoUk9yYWNsZSkgIyBjaGFyZ2VyIGxlIHBhY2thZ2UgUm9yYWNsZQpkcnYgPC0gZGJEcml2ZXIoIk9yYWNsZSIpICMgb2J0ZW5pciBsZSBwaWxvdGUgcG91ciBzZSBjb25uZWN0ZXIgw6AgdW5lIGJhc2Ugb3JhY2xlCmNvbm4gPC0gZGJDb25uZWN0KGRydiwgZGJuYW1lID0gIklQSUFNUFIyLldPUkxEIikgIyBzZSBjb25uZWN0ZXIgw6AgbGEgYmFzZSBJUElBTVBSMi5XT1JMRApTeXMuc2V0ZW52KFRaID0gIkV1cm9wZS9QYXJpcyIpICMgZnVzZWF1eCBob3JhaXJlcwpTeXMuc2V0ZW52KE9SQV9TRFRaID0gIkV1cm9wZS9QYXJpcyIpCmBgYAoKSWwgcGVybWV0IGRlIHNlIGNvbm5lY3RlciDDoCBsYSBiYXNlIGRlIGRvbm7DqWVzIE9yYWNsZSBkZSBsYSBDbmFtLiBWb3VzIHBvdXJyaWV6IMOqdHJlIHRlbnTDqSBwYXIgdW5lIGNvbm5leGlvbiB2aWEgbGUgcGFubmVsICpDb25uZWN0aW9ucyogZGUgUlN0dWRpbyBtYWlzIGNlbGEgbidlc3QgcGFzIHBvc3NpYmxlLiBQb3VyIGwnZXhwbGljYXRpb24gdGVjaG5pcXVlLCBSU3R1ZGlvIG5lIHN1cHBvcnRlIMOgIGNlIGpvdXIgcXVlIGxlcyBjb25uZXhpb25zICoqT0RCQyoqIGV0ICoqU3BhcmsqKiB0YW5kaXMgcXVlIFJPcmFjbGUgdXRpbGlzZSB1bmUgY29ubmV4aW9uICoqREJJKiouCgpPbiBjcsOpZSB1bmUgZmF1c3NlIHRhYmxlIHBvdXIgbGUgdHV0b3JpZWwuCgpgYGB7ciB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPUZBTFNFLCByZXN1bHRzPSJoaWRlIn0KbGlicmFyeShkYXRhLnRhYmxlKSAjIHBhcXVldCB0csOocyBwdWlzc2FudCwgcXVpIHBvdXJyYSB0b3Vqb3VycyBzZXJ2aXIKbGlicmFyeShkcGx5cikKbGlicmFyeShkYnBseXIpCmBgYAoKYGBge3IsIHJlc3VsdHM9ImhpZGUifQojIENyw6llciB1biBqZXUgZXggbmloaWxvIApmYXV4MCA8LSBkYXRhLmZyYW1lKHggPSAxOjQsIHkgPSBjKDEsIE5BKSwgeiA9IGMocmVwKCJBIiwzKSwgIkIiKSkKdHJ5KGRiUmVtb3ZlVGFibGUoY29ubiwgIlBST1YiKSwgc2lsZW50ID0gVCkgIyBzdXBwcmltZXIgbGEgdGFibGUgc2kgZWxsZSBleGlzdGUgZMOpasOgLCBzaW5vbiBlcnJldXIKZGJXcml0ZVRhYmxlKGNvbm4sICJQUk9WIiwgZmF1eDApICMgw6ljcmlyZSBsYSB0YWJsZQpmYXV4IDwtIHRibChjb25uLCAiUFJPViIpICMgY2hhcmdlciBsYSB0YWJsZQpgYGAKCgojIExlcyBkZXV4IG1hbmnDqHJlcyA6IFNRTCBvdSBkcGx5cgoKTGEgZ3VpZGUgZGUgbGEgQ25hbSBwcm9wb3NlIGRlIHRyYXZhaWxsZXIgc29pdCBlbiBTUUwgT3JhY2xlIHNvaXQgZW4gc3ludGF4ZSBkcGx5ci4gSWxsdXN0cmF0aW9uIGV0IGNvbXBhcmFpc29uIDoKCiMjIEV4ZW1wbGUgU1FMIE9yYWNsZQoKPGZvbnQgY29sb3I9IiM3MzczNzMiPkV4ZW1wbGUgOiBvbiByw6ljdXDDqHJlIGxlIHLDqWbDqXJlbnRpZWwgZGUgYmlvbG9naWUuPC9mb250PgoKYGBge3J9CmRiR2V0UXVlcnkoY29ubiwgInNlbGVjdCAqIGZyb20gaXJfYmlvX3IiKVsxOjUsXQpgYGAKCjxzcGFuIHN0eWxlPSJmb250LXNpemU6MjAwJTtkaXNwbGF5OmlubGluZS1ibG9jazsgdmVydGljYWwtYWxpZ246bWlkZGxlOyBtYXJnaW4tYm90dG9tOjBweDsiPuKaoDwvc3Bhbj4gbmUgcGFzIHV0aWxpc2VyIGBmZXRjaCAKZmlyc3QgMTAwIHJvd3Mgb25seWAgcXVpIG4nZXN0IHBhcyBvcHRpbWlzw6kgZXQgbidhYm91dGl0IHBhcyAoW2V4cGxpY2F0aW9uc10oaHR0cHM6Ly9ibG9ncy5vcmFjbGUuY29tL29wdGltaXplci9wb3N0L2ZldGNoLWZpcnN0LXJvd3MtanVzdC1nb3QtZmFzdGVyKSkgY29tbWUgZGFucyBsYSBjb21tYW5kZSBzdWl2YW50ZSA6CgpgYGB7cn0KIyBORSBQQVMgRVhFQ1VURVIsIE4nQUJPVVRJVCBQQVMKIyBkYkdldFF1ZXJ5KGNvbm4sICJzZWxlY3QgKiBmcm9tIEVSX1BSU19GIGZldGNoIGZpcnN0IDEwMCByb3dzIG9ubHkiKQpgYGAKCkFsb3JzIHF1ZSBjZWxsZS1jaSBlc3QgaW1tw6lkaWF0ZSAoY29tbWVudMOpZSBjYXIgaWwgcydhZ2l0IGRlIHLDqXN1bHRhdHMgaW5kaXZpZHVlbHMgbm9uIHB1YmxpYWJsZXMpIDoKCmBgYHtyfQojIGRiR2V0UXVlcnkoY29ubiwgIlNFTEVDVCAqIEZST00gRVJfUFJTX0YgV0hFUkUgUk9XTlVNIDw9IDEwMC4wIikKYGBgCgoKIyMgRXhlbXBsZSBEcGx5cgo8Zm9udCBjb2xvcj0iIzczNzM3MyI+RXhlbXBsZSA6IG9uIHLDqWN1cMOocmUgbGUgcsOpZsOpcmVudGllbCBkZSBiaW9sb2dpZS48L2ZvbnQ+CgpgYGB7ciwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KbGlicmFyeShkcGx5cikgIyBsZSBwYXF1ZXQgZHBseXIgZXN0IG7DqWNlc3NhaXJlIHBvdXIgY2V0dGUgbcOpdGhvZGUKbGlicmFyeShkYnBseXIpICMgamUgcmVjb21tYW5kZSBkZSBjaGFyZ2VyIMOpZ2FsZW1lbnQgZGJwbHlyIHBvdXIgcXVlbHF1ZXMgZm9uY3Rpb25zIHN1cHDDqW1lbnRhaXJlcwoKaXJiaW9yIDwtIHRibChjb25uLCAiSVJfQklPX1IiKQppcmJpb3IgJT4lIGNvbGxlY3QgJT4lIGhlYWQoNSkKYGBgCgojIyBRdWVsbGUgbWFuacOocmUgY2hvaXNpciA/CgpMZXMgaGFiaXR1w6lzIGRlIFNBUyBwcsOpZsOpcmVyb250IHBldXQtw6p0cmUgdHJhdmFpbGxlciBkaXJlY3RlbWVudCBlbiBTUUwgZW4gZW52b3lhbnQgbGVzIHJlcXXDqnRlcyB2aWEgYGRiR2V0UXVlcnkoKWAuClBlcnNvbm5lbGxlbWVudCwgZXQgbcOqbWUgc2kgamUgbidhaW1lIHBhcyBgZHBseXJgIGVuIHRlbXBzIG5vcm1hbCwgamUgdHJvdXZlIGxhIG1hbmnDqHJlIGBkcGx5cmAgYmVhdWNvdXAgcGx1cyBzb3VwbGUuCkVuIHBhcnRpY3VsaWVyIGVsbGUgcGVybWV0IGRlIGZhaXJlIGxlcyBjaG9zZXMgcHJvZ3Jlc3NpdmVtZW50IGdyw6JjZSDDoCBsJ8OpdmFsdWF0aW9uIHBhcmVzc2V1c2UgKCpsYXp5KikuCkxlIGxvZ2ljaWVsIG4nZXjDqWN1dGUgcGFzIGRpcmVjdGVtZW50IGNoYXF1ZSDDqXRhcGUgbWFpcyBsZXMgZW5yZWdpc3RyZSBqdXNxdSdhdSBtb21lbnQgb8O5IGlsIG4nYSBwbHVzIGxlIGNob2l4LCBwYXIgZXhlbXBsZSBsb3JzcXUnb24gbHVpIGRlbWFuZGUgdW4gY29tcHRhZ2UuCgo8Zm9udCBjb2xvcj0iIzczNzM3MyI+RXhlbXBsZSA6IG9uIHBldXQgY3LDqWVyIHVuIG9iamV0IGBlcnByc2ZgIGF2ZWMgZGVzIGZpbHRyZXMgdXN1ZWxzIHN1ciBgRVNORF9FUl9QUlNfRmAgKMOpY2hhbnRpbGxvbiBTTkRTLCAyJSBkZXMgYsOpbsOpZmljaWFpcmVzKS4gU2kgbCdvbiBuZSBjb2xsZWN0ZSBwYXMgbGUgcsOpc3VsdGF0LCBsJ29ww6lyYXRpb24gZXN0IGltbcOpZGlhdGUgcHVpc3F1J29uIG5lIHLDqWFsaXNlIHBhcyByw6llbGxlbWVudCBsZSBmaWx0cmUuCkVuc3VpdGUsIG9uIGEgdG91dCBsZSBsb2lzaXIgZGUgcmVwYXJ0aXIgZGUgY2V0IG9iamV0IHBvdXIgZmlsdHJlciBzdXIgdGVsbGUgZXQgdGVsbGUgcHJlc3RhdGlvbi4KVW4gYGRiR2V0UXVlcnlgIHN1ciBgRVJFX1BSU19GYCBhdmVjIGRlcyBmaWx0cmVzIHVzdWVscyBzZXJhaXQgaW1wb3NzaWJsZSBjYXIgZGVtYW5kZXJhaXQgdHJvcCBkZSByZXNzb3VyY2VzIGVuIG3DqW1vaXJlIHZpdmUuCkRvbmMgaWwgZmF1ZHJhaXQsIHBvdXIgY2hhcXVlIGNpYmxhZ2UgZGUgcHJlc3RhdGlvbiwgcmVtZXR0cmUgdG91cyBsZXMgZmlsdHJlcyBkZXB1aXMgYEVSRV9QUlNfRmAgc2kgbCdvbiBwYXNzYWl0IHBhciBTUUwgT3JhY2xlLiBUcmFkdWN0aW9uIGVuIGNvZGUgOjwvZm9udD4KCmBgYHtyLCB0aWR5PUZBTFNFfQojIEltbcOpZGlhdCAow6l2YWx1YXRpb24gcGFyZXNzZXVzZSkKZXJwcnNmIDwtIHRibChjb25uLCBpbl9zY2hlbWEoIk1FUFNHUF8xNTYiLCAiRVNORF9FUl9QUlNfRiIpKSAlPiUKICAgIGZpbHRlcigKICAgICAgICBGTFhfRElTX0RURCAtIEVYRV9TT0lfRFREIDwgMTgzICYgIyBvbiBlbmzDqHZlCiAgICAgICAgIyBsZXMgc29pbnMgcmVtb250w6lzIDYgbW9pcyBhcHLDqHMgbGEgZGF0ZSBkZSBkw6lidXQgcG91ciByYWlzb25uZXIgw6AgZHVyw6llIGRlIGZsdXggY29udGFudGUKICAgICAgICBEUE5fUUxGICE9IDcxICYKICAgICAgICBQUlNfRFBOX1FMUCAhPSA3MSAmCiAgICAgICAgQ1BMX01BSl9UT1AgPCAyICYKICAgICAgICBDUExfQUZGX0NPRCAhPSAxNiAmCiAgICAgICAgKCFCRU5fU0VYX0NPRCAlaW4lIGMoIjAiKSkgJgogICAgICAgIEJFTl9OQUlfQU5OID4gMTgwMCAmCiAgICAgICAgQkVOX05BSV9BTk4gPD0gMjAxNgogICAgKSAlPiUKICBzZWxlY3QoQkVOX05JUl9QU0EsIEJFTl9STkdfR0VNLCBCRU5fU0VYX0NPRCwgQkVOX0FNQV9DT0QsIEJFTl9SRVNfRFBULCBCRU5fUkVTX0NPTSwgRkxYX0RJU19EVEQsIEVYRV9TT0lfQU1ELCBFWEVfU09JX0RURCwgUFJTX05BVF9SRUYsIFBTRV9BQ1RfTkFULCBQU0VfU1BFX0NPRCwgUFJTX0FDVF9RVEUpCmBgYAoKRmlsdHJlciBsZXMgdmFjY2lucyBjb250cmUgbGEgKipncmlwcGUqKiAoY29kZXMgcHJlc3RhdGlvbnMgMzMzMSkgZW4gcmVwYXJ0YW50IGRlIGwnb2JqZXQgYGVycHJzZmAgOgoKYGBge3J9CmVycHJzZiAlPiUgZmlsdGVyKFBSU19OQVRfUkVGID09ICIzMzMxIikgJT4lIHRhbGx5CmBgYAoKRmlsdHJlciBsZXMgc29pbnMgZGVzICoqbcOpZGVjaW5zICoqZ8OpbsOpcmFsaXN0ZXMgKGNvZGVzIHNww6ljaWFsaXTDqSAxLCAyMiBvdSAyMykgZW4gcmVwYXJ0YW50IGRlIGwnb2JqZXQgYGVycHJzZmAgOgoKYGBge3J9CmVycHJzZiAlPiUgZmlsdGVyKFBTRV9TUEVfQ09EICVpbiUgYygxLCAyMiwgMjMpKSAlPiUgdGFsbHkKYGBgCgoKTGEgcGx1cGFydCBkZXMgY29tbWFuZGVzIHByw6lzZW50w6llcyBlbnN1aXRlIGNvbmNlcm5lbnQgbGEgbWFuacOocmUgZHBseXIuCgojIyBSRUdMRSBEJ09SCgpJbCBmYXV0IGJpZW4gY2hvaXNpciBsZSBtb21lbnQgZHUgcmFwYXRyaWVtZW50IGVuIFIgKGBjb2xsZWN0YCBvdSBgZGJHZXRRdWVyeWApLiA8Zm9udCBjb2xvcj0iI2NmMDUwNSI+KipHbG9iYWxlbWVudCwgcGx1cyBjZSBtb21lbnQgdmllbnQgdGFyZCwgbWlldXggYydlc3QuKio8L2ZvbnQ+IEF1dHJlbWVudCBkaXQgcGx1cyBvbiBmYWl0IHRyYXZhaWxsZXIgT3JhY2xlIGF1IGxpZXUgZGUgUiwgbWlldXggYydlc3QuIEMnZXN0IGxlIG3Dqm1lIHByaW5jaXBlIHNpIG9uIHRyYXZhaWxsYWl0IHN1ciB1biBhdXRyZSBtb3RldXIgZGUgYmFzZSBkZSBkb25uw6llcyBvdSBzdXIgU3BhcmsuIE5lIGphbWFpcyByYXBhdHJpZXIgZGVzIGRvbm7DqWVzIGRlIHByZXN0YXRpb25zIChwYXIgZXhlbXBsZSB1biBib3V0IGRlIGBFUl9QUlNfRmApLCBjZWxhIHZhIMOqdHJlIHRyw6hzIGxvbmcgdG91dCBlbiByaXNxdWFudCBkZSBzdXJjaGFyZ2VyIGxhIG3DqW1vaXJlIHZpdmUgZHUgc2VydmV1ciwgY29uZHVpc2FudCDDoCB1biBtYWlsIGRlIGxhIENuYW0gZXQgbCdhcnLDqnQgZGUgbGEgc2Vzc2lvbi4KT24gcGV1dCDDqXZlbnR1ZWxsZW1lbnQgcmFwYXRyaWVyIHVuZSB0YWJsZSBhdmVjIGF1dGFudCBkZSBsaWduZXMgcXVlIGQnaW5kaXZpZHVzLCDDoCBjb25kaXRpb24gZGUgc8OpbGVjdGlvbm5lciBwZXUgZGUgY29sb25uZXMuCkVuc3VpdGUgZmluaXIgZW4gUi4KCiMjIFRyYWR1aXJlIHVuZSBjb21tYW5kZSBkcGx5ciBlbiBTUUwgT3JhY2xlCgpEYW5zIGxlcyBjb3VsaXNzZXMsIGxlIHBhcXVldCBkKipiKipwbHlyIHRyYWR1aXQgbGUgY29kZSB0eXBlIGRwbHlyIGVuIFNRTCBPcmFjbGUuClBvdXIgcydlbiBjb252YWluY3JlLCBvbiBwZXV0IG1vbnRyZXIgbGEgcmVxdcOqdGUgZWZmZWN0aXZlbWVudCBleMOpY3V0w6llIGF2ZWMgYHNob3dfcXVlcnkoKWAgb3UgYHJlbW90ZV9xdWVyeSgpYApgYGB7cn0KdGJsKGNvbm4sICJJUl9CSU9fUiIpICU+JSBzaG93X3F1ZXJ5ICMgZHUgcGFxdWV0IGRwbHlyCnRibChjb25uLCAiSVJfQklPX1IiKSAlPiUgcmVtb3RlX3F1ZXJ5ICMgZHUgcGFxdWV0IGRicGx5cgpgYGAKCmBzaG93X3F1ZXJ5YCBhZmZpY2hlIGxhIGNvbW1hbmRlIHNvdXMgbGEgZm9ybWUgZCd1biBtZXNzYWdlIHRhbmRpcyBxdWUgYHJlbW90ZV9xdWVyeWAgcmVudm9pZSBsYSBjb21tYW5kZSBzb3VzIGxhIGZvcm1lIGQndW4gcsOpc3VsdGF0LCBjZSBxdWkgcGV1dCBwZXJtZXR0cmUgZCdlbnJlZ2lzdHJlciBsZSBjb2RlIGRlIGxhIHJlcXXDqnRlIGNvbW1lIG9uIGxlIHZlcnJhIHBsdXMgYmFzLgoKYHNob3dfcXVlcnkoKWAgZXQgYHJlbW90ZV9xdWVyeSgpYCBwZXV2ZW50IMOqdHJlIHByYXRpcXVlcyBzaSBsJ29uIHZldXQgdHJhdmFpbGxlciBkaXJlY3RlbWVudCBhdmVjIGxlIFNRTCBPcmFjbGUgKHZpYSBgZGJHZXRRdWVyeSgpYCkgbWFpcyBxdWUgbCdvbiBlc3Qgcm91aWxsw6kuIENlcyBjb21tYW5kZXMgbm91cyBkb25uZW50IGxhIHRyYWR1Y3Rpb24gU1FMIG91Ymxpw6llIGQndW5lIGNvbW1hbmRlIGRwbHlyIHF1ZSBsJ29uIGNvbm5haXQuCgpBc3R1Y2UgOiBvbiBwZXV0IG1vbnRyZXIgbGEgdHJhZHVjdGlvbiBlbiBtw6ptZSB0ZW1wcyBxdSdvbiBkZW1hbmRlIGwnZXjDqWN1dGlvbiBkZSBsYSByZXF1w6p0ZSBlbiBlbmNoYWluYW50IHBhciBkZSBub3V2ZWxsZXMgY29tbWFuZGVzIGFwcsOocyBgc2hvd19xdWVyeWAgOgoKYGBge3J9CnRibChjb25uLCAiSVJfQklPX1IiKSAlPiUKICBzaG93X3F1ZXJ5ICAlPiUKICBoZWFkKDEpICU+JQogIGNvbGxlY3QKYGBgCgoKIyMgQ29tbWFuZGVzIGRwbHlyIG5vbiBzdXBwb3J0w6llcwpUb3V0ZXMgbGVzIGNvbW1hbmRlcyBkcGx5ciBuZSBmb25jdGlvbm5lcm9udCBwYXMgY2FyIGxlIHBhcXVldCBkKipiKipwbHlyIG5lIGxlcyB0cmFkdWl0IHBhcyB0b3V0ZXMuCgpQYXIgZXhlbXBsZSwgbGEgY29tbWFuZGUgYHNsaWNlYCBxdWkgZG9pdCBwZXJtZXR0cmUgZGUgc8OpbGVjdGlvbm5lciB1bmUgbGlnbmUgcGFyIHNvbiBudW3DqXJvLCBuZSBwZXV0IHBhcyDDqnRyZSB0cmFkdWl0ZSA6CgpgYGB7cn0KIyBOZSBmb25jdGlvbm5lIFBBUyBzdXIgdW5lIGNvbm5leGlvbiDDoCB1bmUgYmFzZSBPcmFjbGUgOgojIHRibChjb25uLCAiSVJfQklPX1IiKSAlPiUgc2xpY2UoMTAwKQoKIyBBbG9ycyBxdSdlbGxlIGZvbmN0aW9ubmUgc3VyIHVuIHRhYmxlYXUgZMOpasOgIHJhcHBhdHJpw6kgZW4gUiA6CnRibChjb25uLCAiSVJfQklPX1IiKSAlPiUgY29sbGVjdCAlPiUgc2xpY2UoMTAwKQpgYGAKCgojIyBGb25jdGlvbnMgY291cmFudGVzCgpQcmluY2lwYWxlbWVudCBkZXMgZm9uY3Rpb25zIGRicGx5ci4KCiMjIyBDcsOpZXIgdW5lIGNvbG9ubmUgOiBgbXV0YXRlYAoKT24gcGV1dCBtb2RpZmllciB1bmUgY29sb25uZSBvdSBsYSBjcsOpZXIgc2kgZWxsZSBuJ2V4aXN0ZSBwYXMgZW5jb3JlLgoKPGZvbnQgY29sb3I9IiM3MzczNzMiPkV4ZW1wbGUgc3VyIGxlIGZhdXggamV1IDogcGFzc2VyIGxhIGNvbG9ubmUgYHhgIGF1IGNhcnLDqS48L2ZvbnQ+CmBgYHtyfQpmYXV4ICU+JSBtdXRhdGUoeCA9IHheMikKYGBgCgo8Zm9udCBjb2xvcj0iIzczNzM3MyI+Q3LDqWVyIHVuZSBub3V2ZWxsZSBjb2xvbm5lIGF2ZWMgbGUgY2FycsOpIGRlIGxhIGNvbG9ubmUgYHhgLjwvZm9udD4KYGBge3J9CmZhdXggJT4lIG11dGF0ZSh4MiA9IHheMikKYGBgCgpQb3VyIMOpdml0ZXIgbGVzIHByb2Jsw6htZXMsICoqcmVzcGVjdGVyIGxhIGNhc3NlKiogOyBzaSBvbiBjcsOpZSB1bmUgY29sb25uZSBzb3VzIGxlIG5vbSBkZSAiTm9NIiwgaWwgdmF1dCBtaWV1eCBsJ2FwcGVsZXIgcGFyICJOb00iLiAibm9tIiBtYXJjaGVyYSBkYW5zIGNlcnRhaW5zIGNhcyAoZW4gU1FMKSBldCBwYXMgZGFucyBkJ2F1dHJlcy4KCjxmb250IGNvbG9yPSIjY2YwNTA1Ij48c3BhbiBzdHlsZT0iZm9udC1zaXplOjIwMCU7ZGlzcGxheTppbmxpbmUtYmxvY2s7IHZlcnRpY2FsLWFsaWduOm1pZGRsZTsgbWFyZ2luLWJvdHRvbTowcHg7Ij7imqA8L3NwYW4+IHF1ZWxxdWVzIHJhY2NvdXJjaXMgaGFiaXR1ZWxzIGVuIFIgbmUgZm9uY3Rpb25uZW50IHBsdXM8L2ZvbnQ+Cgo8Zm9udCBjb2xvcj0iIzczNzM3MyI+UGFyIGV4ZW1wbGUsIHN1ciB1biBvYmpldCBSIChpY2kgcmFwcGF0cmnDqSB2aWEgYGNvbGxlY3RgKSwgb24gcGV1dCBjcsOpZXIgdW5lIGNvbG9ubmUgYXZlYyB1bmUgY29uZGl0aW9uLjwvZm9udD4KYGBge3J9CmZhdXggJT4lIGNvbGxlY3QgJT4lIG11dGF0ZSh4MiA9IHggPiAyKQpgYGAKCjxmb250IGNvbG9yPSIjNzM3MzczIj5TdXIgdW5lIGJhc2UgT3JhY2xlIChzYW5zIGxlIGBjb2xsZWN0YCksIGNlbGEgZ8OpbsOpcmVyYSB1bmUgZXJyZXVyIDo8L2ZvbnQ+CmBgYHtyLCBlcnJvcj1UUlVFfQpmYXV4ICU+JSBtdXRhdGUoeDIgPSB4ID4gMikgJT4lIHNob3dfcXVlcnkoKSAlPiUgY29sbGVjdApgYGAKCkV0IGlsIGZhdWRyYSBleHBsaWNpdGVyIGF2ZWMgYGlmZWxzZWAsIHF1aSBzZXJhIHRyYWR1aXQgcGFyIGBDQVNFIFdIRU5gIGVuIFNRTCA6CgpgYGB7cn0KZmF1eCAlPiUgbXV0YXRlKHgyID0gaWZlbHNlKHggPiAyLCAxLCAwKSkgJT4lIHNob3dfcXVlcnkoKSAlPiUgY29sbGVjdCAjIGZvbmN0aW9ubmUKYGBgCgoKIyMjIyBBc3R1Y2UgOiBtYW5pcHVsZXIgdW5lIGNvbG9ubmUganVzdGUgYXByw6hzIHNhIGNyw6lhdGlvbgoKT24gcGV1dCB1dGlsaXNlciBkYW5zIGxlIG3Dqm1lIGBtdXRhdGVgIHVuZSBjb2xvbm5lIHF1aSB2aWVudCBkJ8OqdHJlIGNyw6nDqWUuCgo8Zm9udCBjb2xvcj0iIzczNzM3MyI+IE9uIGNyw6llIHVuZSB2YXJpYWJsZSBgeDNgIHF1aSBkw6lwZW5kIGQndW5lIHZhcmlhYmxlIGB4MmAgY3LDqcOpZSBwYXIgbGEgbcOqbWUgb2NjYXNpb24uPC9mb250PgoKYGBge3J9CmZhdXggJT4lIG11dGF0ZSh4MiA9IHheMiwKICAgICAgICAgICAgICAgIHgzID0geDIgKyAxMCkKYGBgCgojIyMgT3JkcmUgOiBgYXJyYW5nZWAKCk9uIHBldXQgb3Jkb25uZXIgYXZlYyBgYXJyYW5nZWAgcXVpIHZhIGNyw6llciB1biBgT1JERVIgQllgIGVuIFNRTCA6CgpgYGB7cn0KZmF1eCAlPiUgYXJyYW5nZSgteCkgJT4lIHNob3dfcXVlcnkoKSAlPiUgY29sbGVjdApgYGAKYC14YCBwZXJtZXQgZCdvYnRlbmlyIHVuIG9yZHJlIGTDqWNyb2lzc2FudCB0b3V0IGNvbW1lIGBkZXNjKHgpYC4KCgojIyMgRMOpZG91Ymxvbm5hZ2UgOiBgZGlzdGluY3RgCgpMYSBmb25jdGlvbiBkYnBseXIgYGRpc3RpbmN0YCBjb3JyZXNwb25kIMOgIGxhIGZvbmN0aW9uIFIgYmFzZSBgdW5pcXVlYCwgZWxsZSDDqWxpbWluZSBsZXMgbGlnbmVzIGVuIGRvdWJsb25zLgoKPGZvbnQgY29sb3I9IiM3MzczNzMiPkV4ZW1wbGUgYXZlYyBub3RyZSBmYXV4IGpldSBkZSBkb25uw6llcy48L2ZvbnQ+CgpgYGB7cn0KZmF1eCAlPiUgc2VsZWN0KHkpICU+JSBkaXN0aW5jdApgYGAKCiMjIyBDb21wdGVzIDogYHRhbGx5YAoKYHRhbGx5YCBwZXJtZXQgZGUgY29tcHRlciBsZSBub21icmUgZGUgbGlnbmVzIGV0IHZhIMOqdHJlIHRyYWR1aXQgcGFyIHVuIGBDT1VOVCgqKWAgZW4gU1FMIDoKCgpgYGB7cn0KbWNvYyA8LSB0YmwoY29ubiwgaW5fc2NoZW1hKCJNRVBTR1BfMTU2IiwgIkVTTkRfVF9NQ08xOUMiKSkKbWNvYyAlPiUKICBzZWxlY3QoIk5JUl9BTk9fMTciKSAlPiUKICBkaXN0aW5jdCAlPiUKICB0YWxseSAlPiUKICBzaG93X3F1ZXJ5ICU+JQogIGNvbGxlY3QKYGBgCgojIyMgR3JvdXBhZ2VzIDogYGdyb3VwX2J5YApgdGFsbHlgIGZvbmN0aW9ubmUgYXVzc2kgYXZlYyBgZ3JvdXBfYnlgIHNpIGwnb24gc291aGFpdGUgZ3JvdXBlciBwYXIgdW5lIHZhcmlhYmxlLgoKPGZvbnQgY29sb3I9IiM3MzczNzMiPkV4ZW1wbGUgb24gdmV1dCBjb21wdGVyIGxlIG5vbWJyZSBkZSBzw6lqb3VycyBmaW5pcyBjaGFxdWUgbW9pcyBkZSBsJ2FubsOpZSAyMDE5LjwvZm9udD4KCmBgYHtyfQptY29jICU+JQogIGdyb3VwX2J5KEVYRV9TT0lfQU1GKSAlPiUKICB0YWxseSAlPiUKICBzaG93X3F1ZXJ5ICU+JQogIGNvbGxlY3QKYGBgCgpgY291bnRgIGVzdCB1biByYWNjb3VyY2kgcXVpIGZhaXQgbGEgbcOqbWUgY2hvc2UuIE5vdGV6IHF1ZSBjJ2VzdCBleGFjdGVtZW50IGxhIG3Dqm1lIHRyYWR1Y3Rpb24gU1FMIDoKCmBgYHtyfQptY29jICU+JQogIGNvdW50KEVYRV9TT0lfQU1GKSAlPiUKICBzaG93X3F1ZXJ5ICU+JQogIGNvbGxlY3QKYGBgCgojIyMgQWdyw6lnYXRpb25zCgpgdGFsbHlgIGVzdCB1biByYWNjb3VyY2kgcG91ciBgc3VtbWFyaXNlKG49bigpKWAKYGBge3IsIHdhcm5pbmc9RkFMU0V9CmZhdXggJT4lCiAgZ3JvdXBfYnkoeSkgJT4lCiAgc3VtbWFyaXNlKG4gPSBuKCkpICU+JSAjIGF1IGxpZXUgZGUgdGFsbHkgCiAgc2hvd19xdWVyeSAlPiUKICBjb2xsZWN0CmBgYApPbiBwZXV0IGNob2lzaXIgZCdhZ3LDqWdlciBkJ3VuZSBhdXRyZSBtYW5pw6hyZSwgZW4gcHJlbmFudCB1biBjb21wdGUgdW5pcXVlLCBsYSBtb3llbm5lLCBsYSBzb21tZSwgbGUgbWF4IDogCgpgYGB7ciwgd2FybmluZz1GQUxTRX0KZmF1eCAlPiUgc3VtbWFyaXNlKG4gPSBuKCksCiAgICAgICAgICAgICAgICAgICBuX3VuaXF1ZSA9IG5fZGlzdGluY3QoeSksCiAgICAgICAgICAgICAgICAgICBtb3kgPSBtZWFuKHgpLAogICAgICAgICAgICAgICAgICAgdG90ID0gc3VtKHgpLAogICAgICAgICAgICAgICAgICAgbWF4ID0gbWF4KHgpKQpgYGAKClBhcmZvaXMgaWwgZXN0IHByYXRpcXVlIGQnb3Jkb25uZXIgZXQgZGUgcHJlbmRyZSBsYSBwcmVtacOocmUgKGBmaXJzdCgpYCkgb3UgbGEgZGVybmnDqHJlIHZhbGV1ciAoYGxhc3QoKWApLgpNYWxoZXVyZXVzZW1lbnQsIGNlcyBkZXV4IGNvbW1hbmRlcyBuZSBmb25jdGlvbm5lbnQgcXUnZW4gdGFudCBxdWUgZm9uY3Rpb24gZGUgZmVuw6p0cmFnZSAoYXZlYyBgbXV0YXRlKClgKSBldCBub24gZW4gZm9uY3Rpb24gZCdhZ3LDqWdhdGlvbiAoYXZlYyBgc3VtbWFyaXplYCkuCkFpbnNpIGNldHRlIGNvbW1hbmRlIMOpY2hvdWUgOgoKYGBge3IsIGVycm9yPVRSVUV9CmZhdXggJT4lIHN1bW1hcmlzZShwcmVtaWVyZSA9IGZpcnN0KHgpKQpgYGAKClVuZSBhc3R1Y2UgY29uc2lzdGUgw6AgY291cGxlciBgbXV0YXRlKClgIGF2ZWMgYHNlbGVjdCgpYCBldCBgZGlzdGluY3QoKWAgOgoKYGBge3IsIGVycm9yPVRSVUV9CmZhdXggJT4lCiAgZ3JvdXBfYnkoeikgJT4lCiAgbXV0YXRlKHByZW1pZXJlID0gZmlyc3QoeCkpICU+JQogIHNlbGVjdCh6LCBwcmVtaWVyZSkgJT4lCiAgZGlzdGluY3QKYGBgCk1haXMgaWwgZmF1dCB2ZWlsbGVyIMOgIGNob2lzaXIgbGVzIGJvbm5lcyBjb2xvbm5lcyBkYW5zIGxlIGBzZWxlY3RgIHBvdXIgYXJyaXZlciBhdSByw6lzdWx0YXQgZXNjb21wdMOpLgoKIyMjIEZvbmN0aW9ucyBkZSBmZW7DqnRyYWdlCgoKIyMjIyBQcmVtacOocmUgb2JzZXJ2YXRpb24KCk9uIHBldXQgdXRpbGlzZXIgYGdyb3VwX2J5YCBwdWlzIGBhcnJhbmdlYCAob3UgZGFucyBsJ2F1dHJlIHNlbnMpIHBvdXIgZW5zdWl0ZSBjcsOpZXIgdW5lIHZhcmlhYmxlIGRlIHJhbmcgb3UgZmlsdHJlciBkaXJlY3RlbWVudCBzdXIgbGUgcmFuZyAxLgoKYGBge3J9CmZhdXggJT4lCiAgYXJyYW5nZSgteCkgJT4lCiAgZ3JvdXBfYnkoeSkgJT4lCiAgZmlsdGVyKHJvd19udW1iZXIoKSA9PSAxKQpgYGAKT24gcmXDp29pdCB1biBhdmVydGlzc2VtZW50IGBPUkRFUiBCWSBpcyBpZ25vcmVkIGluIHN1YnF1ZXJpZXMgd2l0aG91dCBMSU1JVGAgbWFpcyBjZWxhIHNlbWJsZSBwb3VydGFudCBiaWVuIGZvbmN0aW9ubmVyLCBjb21tZSBvbiBwZXV0IGxlIHZvaXIgc2kgb24gcmVtcGxhY2UgYC14YCBwYXIgYHhgIHB1aXNxdWUgbGUgcsOpc3VsdGF0IGVzdCBjb2jDqXJlbnQuIMOAIG1hbmlwdWxlciBhdmVjIHByw6ljYXV0aW9uLgoKYHJvd19udW1iZXJgIGFjY2VwdGUgdW4gYXJndW1lbnQgZCdvcmRyZSwgY2UgcXVpIHBlcm1ldCBkZSBnYWduZXIgZW4gY29uY2lzaW9uIDoKCmBgYHtyfQpmYXV4ICU+JQogIGdyb3VwX2J5KHkpICU+JQogIGZpbHRlcihyb3dfbnVtYmVyKC14KSA9PSAxKQpgYGAKQsOpbW9sIDogYHJvd19udW1iZXJgIG4nYWNjZXB0ZSBxdSd1bmUgdmFyaWFibGUgZCdvcmRyZSwgw6AgbGEgZGlmZsOpcmVuY2UgZGUgYGFycmFuZ2VgLgoKTGEgZm9uY3Rpb24gYHdpbmRvd19vcmRlcigpYCBwb3VycmFpdCDDqnRyZSBsYSBzb2x1dGlvbiA6CgpgYGB7cn0KZmF1eCAlPiUKICBncm91cF9ieSh5KSAlPiUKICB3aW5kb3dfb3JkZXIoLXgsIHopICU+JQogIGZpbHRlcihyb3dfbnVtYmVyKCkgPT0gMSkKYGBgCkVuIHBsdXMgbGUgdGliYmxlIGdhcmRlIGxlcyB2YXJpYWJsZXMgZGUgZ3JvdXBhZ2UgZXQgZCdvcmRyZSBlbiBhdHRyaWJ1dHMgZXQgbGVzIGFmZmljaGUsIGNlIHF1aSBlc3QgcHJhdGlxdWUuCgojIyMjIEFncsOpZ2F0aW9uIHBhciByZWN5Y2xhZ2UKCkwnYWdyw6lnYXRpb24gcGFyIHJlY3ljbGFnZSBlc3QgdW4gdHlwZSBwYXJ0aWN1bGllciBkZSBmb25jdGlvbiBkZSBmZW7DqnRyYWdlLgpBIGxhIGRpZmbDqXJlbmNlIGRlcyBmb25jdGlvbnMgZCdhZ3LDqWdhdGlvbiwgb24gZ2FyZGUgbGUgbcOqbWUgbm9tYnJlIGRlIGxpZ25lcyBtYWlzIG9uIHLDqXBsaXF1ZSBsJ2luZm9ybWF0aW9uIGF1IHNlaW4gZGUgY2hhcXVlIGdyb3VwZS4KSWwgc3VmZml0IGRlIHJlbXBsYWNlciBgc3VtbWFyaXNlKClgIHBhciBgbXV0YXRlKClgLgoKRXhlbXBsZSA6CgpgYGB7cn0KZmF1eCAlPiUKICBncm91cF9ieSh6KSAlPiUKICBtdXRhdGUoc29tID0gbl9kaXN0aW5jdCh4KSkKYGBgCgoKIyMjIyBQb3VyIGxlcyBhbWF0ZXVycwoKUG91ciBkZXMgZm9uY3Rpb25zIGRlIGZlbsOqdHJhZ2UgcGx1cyBjb21wbGV4ZXMsIG9uIGVzdCBvYmxpZ8OpcyBkZSBwYXNzZXIgcGFyIGRlcyBtw6l0aG9kZXMgcGx1cyBwcm9jaGVzIGRlIFNRTC4KVW4gb3B0aW9uIGVzdCBsYSBmb25jdGlvbiBgd2luX292ZXJgIHF1aSBhaWRlIMOgIGNvbnN0cnVpcmUgc29uIGZlbsOqdHJhZ2UuCgpgYGB7cn0KZmF1eCAlPiUKICBtdXRhdGUocmFuZyA9IGRicGx5cjo6d2luX292ZXIoY29uID0gY29ubiwgZXhwciA9IHNxbCgiUkFOSygpIiksIHBhcnRpdGlvbiA9ICJ5Iiwgb3JkZXIgPSAieCIpKQpgYGAKTWFpcyBpbCBuJ2VzdCBwYXMgcG9zc2libGUgZCd1dGlsaXNlciB1biBvcmRyZSBkw6ljcm9pc3NhbnQgw6AgbWEgY29ubmFpc3NhbmNlLgoKUmllbiBuZSB2YXV0IGxlIDEwMCUgU1FMIGRhbnMgY2UgY2FzIDoKCmBgYHtyfQpmYXV4ICU+JQogIG11dGF0ZShyYW5nID0gc3FsKCdSQU5LKCkgT1ZFUiAoUEFSVElUSU9OIEJZICJ5IiBPUkRFUiBCWSAieCIgREVTQyknKSkKYGBgCgoKIyMjIFZhbGV1cnMgbWFucXVhbnRlcyA6IGBOVkxgIHsjTlZMfQpPbiBwZXV0IGZhY2lsZW1lbnQgcmVtcGxhY2VyIGxlcyB2YWxldXJzIG1hbnF1YW50ZXMgZGFucyBPcmFjbGUgYXZlYyBsYSBmb25jdGlvbiBgTlZMYCAoKm5vIHZhbHVlKikuCgo8Zm9udCBjb2xvcj0iIzczNzM3MyI+RXhlbXBsZSBhdmVjIG5vdHJlIGZhdXggamV1IGRlIGRvbm7DqWVzLjwvZm9udD4KCmBgYHtyfQpmYXV4ICU+JQogIG11dGF0ZSh5MiA9IE5WTCh5LCBOQSksICMgbmUgY2hhbmdlIHJpZW4KICAgICAgICAgeTMgPSBOVkwoeSwgMCkpICMgcmVtcGxhY2UgcGFyIDAKCiMgTlZMKHksICJBIikgbmUgZm9uY3Rpb25uZSBwYXMgY2FyIHkgZXN0IGRlIHR5cGUgbnVtZXJpYwoKIyBFeGVtcGxlIHLDqWVsIChjb21tZW50w6kgY2FyIHLDqXN1bHRhdHMgaW5kaXZpZHVlbHMpCiMgaXJiZW5yIDwtIHRibChjb25uLCAiSVJfQkVOX1IiKQojIGlyYmVuciAlPiUKIyAgIHNlbGVjdChJTkRfUk5NX0JFTikgJT4lCiMgICBtdXRhdGUoSU5EX1JOTV9CRU4yID0gTlZMKElORF9STk1fQkVOLCAiTkEiKSkgIyBPbiBwZXV0IG1ldHRyZSBjZSBxdWUgbCdvbiB2ZXV0IMOgIGxhIHBsYWNlIGRlICJOQSIKCmBgYAoKCiMjIyBFbXBpbGVyIGRlcyB0YWJsZXMgOiBgdW5pb25fYWxsYAoKRXF1aXZhbGVudCBkZSBsYSBmb25jdGlvbiBSIGByYmluZGAgOgoKYGBge3J9CnVuaW9uX2FsbChmYXV4LCBmYXV4KSAlPiUgc2hvd19xdWVyeSgpICU+JSBjb2xsZWN0CmBgYAoKIyMjIEpvaW50dXJlcwoKRCdhcHLDqHMgbGUgW21hbnVlbCBkZSBgZGJwbHlyYF0oaHR0cHM6Ly9kYnBseXIudGlkeXZlcnNlLm9yZy9yZWZlcmVuY2Uvam9pbi50Ymxfc3FsLmh0bWwpLCB0b3V0ZXMgbGVzIGpvaW50dXJlcyBwb3NzaWJsZXMgZXQgbGV1ciDDqXF1aXZhbGVudCBlbiBTUUwgOgoKKyBgaW5uZXJfam9pbih4LCB5KWAgOiBgU0VMRUNUICogRlJPTSB4IEpPSU4geSBPTiB4LmEgPSB5LmFgCisgYGxlZnRfam9pbih4LCB5KWAgOiBgU0VMRUNUICogRlJPTSB4IExFRlQgSk9JTiB5IE9OIHguYSA9IHkuYWAKKyBgcmlnaHRfam9pbih4LCB5KWAgOiBgU0VMRUNUICogRlJPTSB4IFJJR0hUIEpPSU4geSBPTiB4LmEgPSB5LmFgCisgYGZ1bGxfam9pbih4LCB5KWAgOiBgU0VMRUNUICogRlJPTSB4IEZVTEwgSk9JTiB5IE9OIHguYSA9IHkuYWAKKyBgc2VtaV9qb2luKHgsIHkpYCA6IGBTRUxFQ1QgKiBGUk9NIHggV0hFUkUgRVhJU1RTIChTRUxFQ1QgMSBGUk9NIHkgV0hFUkUgeC5hID0geS5hKWAKKyBgYW50aV9qb2luKHgsIHkpYCA6IGBTRUxFQ1QgKiBGUk9NIHggV0hFUkUgTk9UIEVYSVNUUyAoU0VMRUNUIDEgRlJPTSB5IFdIRVJFIHguYSA9IHkuYSlgCgoKCjxmb250IGNvbG9yPSIjY2YwNTA1Ij4qKlBvdXIgY2hlcmNoZXIgcGx1cyBkZSBmb25jdGlvbnMsIGxlIG1pZXV4IGVzdCBlbmNvcmUgZGUgY29uc3VsdGVyIGxlIFttYW51ZWwgZGUgZGJwbHlyXShodHRwczovL2RicGx5ci50aWR5dmVyc2Uub3JnL3JlZmVyZW5jZS9pbmRleC5odG1sKS4qKjwvZm9udD4KCgoKCiMjIFV0aWxpc2VyIFNRTCBkYW5zIGRwbHlyCgpJbCBlc3QgcG9zc2libGUgZCd1dGlsaXNlciBkZXMgY29tbWFuZGVzIFNRTCBkaXJlY3RlbWVudCBkYW5zIGxhIHN5bnRheGUgZHBseXIgbG9yc3F1J3VuIGJlc29pbiBuJ2VzdCBwYXMgY291dmVydCBwYXIgdW5lIGNvbW1hbmRlIGRwbHlyLgoKSWwgeSBhIHRyb2lzIG1hbmnDqHJlcyBkZSBmYWlyZSA6IGF2ZWMgZGVzIGZvbmN0aW9ucyBTUUwsIGF2ZWMgZGVzIG9ww6lyYXRldXJzIFNRTCwgYXZlYyBkZXMgYm91dHMgZGUgY29kZSBTUUwuCgojIyMgRm9uY3Rpb25zCgpTaSB1bmUgZm9uY3Rpb24gbidlc3QgcGFzIHJlY29ubnVlIHBhciBSLCBlbGxlIHZhIMOqdHJlIGxhaXNzw6llIHRlbGxlIHF1ZWxsZSBwb3VyIE9yYWNsZSwgY2UgcXVpIGVzdCB0csOocyBwcmF0aXF1ZS4KQSB2cmFpIGRpcmUsIGMnw6l0YWl0IGxlIGNhcyBkZSBsYSBmb25jdGlvbiBgTlZMYCBwcsOpc2VudMOpZSBbY2ktZGVzc3VzXSgjTlZMKS4KCjxmb250IGNvbG9yPSIjNzM3MzczIj5FeGVtcGxlIDogbGEgZm9uY3Rpb24gYENPTkNBVGAgcG91ciBjb25jYXTDqW5lciBuJ2V4aXN0ZSBwYXMgZW4gUiBtYWlzIGVuIE9yYWNsZSA6PC9mb250PgoKYGBge3J9CnRibChjb25uLCAiVF9NQ08xNUMiKSAlPiUgbXV0YXRlKGFub19yZXRvdXIgPSBDT05DQVQoTklSX1JFVCwgTkFJX1JFVCkpICU+JSBzaG93X3F1ZXJ5CmBgYAoKTCdpbmNvbnbDqW5pZW50IGRlIGNlcyBmb25jdGlvbnMgZXN0IHF1J2VsbGVzIG5lIHMnYXV0by1jb21wbMOodGVudCBwYXMgZW4gYXBwdXlhbnQgc3VyIFRBQiBldCBxdSdvbiBuZSBkaXNwb3NlIHBhcyBkJ3VuZSBkb2N1bWVudGF0aW9uIGludMOpZ3LDqWUuCklsIGZhdXQgdXRpbGlzZXIgc29uIG5hdmlnYXRldXIgcG91ciBsZXMgZMOpY291dnJpciBldCBhcHByZW5kcmUgw6AgbGVzIHV0aWxpc2VyLiBbRXhlbXBsZSBkZSBkb2N1bWVudGF0aW9uIHBvdXIgYENPTkNBVGBdKGh0dHBzOi8vZG9jcy5vcmFjbGUuY29tL2NkL0IxOTMwNl8wMS9zZXJ2ZXIuMTAyL2IxNDIwMC9mdW5jdGlvbnMwMjYuaHRtKS4KCiMjIyBPcMOpcmF0ZXVycwoKU2kgdW4gb3DDqXJhdGV1ciBlbnRyZSBgJWAgbidlc3QgcGFzIHJlY29ubnUgcGFyIFIsIGlsIHZhIMOqdHJlIGxhaXNzw6kgdGVsIHF1ZWwgZXQgT3JhY2xlIHBvdXJyYSBsZSBjb21wcmVuZHJlLgoKPGZvbnQgY29sb3I9IiM3MzczNzMiPkV4ZW1wbGUgc3VyIHVuIGZpbHRyZSBjbGFzc2lxdWUgcG91ciByZXRpcmVyIGxlcyBzw6lqb3VycyBkZSBNQ08gYXZlYyB1bmUgZXJyZXVyIHN1ciBsZSBOSVIsIG9uIHV0aWxpc2UgYCV8fCVgIHF1aSB2YSBkZXZlbmlyIHNpbXBsZW1lbnQgYHx8YCBldCBxdWkgcGVybWV0IGxhIGNvbmNhdMOpbmF0aW9uIGRlIHBsdXNpZXVycyB2YXJpYWJsZXMgOiA8L2ZvbnQ+CgpgYGB7cn0KbWNvYyAlPiUKICBtdXRhdGUoYW5vX3JldG91ciA9IE5JUl9SRVQgJXx8JSBOQUlfUkVUICV8fCUgU0VYX1JFVCAlfHwlCiAgU0VKX1JFVCAlfHwlIEZIT19SRVQgJXx8JSBQTVNfUkVUICV8fCUgREFUX1JFVCAlfHwlIENPSF9OQUlfUkVUICV8fCUgQ09IX1NFWF9SRVQpICU+JQogIGZpbHRlcihhbm9fcmV0b3VyID09ICIwMDAwMDAwMDAiKSAlPiUKICB0YWxseSAlPiUKICBzaG93X3F1ZXJ5ICU+JQogIGNvbGxlY3QKYGBgCgpMJ2V4ZW1wbGUgbGUgcGx1cyBwcmF0aXF1ZSBlc3QgbCdvcMOpcmF0ZXVyIGBMSUtFYCBxdWkgcGVybWV0IGQndXRpbGlzZXIgbGVzIFtleHByZXNzaW9ucyByw6lndWxpw6hyZXNdKGh0dHBzOi8vZnIud2lraXBlZGlhLm9yZy93aWtpL0V4cHJlc3Npb25fciVDMyVBOWd1bGklQzMlQThyZSkgKHJlZ2V4KSBPcmFjbGUgcG91ciBkw6ljcmlyZSBkZXMgY2hhw65uZXMgZGUgY2FyYWN0w6hyZXMgY29tcGxleGVzLgpTaSBvbiB2ZXV0IHF1ZSBgUFJTX05BVF9SRUZgIGNvbW1lbmNlIHBhciAzMSA6CgpgYGB7cn0KZXJwcnNmICU+JSBmaWx0ZXIoUFJTX05BVF9SRUYgJUxJS0UlICIzMSUiKSAlPiUgc2hvd19xdWVyeQpgYGAKCjxzcGFuIHN0eWxlPSJmb250LXNpemU6MjAwJTtkaXNwbGF5OmlubGluZS1ibG9jazsgdmVydGljYWwtYWxpZ246bWlkZGxlOyBtYXJnaW4tYm90dG9tOjBweDsiPuKaoDwvc3Bhbj4gbGVzIHJlZ2V4IE9yYWNsZSBuZSBmb25jdGlvbm5lbnQgcGFzIGRlIGxhIG3Dqm1lIG1hbmnDqHJlIHF1ZSBsZXMgcmVnZXggY291cmFudGVzIHN1ciBMaW51eC4KCiMjIyBCb3V0cyBkZSBjb2RlIFNRTHsjc3FsfQpPbiBwZXV0IHRvdWpvdXJzIGdsaXNzZXIgZGVzIGJvdXRzIGRlIGNvZGUgU1FMIHMnaWwgbid5IGEgcXVlbHF1ZSBjaG9zZSBxdWUgbCdvbiBuZSBzYWl0IHBhcyBmYWlyZSBlbiBzeW50YXhlIGRwbHlyLgpQb3VyIMOnYSBvbiB1dGlsaXNlcmEgbGEgY29tbWFuZGUgYHNxbGAuCgoKYGBge3J9CmVycHJzZiAlPiUgZmlsdGVyKFBTRV9TUEVfQ09EID09IHNxbCgiQU5ZICgxLDIsMykiKSkgJT4lIHNob3dfcXVlcnkoKQpgYGAKCjxmb250IGNvbG9yPSIjNzM3MzczIj5EYW5zIGNldCBleGVtcGxlLCBvbiBhdXJhaXQgZmFjaWxlbWVudCBwdSByZW1wbGFjZXIgbGEgY29uZGl0aW9uIHBhciBgUFNFX1NQRV9DT0QgJWluJSAoMSwyLDMpYCwgbWFpcyBkYW5zIHF1ZWxxdWVzIGNhcyBsZSBTUUwgZXN0IGxhIHNldWxlIHNvbHV0aW9uLCBlbiBwYXJ0aWN1bGllciBwb3VyIGxlcyBmb25jdGlvbnMgZGUgZmVuw6p0cmFnZS4KCkF1dHJlIGNhcyBkJ3VzYWdlIDogcG91ciDDqXZpdGVyIGRlcyAqaWZlbHNlKiBpbWJyaXF1w6lzLCBvbiB1dGlsaXNlcmEgZGlyZWN0ZW1lbnQgbGEgZm9uY3Rpb24gYFNRTCBDQVNFIFdIRU5gLgpQYXIgZXhlbXBsZSBzaSBvbiB2ZXV0IHJlbXBsaXIgYEJFTl9SRVNfRFBUMjAxNl9DT01QYCBhdmVjIGxlIGTDqXBhcnRlbWVudCBhdSBkw6lidXQgMjAxNiwgYEJFTl9SRVNfRFBUMjAxNmAsIHByw6lhbGFibGVtZW50IGV4dHJhaXQuCkxvcnNxdSdpbCBlc3QgYWJzZW50IG9uIHZhIGNoZXJjaGVyIGRhbnMgMjAxNyAoYEJFTl9SRVNfRFBUMjAxN2ApLCAyMDE1LCAyMDE4LCAyMDE0LCAyMDE5LCAyMDEzLCAyMDIwLgpTaSBvbiBhIHJpZW4gZGFucyB0b3V0ZXMgY2VzIGFubsOpZXMgKHBvdXIgdW4gbm9uIGNvbnNvbW1hbnQpLCBvbiBwcmVuZCBjZXMgaW5mb3MgZGFucyBgSVJfQkVOX1JgIChgQkVOX1JFU19EUFRgKS48L2ZvbnQ+CgpgYGB7cn0KIyBDZSBjb2RlIHZpZW50IGR1IHByb2pldCBiZW5lZmljaWFpcmVzU05EUyAoMDJfcHJvZHVjdGlvbi5SKSwgb8O5IGlsIGZvbmN0aW9ubmUKIyBpcmJlbnI0ICU+JQojICAgbXV0YXRlKEJFTl9SRVNfRFBUMjAxNl9DT01QID0gc3FsKCdDQVNFCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgV0hFTiBOT1QoKCgiQkVOX1JFU19EUFQyMDE2IikgSVMgTlVMTCkpIFRIRU4gKCJCRU5fUkVTX0RQVDIwMTYiKQojICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFdIRU4gTk9UKCgoIkJFTl9SRVNfRFBUMjAxNyIpIElTIE5VTEwpKSBUSEVOICgiQkVOX1JFU19EUFQyMDE3IikKIyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBXSEVOIE5PVCgoKCJCRU5fUkVTX0RQVDIwMTUiKSBJUyBOVUxMKSkgVEhFTiAoIkJFTl9SRVNfRFBUMjAxNSIpCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgV0hFTiBOT1QoKCgiQkVOX1JFU19EUFQyMDE4IikgSVMgTlVMTCkpIFRIRU4gKCJCRU5fUkVTX0RQVDIwMTgiKQojICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFdIRU4gTk9UKCgoIkJFTl9SRVNfRFBUMjAxOSIpIElTIE5VTEwpKSBUSEVOICgiQkVOX1JFU19EUFQyMDE5IikKIyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBXSEVOIE5PVCgoKCJCRU5fUkVTX0RQVDIwMTMiKSBJUyBOVUxMKSkgVEhFTiAoIkJFTl9SRVNfRFBUMjAxMyIpCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgV0hFTiBOT1QoKCgiQkVOX1JFU19EUFQyMDIwIikgSVMgTlVMTCkpIFRIRU4gKCJCRU5fUkVTX0RQVDIwMjAiKQojICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEVMU0UgIkJFTl9SRVNfRFBUIgojICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEVORCcpCmBgYAoKIyMgTGVzIGRhdGVzCgpQYXMgdnJhaW1lbnQgZmFjaWxlIMOgIGfDqXJlci4KTGUgZ3VpZGUgQ05BTSBkb25uZSBsJ2luZGljYXRpb24gcG91ciB1biBmaWx0cmUgZW4gdXRpbGlzYW50IGxhIGZvbmN0aW9uIE9yYWNsZSBgVE9fREFURWAgOgoKYGBge3J9CmlyYmlvciAlPiUKZmlsdGVyKEJJT19DUkVfREFUID49IFRPX0RBVEUoJzIwMTctMDEtMDEnLCd5eXl5LU1NLWRkJykpICU+JQogIGhlYWQoNSkgJT4lCiAgY29sbGVjdApgYGAKCjxzcGFuIHN0eWxlPSJmb250LXNpemU6MjAwJTtkaXNwbGF5OmlubGluZS1ibG9jazsgdmVydGljYWwtYWxpZ246bWlkZGxlOyBtYXJnaW4tYm90dG9tOjBweDsiPuKaoDwvc3Bhbj4gaWwgcydhZ2l0IGRlIGRhdGVIZXVyZS4KRGV1eCBkYXRlcyBwZXV2ZW50IG5lIGRpZmbDqXJlciBxdWUgZGUgcXVlbHF1ZXMgaGV1cmVzLgpMZSByw6lmw6lyZW50aWVsIGRlcyBiw6luw6lmaWNpYWlyZXMgZXN0IHVuIGJvbiBleGVtcGxlIHF1YW5kIG9uIGNvbXBhcmUgbGEgZGF0ZSBkJ2luc2VydGlvbiBkYW5zIGxlIHLDqWbDqXJlbnRpZWwgKGBCRU5fRFRFX0lOU2ApIGV0IGxhIGRhdGUgZGUgbWlzZSDDoCBqb3VyIChgQkVOX0lEVF9NQUpgKSA6CgpgYGB7cn0KaXJiZW5yIDwtIHRibChjb25uLCAiSVJfQkVOX1IiKQoKaXJiZW5yICU+JQogIHNlbGVjdChCRU5fRFRFX0lOUywgQkVOX0lEVF9NQUopICU+JQogIGZpbHRlcihCRU5fRFRFX0lOUyA9PSBUT19EQVRFKCcyMDIwLTAzLTExJywneXl5eS1NTS1kZCcpCiAgICAgICAgJiBCRU5fSURUX01BSiA8IFRPX0RBVEUoJzIwMjAtMDMtMTInLCd5eXl5LU1NLWRkJykKICAgICAgICAgKSAlPiUKICBtdXRhdGUoaWRlbnRpcXVlcyA9IGlmZWxzZShCRU5fRFRFX0lOUyA9PSBCRU5fSURUX01BSiwgMSwgMCkpICU+JQogIGhlYWQoNSkgJT4lCiAgY29sbGVjdApgYGAKKlJlbWFycXVlIDEgOiogaWwgeSBhIHVuIHByb2Jsw6htZSBkZSBmdXNlYXUgaG9yYWlyZSDDoCBsJ2FmZmljaGFnZSBkZXMgcsOpc3VsdGF0cyAoMjAyMC0wMy0xMCAyMzowMDowMCBldCBub24gMjAyMC0wMy0xMSAwMDowMDowMCkuIENlIHByb2Jsw6htZSBzZSB0cm91dmUgZHUgY8O0dMOpIGRlIGwnYWZmaWNoYWdlIGVuIFIgZXQgbm9uIGR1IGPDtHTDqSBkJ09yYWNsZSBjYXIgbGUgZmlsdHJlIGEgYmllbiBwb3J0w6kgc3VyIGxlIDExIG1hcnMgZXQgbm9uIGxlIDEwLgoKKlJlbWFycXVlIDIgOiogcG91ciBjZXMgbGlnbmVzIGwnaW5zZXJ0aW9uIGV0IGxhIG1pc2Ugw6Agam91ciBvbnQgw6l0w6kgZmFpdGVzIGxlIG3Dqm1lIGpvdXIgbWFpcyDDoCBkZXMgaGV1cmVzIGRpZmbDqXJlbnRlcywgZCdvw7kgbGEgY29sb25uZSBgaWRlbnRpcXVlc2Agw6AgYDBgLgpJbCBmYXV0IGRvbmMgw6p0cmUgdmlnaWxhbnQgcXVhbmQgb24gY29tcGFyZSBkZXMgZGF0ZXMgc2kgbCdvbiBuZSBzb3VoYWl0ZSB1bmUgcHLDqWNpc2lvbiBxdSdhdSBqb3VyIHByw6hzLgoKIyMjIERlIGRhdGVIZXVyZSDDoCBkYXRlCgpQb3VyIHBlcmRyZSBsZSBkw6l0YWlsIGRlIGwnaGV1cmUsIG9uIHBldXQgdXRpbGlzZXIgbGEgZm9uY3Rpb24gU1FMIGBUUlVOQ2AgOgoKYGBge3J9CmlyYmVuciAlPiUKICBmaWx0ZXIoQkVOX0RURV9JTlMgPT0gVE9fREFURSgnMjAyMC0wMy0xMScsJ3l5eXktTU0tZGQnKQogICAgICAgICYgQkVOX0lEVF9NQUogPCBUT19EQVRFKCcyMDIwLTAzLTEyJywneXl5eS1NTS1kZCcpCiAgICAgICAgICkgJT4lCiAgc2VsZWN0KEJFTl9JRFRfTUFKKSAlPiUKICBtdXRhdGUoQkVOX0lEVF9NQUpfdHJ1bmMgPSBUUlVOQyhCRU5fSURUX01BSikpICU+JSAjIMOpcXVpdmFsZW50IDoKICAjIG11dGF0ZShCRU5fSURUX01BSl90cnVuYyA9IHNxbCgiVFJVTkMoQkVOX0lEVF9NQUopIikpICU+JQogIGhlYWQoNSkgJT4lCiAgY29sbGVjdApgYGAKUG91ciB1bmUgY29tcGFyYWlzb24gZGUgZGV1eCBkYXRlcywgdW5lIGF1dHJlIHNvbHV0aW9uIGF1cmFpdCDDqXTDqSBkZSBzJ2Fzc3VyZXIgcXVlIGxhIHZhbGV1ciBhYnNvbHVlIGRlIGxhIGRpZmbDqXJlbmNlIGVzdCBpbmbDqXJpZXVyZSDDoCAxIChgYWJzKEJFTl9EVEVfSU5TIC0gQkVOX0lEVF9NQUopPDFgKSBtYWlzIGNlbGEgbmUgcmV2aWVudCBwYXMgZXhhY3RlbWVudCBhdSBtw6ptZSBjYXIgb24gdsOpcmlmaWUgYWxvcnMgcXVlIG1vaW5zIGRlIDI0aCBzZSBzb250IMOpY291bMOpZXMgZW50cmUgbGVzIGRldXggZGF0ZXMgZXQgbm9uIHF1J2VsbGVzIHRvbWJlbnQgc3VyIGxlIG3Dqm1lIGpvdXIuCgojIyMgRXh0cmFpcmUgbGUgam91ciwgbGUgbW9pcywgbCdhbm7DqWUKCklsIGZhdXQgZGUgbm91dmVhdSByZWNvdXJpciDDoCBsYSBmb25jdGlvbiBPcmFjbGUgZMOpZGnDqWUsIGBFWFRSQUNUYCA6CgpgYGB7cn0KcmVzIDwtIGlyYmVuciAlPiUKICBzZWxlY3QoQkVOX0RURV9JTlMpICU+JQogIGZpbHRlcihCRU5fRFRFX0lOUyA9PSBUT19EQVRFKCcyMDIwLTAzLTExJywneXl5eS1NTS1kZCcpKSAlPiUKICBoZWFkKDEpICU+JQogIG11dGF0ZShBTk5FRSA9IHNxbCgiRVhUUkFDVChZRUFSIEZST00gQkVOX0RURV9JTlMpIiksIE1PSVMgPSBzcWwoIkVYVFJBQ1QoTU9OVEggRlJPTSBCRU5fRFRFX0lOUykiKSkgJT4lCiAgY29sbGVjdCgpCnJlcwpgYGAKT24gcGV1dCBleHRyYWlyZSBsZXMgw6lsw6ltZW50cyBzdWl2YW50cyA6CgorICpZRUFSKgorICpNT05USCoKKyAqREFZKgorICpIT1VSKgorICpNSU5VVEUqCisgKlNFQ09ORCoKKyAqVElNRVpPTkVfSE9VUioKKyAqVElNRVpPTkVfTUlOVVRFKgorICpUSU1FWk9ORV9SRUdJT04qCisgKlRJTUVaT05FX0FCQlIqCgoKIyMgQW5hbG9naWUgZW50cmUgZGJwbHlyIGV0IFNwYXJrUgoKSWwgeSBhIHVuZSBjbGFpcmUgYW5hbG9naWUgZW50cmUgU3BhcmtSIGV0IFJPcmFjbGUuIERhbnMgbGVzIGRldXggY2FzIFIgZW52b2llIGRlcyByZXF1w6p0ZXMgw6AgdW4gbG9naWNpZWwgZXh0ZXJuZS4KTGVzIHBhcXVldHMgU3BhcmsqKlIqKiBldCAqKlIqKk9yYWNsZSBmb250IGxhIHRyYWR1Y3Rpb24sIFNwYXJrIG91IE9yYWNsZSBmb250IGxlIHRyYXZhaWwuCkVuc3VpdGUgbGUgcsOpc3VsdGF0IGVzdCByYXBhdHJpw6kgZW4gUi4KCkwnYW5hbG9naWUgdmEgcGx1cyBsb2luIHB1aXNxdWUgU3BhcmtSIGEgcmVwcmlzIGxlIHZvY2FidWxhaXJlIGRlIGRwbHlyIChgc2VsZWN0YCwgYGZpbHRlcmAuLi4pLCBjZSBxdWkgZXN0IGdsb2JhbGVtZW50IGFzc2V6IHByYXRpcXVlLgpJbCB5IGEgcXVlbHF1ZXMgc3VidGlsaXTDqXMgcXUnb24gZMOpY291dnJlIHBhciBsYSBwcmF0aXF1ZS4KUGFyIGV4ZW1wbGUgbGUgYGhlYWRgIGRlIGRicGx5ciBuZSByYXBhdHJpZSBwYXMgZW4gUiDDoCBsYSBkaWZmw6lyZW5jZSBkZSBTcGFya1IgcXVpIGludMOoZ3JlIHVuIGBjb2xsZWN0YCBkYW5zIHNvbiBgaGVhZGAuCkF1dHJlIHN1YnRpbGl0w6ksIGxlcyBwcmVtacOocmVzIGxpZ25lcyBzb250IHRvdWpvdXJzIGxlcyBtw6ptZXMgYXZlYyBPcmFjbGUsIGMnZXN0LcOgLWRpcmUgcXVlIGRldXggYGhlYWRgIGRvbm5lbnQgbGUgbcOqbWUgcsOpc3VsdGF0LgpFbiBTcGFyayBsZXMgbGlnbmVzIG5lIHNvbnQgcGFzIHZyYWltZW50IG9yZG9ubsOpZXMgZXQgZGV1eCBgaGVhZGAgZG9ubmVudCBkZXMgcsOpc3VsdGF0cyBkaWZmw6lyZW50cy4KCkVuIFNwYXJrUiwgaWwgZmF1dCBub21tZXIgbGVzIGNvbG9ubmVzIHZpYSBgZG9ubmVlcyRjb2xvbm5lYCBhbG9ycyBxdSdvbiBwZXV0IHV0aWxpc2VyIGBjb2xvbm5lYCBlbiBkYnBseXIuCgpFbiBTcGFya1IsIG9uIHBldXQgdXRpbGlzZXIgbGVzIGZvbmN0aW9ucyB0ZW1wb3JlbGxlcyBwcmF0aXF1ZXMgYHllYXJgIG91IGBtb250aGAgcXVlIGwnb24gbmUgdHJvdXZlIHBhcyBlbiBST3JhY2xlIG/DuSBpbCBmYXVkcmEgcGFyIGV4ZW1wbGUgcGFzc2VyIHBhciBgc3FsKCJFWFRSQUNUKFlFQVIgRlJPTSBCRU5fRENEX0RURSkiKWAuCgojIyBVbiBub20gZGUgdmFyaWFibGUgdmFyaWFibGUKCkxvcnNxdWUgbCdvbiBzb3VoYWl0ZSB1dGlsaXNlciB1bmUgdmFyaWFibGUgYXUgbGlldSBkdSBub20gZW4gZHVyIGQndW5lIHZhcmlhYmxlLCBvbiBwZXV0IHV0aWxpc2VyIGxlcyBvcMOpcmF0ZXVycyBkcGx5ciBgISFgICgqYmFuZy1iYW5nIG9wZXJhdG9yKikgZXQgYCEhIWAgKCpiaWcgYmFuZyBvcGVyYXRvciopLCBxdWkgc2VydmVudCDDoCAqKnByw6ktw6l2YWx1ZXIqKiB1biBvYmpldC4KCjxmb250IGNvbG9yPSIjNzM3MzczIj5BdSBsaWV1IGRlIDwvZm9udD4KCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQpmYXV4ICU+JSBzdW1tYXJpemUobW95ID0gbWVhbih4KSkKYGBgCkplIHBldXggdXRpbGlzZXIgOgpgYGB7ciwgd2FybmluZz1GQUxTRX0Kbm9tVmFyaWFibGUgPC0gIngiCmZhdXggJT4lIHN1bW1hcml6ZShtb3kgPSBtZWFuKCEhc3ltKG5vbVZhcmlhYmxlKSkpCmBgYApDZSBxdWkgcGV1dCDDqnRyZSBwcmF0aXF1ZSBwb3VyIGJvdWNsZXIgc3VyIGRlcyBjb2xvbm5lcy4KClVuZSB2YXJpYW50ZSBwb3VyIGNlcnRhaW5zIGNhcyBzaW1wbGVzIGNvbnNpc3RlIMOgIHV0aWxpc2VyIGxhIGZvbmN0aW9uIGBzcWwoKWAgcXVpIGluamVjdGUgZHUgY29kZSBTUUwgKHByw6lzZW50w6llIFtpY2ldKCNzcWwpKToKCmBgYHtyfQpub21WYXJpYWJsZSA8LSAiWiIKZmF1eCAlPiUKICBtdXRhdGUoWiA9IHopICU+JSAjIGNvbnRyYWludGUgc3VwcGzDqW1lbnRhaXJlIDoKICAjIHNxbCB2YSBwYXNzZXIgbGUgY29kZSBlbiBtYWp1c2N1bGVzLCBpbCBmYXV0IGRvbmMgZGVzIG5vbXMKICAjIGRlIGNvbG9ubmUgZW4gbWFqdXNjdWxlcyBwb3VyIHF1ZSDDp2EgZm9uY3Rpb25uZQogIGdyb3VwX2J5KGdycCA9IHNxbChub21WYXJpYWJsZSkpICU+JSB0YWxseQpgYGAKCgpTaSB2b3VzIMOqdGVzIGhhYml0dcOpIMOgIHV0aWxpc2VyIGBnZXQoKWAgcG91ciBjZSBnZW5yZSBkJ3VzYWdlcywgY2VsYSBuZSBmb25jdGlvbm5lcmEgcGFzIGljaSBjYXIgbGUgYGdldGAgc2VyYSB0cmFuc21pcyDDoCBPcmFjbGUgOgpgYGB7ciwgZXJyb3I9VFJVRX0KZmF1eCAlPiUgc3VtbWFyaXplKG1veSA9IG1lYW4oZ2V0KG5vbVZhcmlhYmxlKSkpICU+JSBzaG93X3F1ZXJ5ICU+JSBjb2xsZWN0CmBgYAoKPGZvbnQgY29sb3I9IiM3MzczNzMiPkV4ZW1wbGUgcsOpZWwgcG91ciBleHBvcnRlciBsZXMgZWZmZWN0aWZzIGRlIGNoYXF1ZSBwYXRob2xvZ2llIGRlIGxhIGNhcnRvZ3JhcGhpZSBkZXMgcGF0aG9sb2dpZXMgZW4gMjAxOCAoMzBzKS48L2ZvbnQ+CgpgYGB7ciwgZXZhbD1GQUxTRX0KY3RpbmQgPC0gdGJsKGNvbm4sIGluX3NjaGVtYSgiTUVQU0dQXzE1NiIsICJFU05EX0NUX0lORF9HOF8yMDE2IikpCgojIExlcyB2YXJpYWJsZXMgcXVpIG5vdXMgaW50w6lyZXNzZW50IGNvbW1lbmNlbnQgcGFyIGxlIG1vdCBUT1Agb3UgU1VQCnRvcHMgPC0gY29sbmFtZXMoY3RpbmQpW2NvbG5hbWVzKGN0aW5kKSAlbGlrZSUgIl4oVE9QfFNVUCkiXQoKIyBPbiB2YSBlbnJlZ2lzdHJlciBsZXMgcsOpc3VsdGF0cyBkYW5zIHVuZSBsaXN0ZSBxdSdvbiBpbml0aWUKbGlzdGUgPC0gbGlzdCgpCgpmb3IgKHRvcCBpbiB0b3BzKXsKICBwcmludCh0b3ApCiAgcHJvdiA8LSBjdGluZCAlPiUKICAgIGZpbHRlcighIXN5bSh0b3ApID09IDEpICU+JSAjIHRlY2huaXF1ZSBwb3VyIGF2b2lyIHVuIG5vbSBkZSBjb2xvbm5lIHZhcmlhYmxlCiAgICB0YWxseSAlPiUKICAgIG11dGF0ZSh0b3AgPSB0b3ApICU+JSAjIHJlbm9tbWVyIGxhIGNvbG9ubmUgYXZlYyBsZSBub20gZGUgbGEgcGF0aG9sb2dpZQogICAgY29sbGVjdCAlPiUKICAgIHNldERUCiAgbGlzdGUgPC0gYXBwZW5kKGxpc3RlLCBsaXN0KHByb3YpKSAjIGFqb3V0ZXIgw6AgbGEgbGlzdGUKfQp0YWIgPC0gcmJpbmRsaXN0KGxpc3RlKSAjIGVtcGlsZXIgbGEgbGlzdGUKc2V0Y29sb3JkZXIodGFiLCAidG9wIikgIyByw6lvcmRvbm5lciBsZXMgY29sb25uZXMgYXZlYyB0b3AgZW4gcHJlbWllcgoKIyBFY3JpdHVyZSBwb3VyIGV4cG9ydAojIGZ3cml0ZSh0YWIsICJ+L0VYUE9SVC9lZmZlY3RpZnNfY2FydG9QYXRob3MuY3N2IikKdGFiWzE6MTAsXQpgYGAKClBvdXIgZMOpcGxveWVyICoqcGx1c2lldXJzKiogdmFyaWFibGVzIGRhbnMgdW5lIGZvbmN0aW9uLCBpbCBmYXVkcmEgc2UgdG91cm5lciB2ZXJzIGxlICpiaWctYmFuZyBvcGVyYXRvciosIGAhISFgICA6CmBgYHtyfQp2YXJpYWJsZXMgPC0gYygieCIsICJ6IikKZmF1eCAlPiUgc2VsZWN0KCEhIXZhcmlhYmxlcykKYGBgCgojIyBGb3JjZXIgbCfDqXZhbHVhdGlvbiBlbiBSCgpMYSBsaWduZSBkZSBwYXJ0YWdlIGVudHJlIGNlIHF1aSBlc3Qgw6l2YWx1w6kgZW4gUiBldCBjZSBxdWkgZXN0IMOpdmFsdcOpIGVuIFNRTCBlc3QgdMOpbnVlLiBMYSBwcmV1dmUgcGFyIGwnZXhlbXBsZSA6IGRhbnMgbGEgY29tbWFuZGUgc3VpdmFudGUgaWQgZXN0IGJpZW4gcmVtcGxhY8OpIHBhciBzYSB2YWxldXIsIHRvdXQgZm9uY3Rpb25uZSA6IAoKYGBge3J9CmlkIDwtIDMKZmF1eCAlPiUgZmlsdGVyKHggPT0gaWQpCmBgYApBbG9ycyBxdWUgc2kgb24gY2hlcmNoZSDDoCBwcmVuZHJlIHVuIMOpbMOpbWVudCBkYW5zIHVuIHZlY3RldXIsIGlsIGZhaXQgdW5lIHRyYWR1Y3Rpb24gU1FMIMOpdHJhbmdlIGF2ZWMgdW4gYENBU0UgV0hFTmAgcXVpIGfDqW7DqHJlIHVuZSBlcnJldXIgOgoKYGBge3IsIGVycm9yPVRSVUV9CmlkcyA8LSAzOjQKZmF1eCAlPiUgZmlsdGVyKHggPT0gaWRzWzFdKSAlPiUgc2hvd19xdWVyeSAlPiUgY29sbGVjdApgYGAKCkxhIHByw6ktw6l2YWx1YXRpb24gcGFyIGwnb3DDqXJhdGV1ciAqYmFuZy1iYW5nKiBgISFgIG5vdXMgc29ydGlyYSBkZSBjZSBtYXV2YWlzIHBhcyA6CgpgYGB7cn0KaWRzIDwtIDM6NApmYXV4ICU+JSBmaWx0ZXIoeCA9PSAhIWlkc1sxXSkgJT4lIHNob3dfcXVlcnkgJT4lIGNvbGxlY3QKYGBgCgpDZWxhIHZhdXQgYXVzc2kgcG91ciBkZXMgZm9uY3Rpb25zIHF1aSBnw6luw6lyZXJhaXQgZHUgY29kZSBTUUwgOgoKYGBge3IsIGVycm9yID0gVFJVRX0KY29udGVudVNRTCA8LSBmdW5jdGlvbigpIHNxbCgnUE9XRVIoIngiLCAyLjApJykKZmF1eCAlPiUgbXV0YXRlKHgyID0gY29udGVudVNRTCgpKSAjIEVSUkVVUgpmYXV4ICU+JSBtdXRhdGUoeDIgPSAhIWNvbnRlbnVTUUwoKSkgIyBGT05DVElPTk5FCmBgYAoKCiMgVGFibGVzIGV0IHZ1ZXMKCklsIG4neSBhIHBhcyBkZSBtYW5pw6hyZSBncmFwaGlxdWUgcG91ciBuYXZpZ3VlciBkYW5zIGxlcyB0YWJsZXMgZGlzcG9uaWJsZXMgY29tbWUgc3VyIFNBUy4KU2kgb24gY29ubmFpdCBsZSBub20gZGUgbGEgdGFibGUsIG9uIGwnYXBwZWxsZSBkaXJlY3RlbWVudC4KU2lub24sIHBvdXIgcmV0cm91dmVyIHVuZSB0YWJsZSwgb24gcGV1dCB0b3Vqb3VycyBhbGxlciBjaGVyY2hlciBzb24gbm9tIGRhbnMgU0FTLgpTaSBsJ29uIG5lIHNvdWhhaXRlIHBhcyBsYW5jZXIgU0FTLCBvbiBwZXV0IHRvdWpvdXJzIHV0aWxpc2VyIGxlcyBjb21tYW5kZXMgY2ktZGVzc291cy4KCk4uQi4gOiBsYSBwbHVwYXJ0IGRlcyBub21zIGRlIHRhYmxlcyBxdWUgbCdvbiBjb25uYcOudCBuZSByZW52b2llbnQgcGFzIHZyYWltZW50IMOgIGRlcyAqKnRhYmxlcyoqIG1haXMgw6AgZGVzICoqdnVlcyoqLCBjJ2VzdC3DoC1kaXJlIGRlcyBlbnJlZ2lzdHJlbWVudHMgZGUgcmVxdcOqdGVzLCBvdSDDoCBkZXMgKipzeW5vbnltZXMqKiwgYydlc3Qtw6AtZGlyZSBkZXMgc3Vybm9tcy4KCiMjIExpc3RlciBsZXMgdGFibGVzIHB1YmxpcXVlcwoKRGFucyB1bmUgYmFzZSBPcmFjbGUsIGlsIGV4aXN0ZSBkZXMgdGFibGVzIHNww6ljaWFsZXMgcXVpIGxpc3RlbnQgbGVzIG9iamV0cyA6CgorIGBhbGxfdGFibGVzYCBsaXN0ZSBsZXMgKip0YWJsZXMqKiA7CisgYGFsbF92aWV3c2AgbGlzdGUgbGVzICoqdnVlcyoqIDsKKyBgYWxsX3N5bm9ueW1zYCBsZXMgKipzeW5vbnltZXMqKiA7CisgYGFsbF9vYmplY3RzYCB0b3VzIGxlcyAqKm9iamV0cyoqICh0YWJsZXMsIHZ1ZXMsIHN5bm9ueW1lcywgaW5kZXgsIHPDqXF1ZW5jZXMsIMOpZGl0aW9ucywgcGFxdWV0cy4uLikuCgpKZSBkw6ljb25zZWlsbGUgbGUgY2hhcmdlbWVudCBlbiBtw6ltb2lyZSBkJ3VuZSBkZSBzZXMgdGFibGVzLCBhc3NleiBsb25nLgpQYXIgZXhlbXBsZSwgbGEgdGFibGUgYGFsbF90YWJsZXNgIHNlIGNoYXJnZSBlbiAxNSBtaW51dGVzIGFsb3JzIHF1ZSBsYSBwbHVwYXJ0IGRlcyBvYmpldHMgcXVpIG5vdXMgaW50w6lyZXNzZW50IChkZXMgdnVlcyBldCBkZXMgc3lub255bWVzKSBlbiBzb250IGFic2VudHMuCgpNaWV1eCB2YXV0IGZhaXJlIGxhIHJlY2hlcmNoZSBkaXJlY3RlbWVudCBlbiBPcmFjbGUgc3VyIGxhIHRhYmxlIGBhbGxfb2JqZWN0c2AuCkltYWdpbm9ucyBxdWUgbCdvbiBjaGVyY2hlIHRvdXMgbGVzIG9iamV0cyBxdWkgY29udGllbm5lbnQgYENUX0lORGAgcG91ciBsYSB0YWJsZSBjZW50cmFsZSBkZSBsYSBjYXJ0b2dyYXBoaWUgZGVzIHBhdGhvbG9naWVzLgoKYGBge3J9CnN5c3RlbS50aW1lKHsKICB0YWJsZXMgPC0gZGJHZXRRdWVyeShjb25uLAogICJTRUxFQ1Qgb3duZXIsIG9iamVjdF9uYW1lLCBvYmplY3RfaWQsIG9iamVjdF90eXBlLCBjcmVhdGVkIEZST00gYWxsX29iamVjdHMgV0hFUkUgb2JqZWN0X25hbWUgTElLRSAnJUNUX0lORCUnIikKICAjICclQ1RfSU5EJScgZXN0IHVuZSBleHByZXNzaW9uIHLDqWd1bGnDqHJlIE9yYWNsZSBvw7kgJSBlc3QgcmVtcGxhY8OpIHBhciBhdXRhbnQgZGUgY2FyYWN0w6hyZXMgcXVlIG7DqWNlc3NhaXJlCiAgfSkgIyA4IHNlY29uZGVzCnRhYmxlcyRPV05FUiA8LSBuZXR0b3kodGFibGVzJE9XTkVSKSAjIHJldHJhaXQgZGUgbCdpZGVudGlmaWFudCB1dGlsaXNhdGV1cgp0YWJsZXNbLCBjKCJPV05FUiIsICJPQkpFQ1RfTkFNRSIsICJPQkpFQ1RfVFlQRSIpXQpgYGAKCkNlcnRlcyBhdmVjIFIsIG5vdXMgbidhdm9ucyBwYXMgbCdhcmJvcmVzY2VuY2UgY29tbWUgZGFucyBTQVMgcG91ciBjaGVyY2hlciBsZXMgdGFibGVzIG1haXMgY2VsYSBlc3QgYXVzc2kgdW4gKiphdG91dCoqLgpQYXIgZXhlbXBsZSBpY2kgaidhaSBwdSBsaXN0ZXIgdG91dGVzIGxlcyB0YWJsZXMgcmVsYXRpdmVzIMOgIGxhIGNhcnRvZ3JhcGhpZSBhbG9ycyBxdWUgbGVzIGFuY2llbm5lcyB2ZXJzaW9ucyBzZSB0cm91dmVudCBkYW5zIE9SQVZVRSBldCBsZXMgbm91dmVsbGVzIGRhbnMgT1JBTUVQUy4gU3VyIFNBUywgaWwgZmF1dCBzYXZvaXIgb8O5IGNoZXJjaGVyIHBvdXIgZMOpcm91bGVyIGxhIGJvbm5lIGJpYmxpb3Row6hxdWUuCgpPbiBkw6ljb3V2cmUgYXVzc2kgbGUgdHlwZSBkZSBsJ29iamV0IGV0IG9uIHBldXQgYWpvdXRlciBzYSBkYXRlIGRlIGNyw6lhdGlvbiAoYGNyZWF0ZWRgKS4KCiMjIExpc3RlciBsZXMgdGFibGVzIHByaXbDqWVzClBvdXIgbGlzdGVyIGxlcyB0YWJsZXMgZGUgbCd1dGlsaXNhdGV1ciwgaWwgc3VmZml0IGQndXRpbGlzZXIgYHVzZXJfdGFibGVzYCBhdSBsaWV1IGRlIGBhbGxfdGFibGVzYC4KRW4gbCdvY2N1cnJlbmNlLCBpbCBzJ2FnaXJhIG5vcm1hbGVtZW50IGRlICoqdGFibGVzKiogZXQgbm9uIGRlIHZ1ZXMgZXQgc3lub255bWVzIGRvbmMgaWwgbidlc3QgcGFzIG7DqWNlc3NhaXJlIGQndXRpbGlzZXIgYHVzZXJfb2JqZWN0c2AuCgpgYGB7cn0Kc3lzdGVtLnRpbWUoe3VzZXJfdGFibGVzIDwtIGRiR2V0UXVlcnkoY29ubiwgIlNFTEVDVCBUQUJMRV9OQU1FIEZST00gdXNlcl90YWJsZXMiKX0pICMgMgppZiAobnJvdyh1c2VyX3RhYmxlcykgPT0gMSkgewogIHVzZXJfdGFibGVzCn0gZWxzZSB7CiAgd2FybmluZygiSWwgeSBhIGQnYXV0cmVzIHRhYmxlcyBxdWUgbGUgZmF1eCBqZXUgZGUgZG9ubsOpZXMsIG9uIGFmZmljaGUgcmllbi4iKQp9CmBgYAoKTGVzIHRhYmxlcyBsaXN0w6llcyBpY2kgc2UgdHJvdXZlbnQgZGFucyBsYSBiaWJsaW90aMOocXVlIE9SQVVTRVIgc3VyIFNBUy4gT24gcmV0cm91dmUgYmllbiBsYSB0YWJsZSBgUFJPVmAgcXVpIGVzdCBub3RyZSBmYXV4IGpldSBkZSBkb25uw6llcy4KCiMjIENoYXJnZXIgdW5lIHRhYmxlIGQndW4gYXV0cmUgc2Now6ltYQoKRW4gT3JhY2xlLCB1biBzY2jDqW1hIGVzdCB1biBlbnNlbWJsZSBkJ29iamV0cyBkJ3VuZSBiYXNlIGRlIGRvbm7DqWVzLgpVbiBzY2jDqW1hIGNvcnJlc3BvbmQgw6AgdW4gcHJvcHJpw6l0YWlyZSAoKm93bmVyKikgZXQgYSBsZSBtw6ptZSBub20gcXVlIGx1aS4KRXN0LWNlIHV0aWxlIGRlIHNhdm9pciBjZWxhID8gT3VpIGNhciBzaSB2b3VzIHZvdWxleiB0cmF2YWlsbGVyIHN1ciBsJ0VTTkQsIHZvdXMgYWxsZXogYXZvaXIgdW4gcHJvYmzDqG1lIDoKCmBgYHtyLCBlcnJvcj1UUlVFfQp0YmwoY29ubiwgIkVTTkRfRVJfUFJTX0YiKQpgYGAKTGEgdnVlIG4nZXN0IHBhcyB0cm91dsOpZS4gT24gdsOpcmlmaWUgcXUnZWxsZSBleGlzdGUgYmllbiA6CgpgYGB7cn0KdGFibGVzIDwtIGRiR2V0UXVlcnkoY29ubiwgIlNFTEVDVCBvd25lciwgb2JqZWN0X25hbWUsIG9iamVjdF90eXBlIEZST00gYWxsX29iamVjdHMgV0hFUkUgb2JqZWN0X25hbWUgTElLRSAnJUVTTkRfRVJfUFJTJSciKQp0YWJsZXMkT1dORVIgPC0gbmV0dG95KHRhYmxlcyRPV05FUikgIyByZXRyYWl0IGRlIGwnaWRlbnRpZmlhbnQgdXRpbGlzYXRldXIKdGFibGVzCmBgYApFbGxlIGV4aXN0ZSBtYWlzIGVsbGUgYXBwYXJ0aWVudCBhdSBzY2jDqW1hL3Byb3ByacOpdGFpcmUgYE1FUFNHUF8xNTZgIChkYW5zIGxlIGNhcyBkdSBwcm9maWwgMTU2KSwgcXVpIGRpZmbDqHJlIGR1IHNjaMOpbWEgYEFER1BQYCBkZSBgRVJfUFJTX0ZgLgpMZSBzY2jDqW1hIGRlIGBFUl9QUlNfRmBkb2l0IMOqdHJlIGNlbHVpIHBhciBkw6lmYXV0IGRlIHRlbGxlIHNvcnRlIHF1ZSBgdGJsKGNvbm4sICJFUl9QUlNfRiIpYCBmb25jdGlvbm5lIGFsb3JzIHF1ZSBgdGJsKGNvbm4sICJFU05EX0VSX1BSU19GIilgIG5lIGZvbmN0aW9ubmUgcGFzLgpPbiBwZXV0IHByw6ljaXNlciB1biBzY2jDqW1hIGRpZmbDqXJlbnQgZHUgc2Now6ltYSBwYXIgZMOpZmF1dCBncsOiY2Ugw6AgYGluX3NjaGVtYSgpYCA6CgpgYGB7cn0KIyBlcnByc2YgPC0gdGJsKGNvbm4sIGluX3NjaGVtYSgiUFJFR1BfMTU2IiwgIkVTTkRfRVJfUFJTX0YiKSkgIyBuZSBmb25jdGlvbm5lIHBhcwplcnByc2YgPC0gdGJsKGNvbm4sIGluX3NjaGVtYSgiTUVQU0dQXzE1NiIsICJFU05EX0VSX1BSU19GIikpICMgZm9uY3Rpb25uZQpgYGAKCiMjIExlcyBiaWJsaW90aMOocXVlcyBTQVMKTGVzIHRhYmxlcyBTQVMgbmUgc29udCBwYXMgYWNjZXNzaWJsZXMuIFN1ciBTQVMgZWxsZXMgcG9ydGVudCB1biBwaWN0b2dyYW1tZSBkZSBsb3VwZS4KClBvdXIgbGVzIHJhbWVuZXIgZGFucyBPcmFjbGUgKGRhbnMgT3JhdXNlcikgZXQgYWluc2kgcG91dm9pciBsZXMgbWFuaXB1bGVyIGF2ZWMgUk9yYWNsZSwgaWwgZmF1dCBleMOpY3V0ZXIgdW5lIHJlcXXDqnRlIHN1ciBTQVMuCgo8Zm9udCBjb2xvcj0iIzczNzM3MyI+UGFyIGV4ZW1wbGUgc2kgbCdvbiB2ZXV0IHLDqWN1cMOpcmVyIGxhIHRhYmxlIEVYVFJBQ1RJT05fUEFUSUVOVFMyMDE5VFIgZGUgY29uc29wYXQgOjwvZm9udD4KCgpgYGB7U0FTLCBldmFsPUZBTFNFfQojIENvZGUgb3B0aW1pc8OpIHF1aSB2aWVudCBkdSBHVUlERSBERVMgQk9OTkVTIFBSQVRJUVVFUyBTQVMgLSB2Mi4wLnBkZgpwcm9jIHNxbDsKCWRyb3AgdGFibGUgb3JhdXNlci50YWJsZW9yYWNsZTsKCWNyZWF0ZSB0YWJsZSBvcmF1c2VyLnRhYmxlb3JhY2xlIChCVUxLTE9BRD15ZXMgQkxfREFUQUZJTEU9IiVzeXNmdW5jKHBhdGhuYW1lKHdvcmspKS90dHQueHl6IiBCTF9ERUxFVEVfREFUQUZJTEU9eWVzKSBhcyBzZWxlY3QgKiBmcm9tIENPTlNPUEFULkVYVFJBQ1RJT05fUEFUSUVOVFMyMDE5VFI7CnF1aXQ7CmBgYAoKPGZvbnQgY29sb3I9IiNjZjA1MDUiPioqSWwgeSBhIHVuIGZvcnQgaW50w6lyw6p0IMOgIHPDqWxlY3Rpb25uZXIgcXVlbHF1ZXMgdmFyaWFibGVzIGF1IGxpZXUgZGUgYHNlbGVjdCAqYCBwb3VyIGFjY8OpbGVyZXIgbCdleMOpY3V0aW9uIGV0IHLDqWR1aXJlIGxhIHBsYWNlIHByaXNlIHN1ciBPUkFVU0VSLioqPC9mb250PgoKVW5lIGF1dHJlIHNvbHV0aW9uIGNvbnNpc3RlIMOgIGNoYXJnZXIgbGVzIHRhYmxlcyBTQVMgYXZlYyBsZSBwYXF1ZXQgaGF2ZW4uCkNlbGEgZm9uY3Rpb25uZSBhdmVjIGxlcyB2cmFpZXMgdGFibGVzIDoKCmBgYHtyfQpoYXZlbjo6cmVhZF9zYXMoIn4vY29uc29wYXQvZXNfbXRzX3Yuc2FzN2JkYXQiKVsxOjQsXQpgYGAKCk1haXMgcGFzIGF2ZWMgbGVzIHZ1ZXMgU0FTICgqLnNhczdidmV3KikgOgoKYGBge3IgZXJyb3I9VFJVRX0KaGF2ZW46OnJlYWRfc2FzKCJ+L2NvbnNvcGF0L2V4dHJhY3Rpb25fcGF0aWVudHMyMDIxdHIuc2FzN2J2ZXciKQpgYGAKClBhcyBkZSBzb2x1dGlvbiBhcHBhcmVudGUgKFt2b2lyIGNldHRlIGRpc2N1c3Npb24gc3VyIFN0YWNrb3ZlcmZsb3ddKGh0dHBzOi8vc3RhY2tvdmVyZmxvdy5jb20vcXVlc3Rpb25zLzUxMjgzMzU2L2hvdy1jYW4taS1yZWFkLXNhczdidmV3LWZpbGVzLWluLXIpKSwgaWwgZmF1dCByZWNvdXJpciBhdSByYXBhdHJpZW1lbnQgZW4gU0FTIHN1ciBPcmF1c2VyLgoKCiMjIFNhdXZlZ2FyZGVyIHVuZSB0YWJsZSBpbnRlcm3DqWRpYWlyZQoKSWwgZmF1dCBiaWVuIGRpc3Rpbmd1ZXIgbGVzIHRhYmxlYXV4IFIgZGVzIHBvaW50ZXVycyB2ZXJzIGxlcyB0YWJsZXMgT3JhY2xlIDoKCmBgYHtyfQphIDwtIHRibChjb25uLCAiSVJfQklPX1IiKSAjIGEgZXN0IHVuIHBvaW50ZXVyIHZlcnMgbGEgdGFibGUgT3JhY2xlCmEgPC0gdGJsKGNvbm4sICJJUl9CSU9fUiIpICU+JSBjb2xsZWN0ICMgYSBlc3QgdW5lIHRhYmxlIFIKYGBgCgpTaSBvbiBwb3Nzw6hkZSB1bmUgdGFibGUgUiwgb24gcGV1dCBzdWl2cmUgbGUgZ3VpZGUgZGUgbGEgQ25hbSBwb3VyIGwnZW5yZWdpc3RyZXIgc291cyBPcmFjbGUgYXZlYyA6CgpgYGB7ciwgZXZhbD1GQUxTRX0KIyBPbiBzdXBwcmltZSBsYSB0YWJsZSBzaSBlbGxlIGV4aXN0ZSBkw6lqw6AsIHNpbm9uIGVycmV1cgp0cnkoZGJSZW1vdmVUYWJsZShjb25uLCAiUFJPViIpLCBzaWxlbnQgPSBUKSAjIGxlIG5vbSBkb2l0IMOqdHJlIGVuIG1hanVzY3VsZQoKIyBFY3JpdHVyZSDDoCBwcm9wcmVtZW50IHBhcmxlcgpkYldyaXRlVGFibGUoY29ubiwgIlBST1YiLCBmYXV4MCkgIyBmYXV4MCBlc3QgdW4gZGF0YS5mcmFtZQpgYGAKCk1haXMgY2VsYSBpbXBsaXF1ZSB1biBjb2xsZWN0IGF1cGFyYXZhbnQuIExlIGNpcmN1aXQgZXN0IGFsb3JzIAoqT3JhY2xlID4gUiA+IE9yYWNsZSogY2UgcXVpIGVzdCBzb3VzLW9wdGltYWwgcHVpc3F1ZSBsZSBgY29sbGVjdGAgZXN0IHVuZSBvcMOpcmF0aW9uIGNvw7t0ZXVzZS4KVW5lIG1laWxsZXVyZSBzb2x1dGlvbiBlc3QgZGUgbmUgcGFzIHJhcGF0cmllciBsJ29iamV0IGVuIFIsIGRlIHRvdXQgZmFpcmUgZW4gT3JhY2xlLgoKVGjDqW9yaXF1ZW1lbnQgbGEgZm9uY3Rpb24gZGJwbHlyIGBjb21wdXRlYCByw6lhbGlzZSBjZWxhIGV0IGRvaXQgw6p0cmUgdHLDqHMgcHJhdGlxdWUgOgpgYGB7ciwgZXZhbD1GQUxTRX0KIyBFUlJFVVIgQSBDRSBKT1VSCiMgYSA8LSB0YmwoY29ubiwgIklSX1BIQV9SIikgJT4lCiMgICBmaWx0ZXIoUEhBX0FUQ19DMDMgPT0gIkExMCIpICU+JSAjIE3DqWRpY2FtZW50cyBkdSBkaWFiw6h0ZQojICAgY29tcHV0ZQpgYGAKRWxsZSBkb2l0IGVucmVnaXN0cmVyIGxlIHLDqXN1bHRhdCBkdSBjYWxjdWwgZW4gdGFudCBxdWUgdGFibGUgT3JhY2xlIGV0IGxhIHJlbGlyZSBwb3VyIGZhaXJlIGRlIGBhYCB1biBwb2ludGV1ci4KQ2VsYSBwZXJtZXR0cmFpdCBkb25jIGRlIGZhaXJlIHVuIHBvaW50IGQnYXJyw6p0IHF1aSDDqXZpdGUgZGUgcmVwcmVuZHJlIHRvdXMgbGVzIGNhbGN1bHMgZGVwdWlzIGxlIGTDqWJ1dC4KCkjDqWxhcyBsYSBmb25jdGlvbiBgY29tcHV0ZWAgbmUgbWFyY2hlIHBhcyBhdmVjIHVuZSBiYXNlIE9yYWNsZSBwb3VyIGwnaW5zdGFudCBjYXIgbGEgdHJhZHVjdGlvbiBvdWJsaWUgbGUgbW90IGNsw6kgR0xPQkFMIGRhbnMgbGEgcmVxdcOqdGUuCkxlIGJ1ZyBjb25jZXJuZSBhdXNzaSBsYSBmb25jdGlvbiBgY29weV90b2AsIHF1aSBmYWl0IGxlIGNvbnRyYWlyZSBkdSBgY29sbGVjdGAsIGMnZXN0LcOgLWRpcmUgZW52b3llciDDoCBPcmFjbGUgdW4gb2JqZXQgUi4KSidhaSBlbnJlZ2lzdHLDqSBbdW4gdGlja2V0XShodHRwczovL2dpdGh1Yi5jb20vdGlkeXZlcnNlL2RicGx5ci9pc3N1ZXMvNjIxKSBzdXIgbGUgR2l0aHViIGR1IHByb2pldCBkYnBseXIsIGxlIGJ1ZyBhIMOpdMOpIHRyYWl0w6kgZXQgbGUgYGNvbXB1dGVgIGRldnJhaXQgYmllbnTDtHQgZGUgbm91dmVhdSBmb25jdGlvbm5lci4KCkVuIGF0dGVuZGFudCBqZSBwcm9wb3NlIGNldHRlIGZvbmN0aW9uIGFydGlzYW5hbGUgcXVpIGZhaXQgbGEgbcOqbWUgY2hvc2UgZXQgamUgbGEgY291cGxlIGF2ZWMgdW5lIGZvbmN0aW9uIGRlIG5ldHRveWFnZSBwb3VyIHN1cHByaW1lciBsZXMgdGFibGVzIHByb3Zpc29pcmVzIGRlIHRlbXBzIGVuIHRlbXBzIDoKCmBgYHtyfQojIFN1cHByaW1lciBkZXMgdGFibGVzIDoKIyAtIGxlc3F1ZWxsZXMgPSAicHJvdiIgOiBsZXMgdGFibGVzIHByb3Zpc29pcmVzCiMgLSBsZXNxdWVsbGVzID0gInByb3ZSZWNlbnRlcyIgOiBsZXMgdGFibGVzIHByb3Zpc29pcmVzIGF2YW50IGF1am91cmQnaHVpCiMgLSBsZXNxdWVsbGVzID0gInRvdXRlcyIgOiB0b3V0ZXMgbGVzIHRhYmxlcyBkZSBsJ3V0aWxpc2F0ZXVyCnN1cHByVGFibGVzIDwtIGZ1bmN0aW9uKGxlc3F1ZWxsZXMgPSAicHJvdlJlY2VudGVzIil7CiAgdmVjdDAgPC0gZGJHZXRRdWVyeShjb25uLCAiU0VMRUNUIFRBQkxFX05BTUUgRlJPTSB1c2VyX3RhYmxlcyIpJFRBQkxFX05BTUUKICAKICAjIE9uIGZpbHRyZSBsZXMgdGFibGVzIHByb3Zpc29pcmVzIAogIHZlY3QxIDwtIHZlY3QwW2dyZXBsKCJQUk9WWzAtOV17MTR9IiwgdmVjdDAsIHBlcmwgPSBUKV0KICAKICB2ZWN0MiA8LSBnc3ViKCJQUk9WKFswLTldezE0fSkiLCAiXFwxIiwgdmVjdDEsIHBlcmwgPSBUKQogIHZlY3QyIDwtIGFzLkRhdGUodmVjdDIsICIlWSVtJWQiKQogIAogIGlmIChsZXNxdWVsbGVzID09ICJ0b3V0ZXMiKSB7CiAgICB2ZWN0QXN1cHAgPC0gdmVjdDAKICB9IGVsc2UgaWYgKGxlc3F1ZWxsZXMgPT0gInByb3YiKXsKICAgIHZlY3RBc3VwcCA8LSB2ZWN0MQogIH0gZWxzZSB7CiAgICB2ZWN0QXN1cHAgPC0gdmVjdDFbdmVjdDIgPCBTeXMuRGF0ZSgpXQogIH0KICAKICBpZiAobGVuZ3RoKHZlY3RBc3VwcCkpIHNwcmludGYoIlRhYmxlcyBzdXBwcmltw6llcyA6ICVzIiwgcGFzdGUodmVjdEFzdXBwLCBjb2xsYXBzZSA9ICIsICIpKQogIAogIGZvciAobm9tVGFibGUgaW4gdmVjdEFzdXBwKSB7CiAgICB0cnkoZGJSZW1vdmVUYWJsZShjb25uLCBub21UYWJsZSksIHNpbGVudCA9IFQpCiAgfQp9CgojIEVxdWl2YWxlbnQgZm9uY3Rpb25uZWwgZGUgbGEgZm9uY3Rpb24gY29tcHV0ZSgpCmNvbXB1dGUyIDwtIGZ1bmN0aW9uKHRibF9vcmFjbGUpIHsKICAjIE9uIGNvbW1lbmNlIHBhciB1biBuZXR0b3lhZ2UgZGVzIGFuY2llbm5lcyB0YWJsZXMgcHJvdmlzb2lyZXMKICAjIHNpIG9uIGEgb3VibGnDqSBkZSBsZSBmYWlyZQogIHN1cHByVGFibGVzKGxlc3F1ZWxsZXMgPSAicHJvdlJlY2VudGVzIikKICAKICAjIE9uIGNob2lzaXIgdW4gbm9tIGRhdMOpIHBvdXIgZW5yZWdpc3RyZXIgbGEgdGFibGUKICBub21UYWJsZSA8LSAgcGFzdGUwKCJQUk9WIiwgZm9ybWF0KFN5cy50aW1lKCksICIlWSVtJWQlSCVNJVMiKSkKICAKICAjIHJlbnZvaWUgdW5lIGVycmV1ciBzaSBsYSB0YWJsZSBuJ2V4aXN0ZSBwYXMKICBzeXN0ZW0udGltZSh7CiAgIwogIGRiRXhlY3V0ZShjb25uLCBwYXN0ZTAoIkNSRUFURSBUQUJMRSAiLCBub21UYWJsZSwiIEFTICgiLCByZW1vdGVfcXVlcnkodGJsX29yYWNsZSksICIpIikpCiAgfSkKICB0YmwoY29ubiwgbm9tVGFibGUpCn0KYGBgCjxmb250IGNvbG9yPSIjNzM3MzczIj5FeGVtcGxlIGQndXRpbGlzYXRpb248L2ZvbnQ+CgpgYGB7cn0KIyBMb25ncyBjYWxjdWxzIChpY2kgY291cnRzIHBvdXIgbCdleGVtcGxlKQphIDwtIHRibChjb25uLCAiSVJfUEhBX1IiKSAlPiUKICBmaWx0ZXIoUEhBX0FUQ19DMDMgPT0gIkExMCIpCgojIE9uIMOpY3JpdCB1bmUgdGFibGUgaW50ZXJtw6lkaWFpcmUKYSA8LSBjb21wdXRlMihhKQpgYGAKCjxmb250IGNvbG9yPSIjNzM3MzczIj5QcmV1dmUgcXVlIGBhYCBlc3QgdW4gcG9pbnRldXIgdmVycyB1bmUgdGFibGUgaW50ZXJtw6lkaWFpcmUgZXQgbm9uIHVuZSBzdWl0ZSBkJ2luc3RydWN0aW9ucyA6PC9mb250PgoKYGBge3J9CmEgJT4lIHNob3dfcXVlcnkKYGBgCgpQb3VyIG5ldHRveWVyIGxlcyB0YWJsZXMgaW50ZXJtw6lkaWFpcmVzIMOpY3JpdGVzLCBwb3J0YW50IGxlIG5vbSBkZSAiUFJPViIsIHZvdXMgcG91dmV6IGV4w6ljdXRlciBkZSB0ZW1wcyBlbiB0ZW1wcyBsYSBmb25jdGlvbiBgc3VwcHJUYWJsZXNgIDoKYGBge3J9CnN1cHByVGFibGVzKGxlc3F1ZWxsZXMgPSAicHJvdiIpCmBgYAoKQXByw6hzIG5ldHRveWFnZSwgbGUgcG9pbnRldXIgYGFgIGVzdCBjYXNzw6kgOgoKYGBge3IsIGVycm9yPVRSVUV9CmEgIyAiT1JBLTAwOTQyOiBUYWJsZSBvdSB2dWUgaW5leGlzdGFudGUiCmBgYAoKIyMgT3JhdXNlcgoKUXVlIGNlIHNvaXQgdmlhIGBkYldyaXRlVGFibGUoY29ubiwgIm5vbVRBQkxFIiwgdW5EYXRhRnJhbWVSKWAgb3UgbGEgZm9uY3Rpb24gY2ktZGVzc3VzIGBjb21wdXRlMmAsIG9uIMOpY3JpdCBzdXIgbCdlc3BhY2UgT3JhdXNlciwgcXVpIGVzdCB1biBlc3BhY2UgcGFydGFnw6kuCkNvbnRyYWlyZW1lbnQgw6AgKipTQVNEQVRBMSoqLCBpbCBuJ3kgYSBwYXMgZGUgcXVvdGFzIGluZGl2aWR1ZWxzLgpMJ2VzcGFjZSBlc3QgcXVhbmQgbcOqbWUgY29udHJhaW50IGV0IGxhIENuYW0gZW52b2llIHLDqWd1bGnDqHJlbWVudCBkZXMgbWFpbHMgcG91ciBmYWlyZSBzdXBwcmltZXIgZGVzIHRhYmxlcyBldCDDqXZpdGVyIGxhIHNhdHVyYXRpb24gZGUgbCdlc3BhY2UgcGFydGFnw6kuCkxhIGJvbm5lIHByYXRpcXVlIGVzdCBkZSBzdXBwcmltZXIgc2VzIHRhYmxlcyBkw6hzIHF1ZSBwb3NzaWJsZS4KTGUgbmV0dG95YWdlIHBldXQgc2UgZmFpcmUgZGUgZmHDp29uIHZpc3VlbGxlIHN1ciBTQVMgb3UgYXZlYyBsYSBmb25jdGlvbiBgc3VwcHJUYWJsZXNgIGTDqWZpbmllIGNpLWRlc3N1cy4KCgoKIyBHw6lyZXIgc2Egc2Vzc2lvbiBSU3R1ZGlvCiMjIFF1ZSBmYWlyZSBlbiBjYXMgZGUgcGVydGUgZGUgY29udHLDtGxlID8KCkFycsOqdGVyIHVuZSByZXF1w6p0ZSBlbiBjb3VycyBkJ2V4w6ljdXRpb24gbidlc3QgcGFzIHVuZSBjaG9zZSBzaW1wbGUuClNvdXZlbnQgbGUgYm91dG9uIHJvdWdlICpTdG9wKiBuZSBmb25jdGlvbm5lIHBhcyBjYXIgY2VsYSBlbnZvaWUgdW4gc2lnbmFsIGQnYXJyw6p0IMOgIGxhIGNvbW1hbmRlIFIgZXQgbm9uIMOgIGxhIHJlcXXDqnRlIE9yYWNsZS4KCk9uIHBldXQgdG91am91cnMgZXNzYXllciBtYWlzIHNpIMOnYSBuZSBmb25jdGlvbm5lIHBhcywgbGEgbWVpbGxldXJlIHNvbHV0aW9uIGVzdCBkZSBmb3JjZXIgbCdhcnLDqnQgZGUgbGEgc2Vzc2lvbiA6CgoxLiBzw6lsZWN0aW9ubmVyIGxlIGNvZGUgcydpbCBuJ2VzdCBwYXMgZW5yZWdpc3Ryw6ksIGxlIGNvcGllci1jb2xsZXIgcXVlbHF1ZSBwYXJ0LCBlbiBzw6ljdXJpdMOpIDsKMi4gY2xpcXVlciBzdXIgbGUgZ3JvcyBSIGJsZXUgZW4gaGF1dCDDoCBnYXVjaGUgcG91ciByZWpvaW5kcmUgbGUgbWVudSA7CjMuIGNvY2hlciBsYSBzZXNzaW9uIGVuIHF1ZXN0aW9uIDsKNC4gc8OpbGVjdGlvbm5lciAiKmZvcmNlIHF1aXQqIiA7CjUuIHLDqW91dnJpciB1bmUgc2Vzc2lvbiAvIGxlIHByb2pldCA7CjYuIHJlcHJlbmRyZSBzb24gY29kZSBub24tZW5yZWdpc3Ryw6kuCgpTaSBsJ8OpdGFwZSAyIGVzdCBpbXBvc3NpYmxlIGNhciBsZSBncm9zIFIgYmxldSBuJ2VzdCBwYXMgdmlzaWJsZSwgdm91cyBwb3V2ZXogZGlyZWN0ZW1lbnQgY29waWVyLWNvbGxlciBsJ1VSTCBbaHR0cHM6Ly9yc3R1ZGlvLXByb2QubG9jYWwvUi9dKGh0dHBzOi8vcnN0dWRpby1wcm9kLmxvY2FsL1IvKSBkYW5zIGxlIG5hdmlnYXRldXIgY2hyb21pdW0gKGNlbHVpIG91dmVydCBwb3VyIFIpLgoKIyMgQ291cHVyZXMKCkxvcnMgZGVzIHBhdXNlcywgQ2l0cml4IHBldXQgc2UgZMOpY29ubmVjdGVyIGNlIHF1aSBmZXJtZSBSU3R1ZGlvLgpOw6lhbm1vaW5zIGxlcyBjYWxjdWxzIGVuIGNvdXJzIG5lIHNvbnQgcGFzIGFycsOqdMOpcywgbcOqbWUgZW4gcGxlaW5lIG51aXQsIMOgIGxhIGRpZmbDqXJlbmNlIGRlIFNBUyBxdWkgZXN0IHJlZMOpbWFycsOpIMOgIDJoLgpMb3JzcXUnb24gbGFuY2Ugw6Agbm91dmVhdSAiUlN0dWRpbyBXb3JrYmVuY2giIGRlcHVpcyBsZSBwb3J0YWlsLCBsYSBzZXNzaW9uIHMnb3V2cmUgZGFucyBsZSBtw6ptZSDDqXRhdCBxdSdvbiBsJ2EgbGFpc3PDqWUsIGF2ZWMgdG91cyBsZXMgb2JqZXRzIGNoYXJnw6lzLgpBdHRlbnRpb24sIHNpIGxhIHNlc3Npb24gY29tcHRlIGRlIGdyb3Mgb2JqZXRzLCBsJ291dmVydHVyZSBkZSBsYSBzZXNzaW9uIHBldXQgw6p0cmUgbG9uZy4KSWwgZXN0IGNvbnNlaWxsw6kgYXV0YW50IHF1ZSBwb3NzaWJsZSBkZSBmZXJtZXIgc2Egc2Vzc2lvbiBsb3JzcXUnb24gYSBmaW5pIGRlIHRyYXZhaWxsZXIgZXQgZCdlbiBvdXZyaXIgdW5lIG5vdXZlbGxlLgpDZWxhIGxpYsOocmUgZGUgbGEgbcOpbW9pcmUgdG91dCBlbiBnYXJhbnRpc3NhbnQgbGEgcsOpcGxpY2FiaWxpdMOpIGRlIHNlcyByw6lzdWx0YXRzLgoKPGZvbnQgY29sb3I9IiNjZjA1MDUiPkxvcnNxdSd1bmUgc2Vzc2lvbiBlc3QgaW5hY3RpdmUgZGVwdWlzIHRyb3AgbG9uZ3RlbXBzLCBlbGxlIHBldXQgw6p0cmUgc3VwcHJpbcOpZSBhdXRvbWF0aXF1ZW1lbnQuCklsIGVzdCBwbHVzIHBydWRlbnQgZGUgbmUgamFtYWlzIGNvbXB0ZXIgc3VyIGxlcyBvYmpldHMgUiBkZSBsYSBzZXNzaW9uLjwvZm9udD4KCiMjIENvcGllci1jb2xsZXIKCkRlcHVpcyBsYSBtaXNlIGVuIHByb2R1Y3Rpb24gZGUgbCdpbnRlcmZhY2UgUlN0dWRpbywgbGUgY29sbGFnZSBkZXB1aXMgbGUgcHJlc3NlLXBhcGllciDDqWNob3VlIHBhcmZvaXMuCkxvcnNxdSdvbiBjb3BpZSBkZXB1aXMgUlN0dWRpbyBldCBxdSdvbiBjb2xsZSBkYW5zIFJTdHVkaW8sIGF1Y3VuIHByb2Jsw6htZS4KTG9yc3F1J29uIGNvcGllIHVuIGJvdXQgZGUgY29kZSBob3JzIGRlIFJTdHVkaW8gZXQgcXUnb24gbGUgY29sbGUgZGFucyBSU3R1ZGlvLCBjZWxhIMOpY2hvdWUgc2kgbCdvbiBhdmFpdCBkw6lqw6Agc8OpbGVjdGlvbm7DqSB1biBib3V0IGRlIHRleHRlIHF1ZSBsJ29uIGNvbXB0YWl0IHJlbXBsYWNlci4KSWwgZmF1dCBkb25jIHN1cHByaW1lciBsZSB0ZXh0ZSBhdmFudCwgcHVpcyBjb3BpZXIgbGUgY29udGVudSBldCBsZSBjb2xsZXIgZW5zdWl0ZSBzYW5zIGF2b2lyIHPDqWxlY3Rpb25uw6kgZGUgdGV4dGUuCkF0dGVudGlvbiBjZWxhIMOpY2hvdWUgc2kgb24gaW52ZXJzZSBsJ29yZHJlIGRlcyBvcMOpcmF0aW9ucyAocGFyIGV4ZW1wbGUgOiBjb3BpZXItc3VwcHJpbWVyIGxlIHRleHRlLWNvbGxlcikuCgojIEdpdAoKTG9naXF1ZW1lbnQsIGlsIG4nZXN0IHBhcyBwb3NzaWJsZSBkZSBjb25uZWN0ZXIgc29uIHByb2pldCDDoCB1biBkw6lww7R0IEdpdCBzdXIgR2l0aHViIG91IEdpdGxhYiBwdWlzcXUnaWwgcydhZ2l0IGQndW5lIGJ1bGxlIHPDqWN1cmlzw6llLgpDZWxhIG4nZW1ww6pjaGUgcGFzIGwndXRpbGlzYXRpb24gZGUgR2l0IGVuIGxvY2FsLgrDh2EgbmUgcGVybWV0IHBhcyBkZSB0cmF2YWlsbGVyIMOgIHBsdXNpZXVycyBzdXIgbGUgbcOqbWUgY29kZSAocXVvaSBxdWUgw6dhIHMnZW52aXNhZ2UgYXZlYyB1biBlc3BhY2UgcHJvamV0KSBtYWlzIGNlbGEgcGVybWV0IHRvdXQgZGUgbcOqbWUgZGUgZmFpcmUgbGUgdmVyc2lvbm5hZ2UgZGUgc29uIGNvZGUgdG91dCBlbiBsZSBzw6ljdXJpc2FudC4KClBvdXIgaW5pdGlhbGlzZXIgdW4gZG9zc2llciBHaXQgOiAqRmlsZSogPiAqTmV3IHByb2plY3QqID4gKk5ldyBEaXJlY3RvcnkqID4gKk5ldyBQcm9qZWN0KiBwdWlzIGNob2lzaXIgdW4gbm9tIGRlIHByb2pldCwgdW4gZW1wbGFjZW1lbnQgKipldCBjb2NoZXIqKiAqQ3JlYXRlIGEgZ2l0IHJlcG9zaXRvcnkqLgpFbmZpbiB2YWxpZGVyIGF2ZWMgKkNyZWF0ZSBwcm9qZWN0IGFzIHN1YmRpcmVjdG9yeSouCgpFbnN1aXRlIG9uIHBldXQgdXRpbGlzZXIgR2l0IG5vcm1hbGVtZW50IMOgIHBhcnRpciBkdSBwYW5uZWF1IGVuIGhhdXQgw6AgZHJvaXRlLgpTaSBsJ29uIHZldXQgZmFpcmUgY2VydGFpbmVzIG9ww6lyYXRpb25zIG5vbiBpbmNsdXNlcyBkYW5zIGxlIHBhbm5lYXUsIG9uIHBldXQgdG91am91cnMgdXRpbGlzZXIgbGEgY29tbWFuZGUgYHN5c3RlbWAgcG91ciBsYW5jZXIgdW5lIGNvbW1hbmRlIHN5c3TDqG1lLgoKPGZvbnQgY29sb3I9IiM3MzczNzMiPlF1ZWxxdWVzIGV4ZW1wbGVzIDo8L2ZvbnQ+CgpgYGB7cn0Kc3lzdGVtKAogICMgRMOpY29tbWVudGVyIGxhIGxpZ25lIGludMOpcmVzc2FudGUKICAKICAjIENvbmZpZ3VyZXIgc29uIG5vbQogICMgImdpdCBjb25maWcgLS1nbG9iYWwgdXNlci5uYW1lICdTYW11ZWwgQWxsYWluJyIKICAjIFbDqXJpZmllcgogICMgImdpdCBjb25maWcgLS1nbG9iYWwgdXNlci5uYW1lIgogICAKICAjIENvbmZpZ3VyZXIgc29uIGFkcmVzc2UgbWFpbAogICMgImdpdCBjb25maWcgLS1nbG9iYWwgdXNlci5lbWFpbCAnc2FtdWVsLmFsbGFpbkBzYW50ZS5nb3V2LmZyJyIKICAjIFbDqXJpZmllcgogICAjICJnaXQgY29uZmlnIC0tZ2xvYmFsIHVzZXIuZW1haWwiCiAgIAogICMgRXRhdCBkdSBkw6lww7R0CiAgIyAiZ2l0IHN0YXR1cyIKICAKICAjIEFqb3V0ZXIgdW5lIMOpdGlxdWV0dGUKICAjICJnaXQgdGFnIHYwLjIiCiAgCiAgIyBTdXBwcmltZXIgdW5lIMOpdGlxdWV0dGUKICAjICJnaXQgdGFnIC1kIHYwLjIiCiAgCiAgIyBWZXJzaW9uIGRlIEdpdAogICJnaXQgLS12ZXJzaW9uIgopCmBgYAoKPGZvbnQgY29sb3I9IiNjZjA1MDUiPkLDqW1vbCA6IGxvcnMgZGUgbGEgcHNldWRvbnltaXNhdGlvbiwgaWwgZXN0IGRpZmZpY2lsZSBkZSBnw6lyZXIgbCdoaXN0b3JpcXVlIEdpdCwgY29uc3RpdHXDqSBkJ3VuZSBmb3VsZSBkZSBmaWNoaWVycyBkYW5zIGxlIGRvc3NpZXIgLmdpdC4gRW4gZWZmZXQgbCd1dGlsaXRhaXJlIGZvdXJuaSBwYXIgbGEgQ25hbSwgQlJVUywgZGVtYW5kZSBkZSB2YWxpZGVyIGNoYXF1ZSBmaWNoaWVyLCB1biDDoCB1bi48L2ZvbnQ+CgoKIyBDb25maWd1cmF0aW9uIFJTdHVkaW8KCiMjIERpY3Rpb25uYWlyZSBmcmFuw6dhaXMgcG91ciBsYSBjb3JyZWN0aW9uIG9ydGhvZ3JhcGhpcXVlCkxlIGRpY3Rpb25uYWlyZSBmcmFuw6dhaXMgbidlc3QgcGFzIHByw6lzZW50IHBhciBkw6lmYXV0LgpJbCBuJ2VzdCBwYXMgcG9zc2libGUgZGUgbGUgdMOpbMOpY2hhcmdlciBkaXJlY3RlbWVudCBlbiB1dGlsaXNhbnQgKlRvb2xzID4gR2xvYmFsIE9wdGlvbnMgPiBTcGVsbGluZyA+IE1haW4gZGljdGlvbmFyeSBsYW5ndWFnZSA+IEluc3RhbGwgbW9yZSBsYW5ndWFnZSogY2FyIFJTdHVkaW8gZXN0IGNvdXDDqSBkdSByZXN0ZSBkJ2ludGVybmV0LgpPbiBwZXV0IGVuIHJldmFuY2hlIHLDqWN1cMOpcmVyIGwnVVJMIGluZGlxdcOpZSBwYXIgbCdlcnJldXIgOgoKPiBbaHR0cHM6Ly9zMy5hbWF6b25hd3MuY29tL3JzdHVkaW8tYnVpbGR0b29scy9kaWN0aW9uYXJpZXMvYWxsLWRpY3Rpb25hcmllcy56aXBdKGh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS9yc3R1ZGlvLWJ1aWxkdG9vbHMvZGljdGlvbmFyaWVzL2FsbC1kaWN0aW9uYXJpZXMuemlwKQoKUHVpcyB0w6lsw6ljaGFyZ2VyIGxlIHppcCwgbGUgZMOpY29tcHJlc3NlciwgZXQgaW1wb3J0ZXIgbGVzIGZpY2hpZXJzICpmcl9GUi5kaWMqIChkaWN0aW9ubmFpcmUpIGV0ICpmcl9GUi5hZmYqIChhZmZpeGVzIHBvdXIgbGVzIGFjY29yZHMgZXRjLikgZGFucyBzb24gSG9tZS4gVW5lIGZvaXMgY2VsYSBmYWl0LCBpbCBmYXV0IGTDqXBsYWNlciBsZXMgZmljaGllcnMgYXUgYm9uIGVuZHJvaXQsIGNlIHF1ZSBkZXZyYWllbnQgZmFpcmUgY2VzIGNvbW1hbmRlcyA6CgpgYGB7ciwgZXZhbD1GQUxTRX0KIyBPbiBjcsOpZSBsZSBkb3NzaWVyIHBvdXIgYWNjdWVpbGxpciBsZXMgZmljaGllcnMgZGUgZGljdGlvbm5haXJlcwpzeXN0ZW0oIm1rZGlyIC1wIH4vLmNvbmZpZy9yc3R1ZGlvL2RpY3Rpb25hcmllcy9sYW5ndWFnZXMtc3lzdGVtIikKCiMgQ29waWVyIGxlcyBmaWNoaWVycyBkYW5zIGNlIG5vdXZlYXUgZG9zc2llcgpzeXN0ZW0oImNwIH4vZnJfRlIuZGljIH4vLmNvbmZpZy9yc3R1ZGlvL2RpY3Rpb25hcmllcy9sYW5ndWFnZXMtc3lzdGVtIikKc3lzdGVtKCJjcCB+L2ZyX0ZSLmFmZiB+Ly5jb25maWcvcnN0dWRpby9kaWN0aW9uYXJpZXMvbGFuZ3VhZ2VzLXN5c3RlbSIpCgpgYGAKCkVuc3VpdGUgcmVkw6ltYXJyZXIgUlN0dWRpbyAoKlNlc3Npb24gPiBUZXJtaW5hdGUgUiopIHBvdXIgcHJlbmRyZSBlbiBjb21wdGUgbGVzIGNoYW5nZW1lbnRzLgpWw6lyaWZpZXIgZGFucyAqVG9vbHMgPiBHbG9iYWwgT3B0aW9ucyA+IFNwZWxsaW5nKiBxdWUgbGEgbWFuaXB1bGF0aW9uIGEgZm9uY3Rpb25uw6kgKCJGcmVuY2giKS4KCgoK
1 Comment accéder à R ?
La démarche d’accès est expliquée dans le document R - Accès à R sur portail SNDS.pdf que l’on trouve dans l’onglet Accueil du portail.