1 Libraries and data

library(tidyverse)
library(magrittr)
library(sf)

1.1 Out toy data

Disclaimer: These files might be wrong or outdated. Do not use them for research or serious projects.

2 Warm up

2.1 Read (import)

Zones <- st_read(dsn = "Data/Bogota_UPZ_Population.shp")
Reading layer `Bogota_UPZ_Population' from data source 
  `C:\Users\Orlan\Dropbox\Teaching\SpatialAnalysis\Tutorials\02_R_Spatial\Data\Bogota_UPZ_Population.shp' 
  using driver `ESRI Shapefile'
Simple feature collection with 113 features and 7 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -74.22358 ymin: 4.435827 xmax: -74.00978 ymax: 4.83066
Geodetic CRS:  WGS 84
Zones_SES <- st_read("Data/Bogota_NSE.shp")
Reading layer `Bogota_NSE' from data source 
  `C:\Users\Orlan\Dropbox\Teaching\SpatialAnalysis\Tutorials\02_R_Spatial\Data\Bogota_NSE.shp' 
  using driver `ESRI Shapefile'
Simple feature collection with 45051 features and 13 fields
Geometry type: POLYGON
Dimension:     XY
Bounding box:  xmin: -74.22339 ymin: 4.466349 xmax: -74.00643 ymax: 4.832934
Geodetic CRS:  MAGNA-SIRGAS
BRT_Stations <- st_read("Data/Bogota_BRT_Stations.shp")
Reading layer `Bogota_BRT_Stations' from data source 
  `C:\Users\Orlan\Dropbox\Teaching\SpatialAnalysis\Tutorials\02_R_Spatial\Data\Bogota_BRT_Stations.shp' 
  using driver `ESRI Shapefile'
Simple feature collection with 149 features and 16 fields
Geometry type: POINT
Dimension:     XY
Bounding box:  xmin: -74.20546 ymin: 4.531715 xmax: -74.04357 ymax: 4.768818
Geodetic CRS:  WGS 84
BRT_Trunk <- st_read("Data/Bogota_BRT_TrunckLines.shp")
Reading layer `Bogota_BRT_TrunckLines' from data source 
  `C:\Users\Orlan\Dropbox\Teaching\SpatialAnalysis\Tutorials\02_R_Spatial\Data\Bogota_BRT_TrunckLines.shp' 
  using driver `ESRI Shapefile'
Simple feature collection with 13 features and 8 fields
Geometry type: MULTILINESTRING
Dimension:     XY
Bounding box:  xmin: -74.20615 ymin: 4.55645 xmax: -74.04325 ymax: 4.770763
Geodetic CRS:  WGS 84
Bicycle_ParkingSpots <- st_read("Data/Bogota_BicycleParkingSpots.shp")
Reading layer `Bogota_BicycleParkingSpots' from data source 
  `C:\Users\Orlan\Dropbox\Teaching\SpatialAnalysis\Tutorials\02_R_Spatial\Data\Bogota_BicycleParkingSpots.shp' 
  using driver `ESRI Shapefile'
Simple feature collection with 255 features and 11 fields
Geometry type: POINT
Dimension:     XY
Bounding box:  xmin: -8257660 ymin: 506987.2 xmax: -8240328 ymax: 535725.3
Projected CRS: WGS 84 / Pseudo-Mercator
class(Zones)
[1] "sf"         "data.frame"
class(Zones_SES)
[1] "sf"         "data.frame"
class(BRT_Stations)
[1] "sf"         "data.frame"
class(BRT_Trunk)
[1] "sf"         "data.frame"
class(Bicycle_ParkingSpots)
[1] "sf"         "data.frame"
dim(Zones)
[1] 113   8
dim(Zones_SES)
[1] 45051    14
dim(BRT_Stations)
[1] 149  17
dim(BRT_Trunk)
[1] 13  9
dim(Bicycle_ParkingSpots)
[1] 255  12

2.2 Write (export)

# st_write(obj = BRT_Stations, dsn = "ChooseTheName.shp")

2.3 Basic mapping

The following is a mess!

plot(Zones)

If you want to see the geometry, you do this:

plot(st_geometry(Zones))

For a choroplet map:

plot(Zones["poblacion_"])

The intuitive way fails here:

plot(Zones$poblacion_)

But the tidy intuitive way does not

plot(Zones %>% select(poblacion_))

You can improve your map in many ways. Neverheless, we will use the tmap library for all the map-making and to produce images. Another alternative is to use ggplot2. You can also produce interactive maps with the leaflet library.

2.4 CSR

Planet earth is not a flat surface and is not perfectly round. Nevertheless, when you make maps you have to put a certain geographic area in two dimensions. This gets even more complicated if you consider mountains and different sea levels.

Projections are the way we transform the shape of the earth (or an area) to only two dimensions. There are many projections and all have some level of distortion. You can see the section 2.4 from the Geocomputation With R book for a clear introduction to the topic. So far, you have to know that every sf object has a Coordinate Referense System (CRS) that can be configured using EPSG codes. There are geographic projections where the notion of distance is not related to a regular understanding of distance (it is not in meter or icnhes). In Projected projections on the other hand, the distance is in meters.

In the past, many sf functions did not distinguish geographic projections from projected when running some operation sus as buffers or distances calculations. I understand this is not longer an isse, but I recommend you to doublecheck every output.

How to get the CRS?

st_crs(x = Zones)
Coordinate Reference System:
  User input: WGS 84 
  wkt:
GEOGCRS["WGS 84",
    DATUM["World Geodetic System 1984",
        ELLIPSOID["WGS 84",6378137,298.257223563,
            LENGTHUNIT["metre",1]]],
    PRIMEM["Greenwich",0,
        ANGLEUNIT["degree",0.0174532925199433]],
    CS[ellipsoidal,2],
        AXIS["latitude",north,
            ORDER[1],
            ANGLEUNIT["degree",0.0174532925199433]],
        AXIS["longitude",east,
            ORDER[2],
            ANGLEUNIT["degree",0.0174532925199433]],
    ID["EPSG",4326]]
st_crs(BRT_Stations)
Coordinate Reference System:
  User input: WGS 84 
  wkt:
GEOGCRS["WGS 84",
    DATUM["World Geodetic System 1984",
        ELLIPSOID["WGS 84",6378137,298.257223563,
            LENGTHUNIT["metre",1]]],
    PRIMEM["Greenwich",0,
        ANGLEUNIT["degree",0.0174532925199433]],
    CS[ellipsoidal,2],
        AXIS["latitude",north,
            ORDER[1],
            ANGLEUNIT["degree",0.0174532925199433]],
        AXIS["longitude",east,
            ORDER[2],
            ANGLEUNIT["degree",0.0174532925199433]],
    ID["EPSG",4326]]
st_crs(Zones) == st_crs(BRT_Stations)
[1] TRUE

The function st_transform() changes the CRS (re-project) of any sf object. On the other hand, the st_set_crs() function assigns (sets) a CRS, but is not projecting the object. The st_set_crs() function is useful, for example, when you manually collect data or when you have a file with missing CRS

Zones_Projected <- st_transform(Zones, 3116)
st_crs(Zones) == st_crs(Zones_Projected) 
[1] FALSE
st_is_longlat(Zones)
[1] TRUE
st_is_longlat(Zones_Projected)
[1] FALSE

3 Basic tidy operations

An sf objects works just like as data.frame or tibble. It is a table (databe) with one additional column for the geometry that is “ignored” for certain operations.

3.1 summary

summary(Zones$poblacion_)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
    167   24532   50262   64671   93076  302006       1 
glimpse(Zones)
Rows: 113
Columns: 8
$ cod_loc    <dbl> 2, 3, 7, 8, 8, 8, 8, 11, 11, 11, 13, 15, 19, 2, 3, 4, 9, 10, 11, …
$ nomb_loc   <chr> "CHAPINERO", "SANTA FE", "BOSA", "KENNEDY", "KENNEDY", "KENNEDY",…
$ cod_upz    <chr> "88", "91", "86", "44", "48", "82", "113", "3", "23", "28", "107"…
$ nom_upz    <chr> "EL REFUGIO", "SAGRADO CORAZON", "EL PORVENIR", "AMERICAS", "TIMI…
$ area_urban <dbl> 335.97849, 146.18927, 461.03212, 380.96940, 430.43831, 317.32166,…
$ poblacion_ <dbl> 30763, 5879, 73629, 84584, 147298, 174145, 20993, 167, 36521, 302…
$ densidad_u <dbl> 91.562410, 40.214989, 159.704707, 222.023080, 342.204667, 548.796…
$ geometry   <MULTIPOLYGON [°]> MULTIPOLYGON (((-74.03579 4..., MULTIPOLYGON (((-74.…

3.2 Filter

Zones_A <- Zones %>% filter(poblacion_ < 64000)
dim(Zones)
[1] 113   8
dim(Zones_A)
[1] 64  8
plot(st_geometry(Zones_A))

3.3 Mutate

Zones %<>% 
  mutate(Density = poblacion_/area_urban) %>% 
  mutate(Classification = if_else(poblacion_ < 64000, "small", "large"))

3.4 group_by()

Zones %>% group_by(nomb_loc) %>% 
  summarise(Total_UPZ = n())
Simple feature collection with 19 features and 2 fields
Geometry type: GEOMETRY
Dimension:     XY
Bounding box:  xmin: -74.22358 ymin: 4.435827 xmax: -74.00978 ymax: 4.83066
Geodetic CRS:  WGS 84
Zones %>% group_by(nomb_loc) %>% 
  summarise(Total_UPZ = n(),
            TotalPopulation = sum(poblacion_))
Simple feature collection with 19 features and 3 fields
Geometry type: GEOMETRY
Dimension:     XY
Bounding box:  xmin: -74.22358 ymin: 4.435827 xmax: -74.00978 ymax: 4.83066
Geodetic CRS:  WGS 84

3.5 No geometry

Zones_Data <- Zones %>% st_set_geometry(NULL)
class(Zones_Data)
[1] "data.frame"
Zones_Data
Zones_Data <- Zones %>% st_drop_geometry()
Zones_Data
Zones %>% 
  st_set_geometry(NULL) %>% 
  group_by(nomb_loc) %>% 
  summarise(Total_UPZ = n(),
            TotalPopulation = sum(poblacion_))

4 Spatial Operations

4.1 Union

Bogota <- st_union(Zones)
plot(st_geometry(Bogota))


# st_combine() is not the best way to go
Bogota_Localidades <- Zones %>% 
  group_by(cod_loc) %>% 
  summarise(Total_UPZ = n(),
            TotalPopulation = sum(poblacion_))
plot(st_geometry(Bogota_Localidades))

Bogota_Localidades_Name <- Zones %>% 
  group_by(nomb_loc) %>% 
  summarise(Total_UPZ = n(),
            TotalPopulation = sum(poblacion_))

4.2 Joining

Localidad_SantaFe <- Bogota_Localidades_Name %>% filter(nomb_loc == "SANTA FE")

Localidad_Chapinero <- Bogota_Localidades_Name %>% filter(nomb_loc == "CHAPINERO")


SantaFe_Chapinero <- bind_rows(Localidad_SantaFe, Localidad_Chapinero)
# SantaFe_Chapinero <- rbind(Localidad_SantaFe, Localidad_Chapinero)
plot(st_geometry(SantaFe_Chapinero))

4.3 Centroids

Zones_Centroids <- st_centroid(Zones)
Warning: st_centroid assumes attributes are constant over geometries of x
plot(st_geometry(Zones_Centroids))

plot(st_geometry(Zones))
plot(st_geometry(Zones_Centroids), add = TRUE)

4.4 Points in polygons

plot(st_geometry(Bicycle_ParkingSpots))

plot(st_geometry(Bogota_Localidades))
plot(st_geometry(Bicycle_ParkingSpots), add = TRUE)

st_crs(Bogota_Localidades) == st_crs(Bicycle_ParkingSpots)
[1] FALSE
st_crs(Bogota_Localidades) #EPSG: 4326
Coordinate Reference System:
  User input: WGS 84 
  wkt:
GEOGCRS["WGS 84",
    DATUM["World Geodetic System 1984",
        ELLIPSOID["WGS 84",6378137,298.257223563,
            LENGTHUNIT["metre",1]]],
    PRIMEM["Greenwich",0,
        ANGLEUNIT["degree",0.0174532925199433]],
    CS[ellipsoidal,2],
        AXIS["latitude",north,
            ORDER[1],
            ANGLEUNIT["degree",0.0174532925199433]],
        AXIS["longitude",east,
            ORDER[2],
            ANGLEUNIT["degree",0.0174532925199433]],
    ID["EPSG",4326]]
st_crs(Bicycle_ParkingSpots) # EPSG: 3857
Coordinate Reference System:
  User input: WGS 84 / Pseudo-Mercator 
  wkt:
PROJCRS["WGS 84 / Pseudo-Mercator",
    BASEGEOGCRS["WGS 84",
        DATUM["World Geodetic System 1984",
            ELLIPSOID["WGS 84",6378137,298.257223563,
                LENGTHUNIT["metre",1]]],
        PRIMEM["Greenwich",0,
            ANGLEUNIT["degree",0.0174532925199433]],
        ID["EPSG",4326]],
    CONVERSION["Popular Visualisation Pseudo-Mercator",
        METHOD["Popular Visualisation Pseudo Mercator",
            ID["EPSG",1024]],
        PARAMETER["Latitude of natural origin",0,
            ANGLEUNIT["degree",0.0174532925199433],
            ID["EPSG",8801]],
        PARAMETER["Longitude of natural origin",0,
            ANGLEUNIT["degree",0.0174532925199433],
            ID["EPSG",8802]],
        PARAMETER["False easting",0,
            LENGTHUNIT["metre",1],
            ID["EPSG",8806]],
        PARAMETER["False northing",0,
            LENGTHUNIT["metre",1],
            ID["EPSG",8807]]],
    CS[Cartesian,2],
        AXIS["easting (X)",east,
            ORDER[1],
            LENGTHUNIT["metre",1]],
        AXIS["northing (Y)",north,
            ORDER[2],
            LENGTHUNIT["metre",1]],
    USAGE[
        SCOPE["Web mapping and visualisation."],
        AREA["World between 85.06°S and 85.06°N."],
        BBOX[-85.06,-180,85.06,180]],
    ID["EPSG",3857]]
Bicycle_ParkingSpots <- st_transform(Bicycle_ParkingSpots, 4326)
st_crs(Bogota_Localidades) == st_crs(Bicycle_ParkingSpots)
[1] TRUE
plot(st_geometry(Bogota_Localidades))
plot(st_geometry(Bicycle_ParkingSpots), add = TRUE)

SpotsInLocalities <- st_join(Bicycle_ParkingSpots, Bogota_Localidades)
class(SpotsInLocalities)
[1] "sf"         "data.frame"
SpotsInLocalities
Simple feature collection with 255 features and 14 fields
Geometry type: POINT
Dimension:     XY
Bounding box:  xmin: -74.17982 ymin: 4.549555 xmax: -74.02413 ymax: 4.806854
Geodetic CRS:  WGS 84
First 10 features:
   OBJECTID                            NOMBRE                            HORARIO
1        58 Universidad Central (Sede Centro) L-S 06,00 - 22:30  D 06:00 - 16:00
2        59        Tribunales - Rama Judicial                  L-V 07:00 - 20:00
3        60                       Portal Suba                  L-D 05:00 - 24:00
4        61                   Portal Américas                  L-D 05:00 - 24:00
5        62           Estación Transversal 86                  L-D 05:00 - 24:00
6        63                 Estación Banderas                  L-D 05:00 - 24:00
7        64           Estación Mundo Aventura                  L-D 05:00 - 24:00
8        65                 Estación Marsella                  L-D 05:00 - 24:00
9        66                  Estación Pradera                  L-D 05:00 - 24:00
10       67                    Portal del Sur                  L-D 05:00 - 24:00
          DIRECCION CUPOS    TIPOLOGIA  AÑO         X        Y LOCALIDAD SELLO
1        CL 21 4 40   126      Privado 2017 -74.06866 4.605609         3     1
2       KR 57 43 91    60      Privado 2017 -74.09393 4.646599        13     1
3      Troncal Suba   710 Transmilenio 2018 -74.09431 4.746746        11     1
4  Troncal Americas   785 Transmilenio 2018 -74.17294 4.629054         8     1
5  Troncal Americas    84 Transmilenio 2018 -74.15295 4.635053         8     1
6  Troncal Americas   101 Transmilenio 2018 -74.14555 4.631256         8     1
7  Troncal Americas    32 Transmilenio 2018 -74.13522 4.630140         8     1
8  Troncal Americas    32 Transmilenio 2018 -74.12860 4.629488         8     1
9  Troncal Americas    32 Transmilenio    0 -74.11969 4.628609        16     1
10  Troncal NQS Sur   409 Transmilenio    0 -74.16935 4.596727         7     1
   cod_loc Total_UPZ TotalPopulation                   geometry
1        3         5          103544 POINT (-74.06866 4.605609)
2       13         6          143891 POINT (-74.09393 4.646599)
3       11        12         1018450 POINT (-74.09431 4.746746)
4        8        12          997693 POINT (-74.17294 4.629054)
5        8        12          997693 POINT (-74.15295 4.635053)
6        8        12          997693 POINT (-74.14555 4.631256)
7        8        12          997693  POINT (-74.13522 4.63014)
8        8        12          997693  POINT (-74.1286 4.629488)
9       16         5          258368 POINT (-74.11969 4.628609)
10       7         5          554389 POINT (-74.16935 4.596727)
names(Bicycle_ParkingSpots)
 [1] "OBJECTID"  "NOMBRE"    "HORARIO"   "DIRECCION" "CUPOS"     "TIPOLOGIA"
 [7] "AÑO"       "X"         "Y"         "LOCALIDAD" "SELLO"     "geometry" 
names(SpotsInLocalities)
 [1] "OBJECTID"        "NOMBRE"          "HORARIO"         "DIRECCION"      
 [5] "CUPOS"           "TIPOLOGIA"       "AÑO"             "X"              
 [9] "Y"               "LOCALIDAD"       "SELLO"           "cod_loc"        
[13] "Total_UPZ"       "TotalPopulation" "geometry"       
SpotsInLocalities %>% 
  st_drop_geometry() %>% 
  group_by(cod_loc) %>% summarise(ParkingSpots = n())
Bogota_Localidades %<>%
  left_join(
    SpotsInLocalities %>% 
      st_drop_geometry() %>% 
      group_by(cod_loc) %>% summarise(ParkingSpots = n())
  )
Joining, by = "cod_loc"
plot(Bogota_Localidades["ParkingSpots"])

4.5 Buffers (points)

Temp <- st_centroid(SantaFe_Chapinero)
Warning: st_centroid assumes attributes are constant over geometries of x
Temp <- st_transform(Temp, 3116)

st_is_longlat(Temp)
[1] FALSE
Point1 <- Temp %>% slice(1)
Point2 <- Temp %>% slice(2)
Buffer1 <- st_buffer(Point1, 2000)  #The units are un meters
Buffer2 <- st_buffer(Point2, 2000)
plot(st_geometry(st_transform(SantaFe_Chapinero, 3116)))
plot(st_geometry(Buffer1), add = TRUE)
plot(st_geometry(Buffer2), add = TRUE)

Buffers <- bind_rows(Buffer1, Buffer2)
# st_write(Buffers, "Data/Buffers_Tests.shp")

rm(Buffers)

# st_write(Temp, "Data/Centroids_Test.shp")

You probably want to change the EPSG before exporing the files.

Take a look at the buffers in QGIS, what do you think? Here is the answer

4.6 Buffers (lines)

plot(st_geometry(BRT_Trunk))

st_is_longlat(BRT_Trunk)
[1] TRUE
BRT_Trunk_Proj <- st_transform(BRT_Trunk, 3116)
Lines_Buffer <- st_buffer(BRT_Trunk, 600)  #The units are un meters
plot(st_geometry(Lines_Buffer))

Lines_Buffer_Union_1 <- st_combine(Lines_Buffer)
plot(st_geometry(Lines_Buffer_Union_1, singleSide = FALSE))

Lines_Buffer_Union_2 <- st_union(Lines_Buffer)
plot(st_geometry(Lines_Buffer_Union_2, singleSide = FALSE))

4.7 Intersection

TwoPolygons <- Bogota_Localidades %>% 
  st_transform(3116) %>% st_centroid() %>% slice(1,2) %>% st_buffer(6000) %>% 
  st_transform(4326)
Warning: st_centroid assumes attributes are constant over geometries of x
plot(st_geometry(TwoPolygons))

Polygon1 <- TwoPolygons %>% slice(1)
Polygon2 <- TwoPolygons %>% slice(2)
Intersection <- st_intersection(Polygon1,Polygon2)
Warning: attribute variables are assumed to be spatially constant throughout all geometries
#Try st_intersects(Polygon1, Polygon2)
plot(st_geometry(Intersection))

plot(st_geometry(TwoPolygons))
plot(st_geometry(Intersection), col = "darkgreen", add = TRUE)

4.8 Difference

Difference1 <- st_difference(Polygon1, Polygon2)
Warning: attribute variables are assumed to be spatially constant throughout all geometries
plot(st_geometry(TwoPolygons))
plot(st_geometry(Difference1), col = "darkgreen", add = TRUE)

Difference2 <- st_difference(Polygon1, Polygon2)
Warning: attribute variables are assumed to be spatially constant throughout all geometries
plot(st_geometry(TwoPolygons))
plot(st_geometry(Difference2), col = "darkgreen", add = TRUE)

4.9 Distances

Points <- Bogota_Localidades %>% st_centroid()
Warning: st_centroid assumes attributes are constant over geometries of x
st_distance(Points[1,], Points[2,])
Units: [m]
        [,1]
[1,] 8633.54
st_distance(Points[1,], Points %>% slice(1:10))
Units: [m]
     [,1]    [,2]     [,3]     [,4]     [,5]     [,6]     [,7]     [,8]     [,9]
[1,]    0 8633.54 15602.66 21003.34 27513.17 21173.11 21667.24 17458.33 13225.83
        [,10]
[1,] 9430.646

4.10 Areas (of polygons)

Bogota_Localidades
Simple feature collection with 19 features and 4 fields
Geometry type: GEOMETRY
Dimension:     XY
Bounding box:  xmin: -74.22358 ymin: 4.435827 xmax: -74.00978 ymax: 4.83066
Geodetic CRS:  WGS 84
Bogota_Localidades %>% st_area()
Units: [m^2]
 [1] 38206434 13150137  7453510 16142036 32611697 10525532 24023801 38704812 33374184
[10] 35685857 67015948 11938006 14261459  6569395  4953664 17307013  1820002 18571827
[19] 35153482
Bogota_Localidades %>% 
  mutate(AreaTotal = st_area(.))
Simple feature collection with 19 features and 5 fields
Geometry type: GEOMETRY
Dimension:     XY
Bounding box:  xmin: -74.22358 ymin: 4.435827 xmax: -74.00978 ymax: 4.83066
Geodetic CRS:  WGS 84

4.11 Length (of lines)

BRT_Trunk %>% st_length()
Units: [m]
 [1] 11818.012  7299.874  9727.726  9720.276 12107.235  1559.293  6593.208  2284.442
 [9]  8249.366 11498.975 10042.141  8886.344  2755.109
LS0tDQp0aXRsZTogIlNwYXRpYWwgYW5hbHlzaXMgd2l0aCBSIg0KYXV0aG9yOiAiT3JsYW5kbyBTYWJvZ2FsLUNhcmRvbmEiDQpkYXRlOiAiU3VtbWVyIDIwMjMiDQpvdXRwdXQ6IA0KICBodG1sX25vdGVib29rOiANCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDoNCiAgICAgIGNvbGxhcHNlZDogdHJ1ZQ0KICAgICAgc21vb3RoX3Njcm9sbDogZmFsc2UNCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUNCi0tLQ0KDQojIExpYnJhcmllcyBhbmQgZGF0YQ0KDQpgYGB7cn0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShtYWdyaXR0cikNCmxpYnJhcnkoc2YpDQpgYGANCg0KDQojIyBPdXQgdG95IGRhdGENCg0KLSBCb2dvdGE6IFtQb3B1bGF0aW9uIGJ5IFVQWl0oaHR0cHM6Ly9ib2dvdGEtbGFidXJiYW5vLm9wZW5kYXRhc29mdC5jb20vZXhwbG9yZS9kYXRhc2V0L3BvYmxhY2lvbi11cHotYm9nb3RhL21hcC8/bG9jYXRpb249MTAsNC42MzMyNywtNzQuMTE2NjgmYmFzZW1hcD1qYXdnLnN0cmVldHMpDQotIEJvZ290YSAod2l0aCBTRVMpDQotIEJvZ290YTogbWFzcyB0cmFuc2l0IHN0YXRpb25zDQotIEJvZ290YTogbWFzcyB0cmFuc2l0IHRydW5rIGxpbmVzDQotIEJvZ290YTogW0JpY3ljbGUgcGFya2luZyBzcG90c10oaHR0cHM6Ly93d3cuc2ltdXIuZ292LmNvL2RhdG9zLWFiaWVydG9zKSANCg0KRGlzY2xhaW1lcjogVGhlc2UgZmlsZXMgbWlnaHQgYmUgd3Jvbmcgb3Igb3V0ZGF0ZWQuIERvIG5vdCB1c2UgdGhlbSBmb3IgcmVzZWFyY2ggb3Igc2VyaW91cyBwcm9qZWN0cy4gDQoNCg0KIyBXYXJtIHVwDQoNCiMjIFJlYWQgKGltcG9ydCkNCg0KYGBge3J9DQpab25lcyA8LSBzdF9yZWFkKGRzbiA9ICJEYXRhL0JvZ290YV9VUFpfUG9wdWxhdGlvbi5zaHAiKQ0KDQpab25lc19TRVMgPC0gc3RfcmVhZCgiRGF0YS9Cb2dvdGFfTlNFLnNocCIpDQoNCkJSVF9TdGF0aW9ucyA8LSBzdF9yZWFkKCJEYXRhL0JvZ290YV9CUlRfU3RhdGlvbnMuc2hwIikNCg0KQlJUX1RydW5rIDwtIHN0X3JlYWQoIkRhdGEvQm9nb3RhX0JSVF9UcnVuY2tMaW5lcy5zaHAiKQ0KDQpCaWN5Y2xlX1BhcmtpbmdTcG90cyA8LSBzdF9yZWFkKCJEYXRhL0JvZ290YV9CaWN5Y2xlUGFya2luZ1Nwb3RzLnNocCIpDQpgYGANCg0KYGBge3J9DQpjbGFzcyhab25lcykNCmNsYXNzKFpvbmVzX1NFUykNCmNsYXNzKEJSVF9TdGF0aW9ucykNCmNsYXNzKEJSVF9UcnVuaykNCmNsYXNzKEJpY3ljbGVfUGFya2luZ1Nwb3RzKQ0KYGBgDQpgYGB7cn0NCmRpbShab25lcykNCmRpbShab25lc19TRVMpDQpkaW0oQlJUX1N0YXRpb25zKQ0KZGltKEJSVF9UcnVuaykNCmRpbShCaWN5Y2xlX1BhcmtpbmdTcG90cykNCmBgYA0KDQoNCiMjIFdyaXRlIChleHBvcnQpDQoNCmBgYHtyfQ0KIyBzdF93cml0ZShvYmogPSBCUlRfU3RhdGlvbnMsIGRzbiA9ICJDaG9vc2VUaGVOYW1lLnNocCIpDQpgYGANCg0KIyMgQmFzaWMgbWFwcGluZw0KDQpUaGUgZm9sbG93aW5nIGlzIGEgbWVzcyENCg0KYGBge3J9DQpwbG90KFpvbmVzKQ0KYGBgDQoNCklmIHlvdSB3YW50IHRvIHNlZSB0aGUgZ2VvbWV0cnksIHlvdSBkbyB0aGlzOg0KDQpgYGB7cn0NCnBsb3Qoc3RfZ2VvbWV0cnkoWm9uZXMpKQ0KYGBgDQoNCkZvciBhIGNob3JvcGxldCBtYXA6IA0KDQpgYGB7cn0NCnBsb3QoWm9uZXNbInBvYmxhY2lvbl8iXSkNCmBgYA0KDQpUaGUgaW50dWl0aXZlIHdheSBmYWlscyBoZXJlOg0KDQpgYGB7cn0NCnBsb3QoWm9uZXMkcG9ibGFjaW9uXykNCmBgYA0KDQpCdXQgdGhlIHRpZHkgaW50dWl0aXZlIHdheSBkb2VzIG5vdA0KDQpgYGB7cn0NCnBsb3QoWm9uZXMgJT4lIHNlbGVjdChwb2JsYWNpb25fKSkNCmBgYA0KDQoNCllvdSBjYW4gaW1wcm92ZSB5b3VyIG1hcCBpbiBtYW55IHdheXMuICpOZXZlcmhlbGVzcywqIHdlIHdpbGwgdXNlIHRoZSAqdG1hcCogbGlicmFyeSBmb3IgYWxsIHRoZSBtYXAtbWFraW5nIGFuZCB0byBwcm9kdWNlIGltYWdlcy4gQW5vdGhlciBhbHRlcm5hdGl2ZSBpcyB0byB1c2UgKmdncGxvdDIqLiBZb3UgY2FuIGFsc28gcHJvZHVjZSBpbnRlcmFjdGl2ZSBtYXBzIHdpdGggdGhlICpsZWFmbGV0KiBsaWJyYXJ5Lg0KDQoNCg0KIyMgQ1NSDQoNClBsYW5ldCBlYXJ0aCBpcyBub3QgYSBmbGF0IHN1cmZhY2UgYW5kIGlzIG5vdCBwZXJmZWN0bHkgcm91bmQuIE5ldmVydGhlbGVzcywgd2hlbiB5b3UgbWFrZSBtYXBzIHlvdSBoYXZlIHRvIHB1dCBhIGNlcnRhaW4gZ2VvZ3JhcGhpYyBhcmVhIGluIHR3byBkaW1lbnNpb25zLiBUaGlzIGdldHMgZXZlbiBtb3JlIGNvbXBsaWNhdGVkIGlmICB5b3UgY29uc2lkZXIgbW91bnRhaW5zIGFuZCBkaWZmZXJlbnQgc2VhIGxldmVscy4gPGJyLz4NCg0KUHJvamVjdGlvbnMgYXJlIHRoZSB3YXkgd2UgdHJhbnNmb3JtIHRoZSBzaGFwZSBvZiB0aGUgZWFydGggKG9yIGFuIGFyZWEpIHRvIG9ubHkgdHdvIGRpbWVuc2lvbnMuIFRoZXJlIGFyZSBtYW55IHByb2plY3Rpb25zIGFuZCBhbGwgaGF2ZSBzb21lIGxldmVsIG9mIGRpc3RvcnRpb24uIFlvdSBjYW4gc2VlIHRoZSBbc2VjdGlvbiAyLjRdKGh0dHBzOi8vZ2VvY29tcHIucm9iaW5sb3ZlbGFjZS5uZXQvc3BhdGlhbC1jbGFzcy5odG1sI2Nycy1pbnRybykgZnJvbSB0aGUgKipHZW9jb21wdXRhdGlvbiBXaXRoIFIqKiBib29rIGZvciBhIGNsZWFyIGludHJvZHVjdGlvbiB0byB0aGUgdG9waWMuIFNvIGZhciwgeW91IGhhdmUgdG8ga25vdyB0aGF0IGV2ZXJ5ICoqc2YqKiBvYmplY3QgaGFzIGEgQ29vcmRpbmF0ZSBSZWZlcmVuc2UgU3lzdGVtIChDUlMpIHRoYXQgY2FuIGJlIGNvbmZpZ3VyZWQgdXNpbmcgKipFUFNHKiogY29kZXMuIFRoZXJlIGFyZSAqKmdlb2dyYXBoaWMqKiBwcm9qZWN0aW9ucyB3aGVyZSB0aGUgbm90aW9uIG9mIGRpc3RhbmNlIGlzIG5vdCByZWxhdGVkIHRvIGEgcmVndWxhciB1bmRlcnN0YW5kaW5nIG9mIGRpc3RhbmNlIChpdCBpcyBub3QgaW4gbWV0ZXIgb3IgaWNuaGVzKS4gSW4gKipQcm9qZWN0ZWQqKiBwcm9qZWN0aW9ucyBvbiB0aGUgb3RoZXIgaGFuZCwgdGhlIGRpc3RhbmNlIGlzIGluIG1ldGVycy4gIDxici8+DQoNCkluIHRoZSBwYXN0LCBtYW55ICpzZiogZnVuY3Rpb25zIGRpZCBub3QgZGlzdGluZ3Vpc2ggKipnZW9ncmFwaGljKiogcHJvamVjdGlvbnMgZnJvbSAqKnByb2plY3RlZCoqIHdoZW4gcnVubmluZyBzb21lIG9wZXJhdGlvbiBzdXMgYXMgYnVmZmVycyBvciBkaXN0YW5jZXMgY2FsY3VsYXRpb25zLiBJIHVuZGVyc3RhbmQgdGhpcyBpcyBub3QgbG9uZ2VyIGFuIGlzc2UsIGJ1dCBJIHJlY29tbWVuZCB5b3UgdG8gZG91YmxlY2hlY2sgZXZlcnkgb3V0cHV0LiAgPGJyLz4NCg0KSG93IHRvIGdldCB0aGUgQ1JTPw0KDQpgYGB7cn0NCnN0X2Nycyh4ID0gWm9uZXMpDQpgYGANCg0KYGBge3J9DQpzdF9jcnMoQlJUX1N0YXRpb25zKQ0KYGBgDQoNCmBgYHtyfQ0Kc3RfY3JzKFpvbmVzKSA9PSBzdF9jcnMoQlJUX1N0YXRpb25zKQ0KYGBgDQpUaGUgZnVuY3Rpb24gKipzdF90cmFuc2Zvcm0oKSoqIGNoYW5nZXMgdGhlIENSUyAocmUtcHJvamVjdCkgb2YgYW55IHNmIG9iamVjdC4gT24gdGhlIG90aGVyIGhhbmQsIHRoZSAqKnN0X3NldF9jcnMoKSoqIGZ1bmN0aW9uICphc3NpZ25zKiAoc2V0cykgYSBDUlMsIGJ1dCAqaXMgbm90IHByb2plY3RpbmcqIHRoZSBvYmplY3QuIFRoZSAqKnN0X3NldF9jcnMoKSoqIGZ1bmN0aW9uIGlzIHVzZWZ1bCwgZm9yIGV4YW1wbGUsIHdoZW4geW91IG1hbnVhbGx5IGNvbGxlY3QgZGF0YSBvciB3aGVuIHlvdSBoYXZlIGEgZmlsZSB3aXRoIG1pc3NpbmcgQ1JTDQoNCmBgYHtyfQ0KWm9uZXNfUHJvamVjdGVkIDwtIHN0X3RyYW5zZm9ybShab25lcywgMzExNikNCmBgYA0KDQpgYGB7cn0NCnN0X2Nycyhab25lcykgPT0gc3RfY3JzKFpvbmVzX1Byb2plY3RlZCkgDQpgYGANCg0KYGBge3J9DQpzdF9pc19sb25nbGF0KFpvbmVzKQ0Kc3RfaXNfbG9uZ2xhdChab25lc19Qcm9qZWN0ZWQpDQpgYGANCg0KDQojIEJhc2ljIHRpZHkgb3BlcmF0aW9ucw0KDQpBbiAqc2YqIG9iamVjdHMgd29ya3MganVzdCBsaWtlIGFzICpkYXRhLmZyYW1lKiBvciAqdGliYmxlKi4gSXQgaXMgYSB0YWJsZSAoZGF0YWJlKSB3aXRoIG9uZSBhZGRpdGlvbmFsIGNvbHVtbiBmb3IgdGhlIGdlb21ldHJ5IHRoYXQgaXMgImlnbm9yZWQiIGZvciBjZXJ0YWluIG9wZXJhdGlvbnMuIA0KDQojIyBzdW1tYXJ5DQoNCmBgYHtyfQ0Kc3VtbWFyeShab25lcyRwb2JsYWNpb25fKQ0KYGBgDQoNCmBgYHtyfQ0KZ2xpbXBzZShab25lcykNCmBgYA0KDQoNCiMjIEZpbHRlcg0KDQpgYGB7cn0NClpvbmVzX0EgPC0gWm9uZXMgJT4lIGZpbHRlcihwb2JsYWNpb25fIDwgNjQwMDApDQpkaW0oWm9uZXMpDQpkaW0oWm9uZXNfQSkNCmBgYA0KDQpgYGB7cn0NCnBsb3Qoc3RfZ2VvbWV0cnkoWm9uZXNfQSkpDQpgYGANCg0KIyMgTXV0YXRlDQoNCmBgYHtyfQ0KWm9uZXMgJTw+JSANCiAgbXV0YXRlKERlbnNpdHkgPSBwb2JsYWNpb25fL2FyZWFfdXJiYW4pICU+JSANCiAgbXV0YXRlKENsYXNzaWZpY2F0aW9uID0gaWZfZWxzZShwb2JsYWNpb25fIDwgNjQwMDAsICJzbWFsbCIsICJsYXJnZSIpKQ0KYGBgDQoNCg0KIyMgZ3JvdXBfYnkoKQ0KDQpgYGB7cn0NClpvbmVzICU+JSBncm91cF9ieShub21iX2xvYykgJT4lIA0KICBzdW1tYXJpc2UoVG90YWxfVVBaID0gbigpKQ0KYGBgDQoNCmBgYHtyfQ0KWm9uZXMgJT4lIGdyb3VwX2J5KG5vbWJfbG9jKSAlPiUgDQogIHN1bW1hcmlzZShUb3RhbF9VUFogPSBuKCksDQogICAgICAgICAgICBUb3RhbFBvcHVsYXRpb24gPSBzdW0ocG9ibGFjaW9uXykpDQpgYGANCg0KDQojIyBObyBnZW9tZXRyeQ0KDQpgYGB7cn0NClpvbmVzX0RhdGEgPC0gWm9uZXMgJT4lIHN0X3NldF9nZW9tZXRyeShOVUxMKQ0KYGBgDQoNCmBgYHtyfQ0KY2xhc3MoWm9uZXNfRGF0YSkNClpvbmVzX0RhdGENCmBgYA0KDQpgYGB7cn0NClpvbmVzX0RhdGEgPC0gWm9uZXMgJT4lIHN0X2Ryb3BfZ2VvbWV0cnkoKQ0KWm9uZXNfRGF0YQ0KYGBgDQoNCmBgYHtyfQ0KWm9uZXMgJT4lIA0KICBzdF9zZXRfZ2VvbWV0cnkoTlVMTCkgJT4lIA0KICBncm91cF9ieShub21iX2xvYykgJT4lIA0KICBzdW1tYXJpc2UoVG90YWxfVVBaID0gbigpLA0KICAgICAgICAgICAgVG90YWxQb3B1bGF0aW9uID0gc3VtKHBvYmxhY2lvbl8pKQ0KYGBgDQoNCiMgU3BhdGlhbCBPcGVyYXRpb25zDQoNCiMjIFVuaW9uDQoNCmBgYHtyfQ0KQm9nb3RhIDwtIHN0X3VuaW9uKFpvbmVzKQ0KcGxvdChzdF9nZW9tZXRyeShCb2dvdGEpKQ0KDQojIHN0X2NvbWJpbmUoKSBpcyBub3QgdGhlIGJlc3Qgd2F5IHRvIGdvDQpgYGANCg0KYGBge3J9DQpCb2dvdGFfTG9jYWxpZGFkZXMgPC0gWm9uZXMgJT4lIA0KICBncm91cF9ieShjb2RfbG9jKSAlPiUgDQogIHN1bW1hcmlzZShUb3RhbF9VUFogPSBuKCksDQogICAgICAgICAgICBUb3RhbFBvcHVsYXRpb24gPSBzdW0ocG9ibGFjaW9uXykpDQpgYGANCg0KYGBge3J9DQpwbG90KHN0X2dlb21ldHJ5KEJvZ290YV9Mb2NhbGlkYWRlcykpDQpgYGANCg0KYGBge3J9DQpCb2dvdGFfTG9jYWxpZGFkZXNfTmFtZSA8LSBab25lcyAlPiUgDQogIGdyb3VwX2J5KG5vbWJfbG9jKSAlPiUgDQogIHN1bW1hcmlzZShUb3RhbF9VUFogPSBuKCksDQogICAgICAgICAgICBUb3RhbFBvcHVsYXRpb24gPSBzdW0ocG9ibGFjaW9uXykpDQpgYGANCg0KIyMgSm9pbmluZyANCg0KYGBge3J9DQpMb2NhbGlkYWRfU2FudGFGZSA8LSBCb2dvdGFfTG9jYWxpZGFkZXNfTmFtZSAlPiUgZmlsdGVyKG5vbWJfbG9jID09ICJTQU5UQSBGRSIpDQoNCkxvY2FsaWRhZF9DaGFwaW5lcm8gPC0gQm9nb3RhX0xvY2FsaWRhZGVzX05hbWUgJT4lIGZpbHRlcihub21iX2xvYyA9PSAiQ0hBUElORVJPIikNCg0KDQpTYW50YUZlX0NoYXBpbmVybyA8LSBiaW5kX3Jvd3MoTG9jYWxpZGFkX1NhbnRhRmUsIExvY2FsaWRhZF9DaGFwaW5lcm8pDQojIFNhbnRhRmVfQ2hhcGluZXJvIDwtIHJiaW5kKExvY2FsaWRhZF9TYW50YUZlLCBMb2NhbGlkYWRfQ2hhcGluZXJvKQ0KYGBgDQoNCmBgYHtyfQ0KcGxvdChzdF9nZW9tZXRyeShTYW50YUZlX0NoYXBpbmVybykpDQpgYGANCg0KIyMgQ2VudHJvaWRzDQoNCmBgYHtyfQ0KWm9uZXNfQ2VudHJvaWRzIDwtIHN0X2NlbnRyb2lkKFpvbmVzKQ0KcGxvdChzdF9nZW9tZXRyeShab25lc19DZW50cm9pZHMpKQ0KYGBgDQoNCmBgYHtyfQ0KcGxvdChzdF9nZW9tZXRyeShab25lcykpDQpwbG90KHN0X2dlb21ldHJ5KFpvbmVzX0NlbnRyb2lkcyksIGFkZCA9IFRSVUUpDQpgYGANCg0KIyMgUG9pbnRzIGluIHBvbHlnb25zDQoNCmBgYHtyfQ0KcGxvdChzdF9nZW9tZXRyeShCaWN5Y2xlX1BhcmtpbmdTcG90cykpDQpgYGANCg0KYGBge3J9DQpwbG90KHN0X2dlb21ldHJ5KEJvZ290YV9Mb2NhbGlkYWRlcykpDQpwbG90KHN0X2dlb21ldHJ5KEJpY3ljbGVfUGFya2luZ1Nwb3RzKSwgYWRkID0gVFJVRSkNCmBgYA0KDQpgYGB7cn0NCnN0X2NycyhCb2dvdGFfTG9jYWxpZGFkZXMpID09IHN0X2NycyhCaWN5Y2xlX1BhcmtpbmdTcG90cykNCmBgYA0KDQpgYGB7cn0NCnN0X2NycyhCb2dvdGFfTG9jYWxpZGFkZXMpICNFUFNHOiA0MzI2DQpzdF9jcnMoQmljeWNsZV9QYXJraW5nU3BvdHMpICMgRVBTRzogMzg1Nw0KYGBgDQoNCmBgYHtyfQ0KQmljeWNsZV9QYXJraW5nU3BvdHMgPC0gc3RfdHJhbnNmb3JtKEJpY3ljbGVfUGFya2luZ1Nwb3RzLCA0MzI2KQ0Kc3RfY3JzKEJvZ290YV9Mb2NhbGlkYWRlcykgPT0gc3RfY3JzKEJpY3ljbGVfUGFya2luZ1Nwb3RzKQ0KYGBgDQoNCmBgYHtyfQ0KcGxvdChzdF9nZW9tZXRyeShCb2dvdGFfTG9jYWxpZGFkZXMpKQ0KcGxvdChzdF9nZW9tZXRyeShCaWN5Y2xlX1BhcmtpbmdTcG90cyksIGFkZCA9IFRSVUUpDQpgYGANCg0KYGBge3J9DQpTcG90c0luTG9jYWxpdGllcyA8LSBzdF9qb2luKEJpY3ljbGVfUGFya2luZ1Nwb3RzLCBCb2dvdGFfTG9jYWxpZGFkZXMpDQpgYGANCg0KYGBge3J9DQpjbGFzcyhTcG90c0luTG9jYWxpdGllcykNClNwb3RzSW5Mb2NhbGl0aWVzDQpgYGANCg0KYGBge3J9DQpuYW1lcyhCaWN5Y2xlX1BhcmtpbmdTcG90cykNCm5hbWVzKFNwb3RzSW5Mb2NhbGl0aWVzKQ0KYGBgDQoNCmBgYHtyfQ0KU3BvdHNJbkxvY2FsaXRpZXMgJT4lIA0KICBzdF9kcm9wX2dlb21ldHJ5KCkgJT4lIA0KICBncm91cF9ieShjb2RfbG9jKSAlPiUgc3VtbWFyaXNlKFBhcmtpbmdTcG90cyA9IG4oKSkNCmBgYA0KDQpgYGB7cn0NCkJvZ290YV9Mb2NhbGlkYWRlcyAlPD4lDQogIGxlZnRfam9pbigNCiAgICBTcG90c0luTG9jYWxpdGllcyAlPiUgDQogICAgICBzdF9kcm9wX2dlb21ldHJ5KCkgJT4lIA0KICAgICAgZ3JvdXBfYnkoY29kX2xvYykgJT4lIHN1bW1hcmlzZShQYXJraW5nU3BvdHMgPSBuKCkpDQogICkNCmBgYA0KDQpgYGB7cn0NCnBsb3QoQm9nb3RhX0xvY2FsaWRhZGVzWyJQYXJraW5nU3BvdHMiXSkNCmBgYA0KDQoNCiMjIEJ1ZmZlcnMgKHBvaW50cykNCg0KYGBge3J9DQpUZW1wIDwtIHN0X2NlbnRyb2lkKFNhbnRhRmVfQ2hhcGluZXJvKQ0KVGVtcCA8LSBzdF90cmFuc2Zvcm0oVGVtcCwgMzExNikNCg0Kc3RfaXNfbG9uZ2xhdChUZW1wKQ0KDQpQb2ludDEgPC0gVGVtcCAlPiUgc2xpY2UoMSkNClBvaW50MiA8LSBUZW1wICU+JSBzbGljZSgyKQ0KYGBgDQoNCmBgYHtyfQ0KQnVmZmVyMSA8LSBzdF9idWZmZXIoUG9pbnQxLCAyMDAwKSAgI1RoZSB1bml0cyBhcmUgdW4gbWV0ZXJzDQpCdWZmZXIyIDwtIHN0X2J1ZmZlcihQb2ludDIsIDIwMDApDQpgYGANCg0KDQpgYGB7cn0NCnBsb3Qoc3RfZ2VvbWV0cnkoc3RfdHJhbnNmb3JtKFNhbnRhRmVfQ2hhcGluZXJvLCAzMTE2KSkpDQpwbG90KHN0X2dlb21ldHJ5KEJ1ZmZlcjEpLCBhZGQgPSBUUlVFKQ0KcGxvdChzdF9nZW9tZXRyeShCdWZmZXIyKSwgYWRkID0gVFJVRSkNCmBgYA0KDQpgYGB7cn0NCkJ1ZmZlcnMgPC0gYmluZF9yb3dzKEJ1ZmZlcjEsIEJ1ZmZlcjIpDQojIHN0X3dyaXRlKEJ1ZmZlcnMsICJEYXRhL0J1ZmZlcnNfVGVzdHMuc2hwIikNCg0Kcm0oQnVmZmVycykNCg0KIyBzdF93cml0ZShUZW1wLCAiRGF0YS9DZW50cm9pZHNfVGVzdC5zaHAiKQ0KYGBgDQoNCllvdSBwcm9iYWJseSB3YW50IHRvIGNoYW5nZSB0aGUgRVBTRyBiZWZvcmUgZXhwb3JpbmcgdGhlIGZpbGVzLiANCg0KVGFrZSBhIGxvb2sgYXQgdGhlIGJ1ZmZlcnMgaW4gUUdJUywgd2hhdCBkbyB5b3UgdGhpbms/DQpbSGVyZSBpcyB0aGUgYW5zd2VyXShodHRwczovL3IuZ2VvY29tcHgub3JnL2dlb21ldHJ5LW9wZXJhdGlvbnMuaHRtbCNidWZmZXJzKQ0KDQoNCiMjIEJ1ZmZlcnMgKGxpbmVzKQ0KDQpgYGB7cn0NCnBsb3Qoc3RfZ2VvbWV0cnkoQlJUX1RydW5rKSkNCmBgYA0KDQpgYGB7cn0NCnN0X2lzX2xvbmdsYXQoQlJUX1RydW5rKQ0KYGBgDQoNCmBgYHtyfQ0KQlJUX1RydW5rX1Byb2ogPC0gc3RfdHJhbnNmb3JtKEJSVF9UcnVuaywgMzExNikNCmBgYA0KDQpgYGB7cn0NCkxpbmVzX0J1ZmZlciA8LSBzdF9idWZmZXIoQlJUX1RydW5rLCA2MDApICAjVGhlIHVuaXRzIGFyZSB1biBtZXRlcnMNCmBgYA0KDQpgYGB7cn0NCnBsb3Qoc3RfZ2VvbWV0cnkoTGluZXNfQnVmZmVyKSkNCmBgYA0KDQpgYGB7cn0NCkxpbmVzX0J1ZmZlcl9Vbmlvbl8xIDwtIHN0X2NvbWJpbmUoTGluZXNfQnVmZmVyKQ0KcGxvdChzdF9nZW9tZXRyeShMaW5lc19CdWZmZXJfVW5pb25fMSwgc2luZ2xlU2lkZSA9IEZBTFNFKSkNCmBgYA0KDQpgYGB7cn0NCkxpbmVzX0J1ZmZlcl9Vbmlvbl8yIDwtIHN0X3VuaW9uKExpbmVzX0J1ZmZlcikNCnBsb3Qoc3RfZ2VvbWV0cnkoTGluZXNfQnVmZmVyX1VuaW9uXzIsIHNpbmdsZVNpZGUgPSBGQUxTRSkpDQpgYGANCg0KIyMgSW50ZXJzZWN0aW9uDQoNCg0KYGBge3J9DQpUd29Qb2x5Z29ucyA8LSBCb2dvdGFfTG9jYWxpZGFkZXMgJT4lIA0KICBzdF90cmFuc2Zvcm0oMzExNikgJT4lIHN0X2NlbnRyb2lkKCkgJT4lIHNsaWNlKDEsMikgJT4lIHN0X2J1ZmZlcig2MDAwKSAlPiUgDQogIHN0X3RyYW5zZm9ybSg0MzI2KQ0KDQpwbG90KHN0X2dlb21ldHJ5KFR3b1BvbHlnb25zKSkNCmBgYA0KDQpgYGB7cn0NClBvbHlnb24xIDwtIFR3b1BvbHlnb25zICU+JSBzbGljZSgxKQ0KUG9seWdvbjIgPC0gVHdvUG9seWdvbnMgJT4lIHNsaWNlKDIpDQpgYGANCg0KYGBge3J9DQpJbnRlcnNlY3Rpb24gPC0gc3RfaW50ZXJzZWN0aW9uKFBvbHlnb24xLFBvbHlnb24yKQ0KI1RyeSBzdF9pbnRlcnNlY3RzKFBvbHlnb24xLCBQb2x5Z29uMikNCmBgYA0KYGBge3J9DQpwbG90KHN0X2dlb21ldHJ5KEludGVyc2VjdGlvbikpDQpgYGANCg0KYGBge3J9DQpwbG90KHN0X2dlb21ldHJ5KFR3b1BvbHlnb25zKSkNCnBsb3Qoc3RfZ2VvbWV0cnkoSW50ZXJzZWN0aW9uKSwgY29sID0gImRhcmtncmVlbiIsIGFkZCA9IFRSVUUpDQpgYGANCg0KDQojIyBEaWZmZXJlbmNlDQoNCmBgYHtyfQ0KRGlmZmVyZW5jZTEgPC0gc3RfZGlmZmVyZW5jZShQb2x5Z29uMSwgUG9seWdvbjIpDQpgYGANCg0KYGBge3J9DQpwbG90KHN0X2dlb21ldHJ5KFR3b1BvbHlnb25zKSkNCnBsb3Qoc3RfZ2VvbWV0cnkoRGlmZmVyZW5jZTEpLCBjb2wgPSAiZGFya2dyZWVuIiwgYWRkID0gVFJVRSkNCmBgYA0KDQpgYGB7cn0NCkRpZmZlcmVuY2UyIDwtIHN0X2RpZmZlcmVuY2UoUG9seWdvbjEsIFBvbHlnb24yKQ0KYGBgDQoNCmBgYHtyfQ0KcGxvdChzdF9nZW9tZXRyeShUd29Qb2x5Z29ucykpDQpwbG90KHN0X2dlb21ldHJ5KERpZmZlcmVuY2UyKSwgY29sID0gImRhcmtncmVlbiIsIGFkZCA9IFRSVUUpDQpgYGANCg0KIyMgRGlzdGFuY2VzDQoNCmBgYHtyfQ0KUG9pbnRzIDwtIEJvZ290YV9Mb2NhbGlkYWRlcyAlPiUgc3RfY2VudHJvaWQoKQ0KYGBgDQpgYGB7cn0NCnN0X2Rpc3RhbmNlKFBvaW50c1sxLF0sIFBvaW50c1syLF0pDQpgYGANCmBgYHtyfQ0Kc3RfZGlzdGFuY2UoUG9pbnRzWzEsXSwgUG9pbnRzICU+JSBzbGljZSgxOjEwKSkNCmBgYA0KDQoNCiMjIEFyZWFzIChvZiBwb2x5Z29ucykNCg0KYGBge3J9DQpCb2dvdGFfTG9jYWxpZGFkZXMNCmBgYA0KDQpgYGB7cn0NCkJvZ290YV9Mb2NhbGlkYWRlcyAlPiUgc3RfYXJlYSgpDQpgYGANCmBgYHtyfQ0KQm9nb3RhX0xvY2FsaWRhZGVzICU+JSANCiAgbXV0YXRlKEFyZWFUb3RhbCA9IHN0X2FyZWEoLikpDQpgYGANCg0KDQojIyBMZW5ndGggKG9mIGxpbmVzKQ0KDQpgYGB7cn0NCkJSVF9UcnVuayAlPiUgc3RfbGVuZ3RoKCkNCmBgYA0KDQoNCiMgUmVjb21tZW5kZWQgcmVhZGluZ3MNCg0KLSBbU3BhdGlhbCBNYW5pcHVsYXRpb24gd2l0aCBzZjogQ0hFQVQgU0hFRVRdKGh0dHBzOi8vZ2l0aHViLmNvbS9yc3R1ZGlvL2NoZWF0c2hlZXRzL2Jsb2IvbWFpbi9zZi5wZGYpDQotIENoYXB0ZXJzIDIgdG8gNyBpbiB0aGUgW0dlb2NvbXB1dGF0aW9uIHdpdGggUiBib29rXShodHRwczovL2Jvb2tkb3duLm9yZy9yb2JpbmxvdmVsYWNlL2dlb2NvbXByLykNCi0gQ2hhcHRlcnMgMSB0byA2IGluIHRuZSBbU3BhdGlhbCBEYXRhIFNjaWVuY2UgYm9va10oaHR0cHM6Ly9yLXNwYXRpYWwub3JnL2Jvb2svKQ0KLSBbQWxsIGFydGljbGVzIGluIHRoZSBzZiB3ZWJzaXRlXShodHRwczovL3Itc3BhdGlhbC5naXRodWIuaW8vc2YvaW5kZXguaHRtbCkNCg0K